reline 0.5.1 → 0.5.3

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
@@ -423,7 +431,7 @@ class Reline::LineEditor
423
431
  prompt_width = calculate_width(prompt_list[@line_index], true)
424
432
  line_before_cursor = whole_lines[@line_index].byteslice(0, @byte_pointer)
425
433
  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
434
+ wrapped_cursor_y = wrapped_prompt_and_input_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1
427
435
  wrapped_cursor_x = calculate_width(wrapped_line_before_cursor.last)
428
436
  [wrapped_cursor_x, wrapped_cursor_y]
429
437
  end
@@ -487,8 +495,9 @@ class Reline::LineEditor
487
495
  wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
488
496
 
489
497
  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]]
498
+ new_lines = wrapped_prompt_and_input_lines.flatten(1)[screen_scroll_top, screen_height].map do |prompt, line|
499
+ prompt_width = Reline::Unicode.calculate_width(prompt, true)
500
+ [[0, prompt_width, prompt], [prompt_width, Reline::Unicode.calculate_width(line, true), line]]
492
501
  end
493
502
  if @menu_info
494
503
  @menu_info.lines(screen_width).each do |item|
@@ -504,7 +513,8 @@ class Reline::LineEditor
504
513
  y_range.each do |row|
505
514
  next if row < 0 || row >= screen_height
506
515
  dialog_rows = new_lines[row] ||= []
507
- dialog_rows[index + 1] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
516
+ # index 0 is for prompt, index 1 is for line, index 2.. is for dialog
517
+ dialog_rows[index + 2] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
508
518
  end
509
519
  end
510
520
 
@@ -541,10 +551,6 @@ class Reline::LineEditor
541
551
  new_lines.size - y
542
552
  end
543
553
 
544
- def current_row
545
- wrapped_lines.flatten[wrapped_cursor_y]
546
- end
547
-
548
554
  def upper_space_height(wrapped_cursor_y)
549
555
  wrapped_cursor_y - screen_scroll_top
550
556
  end
@@ -881,10 +887,12 @@ class Reline::LineEditor
881
887
  @completion_state = CompletionState::PERFECT_MATCH
882
888
  else
883
889
  @completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
890
+ complete(list, true) if @config.show_all_if_ambiguous
884
891
  end
885
892
  @perfect_matched = completed
886
893
  else
887
894
  @completion_state = CompletionState::MENU
895
+ complete(list, true) if @config.show_all_if_ambiguous
888
896
  end
889
897
  if not just_show_list and target < completed
890
898
  @buffer_of_lines[@line_index] = (preposing + completed + completion_append_character.to_s + postposing).split("\n")[@line_index] || String.new(encoding: @encoding)
@@ -935,37 +943,23 @@ class Reline::LineEditor
935
943
  end
936
944
 
937
945
  private def run_for_operators(key, method_symbol, &block)
938
- if @waiting_operator_proc
946
+ if @vi_waiting_operator
939
947
  if VI_MOTIONS.include?(method_symbol)
940
948
  old_byte_pointer = @byte_pointer
941
- @vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg&.> 1
949
+ @vi_arg = (@vi_arg || 1) * @vi_waiting_operator_arg
942
950
  block.(true)
943
951
  unless @waiting_proc
944
952
  byte_pointer_diff = @byte_pointer - old_byte_pointer
945
953
  @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
- }
954
+ send(@vi_waiting_operator, byte_pointer_diff)
955
+ cleanup_waiting
959
956
  end
960
957
  else
961
958
  # Ignores operator when not motion is given.
962
959
  block.(false)
960
+ cleanup_waiting
963
961
  end
964
- @waiting_operator_proc = nil
965
- @waiting_operator_vi_arg = nil
966
- if @vi_arg
967
- @vi_arg = nil
968
- end
962
+ @vi_arg = nil
969
963
  else
970
964
  block.(false)
971
965
  end
