reline 0.5.1 → 0.5.4

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.
@@ -33,8 +33,6 @@ class Reline::LineEditor
33
33
  vi_next_big_word
34
34
  vi_prev_big_word
35
35
  vi_end_big_word
36
- vi_repeat_next_char
37
- vi_repeat_prev_char
38
36
  }
39
37
 
40
38
  module CompletionState
@@ -114,7 +112,11 @@ class Reline::LineEditor
114
112
  else
115
113
  prompt = @prompt
116
114
  end
117
- if @prompt_proc
115
+ if !@is_multiline
116
+ mode_string = check_mode_string
117
+ prompt = mode_string + prompt if mode_string
118
+ [prompt] + [''] * (buffer.size - 1)
119
+ elsif @prompt_proc
118
120
  prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") }
119
121
  prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
120
122
  prompt_list = [prompt] if prompt_list.empty?
@@ -229,10 +231,11 @@ class Reline::LineEditor
229
231
  @vi_clipboard = ''
230
232
  @vi_arg = nil
231
233
  @waiting_proc = nil
232
- @waiting_operator_proc = nil
233
- @waiting_operator_vi_arg = nil
234
+ @vi_waiting_operator = nil
235
+ @vi_waiting_operator_arg = nil
234
236
  @completion_journey_state = nil
235
237
  @completion_state = CompletionState::NORMAL
238
+ @completion_occurs = false
236
239
  @perfect_matched = nil
237
240
  @menu_info = nil
238
241
  @searching_prompt = nil
@@ -292,8 +295,8 @@ class Reline::LineEditor
292
295
  end
293
296
  end
294
297
 
295
- private def split_by_width(str, max_width)
296
- Reline::Unicode.split_by_width(str, max_width, @encoding)
298
+ private def split_by_width(str, max_width, offset: 0)
299
+ Reline::Unicode.split_by_width(str, max_width, @encoding, offset: offset)
297
300
  end
298
301
 
299
302
  def current_byte_pointer_cursor
@@ -367,7 +370,7 @@ class Reline::LineEditor
367
370
  @scroll_partial_screen
368
371
  end
369
372
 
370
- def wrapped_lines
373
+ def wrapped_prompt_and_input_lines
371
374
  with_cache(__method__, @buffer_of_lines.size, modified_lines, prompt_list, screen_width) do |n, lines, prompts, width, prev_cache_key, cached_value|
372
375
  prev_n, prev_lines, prev_prompts, prev_width = prev_cache_key
373
376
  cached_wraps = {}
@@ -378,9 +381,14 @@ class Reline::LineEditor
378
381
  end
379
382
 
380
383
  n.times.map do |i|
381
- prompt = prompts[i]
382
- line = lines[i]
383
- cached_wraps[[prompt, line]] || split_by_width("#{prompt}#{line}", width).first.compact
384
+ prompt = prompts[i] || ''
385
+ line = lines[i] || ''
386
+ if (cached = cached_wraps[[prompt, line]])
387
+ next cached
388
+ end
389
+ *wrapped_prompts, code_line_prompt = split_by_width(prompt, width).first.compact
390
+ wrapped_lines = split_by_width(line, width, offset: calculate_width(code_line_prompt)).first.compact
391
+ wrapped_prompts.map { |p| [p, ''] } + [[code_line_prompt, wrapped_lines.first]] + wrapped_lines.drop(1).map { |c| ['', c] }
384
392
  end
385
393
  end
386
394
  end
@@ -406,8 +414,13 @@ class Reline::LineEditor
406
414
  @output.write "#{Reline::IOGate::RESET_COLOR}#{' ' * width}"
407
415
  else
408
416
  x, w, content = new_items[level]
409
- content = Reline::Unicode.take_range(content, base_x - x, width) unless x == base_x && w == width
410
- Reline::IOGate.move_cursor_column base_x
417
+ cover_begin = base_x != 0 && new_levels[base_x - 1] == level
418
+ cover_end = new_levels[base_x + width] == level
419
+ pos = 0
420
+ unless x == base_x && w == width
421
+ content, pos = Reline::Unicode.take_mbchar_range(content, base_x - x, width, cover_begin: cover_begin, cover_end: cover_end, padding: true)
422
+ end
423
+ Reline::IOGate.move_cursor_column x + pos
411
424
  @output.write "#{Reline::IOGate::RESET_COLOR}#{content}#{Reline::IOGate::RESET_COLOR}"
412
425
  end
413
426
  base_x += width
@@ -423,7 +436,7 @@ class Reline::LineEditor
423
436
  prompt_width = calculate_width(prompt_list[@line_index], true)
424
437
  line_before_cursor = whole_lines[@line_index].byteslice(0, @byte_pointer)
425
438
  wrapped_line_before_cursor = split_by_width(' ' * prompt_width + line_before_cursor, screen_width).first.compact
426
- wrapped_cursor_y = wrapped_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1
439
+ wrapped_cursor_y = wrapped_prompt_and_input_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1
427
440
  wrapped_cursor_x = calculate_width(wrapped_line_before_cursor.last)
428
441
  [wrapped_cursor_x, wrapped_cursor_y]
429
442
  end
@@ -487,8 +500,9 @@ class Reline::LineEditor
487
500
  wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
488
501
 
489
502
  rendered_lines = @rendered_screen.lines
490
- new_lines = wrapped_lines.flatten[screen_scroll_top, screen_height].map do |l|
491
- [[0, Reline::Unicode.calculate_width(l, true), l]]
503
+ new_lines = wrapped_prompt_and_input_lines.flatten(1)[screen_scroll_top, screen_height].map do |prompt, line|
504
+ prompt_width = Reline::Unicode.calculate_width(prompt, true)
505
+ [[0, prompt_width, prompt], [prompt_width, Reline::Unicode.calculate_width(line, true), line]]
492
506
  end
493
507
  if @menu_info
494
508
  @menu_info.lines(screen_width).each do |item|
@@ -504,7 +518,8 @@ class Reline::LineEditor
504
518
  y_range.each do |row|
505
519
  next if row < 0 || row >= screen_height
506
520
  dialog_rows = new_lines[row] ||= []
507
- dialog_rows[index + 1] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
521
+ # index 0 is for prompt, index 1 is for line, index 2.. is for dialog
522
+ dialog_rows[index + 2] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
508
523
  end
509
524
  end
510
525
 
@@ -541,10 +556,6 @@ class Reline::LineEditor
541
556
  new_lines.size - y
542
557
  end
543
558
 
544
- def current_row
545
- wrapped_lines.flatten[wrapped_cursor_y]
546
- end
547
-
548
559
  def upper_space_height(wrapped_cursor_y)
