Skip to content

Commit d45c432

Browse files
committed
Refactor perform_completon
Flatten recursive method Remove CompletionState::COMPLETE
1 parent 4d90743 commit d45c432

File tree

4 files changed

+80
-84
lines changed

4 files changed

+80
-84
lines changed

lib/reline/line_editor.rb

Lines changed: 54 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ class Reline::LineEditor
3636

3737
module CompletionState
3838
NORMAL = :normal
39-
COMPLETION = :completion
4039
MENU = :menu
4140
MENU_WITH_PERFECT_MATCH = :menu_with_perfect_match
4241
PERFECT_MATCH = :perfect_match
@@ -800,105 +799,74 @@ def editing_mode
800799
@config.editing_mode
801800
end
802801

803-
private def menu(_target, list)
802+
private def menu(list)
804803
@menu_info = MenuInfo.new(list)
805804
end
806805

807-
private def complete_internal_proc(list, is_menu)
808-
preposing, target, postposing = retrieve_completion_block
809-
candidates = list.select { |i|
810-
if i and not Encoding.compatible?(target.encoding, i.encoding)
811-
raise Encoding::CompatibilityError, "#{target.encoding.name} is not compatible with #{i.encoding.name}"
806+
private def filter_normalize_candidates(target, list)
807+
target = target.downcase if @config.completion_ignore_case
808+
list.select do |item|
809+
next unless item
810+
811+
if Encoding.compatible?(target.encoding, item.encoding)
812+
# Crash with Encoding::CompatibilityError is required by readline-ext/test/readline/test_readline.rb
813+
# TODO: fix the test
814+
raise Encoding::CompatibilityError, "#{target.encoding.name} is not compatible with #{item.encoding.name}"
812815
end
816+
813817
if @config.completion_ignore_case
814-
i&.downcase&.start_with?(target.downcase)
818+
item.downcase.start_with?(target)
815819
else
816-
i&.start_with?(target)
817-
end
818-
}.uniq
819-
if is_menu
820-
menu(target, candidates)
821-
return nil
822-
end
823-
completed = candidates.inject { |memo, item|
824-
begin
825-
memo_mbchars = memo.unicode_normalize.grapheme_clusters
826-
item_mbchars = item.unicode_normalize.grapheme_clusters
827-
rescue Encoding::CompatibilityError
828-
memo_mbchars = memo.grapheme_clusters
829-
item_mbchars = item.grapheme_clusters
830-
end
831-
size = [memo_mbchars.size, item_mbchars.size].min
832-
result = +''
833-
size.times do |i|
834-
if @config.completion_ignore_case
835-
if memo_mbchars[i].casecmp?(item_mbchars[i])
836-
result << memo_mbchars[i]
837-
else
838-
break
839-
end
840-
else
841-
if memo_mbchars[i] == item_mbchars[i]
842-
result << memo_mbchars[i]
843-
else
844-
break
845-
end
846-
end
820+
item.start_with?(target)
847821
end
848-
result
849-
}
850-
851-
[target, preposing, completed, postposing, candidates]
822+
end.filter_map do |item|
823+
item.unicode_normalize
824+
rescue Encoding::CompatibilityError
825+
item
826+
end.uniq
852827
end
853828