@@ -982,7 +976,7 @@ class Reline::LineEditor
982
976
  end
983
977
 
984
978
  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?
979
+ if @config.editing_mode_is?(:emacs, :vi_insert) and @vi_waiting_operator.nil?
986
980
  not_insertion = method_symbol != :ed_insert
987
981
  process_insert(force: not_insertion)
988
982
  end
@@ -1001,11 +995,32 @@ class Reline::LineEditor
1001
995
  end
1002
996
  end
1003
997
 
998
+ private def cleanup_waiting
999
+ @waiting_proc = nil
1000
+ @vi_waiting_operator = nil
1001
+ @vi_waiting_operator_arg = nil
1002
+ @searching_prompt = nil
1003
+ @drop_terminate_spaces = false
1004
+ end
1005
+
1004
1006
  private def process_key(key, method_symbol)
1007
+ if key.is_a?(Symbol)
1008
+ cleanup_waiting
1009
+ elsif @waiting_proc
1010
+ old_byte_pointer = @byte_pointer
1011
+ @waiting_proc.call(key)
1012
+ if @vi_waiting_operator
1013
+ byte_pointer_diff = @byte_pointer - old_byte_pointer
1014
+ @byte_pointer = old_byte_pointer
1015
+ send(@vi_waiting_operator, byte_pointer_diff)
1016
+ cleanup_waiting
1017
+ end
1018
+ @kill_ring.process
1019
+ return
1020
+ end
1021
+
1005
1022
  if method_symbol and respond_to?(method_symbol, true)
1006
1023
  method_obj = method(method_symbol)
1007
- else
1008
- method_obj = nil
1009
1024
  end
1010
1025
  if method_symbol and key.is_a?(Symbol)
1011
1026
  if @vi_arg and argumentable?(method_obj)
@@ -1027,8 +1042,6 @@ class Reline::LineEditor
1027
1042
  run_for_operators(key, method_symbol) do |with_operator|
1028
1043
  wrap_method_call(method_symbol, method_obj, key, with_operator)
1029
1044
  end
1030
- elsif @waiting_proc
1031
- @waiting_proc.(key)
1032
1045
  elsif method_obj
1033
1046
  wrap_method_call(method_symbol, method_obj, key)
1034
1047
  else
@@ -1039,9 +1052,6 @@ class Reline::LineEditor
1039
1052
  @vi_arg = nil
1040
1053
  end
1041
1054
  end
1042
- elsif @waiting_proc
1043
- @waiting_proc.(key)
1044
- @kill_ring.process
1045
1055
  elsif method_obj
1046
1056
  if method_symbol == :ed_argument_digit
1047
1057
  wrap_method_call(method_symbol, method_obj, key)
@@ -1118,42 +1128,35 @@ class Reline::LineEditor
1118
1128
  end
1119
1129
  old_lines = @buffer_of_lines.dup
1120
1130
  @first_char = false
1121
- completion_occurs = false
1131
+ @completion_occurs = false
1122
1132
  if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
1123
1133
  if !@config.disable_completion
1124
1134
  process_insert(force: true)
1125
1135
  if @config.autocompletion
1126
1136
  @completion_state = CompletionState::NORMAL
1127
- completion_occurs = move_completed_list(:down)
1137
+ @completion_occurs = move_completed_list(:down)
1128
1138
  else
1129
1139
  @completion_journey_state = nil
1130
1140
  result = call_completion_proc
1131
1141
  if result.is_a?(Array)
1132
- completion_occurs = true
1142
+ @completion_occurs = true
1133
1143
  complete(result, false)
1134
1144
  end
1135
1145
  end
1136
1146
  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
1147
  elsif @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
1144
1148
  # In vi mode, move completed list even if autocompletion is off
1145
1149
  if not @config.disable_completion
1146
1150
  process_insert(force: true)
1147
1151
  @completion_state = CompletionState::NORMAL
