reline 0.5.2 → 0.5.8

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.
@@ -4,7 +4,6 @@ require 'reline/unicode'
4
4
  require 'tempfile'
5
5
 
6
6
  class Reline::LineEditor
7
- # TODO: undo
8
7
  # TODO: Use "private alias_method" idiom after drop Ruby 2.5.
9
8
  attr_reader :byte_pointer
10
9
  attr_accessor :confirm_multiline_termination_proc
@@ -75,7 +74,7 @@ class Reline::LineEditor
75
74
  def initialize(config, encoding)
76
75
  @config = config
77
76
  @completion_append_character = ''
78
- @screen_size = Reline::IOGate.get_screen_size
77
+ @screen_size = [0, 0] # Should be initialized with actual winsize in LineEditor#reset
79
78
  reset_variables(encoding: encoding)
80
79
  end
81
80
 
@@ -112,7 +111,11 @@ class Reline::LineEditor
112
111
  else
113
112
  prompt = @prompt
114
113
  end
115
- if @prompt_proc
114
+ if !@is_multiline
115
+ mode_string = check_mode_string
116
+ prompt = mode_string + prompt if mode_string
117
+ [prompt] + [''] * (buffer.size - 1)
118
+ elsif @prompt_proc
116
119
  prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") }
117
120
  prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
118
121
  prompt_list = [prompt] if prompt_list.empty?
@@ -231,7 +234,6 @@ class Reline::LineEditor
231
234
  @vi_waiting_operator_arg = nil
232
235
  @completion_journey_state = nil
233
236
  @completion_state = CompletionState::NORMAL
234
- @completion_occurs = false
235
237
  @perfect_matched = nil
236
238
  @menu_info = nil
237
239
  @searching_prompt = nil
@@ -248,6 +250,9 @@ class Reline::LineEditor
248
250
  @resized = false
249
251
  @cache = {}
250
252
  @rendered_screen = RenderedScreen.new(base_y: 0, lines: [], cursor_y: 0)
253
+ @input_lines = [[[""], 0, 0]]
254
+ @input_lines_position = 0
255
+ @undoing = false
251
256
  reset_line
252
257
  end
253
258
 
@@ -280,7 +285,7 @@ class Reline::LineEditor
280
285
  indent1 = @auto_indent_proc.(@buffer_of_lines.take(@line_index - 1).push(''), @line_index - 1, 0, true)
281
286
  indent2 = @auto_indent_proc.(@buffer_of_lines.take(@line_index), @line_index - 1, @buffer_of_lines[@line_index - 1].bytesize, false)
282
287
  indent = indent2 || indent1
283
- @buffer_of_lines[@line_index - 1] = ' ' * indent + @buffer_of_lines[@line_index - 1].gsub(/\A */, '')
288
+ @buffer_of_lines[@line_index - 1] = ' ' * indent + @buffer_of_lines[@line_index - 1].gsub(/\A\s*/, '')
284
289
  )
285
290
  process_auto_indent @line_index, add_newline: true
286
291
  else
@@ -291,8 +296,8 @@ class Reline::LineEditor
291
296
  end
292
297
  end
293
298
 
294
- private def split_by_width(str, max_width)
295
- Reline::Unicode.split_by_width(str, max_width, @encoding)
299
+ private def split_by_width(str, max_width, offset: 0)
300
+ Reline::Unicode.split_by_width(str, max_width, @encoding, offset: offset)
296
301
  end
297
302
 
298
303
  def current_byte_pointer_cursor
@@ -366,7 +371,7 @@ class Reline::LineEditor
366
371
  @scroll_partial_screen
367
372
  end
368
373
 
369
- def wrapped_lines
374
+ def wrapped_prompt_and_input_lines
370
375
  with_cache(__method__, @buffer_of_lines.size, modified_lines, prompt_list, screen_width) do |n, lines, prompts, width, prev_cache_key, cached_value|
371
376
  prev_n, prev_lines, prev_prompts, prev_width = prev_cache_key
372
377
  cached_wraps = {}
@@ -377,9 +382,14 @@ class Reline::LineEditor
377
382
  end
378
383
 
379
384
  n.times.map do |i|
380
- prompt = prompts[i]
381
- line = lines[i]
382
- cached_wraps[[prompt, line]] || split_by_width("#{prompt}#{line}", width).first.compact
385
+ prompt = prompts[i] || ''
386
+ line = lines[i] || ''
387
+ if (cached = cached_wraps[[prompt, line]])
388
+ next cached
389
+ end
390
+ *wrapped_prompts, code_line_prompt = split_by_width(prompt, width).first.compact
391
+ wrapped_lines = split_by_width(line, width, offset: calculate_width(code_line_prompt, true)).first.compact
392
+ wrapped_prompts.map { |p| [p, ''] } + [[code_line_prompt, wrapped_lines.first]] + wrapped_lines.drop(1).map { |c| ['', c] }
383
393
  end
384
394
  end
385
395
  end
@@ -405,8 +415,13 @@ class Reline::LineEditor
405
415
  @output.write "#{Reline::IOGate::RESET_COLOR}#{' ' * width}"
406
416
  else
407
417
  x, w, content = new_items[level]
408
- content = Reline::Unicode.take_range(content, base_x - x, width) unless x == base_x && w == width
409
- Reline::IOGate.move_cursor_column base_x
418
+ cover_begin = base_x != 0 && new_levels[base_x - 1] == level
419
+ cover_end = new_levels[base_x + width] == level
420
+ pos = 0
421
+ unless x == base_x && w == width
422
+ content, pos = Reline::Unicode.take_mbchar_range(content, base_x - x, width, cover_begin: cover_begin, cover_end: cover_end, padding: true)
423
+ end
424
+ Reline::IOGate.move_cursor_column x + pos
410
425
  @output.write "#{Reline::IOGate::RESET_COLOR}#{content}#{Reline::IOGate::RESET_COLOR}"
411
426
  end
412
427
  base_x += width
@@ -422,7 +437,7 @@ class Reline::LineEditor
422
437
  prompt_width = calculate_width(prompt_list[@line_index], true)
423
438
  line_before_cursor = whole_lines[@line_index].byteslice(0, @byte_pointer)
424
439
  wrapped_line_before_cursor = split_by_width(' ' * prompt_width + line_before_cursor, screen_width).first.compact
