reline 0.5.11 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,7 +13,6 @@ class Reline::LineEditor
13
13
  attr_accessor :prompt_proc
14
14
  attr_accessor :auto_indent_proc
15
15
  attr_accessor :dig_perfect_match_proc
16
- attr_writer :output
17
16
 
18
17
  VI_MOTIONS = %i{
19
18
  ed_prev_char
@@ -36,7 +35,6 @@ class Reline::LineEditor
36
35
 
37
36
  module CompletionState
38
37
  NORMAL = :normal
39
- COMPLETION = :completion
40
38
  MENU = :menu
41
39
  MENU_WITH_PERFECT_MATCH = :menu_with_perfect_match
42
40
  PERFECT_MATCH = :perfect_match
@@ -254,7 +252,7 @@ class Reline::LineEditor
254
252
  @rendered_screen = RenderedScreen.new(base_y: 0, lines: [], cursor_y: 0)
255
253
  @input_lines = [[[""], 0, 0]]
256
254
  @input_lines_position = 0
257
- @undoing = false
255
+ @restoring = false
258
256
  @prev_action_state = NullActionState
259
257
  @next_action_state = NullActionState
260
258
  reset_line
@@ -266,7 +264,6 @@ class Reline::LineEditor
266
264
  @line_index = 0
267
265
  @cache.clear
268
266
  @line_backup_in_history = nil
269
- @multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
270
267
  end
271
268
 
272
269
  def multiline_on
@@ -300,8 +297,8 @@ class Reline::LineEditor
300
297
  end
301
298
  end
302
299
 
303
- private def split_by_width(str, max_width, offset: 0)
304
- Reline::Unicode.split_by_width(str, max_width, encoding, offset: offset)
300
+ private def split_line_by_width(str, max_width, offset: 0)
301
+ Reline::Unicode.split_line_by_width(str, max_width, encoding, offset: offset)
305
302
  end
306
303
 
307
304
  def current_byte_pointer_cursor
@@ -391,8 +388,8 @@ class Reline::LineEditor
391
388
  if (cached = cached_wraps[[prompt, line]])
392
389
  next cached
393
390
  end
394
- *wrapped_prompts, code_line_prompt = split_by_width(prompt, width).first.compact
395
- wrapped_lines = split_by_width(line, width, offset: calculate_width(code_line_prompt, true)).first.compact
391
+ *wrapped_prompts, code_line_prompt = split_line_by_width(prompt, width)
392
+ wrapped_lines = split_line_by_width(line, width, offset: calculate_width(code_line_prompt, true))
396
393
  wrapped_prompts.map { |p| [p, ''] } + [[code_line_prompt, wrapped_lines.first]] + wrapped_lines.drop(1).map { |c| ['', c] }
397
394
  end
398
395
  end
@@ -416,7 +413,7 @@ class Reline::LineEditor
416
413
  # do nothing
417
414
  elsif level == :blank
418
415
  Reline::IOGate.move_cursor_column base_x
419
- @output.write "#{Reline::IOGate.reset_color_sequence}#{' ' * width}"
416
+ Reline::IOGate.write "#{Reline::IOGate.reset_color_sequence}#{' ' * width}"
420
417
  else
421
418
  x, w, content = new_items[level]
422
419
  cover_begin = base_x != 0 && new_levels[base_x - 1] == level
@@ -426,7 +423,7 @@ class Reline::LineEditor
426
423
  content, pos = Reline::Unicode.take_mbchar_range(content, base_x - x, width, cover_begin: cover_begin, cover_end: cover_end, padding: true)
427
424
  end
428
425
  Reline::IOGate.move_cursor_column x + pos
429
- @output.write "#{Reline::IOGate.reset_color_sequence}#{content}#{Reline::IOGate.reset_color_sequence}"
426
+ Reline::IOGate.write "#{Reline::IOGate.reset_color_sequence}#{content}#{Reline::IOGate.reset_color_sequence}"
430
427
  end
431
428
  base_x += width
432
429
  end
@@ -439,8 +436,8 @@ class Reline::LineEditor
439
436
  # Calculate cursor position in word wrapped content.
440
437
  def wrapped_cursor_position
441
438
  prompt_width = calculate_width(prompt_list[@line_index], true)
442
- line_before_cursor = whole_lines[@line_index].byteslice(0, @byte_pointer)
443
- wrapped_line_before_cursor = split_by_width(' ' * prompt_width + line_before_cursor, screen_width).first.compact
439
+ line_before_cursor = Reline::Unicode.escape_for_print(whole_lines[@line_index].byteslice(0, @byte_pointer))
440
+ wrapped_line_before_cursor = split_line_by_width(' ' * prompt_width + line_before_cursor, screen_width)
444
441
  wrapped_cursor_y = wrapped_prompt_and_input_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1
445
442
  wrapped_cursor_x = calculate_width(wrapped_line_before_cursor.last)
446
443
  [wrapped_cursor_x, wrapped_cursor_y]
@@ -462,19 +459,21 @@ class Reline::LineEditor
462
459
  end
463
460
 
464
461
  def render_finished
465
- render_differential([], 0, 0)
466
- lines = @buffer_of_lines.size.times.map do |i|
467
- line = Reline::Unicode.strip_non_printing_start_end(prompt_list[i]) + modified_lines[i]
468
- wrapped_lines, = split_by_width(line, screen_width)
469
- wrapped_lines.last.empty? ? "#{line} " : line
462
+ Reline::IOGate.buffered_output do
463
+ render_differential([], 0, 0)
464
+ lines = @buffer_of_lines.size.times.map do |i|
465
+ line = Reline::Unicode.strip_non_printing_start_end(prompt_list[i]) + modified_lines[i]
466
+ wrapped_lines = split_line_by_width(line, screen_width)
467
+ wrapped_lines.last.empty? ? "#{line} " : line
468
+ end
469
+ Reline::IOGate.write lines.map { |l| "#{l}\r\n" }.join
470
470
  end
471
- @output.puts lines.map { |l| "#{l}\r\n" }.join
472
471
  end
473
472
 
474
473
  def print_nomultiline_prompt
475
474
  Reline::IOGate.disable_auto_linewrap(true) if Reline::IOGate.win?
476
475
  # Readline's test `TestRelineAsReadline#test_readline` requires first output to be prompt, not cursor reset escape sequence.
477
- @output.write Reline::Unicode.strip_non_printing_start_end(@prompt) if @prompt && !@is_multiline
476
+ Reline::IOGate.write Reline::Unicode.strip_non_printing_start_end(@prompt) if @prompt && !@is_multiline
478
477
  ensure
479
478
  Reline::IOGate.disable_auto_linewrap(false) if Reline::IOGate.win?
480
479
  end
@@ -505,7 +504,9 @@ class Reline::LineEditor
505
504
  end
506
505
  end
507
506
 
508
- render_differential new_lines, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top
507
+ Reline::IOGate.buffered_output do
508
+ render_differential new_lines, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top
509
+ end
509
510
  end
510
511
 
511
512
  # Reflects lines to be rendered and new cursor position to the screen
@@ -579,8 +580,9 @@ class Reline::LineEditor
579
580
  @context
580
581
  end
581
582
 
582
- def retrieve_completion_block(set_completion_quote_character = false)
583
- @line_editor.retrieve_completion_block(set_completion_quote_character)
583
+ def retrieve_completion_block(_unused = false)
584
+ preposing, target, postposing, _quote = @line_editor.retrieve_completion_block
585
+ [preposing, target, postposing]
584
586
  end
585
587
 
586
588
  def call_completion_proc_with_checking_args(pre, target, post)
@@ -800,105 +802,73 @@ class Reline::LineEditor
800
802
  @config.editing_mode
801
803
  end
802
804
 
803
- private def menu(_target, list)
805
+ private def menu(list)
804
806
  @menu_info = MenuInfo.new(list)
805
807
  end
806
808
 
807
- private def complete_internal_proc(list, is_menu)
808
- preposing, target, postposing = retrieve_completion_block
809
- candidates = list.select { |i|
810
- if i and not Encoding.compatible?(target.encoding, i.encoding)
811
- raise Encoding::CompatibilityError, "#{target.encoding.name} is not compatible with #{i.encoding.name}"
809
+ private def filter_normalize_candidates(target, list)
810
+ target = target.downcase if @config.completion_ignore_case
811
+ list.select do |item|
812
+ next unless item
813
+ unless Encoding.compatible?(target.encoding, item.encoding)
814
+ # Workaround for Readline test
815
+ if defined?(::Readline) && ::Readline == ::Reline
816
+ raise Encoding::CompatibilityError, "incompatible character encodings: #{target.encoding} and #{item.encoding}"
817
+ end
812
818
  end
819
+
813
820
  if @config.completion_ignore_case
814
- i&.downcase&.start_with?(target.downcase)
821
+ item.downcase.start_with?(target)
815
822
  else
816
- i&.start_with?(target)
817
- end
818
- }.uniq
819
- if is_menu
820
- menu(target, candidates)
821
- return nil
822
- end
823
- completed = candidates.inject { |memo, item|
824
- begin
825
- memo_mbchars = memo.unicode_normalize.grapheme_clusters
826
- item_mbchars = item.unicode_normalize.grapheme_clusters
827
- rescue Encoding::CompatibilityError
828
- memo_mbchars = memo.grapheme_clusters
829
- item_mbchars = item.grapheme_clusters
830
- end
831
- size = [memo_mbchars.size, item_mbchars.size].min
832
- result = +''
833
- size.times do |i|
834
- if @config.completion_ignore_case
835
- if memo_mbchars[i].casecmp?(item_mbchars[i])
836
- result << memo_mbchars[i]
837
- else
838
- break
839
- end
840
- else
841
- if memo_mbchars[i] == item_mbchars[i]
842
- result << memo_mbchars[i]
843
- else
844
- break
845
- end
846
- end
823
+ item.start_with?(target)
847
824
  end
848
- result
849
- }
850
-
851
- [target, preposing, completed, postposing, candidates]
825
+ end.map do |item|
826
+ item.unicode_normalize
827
+ rescue Encoding::CompatibilityError
828
+ item
829
+ end.uniq
852
830
  end
853
831
 
854
- private def perform_completion(list, just_show_list)
832
+ private def perform_completion(preposing, target, postposing, quote, list)
833
+ candidates = filter_normalize_candidates(target, list)
834
+
855
835
  case @completion_state
856
- when CompletionState::NORMAL
857
- @completion_state = CompletionState::COMPLETION
858
836
  when CompletionState::PERFECT_MATCH
859
837
  if @dig_perfect_match_proc
860
- @dig_perfect_match_proc.(@perfect_matched)
861
- else
862
- @completion_state = CompletionState::COMPLETION
838
+ @dig_perfect_match_proc.call(@perfect_matched)
839
+ return
863
840
  end
864
- end
865
- if just_show_list
866
- is_menu = true
867
- elsif @completion_state == CompletionState::MENU
868
- is_menu = true
869
- elsif @completion_state == CompletionState::MENU_WITH_PERFECT_MATCH
870
- is_menu = true
871
- else
872
- is_menu = false
873
- end
874
- result = complete_internal_proc(list, is_menu)
875
- if @completion_state == CompletionState::MENU_WITH_PERFECT_MATCH
841
+ when CompletionState::MENU
842
+ menu(candidates)
843
+ return
844
+ when CompletionState::MENU_WITH_PERFECT_MATCH
845
+ menu(candidates)
876
846
  @completion_state = CompletionState::PERFECT_MATCH
847
+ return
877
848
  end
878
- return if result.nil?
879
- target, preposing, completed, postposing, candidates = result
880
- return if completed.nil?
881
- if target <= completed and (@completion_state == CompletionState::COMPLETION)
882
- append_character = ''
883
- if candidates.include?(completed)
884
- if candidates.one?
885
- append_character = completion_append_character.to_s
886
- @completion_state = CompletionState::PERFECT_MATCH
887
- else
888
- @completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
889
- perform_completion(candidates, true) if @config.show_all_if_ambiguous
890
- end
891
- @perfect_matched = completed
849
+
850
+ completed = Reline::Unicode.common_prefix(candidates, ignore_case: @config.completion_ignore_case)
851
+ return if completed.empty?
852
+
853
+ append_character = ''
854
+ if candidates.include?(completed)
855
+ if candidates.one?
856
+ append_character = quote || completion_append_character.to_s
857
+ @completion_state = CompletionState::PERFECT_MATCH
858
+ elsif @config.show_all_if_ambiguous
859
+ menu(candidates)
860
+ @completion_state = CompletionState::PERFECT_MATCH
892
861
  else
893
- @completion_state = CompletionState::MENU
894
- perform_completion(candidates, true) if @config.show_all_if_ambiguous
895
- end
896
- unless just_show_list
897
- @buffer_of_lines[@line_index] = (preposing + completed + append_character + postposing).split("\n")[@line_index] || String.new(encoding: encoding)
898
- line_to_pointer = (preposing + completed + append_character).split("\n")[@line_index] || String.new(encoding: encoding)
899
- @byte_pointer = line_to_pointer.bytesize
862
+ @completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
900
863
  end
864
+ @perfect_matched = completed
865
+ else
866
+ @completion_state = CompletionState::MENU
867
+ menu(candidates) if @config.show_all_if_ambiguous
901
868
  end
869
+ @buffer_of_lines[@line_index] = (preposing + completed + append_character + postposing).split("\n")[@line_index] || String.new(encoding: encoding)
870
+ line_to_pointer = (preposing + completed + append_character).split("\n")[@line_index] || String.new(encoding: encoding)
871
+ @byte_pointer = line_to_pointer.bytesize
902
872
  end
903
873
 
904
874
  def dialog_proc_scope_completion_journey_data
@@ -927,8 +897,8 @@ class Reline::LineEditor
927
897
  end
928
898
 
929
899
  private def retrieve_completion_journey_state
930
- preposing, target, postposing = retrieve_completion_block
931
- list = call_completion_proc
900
+ preposing, target, postposing, quote = retrieve_completion_block
901
+ list = call_completion_proc(preposing, target, postposing, quote)
932
902
  return unless list.is_a?(Array)
933
903
 
934
904
  candidates = list.select{ |item| item.start_with?(target) }
@@ -1003,10 +973,18 @@ class Reline::LineEditor
1003
973
  @drop_terminate_spaces = false
1004
974
  end
1005
975
 
976
+ ARGUMENT_DIGIT_METHODS = %i[ed_digit vi_zero ed_argument_digit]
977
+ VI_WAITING_ACCEPT_METHODS = %i[vi_change_meta vi_delete_meta vi_yank ed_insert ed_argument_digit]
978
+
1006
979
  private def process_key(key, method_symbol)
1007
- if key.is_a?(Symbol)
1008
- cleanup_waiting
1009
- elsif @waiting_proc
980
+ if @waiting_proc
981
+ cleanup_waiting unless key.size == 1
982
+ end
983
+ if @vi_waiting_operator
984
+ cleanup_waiting unless VI_WAITING_ACCEPT_METHODS.include?(method_symbol) || VI_MOTIONS.include?(method_symbol)
985
+ end
986
+
987
+ if @waiting_proc
1010
988
  old_byte_pointer = @byte_pointer
1011
989
  @waiting_proc.call(key)
1012
990
  if @vi_waiting_operator
@@ -1020,23 +998,14 @@ class Reline::LineEditor
1020
998
  return
1021
999
  end
1022
1000
 
1001
+ # Reject multibyte input (converted to ed_insert) in vi_command mode
1002
+ return if method_symbol == :ed_insert && @config.editing_mode_is?(:vi_command)
1003
+
1023
1004
  if method_symbol and respond_to?(method_symbol, true)
1024
1005
  method_obj = method(method_symbol)
1025
1006
  end
1026
- if method_symbol and key.is_a?(Symbol)
1027
- if @vi_arg and argumentable?(method_obj)
1028
- run_for_operators(key, method_symbol) do |with_operator|
1029
- wrap_method_call(method_symbol, method_obj, key, with_operator)
1030
- end
1031
- else
1032
- wrap_method_call(method_symbol, method_obj, key) if method_obj
1033
- end
1034
- @kill_ring.process
1035
- if @vi_arg
1036
- @vi_arg = nil
1037
- end
1038
- elsif @vi_arg
1039
- if key.chr =~ /[0-9]/
1007
+ if @vi_arg
1008
+ if ARGUMENT_DIGIT_METHODS.include?(method_symbol)
1040
1009
  ed_argument_digit(key)
1041
1010
  else
1042
1011
  if argumentable?(method_obj)
@@ -1045,13 +1014,9 @@ class Reline::LineEditor
1045
1014
  end
1046
1015
  elsif method_obj
1047
1016
  wrap_method_call(method_symbol, method_obj, key)
1048
- else
1049
- ed_insert(key) unless @config.editing_mode_is?(:vi_command)
1050
1017
  end
1051
1018
  @kill_ring.process
1052
- if @vi_arg
1053
- @vi_arg = nil
1054
- end
1019
+ @vi_arg = nil
1055
1020
  end
1056
1021
  elsif method_obj
1057
1022
  if method_symbol == :ed_argument_digit
@@ -1062,30 +1027,6 @@ class Reline::LineEditor
1062
1027
  end
1063
1028
  end
1064
1029
  @kill_ring.process
1065
- else
1066
- ed_insert(key) unless @config.editing_mode_is?(:vi_command)
1067
- end
1068
- end
1069
-
1070
- private def normal_char(key)
1071
- @multibyte_buffer << key.combined_char
1072
- if @multibyte_buffer.size > 1
1073
- if @multibyte_buffer.dup.force_encoding(encoding).valid_encoding?
1074
- process_key(@multibyte_buffer.dup.force_encoding(encoding), nil)
1075
- @multibyte_buffer.clear
1076
- else
1077
- # invalid
1078
- return
1079
- end
1080
- else # single byte
1081
- return if key.char >= 128 # maybe, first byte of multi byte
1082
- method_symbol = @config.editing_mode.get_method(key.combined_char)
1083
- process_key(key.combined_char, method_symbol)
1084
- @multibyte_buffer.clear
1085
- end
1086
- if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
1087
- byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer)
1088
- @byte_pointer -= byte_size
1089
1030
  end
