reline 0.5.0 → 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,23 +33,42 @@ 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
41
39
  NORMAL = :normal
42
40
  COMPLETION = :completion
43
41
  MENU = :menu
44
- JOURNEY = :journey
45
42
  MENU_WITH_PERFECT_MATCH = :menu_with_perfect_match
46
43
  PERFECT_MATCH = :perfect_match
47
44
  end
48
45
 
49
46
  RenderedScreen = Struct.new(:base_y, :lines, :cursor_y, keyword_init: true)
50
47
 
51
- CompletionJourneyData = Struct.new(:preposing, :postposing, :list, :pointer)
52
- MenuInfo = Struct.new(:target, :list)
48
+ CompletionJourneyState = Struct.new(:line_index, :pre, :target, :post, :list, :pointer)
49
+
50
+ class MenuInfo
51
+ attr_reader :list
52
+
53
+ def initialize(list)
54
+ @list = list
55
+ end
56
+
57
+ def lines(screen_width)
58
+ return [] if @list.empty?
59
+
60
+ list = @list.sort
61
+ sizes = list.map { |item| Reline::Unicode.calculate_width(item) }
62
+ item_width = sizes.max + 2
63
+ num_cols = [screen_width / item_width, 1].max
64
+ num_rows = list.size.fdiv(num_cols).ceil
65
+ list_with_padding = list.zip(sizes).map { |item, size| item + ' ' * (item_width - size) }
66
+ aligned = (list_with_padding + [nil] * (num_rows * num_cols - list_with_padding.size)).each_slice(num_rows).to_a.transpose
67
+ aligned.map do |row|
68
+ row.join.rstrip
69
+ end
70
+ end
71
+ end
53
72
 
54
73
  MINIMUM_SCROLLBAR_HEIGHT = 1
55
74
 
@@ -93,7 +112,11 @@ class Reline::LineEditor
93
112
  else
94
113
  prompt = @prompt
95
114
  end
96
- 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
97
120
  prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") }
98
121
  prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
99
122
  prompt_list = [prompt] if prompt_list.empty?
@@ -117,9 +140,6 @@ class Reline::LineEditor
117
140
  @screen_size = Reline::IOGate.get_screen_size
118
141
  reset_variables(prompt, encoding: encoding)
119
142
  @rendered_screen.base_y = Reline::IOGate.cursor_pos.y
120
- Reline::IOGate.set_winch_handler do
121
- @resized = true
122
- end
123
143
  if ENV.key?('RELINE_ALT_SCROLLBAR')
124
144
  @full_block = '::'
125
145
  @upper_half_block = "''"
@@ -143,7 +163,12 @@ class Reline::LineEditor
143
163
  end
144
164
  end
145
165
 
146
- def resize
166
+ def handle_signal
167
+ handle_interrupted
168
+ handle_resized
169
+ end
170
+
171
+ private def handle_resized
147
172
  return unless @resized
148
173
 
149
174
  @screen_size = Reline::IOGate.get_screen_size
@@ -156,25 +181,35 @@ class Reline::LineEditor
156
181
  render_differential
157
182
  end
158
183
 
184
+ private def handle_interrupted
185
+ return unless @interrupted
186
+
187
+ @interrupted = false
188
+ clear_dialogs
189
+ scrolldown = render_differential
190
+ Reline::IOGate.scroll_down scrolldown
191
+ Reline::IOGate.move_cursor_column 0
192
+ @rendered_screen.lines = []
193
+ @rendered_screen.cursor_y = 0
194
+ case @old_trap
195
+ when 'DEFAULT', 'SYSTEM_DEFAULT'
196
+ raise Interrupt
197
+ when 'IGNORE'
198
+ # Do nothing
199
+ when 'EXIT'
200
+ exit
201
+ else
202
+ @old_trap.call if @old_trap.respond_to?(:call)
203
+ end
204
+ end
205
+
159
206
  def set_signal_handlers
160
- @old_trap = Signal.trap('INT') {
161
- clear_dialogs
162
- scrolldown = render_differential
163
- Reline::IOGate.scroll_down scrolldown
164
- Reline::IOGate.move_cursor_column 0
165
- @rendered_screen.lines = []
166
- @rendered_screen.cursor_y = 0
167
- case @old_trap
168
- when 'DEFAULT', 'SYSTEM_DEFAULT'
169
- raise Interrupt
170
- when 'IGNORE'
171
- # Do nothing
172
- when 'EXIT'
173
- exit
174
- else
175
- @old_trap.call if @old_trap.respond_to?(:call)
176
- end
177
- }
207
+ Reline::IOGate.set_winch_handler do
208
+ @resized = true
209
+ end
210
+ @old_trap = Signal.trap('INT') do
211
+ @interrupted = true
212
+ end
178
213
  end
179
214
 
180
215
  def finalize
@@ -191,16 +226,16 @@ class Reline::LineEditor
191
226
  @encoding = encoding
192
227
  @is_multiline = false
193
228
  @finished = false
194
- @cleared = false
195
229
  @history_pointer = nil
196
230
  @kill_ring ||= Reline::KillRing.new
197
231
  @vi_clipboard = ''
198
232
  @vi_arg = nil
199
233
  @waiting_proc = nil
200
- @waiting_operator_proc = nil
201
- @waiting_operator_vi_arg = nil
202
- @completion_journey_data = nil
234
+ @vi_waiting_operator = nil
235
+ @vi_waiting_operator_arg = nil
236
+ @completion_journey_state = nil
203
237
  @completion_state = CompletionState::NORMAL
238
+ @completion_occurs = false
204
239
  @perfect_matched = nil
205
240
  @menu_info = nil
206
241
  @searching_prompt = nil
@@ -213,6 +248,7 @@ class Reline::LineEditor
213
248
  @in_pasting = false
214
249
  @auto_indent_proc = nil
215
250
  @dialogs = []
251
+ @interrupted = false
216
252
  @resized = false
217
253
  @cache = {}
218
254
  @rendered_screen = RenderedScreen.new(base_y: 0, lines: [], cursor_y: 0)
@@ -259,8 +295,8 @@ class Reline::LineEditor
259
295
  end
260
296
  end
261
297
 
262
- private def split_by_width(str, max_width)
263
- 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)
264
300
  end
265
301
 