549
560
  wrapped_cursor_y - screen_scroll_top
550
561
  end
@@ -693,13 +704,6 @@ class Reline::LineEditor
693
704
 
694
705
  DIALOG_DEFAULT_HEIGHT = 20
695
706
 
696
- private def padding_space_with_escape_sequences(str, width)
697
- padding_width = width - calculate_width(str, true)
698
- # padding_width should be only positive value. But macOS and Alacritty returns negative value.
699
- padding_width = 0 if padding_width < 0
700
- str + (' ' * padding_width)
701
- end
702
-
703
707
  private def dialog_range(dialog, dialog_y)
704
708
  x_range = dialog.column...dialog.column + dialog.width
705
709
  y_range = dialog_y + dialog.vertical_offset...dialog_y + dialog.vertical_offset + dialog.contents.size
@@ -772,7 +776,7 @@ class Reline::LineEditor
772
776
  dialog.contents = contents.map.with_index do |item, i|
773
777
  line_sgr = i == pointer ? enhanced_sgr : default_sgr
774
778
  str_width = dialog.width - (scrollbar_pos.nil? ? 0 : @block_elem_width)
775
- str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, str_width), str_width)
779
+ str, = Reline::Unicode.take_mbchar_range(item, 0, str_width, padding: true)
776
780
  colored_content = "#{line_sgr}#{str}"
777
781
  if scrollbar_pos
778
782
  if scrollbar_pos <= (i * 2) and (i * 2 + 1) < (scrollbar_pos + bar_height)
@@ -881,10 +885,12 @@ class Reline::LineEditor
881
885
  @completion_state = CompletionState::PERFECT_MATCH
882
886
  else
883
887
  @completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
888
+ complete(list, true) if @config.show_all_if_ambiguous
884
889
  end
885
890
  @perfect_matched = completed
886
891
  else
887
892
  @completion_state = CompletionState::MENU
893
+ complete(list, true) if @config.show_all_if_ambiguous
888
894
  end
889
895
  if not just_show_list and target < completed
890
896
  @buffer_of_lines[@line_index] = (preposing + completed + completion_append_character.to_s + postposing).split("\n")[@line_index] || String.new(encoding: @encoding)
@@ -935,37 +941,23 @@ class Reline::LineEditor
935
941
  end
936
942
 
937
943
  private def run_for_operators(key, method_symbol, &block)
938
- if @waiting_operator_proc
944
+ if @vi_waiting_operator
939
945
  if VI_MOTIONS.include?(method_symbol)
940
946
  old_byte_pointer = @byte_pointer
941
- @vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg&.> 1
947
+ @vi_arg = (@vi_arg || 1) * @vi_waiting_operator_arg
942
948
  block.(true)
943
949
  unless @waiting_proc
944
950
  byte_pointer_diff = @byte_pointer - old_byte_pointer
945
951
  @byte_pointer = old_byte_pointer
946
- @waiting_operator_proc.(byte_pointer_diff)
947
- else
948
- old_waiting_proc = @waiting_proc
949
- old_waiting_operator_proc = @waiting_operator_proc
950
- current_waiting_operator_proc = @waiting_operator_proc
951
- @waiting_proc = proc { |k|
952
- old_byte_pointer = @byte_pointer
953
- old_waiting_proc.(k)
954
- byte_pointer_diff = @byte_pointer - old_byte_pointer
955
- @byte_pointer = old_byte_pointer
956
- current_waiting_operator_proc.(byte_pointer_diff)
957
- @waiting_operator_proc = old_waiting_operator_proc
958
- }
952
+ send(@vi_waiting_operator, byte_pointer_diff)
953
+ cleanup_waiting
959
954
  end
960
955
  else
961
956
  # Ignores operator when not motion is given.
962
957
  block.(false)
958
+ cleanup_waiting
963
959
  end
964
- @waiting_operator_proc = nil
965
- @waiting_operator_vi_arg = nil
966
- if @vi_arg
967
- @vi_arg = nil
968
- end
960
+ @vi_arg = nil
969
961
  else
970
962
  block.(false)
971
963
  end
@@ -982,7 +974,7 @@ class Reline::LineEditor
982
974
  end
983
975
 
984
976
  def wrap_method_call(method_symbol, method_obj, key, with_operator = false)
985
- if @config.editing_mode_is?(:emacs, :vi_insert) and @waiting_proc.nil? and @waiting_operator_proc.nil?
977
+ if @config.editing_mode_is?(:emacs, :vi_insert) and @vi_waiting_operator.nil?
986
978
  not_insertion = method_symbol != :ed_insert
987
979
  process_insert(force: not_insertion)
988
980
  end
@@ -1001,11 +993,32 @@ class Reline::LineEditor
1001
993
  end
1002
994
  end
1003
995
 
996
+ private def cleanup_waiting
997
+ @waiting_proc = nil
998
+ @vi_waiting_operator = nil
999
+ @vi_waiting_operator_arg = nil
1000
+ @searching_prompt = nil
1001
+ @drop_terminate_spaces = false
1002
+ end
1003
+
1004
1004
  private def process_key(key, method_symbol)
1005
+ if key.is_a?(Symbol)
1006
+ cleanup_waiting
1007
+ elsif @waiting_proc
1008
+ old_byte_pointer = @byte_pointer
1009
+ @waiting_proc.call(key)
1010
+ if @vi_waiting_operator
1011
+ byte_pointer_diff = @byte_pointer - old_byte_pointer
1012
+ @byte_pointer = old_byte_pointer
1013
+ send(@vi_waiting_operator, byte_pointer_diff)
1014
+ cleanup_waiting
1015
+ end
1016
+ @kill_ring.process
1017
+ return
1018
+ end
1019
+
1005
1020
  if method_symbol and respond_to?(method_symbol, true)
1006
1021
  method_obj = method(method_symbol)
1007
- else
1008
- method_obj = nil
1009
1022
  end
1010
1023
  if method_symbol and key.is_a?(Symbol)
1011
1024
  if @vi_arg and argumentable?(method_obj)
@@ -1027,8 +1040,6 @@ class Reline::LineEditor
1027
1040
  run_for_operators(key, method_symbol) do |with_operator|
1028
1041
  wrap_method_call(method_symbol, method_obj, key, with_operator)
1029
1042
  end
1030
- elsif @waiting_proc
1031
- @waiting_proc.(key)
1032
1043
  elsif method_obj
1033
1044
  wrap_method_call(method_symbol, method_obj, key)
1034
1045
  else
@@ -1039,9 +1050,6 @@ class Reline::LineEditor
1039
1050
  @vi_arg = nil