1090
1031
  end
1091
1032
 
@@ -1102,23 +1043,23 @@ class Reline::LineEditor
1102
1043
  def input_key(key)
1103
1044
  save_old_buffer
1104
1045
  @config.reset_oneshot_key_bindings
1105
- @dialogs.each do |dialog|
1106
- if key.char.instance_of?(Symbol) and key.char == dialog.name
1107
- return
1108
- end
1109
- end
1110
1046
  if key.char.nil?
1111
1047
  process_insert(force: true)
1112
1048
  @eof = buffer_empty?
1113
1049
  finish
1114
1050
  return
1115
1051
  end
1052
+ @dialogs.each do |dialog|
1053
+ if key.method_symbol == dialog.name
1054
+ return
1055
+ end
1056
+ end
1116
1057
  @completion_occurs = false
1117
1058
 
1118
- if key.char.is_a?(Symbol)
1119
- process_key(key.char, key.char)
1120
- else
1121
- normal_char(key)
1059
+ process_key(key.char, key.method_symbol)
1060
+ if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
1061
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer)
1062
+ @byte_pointer -= byte_size
1122
1063
  end
1123
1064
 
1124
1065
  @prev_action_state, @next_action_state = @next_action_state, NullActionState
@@ -1128,8 +1069,8 @@ class Reline::LineEditor
1128
1069
  @completion_journey_state = nil
