reline 0.5.12 → 0.6.1

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
@@ -251,9 +250,9 @@ class Reline::LineEditor
251
250
  @resized = false
252
251
  @cache = {}
253
252
  @rendered_screen = RenderedScreen.new(base_y: 0, lines: [], cursor_y: 0)
254
- @input_lines = [[[""], 0, 0]]
255
- @input_lines_position = 0
256
- @undoing = false
253
+ @undo_redo_history = [[[""], 0, 0]]
254
+ @undo_redo_index = 0
255
+ @restoring = false
257
256
  @prev_action_state = NullActionState
258
257
  @next_action_state = NullActionState
259
258
  reset_line
@@ -414,7 +413,7 @@ class Reline::LineEditor
414
413
  # do nothing
415
414
  elsif level == :blank
416
415
  Reline::IOGate.move_cursor_column base_x
417
- @output.write "#{Reline::IOGate.reset_color_sequence}#{' ' * width}"
416
+ Reline::IOGate.write "#{Reline::IOGate.reset_color_sequence}#{' ' * width}"
418
417
  else
419
418
  x, w, content = new_items[level]
420
419
  cover_begin = base_x != 0 && new_levels[base_x - 1] == level
@@ -424,7 +423,7 @@ class Reline::LineEditor
424
423
  content, pos = Reline::Unicode.take_mbchar_range(content, base_x - x, width, cover_begin: cover_begin, cover_end: cover_end, padding: true)
425
424
  end
426
425
  Reline::IOGate.move_cursor_column x + pos
427
- @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}"
428
427
  end
429
428
  base_x += width
430
429
  end
@@ -437,7 +436,7 @@ class Reline::LineEditor
437
436
  # Calculate cursor position in word wrapped content.
438
437
  def wrapped_cursor_position
439
438
  prompt_width = calculate_width(prompt_list[@line_index], true)
440
- line_before_cursor = whole_lines[@line_index].byteslice(0, @byte_pointer)
439
+ line_before_cursor = Reline::Unicode.escape_for_print(whole_lines[@line_index].byteslice(0, @byte_pointer))
441
440
  wrapped_line_before_cursor = split_line_by_width(' ' * prompt_width + line_before_cursor, screen_width)
442
441
  wrapped_cursor_y = wrapped_prompt_and_input_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1
443
442
  wrapped_cursor_x = calculate_width(wrapped_line_before_cursor.last)
@@ -460,19 +459,21 @@ class Reline::LineEditor
460
459
  end
461
460
 
462
461
  def render_finished
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
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
468
470
  end
469
- @output.puts lines.map { |l| "#{l}\r\n" }.join
470
471
  end
471
472
 
472
473
  def print_nomultiline_prompt
473
474
  Reline::IOGate.disable_auto_linewrap(true) if Reline::IOGate.win?
474
475
  # Readline's test `TestRelineAsReadline#test_readline` requires first output to be prompt, not cursor reset escape sequence.
475
- @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
476
477
  ensure
477
478
  Reline::IOGate.disable_auto_linewrap(false) if Reline::IOGate.win?
478
479
  end
@@ -503,7 +504,9 @@ class Reline::LineEditor
503
504
  end
504
505
  end
505
506
 
506
- 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
507
510
  end
508
511
 
509
512
  # Reflects lines to be rendered and new cursor position to the screen
@@ -539,6 +542,7 @@ class Reline::LineEditor
539
542
  Reline::IOGate.show_cursor
540
543
  end
541
544
  Reline::IOGate.move_cursor_column new_cursor_x
545
+ new_cursor_y = new_cursor_y.clamp(0, screen_height - 1)
542
546
  Reline::IOGate.move_cursor_down new_cursor_y - cursor_y
543
547
  @rendered_screen.cursor_y = new_cursor_y
544
548
  ensure
@@ -807,11 +811,11 @@ class Reline::LineEditor
807
811
  target = target.downcase if @config.completion_ignore_case
808
812
  list.select do |item|
809
813
  next unless item
810
-
811
814
  unless Encoding.compatible?(target.encoding, item.encoding)