1040
1051
  end
1041
1052
  end
1042
- elsif @waiting_proc
1043
- @waiting_proc.(key)
1044
- @kill_ring.process
1045
1053
  elsif method_obj
1046
1054
  if method_symbol == :ed_argument_digit
1047
1055
  wrap_method_call(method_symbol, method_obj, key)
@@ -1110,6 +1118,7 @@ class Reline::LineEditor
1110
1118
  end
1111
1119
  end
1112
1120
  if key.char.nil?
1121
+ process_insert(force: true)
1113
1122
  if @first_char
1114
1123
  @eof = true
1115
1124
  end
@@ -1118,42 +1127,35 @@ class Reline::LineEditor
1118
1127
  end
1119
1128
  old_lines = @buffer_of_lines.dup
1120
1129
  @first_char = false
1121
- completion_occurs = false
1130
+ @completion_occurs = false
1122
1131
  if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
1123
1132
  if !@config.disable_completion
1124
1133
  process_insert(force: true)
1125
1134
  if @config.autocompletion
1126
1135
  @completion_state = CompletionState::NORMAL
1127
- completion_occurs = move_completed_list(:down)
1136
+ @completion_occurs = move_completed_list(:down)
1128
1137
  else
1129
1138
  @completion_journey_state = nil
1130
1139
  result = call_completion_proc
1131
1140
  if result.is_a?(Array)
1132
- completion_occurs = true
1141
+ @completion_occurs = true
1133
1142
  complete(result, false)
1134
1143
  end
1135
1144
  end
1136
1145
  end
1137
- elsif @config.editing_mode_is?(:emacs, :vi_insert) and key.char == :completion_journey_up
1138
- if not @config.disable_completion and @config.autocompletion
1139
- process_insert(force: true)
1140
- @completion_state = CompletionState::NORMAL
1141
- completion_occurs = move_completed_list(:up)
1142
- end
1143
1146
  elsif @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
1144
1147
  # In vi mode, move completed list even if autocompletion is off
1145
1148
  if not @config.disable_completion
1146
1149
  process_insert(force: true)
1147
1150
  @completion_state = CompletionState::NORMAL
1148
- completion_occurs = move_completed_list("\C-p".ord == key.char ? :up : :down)
1151
+ @completion_occurs = move_completed_list("\C-p".ord == key.char ? :up : :down)
1149
1152
  end
1150
1153
  elsif Symbol === key.char and respond_to?(key.char, true)
1151
1154
  process_key(key.char, key.char)
1152
1155
  else
1153
1156
  normal_char(key)
1154
1157
  end
1155
-
1156
- unless completion_occurs
1158
+ unless @completion_occurs
1157
1159
  @completion_state = CompletionState::NORMAL
1158
1160
  @completion_journey_state = nil
1159
1161
  end
@@ -1164,7 +1166,7 @@ class Reline::LineEditor
1164
1166
  end
1165
1167
 
1166
1168
  modified = old_lines != @buffer_of_lines
1167
- if !completion_occurs && modified && !@config.disable_completion && @config.autocompletion
1169
+ if !@completion_occurs && modified && !@config.disable_completion && @config.autocompletion
1168
1170
  # Auto complete starts only when edited
1169
1171
  process_insert(force: true)
1170
1172
  @completion_journey_state = retrieve_completion_journey_state
@@ -1230,7 +1232,7 @@ class Reline::LineEditor
1230
1232
  end
1231
1233
 
1232
1234
  def line()
1233
- current_line unless eof?
1235
+ @buffer_of_lines.join("\n") unless eof?
1234
1236
  end
1235
1237
 
1236
1238
  def current_line
@@ -1314,14 +1316,12 @@ class Reline::LineEditor
1314
1316
  end
1315
1317
  target = before
1316
1318
  end
1317
- if @is_multiline
1318
- lines = whole_lines
1319
- if @line_index > 0
1320
- preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
1321
- end
1322
- if (lines.size - 1) > @line_index
1323
- postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
1324
- end
1319
+ lines = whole_lines
1320
+ if @line_index > 0
1321
+ preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
1322
+ end
1323
+ if (lines.size - 1) > @line_index
1324
+ postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
1325
1325
  end
