Skip to content

Commit

Permalink
Implement the redo command (#707)
Browse files Browse the repository at this point in the history
* 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`
  • Loading branch information
verdy89 authored May 27, 2024
1 parent d60f1e1 commit 0b2d9fa
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 19 deletions.
2 changes: 1 addition & 1 deletion lib/reline/key_actor/emacs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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-!
Expand Down
47 changes: 30 additions & 17 deletions lib/reline/line_editor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
106 changes: 105 additions & 1 deletion test/reline/test_key_actor_emacs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
16 changes: 16 additions & 0 deletions test/reline/yamatanooroti/test_rendering.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down

0 comments on commit 0b2d9fa

Please sign in to comment.