[ruby/reline] Implement the undo command
(https://github.com/ruby/reline/pull/701) * Refactor send * Implement the undo command * Fix @past_lines initialization * Improve assertion * Hide to save buffer in insert_pasted_text * Replace @using_delete_command with @undoing * Refactor `@past_lines` https://github.com/ruby/reline/commit/4ab72f9cbd
This commit is contained in:
parent
5319587ac3
commit
9d01f657b3
@ -63,7 +63,7 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
|
||||
# 30 ^^
|
||||
:ed_unassigned,
|
||||
# 31 ^_
|
||||
:ed_unassigned,
|
||||
:undo,
|
||||
# 32 SPACE
|
||||
:ed_insert,
|
||||
# 33 !
|
||||
|
@ -4,7 +4,6 @@ require 'reline/unicode'
|
||||
require 'tempfile'
|
||||
|
||||
class Reline::LineEditor
|
||||
# TODO: undo
|
||||
# TODO: Use "private alias_method" idiom after drop Ruby 2.5.
|
||||
attr_reader :byte_pointer
|
||||
attr_accessor :confirm_multiline_termination_proc
|
||||
@ -251,6 +250,8 @@ class Reline::LineEditor
|
||||
@resized = false
|
||||
@cache = {}
|
||||
@rendered_screen = RenderedScreen.new(base_y: 0, lines: [], cursor_y: 0)
|
||||
@past_lines = []
|
||||
@undoing = false
|
||||
reset_line
|
||||
end
|
||||
|
||||
@ -948,7 +949,8 @@ class Reline::LineEditor
|
||||
unless @waiting_proc
|
||||
byte_pointer_diff = @byte_pointer - old_byte_pointer
|
||||
@byte_pointer = old_byte_pointer
|
||||
send(@vi_waiting_operator, byte_pointer_diff)
|
||||
method_obj = method(@vi_waiting_operator)
|
||||
wrap_method_call(@vi_waiting_operator, method_obj, byte_pointer_diff)
|
||||
cleanup_waiting
|
||||
end
|
||||
else
|
||||
@ -1009,7 +1011,8 @@ class Reline::LineEditor
|
||||
if @vi_waiting_operator
|
||||
byte_pointer_diff = @byte_pointer - old_byte_pointer
|
||||
@byte_pointer = old_byte_pointer
|
||||
send(@vi_waiting_operator, byte_pointer_diff)
|
||||
method_obj = method(@vi_waiting_operator)
|
||||
wrap_method_call(@vi_waiting_operator, method_obj, byte_pointer_diff)
|
||||
cleanup_waiting
|
||||
end
|
||||
@kill_ring.process
|
||||
@ -1106,6 +1109,7 @@ class Reline::LineEditor
|
||||
end
|
||||
|
||||
def input_key(key)
|
||||
save_old_buffer
|
||||
@config.reset_oneshot_key_bindings
|
||||
@dialogs.each do |dialog|
|
||||
if key.char.instance_of?(Symbol) and key.char == dialog.name
|
||||
@ -1120,7 +1124,6 @@ class Reline::LineEditor
|
||||
finish
|
||||
return
|
||||
end
|
||||
old_lines = @buffer_of_lines.dup
|
||||
@first_char = false
|
||||
@completion_occurs = false
|
||||
|
||||
@ -1134,12 +1137,15 @@ class Reline::LineEditor
|
||||
@completion_journey_state = nil
|
||||
end
|
||||
|
||||
push_past_lines unless @undoing
|
||||
@undoing = false
|
||||
|
||||
if @in_pasting
|
||||
clear_dialogs
|
||||
return
|
||||
end
|
||||
|
||||
modified = old_lines != @buffer_of_lines
|
||||
modified = @old_buffer_of_lines != @buffer_of_lines
|
||||
if !@completion_occurs && modified && !@config.disable_completion && @config.autocompletion
|
||||
# Auto complete starts only when edited
|
||||
process_insert(force: true)
|
||||
@ -1148,6 +1154,26 @@ class Reline::LineEditor
|
||||
modified
|
||||
end
|
||||
|
||||
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])
|
||||
end
|
||||
trim_past_lines
|
||||
end
|
||||
|
||||
MAX_PAST_LINES = 100
|
||||
def trim_past_lines
|
||||
if @past_lines.size > MAX_PAST_LINES
|
||||
@past_lines.shift
|
||||
end
|
||||
end
|
||||
|
||||
def scroll_into_view
|
||||
_wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
|
||||
if wrapped_cursor_y < screen_scroll_top
|
||||
@ -1224,6 +1250,18 @@ class Reline::LineEditor
|
||||
process_auto_indent
|
||||
end
|
||||
|
||||
def set_current_lines(lines, byte_pointer = nil, line_index = 0)
|
||||
cursor = current_byte_pointer_cursor
|
||||
@buffer_of_lines = lines
|
||||
@line_index = line_index
|
||||
if byte_pointer
|
||||
@byte_pointer = byte_pointer
|
||||
else
|
||||
calculate_nearest_cursor(cursor)
|
||||
end
|
||||
process_auto_indent
|
||||
end
|
||||
|
||||
def retrieve_completion_block(set_completion_quote_character = false)
|
||||
if Reline.completer_word_break_characters.empty?
|
||||
word_break_regexp = nil
|
||||
@ -1306,6 +1344,7 @@ class Reline::LineEditor
|
||||
end
|
||||
|
||||
def insert_pasted_text(text)
|
||||
save_old_buffer
|
||||
pre = @buffer_of_lines[@line_index].byteslice(0, @byte_pointer)
|
||||
post = @buffer_of_lines[@line_index].byteslice(@byte_pointer..)
|
||||
lines = (pre + text.gsub(/\r\n?/, "\n") + post).split("\n", -1)
|
||||
@ -1313,6 +1352,7 @@ class Reline::LineEditor
|
||||
@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
|
||||
end
|
||||
|
||||
def insert_text(text)
|
||||
@ -2487,4 +2527,15 @@ class Reline::LineEditor
|
||||
private def vi_editing_mode(key)
|
||||
@config.editing_mode = :vi_insert
|
||||
end
|
||||
|
||||
private def undo(_key)
|
||||
return if @past_lines.empty?
|
||||
|
||||
@undoing = true
|
||||
|
||||
target_lines, target_cursor_x, target_cursor_y = @past_lines.last
|
||||
set_current_lines(target_lines, target_cursor_x, target_cursor_y)
|
||||
|
||||
@past_lines.pop
|
||||
end
|
||||
end
|
||||
|
@ -1437,4 +1437,72 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
|
||||
@line_editor.__send__(:vi_editing_mode, nil)
|
||||
assert(@config.editing_mode_is?(:vi_insert))
|
||||
end
|
||||
|
||||
def test_undo
|
||||
input_keys("\C-_", false)
|
||||
assert_line_around_cursor('', '')
|
||||
input_keys("aあb\C-h\C-h\C-h", false)
|
||||
assert_line_around_cursor('', '')
|
||||
input_keys("\C-_", false)
|
||||
assert_line_around_cursor('a', '')
|
||||
input_keys("\C-_", false)
|
||||
assert_line_around_cursor('aあ', '')
|
||||
input_keys("\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("\C-_", false)
|
||||
assert_line_around_cursor('', '')
|
||||
end
|
||||
|
||||
def test_undo_with_cursor_position
|
||||
input_keys("abc\C-b\C-h", false)
|
||||
assert_line_around_cursor('a', 'c')
|
||||
input_keys("\C-_", false)
|
||||
assert_line_around_cursor('ab', 'c')
|
||||
input_keys("あいう\C-b\C-h", false)
|
||||
assert_line_around_cursor('abあ', 'うc')
|
||||
input_keys("\C-_", false)
|
||||
assert_line_around_cursor('abあい', 'うc')
|
||||
end
|
||||
|
||||
def test_undo_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-p\C-h\C-h", false)
|
||||
assert_whole_lines(["1", "3"])
|
||||
assert_line_index(0)
|
||||
assert_line_around_cursor('1', '')
|
||||
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("\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', '')
|
||||
end
|
||||
|
||||
def test_undo_with_many_times
|
||||
str = "a" + "b" * 100
|
||||
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
|
||||
end
|
||||
|
@ -556,6 +556,19 @@ begin
|
||||
EOC
|
||||
end
|
||||
|
||||
def test_bracketed_paste_with_undo
|
||||
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-_")
|
||||
close
|
||||
assert_screen(<<~EOC)
|
||||
Multiline REPL.
|
||||
prompt> abc
|
||||
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")
|
||||
|
Loading…
x
Reference in New Issue
Block a user