1326
1326
  [preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
1327
1327
  end
@@ -1343,20 +1343,16 @@ class Reline::LineEditor
1343
1343
 
1344
1344
  def delete_text(start = nil, length = nil)
1345
1345
  if start.nil? and length.nil?
1346
- if @is_multiline
1347
- if @buffer_of_lines.size == 1
1348
- @buffer_of_lines[@line_index] = ''
1349
- @byte_pointer = 0
1350
- elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
1351
- @buffer_of_lines.pop
1352
- @line_index -= 1
1353
- @byte_pointer = 0
1354
- elsif @line_index < (@buffer_of_lines.size - 1)
1355
- @buffer_of_lines.delete_at(@line_index)
1356
- @byte_pointer = 0
1357
- end
1358
- else
1359
- set_current_line('', 0)
1346
+ if @buffer_of_lines.size == 1
1347
+ @buffer_of_lines[@line_index] = ''
1348
+ @byte_pointer = 0
1349
+ elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
1350
+ @buffer_of_lines.pop
1351
+ @line_index -= 1
1352
+ @byte_pointer = 0
1353
+ elsif @line_index < (@buffer_of_lines.size - 1)
1354
+ @buffer_of_lines.delete_at(@line_index)
1355
+ @byte_pointer = 0
1360
1356
  end
1361
1357
  elsif not start.nil? and not length.nil?
1362
1358
  if current_line
@@ -1433,6 +1429,14 @@ class Reline::LineEditor
1433
1429
  end
1434
1430
  end
1435
1431
 
1432
+ private def completion_journey_up(key)
1433
+ if not @config.disable_completion and @config.autocompletion
1434
+ @completion_state = CompletionState::NORMAL
1435
+ @completion_occurs = move_completed_list(:up)
1436
+ end
1437
+ end
1438
+ alias_method :menu_complete_backward, :completion_journey_up
1439
+
1436
1440
  # Editline:: +ed-unassigned+ This editor command always results in an error.
1437
1441
  # GNU Readline:: There is no corresponding macro.
1438
1442
  private def ed_unassigned(key) end # do nothing
@@ -1504,7 +1508,7 @@ class Reline::LineEditor
1504
1508
  byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
1505
1509
  if (@byte_pointer < current_line.bytesize)
1506
1510
  @byte_pointer += byte_size
1507
- elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == current_line.bytesize and @line_index < @buffer_of_lines.size - 1
1511
+ elsif @config.editing_mode_is?(:emacs) and @byte_pointer == current_line.bytesize and @line_index < @buffer_of_lines.size - 1
1508
1512
  @byte_pointer = 0
1509
1513
  @line_index += 1
1510
1514
  end
@@ -1517,7 +1521,7 @@ class Reline::LineEditor
1517
1521
  if @byte_pointer > 0
1518
1522
  byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
1519
1523
  @byte_pointer -= byte_size
1520
- elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0
1524
+ elsif @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0
1521
1525
  @line_index -= 1
1522
1526
  @byte_pointer = current_line.bytesize
1523
1527
  end
@@ -1534,141 +1538,102 @@ class Reline::LineEditor
1534
1538
  @byte_pointer = 0
1535
1539
  end
1536
1540
  alias_method :beginning_of_line, :ed_move_to_beg
1541
+ alias_method :vi_zero, :ed_move_to_beg
1537
1542
 
1538
1543
  private def ed_move_to_end(key)
1539
- @byte_pointer = 0
1540
- while @byte_pointer < current_line.bytesize
1541
- byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
1542
- @byte_pointer += byte_size
1543
- end
1544
+ @byte_pointer = current_line.bytesize
1544
1545
  end
1545
1546
  alias_method :end_of_line, :ed_move_to_end
1546
1547
 
1547
- private def generate_searcher
1548
- Fiber.new do |first_key|
1549
- prev_search_key = first_key
1550
- search_word = String.new(encoding: @encoding)
1551
- multibyte_buf = String.new(encoding: 'ASCII-8BIT')
1552
- last_hit = nil
1553
- case first_key
1554
- when "\C-r".ord
1555
- prompt_name = 'reverse-i-search'
1556
- when "\C-s".ord
1557
- prompt_name = 'i-search'
1548
+ private def generate_searcher(search_key)
1549
+ search_word = String.new(encoding: @encoding)
1550
+ multibyte_buf = String.new(encoding: 'ASCII-8BIT')
1551
+ hit_pointer = nil
1552
+ lambda do |key|
1553
+ search_again = false
1554
+ case key
1555
+ when "\C-h".ord, "\C-?".ord
1556
+ grapheme_clusters = search_word.grapheme_clusters
1557
+ if grapheme_clusters.size > 0
1558
+ grapheme_clusters.pop
1559
+ search_word = grapheme_clusters.join
1560
+ end
1561
+ when "\C-r".ord, "\C-s".ord
1562
+ search_again = true if search_key == key
1563
+ search_key = key
1564
+ else
1565
+ multibyte_buf << key
1566
+ if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
1567
+ search_word << multibyte_buf.dup.force_encoding(@encoding)
1568
+ multibyte_buf.clear
1569
+ end
1558
1570
  end
1559
- loop do
1560
- key = Fiber.yield(search_word)
1561
- search_again = false
1562
- case key
1563
- when -1 # determined
1564
- Reline.last_incremental_search = search_word
1565
- break
1566
- when "\C-h".ord, "\C-?".ord
1567
- grapheme_clusters = search_word.grapheme_clusters
1568
- if grapheme_clusters.size > 0
1569
- grapheme_clusters.pop
1570
- search_word = grapheme_clusters.join
1571
- end
1572
- when "\C-r".ord, "\C-s".ord
1573
- search_again = true if prev_search_key == key
1574
- prev_search_key = key
1575
- else
1576
- multibyte_buf << key
1577
- if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
1578
- search_word << multibyte_buf.dup.force_encoding(@encoding)
1579
- multibyte_buf.clear
1571
+ hit = nil
1572
+ if not search_word.empty? and @line_backup_in_history&.include?(search_word)
1573
+ hit_pointer = Reline::HISTORY.size
1574
+ hit = @line_backup_in_history
1575
+ else
1576
+ if search_again
1577
+ if search_word.empty? and Reline.last_incremental_search
1578
+ search_word = Reline.last_incremental_search
1580
1579
  end
1581
- end
1582
- hit = nil
1583
- if not search_word.empty? and @line_backup_in_history&.include?(search_word)
1584
- @history_pointer = nil
1585
- hit = @line_backup_in_history
1586
- else
1587
- if search_again
1588
- if search_word.empty? and Reline.last_incremental_search
1589
- search_word = Reline.last_incremental_search
1590
- end
1591
- if @history_pointer
1592
- case prev_search_key
1593
- when "\C-r".ord
1594
- history_pointer_base = 0
1595
- history = Reline::HISTORY[0..(@history_pointer - 1)]
1596
- when "\C-s".ord
1597
- history_pointer_base = @history_pointer + 1
1598
- history = Reline::HISTORY[(@history_pointer + 1)..-1]
1599
- end
1600
- else
1601
- history_pointer_base = 0
1602
- history = Reline::HISTORY
1603
- end
1604
- elsif @history_pointer
1605
- case prev_search_key
1580
+ if @history_pointer
1581
+ case search_key
1606
1582
  when "\C-r".ord
1607
1583
  history_pointer_base = 0
1608
- history = Reline::HISTORY[0..@history_pointer]
1584
+ history = Reline::HISTORY[0..(@history_pointer - 1)]
1609
1585
  when "\C-s".ord
1610
- history_pointer_base = @history_pointer
1611
- history = Reline::HISTORY[@history_pointer..-1]
1586
+ history_pointer_base = @history_pointer + 1
1587
+ history = Reline::HISTORY[(@history_pointer + 1)..-1]
1612
1588
  end
1613
1589
  else
1614
1590
  history_pointer_base = 0
1615
1591
  history = Reline::HISTORY
1616
1592
  end
1617
- case prev_search_key
1593
+ elsif @history_pointer
1594
+ case search_key
1618
1595
  when "\C-r".ord
1619
- hit_index = history.rindex { |item|
1620
- item.include?(search_word)
1621
- }
1596
+ history_pointer_base = 0
1597
+ history = Reline::HISTORY[0..@history_pointer]
1622
1598
  when "\C-s".ord
1623
- hit_index = history.index { |item|
1624
- item.include?(search_word)
1625
- }
1626
- end
1627
- if hit_index
1628
- @history_pointer = history_pointer_base + hit_index
1629
- hit = Reline::HISTORY[@history_pointer]
1599
+ history_pointer_base = @history_pointer
1600
+ history = Reline::HISTORY[@history_pointer..-1]
1630
1601
  end
1602
+ else
1603
+ history_pointer_base = 0
1604
+ history = Reline::HISTORY
1631
1605
  end
1632
- case prev_search_key
1606
+ case search_key
1633
1607
  when "\C-r".ord
1634
- prompt_name = 'reverse-i-search'
1608
+ hit_index = history.rindex { |item|
1609
+ item.include?(search_word)
1610
+ }
1635
1611
  when "\C-s".ord
1636
- prompt_name = 'i-search'
1612
+ hit_index = history.index { |item|
1613
+ item.include?(search_word)
1614
+ }
1637
1615
  end
1638
- if hit
1639
- if @is_multiline
1640
- @buffer_of_lines = hit.split("\n")
1641
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1642
- @line_index = @buffer_of_lines.size - 1
1643
- @byte_pointer = current_line.bytesize
1644
- @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
1645
- else
1646
- @buffer_of_lines = [hit]
1647
- @byte_pointer = hit.bytesize
1648
- @searching_prompt = "(%s)`%s': %s" % [prompt_name, search_word, hit]
1649
- end
1650
- last_hit = hit
1651
- else
1652
- if @is_multiline
1653
- @searching_prompt = "(failed %s)`%s'" % [prompt_name, search_word]
1654
- else
1655
- @searching_prompt = "(failed %s)`%s': %s" % [prompt_name, search_word, last_hit]
1656
- end
1616
+ if hit_index
1617
+ hit_pointer = history_pointer_base + hit_index
1618
+ hit = Reline::HISTORY[hit_pointer]
1657
1619
  end
1658
1620
  end
1621
+ case search_key
1622
+ when "\C-r".ord
1623
+ prompt_name = 'reverse-i-search'
1624
+ when "\C-s".ord
1625
+ prompt_name = 'i-search'
1626
+ end
1627
+ prompt_name = "failed #{prompt_name}" unless hit
1628
+ [search_word, prompt_name, hit_pointer]
1659
1629
  end
1660
1630
  end
1661
1631
 
1662
1632
  private def incremental_search_history(key)
1663
1633
  unless @history_pointer
1664
- if @is_multiline
1665
- @line_backup_in_history = whole_buffer
1666
- else
1667
- @line_backup_in_history = current_line
1668
- end
1634
+ @line_backup_in_history = whole_buffer
1669
1635
  end
1670
- searcher = generate_searcher
1671
- searcher.resume(key)
1636
+ searcher = generate_searcher(key)
1672
1637
  @searching_prompt = "(reverse-i-search)`': "
1673
1638
  termination_keys = ["\C-j".ord]
1674
1639
  termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators
@@ -1680,53 +1645,41 @@ class Reline::LineEditor
1680
1645
  else
1681
1646
  buffer = @line_backup_in_history
1682
1647
  end
1683
- if @is_multiline
1684
- @buffer_of_lines = buffer.split("\n")
1685
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1686
- @line_index = @buffer_of_lines.size - 1
1687
- else
1688
- @buffer_of_lines = [buffer]
1689
- end
1648
+ @buffer_of_lines = buffer.split("\n")
1649
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1650
+ @line_index = @buffer_of_lines.size - 1
1690
1651
  @searching_prompt = nil
1691
1652
  @waiting_proc = nil
1692
1653
  @byte_pointer = 0
1693
- searcher.resume(-1)
1694
1654
  when "\C-g".ord
1695
- if @is_multiline
1696
- @buffer_of_lines = @line_backup_in_history.split("\n")
1697
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1698
- @line_index = @buffer_of_lines.size - 1
1699
- else
1700
- @buffer_of_lines = [@line_backup_in_history]
1701
- end
1702
- @history_pointer = nil
1655
+ @buffer_of_lines = @line_backup_in_history.split("\n")
1656
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1657
+ @line_index = @buffer_of_lines.size - 1
1658
+ move_history(nil, line: :end, cursor: :end, save_buffer: false)
1703
1659
  @searching_prompt = nil
1704
1660
  @waiting_proc = nil
1705
- @line_backup_in_history = nil
1706
1661
  @byte_pointer = 0
1707
1662
  else
1708
1663
  chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
1709
1664
  if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == "\C-?".ord or k == "\C-r".ord or k == "\C-s".ord
1710
- searcher.resume(k)
1665
+ search_word, prompt_name, hit_pointer = searcher.call(k)
1666
+ Reline.last_incremental_search = search_word
1667
+ @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
1668
+ @searching_prompt += ': ' unless @is_multiline
1669
+ move_history(hit_pointer, line: :end, cursor: :end, save_buffer: false) if hit_pointer
1711
1670
  else
1712
1671
  if @history_pointer
1713
1672
  line = Reline::HISTORY[@history_pointer]
1714
1673
  else
1715
1674
  line = @line_backup_in_history
1716
1675
  end
1717
- if @is_multiline
1718
- @line_backup_in_history = whole_buffer
1719
- @buffer_of_lines = line.split("\n")
1720
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1721
- @line_index = @buffer_of_lines.size - 1
1722
- else
1723
- @line_backup_in_history = current_line
1724
- @buffer_of_lines = [line]
1725
- end
1676
+ @line_backup_in_history = whole_buffer
1677
+ @buffer_of_lines = line.split("\n")
1678
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1679
+ @line_index = @buffer_of_lines.size - 1
1726
1680
  @searching_prompt = nil
1727
1681
  @waiting_proc = nil
1728
1682
  @byte_pointer = 0
1729
- searcher.resume(-1)
1730
1683
  end
1731
1684
  end
1732
1685
  }