1129
1070
  end
1130
1071
 
1131
- push_input_lines unless @undoing
1132
- @undoing = false
1072
+ push_input_lines unless @restoring
1073
+ @restoring = false
1133
1074
 
1134
1075
  if @in_pasting
1135
1076
  clear_dialogs
@@ -1178,9 +1119,8 @@ class Reline::LineEditor
1178
1119
  end
1179
1120
  end
1180
1121
 
1181
- def call_completion_proc
1182
- result = retrieve_completion_block(true)
1183
- pre, target, post = result
1122
+ def call_completion_proc(pre, target, post, quote)
1123
+ Reline.core.instance_variable_set(:@completion_quote_character, quote)
1184
1124
  result = call_completion_proc_with_checking_args(pre, target, post)
1185
1125
  Reline.core.instance_variable_set(:@completion_quote_character, nil)
1186
1126
  result
@@ -1244,84 +1184,32 @@ class Reline::LineEditor
1244
1184
  process_auto_indent
1245
1185
  end
1246
1186
 
1247
- def set_current_lines(lines, byte_pointer = nil, line_index = 0)
1248
- cursor = current_byte_pointer_cursor
1249
- @buffer_of_lines = lines
1250
- @line_index = line_index
1251
- if byte_pointer
1252
- @byte_pointer = byte_pointer
1253
- else
1254
- calculate_nearest_cursor(cursor)
1255
- end
1256
- process_auto_indent
1257
- end
1258
-
1259
- def retrieve_completion_block(set_completion_quote_character = false)
1260
- if Reline.completer_word_break_characters.empty?
1261
- word_break_regexp = nil
1262
- else
1263
- word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
1264
- end
1265
- if Reline.completer_quote_characters.empty?
1266
- quote_characters_regexp = nil
1267
- else
1268
- quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
1269
- end
1270
- before = current_line.byteslice(0, @byte_pointer)
1271
- rest = nil
1272
- break_pointer = nil
1187
+ def retrieve_completion_block
1188
+ quote_characters = Reline.completer_quote_characters
1189
+ before = current_line.byteslice(0, @byte_pointer).grapheme_clusters
1273
1190
  quote = nil
