summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortomoya ishida <tomoyapenguin@gmail.com>2024-11-25 00:45:13 +0900
committergit <svn-admin@ruby-lang.org>2024-11-24 15:45:18 +0000
commitbf47b1b5230759dd16fc0930753d37a3eb6f30e2 (patch)
tree38a2dbd129d95c5739b2384833ff0719845d81b2
parentc6ca33995592ebc5abd7ddcff244c3ea6ae29c47 (diff)
[ruby/reline] Fix completion quote, preposing and target calculation
bug (https://github.com/ruby/reline/pull/763) https://github.com/ruby/reline/commit/d3ba7216eb
-rw-r--r--lib/reline/line_editor.rb79
-rw-r--r--test/reline/test_key_actor_emacs.rb22
-rw-r--r--test/reline/test_line_editor.rb60
3 files changed, 82 insertions, 79 deletions
diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb
index c8a7fc9fea..72e756803c 100644
--- a/lib/reline/line_editor.rb
+++ b/lib/reline/line_editor.rb
@@ -1225,70 +1225,35 @@ class Reline::LineEditor
end
def retrieve_completion_block(set_completion_quote_character = false)
- if Reline.completer_word_break_characters.empty?
- word_break_regexp = nil
- else
- word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
- end
- if Reline.completer_quote_characters.empty?
- quote_characters_regexp = nil
- else
- quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
- end
- before = current_line.byteslice(0, @byte_pointer)
- rest = nil
- break_pointer = nil
+ quote_characters = Reline.completer_quote_characters
+ before = current_line.byteslice(0, @byte_pointer).grapheme_clusters
quote = nil
- closing_quote = nil
- escaped_quote = nil
- i = 0
- while i < @byte_pointer do
- slice = current_line.byteslice(i, @byte_pointer - i)
- unless slice.valid_encoding?
- i += 1
- next
- end
- if quote and slice.start_with?(closing_quote)
- quote = nil
- i += 1
- rest = nil
- elsif quote and slice.start_with?(escaped_quote)
- # skip
- i += 2
- elsif quote_characters_regexp and slice =~ quote_characters_regexp # find new "
- rest = $'
- quote = $&
- closing_quote = /(?!\\)#{Regexp.escape(quote)}/
- escaped_quote = /\\#{Regexp.escape(quote)}/
- i += 1
- break_pointer = i - 1
- elsif word_break_regexp and not quote and slice =~ word_break_regexp
- rest = $'
- i += 1
- before = current_line.byteslice(i, @byte_pointer - i)
- break_pointer = i
- else
- i += 1
+ unless quote_characters.empty?
+ escaped = false
+ before.each do |c|
+ if escaped
+ escaped = false
+ next
+ elsif c == '\\'
+ escaped = true
+ elsif quote
+ quote = nil if c == quote
+ elsif quote_characters.include?(c)
+ quote = c
+ end
end
end
+ word_break_characters = quote_characters + Reline.completer_word_break_characters
+ break_index = before.rindex { |c| word_break_characters.include?(c) || quote_characters.include?(c) } || -1
+ preposing = before.take(break_index + 1).join
+ target = before.drop(break_index + 1).join
postposing = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
- if rest
- preposing = current_line.byteslice(0, break_pointer)
- target = rest
+ if target
if set_completion_quote_character and quote
Reline.core.instance_variable_set(:@completion_quote_character, quote)
- if postposing !~ /(?!\\)#{Regexp.escape(quote)}/ # closing quote
- insert_text(quote)
- end
- end
- else
- preposing = ''
- if break_pointer
- preposing = current_line.byteslice(0, break_pointer)
- else
- preposing = ''
+ insert_text(quote) # FIXME: should not be here
+ target += quote
end
- target = before
end
lines = whole_lines
if @line_index > 0
diff --git a/test/reline/test_key_actor_emacs.rb b/test/reline/test_key_actor_emacs.rb
index d07a2ee3c3..fd16e96621 100644
--- a/test/reline/test_key_actor_emacs.rb
+++ b/test/reline/test_key_actor_emacs.rb
@@ -853,28 +853,6 @@ class Reline::KeyActor::EmacsTest < Reline::TestCase
assert_equal(%w{foo_foo foo_bar foo_baz}, @line_editor.instance_variable_get(:@menu_info).list)
end
- def test_completion_with_indent_and_completer_quote_characters
- @line_editor.completion_proc = proc { |word|
- %w{
- "".foo_foo
- "".foo_bar
- "".foo_baz
- "".qux
- }.map { |i|
- i.encode(@encoding)
- }
- }
- input_keys(' "".fo')
- assert_line_around_cursor(' "".fo', '')
- assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
- input_keys("\C-i", false)
- assert_line_around_cursor(' "".foo_', '')
- assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
- input_keys("\C-i", false)
- assert_line_around_cursor(' "".foo_', '')
- assert_equal(%w{"".foo_foo "".foo_bar "".foo_baz}, @line_editor.instance_variable_get(:@menu_info).list)
- end
-
def test_completion_with_perfect_match
@line_editor.completion_proc = proc { |word|
%w{
diff --git a/test/reline/test_line_editor.rb b/test/reline/test_line_editor.rb
index 18fc2296ff..cc4dc6d29a 100644
--- a/test/reline/test_line_editor.rb
+++ b/test/reline/test_line_editor.rb
@@ -3,6 +3,66 @@ require 'reline/line_editor'
require 'stringio'
class Reline::LineEditor
+
+ class CompletionBlockTest < Reline::TestCase
+ def setup
+ @original_quote_characters = Reline.completer_quote_characters
+ @original_word_break_characters = Reline.completer_word_break_characters
+ @line_editor = Reline::LineEditor.new(nil, Encoding::UTF_8)
+ end
+
+ def retrieve_completion_block(lines, line_index, byte_pointer)
+ @line_editor.instance_variable_set(:@buffer_of_lines, lines)
+ @line_editor.instance_variable_set(:@line_index, line_index)
+ @line_editor.instance_variable_set(:@byte_pointer, byte_pointer)
+ @line_editor.retrieve_completion_block(false)
+ end
+
+ def retrieve_completion_quote(line)
+ retrieve_completion_block([line], 0, line.bytesize)
+ _, target = @line_editor.retrieve_completion_block(false)
+ _, target2 = @line_editor.retrieve_completion_block(true)
+ # This is a hack to get the quoted character.
+ # retrieve_completion_block should be refactored to return the quoted character.
+ target2.chars.last if target2 != target
+ end
+
+ def teardown
+ Reline.completer_quote_characters = @original_quote_characters
+ Reline.completer_word_break_characters = @original_word_break_characters
+ end
+
+ def test_retrieve_completion_block
+ Reline.completer_word_break_characters = ' ([{'
+ Reline.completer_quote_characters = ''
+ assert_equal(['', '', 'foo'], retrieve_completion_block(['foo'], 0, 0))
+ assert_equal(['', 'f', 'oo'], retrieve_completion_block(['foo'], 0, 1))
+ assert_equal(['foo ', 'ba', 'r baz'], retrieve_completion_block(['foo bar baz'], 0, 6))
+ assert_equal(['foo([', 'b', 'ar])baz'], retrieve_completion_block(['foo([bar])baz'], 0, 6))
+ assert_equal(['foo([{', '', '}])baz'], retrieve_completion_block(['foo([{}])baz'], 0, 6))
+ assert_equal(["abc\nfoo ", 'ba', "r baz\ndef"], retrieve_completion_block(['abc', 'foo bar baz', 'def'], 1, 6))
+ end
+
+ def test_retrieve_completion_block_with_quote_characters
+ Reline.completer_word_break_characters = ' ([{'
+ Reline.completer_quote_characters = ''
+ assert_equal(['"" ', '"wo', 'rd'], retrieve_completion_block(['"" "word'], 0, 6))
+ Reline.completer_quote_characters = '"'
+ assert_equal(['"" "', 'wo', 'rd'], retrieve_completion_block(['"" "word'], 0, 6))
+ end
+
+ def test_retrieve_completion_quote
+ Reline.completer_quote_characters = '"\''
+ assert_equal('"', retrieve_completion_quote('"\''))
+ assert_equal(nil, retrieve_completion_quote('""'))
+ assert_equal("'", retrieve_completion_quote('""\'"'))
+ assert_equal(nil, retrieve_completion_quote('""\'\''))
+ assert_equal('"', retrieve_completion_quote('"\\"'))
+ assert_equal(nil, retrieve_completion_quote('"\\""'))
+ assert_equal(nil, retrieve_completion_quote('"\\\\"'))
+ end
+ end
+
class RenderLineDifferentialTest < Reline::TestCase
class TestIO < Reline::IO
def move_cursor_column(col)