@@ -1742,191 +1695,95 @@ class Reline::LineEditor
1742
1695
  end
1743
1696
  alias_method :forward_search_history, :vi_search_next
1744
1697
 
1745
- private def ed_search_prev_history(key, arg: 1)
1746
- history = nil
1747
- h_pointer = nil
1748
- line_no = nil
1749
- substr = current_line.slice(0, @byte_pointer)
1750
- if @history_pointer.nil?
1751
- return if not current_line.empty? and substr.empty?
1752
- history = Reline::HISTORY
1753
- elsif @history_pointer.zero?
1754
- history = nil
1755
- h_pointer = nil
1756
- else
1757
- history = Reline::HISTORY.slice(0, @history_pointer)
1758
- end
1759
- return if history.nil?
1760
- if @is_multiline
1761
- h_pointer = history.rindex { |h|
1762
- h.split("\n").each_with_index { |l, i|
1763
- if l.start_with?(substr)
1764
- line_no = i
1765
- break
1766
- end
1767
- }
1768
- not line_no.nil?
1769
- }
1770
- else
1771
- h_pointer = history.rindex { |l|
1772
- l.start_with?(substr)
1773
- }
1774
- end
1775
- return if h_pointer.nil?
1776
- @history_pointer = h_pointer
1777
- cursor = current_byte_pointer_cursor
1778
- if @is_multiline
1779
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1780
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1781
- @line_index = line_no
1782
- calculate_nearest_cursor(cursor)
1783
- else
1784
- @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
1785
- calculate_nearest_cursor(cursor)
1698
+ private def search_history(prefix, pointer_range)
1699
+ pointer_range.each do |pointer|
1700
+ lines = Reline::HISTORY[pointer].split("\n")
1701
+ lines.each_with_index do |line, index|
1702
+ return [pointer, index] if line.start_with?(prefix)
1703
+ end
1786
1704
  end