425
- wrapped_cursor_y = wrapped_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1
440
+ wrapped_cursor_y = wrapped_prompt_and_input_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1
426
441
  wrapped_cursor_x = calculate_width(wrapped_line_before_cursor.last)
427
442
  [wrapped_cursor_x, wrapped_cursor_y]
428
443
  end
@@ -486,8 +501,9 @@ class Reline::LineEditor
486
501
  wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
487
502
 
488
503
  rendered_lines = @rendered_screen.lines
489
- new_lines = wrapped_lines.flatten[screen_scroll_top, screen_height].map do |l|
490
- [[0, Reline::Unicode.calculate_width(l, true), l]]
504
+ new_lines = wrapped_prompt_and_input_lines.flatten(1)[screen_scroll_top, screen_height].map do |prompt, line|
505
+ prompt_width = Reline::Unicode.calculate_width(prompt, true)
506
+ [[0, prompt_width, prompt], [prompt_width, Reline::Unicode.calculate_width(line, true), line]]
491
507
  end
492
508
  if @menu_info
493
509
  @menu_info.lines(screen_width).each do |item|
@@ -503,7 +519,8 @@ class Reline::LineEditor
503
519
  y_range.each do |row|
504
520
  next if row < 0 || row >= screen_height
505
521
  dialog_rows = new_lines[row] ||= []
506
- dialog_rows[index + 1] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
522
+ # index 0 is for prompt, index 1 is for line, index 2.. is for dialog
523
+ dialog_rows[index + 2] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
507
524
  end
508
525
  end
509
526
 
@@ -688,13 +705,6 @@ class Reline::LineEditor
688
705
 
689
706
  DIALOG_DEFAULT_HEIGHT = 20
690
707
 
691
- private def padding_space_with_escape_sequences(str, width)
692
- padding_width = width - calculate_width(str, true)
693
- # padding_width should be only positive value. But macOS and Alacritty returns negative value.
694
- padding_width = 0 if padding_width < 0
695
- str + (' ' * padding_width)
696
- end
697
-
698
708
  private def dialog_range(dialog, dialog_y)
699
709
  x_range = dialog.column...dialog.column + dialog.width
700
710
  y_range = dialog_y + dialog.vertical_offset...dialog_y + dialog.vertical_offset + dialog.contents.size
@@ -767,7 +777,7 @@ class Reline::LineEditor
767
777
  dialog.contents = contents.map.with_index do |item, i|
768
778
  line_sgr = i == pointer ? enhanced_sgr : default_sgr
769
779
  str_width = dialog.width - (scrollbar_pos.nil? ? 0 : @block_elem_width)
770
- str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, str_width), str_width)
780
+ str, = Reline::Unicode.take_mbchar_range(item, 0, str_width, padding: true)
771
781
  colored_content = "#{line_sgr}#{str}"
772
782
  if scrollbar_pos
773
783
  if scrollbar_pos <= (i * 2) and (i * 2 + 1) < (scrollbar_pos + bar_height)
@@ -847,7 +857,7 @@ class Reline::LineEditor
847
857
  [target, preposing, completed, postposing]
848
858
  end
849
859
 
850
- private def complete(list, just_show_list)
860
+ private def perform_completion(list, just_show_list)
851
861
  case @completion_state
852
862
  when CompletionState::NORMAL
853
863
  @completion_state = CompletionState::COMPLETION
@@ -876,10 +886,12 @@ class Reline::LineEditor
876
886
  @completion_state = CompletionState::PERFECT_MATCH
877
887
  else
878
888
  @completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
889
+ perform_completion(list, true) if @config.show_all_if_ambiguous
879
890
  end
880
891
  @perfect_matched = completed
881
892
  else
882
893
  @completion_state = CompletionState::MENU
894
+ perform_completion(list, true) if @config.show_all_if_ambiguous
883
895
  end
884
896
  if not just_show_list and target < completed
885
897
  @buffer_of_lines[@line_index] = (preposing + completed + completion_append_character.to_s + postposing).split("\n")[@line_index] || String.new(encoding: @encoding)
@@ -938,7 +950,8 @@ class Reline::LineEditor
938
950
  unless @waiting_proc
939
951
  byte_pointer_diff = @byte_pointer - old_byte_pointer
940
952
  @byte_pointer = old_byte_pointer
941
- send(@vi_waiting_operator, byte_pointer_diff)
953
+ method_obj = method(@vi_waiting_operator)
954
+ wrap_method_call(@vi_waiting_operator, method_obj, byte_pointer_diff)
942
955
  cleanup_waiting
943
956
  end
944
957
  else
@@ -999,7 +1012,8 @@ class Reline::LineEditor
999
1012
  if @vi_waiting_operator
1000
1013
  byte_pointer_diff = @byte_pointer - old_byte_pointer
1001
1014
  @byte_pointer = old_byte_pointer
1002
- send(@vi_waiting_operator, byte_pointer_diff)
1015
+ method_obj = method(@vi_waiting_operator)
1016
+ wrap_method_call(@vi_waiting_operator, method_obj, byte_pointer_diff)
1003
1017
  cleanup_waiting
1004
1018
  end
1005
1019
  @kill_ring.process
@@ -1054,10 +1068,6 @@ class Reline::LineEditor
1054
1068
  end
1055
1069
 
1056
1070
  private def normal_char(key)
1057
- if key.combined_char.is_a?(Symbol)
1058
- process_key(key.combined_char, key.combined_char)
1059
- return
1060
- end
1061
1071
  @multibyte_buffer << key.combined_char
1062
1072
  if @multibyte_buffer.size > 1
1063
1073
  if @multibyte_buffer.dup.force_encoding(@encoding).valid_encoding?
@@ -1100,6 +1110,7 @@ class Reline::LineEditor
1100
1110
  end
1101
1111
 
1102
1112
  def input_key(key)
1113
+ save_old_buffer
1103
1114
  @config.reset_oneshot_key_bindings
1104
1115
  @dialogs.each do |dialog|
1105
1116
  if key.char.instance_of?(Symbol) and key.char == dialog.name
@@ -1107,38 +1118,17 @@ class Reline::LineEditor
1107
1118
  end
1108
1119
  end
1109
1120
  if key.char.nil?