1274
- closing_quote = nil
1275
- escaped_quote = nil
1276
- i = 0
1277
- while i < @byte_pointer do
1278
- slice = current_line.byteslice(i, @byte_pointer - i)
1279
- unless slice.valid_encoding?
1280
- i += 1
1281
- next
1282
- end
1283
- if quote and slice.start_with?(closing_quote)
1284
- quote = nil
1285
- i += 1
1286
- rest = nil
1287
- elsif quote and slice.start_with?(escaped_quote)
1288
- # skip
1289
- i += 2
1290
- elsif quote_characters_regexp and slice =~ quote_characters_regexp # find new "
1291
- rest = $'
1292
- quote = $&
1293
- closing_quote = /(?!\\)#{Regexp.escape(quote)}/
1294
- escaped_quote = /\\#{Regexp.escape(quote)}/
1295
- i += 1
1296
- break_pointer = i - 1
1297
- elsif word_break_regexp and not quote and slice =~ word_break_regexp
1298
- rest = $'
1299
- i += 1
1300
- before = current_line.byteslice(i, @byte_pointer - i)
1301
- break_pointer = i
1302
- else
1303
- i += 1
1304
- end
1305
- end
1306
- postposing = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
1307
- if rest
1308
- preposing = current_line.byteslice(0, break_pointer)
1309
- target = rest
1310
- if set_completion_quote_character and quote
1311
- Reline.core.instance_variable_set(:@completion_quote_character, quote)
1312
- if postposing !~ /(?!\\)#{Regexp.escape(quote)}/ # closing quote
1313
- insert_text(quote)
1191
+ # Calcualte closing quote when cursor is at the end of the line
1192
+ if current_line.bytesize == @byte_pointer && !quote_characters.empty?
1193
+ escaped = false
1194
+ before.each do |c|
1195
+ if escaped
1196
+ escaped = false
1197
+ next
1198
+ elsif c == '\\'
1199
+ escaped = true
1200
+ elsif quote
1201
+ quote = nil if c == quote
1202
+ elsif quote_characters.include?(c)
1203
+ quote = c
1314
1204
  end