1705
+ nil
1706
+ end
1707
+
1708
+ private def ed_search_prev_history(key, arg: 1)
1709
+ substr = current_line.byteslice(0, @byte_pointer)
1710
+ return if @history_pointer == 0
1711
+ return if @history_pointer.nil? && substr.empty? && !current_line.empty?
1712
+
1713
+ history_range = 0...(@history_pointer || Reline::HISTORY.size)
1714
+ h_pointer, line_index = search_history(substr, history_range.reverse_each)
1715
+ return unless h_pointer
1716
+ move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer)
1787
1717
  arg -= 1
1788
1718
  ed_search_prev_history(key, arg: arg) if arg > 0
1789
1719
  end
1790
1720
  alias_method :history_search_backward, :ed_search_prev_history
1791
1721
 
1792
1722
  private def ed_search_next_history(key, arg: 1)
1793
- substr = current_line.slice(0, @byte_pointer)
1794
- if @history_pointer.nil?
1795
- return
1796
- elsif @history_pointer == (Reline::HISTORY.size - 1) and not substr.empty?
1797
- return
1798
- end
1799
- history = Reline::HISTORY.slice((@history_pointer + 1)..-1)
1800
- h_pointer = nil
1801
- line_no = nil
1802
- if @is_multiline
1803
- h_pointer = history.index { |h|
1804
- h.split("\n").each_with_index { |l, i|
1805
- if l.start_with?(substr)
1806
- line_no = i
1807
- break
1808
- end
1809
- }
1810
- not line_no.nil?
1811
- }
1812
- else
1813
- h_pointer = history.index { |l|
1814
- l.start_with?(substr)
1815
- }
1816
- end
1817
- h_pointer += @history_pointer + 1 if h_pointer and @history_pointer
1723
+ substr = current_line.byteslice(0, @byte_pointer)
1724
+ return if @history_pointer.nil?
1725
+
1726
+ history_range = @history_pointer + 1...Reline::HISTORY.size
1727
+ h_pointer, line_index = search_history(substr, history_range)
1818
1728
  return if h_pointer.nil? and not substr.empty?
1819
- @history_pointer = h_pointer
1820
- if @is_multiline
1821
- if @history_pointer.nil? and substr.empty?
1822
- @buffer_of_lines = []
1823
- @line_index = 0
1824
- @byte_pointer = 0
1825
- else
1826
- cursor = current_byte_pointer_cursor
1827
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1828
- @line_index = line_no
1829
- calculate_nearest_cursor(cursor)
1830
- end
1831
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1832
- else
1833
- if @history_pointer.nil? and substr.empty?
1834
- set_current_line('', 0)
1835
- else
1836
- set_current_line(Reline::HISTORY[@history_pointer])
1837
- end
1838
- end
1729
+
1730
+ move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer)
1839
1731
  arg -= 1
1840
1732
  ed_search_next_history(key, arg: arg) if arg > 0
1841
1733
  end
1842
1734
  alias_method :history_search_forward, :ed_search_next_history
1843
1735
 
1736
+ private def move_history(history_pointer, line:, cursor:, save_buffer: true)
1737
+ history_pointer ||= Reline::HISTORY.size
1738
+ return if history_pointer < 0 || history_pointer > Reline::HISTORY.size
1739
+ old_history_pointer = @history_pointer || Reline::HISTORY.size
1740
+ if old_history_pointer == Reline::HISTORY.size
1741
+ @line_backup_in_history = save_buffer ? whole_buffer : ''
1742
+ else
1743
+ Reline::HISTORY[old_history_pointer] = whole_buffer if save_buffer
1744
+ end
1745
+ if history_pointer == Reline::HISTORY.size
1746
+ buf = @line_backup_in_history
1747
+ @history_pointer = @line_backup_in_history = nil
1748
+ else
1749
+ buf = Reline::HISTORY[history_pointer]
1750
+ @history_pointer = history_pointer
1751
+ end
1752
+ @buffer_of_lines = buf.split("\n")
1753
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1754
+ @line_index = line == :start ? 0 : line == :end ? @buffer_of_lines.size - 1 : line
1755
+ @byte_pointer = cursor == :start ? 0 : cursor == :end ? current_line.bytesize : cursor
1756
+ end
1757
+
1844
1758
  private def ed_prev_history(key, arg: 1)
1845
- if @is_multiline and @line_index > 0
1759
+ if @line_index > 0
1846
1760
  cursor = current_byte_pointer_cursor
1847
1761
  @line_index -= 1
1848
1762
  calculate_nearest_cursor(cursor)
1849
1763
  return
1850
1764
  end
1851
- if Reline::HISTORY.empty?
1852
- return
1853
- end
1854
- if @history_pointer.nil?
1855
- @history_pointer = Reline::HISTORY.size - 1
1856
- cursor = current_byte_pointer_cursor
1857
- if @is_multiline
1858
- @line_backup_in_history = whole_buffer
1859
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1860
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1861
- @line_index = @buffer_of_lines.size - 1
1862
- calculate_nearest_cursor(cursor)
1863
- else
1864
- @line_backup_in_history = whole_buffer
1865
- @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
1866
- calculate_nearest_cursor(cursor)
1867
- end
1868
- elsif @history_pointer.zero?
1869
- return
1870
- else
1871
- if @is_multiline
1872
- Reline::HISTORY[@history_pointer] = whole_buffer
1873
- @history_pointer -= 1
1874
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1875
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1876
- @line_index = @buffer_of_lines.size - 1
1877
- else
1878
- Reline::HISTORY[@history_pointer] = whole_buffer
1879
- @history_pointer -= 1
1880
- @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
1881
- end
1882
- end
1883
- if @config.editing_mode_is?(:emacs, :vi_insert)
1884
- @byte_pointer = current_line.bytesize
1885
- elsif @config.editing_mode_is?(:vi_command)
1886
- @byte_pointer = 0
1887
- end
1765
+ move_history(
1766
+ (@history_pointer || Reline::HISTORY.size) - 1,
1767
+ line: :end,
1768
+ cursor: @config.editing_mode_is?(:vi_command) ? :start : :end,
1769
+ )
1888
1770
  arg -= 1