1121
+ process_insert(force: true)
1110
1122
  if @first_char
1111
1123
  @eof = true
1112
1124
  end
1113
1125
  finish
1114
1126
  return
1115
1127
  end
1116
- old_lines = @buffer_of_lines.dup
1117
1128
  @first_char = false
1118
1129
  @completion_occurs = false
1119
- if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
1120
- if !@config.disable_completion
1121
- process_insert(force: true)
1122
- if @config.autocompletion
1123
- @completion_state = CompletionState::NORMAL
1124
- @completion_occurs = move_completed_list(:down)
1125
- else
1126
- @completion_journey_state = nil
1127
- result = call_completion_proc
1128
- if result.is_a?(Array)
1129
- @completion_occurs = true
1130
- complete(result, false)
1131
- end
1132
- end
1133
- end
1134
- elsif @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
1135
- # In vi mode, move completed list even if autocompletion is off
1136
- if not @config.disable_completion
1137
- process_insert(force: true)
1138
- @completion_state = CompletionState::NORMAL
1139
- @completion_occurs = move_completed_list("\C-p".ord == key.char ? :up : :down)
1140
- end
1141
- elsif Symbol === key.char and respond_to?(key.char, true)
1130
+
1131
+ if key.char.is_a?(Symbol)
1142
1132
  process_key(key.char, key.char)
1143
1133
  else
1144
1134
  normal_char(key)
@@ -1148,12 +1138,15 @@ class Reline::LineEditor
1148
1138
  @completion_journey_state = nil
1149
1139
  end
1150
1140
 
1141
+ push_input_lines unless @undoing
1142
+ @undoing = false
1143
+
1151
1144
  if @in_pasting
1152
1145
  clear_dialogs
1153
1146
  return
1154
1147
  end
1155
1148
 
1156
- modified = old_lines != @buffer_of_lines
1149
+ modified = @old_buffer_of_lines != @buffer_of_lines
1157
1150
  if !@completion_occurs && modified && !@config.disable_completion && @config.autocompletion
1158
1151
  # Auto complete starts only when edited
1159
1152
  process_insert(force: true)
@@ -1162,6 +1155,29 @@ class Reline::LineEditor
1162
1155
  modified
1163
1156
  end
1164
1157
 
1158
+ def save_old_buffer
1159
+ @old_buffer_of_lines = @buffer_of_lines.dup
1160
+ end
1161
+
1162
+ def push_input_lines
1163
+ if @old_buffer_of_lines == @buffer_of_lines
1164
+ @input_lines[@input_lines_position] = [@buffer_of_lines.dup, @byte_pointer, @line_index]
1165
+ else
1166
+ @input_lines = @input_lines[0..@input_lines_position]
1167
+ @input_lines_position += 1
1168
+ @input_lines.push([@buffer_of_lines.dup, @byte_pointer, @line_index])
1169
+ end
1170
+ trim_input_lines
1171
+ end
1172
+
1173
+ MAX_INPUT_LINES = 100
1174
+ def trim_input_lines
1175
+ if @input_lines.size > MAX_INPUT_LINES
1176
+ @input_lines.shift
1177
+ @input_lines_position -= 1
1178
+ end
1179
+ end
1180
+
1165
1181
  def scroll_into_view
1166
1182
  _wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
1167
1183
  if wrapped_cursor_y < screen_scroll_top
@@ -1220,7 +1236,7 @@ class Reline::LineEditor
1220
1236
  end
1221
1237
 
1222
1238
  def line()
1223
- current_line unless eof?
1239
+ @buffer_of_lines.join("\n") unless eof?
1224
1240
  end
1225
1241
 
1226
1242
  def current_line
@@ -1238,6 +1254,18 @@ class Reline::LineEditor
1238
1254
  process_auto_indent
1239
1255
  end
1240
1256
 
1257
+ def set_current_lines(lines, byte_pointer = nil, line_index = 0)
1258
+ cursor = current_byte_pointer_cursor
1259
+ @buffer_of_lines = lines
1260
+ @line_index = line_index
1261
+ if byte_pointer
1262
+ @byte_pointer = byte_pointer
1263
+ else
1264
+ calculate_nearest_cursor(cursor)
1265
+ end
1266
+ process_auto_indent
1267
+ end
1268
+
1241
1269
  def retrieve_completion_block(set_completion_quote_character = false)
1242
1270
  if Reline.completer_word_break_characters.empty?
1243
1271
  word_break_regexp = nil
@@ -1304,14 +1332,12 @@ class Reline::LineEditor
1304
1332
  end
1305
1333
  target = before
1306
1334
  end
1307
- if @is_multiline
1308
- lines = whole_lines
1309
- if @line_index > 0
1310
- preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
1311
- end
1312
- if (lines.size - 1) > @line_index
1313
- postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
1314
- end
1335
+ lines = whole_lines
1336
+ if @line_index > 0
1337
+ preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
1338
+ end
1339
+ if (lines.size - 1) > @line_index
1340
+ postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
1315
1341
  end