1315
1205
  end
1316
- else
1317
- preposing = ''
1318
- if break_pointer
1319
- preposing = current_line.byteslice(0, break_pointer)
1320
- else
1321
- preposing = ''
1322
- end
1323
- target = before
1324
1206
  end
1207
+
1208
+ word_break_characters = quote_characters + Reline.completer_word_break_characters
1209
+ break_index = before.rindex { |c| word_break_characters.include?(c) || quote_characters.include?(c) } || -1
1210
+ preposing = before.take(break_index + 1).join
1211
+ target = before.drop(break_index + 1).join
1212
+ postposing = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
1325
1213
  lines = whole_lines
1326
1214
  if @line_index > 0
1327
1215
  preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
@@ -1329,7 +1217,7 @@ class Reline::LineEditor
1329
1217
  if (lines.size - 1) > @line_index
1330
1218
  postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
1331
1219
  end
1332
- [preposing.encode(encoding), target.encode(encoding), postposing.encode(encoding)]
1220
+ [preposing.encode(encoding), target.encode(encoding), postposing.encode(encoding), quote&.encode(encoding)]
1333
1221
  end
1334
1222
 
1335
1223
  def confirm_multiline_termination
@@ -1338,7 +1226,6 @@ class Reline::LineEditor
1338
1226
  end
1339
1227
 
1340
1228
  def insert_multiline_text(text)
1341
- save_old_buffer
1342
1229
  pre = @buffer_of_lines[@line_index].byteslice(0, @byte_pointer)
1343
1230
  post = @buffer_of_lines[@line_index].byteslice(@byte_pointer..)
1344
1231
  lines = (pre + Reline::Unicode.safe_encode(text, encoding).gsub(/\r\n?/, "\n") + post).split("\n", -1)
@@ -1346,7 +1233,6 @@ class Reline::LineEditor
1346
1233
  @buffer_of_lines[@line_index, 1] = lines
1347
1234
  @line_index += lines.size - 1
1348
1235
  @byte_pointer = @buffer_of_lines[@line_index].bytesize - post.bytesize
1349
- push_input_lines
1350
1236
  end
1351
1237
 
1352
1238
  def insert_text(text)
@@ -1460,10 +1346,11 @@ class Reline::LineEditor
1460
1346
  @completion_occurs = move_completed_list(:down)
1461
1347
  else
1462
1348
  @completion_journey_state = nil
1463
- result = call_completion_proc
1349
+ pre, target, post, quote = retrieve_completion_block
1350
+ result = call_completion_proc(pre, target, post, quote)
1464
1351
  if result.is_a?(Array)
1465
1352
  @completion_occurs = true
1466
- perform_completion(result, false)
1353
+ perform_completion(pre, target, post, quote, result)
1467
1354
  end
1468
1355
  end
1469
1356
  end
@@ -1511,21 +1398,11 @@ class Reline::LineEditor
1511
1398
  # digit or if the existing argument is already greater than a
1512
1399
  # million.
1513
1400
  # GNU Readline:: +self-insert+ (a, b, A, 1, !, …) Insert yourself.
1514
- private def ed_insert(key)
1515
- if key.instance_of?(String)
1516
- begin
1517
- key.encode(Encoding::UTF_8)
1518
- rescue Encoding::UndefinedConversionError
1519
- return
1520
- end
1521
- str = key
1522
- else
1523
- begin
1524
- key.chr.encode(Encoding::UTF_8)
1525
- rescue Encoding::UndefinedConversionError
1526
- return
1527
- end
1528
- str = key.chr
1401
+ private def ed_insert(str)
1402
+ begin
1403
+ str.encode(Encoding::UTF_8)
1404
+ rescue Encoding::UndefinedConversionError
1405
+ return
1529
1406
  end
1530
1407
  if @in_pasting
1531
1408
  @continuous_insertion_buffer << str
@@ -1539,21 +1416,16 @@ class Reline::LineEditor
1539
1416
  alias_method :ed_digit, :ed_insert
1540
1417
  alias_method :self_insert, :ed_insert
1541
1418
 
1542
- private def ed_quoted_insert(str, arg: 1)
1543
- @waiting_proc = proc { |key|
1544
- arg.times do
1545
- if key == "\C-j".ord or key == "\C-m".ord
1546
- key_newline(key)
1547
- elsif key == 0
1548
- # Ignore NUL.
1549
- else
1550
- ed_insert(key)
1551
- end
1419
+ private def insert_raw_char(str, arg: 1)
1420
+ arg.times do
1421
+ if str == "\C-j" or str == "\C-m"
1422
+ key_newline(str)
1423
+ elsif str != "\0"
1424
+ # Ignore NUL.
1425
+ ed_insert(str)
1552
1426
  end
1553
- @waiting_proc = nil
1554
- }
1427
+ end
1555
1428
  end
1556
- alias_method :quoted_insert, :ed_quoted_insert
1557
1429
 
1558
1430
  private def ed_next_char(key, arg: 1)
1559
1431
  byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
@@ -1582,7 +1454,7 @@ class Reline::LineEditor
1582
1454
  alias_method :backward_char, :ed_prev_char
1583
1455
 
1584
1456
  private def vi_first_print(key)
1585
- @byte_pointer, = Reline::Unicode.vi_first_print(current_line)
1457
+ @byte_pointer = Reline::Unicode.vi_first_print(current_line)
1586
1458
  end
1587
1459
 
1588
1460
  private def ed_move_to_beg(key)
@@ -1598,26 +1470,21 @@ class Reline::LineEditor
1598
1470
 
1599
1471
  private def generate_searcher(search_key)
1600
1472
  search_word = String.new(encoding: encoding)