1889
1771
  ed_prev_history(key, arg: arg) if arg > 0
1890
1772
  end
1891
1773
  alias_method :previous_history, :ed_prev_history
1892
1774
 
1893
1775
  private def ed_next_history(key, arg: 1)
1894
- if @is_multiline and @line_index < (@buffer_of_lines.size - 1)
1776
+ if @line_index < (@buffer_of_lines.size - 1)
1895
1777
  cursor = current_byte_pointer_cursor
1896
1778
  @line_index += 1
1897
1779
  calculate_nearest_cursor(cursor)
1898
1780
  return
1899
1781
  end
1900
- if @history_pointer.nil?
1901
- return
1902
- elsif @history_pointer == (Reline::HISTORY.size - 1)
1903
- if @is_multiline
1904
- @history_pointer = nil
1905
- @buffer_of_lines = @line_backup_in_history.split("\n")
1906
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1907
- @line_index = 0
1908
- else
1909
- @history_pointer = nil
1910
- @buffer_of_lines = [@line_backup_in_history]
1911
- end
1912
- else
1913
- if @is_multiline
1914
- Reline::HISTORY[@history_pointer] = whole_buffer
1915
- @history_pointer += 1
1916
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1917
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1918
- @line_index = 0
1919
- else
1920
- Reline::HISTORY[@history_pointer] = whole_buffer
1921
- @history_pointer += 1
1922
- @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
1923
- end
1924
- end
1925
- if @config.editing_mode_is?(:emacs, :vi_insert)
1926
- @byte_pointer = current_line.bytesize
1927
- elsif @config.editing_mode_is?(:vi_command)
1928
- @byte_pointer = 0
1929
- end
1782
+ move_history(
1783
+ (@history_pointer || Reline::HISTORY.size) + 1,
1784
+ line: :start,
1785
+ cursor: @config.editing_mode_is?(:vi_command) ? :start : :end,
1786
+ )
1930
1787
  arg -= 1
1931
1788
  ed_next_history(key, arg: arg) if arg > 0
1932
1789
  end
@@ -1957,17 +1814,13 @@ class Reline::LineEditor
1957
1814
  end
1958
1815
  end
1959
1816
  else
1960
- if @history_pointer
1961
- Reline::HISTORY[@history_pointer] = whole_buffer
1962
- @history_pointer = nil
1963
- end
1964
1817
  finish
1965
1818
  end
1966
1819
  end
1967
1820
 
1968
1821
  private def em_delete_prev_char(key, arg: 1)
1969
1822
  arg.times do
1970
- if @is_multiline and @byte_pointer == 0 and @line_index > 0
1823
+ if @byte_pointer == 0 and @line_index > 0
1971
1824
  @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
1972
1825
  @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
1973
1826
  @line_index -= 1
@@ -1991,7 +1844,7 @@ class Reline::LineEditor
1991
1844
  line, deleted = byteslice!(current_line, @byte_pointer, current_line.bytesize - @byte_pointer)
1992
1845
  set_current_line(line, line.bytesize)
1993
1846
  @kill_ring.append(deleted)
1994
- elsif @is_multiline and @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
1847
+ elsif @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
1995
1848
  set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize)
1996
1849
  end
1997
1850
  end
@@ -2031,7 +1884,7 @@ class Reline::LineEditor
2031
1884
  alias_method :kill_whole_line, :em_kill_line
2032
1885
 
2033
1886
  private def em_delete(key)
2034
- if current_line.empty? and (not @is_multiline or @buffer_of_lines.size == 1) and key == "\C-d".ord
1887
+ if current_line.empty? and @buffer_of_lines.size == 1 and key == "\C-d".ord
2035
1888
  @eof = true
2036
1889
  finish
2037
1890
  elsif @byte_pointer < current_line.bytesize
@@ -2039,7 +1892,7 @@ class Reline::LineEditor
2039
1892
  mbchar = splitted_last.grapheme_clusters.first
2040
1893
  line, = byteslice!(current_line, @byte_pointer, mbchar.bytesize)
2041
1894
  set_current_line(line)
2042
- elsif @is_multiline and @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
1895
+ elsif @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
2043
1896
  set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize)
2044
1897
  end
2045
1898
  end
@@ -2282,7 +2135,7 @@ class Reline::LineEditor
2282
2135
  end
2283
2136
 
2284
2137
  private def vi_delete_prev_char(key)
2285
- if @is_multiline and @byte_pointer == 0 and @line_index > 0
2138
+ if @byte_pointer == 0 and @line_index > 0
2286
2139
  @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
2287
2140
  @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
2288
2141
  @line_index -= 1
@@ -2319,54 +2172,67 @@ class Reline::LineEditor
2319
2172
  copy_for_vi(deleted)
2320
2173
  end
2321
2174
 
2322
- private def vi_zero(key)
2323
- @byte_pointer = 0
2175
+ private def vi_change_meta(key, arg: nil)
2176
+ if @vi_waiting_operator
2177
+ set_current_line('', 0) if @vi_waiting_operator == :vi_change_meta_confirm && arg.nil?
2178
+ @vi_waiting_operator = nil
2179
+ @vi_waiting_operator_arg = nil
2180
+ else
2181
+ @drop_terminate_spaces = true
2182
+ @vi_waiting_operator = :vi_change_meta_confirm
2183
+ @vi_waiting_operator_arg = arg || 1
2184
+ end
2324
2185
  end
2325
2186
 
2326
- private def vi_change_meta(key, arg: 1)
2327
- @drop_terminate_spaces = true
2328
- @waiting_operator_proc = proc { |byte_pointer_diff|
2329
- if byte_pointer_diff > 0
2330
- line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff)
2331
- elsif byte_pointer_diff < 0
2332
- line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2333
- end
2334
- set_current_line(line)
2335
- copy_for_vi(cut)
2336
- @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2337
- @config.editing_mode = :vi_insert
2338
- @drop_terminate_spaces = false
2339
- }
2340
- @waiting_operator_vi_arg = arg
2187
+ private def vi_change_meta_confirm(byte_pointer_diff)
2188
+ vi_delete_meta_confirm(byte_pointer_diff)
2189
+ @config.editing_mode = :vi_insert
2190
+ @drop_terminate_spaces = false
2341
2191
  end
2342
2192
 
