From 0b2d9fab5fb2f8e53946febacffc73cd1d246f4a Mon Sep 17 00:00:00 2001 From: verdy89 Date: Tue, 28 May 2024 01:38:19 +0900 Subject: [PATCH] Implement the redo command (#707) * Implement the redo command * Commented out a test that does not pass * Changed key assignment for redo from "\C-[" to "\C-g" * Changed redo key assignment from `\C-g` to `\M-\C-_` * Revert the first implemantation * Implemented redo by sharing `@past_lines` between undo and redo * Fixed the index of past_lines that is updated when the cursor is moved * Fixed deletion of the redo history in regular input * Renamed variables: past_lines -> input_lines * Rename @position to @input_lines_position * Deleted unused variables: `@old_byte_pointer` and `@old_line_index` --- lib/reline/key_actor/emacs.rb | 2 +- lib/reline/line_editor.rb | 47 +++++---- test/reline/test_key_actor_emacs.rb | 106 +++++++++++++++++++- test/reline/yamatanooroti/test_rendering.rb | 16 +++ 4 files changed, 152 insertions(+), 19 deletions(-) diff --git a/lib/reline/key_actor/emacs.rb b/lib/reline/key_actor/emacs.rb index edd88289a3..d7354520b0 100644 --- a/lib/reline/key_actor/emacs.rb +++ b/lib/reline/key_actor/emacs.rb @@ -319,7 +319,7 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base # 158 M-^^ :ed_unassigned, # 159 M-^_ - :ed_unassigned, + :redo, # 160 M-SPACE :em_set_mark, # 161 M-! diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index 23ece60220..8f6421fb10 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -250,7 +250,8 @@ def reset_variables(prompt = '', encoding:) @resized = false @cache = {} @rendered_screen = RenderedScreen.new(base_y: 0, lines: [], cursor_y: 0) - @past_lines = [] + @input_lines = [[[""], 0, 0]] + @input_lines_position = 0 @undoing = false reset_line end @@ -1137,7 +1138,7 @@ def input_key(key) @completion_journey_state = nil end - push_past_lines unless @undoing + push_input_lines unless @undoing @undoing = false if @in_pasting @@ -1156,21 +1157,24 @@ def input_key(key) def save_old_buffer @old_buffer_of_lines = @buffer_of_lines.dup - @old_byte_pointer = @byte_pointer.dup - @old_line_index = @line_index.dup end - def push_past_lines - if @old_buffer_of_lines != @buffer_of_lines - @past_lines.push([@old_buffer_of_lines, @old_byte_pointer, @old_line_index]) + def push_input_lines + if @old_buffer_of_lines == @buffer_of_lines + @input_lines[@input_lines_position] = [@buffer_of_lines.dup, @byte_pointer, @line_index] + else + @input_lines = @input_lines[0..@input_lines_position] + @input_lines_position += 1 + @input_lines.push([@buffer_of_lines.dup, @byte_pointer, @line_index]) end - trim_past_lines + trim_input_lines end - MAX_PAST_LINES = 100 - def trim_past_lines - if @past_lines.size > MAX_PAST_LINES - @past_lines.shift + MAX_INPUT_LINES = 100 + def trim_input_lines + if @input_lines.size > MAX_INPUT_LINES + @input_lines.shift + @input_lines_position -= 1 end end @@ -1352,7 +1356,7 @@ def insert_pasted_text(text) @buffer_of_lines[@line_index, 1] = lines @line_index += lines.size - 1 @byte_pointer = @buffer_of_lines[@line_index].bytesize - post.bytesize - push_past_lines + push_input_lines end def insert_text(text) @@ -2529,13 +2533,22 @@ def finish end private def undo(_key) - return if @past_lines.empty? + @undoing = true + + return if @input_lines_position <= 0 + + @input_lines_position -= 1 + target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position] + set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y) + end + private def redo(_key) @undoing = true - target_lines, target_cursor_x, target_cursor_y = @past_lines.last - set_current_lines(target_lines, target_cursor_x, target_cursor_y) + return if @input_lines_position >= @input_lines.size - 1 - @past_lines.pop + @input_lines_position += 1 + target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position] + set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y) end end diff --git a/test/reline/test_key_actor_emacs.rb b/test/reline/test_key_actor_emacs.rb index 013ca2f7b3..a2ea060ab9 100644 --- a/test/reline/test_key_actor_emacs.rb +++ b/test/reline/test_key_actor_emacs.rb @@ -1498,11 +1498,115 @@ def test_undo_with_multiline end def test_undo_with_many_times - str = "a" + "b" * 100 + str = "a" + "b" * 99 input_keys(str, false) 100.times { input_keys("\C-_", false) } assert_line_around_cursor('a', '') input_keys("\C-_", false) assert_line_around_cursor('a', '') end + + def test_redo + input_keys("aあb", false) + assert_line_around_cursor('aあb', '') + input_keys("\M-\C-_", false) + assert_line_around_cursor('aあb', '') + input_keys("\C-_", false) + assert_line_around_cursor('aあ', '') + input_keys("\C-_", false) + assert_line_around_cursor('a', '') + input_keys("\M-\C-_", false) + assert_line_around_cursor('aあ', '') + input_keys("\M-\C-_", false) + assert_line_around_cursor('aあb', '') + input_keys("\C-_", false) + assert_line_around_cursor('aあ', '') + input_keys("c", false) + assert_line_around_cursor('aあc', '') + input_keys("\M-\C-_", false) + assert_line_around_cursor('aあc', '') + end + + def test_redo_with_cursor_position + input_keys("abc\C-b\C-h", false) + assert_line_around_cursor('a', 'c') + input_keys("\M-\C-_", false) + assert_line_around_cursor('a', 'c') + input_keys("\C-_", false) + assert_line_around_cursor('ab', 'c') + input_keys("\M-\C-_", false) + assert_line_around_cursor('a', 'c') + end + + def test_redo_with_multiline + @line_editor.multiline_on + @line_editor.confirm_multiline_termination_proc = proc {} + input_keys("1\n2\n3", false) + assert_whole_lines(["1", "2", "3"]) + assert_line_index(2) + assert_line_around_cursor('3', '') + + input_keys("\C-_", false) + assert_whole_lines(["1", "2", ""]) + assert_line_index(2) + assert_line_around_cursor('', '') + + input_keys("\C-_", false) + assert_whole_lines(["1", "2"]) + assert_line_index(1) + assert_line_around_cursor('2', '') + + input_keys("\M-\C-_", false) + assert_whole_lines(["1", "2", ""]) + assert_line_index(2) + assert_line_around_cursor('', '') + + input_keys("\M-\C-_", false) + assert_whole_lines(["1", "2", "3"]) + assert_line_index(2) + assert_line_around_cursor('3', '') + + input_keys("\C-p\C-h\C-h", false) + assert_whole_lines(["1", "3"]) + assert_line_index(0) + assert_line_around_cursor('1', '') + + input_keys("\C-n", false) + assert_whole_lines(["1", "3"]) + assert_line_index(1) + assert_line_around_cursor('3', '') + + input_keys("\C-_", false) + assert_whole_lines(["1", "", "3"]) + assert_line_index(1) + assert_line_around_cursor('', '') + + input_keys("\C-_", false) + assert_whole_lines(["1", "2", "3"]) + assert_line_index(1) + assert_line_around_cursor('2', '') + + input_keys("\M-\C-_", false) + assert_whole_lines(["1", "", "3"]) + assert_line_index(1) + assert_line_around_cursor('', '') + + input_keys("\M-\C-_", false) + assert_whole_lines(["1", "3"]) + assert_line_index(1) + assert_line_around_cursor('3', '') + end + + def test_redo_with_many_times + str = "a" + "b" * 98 + "c" + input_keys(str, false) + 100.times { input_keys("\C-_", false) } + assert_line_around_cursor('a', '') + input_keys("\C-_", false) + assert_line_around_cursor('a', '') + 100.times { input_keys("\M-\C-_", false) } + assert_line_around_cursor(str, '') + input_keys("\M-\C-_", false) + assert_line_around_cursor(str, '') + end end diff --git a/test/reline/yamatanooroti/test_rendering.rb b/test/reline/yamatanooroti/test_rendering.rb index 37a1c1a193..1cf46b4cd1 100644 --- a/test/reline/yamatanooroti/test_rendering.rb +++ b/test/reline/yamatanooroti/test_rendering.rb @@ -569,6 +569,22 @@ def test_bracketed_paste_with_undo EOC end + def test_bracketed_paste_with_redo + omit if Reline.core.io_gate.win? + start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') + write("abc") + write("\e[200~def hoge\r\t3\rend\e[201~") + write("\C-_") + write("\M-\C-_") + close + assert_screen(<<~EOC) + Multiline REPL. + prompt> abcdef hoge + prompt> 3 + prompt> end + EOC + end + def test_backspace_until_returns_to_initial start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') write("ABC")