266
302
  def current_byte_pointer_cursor
@@ -334,7 +370,7 @@ class Reline::LineEditor
334
370
  @scroll_partial_screen
335
371
  end
336
372
 
337
- def wrapped_lines
373
+ def wrapped_prompt_and_input_lines
338
374
  with_cache(__method__, @buffer_of_lines.size, modified_lines, prompt_list, screen_width) do |n, lines, prompts, width, prev_cache_key, cached_value|
339
375
  prev_n, prev_lines, prev_prompts, prev_width = prev_cache_key
340
376
  cached_wraps = {}
@@ -345,9 +381,14 @@ class Reline::LineEditor
345
381
  end
346
382
 
347
383
  n.times.map do |i|
348
- prompt = prompts[i]
349
- line = lines[i]
350
- 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] }
351
392
  end
352
393
  end
353
394
  end
@@ -390,7 +431,7 @@ class Reline::LineEditor
390
431
  prompt_width = calculate_width(prompt_list[@line_index], true)
391
432
  line_before_cursor = whole_lines[@line_index].byteslice(0, @byte_pointer)
392
433
  wrapped_line_before_cursor = split_by_width(' ' * prompt_width + line_before_cursor, screen_width).first.compact
393
- 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
394
435
  wrapped_cursor_x = calculate_width(wrapped_line_before_cursor.last)
395
436
  [wrapped_cursor_x, wrapped_cursor_y]
396
437
  end
@@ -454,11 +495,12 @@ class Reline::LineEditor
454
495
  wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
455
496
 
456
497
  rendered_lines = @rendered_screen.lines
457
- new_lines = wrapped_lines.flatten[screen_scroll_top, screen_height].map do |l|
458
- [[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]]
459
501
  end
460
502
  if @menu_info
461
- @menu_info.list.sort!.each do |item|
503
+ @menu_info.lines(screen_width).each do |item|
462
504
  new_lines << [[0, Reline::Unicode.calculate_width(item), item]]
463
505
  end
464
506
  @menu_info = nil # TODO: do not change state here
@@ -471,7 +513,8 @@ class Reline::LineEditor
471
513
  y_range.each do |row|
472
514
  next if row < 0 || row >= screen_height
473
515
  dialog_rows = new_lines[row] ||= []
474
- 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]]
475
518
  end
476
519
  end
477
520
 
@@ -508,10 +551,6 @@ class Reline::LineEditor
508
551
  new_lines.size - y
509
552
  end
510
553
 
511
- def current_row
512
- wrapped_lines.flatten[wrapped_cursor_y]
513
- end
514
-
515
554
  def upper_space_height(wrapped_cursor_y)
516
555
  wrapped_cursor_y - screen_scroll_top
517
556
  end
@@ -520,23 +559,13 @@ class Reline::LineEditor
520
559
  screen_height - wrapped_cursor_y + screen_scroll_top - @rendered_screen.base_y - 1
521
560
  end
522
561
 
523
- def handle_cleared
524
- return unless @cleared
525
-
526
- @cleared = false
527
- Reline::IOGate.clear_screen
528
- @screen_size = Reline::IOGate.get_screen_size
529
- @rendered_screen.lines = []
530
- @rendered_screen.base_y = 0
531
- @rendered_screen.cursor_y = 0
532
- end
533
-
534
562
  def rerender
535
- handle_cleared
536
563
  render_differential unless @in_pasting
537
564
  end
538
565
 
539
566
  class DialogProcScope
567
+ CompletionJourneyData = Struct.new(:preposing, :postposing, :list, :pointer)
568
+
540
569
  def initialize(line_editor, config, proc_to_exec, context)
541
570
  @line_editor = line_editor
542
571
  @config = config
@@ -600,7 +629,7 @@ class Reline::LineEditor
600
629
  end
601
630
 
602
631
  def completion_journey_data
603
- @line_editor.instance_variable_get(:@completion_journey_data)
632
+ @line_editor.dialog_proc_scope_completion_journey_data
604
633
  end
605
634
 
606
635
  def config
@@ -771,7 +800,7 @@ class Reline::LineEditor
771
800
  if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: complete)
772
801
  after.lines("\n").map { |l| l.chomp('') }
773
802
  else
774
- before
803
+ before.map { |l| Reline::Unicode.escape_for_print(l) }
775
804
  end
776
805
  end
777
806
 
@@ -779,8 +808,8 @@ class Reline::LineEditor
779
808
  @config.editing_mode
780
809
  end
781
810
 
782
- private def menu(target, list)
783
- @menu_info = MenuInfo.new(target, list)
811
+ private def menu(_target, list)
812
+ @menu_info = MenuInfo.new(list)
784
813
  end
785
814
 
786
815
  private def complete_internal_proc(list, is_menu)
@@ -829,9 +858,9 @@ class Reline::LineEditor
829
858
  [target, preposing, completed, postposing]
830
859
  end
831
860
 
832
- private def complete(list, just_show_list = false)
861
+ private def complete(list, just_show_list)
833
862
  case @completion_state
834
- when CompletionState::NORMAL, CompletionState::JOURNEY
863
+ when CompletionState::NORMAL
835
864
  @completion_state = CompletionState::COMPLETION
836
865
  when CompletionState::PERFECT_MATCH
837
866
  @dig_perfect_match_proc&.(@perfect_matched)
@@ -858,10 +887,12 @@ class Reline::LineEditor
858
887
  @completion_state = CompletionState::PERFECT_MATCH
859
888
  else
860
889
  @completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
890
+ complete(list, true) if @config.show_all_if_ambiguous
861
891
  end
862
892
  @perfect_matched = completed
863
893
  else
864
894
  @completion_state = CompletionState::MENU
895
+ complete(list, true) if @config.show_all_if_ambiguous
865
896
  end
866
897
  if not just_show_list and target < completed
867
898
  @buffer_of_lines[@line_index] = (preposing + completed + completion_append_character.to_s + postposing).split("\n")[@line_index] || String.new(encoding: @encoding)
@@ -871,80 +902,64 @@ class Reline::LineEditor
871
902
  end
872
903
  end
873
904
 