2343
- private def vi_delete_meta(key, arg: 1)
2344
- @waiting_operator_proc = proc { |byte_pointer_diff|
2345
- if byte_pointer_diff > 0
2346
- line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff)
2347
- elsif byte_pointer_diff < 0
2348
- line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2349
- end
2350
- copy_for_vi(cut)
2351
- set_current_line(line || '', @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0))
2352
- }
2353
- @waiting_operator_vi_arg = arg
2193
+ private def vi_delete_meta(key, arg: nil)
2194
+ if @vi_waiting_operator
2195
+ set_current_line('', 0) if @vi_waiting_operator == :vi_delete_meta_confirm && arg.nil?
2196
+ @vi_waiting_operator = nil
2197
+ @vi_waiting_operator_arg = nil
2198
+ else
2199
+ @vi_waiting_operator = :vi_delete_meta_confirm
2200
+ @vi_waiting_operator_arg = arg || 1
2201
+ end
2354
2202
  end
2355
2203
 
2356
- private def vi_yank(key, arg: 1)
2357
- @waiting_operator_proc = proc { |byte_pointer_diff|
2358
- if byte_pointer_diff > 0
2359
- cut = current_line.byteslice(@byte_pointer, byte_pointer_diff)
2360
- elsif byte_pointer_diff < 0
2361
- cut = current_line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2362
- end
2363
- copy_for_vi(cut)
2364
- }
2365
- @waiting_operator_vi_arg = arg
2204
+ private def vi_delete_meta_confirm(byte_pointer_diff)
2205
+ if byte_pointer_diff > 0
2206
+ line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff)
2207
+ elsif byte_pointer_diff < 0
2208
+ line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2209
+ end
2210
+ copy_for_vi(cut)
2211
+ set_current_line(line || '', @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0))
2212
+ end
2213
+
2214
+ private def vi_yank(key, arg: nil)
2215
+ if @vi_waiting_operator
2216
+ copy_for_vi(current_line) if @vi_waiting_operator == :vi_yank_confirm && arg.nil?
2217
+ @vi_waiting_operator = nil
2218
+ @vi_waiting_operator_arg = nil
2219
+ else
2220
+ @vi_waiting_operator = :vi_yank_confirm
2221
+ @vi_waiting_operator_arg = arg || 1
2222
+ end
2223
+ end
2224
+
2225
+ private def vi_yank_confirm(byte_pointer_diff)
2226
+ if byte_pointer_diff > 0
2227
+ cut = current_line.byteslice(@byte_pointer, byte_pointer_diff)
2228
+ elsif byte_pointer_diff < 0
2229
+ cut = current_line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2230
+ end
2231
+ copy_for_vi(cut)
2366
2232
  end
2367
2233
 
2368
2234
  private def vi_list_or_eof(key)
2369
- if (not @is_multiline and current_line.empty?) or (@is_multiline and current_line.empty? and @buffer_of_lines.size == 1)
2235
+ if current_line.empty? and @buffer_of_lines.size == 1
2370
2236
  set_current_line('', 0)
2371
2237
  @eof = true
2372
2238
  finish
@@ -2397,36 +2263,18 @@ class Reline::LineEditor
2397
2263
  if Reline::HISTORY.empty?
2398
2264
  return
2399
2265
  end
2400
- if @history_pointer.nil?
2401
- @history_pointer = 0
2402
- @line_backup_in_history = current_line
2403
- set_current_line(Reline::HISTORY[@history_pointer], 0)
2404
- elsif @history_pointer.zero?
2405
- return
2406
- else
2407
- Reline::HISTORY[@history_pointer] = current_line
2408
- @history_pointer = 0
2409
- set_current_line(Reline::HISTORY[@history_pointer], 0)
2410
- end
2266
+ move_history(0, line: :start, cursor: :start)
2411
2267
  end
2412
2268
 
2413
2269
  private def vi_histedit(key)
2414
2270
  path = Tempfile.open { |fp|
2415
- if @is_multiline
2416
- fp.write whole_lines.join("\n")
2417
- else
2418
- fp.write current_line
2419
- end
2271
+ fp.write whole_lines.join("\n")
2420
2272
  fp.path
2421
2273
  }
2422
2274
  system("#{ENV['EDITOR']} #{path}")
2423
- if @is_multiline
2424
- @buffer_of_lines = File.read(path).split("\n")
2425
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2426
- @line_index = 0
2427
- else
2428
- @buffer_of_lines = File.read(path).split("\n")
2429
- end
2275
+ @buffer_of_lines = File.read(path).split("\n")
2276
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2277
+ @line_index = 0
2430
2278
  finish
2431
2279
  end
2432
2280
 
@@ -2467,18 +2315,11 @@ class Reline::LineEditor
2467
2315
  end
2468
2316
 
2469
2317
  private def vi_to_column(key, arg: 0)
2470
- current_row_width = calculate_width(current_row)
2471
- @byte_pointer, = current_line.grapheme_clusters.inject([0, 0]) { |total, gc|
2472
- # total has [byte_size, cursor]
2318
+ # Implementing behavior of vi, not Readline's vi-mode.
2319
+ @byte_pointer, = current_line.grapheme_clusters.inject([0, 0]) { |(total_byte_size, total_width), gc|
2473
2320
  mbchar_width = Reline::Unicode.get_mbchar_width(gc)
2474
- if (total.last + mbchar_width) >= arg
2475
- break total
2476
- elsif (total.last + mbchar_width) >= current_row_width
2477
- break total
2478
- else
2479
- total = [total.first + gc.bytesize, total.last + mbchar_width]
2480
- total
2481
- end
2321
+ break [total_byte_size, total_width] if (total_width + mbchar_width) >= arg
2322
+ [total_byte_size + gc.bytesize, total_width + mbchar_width]
2482
2323
  }
2483
2324
  end
2484
2325
 
@@ -2605,7 +2446,7 @@ class Reline::LineEditor
2605
2446
  end
2606
2447
 
2607
2448
  private def vi_join_lines(key, arg: 1)
2608
- if @is_multiline and @buffer_of_lines.size > @line_index + 1
2449
+ if @buffer_of_lines.size > @line_index + 1
2609
2450
  next_line = @buffer_of_lines.delete_at(@line_index + 1).lstrip
2610
2451
  set_current_line(current_line + ' ' + next_line, current_line.bytesize)
2611
2452
  end
@@ -2626,6 +2467,11 @@ class Reline::LineEditor
2626
2467
  end
2627
2468
  alias_method :exchange_point_and_mark, :em_exchange_mark
2628
2469
 
2629
- private def em_meta_next(key)
2470
+ private def emacs_editing_mode(key)
2471
+ @config.editing_mode = :emacs
2472
+ end
2473
+
2474
+ private def vi_editing_mode(key)
2475
+ @config.editing_mode = :vi_insert
2630
2476
  end
2631
2477
  end