812
- # Crash with Encoding::CompatibilityError is required by readline-ext/test/readline/test_readline.rb
813
- # TODO: fix the test
814
- raise Encoding::CompatibilityError, "#{target.encoding.name} is not compatible with #{item.encoding.name}"
815
+ # Workaround for Readline test
816
+ if defined?(::Readline) && ::Readline == ::Reline
817
+ raise Encoding::CompatibilityError, "incompatible character encodings: #{target.encoding} and #{item.encoding}"
818
+ end
815
819
  end
816
820
 
817
821
  if @config.completion_ignore_case
@@ -908,28 +912,36 @@ class Reline::LineEditor
908
912
  )
909
913
  end
910
914
 
911
- private def run_for_operators(key, method_symbol, &block)
915
+ private def run_for_operators(key, method_symbol)
916
+ # Reject multibyte input (converted to ed_insert) in vi_command mode
917
+ return if method_symbol == :ed_insert && @config.editing_mode_is?(:vi_command) && !@waiting_proc
918
+
919
+ if ARGUMENT_DIGIT_METHODS.include?(method_symbol) && !@waiting_proc
920
+ wrap_method_call(method_symbol, key, false)
921
+ return
922
+ end
923
+
912
924
  if @vi_waiting_operator
913
- if VI_MOTIONS.include?(method_symbol)
925
+ if @waiting_proc || VI_MOTIONS.include?(method_symbol)
914
926
  old_byte_pointer = @byte_pointer
915
927
  @vi_arg = (@vi_arg || 1) * @vi_waiting_operator_arg
916
- block.(true)
928
+ wrap_method_call(method_symbol, key, true)
917
929
  unless @waiting_proc
918
930
  byte_pointer_diff = @byte_pointer - old_byte_pointer
919
931
  @byte_pointer = old_byte_pointer
920
- method_obj = method(@vi_waiting_operator)
921
- wrap_method_call(@vi_waiting_operator, method_obj, byte_pointer_diff)
932
+ __send__(@vi_waiting_operator, byte_pointer_diff)
922
933
  cleanup_waiting
923
934
  end
924
935
  else
925
936
  # Ignores operator when not motion is given.
926
- block.(false)
937
+ wrap_method_call(method_symbol, key, false)
927
938
  cleanup_waiting
928
939
  end
929
- @vi_arg = nil
930
940
  else
931
- block.(false)
941
+ wrap_method_call(method_symbol, key, false)
932
942
  end
943
+ @vi_arg = nil
944
+ @kill_ring.process
933
945
  end
934
946
 
935
947
  private def argumentable?(method_obj)
@@ -942,20 +954,23 @@ class Reline::LineEditor
942
954
  method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :inclusive }
943
955
  end
944
956
 
945
- def wrap_method_call(method_symbol, method_obj, key, with_operator = false)
946
- if @config.editing_mode_is?(:emacs, :vi_insert) and @vi_waiting_operator.nil?
947
- not_insertion = method_symbol != :ed_insert
948
- process_insert(force: not_insertion)
957
+ def wrap_method_call(method_symbol, key, with_operator)
958
+ if @waiting_proc
959
+ @waiting_proc.call(key)
960
+ return
949
961
  end
962
+
963
+ return unless respond_to?(method_symbol, true)
964
+ method_obj = method(method_symbol)
950
965
  if @vi_arg and argumentable?(method_obj)
951
- if with_operator and inclusive?(method_obj)
952
- method_obj.(key, arg: @vi_arg, inclusive: true)
966
+ if inclusive?(method_obj)
967
+ method_obj.(key, arg: @vi_arg, inclusive: with_operator)
953
968
  else
954
969
  method_obj.(key, arg: @vi_arg)
955
970
  end
956
971
  else
957
- if with_operator and inclusive?(method_obj)
958
- method_obj.(key, inclusive: true)
972
+ if inclusive?(method_obj)
973
+ method_obj.(key, inclusive: with_operator)
959
974
  else
960
975
  method_obj.(key)
961
976
  end