874
- private def move_completed_list(list, direction)
875
- case @completion_state
876
- when CompletionState::NORMAL, CompletionState::COMPLETION,
877
- CompletionState::MENU, CompletionState::MENU_WITH_PERFECT_MATCH
878
- @completion_state = CompletionState::JOURNEY
879
- result = retrieve_completion_block
880
- return if result.nil?
881
- preposing, target, postposing = result
882
- @completion_journey_data = CompletionJourneyData.new(
883
- preposing, postposing,
884
- [target] + list.select{ |item| item.start_with?(target) }, 0)
885
- if @completion_journey_data.list.size == 1
886
- @completion_journey_data.pointer = 0
887
- else
888
- case direction
889
- when :up
890
- @completion_journey_data.pointer = @completion_journey_data.list.size - 1
891
- when :down
892
- @completion_journey_data.pointer = 1
893
- end
894
- end
895
- @completion_state = CompletionState::JOURNEY
896
- else
897
- case direction
898
- when :up
899
- @completion_journey_data.pointer -= 1
900
- if @completion_journey_data.pointer < 0
901
- @completion_journey_data.pointer = @completion_journey_data.list.size - 1
902
- end
903
- when :down
904
- @completion_journey_data.pointer += 1
905
- if @completion_journey_data.pointer >= @completion_journey_data.list.size
906
- @completion_journey_data.pointer = 0
907
- end
908
- end
905
+ def dialog_proc_scope_completion_journey_data
906
+ return nil unless @completion_journey_state
907
+ line_index = @completion_journey_state.line_index
908
+ pre_lines = @buffer_of_lines[0...line_index].map { |line| line + "\n" }
909
+ post_lines = @buffer_of_lines[(line_index + 1)..-1].map { |line| line + "\n" }
910
+ DialogProcScope::CompletionJourneyData.new(
911
+ pre_lines.join + @completion_journey_state.pre,
912
+ @completion_journey_state.post + post_lines.join,
913
+ @completion_journey_state.list,
914
+ @completion_journey_state.pointer
915
+ )
916
+ end
917
+
918
+ private def move_completed_list(direction)
919
+ @completion_journey_state ||= retrieve_completion_journey_state
920
+ return false unless @completion_journey_state
921
+
922
+ if (delta = { up: -1, down: +1 }[direction])
923
+ @completion_journey_state.pointer = (@completion_journey_state.pointer + delta) % @completion_journey_state.list.size
909
924
  end
910
- completed = @completion_journey_data.list[@completion_journey_data.pointer]
911
- line_to_pointer = (@completion_journey_data.preposing + completed).split("\n")[@line_index] || String.new(encoding: @encoding)
912
- new_line = line_to_pointer + (@completion_journey_data.postposing.split("\n").first || '')
913
- set_current_line(new_line, line_to_pointer.bytesize)
925
+ completed = @completion_journey_state.list[@completion_journey_state.pointer]
926
+ set_current_line(@completion_journey_state.pre + completed + @completion_journey_state.post, @completion_journey_state.pre.bytesize + completed.bytesize)
927
+ true
928
+ end
929
+
930
+ private def retrieve_completion_journey_state
931
+ preposing, target, postposing = retrieve_completion_block
932
+ list = call_completion_proc
933
+ return unless list.is_a?(Array)
934
+
935
+ candidates = list.select{ |item| item.start_with?(target) }
936
+ return if candidates.empty?
937
+
938
+ pre = preposing.split("\n", -1).last || ''
939
+ post = postposing.split("\n", -1).first || ''
940
+ CompletionJourneyState.new(
941
+ @line_index, pre, target, post, [target] + candidates, 0
942
+ )
914
943
  end
915
944
 
916
945
  private def run_for_operators(key, method_symbol, &block)
917
- if @waiting_operator_proc
946
+ if @vi_waiting_operator
918
947
  if VI_MOTIONS.include?(method_symbol)
919
948
  old_byte_pointer = @byte_pointer
920
- @vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg&.> 1
949
+ @vi_arg = (@vi_arg || 1) * @vi_waiting_operator_arg
921
950
  block.(true)
922
951
  unless @waiting_proc
923
952
  byte_pointer_diff = @byte_pointer - old_byte_pointer
924
953
  @byte_pointer = old_byte_pointer
925
- @waiting_operator_proc.(byte_pointer_diff)
926
- else
927
- old_waiting_proc = @waiting_proc
928
- old_waiting_operator_proc = @waiting_operator_proc
929
- current_waiting_operator_proc = @waiting_operator_proc
930
- @waiting_proc = proc { |k|
931
- old_byte_pointer = @byte_pointer
932
- old_waiting_proc.(k)
933
- byte_pointer_diff = @byte_pointer - old_byte_pointer
934
- @byte_pointer = old_byte_pointer
935
- current_waiting_operator_proc.(byte_pointer_diff)
936
- @waiting_operator_proc = old_waiting_operator_proc
937
- }
954
+ send(@vi_waiting_operator, byte_pointer_diff)
955
+ cleanup_waiting
938
956
  end
939
957
  else
940
958
  # Ignores operator when not motion is given.
941
959
  block.(false)
960
+ cleanup_waiting
942
961
  end
943
- @waiting_operator_proc = nil
944
- @waiting_operator_vi_arg = nil
945
- if @vi_arg
946
- @vi_arg = nil
947
- end
962
+ @vi_arg = nil
948
963
  else
949
964
  block.(false)
950
965
  end
@@ -961,7 +976,7 @@ class Reline::LineEditor
961
976
  end
962
977
 
963
978
  def wrap_method_call(method_symbol, method_obj, key, with_operator = false)
964
- 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?
965
980
  not_insertion = method_symbol != :ed_insert
966
981
  process_insert(force: not_insertion)
967
982
  end
@@ -980,11 +995,32 @@ class Reline::LineEditor
980
995
  end
981
996
  end
982
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
+
983
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
+
984
1022
  if method_symbol and respond_to?(method_symbol, true)
985
1023
  method_obj = method(method_symbol)
986
- else
987
- method_obj = nil
988
1024
  end
989
1025
  if method_symbol and key.is_a?(Symbol)
990
1026
  if @vi_arg and argumentable?(method_obj)
@@ -1006,8 +1042,6 @@ class Reline::LineEditor
1006
1042
  run_for_operators(key, method_symbol) do |with_operator|
1007
1043
  wrap_method_call(method_symbol, method_obj, key, with_operator)
1008
1044
  end
