Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix completion quote, preposing and target calculation bug #763

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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([', '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)
Expand Down
Loading