@@ -970,81 +985,20 @@ class Reline::LineEditor
970
985
  @drop_terminate_spaces = false
971
986
  end
972
987
 
973
- private def process_key(key, method_symbol)
974
- if key.is_a?(Symbol)
975
- cleanup_waiting
976
- elsif @waiting_proc
977
- old_byte_pointer = @byte_pointer
978
- @waiting_proc.call(key)
979
- if @vi_waiting_operator
980
- byte_pointer_diff = @byte_pointer - old_byte_pointer
981
- @byte_pointer = old_byte_pointer
982
- method_obj = method(@vi_waiting_operator)
983
- wrap_method_call(@vi_waiting_operator, method_obj, byte_pointer_diff)
984
- cleanup_waiting
985
- end
986
- @kill_ring.process
987
- return
988
- end
988
+ ARGUMENT_DIGIT_METHODS = %i[ed_digit vi_zero ed_argument_digit]
989
+ VI_WAITING_ACCEPT_METHODS = %i[vi_change_meta vi_delete_meta vi_yank ed_insert ed_argument_digit]
989
990
 
990
- if method_symbol and respond_to?(method_symbol, true)
991
- method_obj = method(method_symbol)
991
+ private def process_key(key, method_symbol)
992
+ if @waiting_proc
993
+ cleanup_waiting unless key.size == 1
992
994
  end
993
- if method_symbol and key.is_a?(Symbol)
994
- if @vi_arg and argumentable?(method_obj)
995
- run_for_operators(key, method_symbol) do |with_operator|
996
- wrap_method_call(method_symbol, method_obj, key, with_operator)
997
- end
998
- else
999
- wrap_method_call(method_symbol, method_obj, key) if method_obj
1000
- end
1001
- @kill_ring.process
1002
- if @vi_arg
1003
- @vi_arg = nil
1004
- end
1005
- elsif @vi_arg
1006
- if key.chr =~ /[0-9]/
1007
- ed_argument_digit(key)
1008
- else
1009
- if argumentable?(method_obj)
1010
- run_for_operators(key, method_symbol) do |with_operator|
1011
- wrap_method_call(method_symbol, method_obj, key, with_operator)
1012
- end
1013
- elsif method_obj
1014
- wrap_method_call(method_symbol, method_obj, key)
1015
- else
1016
- ed_insert(key) unless @config.editing_mode_is?(:vi_command)
1017
- end
1018
- @kill_ring.process
1019
- if @vi_arg
1020
- @vi_arg = nil
1021
- end
1022
- end
1023
- elsif method_obj
1024
- if method_symbol == :ed_argument_digit
1025
- wrap_method_call(method_symbol, method_obj, key)
1026
- else
1027
- run_for_operators(key, method_symbol) do |with_operator|
1028
- wrap_method_call(method_symbol, method_obj, key, with_operator)
1029
- end
1030
- end
1031
- @kill_ring.process
1032
- else
1033
- ed_insert(key) unless @config.editing_mode_is?(:vi_command)
995
+ if @vi_waiting_operator
996
+ cleanup_waiting unless VI_WAITING_ACCEPT_METHODS.include?(method_symbol) || VI_MOTIONS.include?(method_symbol)
1034
997
  end
1035
- end
1036
998
 
1037
- private def normal_char(key)
1038
- if key.char < 0x80
1039
- method_symbol = @config.editing_mode.get_method(key.combined_char)
1040
- process_key(key.combined_char, method_symbol)
1041
- else
1042
- process_key(key.char.chr(encoding), nil)
1043
- end
1044
- if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
1045
- byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer)
1046
- @byte_pointer -= byte_size
1047
- end
999
+ process_insert(force: method_symbol != :ed_insert)
1000
+
1001
+ run_for_operators(key, method_symbol)
1048
1002
  end
1049
1003
 
1050
1004
  def update(key)
@@ -1058,25 +1012,22 @@ class Reline::LineEditor
1058
1012
  end
1059
1013
 
1060
1014
  def input_key(key)
