reline 0.5.0 → 0.5.3

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