1148
- completion_occurs = move_completed_list("\C-p".ord == key.char ? :up : :down)
1152
+ @completion_occurs = move_completed_list("\C-p".ord == key.char ? :up : :down)
1149
1153
  end
1150
1154
  elsif Symbol === key.char and respond_to?(key.char, true)
1151
1155
  process_key(key.char, key.char)
1152
1156
  else
1153
1157
  normal_char(key)
1154
1158
  end
1155
-
1156
- unless completion_occurs
1159
+ unless @completion_occurs
1157
1160
  @completion_state = CompletionState::NORMAL
1158
1161
  @completion_journey_state = nil
1159
1162
  end
@@ -1164,7 +1167,7 @@ class Reline::LineEditor
1164
1167
  end
1165
1168
 
1166
1169
  modified = old_lines != @buffer_of_lines
1167
- if !completion_occurs && modified && !@config.disable_completion && @config.autocompletion
1170
+ if !@completion_occurs && modified && !@config.disable_completion && @config.autocompletion
1168
1171
  # Auto complete starts only when edited
1169
1172
  process_insert(force: true)
1170
1173
  @completion_journey_state = retrieve_completion_journey_state
@@ -1230,7 +1233,7 @@ class Reline::LineEditor
1230
1233
  end
1231
1234
 
1232
1235
  def line()
1233
- current_line unless eof?
1236
+ @buffer_of_lines.join("\n") unless eof?
1234
1237
  end
1235
1238
 
1236
1239
  def current_line
@@ -1314,14 +1317,12 @@ class Reline::LineEditor
1314
1317
  end
1315
1318
  target = before
1316
1319
  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
1320
+ lines = whole_lines
1321
+ if @line_index > 0
1322
+ preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
1323
+ end
1324
+ if (lines.size - 1) > @line_index
1325
+ postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
1325
1326
  end
1326
1327
  [preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
1327
1328
  end
@@ -1343,20 +1344,16 @@ class Reline::LineEditor
1343
1344
 
1344
1345
  def delete_text(start = nil, length = nil)
1345
1346
  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)
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
1360
1357
  end
1361
1358
  elsif not start.nil? and not length.nil?
1362
1359
  if current_line
@@ -1433,6 +1430,14 @@ class Reline::LineEditor
1433
1430
  end
1434
1431
  end
1435
1432
 
1433
+ private def completion_journey_up(key)
1434
+ if not @config.disable_completion and @config.autocompletion
1435
+ @completion_state = CompletionState::NORMAL
1436
+ @completion_occurs = move_completed_list(:up)
1437
+ end
1438
+ end
1439
+ alias_method :menu_complete_backward, :completion_journey_up
1440
+
1436
1441
  # Editline:: +ed-unassigned+ This editor command always results in an error.
1437
1442
  # GNU Readline:: There is no corresponding macro.
1438
1443
  private def ed_unassigned(key) end # do nothing
@@ -1504,7 +1509,7 @@ class Reline::LineEditor
1504
1509
  byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
1505
1510
  if (@byte_pointer < current_line.bytesize)
1506
1511
  @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
1512
+ elsif @config.editing_mode_is?(:emacs) and @byte_pointer == current_line.bytesize and @line_index < @buffer_of_lines.size - 1
1508
1513
  @byte_pointer = 0
1509
1514
  @line_index += 1
1510
1515
  end
@@ -1517,7 +1522,7 @@ class Reline::LineEditor
1517
1522
  if @byte_pointer > 0
1518
1523
  byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
1519
1524
  @byte_pointer -= byte_size
1520
- elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0
1525
+ elsif @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0
1521
1526
  @line_index -= 1
1522
1527
  @byte_pointer = current_line.bytesize
1523
1528
  end
@@ -1534,6 +1539,7 @@ class Reline::LineEditor
1534
1539
  @byte_pointer = 0
1535
1540
  end
1536
1541
  alias_method :beginning_of_line, :ed_move_to_beg