1316
1342
  [preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
1317
1343
  end
@@ -1321,6 +1347,18 @@ class Reline::LineEditor
1321
1347
  @confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
1322
1348
  end
1323
1349
 
1350
+ def insert_pasted_text(text)
1351
+ save_old_buffer
1352
+ pre = @buffer_of_lines[@line_index].byteslice(0, @byte_pointer)
1353
+ post = @buffer_of_lines[@line_index].byteslice(@byte_pointer..)
1354
+ lines = (pre + text.gsub(/\r\n?/, "\n") + post).split("\n", -1)
1355
+ lines << '' if lines.empty?
1356
+ @buffer_of_lines[@line_index, 1] = lines
1357
+ @line_index += lines.size - 1
1358
+ @byte_pointer = @buffer_of_lines[@line_index].bytesize - post.bytesize
1359
+ push_input_lines
1360
+ end
1361
+
1324
1362
  def insert_text(text)
1325
1363
  if @buffer_of_lines[@line_index].bytesize == @byte_pointer
1326
1364
  @buffer_of_lines[@line_index] += text
@@ -1333,20 +1371,16 @@ class Reline::LineEditor
1333
1371
 
1334
1372
  def delete_text(start = nil, length = nil)
1335
1373
  if start.nil? and length.nil?
1336
- if @is_multiline
1337
- if @buffer_of_lines.size == 1
1338
- @buffer_of_lines[@line_index] = ''
1339
- @byte_pointer = 0
1340
- elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
1341
- @buffer_of_lines.pop
1342
- @line_index -= 1
1343
- @byte_pointer = 0
1344
- elsif @line_index < (@buffer_of_lines.size - 1)
1345
- @buffer_of_lines.delete_at(@line_index)
1346
- @byte_pointer = 0
1347
- end
1348
- else
1349
- set_current_line('', 0)
1374
+ if @buffer_of_lines.size == 1
1375
+ @buffer_of_lines[@line_index] = ''
1376
+ @byte_pointer = 0
1377
+ elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
1378
+ @buffer_of_lines.pop
1379
+ @line_index -= 1
1380
+ @byte_pointer = 0
1381
+ elsif @line_index < (@buffer_of_lines.size - 1)
1382
+ @buffer_of_lines.delete_at(@line_index)
1383
+ @byte_pointer = 0
1350
1384
  end
1351
1385
  elsif not start.nil? and not length.nil?
1352
1386
  if current_line
@@ -1423,13 +1457,42 @@ class Reline::LineEditor
1423
1457
  end
1424
1458
  end
1425
1459
 
1426
- private def completion_journey_up(key)
1427
- if not @config.disable_completion and @config.autocompletion
1460
+ private def complete(_key)
1461
+ return if @config.disable_completion
1462
+
1463
+ process_insert(force: true)
1464
+ if @config.autocompletion
1428
1465
  @completion_state = CompletionState::NORMAL
1429
- @completion_occurs = move_completed_list(:up)
1466
+ @completion_occurs = move_completed_list(:down)
1467
+ else
1468
+ @completion_journey_state = nil
1469
+ result = call_completion_proc
1470
+ if result.is_a?(Array)
1471
+ @completion_occurs = true
1472
+ perform_completion(result, false)
1473
+ end
1430
1474
  end
1431
1475
  end
1432
- alias_method :menu_complete_backward, :completion_journey_up
1476
+
1477
+ private def completion_journey_move(direction)
1478
+ return if @config.disable_completion
1479
+
1480
+ process_insert(force: true)
1481
+ @completion_state = CompletionState::NORMAL
1482
+ @completion_occurs = move_completed_list(direction)
1483
+ end
1484
+
1485
+ private def menu_complete(_key)
1486
+ completion_journey_move(:down)
1487
+ end
1488
+
1489
+ private def menu_complete_backward(_key)
1490
+ completion_journey_move(:up)
1491
+ end
1492
+
1493
+ private def completion_journey_up(_key)
1494
+ completion_journey_move(:up) if @config.autocompletion
1495
+ end
1433
1496
 
1434
1497
  # Editline:: +ed-unassigned+ This editor command always results in an error.
1435
1498
  # GNU Readline:: There is no corresponding macro.
@@ -1502,7 +1565,7 @@ class Reline::LineEditor
1502
1565
  byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
1503
1566
  if (@byte_pointer < current_line.bytesize)
1504
1567
  @byte_pointer += byte_size
1505
- elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == current_line.bytesize and @line_index < @buffer_of_lines.size - 1
1568
+ elsif @config.editing_mode_is?(:emacs) and @byte_pointer == current_line.bytesize and @line_index < @buffer_of_lines.size - 1
1506
1569
  @byte_pointer = 0
1507
1570
  @line_index += 1
1508
1571
  end
@@ -1515,7 +1578,7 @@ class Reline::LineEditor
1515
1578
  if @byte_pointer > 0
1516
1579
  byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
1517
1580
  @byte_pointer -= byte_size
1518
- elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0
1581
+ elsif @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0
1519
1582
  @line_index -= 1
1520
1583
  @byte_pointer = current_line.bytesize
1521
1584
  end
@@ -1535,139 +1598,99 @@ class Reline::LineEditor
1535
1598
  alias_method :vi_zero, :ed_move_to_beg
1536
1599
 
1537
1600
  private def ed_move_to_end(key)
1538
- @byte_pointer = 0
1539
- while @byte_pointer < current_line.bytesize
1540
- byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
1541
- @byte_pointer += byte_size
1542
- end
1601
+ @byte_pointer = current_line.bytesize
1543
1602
  end
1544
1603
  alias_method :end_of_line, :ed_move_to_end
1545
1604
 
1546
- private def generate_searcher
1547
- Fiber.new do |first_key|
1548
- prev_search_key = first_key
1549
- search_word = String.new(encoding: @encoding)
1550
- multibyte_buf = String.new(encoding: 'ASCII-8BIT')
1551
- last_hit = nil
1552
- case first_key
1553
- when "\C-r".ord
1554
- prompt_name = 'reverse-i-search'
1555
- when "\C-s".ord
1556
- prompt_name = 'i-search'
1605
+ private def generate_searcher(search_key)
1606
+ search_word = String.new(encoding: @encoding)
1607
+ multibyte_buf = String.new(encoding: 'ASCII-8BIT')
1608
+ hit_pointer = nil
1609
+ lambda do |key|
1610
+ search_again = false
1611
+ case key
1612
+ when "\C-h".ord, "\C-?".ord
1613
+ grapheme_clusters = search_word.grapheme_clusters
1614
+ if grapheme_clusters.size > 0
1615
+ grapheme_clusters.pop
1616
+ search_word = grapheme_clusters.join
1617
+ end
1618
+ when "\C-r".ord, "\C-s".ord
1619
+ search_again = true if search_key == key
1620
+ search_key = key
1621
+ else
1622
+ multibyte_buf << key
1623
+ if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
1624
+ search_word << multibyte_buf.dup.force_encoding(@encoding)
1625
+ multibyte_buf.clear
1626
+ end
1557
1627
  end
1558
- loop do
1559
- key = Fiber.yield(search_word)
1560
- search_again = false
1561
- case key
1562
- when -1 # determined
1563
- Reline.last_incremental_search = search_word
1564
- break
1565
- when "\C-h".ord, "\C-?".ord
1566
- grapheme_clusters = search_word.grapheme_clusters
1567
- if grapheme_clusters.size > 0
1568
- grapheme_clusters.pop
1569
- search_word = grapheme_clusters.join
1570
- end
1571
- when "\C-r".ord, "\C-s".ord
1572
- search_again = true if prev_search_key == key
1573
- prev_search_key = key
1574
- else
1575
- multibyte_buf << key
1576
- if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
1577
- search_word << multibyte_buf.dup.force_encoding(@encoding)
1578
- multibyte_buf.clear
1628
+ hit = nil
1629
+ if not search_word.empty? and @line_backup_in_history&.include?(search_word)
1630
+ hit_pointer = Reline::HISTORY.size
1631
+ hit = @line_backup_in_history
1632
+ else
1633
+ if search_again
1634
+ if search_word.empty? and Reline.last_incremental_search
1635
+ search_word = Reline.last_incremental_search
1579
1636
  end
1580
- end
1581
- hit = nil
1582
- if not search_word.empty? and @line_backup_in_history&.include?(search_word)
1583
- @history_pointer = nil
1584
- hit = @line_backup_in_history
1585
- else
1586
- if search_again
1587
- if search_word.empty? and Reline.last_incremental_search
1588
- search_word = Reline.last_incremental_search
1589
- end
1590
- if @history_pointer
1591
- case prev_search_key
1592
- when "\C-r".ord
1593
- history_pointer_base = 0
1594
- history = Reline::HISTORY[0..(@history_pointer - 1)]
1595
- when "\C-s".ord
1596
- history_pointer_base = @history_pointer + 1
1597
- history = Reline::HISTORY[(@history_pointer + 1)..-1]
1598
- end
1599
- else
1600
- history_pointer_base = 0
1601
- history = Reline::HISTORY
1602
- end
1603
- elsif @history_pointer
1604
- case prev_search_key
1637
+ if @history_pointer
1638
+ case search_key
1605
1639
  when "\C-r".ord
1606
1640
  history_pointer_base = 0
1607
- history = Reline::HISTORY[0..@history_pointer]
1641
+ history = Reline::HISTORY[0..(@history_pointer - 1)]
1608
1642
  when "\C-s".ord
1609
- history_pointer_base = @history_pointer
1610
- history = Reline::HISTORY[@history_pointer..-1]
1643
+ history_pointer_base = @history_pointer + 1
1644
+ history = Reline::HISTORY[(@history_pointer + 1)..-1]
1611
1645
  end
1612
1646
  else
1613
1647
  history_pointer_base = 0
1614
1648
  history = Reline::HISTORY
1615
1649
  end
1616
- case prev_search_key
1650
+ elsif @history_pointer
1651
+ case search_key
1617
1652
  when "\C-r".ord
1618
- hit_index = history.rindex { |item|
1619
- item.include?(search_word)
1620
- }
1653
+ history_pointer_base = 0
1654
+ history = Reline::HISTORY[0..@history_pointer]
1621
1655
  when "\C-s".ord
1622
- hit_index = history.index { |item|
1623
- item.include?(search_word)
1624
- }
1625
- end
1626
- if hit_index
1627
- @history_pointer = history_pointer_base + hit_index
1628
- hit = Reline::HISTORY[@history_pointer]
1656
+ history_pointer_base = @history_pointer
1657
+ history = Reline::HISTORY[@history_pointer..-1]
1629
1658
  end
1659
+ else
1660
+ history_pointer_base = 0
1661
+ history = Reline::HISTORY
1630
1662
  end
1631
- case prev_search_key
1663
+ case search_key
1632
1664
  when "\C-r".ord
1633
- prompt_name = 'reverse-i-search'
1665
+ hit_index = history.rindex { |item|
1666
+ item.include?(search_word)
1667
+ }
1634
1668
  when "\C-s".ord
1635
- prompt_name = 'i-search'
1669
+ hit_index = history.index { |item|
1670
+ item.include?(search_word)
1671
+ }
1636
1672
  end
1637
- if hit
1638
- if @is_multiline
1639
- @buffer_of_lines = hit.split("\n")
1640
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1641
- @line_index = @buffer_of_lines.size - 1
1642
- @byte_pointer = current_line.bytesize
1643
- @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
1644
- else
1645
- @buffer_of_lines = [hit]
1646
- @byte_pointer = hit.bytesize
1647
- @searching_prompt = "(%s)`%s': %s" % [prompt_name, search_word, hit]
1648
- end
1649
- last_hit = hit
1650
- else
1651
- if @is_multiline
1652
- @searching_prompt = "(failed %s)`%s'" % [prompt_name, search_word]
1653
- else
1654
- @searching_prompt = "(failed %s)`%s': %s" % [prompt_name, search_word, last_hit]
1655
- end
1673
+ if hit_index
1674
+ hit_pointer = history_pointer_base + hit_index
1675
+ hit = Reline::HISTORY[hit_pointer]
1656
1676
  end
1657
1677
  end
1678
+ case search_key
1679
+ when "\C-r".ord
1680
+ prompt_name = 'reverse-i-search'
1681
+ when "\C-s".ord
1682
+ prompt_name = 'i-search'
1683
+ end
1684
+ prompt_name = "failed #{prompt_name}" unless hit
1685
+ [search_word, prompt_name, hit_pointer]
1658
1686
  end
1659
1687
  end
1660
1688
 
1661
1689
  private def incremental_search_history(key)
1662
1690
  unless @history_pointer
1663
- if @is_multiline
1664
- @line_backup_in_history = whole_buffer
1665
- else
1666
- @line_backup_in_history = current_line
1667
- end
1691
+ @line_backup_in_history = whole_buffer
1668
1692
  end
1669
- searcher = generate_searcher
1670
- searcher.resume(key)
1693
+ searcher = generate_searcher(key)
1671
1694
  @searching_prompt = "(reverse-i-search)`': "
1672
1695
  termination_keys = ["\C-j".ord]
1673
1696
  termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators
@@ -1679,53 +1702,41 @@ class Reline::LineEditor
1679
1702
  else
1680
1703
  buffer = @line_backup_in_history
1681
1704
  end
1682
- if @is_multiline
1683
- @buffer_of_lines = buffer.split("\n")
1684
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1685
- @line_index = @buffer_of_lines.size - 1
1686
- else
1687
- @buffer_of_lines = [buffer]
1688
- end
1705
+ @buffer_of_lines = buffer.split("\n")
1706
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1707
+ @line_index = @buffer_of_lines.size - 1
1689
1708
  @searching_prompt = nil
1690
1709
  @waiting_proc = nil
1691
1710
  @byte_pointer = 0
1692
- searcher.resume(-1)
1693
1711
  when "\C-g".ord
1694
- if @is_multiline
1695
- @buffer_of_lines = @line_backup_in_history.split("\n")
1696
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1697
- @line_index = @buffer_of_lines.size - 1
1698
- else
1699
- @buffer_of_lines = [@line_backup_in_history]
1700
- end
1701
- @history_pointer = nil
1712
+ @buffer_of_lines = @line_backup_in_history.split("\n")
1713
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1714
+ @line_index = @buffer_of_lines.size - 1
1715
+ move_history(nil, line: :end, cursor: :end, save_buffer: false)
1702
1716
  @searching_prompt = nil
1703
1717
  @waiting_proc = nil
1704
- @line_backup_in_history = nil
1705
1718
  @byte_pointer = 0
1706
1719
  else
1707
1720
  chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
1708
1721
  if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == "\C-?".ord or k == "\C-r".ord or k == "\C-s".ord
1709
- searcher.resume(k)
1722
+ search_word, prompt_name, hit_pointer = searcher.call(k)
1723
+ Reline.last_incremental_search = search_word
1724
+ @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
1725
+ @searching_prompt += ': ' unless @is_multiline
1726
+ move_history(hit_pointer, line: :end, cursor: :end, save_buffer: false) if hit_pointer
1710
1727
  else
1711
1728
  if @history_pointer
1712
1729
  line = Reline::HISTORY[@history_pointer]
1713
1730
  else
1714
1731
  line = @line_backup_in_history
1715
1732
  end
1716
- if @is_multiline
1717
- @line_backup_in_history = whole_buffer
1718
- @buffer_of_lines = line.split("\n")
1719
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1720
- @line_index = @buffer_of_lines.size - 1
1721
- else
1722
- @line_backup_in_history = current_line
1723
- @buffer_of_lines = [line]
1724
- end
1733
+ @line_backup_in_history = whole_buffer
1734
+ @buffer_of_lines = line.split("\n")
1735
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1736
+ @line_index = @buffer_of_lines.size - 1
1725
1737
  @searching_prompt = nil
1726
1738
  @waiting_proc = nil
1727
1739
  @byte_pointer = 0
1728
- searcher.resume(-1)
1729
1740
  end
1730
1741
  end
1731
1742
  }