1601
- multibyte_buf = String.new(encoding: 'ASCII-8BIT')
1602
1473
  hit_pointer = nil
1603
1474
  lambda do |key|
1604
1475
  search_again = false
1605
1476
  case key
1606
- when "\C-h".ord, "\C-?".ord
1477
+ when "\C-h", "\C-?"
1607
1478
  grapheme_clusters = search_word.grapheme_clusters
1608
1479
  if grapheme_clusters.size > 0
1609
1480
  grapheme_clusters.pop
1610
1481
  search_word = grapheme_clusters.join
1611
1482
  end
1612
- when "\C-r".ord, "\C-s".ord
1483
+ when "\C-r", "\C-s"
1613
1484
  search_again = true if search_key == key
1614
1485
  search_key = key
1615
1486
  else
1616
- multibyte_buf << key
1617
- if multibyte_buf.dup.force_encoding(encoding).valid_encoding?
1618
- search_word << multibyte_buf.dup.force_encoding(encoding)
1619
- multibyte_buf.clear
1620
- end
1487
+ search_word << key
1621
1488
  end
1622
1489
  hit = nil
1623
1490
  if not search_word.empty? and @line_backup_in_history&.include?(search_word)
@@ -1630,10 +1497,10 @@ class Reline::LineEditor
1630
1497
  end
1631
1498
  if @history_pointer
1632
1499
  case search_key
1633
- when "\C-r".ord
1500
+ when "\C-r"
1634
1501
  history_pointer_base = 0
1635
1502
  history = Reline::HISTORY[0..(@history_pointer - 1)]
1636
- when "\C-s".ord
1503
+ when "\C-s"
1637
1504
  history_pointer_base = @history_pointer + 1
1638
1505
  history = Reline::HISTORY[(@history_pointer + 1)..-1]
1639
1506
  end
@@ -1643,10 +1510,10 @@ class Reline::LineEditor
1643
1510
  end
1644
1511
  elsif @history_pointer
1645
1512
  case search_key
1646
- when "\C-r".ord
1513
+ when "\C-r"
1647
1514
  history_pointer_base = 0
1648
1515
  history = Reline::HISTORY[0..@history_pointer]
1649
- when "\C-s".ord
1516
+ when "\C-s"
1650
1517
  history_pointer_base = @history_pointer
1651
1518
  history = Reline::HISTORY[@history_pointer..-1]
1652
1519
  end
@@ -1655,11 +1522,11 @@ class Reline::LineEditor
1655
1522
  history = Reline::HISTORY
1656
1523
  end
1657
1524
  case search_key
1658
- when "\C-r".ord
1525
+ when "\C-r"
1659
1526
  hit_index = history.rindex { |item|
1660
1527
  item.include?(search_word)
1661
1528
  }
1662
- when "\C-s".ord
1529
+ when "\C-s"
1663
1530
  hit_index = history.index { |item|
1664
1531
  item.include?(search_word)
1665
1532
  }
@@ -1670,9 +1537,9 @@ class Reline::LineEditor
1670
1537
  end
1671
1538
  end
1672
1539
  case search_key
1673
- when "\C-r".ord
1540
+ when "\C-r"
1674
1541
  prompt_name = 'reverse-i-search'
1675
- when "\C-s".ord
1542
+ when "\C-s"
1676
1543
  prompt_name = 'i-search'
1677
1544
  end
1678
1545
  prompt_name = "failed #{prompt_name}" unless hit
@@ -1684,16 +1551,15 @@ class Reline::LineEditor
1684
1551
  backup = @buffer_of_lines.dup, @line_index, @byte_pointer, @history_pointer, @line_backup_in_history
1685
1552
  searcher = generate_searcher(key)
1686
1553
  @searching_prompt = "(reverse-i-search)`': "