1061
- save_old_buffer
1015
+ old_buffer_of_lines = @buffer_of_lines.dup
1062
1016
  @config.reset_oneshot_key_bindings
1063
- @dialogs.each do |dialog|
1064
- if key.char.instance_of?(Symbol) and key.char == dialog.name
1065
- return
1066
- end
1067
- end
1068
1017
  if key.char.nil?
1069
1018
  process_insert(force: true)
1070
1019
  @eof = buffer_empty?
1071
1020
  finish
1072
1021
  return
1073
1022
  end
1023
+ return if @dialogs.any? { |dialog| dialog.name == key.method_symbol }
1024
+
1074
1025
  @completion_occurs = false
1075
1026
 
1076
- if key.char.is_a?(Symbol)
1077
- process_key(key.char, key.char)
1078
- else
1079
- normal_char(key)
1027
+ process_key(key.char, key.method_symbol)
1028
+ if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
1029
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer)
1030
+ @byte_pointer -= byte_size
1080
1031
  end
1081
1032
 
1082
1033
  @prev_action_state, @next_action_state = @next_action_state, NullActionState
@@ -1086,15 +1037,16 @@ class Reline::LineEditor
1086
1037
  @completion_journey_state = nil
1087
1038
  end
1088
1039
 
1089
- push_input_lines unless @undoing
1090
- @undoing = false
1040
+ modified = old_buffer_of_lines != @buffer_of_lines
1041
+
1042
+ push_undo_redo(modified) unless @restoring
1043
+ @restoring = false
1091
1044
 
1092
1045
  if @in_pasting
1093
1046
  clear_dialogs
1094
1047
  return
1095
1048
  end
1096
1049
 
1097
- modified = @old_buffer_of_lines != @buffer_of_lines
1098
1050
  if !@completion_occurs && modified && !@config.disable_completion && @config.autocompletion
1099
1051
  # Auto complete starts only when edited
1100
1052
  process_insert(force: true)
@@ -1103,26 +1055,17 @@ class Reline::LineEditor
1103
1055
  modified
1104
1056
  end
1105
1057
 
1106
- def save_old_buffer
1107
- @old_buffer_of_lines = @buffer_of_lines.dup
1108
- end
1109
-
1110
- def push_input_lines
1111
- if @old_buffer_of_lines == @buffer_of_lines
1112
- @input_lines[@input_lines_position] = [@buffer_of_lines.dup, @byte_pointer, @line_index]
1058
+ MAX_UNDO_REDO_HISTORY_SIZE = 100
1059
+ def push_undo_redo(modified)
1060
+ if modified
1061
+ @undo_redo_history = @undo_redo_history[0..@undo_redo_index]
1062
+ @undo_redo_history.push([@buffer_of_lines.dup, @byte_pointer, @line_index])
1063
+ if @undo_redo_history.size > MAX_UNDO_REDO_HISTORY_SIZE
1064
+ @undo_redo_history.shift
1065
+ end
1066
+ @undo_redo_index = @undo_redo_history.size - 1
1113
1067
  else
1114
- @input_lines = @input_lines[0..@input_lines_position]
1115
- @input_lines_position += 1
1116
- @input_lines.push([@buffer_of_lines.dup, @byte_pointer, @line_index])
1117
- end
1118
- trim_input_lines
1119
- end
1120
-
1121
- MAX_INPUT_LINES = 100
1122
- def trim_input_lines
1123
- if @input_lines.size > MAX_INPUT_LINES
1124
- @input_lines.shift
1125
- @input_lines_position -= 1
1068
+ @undo_redo_history[@undo_redo_index] = [@buffer_of_lines.dup, @byte_pointer, @line_index]
1126
1069
  end
1127
1070
  end
1128
1071
 
@@ -1201,23 +1144,11 @@ class Reline::LineEditor
1201
1144
  process_auto_indent
1202
1145
  end
1203
1146
 
