reline 0.5.2 → 0.5.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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