@@ -1741,191 +1752,95 @@ class Reline::LineEditor
1741
1752
  end
1742
1753
  alias_method :forward_search_history, :vi_search_next
1743
1754
 
1744
- private def ed_search_prev_history(key, arg: 1)
1745
- history = nil
1746
- h_pointer = nil
1747
- line_no = nil
1748
- substr = current_line.slice(0, @byte_pointer)
1749
- if @history_pointer.nil?
1750
- return if not current_line.empty? and substr.empty?
1751
- history = Reline::HISTORY
1752
- elsif @history_pointer.zero?
1753
- history = nil
1754
- h_pointer = nil
1755
- else
1756
- history = Reline::HISTORY.slice(0, @history_pointer)
1757
- end
1758
- return if history.nil?
1759
- if @is_multiline
1760
- h_pointer = history.rindex { |h|
1761
- h.split("\n").each_with_index { |l, i|
1762
- if l.start_with?(substr)
1763
- line_no = i
1764
- break
1765
- end
1766
- }
1767
- not line_no.nil?
1768
- }
1769
- else
1770
- h_pointer = history.rindex { |l|
1771
- l.start_with?(substr)
1772
- }
1773
- end
1774
- return if h_pointer.nil?
1775
- @history_pointer = h_pointer
1776
- cursor = current_byte_pointer_cursor
1777
- if @is_multiline
1778
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1779
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1780
- @line_index = line_no
1781
- calculate_nearest_cursor(cursor)
1782
- else
1783
- @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
1784
- calculate_nearest_cursor(cursor)
1755
+ private def search_history(prefix, pointer_range)
1756
+ pointer_range.each do |pointer|
1757
+ lines = Reline::HISTORY[pointer].split("\n")
1758
+ lines.each_with_index do |line, index|
1759
+ return [pointer, index] if line.start_with?(prefix)
1760
+ end
1785
1761
  end
