reline 0.5.1 → 0.5.3

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