summaryrefslogtreecommitdiff
path: root/lib/reline/line_editor.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/reline/line_editor.rb')
-rw-r--r--lib/reline/line_editor.rb328
1 files changed, 199 insertions, 129 deletions
diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb
index 81413505d7..c71a5f79ee 100644
--- a/lib/reline/line_editor.rb
+++ b/lib/reline/line_editor.rb
@@ -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
@@ -46,6 +45,7 @@ class Reline::LineEditor
RenderedScreen = Struct.new(:base_y, :lines, :cursor_y, keyword_init: true)
CompletionJourneyState = Struct.new(:line_index, :pre, :target, :post, :list, :pointer)
+ NullActionState = [nil, nil].freeze
class MenuInfo
attr_reader :list
@@ -75,7 +75,7 @@ class Reline::LineEditor
def initialize(config, encoding)
@config = config
@completion_append_character = ''
- @screen_size = Reline::IOGate.get_screen_size
+ @screen_size = [0, 0] # Should be initialized with actual winsize in LineEditor#reset
reset_variables(encoding: encoding)
end
@@ -176,9 +176,8 @@ class Reline::LineEditor
scroll_into_view
Reline::IOGate.move_cursor_up @rendered_screen.cursor_y
@rendered_screen.base_y = Reline::IOGate.cursor_pos.y
- @rendered_screen.lines = []
- @rendered_screen.cursor_y = 0
- render_differential
+ clear_rendered_screen_cache
+ render
end
private def handle_interrupted
@@ -186,11 +185,11 @@ class Reline::LineEditor
@interrupted = false
clear_dialogs
- scrolldown = render_differential
- Reline::IOGate.scroll_down scrolldown
+ render
+ cursor_to_bottom_offset = @rendered_screen.lines.size - @rendered_screen.cursor_y
+ Reline::IOGate.scroll_down cursor_to_bottom_offset
Reline::IOGate.move_cursor_column 0
- @rendered_screen.lines = []
- @rendered_screen.cursor_y = 0
+ clear_rendered_screen_cache
case @old_trap
when 'DEFAULT', 'SYSTEM_DEFAULT'
raise Interrupt
@@ -235,11 +234,9 @@ class Reline::LineEditor
@vi_waiting_operator_arg = nil
@completion_journey_state = nil
@completion_state = CompletionState::NORMAL
- @completion_occurs = false
@perfect_matched = nil
@menu_info = nil
@searching_prompt = nil
- @first_char = true
@just_cursor_moving = false
@eof = false
@continuous_insertion_buffer = String.new(encoding: @encoding)
@@ -252,6 +249,11 @@ class Reline::LineEditor
@resized = false
@cache = {}
@rendered_screen = RenderedScreen.new(base_y: 0, lines: [], cursor_y: 0)
+ @input_lines = [[[""], 0, 0]]
+ @input_lines_position = 0
+ @undoing = false
+ @prev_action_state = NullActionState
+ @next_action_state = NullActionState
reset_line
end
@@ -284,7 +286,7 @@ class Reline::LineEditor
indent1 = @auto_indent_proc.(@buffer_of_lines.take(@line_index - 1).push(''), @line_index - 1, 0, true)
indent2 = @auto_indent_proc.(@buffer_of_lines.take(@line_index), @line_index - 1, @buffer_of_lines[@line_index - 1].bytesize, false)
indent = indent2 || indent1
- @buffer_of_lines[@line_index - 1] = ' ' * indent + @buffer_of_lines[@line_index - 1].gsub(/\A */, '')
+ @buffer_of_lines[@line_index - 1] = ' ' * indent + @buffer_of_lines[@line_index - 1].gsub(/\A\s*/, '')
)
process_auto_indent @line_index, add_newline: true
else
@@ -387,7 +389,7 @@ class Reline::LineEditor
next cached
end
*wrapped_prompts, code_line_prompt = split_by_width(prompt, width).first.compact
- wrapped_lines = split_by_width(line, width, offset: calculate_width(code_line_prompt)).first.compact
+ wrapped_lines = split_by_width(line, width, offset: calculate_width(code_line_prompt, true)).first.compact
wrapped_prompts.map { |p| [p, ''] } + [[code_line_prompt, wrapped_lines.first]] + wrapped_lines.drop(1).map { |c| ['', c] }
end
end
@@ -411,12 +413,17 @@ class Reline::LineEditor
# do nothing
elsif level == :blank
Reline::IOGate.move_cursor_column base_x
- @output.write "#{Reline::IOGate::RESET_COLOR}#{' ' * width}"
+ @output.write "#{Reline::IOGate.reset_color_sequence}#{' ' * width}"
else
x, w, content = new_items[level]
- content = Reline::Unicode.take_range(content, base_x - x, width) unless x == base_x && w == width
- Reline::IOGate.move_cursor_column base_x
- @output.write "#{Reline::IOGate::RESET_COLOR}#{content}#{Reline::IOGate::RESET_COLOR}"
+ cover_begin = base_x != 0 && new_levels[base_x - 1] == level
+ cover_end = new_levels[base_x + width] == level
+ pos = 0
+ unless x == base_x && w == width
+ content, pos = Reline::Unicode.take_mbchar_range(content, base_x - x, width, cover_begin: cover_begin, cover_end: cover_end, padding: true)
+ end
+ Reline::IOGate.move_cursor_column x + pos
+ @output.write "#{Reline::IOGate.reset_color_sequence}#{content}#{Reline::IOGate.reset_color_sequence}"
end
base_x += width
end
@@ -452,28 +459,7 @@ class Reline::LineEditor
end
def render_finished
- clear_rendered_lines
- render_full_content
- end
-
- def clear_rendered_lines
- Reline::IOGate.move_cursor_up @rendered_screen.cursor_y
- Reline::IOGate.move_cursor_column 0
-
- num_lines = @rendered_screen.lines.size
- return unless num_lines && num_lines >= 1
-
- Reline::IOGate.move_cursor_down num_lines - 1
- (num_lines - 1).times do
- Reline::IOGate.erase_after_cursor
- Reline::IOGate.move_cursor_up 1
- end
- Reline::IOGate.erase_after_cursor
- @rendered_screen.lines = []
- @rendered_screen.cursor_y = 0
- end
-
- def render_full_content
+ render_differential([], 0, 0)
lines = @buffer_of_lines.size.times.map do |i|
line = prompt_list[i] + modified_lines[i]
wrapped_lines, = split_by_width(line, screen_width)
@@ -482,19 +468,13 @@ class Reline::LineEditor
@output.puts lines.map { |l| "#{l}\r\n" }.join
end
- def print_nomultiline_prompt(prompt)
- return unless prompt && !@is_multiline
-
+ def print_nomultiline_prompt
# Readline's test `TestRelineAsReadline#test_readline` requires first output to be prompt, not cursor reset escape sequence.
- @rendered_screen.lines = [[[0, Reline::Unicode.calculate_width(prompt, true), prompt]]]
- @rendered_screen.cursor_y = 0
- @output.write prompt
+ @output.write @prompt if @prompt && !@is_multiline
end
- def render_differential
+ def render
wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
-
- rendered_lines = @rendered_screen.lines
new_lines = wrapped_prompt_and_input_lines.flatten(1)[screen_scroll_top, screen_height].map do |prompt, line|
prompt_width = Reline::Unicode.calculate_width(prompt, true)
[[0, prompt_width, prompt], [prompt_width, Reline::Unicode.calculate_width(line, true), line]]
@@ -512,12 +492,21 @@ class Reline::LineEditor
x_range, y_range = dialog_range dialog, wrapped_cursor_y - screen_scroll_top
y_range.each do |row|
next if row < 0 || row >= screen_height
+
dialog_rows = new_lines[row] ||= []
# index 0 is for prompt, index 1 is for line, index 2.. is for dialog
dialog_rows[index + 2] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
end
end
+ render_differential new_lines, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top
+ end
+
+ # Reflects lines to be rendered and new cursor position to the screen
+ # by calculating the difference from the previous render.
+
+ private def render_differential(new_lines, new_cursor_x, new_cursor_y)
+ rendered_lines = @rendered_screen.lines
cursor_y = @rendered_screen.cursor_y
if new_lines != rendered_lines
# Hide cursor while rendering to avoid cursor flickering.
@@ -544,11 +533,14 @@ class Reline::LineEditor
@rendered_screen.lines = new_lines
Reline::IOGate.show_cursor
end
- y = wrapped_cursor_y - screen_scroll_top
- Reline::IOGate.move_cursor_column wrapped_cursor_x
- Reline::IOGate.move_cursor_down y - cursor_y
- @rendered_screen.cursor_y = y
- new_lines.size - y
+ Reline::IOGate.move_cursor_column new_cursor_x
+ Reline::IOGate.move_cursor_down new_cursor_y - cursor_y
+ @rendered_screen.cursor_y = new_cursor_y
+ end
+
+ private def clear_rendered_screen_cache
+ @rendered_screen.lines = []
+ @rendered_screen.cursor_y = 0
end
def upper_space_height(wrapped_cursor_y)
@@ -560,7 +552,7 @@ class Reline::LineEditor
end
def rerender
- render_differential unless @in_pasting
+ render unless @in_pasting
end
class DialogProcScope
@@ -678,10 +670,8 @@ class Reline::LineEditor
@trap_key.each do |t|
@config.add_oneshot_key_binding(t, @name)
end
- elsif @trap_key.is_a?(Array)
+ else
@config.add_oneshot_key_binding(@trap_key, @name)
- elsif @trap_key.is_a?(Integer) or @trap_key.is_a?(Reline::Key)
- @config.add_oneshot_key_binding([@trap_key], @name)
end
end
dialog_render_info
@@ -699,13 +689,6 @@ class Reline::LineEditor
DIALOG_DEFAULT_HEIGHT = 20
- private def padding_space_with_escape_sequences(str, width)
- padding_width = width - calculate_width(str, true)
- # padding_width should be only positive value. But macOS and Alacritty returns negative value.
- padding_width = 0 if padding_width < 0
- str + (' ' * padding_width)
- end
-
private def dialog_range(dialog, dialog_y)
x_range = dialog.column...dialog.column + dialog.width
y_range = dialog_y + dialog.vertical_offset...dialog_y + dialog.vertical_offset + dialog.contents.size
@@ -778,7 +761,7 @@ class Reline::LineEditor
dialog.contents = contents.map.with_index do |item, i|
line_sgr = i == pointer ? enhanced_sgr : default_sgr
str_width = dialog.width - (scrollbar_pos.nil? ? 0 : @block_elem_width)
- str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, str_width), str_width)
+ str, = Reline::Unicode.take_mbchar_range(item, 0, str_width, padding: true)
colored_content = "#{line_sgr}#{str}"
if scrollbar_pos
if scrollbar_pos <= (i * 2) and (i * 2 + 1) < (scrollbar_pos + bar_height)
@@ -858,7 +841,7 @@ class Reline::LineEditor
[target, preposing, completed, postposing]
end
- private def complete(list, just_show_list)
+ private def perform_completion(list, just_show_list)
case @completion_state
when CompletionState::NORMAL
@completion_state = CompletionState::COMPLETION
@@ -887,12 +870,12 @@ class Reline::LineEditor
@completion_state = CompletionState::PERFECT_MATCH
else
@completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
- complete(list, true) if @config.show_all_if_ambiguous
+ perform_completion(list, true) if @config.show_all_if_ambiguous
end
@perfect_matched = completed
else
@completion_state = CompletionState::MENU
- complete(list, true) if @config.show_all_if_ambiguous
+ perform_completion(list, true) if @config.show_all_if_ambiguous
end
if not just_show_list and target < completed
@buffer_of_lines[@line_index] = (preposing + completed + completion_append_character.to_s + postposing).split("\n")[@line_index] || String.new(encoding: @encoding)
@@ -951,7 +934,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
@@ -1012,7 +996,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
@@ -1067,10 +1052,6 @@ class Reline::LineEditor
end
private def normal_char(key)
- if key.combined_char.is_a?(Symbol)
- process_key(key.combined_char, key.combined_char)
- return
- end
@multibyte_buffer << key.combined_char
if @multibyte_buffer.size > 1
if @multibyte_buffer.dup.force_encoding(@encoding).valid_encoding?
@@ -1083,17 +1064,7 @@ class Reline::LineEditor
else # single byte
return if key.char >= 128 # maybe, first byte of multi byte
method_symbol = @config.editing_mode.get_method(key.combined_char)
- if key.with_meta and method_symbol == :ed_unassigned
- if @config.editing_mode_is?(:vi_command, :vi_insert)
- # split ESC + key in vi mode
- method_symbol = @config.editing_mode.get_method("\e".ord)
- process_key("\e".ord, method_symbol)
- method_symbol = @config.editing_mode.get_method(key.char)
- process_key(key.char, method_symbol)
- end
- else
- process_key(key.combined_char, method_symbol)
- end
+ process_key(key.combined_char, method_symbol)
@multibyte_buffer.clear
end
if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
@@ -1113,6 +1084,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,53 +1092,35 @@ class Reline::LineEditor
end
end
if key.char.nil?
- if @first_char
- @eof = true
- end
+ process_insert(force: true)
+ @eof = buffer_empty?
finish
return
end
- old_lines = @buffer_of_lines.dup
- @first_char = false
@completion_occurs = false
- if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
- if !@config.disable_completion
- process_insert(force: true)
- if @config.autocompletion
- @completion_state = CompletionState::NORMAL
- @completion_occurs = move_completed_list(:down)
- else
- @completion_journey_state = nil
- result = call_completion_proc
- if result.is_a?(Array)
- @completion_occurs = true
- complete(result, false)
- end
- end
- end
- elsif @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
- # In vi mode, move completed list even if autocompletion is off
- if not @config.disable_completion
- process_insert(force: true)
- @completion_state = CompletionState::NORMAL
- @completion_occurs = move_completed_list("\C-p".ord == key.char ? :up : :down)
- end
- elsif Symbol === key.char and respond_to?(key.char, true)
+
+ if key.char.is_a?(Symbol)
process_key(key.char, key.char)
else
normal_char(key)
end
+
+ @prev_action_state, @next_action_state = @next_action_state, NullActionState
+
unless @completion_occurs
@completion_state = CompletionState::NORMAL
@completion_journey_state = nil
end
+ push_input_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)
@@ -1175,6 +1129,29 @@ class Reline::LineEditor
modified
end
+ def save_old_buffer
+ @old_buffer_of_lines = @buffer_of_lines.dup
+ end
+
+ 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_input_lines
+ end
+
+ MAX_INPUT_LINES = 100
+ def trim_input_lines
+ if @input_lines.size > MAX_INPUT_LINES
+ @input_lines.shift
+ @input_lines_position -= 1
+ end
+ end
+
def scroll_into_view
_wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
if wrapped_cursor_y < screen_scroll_top
@@ -1251,6 +1228,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
@@ -1332,6 +1321,18 @@ class Reline::LineEditor
@confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
end
+ def insert_multiline_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)
+ lines << '' if lines.empty?
+ @buffer_of_lines[@line_index, 1] = lines
+ @line_index += lines.size - 1
+ @byte_pointer = @buffer_of_lines[@line_index].bytesize - post.bytesize
+ push_input_lines
+ end
+
def insert_text(text)
if @buffer_of_lines[@line_index].bytesize == @byte_pointer
@buffer_of_lines[@line_index] += text
@@ -1388,6 +1389,10 @@ class Reline::LineEditor
whole_lines.join("\n")
end
+ private def buffer_empty?
+ current_line.empty? and @buffer_of_lines.size == 1
+ end
+
def finished?
@finished
end
@@ -1430,13 +1435,42 @@ class Reline::LineEditor
end
end
- private def completion_journey_up(key)
- if not @config.disable_completion and @config.autocompletion
+ private def complete(_key)
+ return if @config.disable_completion
+
+ process_insert(force: true)
+ if @config.autocompletion
@completion_state = CompletionState::NORMAL
- @completion_occurs = move_completed_list(:up)
+ @completion_occurs = move_completed_list(:down)
+ else
+ @completion_journey_state = nil
+ result = call_completion_proc
+ if result.is_a?(Array)
+ @completion_occurs = true
+ perform_completion(result, false)
+ end
end
end
- alias_method :menu_complete_backward, :completion_journey_up
+
+ private def completion_journey_move(direction)
+ return if @config.disable_completion
+
+ process_insert(force: true)
+ @completion_state = CompletionState::NORMAL
+ @completion_occurs = move_completed_list(direction)
+ end
+
+ private def menu_complete(_key)
+ completion_journey_move(:down)
+ end
+
+ private def menu_complete_backward(_key)
+ completion_journey_move(:up)
+ end
+
+ private def completion_journey_up(_key)
+ completion_journey_move(:up) if @config.autocompletion
+ end
# Editline:: +ed-unassigned+ This editor command always results in an error.
# GNU Readline:: There is no corresponding macro.
@@ -1707,29 +1741,31 @@ class Reline::LineEditor
end
private def ed_search_prev_history(key, arg: 1)
- substr = current_line.byteslice(0, @byte_pointer)
+ substr = prev_action_state_value(:search_history) == :empty ? '' : current_line.byteslice(0, @byte_pointer)
return if @history_pointer == 0
return if @history_pointer.nil? && substr.empty? && !current_line.empty?
history_range = 0...(@history_pointer || Reline::HISTORY.size)
h_pointer, line_index = search_history(substr, history_range.reverse_each)
return unless h_pointer
- move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer)
+ move_history(h_pointer, line: line_index || :start, cursor: substr.empty? ? :end : @byte_pointer)
arg -= 1
+ set_next_action_state(:search_history, :empty) if substr.empty?
ed_search_prev_history(key, arg: arg) if arg > 0
end
alias_method :history_search_backward, :ed_search_prev_history
private def ed_search_next_history(key, arg: 1)
- substr = current_line.byteslice(0, @byte_pointer)
+ substr = prev_action_state_value(:search_history) == :empty ? '' : current_line.byteslice(0, @byte_pointer)
return if @history_pointer.nil?
history_range = @history_pointer + 1...Reline::HISTORY.size
h_pointer, line_index = search_history(substr, history_range)
return if h_pointer.nil? and not substr.empty?
- move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer)
+ move_history(h_pointer, line: line_index || :start, cursor: substr.empty? ? :end : @byte_pointer)
arg -= 1
+ set_next_action_state(:search_history, :empty) if substr.empty?
ed_search_next_history(key, arg: arg) if arg > 0
end
alias_method :history_search_forward, :ed_search_next_history
@@ -1885,7 +1921,7 @@ class Reline::LineEditor
alias_method :kill_whole_line, :em_kill_line
private def em_delete(key)
- if current_line.empty? and @buffer_of_lines.size == 1 and key == "\C-d".ord
+ if buffer_empty? and key == "\C-d".ord
@eof = true
finish
elsif @byte_pointer < current_line.bytesize
@@ -1905,7 +1941,7 @@ class Reline::LineEditor
elsif !@config.autocompletion # show completed list
result = call_completion_proc
if result.is_a?(Array)
- complete(result, true)
+ perform_completion(result, true)
end
end
end
@@ -1930,9 +1966,8 @@ class Reline::LineEditor
private def ed_clear_screen(key)
Reline::IOGate.clear_screen
@screen_size = Reline::IOGate.get_screen_size
- @rendered_screen.lines = []
@rendered_screen.base_y = 0
- @rendered_screen.cursor_y = 0
+ clear_rendered_screen_cache
end
alias_method :clear_screen, :ed_clear_screen
@@ -2207,9 +2242,11 @@ class Reline::LineEditor
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)
+ else
+ return
end
copy_for_vi(cut)
- set_current_line(line || '', @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0))
+ set_current_line(line, @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0))
end
private def vi_yank(key, arg: nil)
@@ -2228,13 +2265,14 @@ class Reline::LineEditor
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)
+ else
+ return
end
copy_for_vi(cut)
end
private def vi_list_or_eof(key)
- if current_line.empty? and @buffer_of_lines.size == 1
- set_current_line('', 0)
+ if buffer_empty?
@eof = true
finish
else
@@ -2475,4 +2513,36 @@ class Reline::LineEditor
private def vi_editing_mode(key)
@config.editing_mode = :vi_insert
end
+
+ private def undo(_key)
+ @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
+
+ return if @input_lines_position >= @input_lines.size - 1
+
+ @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 prev_action_state_value(type)
+ @prev_action_state[0] == type ? @prev_action_state[1] : nil
+ end
+
+ private def set_next_action_state(type, value)
+ @next_action_state = [type, value]
+ end
+
+ private def re_read_init_file(_key)
+ @config.reload
+ end
end