1762
+ nil
1763
+ end
1764
+
1765
+ private def ed_search_prev_history(key, arg: 1)
1766
+ substr = current_line.byteslice(0, @byte_pointer)
1767
+ return if @history_pointer == 0
1768
+ return if @history_pointer.nil? && substr.empty? && !current_line.empty?
1769
+
1770
+ history_range = 0...(@history_pointer || Reline::HISTORY.size)
1771
+ h_pointer, line_index = search_history(substr, history_range.reverse_each)
1772
+ return unless h_pointer
1773
+ move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer)
1786
1774
  arg -= 1
1787
1775
  ed_search_prev_history(key, arg: arg) if arg > 0
1788
1776
  end
1789
1777
  alias_method :history_search_backward, :ed_search_prev_history
1790
1778
 
1791
1779
  private def ed_search_next_history(key, arg: 1)
1792
- substr = current_line.slice(0, @byte_pointer)
1793
- if @history_pointer.nil?
1794
- return
1795
- elsif @history_pointer == (Reline::HISTORY.size - 1) and not substr.empty?
1796
- return
1797
- end
1798
- history = Reline::HISTORY.slice((@history_pointer + 1)..-1)
1799
- h_pointer = nil
1800
- line_no = nil
1801
- if @is_multiline
1802
- h_pointer = history.index { |h|
1803
- h.split("\n").each_with_index { |l, i|
1804
- if l.start_with?(substr)
1805
- line_no = i
1806
- break
1807
- end
1808
- }
1809
- not line_no.nil?
1810
- }
1811
- else
1812
- h_pointer = history.index { |l|
1813
- l.start_with?(substr)
1814
- }
1815
- end
1816
- h_pointer += @history_pointer + 1 if h_pointer and @history_pointer
1780
+ substr = current_line.byteslice(0, @byte_pointer)
1781
+ return if @history_pointer.nil?
1782
+
1783
+ history_range = @history_pointer + 1...Reline::HISTORY.size
1784
+ h_pointer, line_index = search_history(substr, history_range)
1817
1785
  return if h_pointer.nil? and not substr.empty?