1542
+ alias_method :vi_zero, :ed_move_to_beg
1537
1543
 
1538
1544
  private def ed_move_to_end(key)
1539
1545
  @byte_pointer = 0
@@ -1544,131 +1550,95 @@ class Reline::LineEditor
1544
1550
  end
1545
1551
  alias_method :end_of_line, :ed_move_to_end
1546
1552
 
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'
1553
+ private def generate_searcher(search_key)
1554
+ search_word = String.new(encoding: @encoding)
1555
+ multibyte_buf = String.new(encoding: 'ASCII-8BIT')
1556
+ hit_pointer = nil
1557
+ lambda do |key|
1558
+ search_again = false
1559
+ case key
1560
+ when "\C-h".ord, "\C-?".ord
1561
+ grapheme_clusters = search_word.grapheme_clusters
1562
+ if grapheme_clusters.size > 0
1563
+ grapheme_clusters.pop
1564
+ search_word = grapheme_clusters.join
1565
+ end
1566
+ when "\C-r".ord, "\C-s".ord
1567
+ search_again = true if search_key == key
1568
+ search_key = key
1569
+ else
1570
+ multibyte_buf << key
1571
+ if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
1572
+ search_word << multibyte_buf.dup.force_encoding(@encoding)
1573
+ multibyte_buf.clear
1574
+ end
1558
1575
  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
1576
+ hit = nil
1577
+ if not search_word.empty? and @line_backup_in_history&.include?(search_word)
1578
+ hit_pointer = Reline::HISTORY.size
1579
+ hit = @line_backup_in_history
1580
+ else
1581
+ if search_again
1582
+ if search_word.empty? and Reline.last_incremental_search
1583
+ search_word = Reline.last_incremental_search
1580
1584
  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
1585
+ if @history_pointer
1586
+ case search_key
1606
1587
  when "\C-r".ord
1607
1588
  history_pointer_base = 0
1608
- history = Reline::HISTORY[0..@history_pointer]
1589
+ history = Reline::HISTORY[0..(@history_pointer - 1)]
1609
1590
  when "\C-s".ord
1610
- history_pointer_base = @history_pointer
1611
- history = Reline::HISTORY[@history_pointer..-1]
1591
+ history_pointer_base = @history_pointer + 1
1592
+ history = Reline::HISTORY[(@history_pointer + 1)..-1]
1612
1593
  end
1613
1594
  else
1614
1595
  history_pointer_base = 0
1615
1596
  history = Reline::HISTORY
1616
1597
  end
1617
- case prev_search_key
1598
+ elsif @history_pointer
1599
+ case search_key
1618
1600
  when "\C-r".ord
1619
- hit_index = history.rindex { |item|
1620
- item.include?(search_word)
1621
- }
1601
+ history_pointer_base = 0
1602
+ history = Reline::HISTORY[0..@history_pointer]
1622
1603
  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]
1604
+ history_pointer_base = @history_pointer
1605
+ history = Reline::HISTORY[@history_pointer..-1]
1630
1606
  end
1607
+ else
1608
+ history_pointer_base = 0
1609
+ history = Reline::HISTORY
1631
1610
  end
1632
- case prev_search_key
1611
+ case search_key
1633
1612
  when "\C-r".ord
1634
- prompt_name = 'reverse-i-search'
1613
+ hit_index = history.rindex { |item|
1614
+ item.include?(search_word)
1615
+ }
1635
1616
  when "\C-s".ord
1636
- prompt_name = 'i-search'
1617
+ hit_index = history.index { |item|
1618
+ item.include?(search_word)
1619
+ }
1637
1620
  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
1621
+ if hit_index
1622
+ hit_pointer = history_pointer_base + hit_index
1623
+ hit = Reline::HISTORY[hit_pointer]
1657
1624
  end
1658
1625
  end
