[ruby/reline] Fix completion quote, preposing and target calculation

bug
(https://github.com/ruby/reline/pull/763)

https://github.com/ruby/reline/commit/d3ba7216eb
This commit is contained in:
tomoya ishida 2024-11-25 00:45:13 +09:00 committed by git
parent c6ca339955
commit bf47b1b523
3 changed files with 85 additions and 82 deletions

View File

@ -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
end
end
postposing = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
if rest
preposing = current_line.byteslice(0, break_pointer)
target = rest
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)
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
else
preposing = ''
if break_pointer
preposing = current_line.byteslice(0, break_pointer)
else
preposing = ''
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 target
if set_completion_quote_character and quote
Reline.core.instance_variable_set(:@completion_quote_character, quote)
insert_text(quote) # FIXME: should not be here
target += quote
end
target = before
end
lines = whole_lines
if @line_index > 0

View File

@ -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{

View File

@ -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)