1818
- @history_pointer = h_pointer
1819
- if @is_multiline
1820
- if @history_pointer.nil? and substr.empty?
1821
- @buffer_of_lines = []
1822
- @line_index = 0
1823
- @byte_pointer = 0
1824
- else
1825
- cursor = current_byte_pointer_cursor
1826
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1827
- @line_index = line_no
1828
- calculate_nearest_cursor(cursor)
1829
- end
1830
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1831
- else
1832
- if @history_pointer.nil? and substr.empty?
1833
- set_current_line('', 0)
1834
- else
1835
- set_current_line(Reline::HISTORY[@history_pointer])
1836
- end
1837
- end
1786
+
1787
+ move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer)
1838
1788
  arg -= 1
1839
1789
  ed_search_next_history(key, arg: arg) if arg > 0
1840
1790
  end
1841
1791
  alias_method :history_search_forward, :ed_search_next_history
1842
1792
 
1793
+ private def move_history(history_pointer, line:, cursor:, save_buffer: true)
1794
+ history_pointer ||= Reline::HISTORY.size
1795
+ return if history_pointer < 0 || history_pointer > Reline::HISTORY.size
1796
+ old_history_pointer = @history_pointer || Reline::HISTORY.size
1797
+ if old_history_pointer == Reline::HISTORY.size
1798
+ @line_backup_in_history = save_buffer ? whole_buffer : ''
1799
+ else
1800
+ Reline::HISTORY[old_history_pointer] = whole_buffer if save_buffer
1801
+ end
1802
+ if history_pointer == Reline::HISTORY.size
1803
+ buf = @line_backup_in_history
1804
+ @history_pointer = @line_backup_in_history = nil
1805
+ else
1806
+ buf = Reline::HISTORY[history_pointer]
1807
+ @history_pointer = history_pointer
1808
+ end
1809
+ @buffer_of_lines = buf.split("\n")
1810
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1811
+ @line_index = line == :start ? 0 : line == :end ? @buffer_of_lines.size - 1 : line
1812
+ @byte_pointer = cursor == :start ? 0 : cursor == :end ? current_line.bytesize : cursor
1813
+ end
1814
+
1843
1815
  private def ed_prev_history(key, arg: 1)
1844
- if @is_multiline and @line_index > 0
1816
+ if @line_index > 0
1845
1817
  cursor = current_byte_pointer_cursor
1846
1818
  @line_index -= 1
1847
1819
  calculate_nearest_cursor(cursor)
1848
1820
  return
1849
1821
  end
1850
- if Reline::HISTORY.empty?
1851
- return
1852
- end
1853
- if @history_pointer.nil?
1854
- @history_pointer = Reline::HISTORY.size - 1
1855
- cursor = current_byte_pointer_cursor
1856
- if @is_multiline
1857
- @line_backup_in_history = whole_buffer
1858
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1859
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1860
- @line_index = @buffer_of_lines.size - 1
1861
- calculate_nearest_cursor(cursor)
1862
- else
1863
- @line_backup_in_history = whole_buffer
1864
- @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
1865
- calculate_nearest_cursor(cursor)
1866
- end
1867
- elsif @history_pointer.zero?
1868
- return
1869
- else
1870
- if @is_multiline
1871
- Reline::HISTORY[@history_pointer] = whole_buffer
1872
- @history_pointer -= 1
1873
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1874
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1875
- @line_index = @buffer_of_lines.size - 1
1876
- else
1877
- Reline::HISTORY[@history_pointer] = whole_buffer
1878
- @history_pointer -= 1
1879
- @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
1880
- end
1881
- end
1882
- if @config.editing_mode_is?(:emacs, :vi_insert)
1883
- @byte_pointer = current_line.bytesize
1884
- elsif @config.editing_mode_is?(:vi_command)
1885
- @byte_pointer = 0
1886
- end
1822
+ move_history(
1823
+ (@history_pointer || Reline::HISTORY.size) - 1,
1824
+ line: :end,
1825
+ cursor: @config.editing_mode_is?(:vi_command) ? :start : :end,
1826
+ )
1887
1827
  arg -= 1
1888
1828
  ed_prev_history(key, arg: arg) if arg > 0
1889
1829
  end
1890
1830
  alias_method :previous_history, :ed_prev_history
1891
1831
 
1892
1832
  private def ed_next_history(key, arg: 1)
1893
- if @is_multiline and @line_index < (@buffer_of_lines.size - 1)
1833
+ if @line_index < (@buffer_of_lines.size - 1)
1894
1834
  cursor = current_byte_pointer_cursor
1895
1835
  @line_index += 1
1896
1836
  calculate_nearest_cursor(cursor)
1897
1837
  return
1898
1838
  end
1899
- if @history_pointer.nil?
1900
- return
1901
- elsif @history_pointer == (Reline::HISTORY.size - 1)
1902
- if @is_multiline
1903
- @history_pointer = nil
1904
- @buffer_of_lines = @line_backup_in_history.split("\n")
1905
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1906
- @line_index = 0
1907
- else
1908
- @history_pointer = nil
1909
- @buffer_of_lines = [@line_backup_in_history]
1910
- end
1911
- else
1912
- if @is_multiline
1913
- Reline::HISTORY[@history_pointer] = whole_buffer
1914
- @history_pointer += 1
1915
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1916
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1917
- @line_index = 0
1918
- else
1919
- Reline::HISTORY[@history_pointer] = whole_buffer
1920
- @history_pointer += 1
1921
- @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
1922
- end
1923
- end
1924
- if @config.editing_mode_is?(:emacs, :vi_insert)
1925
- @byte_pointer = current_line.bytesize
1926
- elsif @config.editing_mode_is?(:vi_command)
1927
- @byte_pointer = 0
1928
- end
1839
+ move_history(
1840
+ (@history_pointer || Reline::HISTORY.size) + 1,
1841
+ line: :start,
1842
+ cursor: @config.editing_mode_is?(:vi_command) ? :start : :end,
1843
+ )
1929
1844
  arg -= 1
1930
1845
  ed_next_history(key, arg: arg) if arg > 0
1931
1846
  end