1204
- def set_current_lines(lines, byte_pointer = nil, line_index = 0)
1205
- cursor = current_byte_pointer_cursor
1206
- @buffer_of_lines = lines
1207
- @line_index = line_index
1208
- if byte_pointer
1209
- @byte_pointer = byte_pointer
1210
- else
1211
- calculate_nearest_cursor(cursor)
1212
- end
1213
- process_auto_indent
1214
- end
1215
-
1216
1147
  def retrieve_completion_block
1217
1148
  quote_characters = Reline.completer_quote_characters
1218
1149
  before = current_line.byteslice(0, @byte_pointer).grapheme_clusters
1219
1150
  quote = nil
1220
- # Calcualte closing quote when cursor is at the end of the line
1151
+ # Calculate closing quote when cursor is at the end of the line
1221
1152
  if current_line.bytesize == @byte_pointer && !quote_characters.empty?
1222
1153
  escaped = false
1223
1154
  before.each do |c|
@@ -1255,7 +1186,6 @@ class Reline::LineEditor
1255
1186
  end
1256
1187
 
1257
1188
  def insert_multiline_text(text)
1258
- save_old_buffer
1259
1189
  pre = @buffer_of_lines[@line_index].byteslice(0, @byte_pointer)
1260
1190
  post = @buffer_of_lines[@line_index].byteslice(@byte_pointer..)
1261
1191
  lines = (pre + Reline::Unicode.safe_encode(text, encoding).gsub(/\r\n?/, "\n") + post).split("\n", -1)
@@ -1263,7 +1193,6 @@ class Reline::LineEditor
1263
1193
  @buffer_of_lines[@line_index, 1] = lines
1264
1194
  @line_index += lines.size - 1
1265
1195
  @byte_pointer = @buffer_of_lines[@line_index].bytesize - post.bytesize
1266
- push_input_lines
1267
1196
  end
1268
1197
 
1269
1198
  def insert_text(text)
@@ -1429,21 +1358,11 @@ class Reline::LineEditor
1429
1358
  # digit or if the existing argument is already greater than a
1430
1359
  # million.
1431
1360
  # GNU Readline:: +self-insert+ (a, b, A, 1, !, …) Insert yourself.
1432
- private def ed_insert(key)
1433
- if key.instance_of?(String)
1434
- begin
1435
- key.encode(Encoding::UTF_8)
1436
- rescue Encoding::UndefinedConversionError
1437
- return
1438
- end
1439
- str = key
1440
- else
1441
- begin
1442
- key.chr.encode(Encoding::UTF_8)
1443
- rescue Encoding::UndefinedConversionError
1444
- return
1445
- end
1446
- str = key.chr
1361
+ private def ed_insert(str)
1362
+ begin
1363
+ str.encode(Encoding::UTF_8)
1364
+ rescue Encoding::UndefinedConversionError
1365
+ return
1447
1366
  end
1448
1367
  if @in_pasting
1449
1368
  @continuous_insertion_buffer << str
@@ -1454,24 +1373,26 @@ class Reline::LineEditor
1454
1373
 
1455
1374
  insert_text(str)
1456
1375
  end
1457
- alias_method :ed_digit, :ed_insert
1458
1376
  alias_method :self_insert, :ed_insert
1459
1377
 
1460
- private def ed_quoted_insert(str, arg: 1)
1461
- @waiting_proc = proc { |key|
1462
- arg.times do
1463
- if key == "\C-j".ord or key == "\C-m".ord
1464
- key_newline(key)
1465
- elsif key == 0
1466
- # Ignore NUL.
1467
- else
1468
- ed_insert(key)
1469
- end
1378
+ private def ed_digit(key)
1379
+ if @vi_arg
1380
+ ed_argument_digit(key)
1381
+ else
1382
+ ed_insert(key)
1383
+ end
1384
+ end
1385
+
1386
+ private def insert_raw_char(str, arg: 1)
1387
+ arg.times do
1388
+ if str == "\C-j" or str == "\C-m"
1389
+ key_newline(str)
1390
+ elsif str != "\0"
1391
+ # Ignore NUL.
1392
+ ed_insert(str)
1470
1393
  end
1471
- @waiting_proc = nil
1472
- }
1394
+ end
1473
1395
  end
