From 777dffae1c18e533358a1ac0033789a7ed5b9bc4 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sun, 14 Apr 2024 23:28:10 +0900 Subject: [PATCH] Refactor waiting_proc and waiting_operator_proc (#649) * Fix waiting_proc precedence * Fix waiting_operator bugs * Add waiting_proc and vi_waiting_operator test * Fix vi waiting operator arg number vi_arg and vi_waiting_operator_arg should be multiplied * Implement `yy` copies whole line in vi_command mode * Simplify incremental search cancel test * Add complex vi test with waiting_proc and vi_waiting_operator, split test input --- lib/reline/line_editor.rb | 151 ++++++++++++++++------------ test/reline/test_key_actor_emacs.rb | 8 ++ test/reline/test_key_actor_vi.rb | 63 ++++++++++-- 3 files changed, 149 insertions(+), 73 deletions(-) diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index f23671a942..62dc753dba 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -229,8 +229,8 @@ def reset_variables(prompt = '', encoding:) @vi_clipboard = '' @vi_arg = nil @waiting_proc = nil - @waiting_operator_proc = nil - @waiting_operator_vi_arg = nil + @vi_waiting_operator = nil + @vi_waiting_operator_arg = nil @completion_journey_state = nil @completion_state = CompletionState::NORMAL @completion_occurs = false @@ -936,37 +936,23 @@ def dialog_proc_scope_completion_journey_data end private def run_for_operators(key, method_symbol, &block) - if @waiting_operator_proc + if @vi_waiting_operator if VI_MOTIONS.include?(method_symbol) old_byte_pointer = @byte_pointer - @vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg&.> 1 + @vi_arg = (@vi_arg || 1) * @vi_waiting_operator_arg block.(true) unless @waiting_proc byte_pointer_diff = @byte_pointer - old_byte_pointer @byte_pointer = old_byte_pointer - @waiting_operator_proc.(byte_pointer_diff) - else - old_waiting_proc = @waiting_proc - old_waiting_operator_proc = @waiting_operator_proc - current_waiting_operator_proc = @waiting_operator_proc - @waiting_proc = proc { |k| - old_byte_pointer = @byte_pointer - old_waiting_proc.(k) - byte_pointer_diff = @byte_pointer - old_byte_pointer - @byte_pointer = old_byte_pointer - current_waiting_operator_proc.(byte_pointer_diff) - @waiting_operator_proc = old_waiting_operator_proc - } + send(@vi_waiting_operator, byte_pointer_diff) + cleanup_waiting end else # Ignores operator when not motion is given. block.(false) + cleanup_waiting end - @waiting_operator_proc = nil - @waiting_operator_vi_arg = nil - if @vi_arg - @vi_arg = nil - end + @vi_arg = nil else block.(false) end @@ -983,7 +969,7 @@ def dialog_proc_scope_completion_journey_data end def wrap_method_call(method_symbol, method_obj, key, with_operator = false) - if @config.editing_mode_is?(:emacs, :vi_insert) and @waiting_proc.nil? and @waiting_operator_proc.nil? + if @config.editing_mode_is?(:emacs, :vi_insert) and @vi_waiting_operator.nil? not_insertion = method_symbol != :ed_insert process_insert(force: not_insertion) end @@ -1002,11 +988,32 @@ def wrap_method_call(method_symbol, method_obj, key, with_operator = false) end end + private def cleanup_waiting + @waiting_proc = nil + @vi_waiting_operator = nil + @vi_waiting_operator_arg = nil + @searching_prompt = nil + @drop_terminate_spaces = false + end + private def process_key(key, method_symbol) + if key.is_a?(Symbol) + cleanup_waiting + elsif @waiting_proc + old_byte_pointer = @byte_pointer + @waiting_proc.call(key) + if @vi_waiting_operator + byte_pointer_diff = @byte_pointer - old_byte_pointer + @byte_pointer = old_byte_pointer + send(@vi_waiting_operator, byte_pointer_diff) + cleanup_waiting + end + @kill_ring.process + return + end + if method_symbol and respond_to?(method_symbol, true) method_obj = method(method_symbol) - else - method_obj = nil end if method_symbol and key.is_a?(Symbol) if @vi_arg and argumentable?(method_obj) @@ -1028,8 +1035,6 @@ def wrap_method_call(method_symbol, method_obj, key, with_operator = false) run_for_operators(key, method_symbol) do |with_operator| wrap_method_call(method_symbol, method_obj, key, with_operator) end - elsif @waiting_proc - @waiting_proc.(key) elsif method_obj wrap_method_call(method_symbol, method_obj, key) else @@ -1040,9 +1045,6 @@ def wrap_method_call(method_symbol, method_obj, key, with_operator = false) @vi_arg = nil end end - elsif @waiting_proc - @waiting_proc.(key) - @kill_ring.process elsif method_obj if method_symbol == :ed_argument_digit wrap_method_call(method_symbol, method_obj, key) @@ -2325,46 +2327,63 @@ def finish @byte_pointer = 0 end - private def vi_change_meta(key, arg: 1) - @drop_terminate_spaces = true - @waiting_operator_proc = proc { |byte_pointer_diff| - if byte_pointer_diff > 0 - line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff) - elsif byte_pointer_diff < 0 - line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff) - end - set_current_line(line) - copy_for_vi(cut) - @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0 - @config.editing_mode = :vi_insert - @drop_terminate_spaces = false - } - @waiting_operator_vi_arg = arg + private def vi_change_meta(key, arg: nil) + if @vi_waiting_operator + set_current_line('', 0) if @vi_waiting_operator == :vi_change_meta_confirm && arg.nil? + @vi_waiting_operator = nil + @vi_waiting_operator_arg = nil + else + @drop_terminate_spaces = true + @vi_waiting_operator = :vi_change_meta_confirm + @vi_waiting_operator_arg = arg || 1 + end end - private def vi_delete_meta(key, arg: 1) - @waiting_operator_proc = proc { |byte_pointer_diff| - if byte_pointer_diff > 0 - line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff) - elsif byte_pointer_diff < 0 - line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff) - end - copy_for_vi(cut) - set_current_line(line || '', @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0)) - } - @waiting_operator_vi_arg = arg + private def vi_change_meta_confirm(byte_pointer_diff) + vi_delete_meta_confirm(byte_pointer_diff) + @config.editing_mode = :vi_insert + @drop_terminate_spaces = false end - private def vi_yank(key, arg: 1) - @waiting_operator_proc = proc { |byte_pointer_diff| - if byte_pointer_diff > 0 - cut = current_line.byteslice(@byte_pointer, byte_pointer_diff) - elsif byte_pointer_diff < 0 - cut = current_line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff) - end - copy_for_vi(cut) - } - @waiting_operator_vi_arg = arg + private def vi_delete_meta(key, arg: nil) + if @vi_waiting_operator + set_current_line('', 0) if @vi_waiting_operator == :vi_delete_meta_confirm && arg.nil? + @vi_waiting_operator = nil + @vi_waiting_operator_arg = nil + else + @vi_waiting_operator = :vi_delete_meta_confirm + @vi_waiting_operator_arg = arg || 1 + end + end + + private def vi_delete_meta_confirm(byte_pointer_diff) + if byte_pointer_diff > 0 + line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff) + elsif byte_pointer_diff < 0 + line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff) + end + copy_for_vi(cut) + set_current_line(line || '', @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0)) + end + + private def vi_yank(key, arg: nil) + if @vi_waiting_operator + copy_for_vi(current_line) if @vi_waiting_operator == :vi_yank_confirm && arg.nil? + @vi_waiting_operator = nil + @vi_waiting_operator_arg = nil + else + @vi_waiting_operator = :vi_yank_confirm + @vi_waiting_operator_arg = arg || 1 + end + end + + private def vi_yank_confirm(byte_pointer_diff) + if byte_pointer_diff > 0 + cut = current_line.byteslice(@byte_pointer, byte_pointer_diff) + elsif byte_pointer_diff < 0 + cut = current_line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff) + end + copy_for_vi(cut) end private def vi_list_or_eof(key) diff --git a/test/reline/test_key_actor_emacs.rb b/test/reline/test_key_actor_emacs.rb index 5d7a01ac2d..8f5676a1d4 100644 --- a/test/reline/test_key_actor_emacs.rb +++ b/test/reline/test_key_actor_emacs.rb @@ -1309,6 +1309,14 @@ def test_ed_search_next_history_with_empty assert_line_around_cursor('', '') end + def test_incremental_search_history_cancel_by_symbol_key + # ed_prev_char should move cursor left and cancel incremental search + input_keys("abc\C-r") + input_key_by_symbol(:ed_prev_char) + input_keys('d') + assert_line_around_cursor('abd', 'c') + end + # Unicode emoji test def test_ed_insert_for_include_zwj_emoji omit "This test is for UTF-8 but the locale is #{Reline.core.encoding}" if Reline.core.encoding != Encoding::UTF_8 diff --git a/test/reline/test_key_actor_vi.rb b/test/reline/test_key_actor_vi.rb index 838c162d6c..e72eedb904 100644 --- a/test/reline/test_key_actor_vi.rb +++ b/test/reline/test_key_actor_vi.rb @@ -739,10 +739,16 @@ def test_vi_delete_meta_with_vi_next_char end def test_vi_delete_meta_with_arg - input_keys("aaa bbb ccc\C-[02w") - assert_line_around_cursor('aaa bbb ', 'ccc') + input_keys("aaa bbb ccc ddd\C-[03w") + assert_line_around_cursor('aaa bbb ccc ', 'ddd') input_keys('2dl') - assert_line_around_cursor('aaa bbb ', 'c') + assert_line_around_cursor('aaa bbb ccc ', 'd') + input_keys('d2h') + assert_line_around_cursor('aaa bbb cc', 'd') + input_keys('2d3h') + assert_line_around_cursor('aaa ', 'd') + input_keys('dd') + assert_line_around_cursor('', '') end def test_vi_change_meta @@ -765,6 +771,45 @@ def test_vi_change_meta_with_vi_next_word assert_line_around_cursor('foo hog', 'e baz') end + def test_vi_waiting_operator_with_waiting_proc + input_keys("foo foo foo foo foo\C-[0") + input_keys('2d3fo') + assert_line_around_cursor('', ' foo foo') + input_keys('fo') + assert_line_around_cursor(' f', 'oo foo') + end + + def test_vi_waiting_operator_cancel + input_keys("aaa bbb ccc\C-[02w") + assert_line_around_cursor('aaa bbb ', 'ccc') + # dc dy should cancel delete_meta + input_keys('dch') + input_keys('dyh') + # cd cy should cancel change_meta + input_keys('cdh') + input_keys('cyh') + # yd yc should cancel yank_meta + # P should not paste yanked text because yank_meta is canceled + input_keys('ydhP') + input_keys('ychP') + assert_line_around_cursor('aa', 'a bbb ccc') + end + + def test_cancel_waiting_with_symbol_key + input_keys("aaa bbb lll\C-[0") + assert_line_around_cursor('', 'aaa bbb lll') + # ed_next_char should move cursor right and cancel vi_next_char + input_keys('f') + input_key_by_symbol(:ed_next_char) + input_keys('l') + assert_line_around_cursor('aa', 'a bbb lll') + # ed_next_char should move cursor right and cancel delete_meta + input_keys('d') + input_key_by_symbol(:ed_next_char) + input_keys('l') + assert_line_around_cursor('aaa ', 'bbb lll') + end + def test_unimplemented_vi_command_should_be_no_op input_keys("abc\C-[h") assert_line_around_cursor('a', 'bc') @@ -773,12 +818,16 @@ def test_unimplemented_vi_command_should_be_no_op end def test_vi_yank - input_keys("foo bar\C-[0") - assert_line_around_cursor('', 'foo bar') + input_keys("foo bar\C-[2h") + assert_line_around_cursor('foo ', 'bar') input_keys('y3l') - assert_line_around_cursor('', 'foo bar') + assert_line_around_cursor('foo ', 'bar') input_keys('P') - assert_line_around_cursor('fo', 'ofoo bar') + assert_line_around_cursor('foo ba', 'rbar') + input_keys('3h3yhP') + assert_line_around_cursor('foofo', 'o barbar') + input_keys('yyP') + assert_line_around_cursor('foofofoofoo barba', 'ro barbar') end def test_vi_end_word_with_operator