1687
- termination_keys = ["\C-j".ord]
1688
- termination_keys.concat(@config.isearch_terminators.chars.map(&:ord)) if @config.isearch_terminators
1554
+ termination_keys = ["\C-j"]
1555
+ termination_keys.concat(@config.isearch_terminators.chars) if @config.isearch_terminators
1689
1556
  @waiting_proc = ->(k) {
1690
- chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
1691
- if k == "\C-g".ord
1557
+ if k == "\C-g"
1692
1558
  # cancel search and restore buffer
1693
1559
  @buffer_of_lines, @line_index, @byte_pointer, @history_pointer, @line_backup_in_history = backup
1694
1560
  @searching_prompt = nil
1695
1561
  @waiting_proc = nil
1696
- elsif !termination_keys.include?(k) && (chr.match?(/[[:print:]]/) || k == "\C-h".ord || k == "\C-?".ord || k == "\C-r".ord || k == "\C-s".ord)
1562
+ elsif !termination_keys.include?(k) && (k.match?(/[[:print:]]/) || k == "\C-h" || k == "\C-?" || k == "\C-r" || k == "\C-s")
1697
1563
  search_word, prompt_name, hit_pointer = searcher.call(k)
1698
1564
  Reline.last_incremental_search = search_word
1699
1565
  @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
@@ -1909,7 +1775,7 @@ class Reline::LineEditor
1909
1775
  alias_method :kill_whole_line, :em_kill_line
1910
1776
 
1911
1777
  private def em_delete(key)
1912
- if buffer_empty? and key == "\C-d".ord
1778
+ if buffer_empty? and key == "\C-d"
1913
1779
  @eof = true
1914
1780
  finish
1915
1781
  elsif @byte_pointer < current_line.bytesize
@@ -1927,9 +1793,11 @@ class Reline::LineEditor
1927
1793
  if current_line.empty? or @byte_pointer < current_line.bytesize
1928
1794
  em_delete(key)
1929
1795
  elsif !@config.autocompletion # show completed list
1930
- result = call_completion_proc
1796
+ pre, target, post, quote = retrieve_completion_block
1797
+ result = call_completion_proc(pre, target, post, quote)
1931
1798
  if result.is_a?(Array)
1932
- perform_completion(result, true)
1799
+ candidates = filter_normalize_candidates(target, result)
1800
+ menu(candidates)
1933
1801
  end
1934
1802
  end
1935
1803
  end
@@ -1961,7 +1829,7 @@ class Reline::LineEditor
1961
1829
 
1962
1830
  private def em_next_word(key)
1963
1831
  if current_line.bytesize > @byte_pointer
1964
- byte_size, _ = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1832
+ byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1965
1833
  @byte_pointer += byte_size
1966
1834
  end
1967
1835
  end
@@ -1969,7 +1837,7 @@ class Reline::LineEditor
1969
1837
 
1970
1838
  private def ed_prev_word(key)
1971
1839
  if @byte_pointer > 0
1972
- byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
1840
+ byte_size = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
1973
1841
  @byte_pointer -= byte_size
1974
1842
  end
1975
1843
  end
@@ -1977,7 +1845,7 @@ class Reline::LineEditor
1977
1845
 
1978
1846
  private def em_delete_next_word(key)
1979
1847
  if current_line.bytesize > @byte_pointer
1980
- byte_size, _ = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1848
+ byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1981
1849
  line, word = byteslice!(current_line, @byte_pointer, byte_size)
1982
1850
  set_current_line(line)
1983
1851
  @kill_ring.append(word)
@@ -1987,7 +1855,7 @@ class Reline::LineEditor
1987
1855
 
1988
1856
  private def ed_delete_prev_word(key)
1989
1857
  if @byte_pointer > 0
1990
- byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
1858
+ byte_size = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
1991
1859
  line, word = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
1992
1860
  set_current_line(line, @byte_pointer - byte_size)
1993
1861
  @kill_ring.append(word, true)
@@ -2027,7 +1895,7 @@ class Reline::LineEditor
2027
1895
 
2028
1896
  private def em_capitol_case(key)
2029
1897
  if current_line.bytesize > @byte_pointer
2030
- byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(current_line, @byte_pointer)
1898
+ byte_size, new_str = Reline::Unicode.em_forward_word_with_capitalization(current_line, @byte_pointer)
2031
1899
  before = current_line.byteslice(0, @byte_pointer)
2032
1900
  after = current_line.byteslice((@byte_pointer + byte_size)..-1)
2033
1901
  set_current_line(before + new_str + after, @byte_pointer + new_str.bytesize)
@@ -2037,7 +1905,7 @@ class Reline::LineEditor
2037
1905
 
2038
1906
  private def em_lower_case(key)
2039
1907
  if current_line.bytesize > @byte_pointer
2040
- byte_size, = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1908
+ byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
2041
1909
  part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2042
1910
  mbchar =~ /[A-Z]/ ? mbchar.downcase : mbchar
2043
1911
  }.join
@@ -2050,7 +1918,7 @@ class Reline::LineEditor
2050
1918
 
2051
1919
  private def em_upper_case(key)
2052
1920
  if current_line.bytesize > @byte_pointer
2053
- byte_size, = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1921
+ byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
2054
1922
  part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2055
1923
  mbchar =~ /[a-z]/ ? mbchar.upcase : mbchar
2056
1924
  }.join
@@ -2063,7 +1931,7 @@ class Reline::LineEditor
2063
1931
 
2064
1932
  private def em_kill_region(key)
2065
1933
  if @byte_pointer > 0
2066
- byte_size, _ = Reline::Unicode.em_big_backward_word(current_line, @byte_pointer)
1934
+ byte_size = Reline::Unicode.em_big_backward_word(current_line, @byte_pointer)
2067
1935
  line, deleted = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
2068
1936
  set_current_line(line, @byte_pointer - byte_size)
2069
1937
  @kill_ring.append(deleted, true)
@@ -2094,7 +1962,7 @@ class Reline::LineEditor
2094
1962
 
2095
1963
  private def vi_next_word(key, arg: 1)
2096
1964
  if current_line.bytesize > @byte_pointer
2097
- byte_size, _ = Reline::Unicode.vi_forward_word(current_line, @byte_pointer, @drop_terminate_spaces)
1965
+ byte_size = Reline::Unicode.vi_forward_word(current_line, @byte_pointer, @drop_terminate_spaces)
2098
1966
  @byte_pointer += byte_size
2099
1967
  end
2100
1968
  arg -= 1
@@ -2103,7 +1971,7 @@ class Reline::LineEditor
2103
1971
 
2104
1972
  private def vi_prev_word(key, arg: 1)
2105
1973
  if @byte_pointer > 0
2106
- byte_size, _ = Reline::Unicode.vi_backward_word(current_line, @byte_pointer)
1974
+ byte_size = Reline::Unicode.vi_backward_word(current_line, @byte_pointer)
2107
1975
  @byte_pointer -= byte_size
2108
1976
  end
2109
1977
  arg -= 1
@@ -2112,7 +1980,7 @@ class Reline::LineEditor
2112
1980
 
2113
1981
  private def vi_end_word(key, arg: 1, inclusive: false)
2114
1982
  if current_line.bytesize > @byte_pointer
2115
- byte_size, _ = Reline::Unicode.vi_forward_end_word(current_line, @byte_pointer)
1983
+ byte_size = Reline::Unicode.vi_forward_end_word(current_line, @byte_pointer)
2116
1984
  @byte_pointer += byte_size
2117
1985
  end
2118
1986
  arg -= 1
@@ -2127,7 +1995,7 @@ class Reline::LineEditor
2127
1995
 
2128
1996
  private def vi_next_big_word(key, arg: 1)
2129
1997
  if current_line.bytesize > @byte_pointer
2130
- byte_size, _ = Reline::Unicode.vi_big_forward_word(current_line, @byte_pointer)
1998
+ byte_size = Reline::Unicode.vi_big_forward_word(current_line, @byte_pointer)
2131
1999
  @byte_pointer += byte_size
2132
2000
  end
2133
2001
  arg -= 1
@@ -2136,7 +2004,7 @@ class Reline::LineEditor
2136
2004
 