1474
- alias_method :quoted_insert, :ed_quoted_insert
1475
1396
 
1476
1397
  private def ed_next_char(key, arg: 1)
1477
1398
  byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
@@ -1507,7 +1428,14 @@ class Reline::LineEditor
1507
1428
  @byte_pointer = 0
1508
1429
  end
1509
1430
  alias_method :beginning_of_line, :ed_move_to_beg
1510
- alias_method :vi_zero, :ed_move_to_beg
1431
+
1432
+ private def vi_zero(key)
1433
+ if @vi_arg
1434
+ ed_argument_digit(key)
1435
+ else
1436
+ ed_move_to_beg(key)
1437
+ end
1438
+ end
1511
1439
 
1512
1440
  private def ed_move_to_end(key)
1513
1441
  @byte_pointer = current_line.bytesize
@@ -1520,13 +1448,13 @@ class Reline::LineEditor
1520
1448
  lambda do |key|
1521
1449
  search_again = false
1522
1450
  case key
1523
- when "\C-h".ord, "\C-?".ord
1451
+ when "\C-h", "\C-?"
1524
1452
  grapheme_clusters = search_word.grapheme_clusters
1525
1453
  if grapheme_clusters.size > 0
1526
1454
  grapheme_clusters.pop
1527
1455
  search_word = grapheme_clusters.join
1528
1456
  end
1529
- when "\C-r".ord, "\C-s".ord
1457
+ when "\C-r", "\C-s"
1530
1458
  search_again = true if search_key == key
1531
1459
  search_key = key
1532
1460
  else
@@ -1543,10 +1471,10 @@ class Reline::LineEditor
1543
1471
  end
1544
1472
  if @history_pointer
1545
1473
  case search_key
1546
- when "\C-r".ord
1474
+ when "\C-r"
1547
1475
  history_pointer_base = 0
1548
1476
  history = Reline::HISTORY[0..(@history_pointer - 1)]
1549
- when "\C-s".ord
1477
+ when "\C-s"
1550
1478
  history_pointer_base = @history_pointer + 1
1551
1479
  history = Reline::HISTORY[(@history_pointer + 1)..-1]
1552
1480
  end
@@ -1556,10 +1484,10 @@ class Reline::LineEditor
1556
1484
  end
1557
1485
  elsif @history_pointer
1558
1486
  case search_key
1559
- when "\C-r".ord
1487
+ when "\C-r"
1560
1488
  history_pointer_base = 0
1561
1489
  history = Reline::HISTORY[0..@history_pointer]
1562
- when "\C-s".ord
1490
+ when "\C-s"
1563
1491
  history_pointer_base = @history_pointer
1564
1492
  history = Reline::HISTORY[@history_pointer..-1]
1565
1493
  end
@@ -1568,11 +1496,11 @@ class Reline::LineEditor
1568
1496
  history = Reline::HISTORY
1569
1497
  end
1570
1498
  case search_key
1571
- when "\C-r".ord
1499
+ when "\C-r"
1572
1500
  hit_index = history.rindex { |item|
1573
1501
  item.include?(search_word)
1574
1502
  }
1575
- when "\C-s".ord
1503
+ when "\C-s"
1576
1504
  hit_index = history.index { |item|
1577
1505
  item.include?(search_word)
1578
1506
  }
@@ -1583,9 +1511,9 @@ class Reline::LineEditor
1583
1511
  end
1584
1512
  end
1585
1513
  case search_key
1586
- when "\C-r".ord
1514
+ when "\C-r"
1587
1515
  prompt_name = 'reverse-i-search'
1588
- when "\C-s".ord
1516
+ when "\C-s"
1589
1517
  prompt_name = 'i-search'
1590
1518
  end
1591
1519
  prompt_name = "failed #{prompt_name}" unless hit
@@ -1597,16 +1525,15 @@ class Reline::LineEditor
1597
1525
  backup = @buffer_of_lines.dup, @line_index, @byte_pointer, @history_pointer, @line_backup_in_history
1598
1526
  searcher = generate_searcher(key)