854-
private def perform_completion(list, just_show_list)
829+
private def perform_completion(list)
830+
preposing, target, postposing = retrieve_completion_block
831+
candidates = filter_normalize_candidates(target, list)
832+
855833
case @completion_state
856-
when CompletionState::NORMAL
857-
@completion_state = CompletionState::COMPLETION
858834
when CompletionState::PERFECT_MATCH
859835
if @dig_perfect_match_proc
860-
@dig_perfect_match_proc.(@perfect_matched)
861-
else
862-
@completion_state = CompletionState::COMPLETION
836+
@dig_perfect_match_proc.call(@perfect_matched)
837+
return
863838
end
864-
end
865-
if just_show_list
866-
is_menu = true
867-
elsif @completion_state == CompletionState::MENU
868-
is_menu = true
869-
elsif @completion_state == CompletionState::MENU_WITH_PERFECT_MATCH
870-
is_menu = true
871-
else
872-
is_menu = false
873-
end
874-
result = complete_internal_proc(list, is_menu)
875-
if @completion_state == CompletionState::MENU_WITH_PERFECT_MATCH
839+
when CompletionState::MENU
840+
menu(candidates)
841+
return
842+
when CompletionState::MENU_WITH_PERFECT_MATCH
843+
menu(candidates)
876844
@completion_state = CompletionState::PERFECT_MATCH
845+
return
877846
end
878-
return if result.nil?
879-
target, preposing, completed, postposing, candidates = result
880-
return if completed.nil?
881-
if target <= completed and (@completion_state == CompletionState::COMPLETION)
882-
append_character = ''
883-
if candidates.include?(completed)
884-
if candidates.one?
885-
append_character = completion_append_character.to_s
886-
@completion_state = CompletionState::PERFECT_MATCH
887-
else
888-
@completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
889-
perform_completion(candidates, true) if @config.show_all_if_ambiguous
890-
end
891-
@perfect_matched = completed
847+
848+
completed = Reline::Unicode.common_prefix(candidates, ignore_case: @config.completion_ignore_case)
849+
return if completed.empty?
850+
851+
append_character = ''
852+
if candidates.include?(completed)
853+
if candidates.one?
854+
append_character = completion_append_character.to_s
855+
@completion_state = CompletionState::PERFECT_MATCH
856+
elsif @config.show_all_if_ambiguous
857+
menu(candidates)
858+
@completion_state = CompletionState::PERFECT_MATCH
892859
else
893-
@completion_state = CompletionState::MENU
894-
perform_completion(candidates, true) if @config.show_all_if_ambiguous
895-
end
896-
unless just_show_list
897-
@buffer_of_lines[@line_index] = (preposing + completed + append_character + postposing).split("\n")[@line_index] || String.new(encoding: encoding)
898-
line_to_pointer = (preposing + completed + append_character).split("\n")[@line_index] || String.new(encoding: encoding)
899-
@byte_pointer = line_to_pointer.bytesize
860+
@completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
900861
end
862+
@perfect_matched = completed
863+
else
864+
@completion_state = CompletionState::MENU
865+
menu(candidates) if @config.show_all_if_ambiguous
901866
end
867+
@buffer_of_lines[@line_index] = (preposing + completed + append_character + postposing).split("\n")[@line_index] || String.new(encoding: encoding)
868+
line_to_pointer = (preposing + completed + append_character).split("\n")[@line_index] || String.new(encoding: encoding)
869+
@byte_pointer = line_to_pointer.bytesize
902870
end
903871

904872
def dialog_proc_scope_completion_journey_data
@@ -1463,7 +1431,7 @@ def finish
14631431
result = call_completion_proc
14641432
if result.is_a?(Array)
14651433
@completion_occurs = true
1466-
perform_completion(result, false)
1434+
perform_completion(result)
14671435
end
14681436
end
14691437
end
@@ -1929,7 +1897,9 @@ def finish
19291897
elsif !@config.autocompletion # show completed list
19301898
result = call_completion_proc
19311899
if result.is_a?(Array)
1932-
perform_completion(result, true)
1900+
_preposing, target = retrieve_completion_block
1901+
candidates = filter_normalize_candidates(target, result)
1902+
menu(candidates)
19331903
end
19341904
end
19351905
end

lib/reline/unicode.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,19 @@ def self.vi_backward_word(line, byte_pointer)
670670
[byte_size, width]
671671
end
672672

673+
def self.common_prefix(list, ignore_case: false)
674+
return '' if list.empty?
675+
676+
common_prefix_gcs = list.first.grapheme_clusters
677+
list.each do |item|
678+
gcs = item.grapheme_clusters
679+
common_prefix_gcs = common_prefix_gcs.take_while.with_index do |gc, i|
680+
ignore_case ? gc.casecmp?(gcs[i]) : gc == gcs[i]
681+
end
682+
end
683+
common_prefix_gcs.join
684+
end
685+
673686
def self.vi_first_print(line)
674687
width = 0
675688
byte_size = 0

test/reline/test_key_actor_emacs.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,9 @@ def test_completion_with_completion_ignore_case
986986
input_keys('b')
987987
input_keys("\C-i", false)
988988
assert_line_around_cursor('foo_ba', '')
989+
input_keys('Z')
990+
input_keys("\C-i", false)
991+
assert_line_around_cursor('Foo_baz', '')
989992
end
990993

991994
def test_completion_in_middle_of_line

test/reline/test_unicode.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,16 @@ def test_take_mbchar_range
102102
assert_equal ["\e[41m \e[42mい\e[43m ", 1, 4], Reline::Unicode.take_mbchar_range("\e[41mあ\e[42mい\e[43mう", 1, 4, padding: true)
103103
end
104104

105+
def test_common_prefix
106+
assert_equal('', Reline::Unicode.common_prefix([]))
107+
assert_equal('abc', Reline::Unicode.common_prefix(['abc']))
108+
assert_equal('12', Reline::Unicode.common_prefix(['123', '123️⃣']))
109+
assert_equal('', Reline::Unicode.common_prefix(['abc', 'xyz']))
110+
assert_equal('ab', Reline::Unicode.common_prefix(['abcd', 'abc', 'abx', 'abcd']))
111+
assert_equal('A', Reline::Unicode.common_prefix(['AbcD', 'ABC', 'AbX', 'AbCD']))
112+
assert_equal('Ab', Reline::Unicode.common_prefix(['AbcD', 'ABC', 'AbX', 'AbCD'], ignore_case: true))
113+
end
114+
105115
def test_encoding_conversion
106116
texts = [
107117
String.new("invalid\xFFutf8", encoding: 'utf-8'),

0 commit comments

Comments
 (0)