2137
2005
  private def vi_prev_big_word(key, arg: 1)
2138
2006
  if @byte_pointer > 0
2139
- byte_size, _ = Reline::Unicode.vi_big_backward_word(current_line, @byte_pointer)
2007
+ byte_size = Reline::Unicode.vi_big_backward_word(current_line, @byte_pointer)
2140
2008
  @byte_pointer -= byte_size
2141
2009
  end
2142
2010
  arg -= 1
@@ -2145,7 +2013,7 @@ class Reline::LineEditor
2145
2013
 
2146
2014
  private def vi_end_big_word(key, arg: 1, inclusive: false)
2147
2015
  if current_line.bytesize > @byte_pointer
2148
- byte_size, _ = Reline::Unicode.vi_big_forward_end_word(current_line, @byte_pointer)
2016
+ byte_size = Reline::Unicode.vi_big_forward_end_word(current_line, @byte_pointer)
2149
2017
  @byte_pointer += byte_size
2150
2018
  end
2151
2019
  arg -= 1
@@ -2325,20 +2193,9 @@ class Reline::LineEditor
2325
2193
  end
2326
2194
 
2327
2195
  private def ed_argument_digit(key)
2328
- if @vi_arg.nil?
2329
- if key.chr.to_i.zero?
2330
- if key.anybits?(0b10000000)
2331
- unescaped_key = key ^ 0b10000000
2332
- unless unescaped_key.chr.to_i.zero?
2333
- @vi_arg = unescaped_key.chr.to_i
2334
- end
2335
- end
2336
- else
2337
- @vi_arg = key.chr.to_i
2338
- end
2339
- else
2340
- @vi_arg = @vi_arg * 10 + key.chr.to_i
2341
- end
2196
+ # key is expected to be `ESC digit` or `digit`
2197
+ num = key[/\d/].to_i
2198
+ @vi_arg = (@vi_arg || 0) * 10 + num
2342
2199
  end
2343
2200
 
2344
2201
  private def vi_to_column(key, arg: 0)
@@ -2357,7 +2214,7 @@ class Reline::LineEditor
2357
2214
  before = current_line.byteslice(0, @byte_pointer)
2358
2215
  remaining_point = @byte_pointer + byte_size
2359
2216
  after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
2360
- set_current_line(before + k.chr + after)
2217
+ set_current_line(before + k + after)
2361
2218
  @waiting_proc = nil
2362
2219
  elsif arg > 1
2363
2220
  byte_size = 0
@@ -2367,7 +2224,7 @@ class Reline::LineEditor
2367
2224
  before = current_line.byteslice(0, @byte_pointer)
2368
2225
  remaining_point = @byte_pointer + byte_size
2369
2226
  after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
2370
- replaced = k.chr * arg
2227
+ replaced = k * arg
2371
2228
  set_current_line(before + replaced + after, @byte_pointer + replaced.bytesize)
2372
2229
  @waiting_proc = nil
2373
2230
  end
@@ -2383,11 +2240,6 @@ class Reline::LineEditor
2383
2240
  end
2384
2241
 
2385
2242
  private def search_next_char(key, arg, need_prev_char: false, inclusive: false)
2386
- if key.instance_of?(String)
2387
- inputted_char = key
2388
- else
2389
- inputted_char = key.chr
2390
- end
2391
2243
  prev_total = nil
2392
2244
  total = nil
2393
2245
  found = false
@@ -2398,7 +2250,7 @@ class Reline::LineEditor
2398
2250
  width = Reline::Unicode.get_mbchar_width(mbchar)
2399
2251
  total = [mbchar.bytesize, width]
2400
2252
  else
2401
- if inputted_char == mbchar
2253
+ if key == mbchar
2402
2254
  arg -= 1
2403
2255
  if arg.zero?
2404
2256
  found = true
@@ -2435,11 +2287,6 @@ class Reline::LineEditor
2435
2287
  end
2436
2288
 
2437
2289
  private def search_prev_char(key, arg, need_next_char = false)
2438
- if key.instance_of?(String)
2439
- inputted_char = key
2440
- else
2441
- inputted_char = key.chr
2442
- end
2443
2290
  prev_total = nil
2444
2291
  total = nil
2445
2292
  found = false
@@ -2450,7 +2297,7 @@ class Reline::LineEditor
2450
2297
  width = Reline::Unicode.get_mbchar_width(mbchar)
2451
2298
  total = [mbchar.bytesize, width]
2452
2299
  else
2453
- if inputted_char == mbchar
2300
+ if key == mbchar
2454
2301
  arg -= 1
2455
2302
  if arg.zero?
2456
2303
  found = true
@@ -2502,24 +2349,23 @@ class Reline::LineEditor
2502
2349
  @config.editing_mode = :vi_insert
2503
2350
  end
2504
2351
 
2505
- private def undo(_key)
2506
- @undoing = true
2352
+ private def move_undo_redo(direction)
2353
+ @restoring = true
2354
+ return unless (0..@input_lines.size - 1).cover?(@input_lines_position + direction)
2507
2355
 
2508
- return if @input_lines_position <= 0
2356
+ @input_lines_position += direction
2357
+ buffer_of_lines, byte_pointer, line_index = @input_lines[@input_lines_position]
2358
+ @buffer_of_lines = buffer_of_lines.dup
2359
+ @line_index = line_index
2360
+ @byte_pointer = byte_pointer
2361
+ end
2509
2362
 
2510
- @input_lines_position -= 1
2511
- target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position]
2512
- set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y)
2363
+ private def undo(_key)
2364
+ move_undo_redo(-1)
2513
2365
  end
2514
2366
 
2515
2367
  private def redo(_key)
2516
- @undoing = true
2517
-
2518
- return if @input_lines_position >= @input_lines.size - 1
2519
-
2520
- @input_lines_position += 1
2521
- target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position]
2522
- set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y)
2368
+ move_undo_redo(+1)
2523
2369
  end
2524
2370
 
2525
2371
  private def prev_action_state_value(type)