-
Notifications
You must be signed in to change notification settings - Fork 86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement the redo command #707
Conversation
lib/reline/key_actor/emacs.rb
Outdated
@@ -15,7 +15,7 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base | |||
# 6 ^F | |||
:ed_next_char, | |||
# 7 ^G | |||
:ed_unassigned, | |||
:redo, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about setting redo
to M-C-_
? Maybe it's used in Emacs.
C-g
is assigned abort
in GNU Readline, so I believe it is better to avoid using this.
https://www.gnu.org/savannah-checkouts/gnu/emacs/news/NEWS.User.20.html
Several commands in the group buffer can be undone with M-C-_
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the advice. As you suggested, I have changed the key assignment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about managing the history using only @past_lines
with an index in an instance variable?
This way, it would be more efficient as it avoids moving entries between two arrays.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we aim to use only @past_lines
, it may be necessary to provide instance variables such as @position
to implement the redo. It seems that this would result in a complex mechanism like below.
def normal_input
if @past_lines.length > @position
@past_lines = @past_lines.slice(0, @position + 1)
else
@past_lines.push(current_line)
end
@position += 1
end
def undo
if @past_lines.length == @position
@past_lines.push(current_line)
end
@position -= 1
current_line = @past_lines[@position]
end
def redo
if @past_lines.length - 1 > @position
@position += 1
current_line = @past_lines[@position]
end
end
It may be true that moving elements between two arrays is not efficient, but even when using only @past_lines
, the amount of data that needs to be stored remains the same. Additionally, having a more complex mechanism would make maintenance more challenging.
Is there a better way to achieve this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for writing the sample code! As you mentioned, we need to use instance variables like @position
. Similar operations are performed in the methods that handle history, as shown here: https://github.com/ruby/reline/blob/master/lib/reline/line_editor.rb#L1601-L1843.
While this approach is more complex than using push/pop with two arrays, I don't think it reduces maintainability.
Personally, I feel there are too many instance variables in line_editor.rb
, so I would like to reduce the number of instance variables with significant responsibilities. I find that using integers has a narrower scope of responsibility than using arrays.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, I understand. Thank you for your comments.
With additional advice from @tompng , I was able to implement redo without using @next_lines
!
@@ -1498,11 +1498,115 @@ def test_undo_with_multiline | |||
end | |||
|
|||
def test_undo_with_many_times | |||
str = "a" + "b" * 100 | |||
str = "a" + "b" * 99 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since I changed the implementation to push up to the current state into @past_lines
, the number of actions stored in @past_lines
is now one less than MAX_PAST_LINES
. However, I believe that the value of MAX_PAST_LINES
being set to 100 was chosen simply because 100 is a round number, without any other specific reason. Therefore, instead of changing the value of MAX_PAST_LINES
, I addressed this by modifying the tests.
lib/reline/line_editor.rb
Outdated
@@ -1137,7 +1138,10 @@ def input_key(key) | |||
@completion_journey_state = nil | |||
end | |||
|
|||
push_past_lines unless @undoing | |||
unless @undoing | |||
@past_lines = @past_lines[0..@position] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this part needs to be implemented inside push_past_lines
because:
push_past_lines
is also called frominsert_pasted_text
. This part is also needed in there.- This will blow redo history even when cursor is just moved. This part should be done only when
@old_buffer_of_lines != @buffer_of_lines
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for your comment. I've fixed it.
lib/reline/line_editor.rb
Outdated
@past_lines = [[[""], 0, 0]] | ||
@position = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The instance variable name position
feels too broad. Since the line_editor.rb
is a large file, we want to narrow down what position
refers to. For example, if it’s related to input_lines
, we could use input_lines_position
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went with input_lines_position as well.
lib/reline/line_editor.rb
Outdated
@@ -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 = [] | |||
@past_lines = [[[""], 0, 0]] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we can now move through the past and future with undo and redo, the variable name past_lines
doesn't seem very appropriate. As a suggestion, how about input_lines
? If you have any other ideas, please let me know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since I couldn't come up with a better name, I went with input_lines. Thank you.
@@ -1161,8 +1162,12 @@ def save_old_buffer | |||
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]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like @old_byte_pointer
and @old_line_index
are not used anymore. We can remove initialization code from def save_old_buffer
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've removed initialization code. Thank you.
Deleted unused variables: @old_byte_pointer and @old_line_index
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work 👍 Thanks for your contribution!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
(ruby/reline#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` ruby/reline@0b2d9fab5f
Overview
Implemented the redo method to enable reverting undone actions on a per-action basis.
Implementation Approach
By defining
@position
, I was able to implement the redo command using only@past_lines
, which was originally defined for the undo command. By saving the current line state in@past_lines
, the entire mechanism was simplified. Specifically, it works as follows:@past_lines
, push the input into@past_lines
, and increase@position
by 1.@past_lines
.@position
by 1 and return@past_lines[position]
.@position
by 1 and return@past_lines[position]
.