1009
- elsif @waiting_proc
1010
- @waiting_proc.(key)
1011
1045
  elsif method_obj
1012
1046
  wrap_method_call(method_symbol, method_obj, key)
1013
1047
  else
@@ -1018,9 +1052,6 @@ class Reline::LineEditor
1018
1052
  @vi_arg = nil
1019
1053
  end
1020
1054
  end
1021
- elsif @waiting_proc
1022
- @waiting_proc.(key)
1023
- @kill_ring.process
1024
1055
  elsif method_obj
1025
1056
  if method_symbol == :ed_argument_digit
1026
1057
  wrap_method_call(method_symbol, method_obj, key)
@@ -1097,52 +1128,51 @@ class Reline::LineEditor
1097
1128
  end
1098
1129
  old_lines = @buffer_of_lines.dup
1099
1130
  @first_char = false
1100
- completion_occurs = false
1131
+ @completion_occurs = false
1101
1132
  if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
1102
- unless @config.disable_completion
1103
- result = call_completion_proc
1104
- if result.is_a?(Array)
1105
- completion_occurs = true
1106
- process_insert
1107
- if @config.autocompletion
1108
- move_completed_list(result, :down)
1109
- else
1110
- complete(result)
1133
+ if !@config.disable_completion
1134
+ process_insert(force: true)
1135
+ if @config.autocompletion
1136
+ @completion_state = CompletionState::NORMAL
1137
+ @completion_occurs = move_completed_list(:down)
1138
+ else
1139
+ @completion_journey_state = nil
1140
+ result = call_completion_proc
1141
+ if result.is_a?(Array)
1142
+ @completion_occurs = true
1143
+ complete(result, false)
1111
1144
  end
1112
1145
  end
1113
1146
  end
1114
- elsif @config.editing_mode_is?(:emacs, :vi_insert) and key.char == :completion_journey_up
1115
- if not @config.disable_completion and @config.autocompletion
1116
- result = call_completion_proc
1117
- if result.is_a?(Array)
1118
- completion_occurs = true
1119
- process_insert
1120
- move_completed_list(result, :up)
1121
- end
1122
- end
1123
- elsif not @config.disable_completion and @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
1124
- unless @config.disable_completion
1125
- result = call_completion_proc
1126
- if result.is_a?(Array)
1127
- completion_occurs = true
1128
- process_insert
1129
- move_completed_list(result, "\C-p".ord == key.char ? :up : :down)
1130
- end
1147
+ elsif @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
1148
+ # In vi mode, move completed list even if autocompletion is off
1149
+ if not @config.disable_completion
1150
+ process_insert(force: true)
1151
+ @completion_state = CompletionState::NORMAL
1152
+ @completion_occurs = move_completed_list("\C-p".ord == key.char ? :up : :down)
1131
1153
  end
1132
1154
  elsif Symbol === key.char and respond_to?(key.char, true)
1133
1155
  process_key(key.char, key.char)
1134
1156
  else
1135
1157
  normal_char(key)
1136
1158
  end
1137
- unless completion_occurs
1159
+ unless @completion_occurs
1138
1160
  @completion_state = CompletionState::NORMAL
1139
- @completion_journey_data = nil
1161
+ @completion_journey_state = nil
1140
1162
  end
1163
+
1141
1164
  if @in_pasting
1142
1165
  clear_dialogs
1143
- else
1144
- return old_lines != @buffer_of_lines
1166
+ return
1167
+ end
1168
+
1169
+ modified = old_lines != @buffer_of_lines
1170
+ if !@completion_occurs && modified && !@config.disable_completion && @config.autocompletion
1171
+ # Auto complete starts only when edited
1172
+ process_insert(force: true)
1173
+ @completion_journey_state = retrieve_completion_journey_state
1145
1174
  end
1175
+ modified
1146
1176
  end
1147
1177
 
1148
1178
  def scroll_into_view
@@ -1194,15 +1224,16 @@ class Reline::LineEditor
1194
1224
  new_indent = @auto_indent_proc.(@buffer_of_lines.take(line_index + 1).push(''), line_index, byte_pointer, add_newline)
1195
1225
  return unless new_indent
1196
1226
 
1197
- @buffer_of_lines[line_index] = ' ' * new_indent + line.lstrip
1227
+ new_line = ' ' * new_indent + line.lstrip
1228
+ @buffer_of_lines[line_index] = new_line
1198
1229
  if @line_index == line_index
1199
- old_indent = line[/\A */].size
1200
- @byte_pointer = [@byte_pointer + new_indent - old_indent, 0].max
1230
+ indent_diff = new_line.bytesize - line.bytesize
1231
+ @byte_pointer = [@byte_pointer + indent_diff, 0].max
1201
1232
  end
1202
1233
  end
1203
1234
 
1204
1235
  def line()
1205
- current_line unless eof?
1236
+ @buffer_of_lines.join("\n") unless eof?
1206
1237
  end
1207
1238
 
1208
1239
  def current_line
@@ -1286,14 +1317,12 @@ class Reline::LineEditor
1286
1317
  end
1287
1318
  target = before
1288
1319
  end
1289
- if @is_multiline
1290
- lines = whole_lines
1291
- if @line_index > 0
1292
- preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
1293
- end
1294
- if (lines.size - 1) > @line_index
1295
- postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
1296
- 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")
1297
1326
  end