@@ -1956,17 +1871,13 @@ class Reline::LineEditor
1956
1871
  end
1957
1872
  end
1958
1873
  else
1959
- if @history_pointer
1960
- Reline::HISTORY[@history_pointer] = whole_buffer
1961
- @history_pointer = nil
1962
- end
1963
1874
  finish
1964
1875
  end
1965
1876
  end
1966
1877
 
1967
1878
  private def em_delete_prev_char(key, arg: 1)
1968
1879
  arg.times do
1969
- if @is_multiline and @byte_pointer == 0 and @line_index > 0
1880
+ if @byte_pointer == 0 and @line_index > 0
1970
1881
  @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
1971
1882
  @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
1972
1883
  @line_index -= 1
@@ -1990,7 +1901,7 @@ class Reline::LineEditor
1990
1901
  line, deleted = byteslice!(current_line, @byte_pointer, current_line.bytesize - @byte_pointer)
1991
1902
  set_current_line(line, line.bytesize)
1992
1903
  @kill_ring.append(deleted)
1993
- elsif @is_multiline and @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
1904
+ elsif @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
1994
1905
  set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize)
1995
1906
  end
1996
1907
  end
@@ -2030,7 +1941,7 @@ class Reline::LineEditor
2030
1941
  alias_method :kill_whole_line, :em_kill_line
2031
1942
 
2032
1943
  private def em_delete(key)
2033
- if current_line.empty? and (not @is_multiline or @buffer_of_lines.size == 1) and key == "\C-d".ord
1944
+ if current_line.empty? and @buffer_of_lines.size == 1 and key == "\C-d".ord
2034
1945
  @eof = true
2035
1946
  finish
2036
1947
  elsif @byte_pointer < current_line.bytesize
@@ -2038,7 +1949,7 @@ class Reline::LineEditor
2038
1949
  mbchar = splitted_last.grapheme_clusters.first
2039
1950
  line, = byteslice!(current_line, @byte_pointer, mbchar.bytesize)
2040
1951
  set_current_line(line)
2041
- elsif @is_multiline and @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
1952
+ elsif @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
2042
1953
  set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize)
2043
1954
  end
2044
1955
  end
@@ -2050,7 +1961,7 @@ class Reline::LineEditor
2050
1961
  elsif !@config.autocompletion # show completed list
2051
1962
  result = call_completion_proc
2052
1963
  if result.is_a?(Array)
2053
- complete(result, true)
1964
+ perform_completion(result, true)
2054
1965
  end
2055
1966
  end
2056
1967
  end
@@ -2281,7 +2192,7 @@ class Reline::LineEditor
2281
2192
  end
2282
2193
 
2283
2194
  private def vi_delete_prev_char(key)
2284
- if @is_multiline and @byte_pointer == 0 and @line_index > 0
2195
+ if @byte_pointer == 0 and @line_index > 0
2285
2196
  @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
2286
2197
  @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
2287
2198
  @line_index -= 1
@@ -2378,7 +2289,7 @@ class Reline::LineEditor
2378
2289
  end
2379
2290
 
2380
2291
  private def vi_list_or_eof(key)
2381
- if (not @is_multiline and current_line.empty?) or (@is_multiline and current_line.empty? and @buffer_of_lines.size == 1)
2292
+ if current_line.empty? and @buffer_of_lines.size == 1
2382
2293
  set_current_line('', 0)
2383
2294
  @eof = true
2384
2295
  finish
@@ -2409,36 +2320,18 @@ class Reline::LineEditor
2409
2320
  if Reline::HISTORY.empty?
2410
2321
  return
2411
2322
  end
2412
- if @history_pointer.nil?
2413
- @history_pointer = 0
2414
- @line_backup_in_history = current_line
2415
- set_current_line(Reline::HISTORY[@history_pointer], 0)
2416
- elsif @history_pointer.zero?
2417
- return
2418
- else
2419
- Reline::HISTORY[@history_pointer] = current_line
2420
- @history_pointer = 0
2421
- set_current_line(Reline::HISTORY[@history_pointer], 0)
2422
- end
2323
+ move_history(0, line: :start, cursor: :start)
2423
2324
  end
2424
2325
 
2425
2326
  private def vi_histedit(key)
2426
2327
  path = Tempfile.open { |fp|
2427
- if @is_multiline
2428
- fp.write whole_lines.join("\n")
2429
- else
2430
- fp.write current_line
2431
- end
2328
+ fp.write whole_lines.join("\n")
2432
2329
  fp.path
2433
2330
  }
2434
2331
  system("#{ENV['EDITOR']} #{path}")
2435
- if @is_multiline
2436
- @buffer_of_lines = File.read(path).split("\n")
2437
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2438
- @line_index = 0
2439
- else
2440
- @buffer_of_lines = File.read(path).split("\n")
2441
- end
2332
+ @buffer_of_lines = File.read(path).split("\n")
2333
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2334
+ @line_index = 0
2442
2335
  finish
2443
2336
  end
2444
2337
 
@@ -2610,7 +2503,7 @@ class Reline::LineEditor
2610
2503
  end
2611
2504
 
2612
2505
  private def vi_join_lines(key, arg: 1)
2613
- if @is_multiline and @buffer_of_lines.size > @line_index + 1
2506
+ if @buffer_of_lines.size > @line_index + 1
2614
2507
  next_line = @buffer_of_lines.delete_at(@line_index + 1).lstrip
2615
2508
  set_current_line(current_line + ' ' + next_line, current_line.bytesize)
2616
2509
  end
@@ -2638,4 +2531,24 @@ class Reline::LineEditor
2638
2531
  private def vi_editing_mode(key)
2639
2532
  @config.editing_mode = :vi_insert
2640
2533
  end
2534
+
2535
+ private def undo(_key)
2536
+ @undoing = true
2537
+
2538
+ return if @input_lines_position <= 0
2539
+
2540
+ @input_lines_position -= 1
2541
+ target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position]
2542
+ set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y)
2543
+ end
2544
+
2545
+ private def redo(_key)
2546
+ @undoing = true
2547
+
2548
+ return if @input_lines_position >= @input_lines.size - 1
2549
+
2550
+ @input_lines_position += 1
2551
+ target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position]
2552
+ set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y)
2553
+ end
2641
2554
  end