1626
+ case search_key
1627
+ when "\C-r".ord
1628
+ prompt_name = 'reverse-i-search'
1629
+ when "\C-s".ord
1630
+ prompt_name = 'i-search'
1631
+ end
1632
+ prompt_name = "failed #{prompt_name}" unless hit
1633
+ [search_word, prompt_name, hit_pointer]
1659
1634
  end
1660
1635
  end
1661
1636
 
1662
1637
  private def incremental_search_history(key)
1663
1638
  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
1639
+ @line_backup_in_history = whole_buffer
1669
1640
  end
1670
- searcher = generate_searcher
1671
- searcher.resume(key)
1641
+ searcher = generate_searcher(key)
1672
1642
  @searching_prompt = "(reverse-i-search)`': "
1673
1643
  termination_keys = ["\C-j".ord]
1674
1644
  termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators
@@ -1680,53 +1650,41 @@ class Reline::LineEditor
1680
1650
  else
1681
1651
  buffer = @line_backup_in_history
1682
1652
  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
1653
+ @buffer_of_lines = buffer.split("\n")
1654
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1655
+ @line_index = @buffer_of_lines.size - 1
1690
1656
  @searching_prompt = nil
1691
1657
  @waiting_proc = nil
1692
1658
  @byte_pointer = 0
1693
- searcher.resume(-1)
1694
1659
  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
1660
+ @buffer_of_lines = @line_backup_in_history.split("\n")
1661
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1662
+ @line_index = @buffer_of_lines.size - 1
1663
+ move_history(nil, line: :end, cursor: :end, save_buffer: false)
1703
1664
  @searching_prompt = nil
1704
1665
  @waiting_proc = nil
1705
- @line_backup_in_history = nil
1706
1666
  @byte_pointer = 0
1707
1667
  else
1708
1668
  chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
1709
1669
  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)
1670
+ search_word, prompt_name, hit_pointer = searcher.call(k)
1671
+ Reline.last_incremental_search = search_word
1672
+ @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
1673
+ @searching_prompt += ': ' unless @is_multiline
1674
+ move_history(hit_pointer, line: :end, cursor: :end, save_buffer: false) if hit_pointer
1711
1675
  else
1712
1676
  if @history_pointer
1713
1677
  line = Reline::HISTORY[@history_pointer]
1714
1678
  else
1715
1679
  line = @line_backup_in_history
1716
1680
  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
1681
+ @line_backup_in_history = whole_buffer
1682
+ @buffer_of_lines = line.split("\n")
1683
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1684
+ @line_index = @buffer_of_lines.size - 1
1726
1685
  @searching_prompt = nil
1727
1686
  @waiting_proc = nil
1728
1687
  @byte_pointer = 0
1729
- searcher.resume(-1)
1730
1688
  end
1731
1689
  end
1732
1690
  }
@@ -1742,191 +1700,95 @@ class Reline::LineEditor
1742
1700
  end
1743
1701
  alias_method :forward_search_history, :vi_search_next
1744
1702
 
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)
1703
+ private def search_history(prefix, pointer_range)
1704
+ pointer_range.each do |pointer|
1705
+ lines = Reline::HISTORY[pointer].split("\n")
1706
+ lines.each_with_index do |line, index|
1707
+ return [pointer, index] if line.start_with?(prefix)
1708
+ end
1786
1709
  end
1710
+ nil
1711
+ end
1712
+
1713
+ private def ed_search_prev_history(key, arg: 1)
1714
+ substr = current_line.byteslice(0, @byte_pointer)
1715
+ return if @history_pointer == 0
1716
+ return if @history_pointer.nil? && substr.empty? && !current_line.empty?
1717
+
1718
+ history_range = 0...(@history_pointer || Reline::HISTORY.size)
1719
+ h_pointer, line_index = search_history(substr, history_range.reverse_each)
1720
+ return unless h_pointer
1721
+ move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer)
1787
1722
  arg -= 1