1298
1327
  [preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
1299
1328
  end
@@ -1315,20 +1344,16 @@ class Reline::LineEditor
1315
1344
 
1316
1345
  def delete_text(start = nil, length = nil)
1317
1346
  if start.nil? and length.nil?
1318
- if @is_multiline
1319
- if @buffer_of_lines.size == 1
1320
- @buffer_of_lines[@line_index] = ''
1321
- @byte_pointer = 0
1322
- elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
1323
- @buffer_of_lines.pop
1324
- @line_index -= 1
1325
- @byte_pointer = 0
1326
- elsif @line_index < (@buffer_of_lines.size - 1)
1327
- @buffer_of_lines.delete_at(@line_index)
1328
- @byte_pointer = 0
1329
- end
1330
- else
1331
- 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
1332
1357
  end
1333
1358
  elsif not start.nil? and not length.nil?
1334
1359
  if current_line
@@ -1405,6 +1430,14 @@ class Reline::LineEditor
1405
1430
  end
1406
1431
  end
1407
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
+
1408
1441
  # Editline:: +ed-unassigned+ This editor command always results in an error.
1409
1442
  # GNU Readline:: There is no corresponding macro.
1410
1443
  private def ed_unassigned(key) end # do nothing
@@ -1476,7 +1509,7 @@ class Reline::LineEditor
1476
1509
  byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
1477
1510
  if (@byte_pointer < current_line.bytesize)
1478
1511
  @byte_pointer += byte_size
1479
- 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
1480
1513
  @byte_pointer = 0
1481
1514
  @line_index += 1
1482
1515
  end
@@ -1489,7 +1522,7 @@ class Reline::LineEditor
1489
1522
  if @byte_pointer > 0
1490
1523
  byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
1491
1524
  @byte_pointer -= byte_size
1492
- 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
1493
1526
  @line_index -= 1
1494
1527
  @byte_pointer = current_line.bytesize
1495
1528
  end
@@ -1506,6 +1539,7 @@ class Reline::LineEditor
1506
1539
  @byte_pointer = 0
1507
1540
  end
1508
1541
  alias_method :beginning_of_line, :ed_move_to_beg
1542
+ alias_method :vi_zero, :ed_move_to_beg
1509
1543
 
1510
1544
  private def ed_move_to_end(key)
1511
1545
  @byte_pointer = 0
@@ -1516,131 +1550,95 @@ class Reline::LineEditor
1516
1550
  end
1517
1551
  alias_method :end_of_line, :ed_move_to_end
1518
1552
 
1519
- private def generate_searcher
1520
- Fiber.new do |first_key|
1521
- prev_search_key = first_key
1522
- search_word = String.new(encoding: @encoding)
1523
- multibyte_buf = String.new(encoding: 'ASCII-8BIT')
1524
- last_hit = nil
1525
- case first_key
1526
- when "\C-r".ord
1527
- prompt_name = 'reverse-i-search'
1528
- when "\C-s".ord
1529
- 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
1530
1575
  end
1531
- loop do
1532
- key = Fiber.yield(search_word)
1533
- search_again = false
1534
- case key
1535
- when -1 # determined
1536
- Reline.last_incremental_search = search_word
1537
- break
1538
- when "\C-h".ord, "\C-?".ord
1539
- grapheme_clusters = search_word.grapheme_clusters
1540
- if grapheme_clusters.size > 0
1541
- grapheme_clusters.pop
1542
- search_word = grapheme_clusters.join
1543
- end
1544
- when "\C-r".ord, "\C-s".ord
1545
- search_again = true if prev_search_key == key
1546
- prev_search_key = key
1547
- else
1548
- multibyte_buf << key
1549
- if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
1550
- search_word << multibyte_buf.dup.force_encoding(@encoding)
1551
- 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
1552
1584
  end
1553
- end
1554
- hit = nil
1555
- if not search_word.empty? and @line_backup_in_history&.include?(search_word)
1556
- @history_pointer = nil
1557
- hit = @line_backup_in_history
1558
- else
1559
- if search_again
1560
- if search_word.empty? and Reline.last_incremental_search
1561
- search_word = Reline.last_incremental_search
1562
- end
1563
- if @history_pointer
1564
- case prev_search_key
1565
- when "\C-r".ord
1566
- history_pointer_base = 0
1567
- history = Reline::HISTORY[0..(@history_pointer - 1)]
1568
- when "\C-s".ord
1569
- history_pointer_base = @history_pointer + 1
1570
- history = Reline::HISTORY[(@history_pointer + 1)..-1]
1571
- end
1572
- else
1573
- history_pointer_base = 0
1574
- history = Reline::HISTORY
1575
- end
1576
- elsif @history_pointer
1577
- case prev_search_key
1585
+ if @history_pointer
1586
+ case search_key
1578
1587
  when "\C-r".ord
1579
1588
  history_pointer_base = 0
1580
- history = Reline::HISTORY[0..@history_pointer]
1589
+ history = Reline::HISTORY[0..(@history_pointer - 1)]
1581
1590
  when "\C-s".ord
1582
- history_pointer_base = @history_pointer
1583
- history = Reline::HISTORY[@history_pointer..-1]
1591
+ history_pointer_base = @history_pointer + 1
1592
+ history = Reline::HISTORY[(@history_pointer + 1)..-1]
1584
1593
  end
1585
1594
  else
1586
1595
  history_pointer_base = 0
1587
1596
  history = Reline::HISTORY
1588
1597
  end
1589
- case prev_search_key
1598
+ elsif @history_pointer
1599
+ case search_key
1590
1600
  when "\C-r".ord
1591
- hit_index = history.rindex { |item|
1592
- item.include?(search_word)
1593
- }
1601
+ history_pointer_base = 0
1602
+ history = Reline::HISTORY[0..@history_pointer]
1594
1603
  when "\C-s".ord
1595
- hit_index = history.index { |item|
1596
- item.include?(search_word)
1597
- }
1598
- end
1599
- if hit_index
1600
- @history_pointer = history_pointer_base + hit_index
1601
- hit = Reline::HISTORY[@history_pointer]
1604
+ history_pointer_base = @history_pointer
1605
+ history = Reline::HISTORY[@history_pointer..-1]
1602
1606
  end
1607
+ else
1608
+ history_pointer_base = 0
1609
+ history = Reline::HISTORY
1603
1610
  end
1604
- case prev_search_key
1611
+ case search_key
1605
1612
  when "\C-r".ord
1606
- prompt_name = 'reverse-i-search'
1613
+ hit_index = history.rindex { |item|
1614
+ item.include?(search_word)
1615
+ }
1607
1616
  when "\C-s".ord
1608
- prompt_name = 'i-search'
1617
+ hit_index = history.index { |item|
1618
+ item.include?(search_word)
1619
+ }
1609
1620
  end
1610
- if hit
1611
- if @is_multiline
1612
- @buffer_of_lines = hit.split("\n")
1613
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1614
- @line_index = @buffer_of_lines.size - 1
1615
- @byte_pointer = current_line.bytesize
1616
- @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
1617
- else
1618
- @buffer_of_lines = [hit]
1619
- @byte_pointer = hit.bytesize
1620
- @searching_prompt = "(%s)`%s': %s" % [prompt_name, search_word, hit]
1621
- end
1622
- last_hit = hit
1623
- else
1624
- if @is_multiline
1625
- @searching_prompt = "(failed %s)`%s'" % [prompt_name, search_word]
1626
- else
1627
- @searching_prompt = "(failed %s)`%s': %s" % [prompt_name, search_word, last_hit]
1628
- end
1621
+ if hit_index
1622
+ hit_pointer = history_pointer_base + hit_index
1623
+ hit = Reline::HISTORY[hit_pointer]
1629
1624
  end
1630
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]
1631
1634
  end
