Skip to content

Commit

Permalink
Fix completion quote, preposing and target calculation bug
Browse files Browse the repository at this point in the history
  • Loading branch information
tompng committed Oct 13, 2024
1 parent 546a425 commit 8699364
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 79 deletions.
79 changes: 22 additions & 57 deletions lib/reline/line_editor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1241,70 +1241,35 @@ def set_current_lines(lines, byte_pointer = nil, line_index = 0)
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
Expand Down
22 changes: 0 additions & 22 deletions test/reline/test_key_actor_emacs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -853,28 +853,6 @@ def test_completion_with_indent
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{
Expand Down
60 changes: 60 additions & 0 deletions test/reline/test_line_editor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,66 @@
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(', 'ba', 'r)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)
Expand Down

0 comments on commit 8699364

Please sign in to comment.