1788
1723
  ed_search_prev_history(key, arg: arg) if arg > 0
1789
1724
  end
1790
1725
  alias_method :history_search_backward, :ed_search_prev_history
1791
1726
 
1792
1727
  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
1728
+ substr = current_line.byteslice(0, @byte_pointer)
1729
+ return if @history_pointer.nil?
1730
+
1731
+ history_range = @history_pointer + 1...Reline::HISTORY.size
1732
+ h_pointer, line_index = search_history(substr, history_range)
1818
1733
  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
1734
+
1735
+ move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer)
1839
1736
  arg -= 1
1840
1737
  ed_search_next_history(key, arg: arg) if arg > 0
1841
1738
  end
1842
1739
  alias_method :history_search_forward, :ed_search_next_history
1843
1740
 
1741
+ private def move_history(history_pointer, line:, cursor:, save_buffer: true)
1742
+ history_pointer ||= Reline::HISTORY.size
1743
+ return if history_pointer < 0 || history_pointer > Reline::HISTORY.size
1744
+ old_history_pointer = @history_pointer || Reline::HISTORY.size
1745
+ if old_history_pointer == Reline::HISTORY.size
1746
+ @line_backup_in_history = save_buffer ? whole_buffer : ''
1747
+ else
1748
+ Reline::HISTORY[old_history_pointer] = whole_buffer if save_buffer
1749
+ end
1750
+ if history_pointer == Reline::HISTORY.size
1751
+ buf = @line_backup_in_history
1752
+ @history_pointer = @line_backup_in_history = nil
1753
+ else
1754
+ buf = Reline::HISTORY[history_pointer]
1755
+ @history_pointer = history_pointer
1756
+ end
1757
+ @buffer_of_lines = buf.split("\n")
1758
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1759
+ @line_index = line == :start ? 0 : line == :end ? @buffer_of_lines.size - 1 : line
1760
+ @byte_pointer = cursor == :start ? 0 : cursor == :end ? current_line.bytesize : cursor
1761
+ end
1762
+
1844
1763
  private def ed_prev_history(key, arg: 1)
1845
- if @is_multiline and @line_index > 0
1764
+ if @line_index > 0
1846
1765
  cursor = current_byte_pointer_cursor
1847
1766
  @line_index -= 1
1848
1767
  calculate_nearest_cursor(cursor)
1849
1768
  return
1850
1769
  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
1770
+ move_history(
1771
+ (@history_pointer || Reline::HISTORY.size) - 1,
1772
+ line: :end,
1773
+ cursor: @config.editing_mode_is?(:vi_command) ? :start : :end,
1774
+ )
1888
1775
  arg -= 1
1889
1776
  ed_prev_history(key, arg: arg) if arg > 0
1890
1777
  end
1891
1778
  alias_method :previous_history, :ed_prev_history
1892
1779
 
1893
1780
  private def ed_next_history(key, arg: 1)
1894
- if @is_multiline and @line_index < (@buffer_of_lines.size - 1)
1781
+ if @line_index < (@buffer_of_lines.size - 1)
1895
1782
  cursor = current_byte_pointer_cursor
1896
1783
  @line_index += 1
1897
1784
  calculate_nearest_cursor(cursor)
1898
1785
  return
1899
1786
  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
1787
+ move_history(
1788
+ (@history_pointer || Reline::HISTORY.size) + 1,
1789
+ line: :start,
1790
+ cursor: @config.editing_mode_is?(:vi_command) ? :start : :end,
1791
+ )
1930
1792
  arg -= 1
1931
1793
  ed_next_history(key, arg: arg) if arg > 0
1932
1794
  end
@@ -1957,17 +1819,13 @@ class Reline::LineEditor
1957
1819
  end
1958
1820
  end
1959
1821
  else
1960
- if @history_pointer
1961
- Reline::HISTORY[@history_pointer] = whole_buffer
1962
- @history_pointer = nil
1963
- end
1964
1822
  finish