1632
1635
  end
1633
1636
 
1634
1637
  private def incremental_search_history(key)
1635
1638
  unless @history_pointer
1636
- if @is_multiline
1637
- @line_backup_in_history = whole_buffer
1638
- else
1639
- @line_backup_in_history = current_line
1640
- end
1639
+ @line_backup_in_history = whole_buffer
1641
1640
  end
1642
- searcher = generate_searcher
1643
- searcher.resume(key)
1641
+ searcher = generate_searcher(key)
1644
1642
  @searching_prompt = "(reverse-i-search)`': "
1645
1643
  termination_keys = ["\C-j".ord]
1646
1644
  termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators
@@ -1652,53 +1650,41 @@ class Reline::LineEditor
1652
1650
  else
1653
1651
  buffer = @line_backup_in_history
1654
1652
  end
1655
- if @is_multiline
1656
- @buffer_of_lines = buffer.split("\n")
1657
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1658
- @line_index = @buffer_of_lines.size - 1
1659
- else
1660
- @buffer_of_lines = [buffer]
1661
- 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
1662
1656
  @searching_prompt = nil
1663
1657
  @waiting_proc = nil
1664
1658
  @byte_pointer = 0
1665
- searcher.resume(-1)
1666
1659
  when "\C-g".ord
1667
- if @is_multiline
1668
- @buffer_of_lines = @line_backup_in_history.split("\n")
1669
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1670
- @line_index = @buffer_of_lines.size - 1
1671
- else
1672
- @buffer_of_lines = [@line_backup_in_history]
1673
- end
1674
- @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)
1675
1664
  @searching_prompt = nil
1676
1665
  @waiting_proc = nil
1677
- @line_backup_in_history = nil
1678
1666
  @byte_pointer = 0
1679
1667
  else
1680
1668
  chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
1681
1669
  if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == "\C-?".ord or k == "\C-r".ord or k == "\C-s".ord
1682
- 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
1683
1675
  else
1684
1676
  if @history_pointer
1685
1677
  line = Reline::HISTORY[@history_pointer]
1686
1678
  else
1687
1679
  line = @line_backup_in_history
1688
1680
  end
1689
- if @is_multiline
1690
- @line_backup_in_history = whole_buffer
1691
- @buffer_of_lines = line.split("\n")
1692
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1693
- @line_index = @buffer_of_lines.size - 1
1694
- else
1695
- @line_backup_in_history = current_line
1696
- @buffer_of_lines = [line]
1697
- 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
1698
1685
  @searching_prompt = nil
1699
1686
  @waiting_proc = nil
1700
1687
  @byte_pointer = 0
1701
- searcher.resume(-1)
1702
1688
  end
1703
1689
  end
1704
1690
  }
@@ -1714,191 +1700,95 @@ class Reline::LineEditor
1714
1700
  end
1715
1701
  alias_method :forward_search_history, :vi_search_next
1716
1702
 
1717
- private def ed_search_prev_history(key, arg: 1)
1718
- history = nil
1719
- h_pointer = nil
1720
- line_no = nil
1721
- substr = current_line.slice(0, @byte_pointer)
1722
- if @history_pointer.nil?
1723
- return if not current_line.empty? and substr.empty?
1724
- history = Reline::HISTORY
1725
- elsif @history_pointer.zero?
1726
- history = nil
1727
- h_pointer = nil
1728
- else
1729
- history = Reline::HISTORY.slice(0, @history_pointer)
1730
- end
1731
- return if history.nil?
1732
- if @is_multiline
1733
- h_pointer = history.rindex { |h|
1734
- h.split("\n").each_with_index { |l, i|
1735
- if l.start_with?(substr)
1736
- line_no = i
1737
- break
1738
- end
1739
- }
1740
- not line_no.nil?
1741
- }
1742
- else
1743
- h_pointer = history.rindex { |l|
1744
- l.start_with?(substr)
1745
- }
1746
- end
1747
- return if h_pointer.nil?
1748
- @history_pointer = h_pointer
1749
- cursor = current_byte_pointer_cursor
1750
- if @is_multiline
1751
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1752
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1753
- @line_index = line_no
1754
- calculate_nearest_cursor(cursor)
1755
- else
1756
- @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
1757
- 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
1758
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)
1759
1722
  arg -= 1
1760
1723
  ed_search_prev_history(key, arg: arg) if arg > 0
1761
1724
  end
1762
1725
  alias_method :history_search_backward, :ed_search_prev_history
1763
1726
 
1764
1727
  private def ed_search_next_history(key, arg: 1)
1765
- substr = current_line.slice(0, @byte_pointer)
1766
- if @history_pointer.nil?
1767
- return
1768
- elsif @history_pointer == (Reline::HISTORY.size - 1) and not substr.empty?
1769
- return
1770
- end
1771
- history = Reline::HISTORY.slice((@history_pointer + 1)..-1)
1772
- h_pointer = nil
1773
- line_no = nil
1774
- if @is_multiline
1775
- h_pointer = history.index { |h|
1776
- h.split("\n").each_with_index { |l, i|
1777
- if l.start_with?(substr)
1778
- line_no = i
1779
- break
1780
- end
1781
- }
1782
- not line_no.nil?
1783
- }
1784
- else
1785
- h_pointer = history.index { |l|
1786
- l.start_with?(substr)
1787
- }
1788
- end
1789
- 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)
1790
1733
  return if h_pointer.nil? and not substr.empty?
