From 186a1e48958dc008065f6d5a78f7070e4280faa4 Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 31 Dec 2023 02:40:55 +0900 Subject: [PATCH 1/7] Fix waiting_proc precedence --- lib/reline/line_editor.rb | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index d5c158ac74..055b9436d7 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -1002,10 +1002,19 @@ def wrap_method_call(method_symbol, method_obj, key, with_operator = false) end private def process_key(key, method_symbol) + if @waiting_proc + if key.is_a?(Symbol) + @waiting_proc = nil + @searching_prompt = nil + else + @waiting_proc.call(key) + @kill_ring.process + return + end + 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) @@ -1027,8 +1036,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 @@ -1039,9 +1046,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) From 9f157a94c39efc0cdf55bc3e1100671ca400c1c2 Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 31 Dec 2023 04:30:20 +0900 Subject: [PATCH 2/7] Fix waiting_operator bugs --- lib/reline/line_editor.rb | 143 ++++++++++++++++++++------------------ 1 file changed, 77 insertions(+), 66 deletions(-) diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index 055b9436d7..9ed315c83f 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -218,8 +218,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 @perfect_matched = nil @@ -935,37 +935,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_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 @@ -982,7 +968,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 @@ -1001,16 +987,28 @@ 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 @waiting_proc - if key.is_a?(Symbol) - @waiting_proc = nil - @searching_prompt = nil - else - @waiting_proc.call(key) - @kill_ring.process - return + 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) @@ -2322,46 +2320,59 @@ 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 + 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_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 + 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: 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 + if @vi_waiting_operator + @vi_waiting_operator = nil + else + @vi_waiting_operator = :vi_yank_confirm + @vi_waiting_operator_arg = arg + 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) From 07adea0a8392e89a1a7a1fec5dcf08a13a4d1cb3 Mon Sep 17 00:00:00 2001 From: tompng Date: Mon, 1 Jan 2024 04:30:17 +0900 Subject: [PATCH 3/7] Add waiting_proc and vi_waiting_operator test --- test/reline/test_key_actor_emacs.rb | 8 +++++++ test/reline/test_key_actor_vi.rb | 33 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/test/reline/test_key_actor_emacs.rb b/test/reline/test_key_actor_emacs.rb index 2311af0f5a..aa7fba60f0 100644 --- a/test/reline/test_key_actor_emacs.rb +++ b/test/reline/test_key_actor_emacs.rb @@ -1263,6 +1263,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-rdef") + input_key_by_symbol(:ed_prev_char) + input_keys("g") + assert_line_around_cursor('abg', '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 91cbd49d74..26b4ec0166 100644 --- a/test/reline/test_key_actor_vi.rb +++ b/test/reline/test_key_actor_vi.rb @@ -697,6 +697,12 @@ def test_vi_delete_meta_with_arg assert_line_around_cursor('aaa bbb ', 'ccc') input_keys('2dl') assert_line_around_cursor('aaa bbb ', 'c') + input_keys('d2h') + assert_line_around_cursor('aaa bb', 'c') + input_keys('2d3h') + assert_line_around_cursor('aaa', 'c') + input_keys('dd') + assert_line_around_cursor('', '') end def test_vi_change_meta @@ -719,6 +725,33 @@ def test_vi_change_meta_with_vi_next_word assert_line_around_cursor('foo hog', 'e baz') 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 + # cd cy should cancel change_meta + # yd yc yy should cancel yank_meta + # p should not paste yanked text because yank_meta is canceled + input_keys('dchdyhcdhcyhydhychyyhp') + assert_line_around_cursor('a', 'aa 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') From 20b5e710d0d18863aa0ec8938b13b0c22024cf8c Mon Sep 17 00:00:00 2001 From: tompng Date: Tue, 9 Apr 2024 00:08:22 +0900 Subject: [PATCH 4/7] Fix vi waiting operator arg number vi_arg and vi_waiting_operator_arg should be multiplied --- lib/reline/line_editor.rb | 5 ++++- test/reline/test_key_actor_vi.rb | 16 ++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index 9ed315c83f..cd528cf496 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -938,7 +938,7 @@ def dialog_proc_scope_completion_journey_data if @vi_waiting_operator if VI_MOTIONS.include?(method_symbol) old_byte_pointer = @byte_pointer - @vi_arg ||= @vi_waiting_operator_arg + @vi_arg = (@vi_arg || 1) * @vi_waiting_operator_arg block.(true) unless @waiting_proc byte_pointer_diff = @byte_pointer - old_byte_pointer @@ -2324,6 +2324,7 @@ def finish 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 @@ -2341,6 +2342,7 @@ def finish 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 @@ -2360,6 +2362,7 @@ def finish private def vi_yank(key, arg: 1) if @vi_waiting_operator @vi_waiting_operator = nil + @vi_waiting_operator_arg = nil else @vi_waiting_operator = :vi_yank_confirm @vi_waiting_operator_arg = arg diff --git a/test/reline/test_key_actor_vi.rb b/test/reline/test_key_actor_vi.rb index 26b4ec0166..1233578f87 100644 --- a/test/reline/test_key_actor_vi.rb +++ b/test/reline/test_key_actor_vi.rb @@ -693,14 +693,14 @@ 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 bb', 'c') + assert_line_around_cursor('aaa bbb cc', 'd') input_keys('2d3h') - assert_line_around_cursor('aaa', 'c') + assert_line_around_cursor('aaa ', 'd') input_keys('dd') assert_line_around_cursor('', '') end @@ -731,10 +731,10 @@ def test_vi_waiting_operator_cancel assert_line_around_cursor('aaa bbb ', 'ccc') # dc dy should cancel delete_meta # cd cy should cancel change_meta - # yd yc yy should cancel yank_meta + # yd yc should cancel yank_meta # p should not paste yanked text because yank_meta is canceled - input_keys('dchdyhcdhcyhydhychyyhp') - assert_line_around_cursor('a', 'aa bbb ccc') + input_keys('dchdyhcdhcyhydhPychP') + assert_line_around_cursor('aa', 'a bbb ccc') end def test_cancel_waiting_with_symbol_key From 02e54fe42852e7319022fea29e68e078ebc9ffdd Mon Sep 17 00:00:00 2001 From: tompng Date: Tue, 9 Apr 2024 00:22:08 +0900 Subject: [PATCH 5/7] Implement `yy` copies whole line in vi_command mode --- lib/reline/line_editor.rb | 5 +++-- test/reline/test_key_actor_vi.rb | 12 ++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index cd528cf496..dca621bcf6 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -2359,13 +2359,14 @@ def finish set_current_line(line || '', @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0)) end - private def vi_yank(key, arg: 1) + 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 + @vi_waiting_operator_arg = arg || 1 end end diff --git a/test/reline/test_key_actor_vi.rb b/test/reline/test_key_actor_vi.rb index 1233578f87..d06b33792e 100644 --- a/test/reline/test_key_actor_vi.rb +++ b/test/reline/test_key_actor_vi.rb @@ -760,12 +760,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 From 3125878900abe08a6440afd6a0cee540a2d76549 Mon Sep 17 00:00:00 2001 From: tompng Date: Tue, 9 Apr 2024 01:04:02 +0900 Subject: [PATCH 6/7] Simplify incremental search cancel test --- test/reline/test_key_actor_emacs.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/reline/test_key_actor_emacs.rb b/test/reline/test_key_actor_emacs.rb index aa7fba60f0..9c5868a1d2 100644 --- a/test/reline/test_key_actor_emacs.rb +++ b/test/reline/test_key_actor_emacs.rb @@ -1265,10 +1265,10 @@ def test_ed_search_next_history_with_empty def test_incremental_search_history_cancel_by_symbol_key # ed_prev_char should move cursor left and cancel incremental search - input_keys("abc\C-rdef") + input_keys("abc\C-r") input_key_by_symbol(:ed_prev_char) - input_keys("g") - assert_line_around_cursor('abg', 'c') + input_keys('d') + assert_line_around_cursor('abd', 'c') end # Unicode emoji test From 4e733ab39f4b7ad7bae9c4a3913c30fd7dcabafc Mon Sep 17 00:00:00 2001 From: tompng Date: Fri, 12 Apr 2024 02:38:04 +0900 Subject: [PATCH 7/7] Add complex vi test with waiting_proc and vi_waiting_operator, split test input --- test/reline/test_key_actor_vi.rb | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/test/reline/test_key_actor_vi.rb b/test/reline/test_key_actor_vi.rb index d06b33792e..6b0101209f 100644 --- a/test/reline/test_key_actor_vi.rb +++ b/test/reline/test_key_actor_vi.rb @@ -725,15 +725,27 @@ 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('dchdyhcdhcyhydhPychP') + # 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