1599
1527
  @searching_prompt = "(reverse-i-search)`': "
1600
- termination_keys = ["\C-j".ord]
1601
- termination_keys.concat(@config.isearch_terminators.chars.map(&:ord)) if @config.isearch_terminators
1528
+ termination_keys = ["\C-j"]
1529
+ termination_keys.concat(@config.isearch_terminators.chars) if @config.isearch_terminators
1602
1530
  @waiting_proc = ->(k) {
1603
- chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
1604
- if k == "\C-g".ord
1531
+ if k == "\C-g"
1605
1532
  # cancel search and restore buffer
1606
1533
  @buffer_of_lines, @line_index, @byte_pointer, @history_pointer, @line_backup_in_history = backup
1607
1534
  @searching_prompt = nil
1608
1535
  @waiting_proc = nil
1609
- elsif !termination_keys.include?(k) && (chr.match?(/[[:print:]]/) || k == "\C-h".ord || k == "\C-?".ord || k == "\C-r".ord || k == "\C-s".ord)
1536
+ elsif !termination_keys.include?(k) && (k.match?(/[[:print:]]/) || k == "\C-h" || k == "\C-?" || k == "\C-r" || k == "\C-s")
1610
1537
  search_word, prompt_name, hit_pointer = searcher.call(k)
1611
1538
  Reline.last_incremental_search = search_word
1612
1539
  @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
@@ -1738,17 +1665,10 @@ class Reline::LineEditor
1738
1665
  finish
1739
1666
  end
1740
1667
  else
1741
- if @line_index == (@buffer_of_lines.size - 1)
1742
- if confirm_multiline_termination
1743
- finish
1744
- else
1745
- key_newline(key)
1746
- end
1747
- else
1748
- # should check confirm_multiline_termination to finish?
1749
- @line_index = @buffer_of_lines.size - 1
1750
- @byte_pointer = current_line.bytesize
1668
+ if @line_index == @buffer_of_lines.size - 1 && confirm_multiline_termination
1751
1669
  finish
1670
+ else
1671
+ key_newline(key)
1752
1672
  end
1753
1673
  end
1754
1674
  else
@@ -1756,6 +1676,11 @@ class Reline::LineEditor
1756
1676
  end
1757
1677
  end
1758
1678
 
1679
+ private def ed_force_submit(_key)
1680
+ process_insert(force: true)
1681
+ finish
1682
+ end
1683
+
1759
1684
  private def em_delete_prev_char(key, arg: 1)
1760
1685
  arg.times do
1761
1686
  if @byte_pointer == 0 and @line_index > 0
@@ -1822,7 +1747,7 @@ class Reline::LineEditor
1822
1747
  alias_method :kill_whole_line, :em_kill_line
1823
1748
 
1824
1749
  private def em_delete(key)
1825
- if buffer_empty? and key == "\C-d".ord
1750
+ if buffer_empty? and key == "\C-d"
1826
1751
  @eof = true
1827
1752
  finish
1828
1753
  elsif @byte_pointer < current_line.bytesize
@@ -2240,20 +2165,9 @@ class Reline::LineEditor
2240
2165
  end
2241
2166
 
2242
2167
  private def ed_argument_digit(key)
2243
- if @vi_arg.nil?
2244
- if key.chr.to_i.zero?
2245
- if key.anybits?(0b10000000)
2246
- unescaped_key = key ^ 0b10000000
2247
- unless unescaped_key.chr.to_i.zero?
2248
- @vi_arg = unescaped_key.chr.to_i
2249
- end
2250
- end
2251
- else
2252
- @vi_arg = key.chr.to_i
2253
- end
2254
- else
2255
- @vi_arg = @vi_arg * 10 + key.chr.to_i
2256
- end
2168
+ # key is expected to be `ESC digit` or `digit`
2169
+ num = key[/\d/].to_i
2170
+ @vi_arg = (@vi_arg || 0) * 10 + num
2257
2171
  end
2258
2172
 
2259
2173
  private def vi_to_column(key, arg: 0)
@@ -2272,7 +2186,7 @@ class Reline::LineEditor
2272
2186
  before = current_line.byteslice(0, @byte_pointer)
2273
2187
  remaining_point = @byte_pointer + byte_size
2274
2188
  after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
2275
- set_current_line(before + k.chr + after)
2189
+ set_current_line(before + k + after)
2276
2190
  @waiting_proc = nil
2277
2191
  elsif arg > 1
2278
2192
  byte_size = 0
@@ -2282,7 +2196,7 @@ class Reline::LineEditor
2282
2196
  before = current_line.byteslice(0, @byte_pointer)
2283
2197
  remaining_point = @byte_pointer + byte_size
2284
2198
  after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
2285
- replaced = k.chr * arg
2199
+ replaced = k * arg
2286
2200
  set_current_line(before + replaced + after, @byte_pointer + replaced.bytesize)
2287
2201
  @waiting_proc = nil
2288
2202
  end
@@ -2298,11 +2212,6 @@ class Reline::LineEditor
2298
2212
  end
2299
2213
 
2300
2214
  private def search_next_char(key, arg, need_prev_char: false, inclusive: false)
2301
- if key.instance_of?(String)
2302
- inputted_char = key
2303
- else
2304
- inputted_char = key.chr
2305
- end
2306
2215
  prev_total = nil
2307
2216
  total = nil
2308
2217
  found = false
@@ -2313,7 +2222,7 @@ class Reline::LineEditor
2313
2222
  width = Reline::Unicode.get_mbchar_width(mbchar)
2314
2223
  total = [mbchar.bytesize, width]
2315
2224
  else
2316
- if inputted_char == mbchar
2225
+ if key == mbchar
2317
2226
  arg -= 1
2318
2227
  if arg.zero?
2319
2228
  found = true
@@ -2350,11 +2259,6 @@ class Reline::LineEditor
2350
2259
  end
2351
2260
 
2352
2261
  private def search_prev_char(key, arg, need_next_char = false)
2353
- if key.instance_of?(String)
2354
- inputted_char = key
2355
- else
2356
- inputted_char = key.chr
2357
- end
2358
2262
  prev_total = nil
2359
2263
  total = nil
2360
2264
  found = false
@@ -2365,7 +2269,7 @@ class Reline::LineEditor
2365
2269
  width = Reline::Unicode.get_mbchar_width(mbchar)
2366
2270
  total = [mbchar.bytesize, width]
2367
2271
  else
2368
- if inputted_char == mbchar
2272
+ if key == mbchar
2369
2273
  arg -= 1
2370
2274
  if arg.zero?
2371
2275
  found = true
@@ -2417,24 +2321,23 @@ class Reline::LineEditor
2417
2321
  @config.editing_mode = :vi_insert
2418
2322
  end
2419
2323
 
2420
- private def undo(_key)
2421
- @undoing = true
2324
+ private def move_undo_redo(direction)
2325
+ @restoring = true
2326
+ return unless (0..@undo_redo_history.size - 1).cover?(@undo_redo_index + direction)
2422
2327
 
2423
- return if @input_lines_position <= 0
2328
+ @undo_redo_index += direction
2329
+ buffer_of_lines, byte_pointer, line_index = @undo_redo_history[@undo_redo_index]
2330
+ @buffer_of_lines = buffer_of_lines.dup
2331
+ @line_index = line_index
2332
+ @byte_pointer = byte_pointer
2333
+ end
2424
2334
 
2425
- @input_lines_position -= 1
2426
- target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position]
2427
- set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y)
2335
+ private def undo(_key)
2336
+ move_undo_redo(-1)
2428
2337
  end
2429
2338
 
2430
2339
  private def redo(_key)
2431
- @undoing = true
2432
-
2433
- return if @input_lines_position >= @input_lines.size - 1
2434
-
2435
- @input_lines_position += 1
2436
- target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position]
2437
- set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y)
2340
+ move_undo_redo(+1)
2438
2341
  end
2439
2342
 
2440
2343
  private def prev_action_state_value(type)