1791
- @history_pointer = h_pointer
1792
- if @is_multiline
1793
- if @history_pointer.nil? and substr.empty?
1794
- @buffer_of_lines = []
1795
- @line_index = 0
1796
- @byte_pointer = 0
1797
- else
1798
- cursor = current_byte_pointer_cursor
1799
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1800
- @line_index = line_no
1801
- calculate_nearest_cursor(cursor)
1802
- end
1803
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1804
- else
1805
- if @history_pointer.nil? and substr.empty?
1806
- set_current_line('', 0)
1807
- else
1808
- set_current_line(Reline::HISTORY[@history_pointer])
1809
- end
1810
- end
1734
+
1735
+ move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer)
1811
1736
  arg -= 1
1812
1737
  ed_search_next_history(key, arg: arg) if arg > 0
1813
1738
  end
1814
1739
  alias_method :history_search_forward, :ed_search_next_history
1815
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
+
1816
1763
  private def ed_prev_history(key, arg: 1)
1817
- if @is_multiline and @line_index > 0
1764
+ if @line_index > 0
1818
1765
  cursor = current_byte_pointer_cursor
1819
1766
  @line_index -= 1
1820
1767
  calculate_nearest_cursor(cursor)
1821
1768
  return
1822
1769
  end
1823
- if Reline::HISTORY.empty?
1824
- return
1825
- end
1826
- if @history_pointer.nil?
1827
- @history_pointer = Reline::HISTORY.size - 1
1828
- cursor = current_byte_pointer_cursor
1829
- if @is_multiline
1830
- @line_backup_in_history = whole_buffer
1831
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1832
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1833
- @line_index = @buffer_of_lines.size - 1
1834
- calculate_nearest_cursor(cursor)
1835
- else
1836
- @line_backup_in_history = whole_buffer
1837
- @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
1838
- calculate_nearest_cursor(cursor)
1839
- end
1840
- elsif @history_pointer.zero?
1841
- return
1842
- else
1843
- if @is_multiline
1844
- Reline::HISTORY[@history_pointer] = whole_buffer
1845
- @history_pointer -= 1
1846
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1847
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1848
- @line_index = @buffer_of_lines.size - 1
1849
- else
1850
- Reline::HISTORY[@history_pointer] = whole_buffer
1851
- @history_pointer -= 1
1852
- @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
1853
- end
1854
- end
1855
- if @config.editing_mode_is?(:emacs, :vi_insert)
1856
- @byte_pointer = current_line.bytesize
1857
- elsif @config.editing_mode_is?(:vi_command)
1858
- @byte_pointer = 0
1859
- 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
+ )
1860
1775
  arg -= 1
1861
1776
  ed_prev_history(key, arg: arg) if arg > 0
1862
1777
  end
1863
1778
  alias_method :previous_history, :ed_prev_history
1864
1779
 
1865
1780
  private def ed_next_history(key, arg: 1)
1866
- if @is_multiline and @line_index < (@buffer_of_lines.size - 1)
1781
+ if @line_index < (@buffer_of_lines.size - 1)
1867
1782
  cursor = current_byte_pointer_cursor
1868
1783
  @line_index += 1
1869
1784
  calculate_nearest_cursor(cursor)
1870
1785
  return
1871
1786
  end
1872
- if @history_pointer.nil?
1873
- return
1874
- elsif @history_pointer == (Reline::HISTORY.size - 1)
1875
- if @is_multiline
1876
- @history_pointer = nil
1877
- @buffer_of_lines = @line_backup_in_history.split("\n")
1878
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1879
- @line_index = 0
1880
- else
1881
- @history_pointer = nil
1882
- @buffer_of_lines = [@line_backup_in_history]
1883
- end
1884
- else
1885
- if @is_multiline
1886
- Reline::HISTORY[@history_pointer] = whole_buffer
1887
- @history_pointer += 1
1888
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1889
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1890
- @line_index = 0
1891
- else
1892
- Reline::HISTORY[@history_pointer] = whole_buffer
1893
- @history_pointer += 1
1894
- @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
1895
- end
1896
- end
1897
- if @config.editing_mode_is?(:emacs, :vi_insert)
1898
- @byte_pointer = current_line.bytesize
1899
- elsif @config.editing_mode_is?(:vi_command)
1900
- @byte_pointer = 0
1901
- 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
+ )
1902
1792
  arg -= 1
1903
1793
  ed_next_history(key, arg: arg) if arg > 0
1904
1794
  end
@@ -1929,17 +1819,13 @@ class Reline::LineEditor
1929
1819
  end
1930
1820
  end
1931
1821
  else
1932
- if @history_pointer
1933
- Reline::HISTORY[@history_pointer] = whole_buffer
1934
- @history_pointer = nil
1935
- end
1936
1822
  finish
1937
1823
  end
1938
1824
  end
1939
1825
 
1940
1826
  private def em_delete_prev_char(key, arg: 1)
1941
1827
  arg.times do
1942
- if @is_multiline and @byte_pointer == 0 and @line_index > 0
1828
+ if @byte_pointer == 0 and @line_index > 0
1943
1829
  @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
1944
1830
  @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
1945
1831
  @line_index -= 1
@@ -1963,7 +1849,7 @@ class Reline::LineEditor
1963
1849
  line, deleted = byteslice!(current_line, @byte_pointer, current_line.bytesize - @byte_pointer)
1964
1850
  set_current_line(line, line.bytesize)
1965
1851
  @kill_ring.append(deleted)
1966
- 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
1967
1853
  set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize)
1968
1854
  end
1969
1855
  end
@@ -2003,7 +1889,7 @@ class Reline::LineEditor
2003
1889
  alias_method :kill_whole_line, :em_kill_line
2004
1890
 
2005
1891
  private def em_delete(key)
2006
- 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
2007
1893
  @eof = true
2008
1894
  finish
2009
1895
  elsif @byte_pointer < current_line.bytesize
@@ -2011,7 +1897,7 @@ class Reline::LineEditor
2011
1897
  mbchar = splitted_last.grapheme_clusters.first
2012
1898
  line, = byteslice!(current_line, @byte_pointer, mbchar.bytesize)
2013
1899
  set_current_line(line)
2014
- 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
2015
1901
  set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize)
2016
1902
  end
2017
1903
  end
@@ -2020,7 +1906,7 @@ class Reline::LineEditor
2020
1906
  private def em_delete_or_list(key)
2021
1907
  if current_line.empty? or @byte_pointer < current_line.bytesize
2022
1908
  em_delete(key)
2023
- else # show completed list
1909
+ elsif !@config.autocompletion # show completed list
2024
1910
  result = call_completion_proc