1965
1823
  end
1966
1824
  end
1967
1825
 
1968
1826
  private def em_delete_prev_char(key, arg: 1)
1969
1827
  arg.times do
1970
- if @is_multiline and @byte_pointer == 0 and @line_index > 0
1828
+ if @byte_pointer == 0 and @line_index > 0
1971
1829
  @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
1972
1830
  @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
1973
1831
  @line_index -= 1
@@ -1991,7 +1849,7 @@ class Reline::LineEditor
1991
1849
  line, deleted = byteslice!(current_line, @byte_pointer, current_line.bytesize - @byte_pointer)
1992
1850
  set_current_line(line, line.bytesize)
1993
1851
  @kill_ring.append(deleted)
1994
- elsif @is_multiline and @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
1852
+ elsif @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
1995
1853
  set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize)
1996
1854
  end
1997
1855
  end
@@ -2031,7 +1889,7 @@ class Reline::LineEditor
2031
1889
  alias_method :kill_whole_line, :em_kill_line
2032
1890
 
2033
1891
  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
1892
+ if current_line.empty? and @buffer_of_lines.size == 1 and key == "\C-d".ord
2035
1893
  @eof = true
2036
1894
  finish
2037
1895
  elsif @byte_pointer < current_line.bytesize
@@ -2039,7 +1897,7 @@ class Reline::LineEditor
2039
1897
  mbchar = splitted_last.grapheme_clusters.first
2040
1898
  line, = byteslice!(current_line, @byte_pointer, mbchar.bytesize)
2041
1899
  set_current_line(line)
2042
- elsif @is_multiline and @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
1900
+ elsif @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
2043
1901
  set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize)
2044
1902
  end
2045
1903
  end
@@ -2282,7 +2140,7 @@ class Reline::LineEditor
2282
2140
  end
2283
2141
 
2284
2142
  private def vi_delete_prev_char(key)
2285
- if @is_multiline and @byte_pointer == 0 and @line_index > 0
2143
+ if @byte_pointer == 0 and @line_index > 0
2286
2144
  @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
2287
2145
  @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
2288
2146
  @line_index -= 1
@@ -2319,54 +2177,67 @@ class Reline::LineEditor
2319
2177
  copy_for_vi(deleted)
2320
2178
  end
2321
2179
 
2322
- private def vi_zero(key)
2323
- @byte_pointer = 0
2180
+ private def vi_change_meta(key, arg: nil)
2181
+ if @vi_waiting_operator
2182
+ set_current_line('', 0) if @vi_waiting_operator == :vi_change_meta_confirm && arg.nil?
2183
+ @vi_waiting_operator = nil
2184
+ @vi_waiting_operator_arg = nil
2185
+ else
2186
+ @drop_terminate_spaces = true
2187
+ @vi_waiting_operator = :vi_change_meta_confirm
2188
+ @vi_waiting_operator_arg = arg || 1
2189
+ end
2324
2190
  end
2325
2191
 
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
2192
+ private def vi_change_meta_confirm(byte_pointer_diff)
2193
+ vi_delete_meta_confirm(byte_pointer_diff)
2194
+ @config.editing_mode = :vi_insert
2195
+ @drop_terminate_spaces = false
2341
2196
  end
2342
2197
 
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
2198
+ private def vi_delete_meta(key, arg: nil)
2199
+ if @vi_waiting_operator
2200
+ set_current_line('', 0) if @vi_waiting_operator == :vi_delete_meta_confirm && arg.nil?
2201
+ @vi_waiting_operator = nil
2202
+ @vi_waiting_operator_arg = nil
2203
+ else
2204
+ @vi_waiting_operator = :vi_delete_meta_confirm
2205
+ @vi_waiting_operator_arg = arg || 1
2206
+ end
2354
2207
  end
2355
2208
 
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
2209
+ private def vi_delete_meta_confirm(byte_pointer_diff)
2210
+ if byte_pointer_diff > 0
2211
+ line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff)
2212
+ elsif byte_pointer_diff < 0
2213
+ line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2214
+ end
2215
+ copy_for_vi(cut)
2216
+ set_current_line(line || '', @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0))
2217
+ end
2218
+
2219
+ private def vi_yank(key, arg: nil)
2220
+ if @vi_waiting_operator
2221
+ copy_for_vi(current_line) if @vi_waiting_operator == :vi_yank_confirm && arg.nil?
2222
+ @vi_waiting_operator = nil
2223
+ @vi_waiting_operator_arg = nil
2224
+ else
2225
+ @vi_waiting_operator = :vi_yank_confirm
2226
+ @vi_waiting_operator_arg = arg || 1
2227
+ end
2228
+ end
2229
+
2230
+ private def vi_yank_confirm(byte_pointer_diff)
2231
+ if byte_pointer_diff > 0
2232
+ cut = current_line.byteslice(@byte_pointer, byte_pointer_diff)
2233
+ elsif byte_pointer_diff < 0
2234
+ cut = current_line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2235
+ end
2236
+ copy_for_vi(cut)
2366
2237
  end
2367
2238
 
2368
2239
  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)
2240
+ if current_line.empty? and @buffer_of_lines.size == 1
2370
2241
  set_current_line('', 0)
2371
2242
  @eof = true
2372
2243
  finish
@@ -2397,36 +2268,18 @@ class Reline::LineEditor
2397
2268
  if Reline::HISTORY.empty?
2398
2269
  return
2399
2270
  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
2271
+ move_history(0, line: :start, cursor: :start)
2411
2272
  end
2412
2273
 
2413
2274
  private def vi_histedit(key)
2414
2275
  path = Tempfile.open { |fp|
2415
- if @is_multiline
2416
- fp.write whole_lines.join("\n")
2417
- else
2418
- fp.write current_line
2419
- end
2276
+ fp.write whole_lines.join("\n")
2420
2277
  fp.path
2421
2278
  }
2422
2279
  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
2280
+ @buffer_of_lines = File.read(path).split("\n")
2281
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2282
+ @line_index = 0
2430
2283
  finish
2431
2284
  end
2432
2285
 
@@ -2467,18 +2320,11 @@ class Reline::LineEditor
2467
2320
  end
2468
2321
 
2469
2322
  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]
2323
+ # Implementing behavior of vi, not Readline's vi-mode.
2324
+ @byte_pointer, = current_line.grapheme_clusters.inject([0, 0]) { |(total_byte_size, total_width), gc|
2473
2325
  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
2326
+ break [total_byte_size, total_width] if (total_width + mbchar_width) >= arg
2327
+ [total_byte_size + gc.bytesize, total_width + mbchar_width]
2482
2328
  }
2483
2329
  end
2484
2330
 
@@ -2605,7 +2451,7 @@ class Reline::LineEditor
2605
2451
  end
2606
2452
 
2607
2453
  private def vi_join_lines(key, arg: 1)
2608
- if @is_multiline and @buffer_of_lines.size > @line_index + 1
2454
+ if @buffer_of_lines.size > @line_index + 1
2609
2455
  next_line = @buffer_of_lines.delete_at(@line_index + 1).lstrip
2610
2456
  set_current_line(current_line + ' ' + next_line, current_line.bytesize)
2611
2457
  end
@@ -2626,6 +2472,11 @@ class Reline::LineEditor
2626
2472
  end
2627
2473
  alias_method :exchange_point_and_mark, :em_exchange_mark
2628
2474
 
2629
- private def em_meta_next(key)
2475
+ private def emacs_editing_mode(key)
2476
+ @config.editing_mode = :emacs
2477
+ end
2478
+
2479
+ private def vi_editing_mode(key)
2480
+ @config.editing_mode = :vi_insert
2630
2481
  end
2631
2482
  end