2025
1911
  if result.is_a?(Array)
2026
1912
  complete(result, true)
@@ -2046,7 +1932,11 @@ class Reline::LineEditor
2046
1932
  alias_method :yank_pop, :em_yank_pop
2047
1933
 
2048
1934
  private def ed_clear_screen(key)
2049
- @cleared = true
1935
+ Reline::IOGate.clear_screen
1936
+ @screen_size = Reline::IOGate.get_screen_size
1937
+ @rendered_screen.lines = []
1938
+ @rendered_screen.base_y = 0
1939
+ @rendered_screen.cursor_y = 0
2050
1940
  end
2051
1941
  alias_method :clear_screen, :ed_clear_screen
2052
1942
 
@@ -2250,7 +2140,7 @@ class Reline::LineEditor
2250
2140
  end
2251
2141
 
2252
2142
  private def vi_delete_prev_char(key)
2253
- if @is_multiline and @byte_pointer == 0 and @line_index > 0
2143
+ if @byte_pointer == 0 and @line_index > 0
2254
2144
  @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
2255
2145
  @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
2256
2146
  @line_index -= 1
@@ -2287,54 +2177,67 @@ class Reline::LineEditor
2287
2177
  copy_for_vi(deleted)
2288
2178
  end
2289
2179
 
2290
- private def vi_zero(key)
2291
- @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
2292
2190
  end
2293
2191
 
2294
- private def vi_change_meta(key, arg: 1)
2295
- @drop_terminate_spaces = true
2296
- @waiting_operator_proc = proc { |byte_pointer_diff|
2297
- if byte_pointer_diff > 0
2298
- line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff)
2299
- elsif byte_pointer_diff < 0
2300
- line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2301
- end
2302
- set_current_line(line)
2303
- copy_for_vi(cut)
2304
- @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2305
- @config.editing_mode = :vi_insert
2306
- @drop_terminate_spaces = false
2307
- }
2308
- @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
2309
2196
  end
2310
2197
 
2311
- private def vi_delete_meta(key, arg: 1)
2312
- @waiting_operator_proc = proc { |byte_pointer_diff|
2313
- if byte_pointer_diff > 0
2314
- line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff)
2315
- elsif byte_pointer_diff < 0
2316
- line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2317
- end
2318
- copy_for_vi(cut)
2319
- set_current_line(line || '', @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0))
2320
- }
2321
- @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
2322
2207
  end
2323
2208
 
2324
- private def vi_yank(key, arg: 1)
2325
- @waiting_operator_proc = proc { |byte_pointer_diff|
2326
- if byte_pointer_diff > 0
2327
- cut = current_line.byteslice(@byte_pointer, byte_pointer_diff)
2328
- elsif byte_pointer_diff < 0
2329
- cut = current_line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2330
- end
2331
- copy_for_vi(cut)
2332
- }
2333
- @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)
2334
2237
  end
2335
2238
 
2336
2239
  private def vi_list_or_eof(key)
2337
- 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
2338
2241
  set_current_line('', 0)
2339
2242
  @eof = true
2340
2243
  finish
@@ -2365,36 +2268,18 @@ class Reline::LineEditor
2365
2268
  if Reline::HISTORY.empty?
2366
2269
  return
2367
2270
  end
2368
- if @history_pointer.nil?
2369
- @history_pointer = 0
2370
- @line_backup_in_history = current_line
2371
- set_current_line(Reline::HISTORY[@history_pointer], 0)
2372
- elsif @history_pointer.zero?
2373
- return
2374
- else
2375
- Reline::HISTORY[@history_pointer] = current_line
2376
- @history_pointer = 0
2377
- set_current_line(Reline::HISTORY[@history_pointer], 0)
2378
- end
2271
+ move_history(0, line: :start, cursor: :start)
2379
2272
  end
2380
2273
 
2381
2274
  private def vi_histedit(key)
2382
2275
  path = Tempfile.open { |fp|
2383
- if @is_multiline
2384
- fp.write whole_lines.join("\n")
2385
- else
2386
- fp.write current_line
2387
- end
2276
+ fp.write whole_lines.join("\n")
2388
2277
  fp.path
2389
2278
  }
2390
2279
  system("#{ENV['EDITOR']} #{path}")
2391
- if @is_multiline
2392
- @buffer_of_lines = File.read(path).split("\n")
2393
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2394
- @line_index = 0
2395
- else
2396
- @buffer_of_lines = File.read(path).split("\n")
2397
- 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
2398
2283
  finish
2399
2284
  end
2400
2285
 
@@ -2435,18 +2320,11 @@ class Reline::LineEditor
2435
2320
  end
2436
2321
 
2437
2322
  private def vi_to_column(key, arg: 0)
2438
- current_row_width = calculate_width(current_row)
2439
- @byte_pointer, = current_line.grapheme_clusters.inject([0, 0]) { |total, gc|
2440
- # 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|
2441
2325
  mbchar_width = Reline::Unicode.get_mbchar_width(gc)
2442
- if (total.last + mbchar_width) >= arg
2443
- break total
2444
- elsif (total.last + mbchar_width) >= current_row_width
2445
- break total
2446
- else
2447
- total = [total.first + gc.bytesize, total.last + mbchar_width]
2448
- total
2449
- end
2326
+ break [total_byte_size, total_width] if (total_width + mbchar_width) >= arg
2327
+ [total_byte_size + gc.bytesize, total_width + mbchar_width]
2450
2328
  }
2451
2329
  end
2452
2330
 
@@ -2573,7 +2451,7 @@ class Reline::LineEditor
2573
2451
  end
2574
2452
 
2575
2453
  private def vi_join_lines(key, arg: 1)
2576
- if @is_multiline and @buffer_of_lines.size > @line_index + 1
2454
+ if @buffer_of_lines.size > @line_index + 1
2577
2455
  next_line = @buffer_of_lines.delete_at(@line_index + 1).lstrip
2578
2456
  set_current_line(current_line + ' ' + next_line, current_line.bytesize)
2579
2457
  end
@@ -2594,6 +2472,11 @@ class Reline::LineEditor
2594
2472
  end
2595
2473
  alias_method :exchange_point_and_mark, :em_exchange_mark
2596
2474
 
2597
- 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
2598
2481
  end
2599
2482
  end