reline 0.5.9 → 0.6.1

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.
@@ -13,7 +13,6 @@ class Reline::LineEditor
13
13
  attr_accessor :prompt_proc
14
14
  attr_accessor :auto_indent_proc
15
15
  attr_accessor :dig_perfect_match_proc
16
- attr_writer :output
17
16
 
18
17
  VI_MOTIONS = %i{
19
18
  ed_prev_char
@@ -36,7 +35,6 @@ class Reline::LineEditor
36
35
 
37
36
  module CompletionState
38
37
  NORMAL = :normal
39
- COMPLETION = :completion
40
38
  MENU = :menu
41
39
  MENU_WITH_PERFECT_MATCH = :menu_with_perfect_match
42
40
  PERFECT_MATCH = :perfect_match
@@ -72,17 +70,21 @@ class Reline::LineEditor
72
70
 
73
71
  MINIMUM_SCROLLBAR_HEIGHT = 1
74
72
 
75
- def initialize(config, encoding)
73
+ def initialize(config)
76
74
  @config = config
77
75
  @completion_append_character = ''
78
76
  @screen_size = [0, 0] # Should be initialized with actual winsize in LineEditor#reset
79
- reset_variables(encoding: encoding)
77
+ reset_variables
80
78
  end
81
79
 
82
80
  def io_gate
83
81
  Reline::IOGate
84
82
  end
85
83
 
84
+ def encoding
85
+ io_gate.encoding
86
+ end
87
+
86
88
  def set_pasting_state(in_pasting)
87
89
  # While pasting, text to be inserted is stored to @continuous_insertion_buffer.
88
90
  # After pasting, this buffer should be force inserted.
@@ -136,9 +138,9 @@ class Reline::LineEditor
136
138
  end
137
139
  end
138
140
 
139
- def reset(prompt = '', encoding:)
141
+ def reset(prompt = '')
140
142
  @screen_size = Reline::IOGate.get_screen_size
141
- reset_variables(prompt, encoding: encoding)
143
+ reset_variables(prompt)
142
144
  @rendered_screen.base_y = Reline::IOGate.cursor_pos.y
143
145
  if ENV.key?('RELINE_ALT_SCROLLBAR')
144
146
  @full_block = '::'
@@ -150,7 +152,7 @@ class Reline::LineEditor
150
152
  @upper_half_block = '▀'
151
153
  @lower_half_block = '▄'
152
154
  @block_elem_width = 1
153
- elsif @encoding == Encoding::UTF_8
155
+ elsif encoding == Encoding::UTF_8
154
156
  @full_block = '█'
155
157
  @upper_half_block = '▀'
156
158
  @lower_half_block = '▄'
@@ -176,9 +178,8 @@ class Reline::LineEditor
176
178
  scroll_into_view
177
179
  Reline::IOGate.move_cursor_up @rendered_screen.cursor_y
178
180
  @rendered_screen.base_y = Reline::IOGate.cursor_pos.y
179
- @rendered_screen.lines = []
180
- @rendered_screen.cursor_y = 0
181
- render_differential
181
+ clear_rendered_screen_cache
182
+ render
182
183
  end
183
184
 
184
185
  private def handle_interrupted
@@ -186,11 +187,11 @@ class Reline::LineEditor
186
187
 
187
188
  @interrupted = false
188
189
  clear_dialogs
189
- scrolldown = render_differential
190
- Reline::IOGate.scroll_down scrolldown
190
+ render
191
+ cursor_to_bottom_offset = @rendered_screen.lines.size - @rendered_screen.cursor_y
192
+ Reline::IOGate.scroll_down cursor_to_bottom_offset
191
193
  Reline::IOGate.move_cursor_column 0
192
- @rendered_screen.lines = []
193
- @rendered_screen.cursor_y = 0
194
+ clear_rendered_screen_cache
194
195
  case @old_trap
195
196
  when 'DEFAULT', 'SYSTEM_DEFAULT'
196
197
  raise Interrupt
@@ -220,10 +221,9 @@ class Reline::LineEditor
220
221
  @eof
221
222
  end
222
223
 
223
- def reset_variables(prompt = '', encoding:)
224
+ def reset_variables(prompt = '')
224
225
  @prompt = prompt.gsub("\n", "\\n")
225
226
  @mark_pointer = nil
226
- @encoding = encoding
227
227
  @is_multiline = false
228
228
  @finished = false
229
229
  @history_pointer = nil
@@ -240,7 +240,7 @@ class Reline::LineEditor
240
240
  @searching_prompt = nil
241
241
  @just_cursor_moving = false
242
242
  @eof = false
243
- @continuous_insertion_buffer = String.new(encoding: @encoding)
243
+ @continuous_insertion_buffer = String.new(encoding: encoding)
244
244
  @scroll_partial_screen = 0
245
245
  @drop_terminate_spaces = false
246
246
  @in_pasting = false
@@ -250,9 +250,9 @@ class Reline::LineEditor
250
250
  @resized = false
251
251
  @cache = {}
252
252
  @rendered_screen = RenderedScreen.new(base_y: 0, lines: [], cursor_y: 0)
253
- @input_lines = [[[""], 0, 0]]
254
- @input_lines_position = 0
255
- @undoing = false
253
+ @undo_redo_history = [[[""], 0, 0]]
254
+ @undo_redo_index = 0
255
+ @restoring = false
256
256
  @prev_action_state = NullActionState
257
257
  @next_action_state = NullActionState
258
258
  reset_line
@@ -260,11 +260,10 @@ class Reline::LineEditor
260
260
 
261
261
  def reset_line
262
262
  @byte_pointer = 0
263
- @buffer_of_lines = [String.new(encoding: @encoding)]
263
+ @buffer_of_lines = [String.new(encoding: encoding)]
264
264
  @line_index = 0
265
265
  @cache.clear
266
266
  @line_backup_in_history = nil
267
- @multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
268
267
  end
269
268
 
270
269
  def multiline_on
@@ -276,7 +275,7 @@ class Reline::LineEditor
276
275
  end
277
276
 
278
277
  private def insert_new_line(cursor_line, next_line)
279
- @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
278
+ @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: encoding))
280
279
  @buffer_of_lines[@line_index] = cursor_line
281
280
  @line_index += 1
282
281
  @byte_pointer = 0
@@ -298,8 +297,8 @@ class Reline::LineEditor
298
297
  end
299
298
  end
300
299
 
301
- private def split_by_width(str, max_width, offset: 0)
302
- Reline::Unicode.split_by_width(str, max_width, @encoding, offset: offset)
300
+ private def split_line_by_width(str, max_width, offset: 0)
301
+ Reline::Unicode.split_line_by_width(str, max_width, encoding, offset: offset)
303
302
  end
304
303
 
305
304
  def current_byte_pointer_cursor
@@ -389,8 +388,8 @@ class Reline::LineEditor
389
388
  if (cached = cached_wraps[[prompt, line]])
390
389
  next cached
391
390
  end
392
- *wrapped_prompts, code_line_prompt = split_by_width(prompt, width).first.compact
393
- wrapped_lines = split_by_width(line, width, offset: calculate_width(code_line_prompt, true)).first.compact
391
+ *wrapped_prompts, code_line_prompt = split_line_by_width(prompt, width)
392
+ wrapped_lines = split_line_by_width(line, width, offset: calculate_width(code_line_prompt, true))
394
393
  wrapped_prompts.map { |p| [p, ''] } + [[code_line_prompt, wrapped_lines.first]] + wrapped_lines.drop(1).map { |c| ['', c] }
395
394
  end
396
395
  end
@@ -414,7 +413,7 @@ class Reline::LineEditor
414
413
  # do nothing
415
414
  elsif level == :blank
416
415
  Reline::IOGate.move_cursor_column base_x
417
- @output.write "#{Reline::IOGate.reset_color_sequence}#{' ' * width}"
416
+ Reline::IOGate.write "#{Reline::IOGate.reset_color_sequence}#{' ' * width}"
418
417
  else
419
418
  x, w, content = new_items[level]
420
419
  cover_begin = base_x != 0 && new_levels[base_x - 1] == level
@@ -424,7 +423,7 @@ class Reline::LineEditor
424
423
  content, pos = Reline::Unicode.take_mbchar_range(content, base_x - x, width, cover_begin: cover_begin, cover_end: cover_end, padding: true)
425
424
  end
426
425
  Reline::IOGate.move_cursor_column x + pos
427
- @output.write "#{Reline::IOGate.reset_color_sequence}#{content}#{Reline::IOGate.reset_color_sequence}"
426
+ Reline::IOGate.write "#{Reline::IOGate.reset_color_sequence}#{content}#{Reline::IOGate.reset_color_sequence}"
428
427
  end
429
428
  base_x += width
430
429
  end
@@ -437,8 +436,8 @@ class Reline::LineEditor
437
436
  # Calculate cursor position in word wrapped content.
438
437
  def wrapped_cursor_position
439
438
  prompt_width = calculate_width(prompt_list[@line_index], true)
440
- line_before_cursor = whole_lines[@line_index].byteslice(0, @byte_pointer)
441
- wrapped_line_before_cursor = split_by_width(' ' * prompt_width + line_before_cursor, screen_width).first.compact
439
+ line_before_cursor = Reline::Unicode.escape_for_print(whole_lines[@line_index].byteslice(0, @byte_pointer))
440
+ wrapped_line_before_cursor = split_line_by_width(' ' * prompt_width + line_before_cursor, screen_width)
442
441
  wrapped_cursor_y = wrapped_prompt_and_input_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1
443
442
  wrapped_cursor_x = calculate_width(wrapped_line_before_cursor.last)
444
443
  [wrapped_cursor_x, wrapped_cursor_y]
@@ -460,49 +459,27 @@ class Reline::LineEditor
460
459
  end
461
460
 
462
461
  def render_finished
463
- clear_rendered_lines
464
- render_full_content
465
- end
466
-
467
- def clear_rendered_lines
468
- Reline::IOGate.move_cursor_up @rendered_screen.cursor_y
469
- Reline::IOGate.move_cursor_column 0
470
-
471
- num_lines = @rendered_screen.lines.size
472
- return unless num_lines && num_lines >= 1
473
-
474
- Reline::IOGate.move_cursor_down num_lines - 1
475
- (num_lines - 1).times do
476
- Reline::IOGate.erase_after_cursor
477
- Reline::IOGate.move_cursor_up 1
478
- end
479
- Reline::IOGate.erase_after_cursor
480
- @rendered_screen.lines = []
481
- @rendered_screen.cursor_y = 0
482
- end
483
-
484
- def render_full_content
485
- lines = @buffer_of_lines.size.times.map do |i|
486
- line = prompt_list[i] + modified_lines[i]
487
- wrapped_lines, = split_by_width(line, screen_width)
488
- wrapped_lines.last.empty? ? "#{line} " : line
462
+ Reline::IOGate.buffered_output do
463
+ render_differential([], 0, 0)
464
+ lines = @buffer_of_lines.size.times.map do |i|
465
+ line = Reline::Unicode.strip_non_printing_start_end(prompt_list[i]) + modified_lines[i]
466
+ wrapped_lines = split_line_by_width(line, screen_width)
467
+ wrapped_lines.last.empty? ? "#{line} " : line
468
+ end
469
+ Reline::IOGate.write lines.map { |l| "#{l}\r\n" }.join
489
470
  end
490
- @output.puts lines.map { |l| "#{l}\r\n" }.join
491
471
  end
492
472
 
493
- def print_nomultiline_prompt(prompt)
494
- return unless prompt && !@is_multiline
495
-
473
+ def print_nomultiline_prompt
474
+ Reline::IOGate.disable_auto_linewrap(true) if Reline::IOGate.win?
496
475
  # Readline's test `TestRelineAsReadline#test_readline` requires first output to be prompt, not cursor reset escape sequence.
497
- @rendered_screen.lines = [[[0, Reline::Unicode.calculate_width(prompt, true), prompt]]]
498
- @rendered_screen.cursor_y = 0
499
- @output.write prompt
476
+ Reline::IOGate.write Reline::Unicode.strip_non_printing_start_end(@prompt) if @prompt && !@is_multiline
477
+ ensure
478
+ Reline::IOGate.disable_auto_linewrap(false) if Reline::IOGate.win?
500
479
  end
501
480
 
502
- def render_differential
481
+ def render
503
482
  wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
504
-
505
- rendered_lines = @rendered_screen.lines
506
483
  new_lines = wrapped_prompt_and_input_lines.flatten(1)[screen_scroll_top, screen_height].map do |prompt, line|
507
484
  prompt_width = Reline::Unicode.calculate_width(prompt, true)
508
485
  [[0, prompt_width, prompt], [prompt_width, Reline::Unicode.calculate_width(line, true), line]]
@@ -520,12 +497,24 @@ class Reline::LineEditor
520
497
  x_range, y_range = dialog_range dialog, wrapped_cursor_y - screen_scroll_top
521
498
  y_range.each do |row|
522
499
  next if row < 0 || row >= screen_height
500
+
523
501
  dialog_rows = new_lines[row] ||= []
524
502
  # index 0 is for prompt, index 1 is for line, index 2.. is for dialog
525
503
  dialog_rows[index + 2] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
526
504
  end
527
505
  end
528
506
 
507
+ Reline::IOGate.buffered_output do
508
+ render_differential new_lines, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top
509
+ end
510
+ end
511
+
512
+ # Reflects lines to be rendered and new cursor position to the screen
513
+ # by calculating the difference from the previous render.
514
+
515
+ private def render_differential(new_lines, new_cursor_x, new_cursor_y)
516
+ Reline::IOGate.disable_auto_linewrap(true) if Reline::IOGate.win?
517
+ rendered_lines = @rendered_screen.lines
529
518
  cursor_y = @rendered_screen.cursor_y
530
519
  if new_lines != rendered_lines
531
520
  # Hide cursor while rendering to avoid cursor flickering.
@@ -552,11 +541,17 @@ class Reline::LineEditor
552
541
  @rendered_screen.lines = new_lines
553
542
  Reline::IOGate.show_cursor
554
543
  end
555
- y = wrapped_cursor_y - screen_scroll_top
556
- Reline::IOGate.move_cursor_column wrapped_cursor_x
557
- Reline::IOGate.move_cursor_down y - cursor_y
558
- @rendered_screen.cursor_y = y
559
- new_lines.size - y
544
+ Reline::IOGate.move_cursor_column new_cursor_x
545
+ new_cursor_y = new_cursor_y.clamp(0, screen_height - 1)
546
+ Reline::IOGate.move_cursor_down new_cursor_y - cursor_y
547
+ @rendered_screen.cursor_y = new_cursor_y
548
+ ensure
549
+ Reline::IOGate.disable_auto_linewrap(false) if Reline::IOGate.win?
550
+ end
551
+
552
+ private def clear_rendered_screen_cache
553
+ @rendered_screen.lines = []
554
+ @rendered_screen.cursor_y = 0
560
555
  end
561
556
 
562
557
  def upper_space_height(wrapped_cursor_y)
@@ -568,7 +563,7 @@ class Reline::LineEditor
568
563
  end
569
564
 
570
565
  def rerender
571
- render_differential unless @in_pasting
566
+ render unless @in_pasting
572
567
  end
573
568
 
574
569
  class DialogProcScope
@@ -586,8 +581,9 @@ class Reline::LineEditor
586
581
  @context
587
582
  end
588
583
 
589
- def retrieve_completion_block(set_completion_quote_character = false)
590
- @line_editor.retrieve_completion_block(set_completion_quote_character)
584
+ def retrieve_completion_block(_unused = false)
585
+ preposing, target, postposing, _quote = @line_editor.retrieve_completion_block
586
+ [preposing, target, postposing]
591
587
  end
592
588
 
593
589
  def call_completion_proc_with_checking_args(pre, target, post)
@@ -807,98 +803,73 @@ class Reline::LineEditor
807
803
  @config.editing_mode
808
804
  end
809
805
 
810
- private def menu(_target, list)
806
+ private def menu(list)
811
807
  @menu_info = MenuInfo.new(list)
812
808
  end
813
809
 
814
- private def complete_internal_proc(list, is_menu)
815
- preposing, target, postposing = retrieve_completion_block
816
- list = list.select { |i|
817
- if i and not Encoding.compatible?(target.encoding, i.encoding)
818
- raise Encoding::CompatibilityError, "#{target.encoding.name} is not compatible with #{i.encoding.name}"
810
+ private def filter_normalize_candidates(target, list)
811
+ target = target.downcase if @config.completion_ignore_case
812
+ list.select do |item|
813
+ next unless item
814
+ unless Encoding.compatible?(target.encoding, item.encoding)
815
+ # Workaround for Readline test
816
+ if defined?(::Readline) && ::Readline == ::Reline
817
+ raise Encoding::CompatibilityError, "incompatible character encodings: #{target.encoding} and #{item.encoding}"
818
+ end
819
819
  end
820
+
820
821
  if @config.completion_ignore_case
821
- i&.downcase&.start_with?(target.downcase)
822
+ item.downcase.start_with?(target)
822
823
  else
823
- i&.start_with?(target)
824
- end
825
- }.uniq
826
- if is_menu
827
- menu(target, list)
828
- return nil
829
- end
830
- completed = list.inject { |memo, item|
831
- begin
832
- memo_mbchars = memo.unicode_normalize.grapheme_clusters
833
- item_mbchars = item.unicode_normalize.grapheme_clusters
834
- rescue Encoding::CompatibilityError
835
- memo_mbchars = memo.grapheme_clusters
836
- item_mbchars = item.grapheme_clusters
837
- end
838
- size = [memo_mbchars.size, item_mbchars.size].min
839
- result = +''
840
- size.times do |i|
841
- if @config.completion_ignore_case
842
- if memo_mbchars[i].casecmp?(item_mbchars[i])
843
- result << memo_mbchars[i]
844
- else
845
- break
846
- end
847
- else
848
- if memo_mbchars[i] == item_mbchars[i]
849
- result << memo_mbchars[i]
850
- else
851
- break
852
- end
853
- end
824
+ item.start_with?(target)
854
825
  end
855
- result
856
- }
857
- [target, preposing, completed, postposing]
826
+ end.map do |item|
827
+ item.unicode_normalize
828
+ rescue Encoding::CompatibilityError
829
+ item
830
+ end.uniq
858
831
  end
859
832
 
860
- private def perform_completion(list, just_show_list)
833
+ private def perform_completion(preposing, target, postposing, quote, list)
834
+ candidates = filter_normalize_candidates(target, list)
835
+
861
836
  case @completion_state
862
- when CompletionState::NORMAL
863
- @completion_state = CompletionState::COMPLETION
864
837
  when CompletionState::PERFECT_MATCH
865
- @dig_perfect_match_proc&.(@perfect_matched)
866
- end
867
- if just_show_list
868
- is_menu = true
869
- elsif @completion_state == CompletionState::MENU
870
- is_menu = true
871
- elsif @completion_state == CompletionState::MENU_WITH_PERFECT_MATCH
872
- is_menu = true
873
- else
874
- is_menu = false
875
- end
876
- result = complete_internal_proc(list, is_menu)
877
- if @completion_state == CompletionState::MENU_WITH_PERFECT_MATCH
838
+ if @dig_perfect_match_proc
839
+ @dig_perfect_match_proc.call(@perfect_matched)
840
+ return
841
+ end
842
+ when CompletionState::MENU
843
+ menu(candidates)
844
+ return
845
+ when CompletionState::MENU_WITH_PERFECT_MATCH
846
+ menu(candidates)
878
847
  @completion_state = CompletionState::PERFECT_MATCH
848
+ return
879
849
  end
880
- return if result.nil?
881
- target, preposing, completed, postposing = result
882
- return if completed.nil?
883
- if target <= completed and (@completion_state == CompletionState::COMPLETION)
884
- if list.include?(completed)
885
- if list.one?
886
- @completion_state = CompletionState::PERFECT_MATCH
887
- else
888
- @completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
889
- perform_completion(list, true) if @config.show_all_if_ambiguous
890
- end
891
- @perfect_matched = completed
850
+
851
+ completed = Reline::Unicode.common_prefix(candidates, ignore_case: @config.completion_ignore_case)
852
+ return if completed.empty?
853
+
854
+ append_character = ''
855
+ if candidates.include?(completed)
856
+ if candidates.one?
857
+ append_character = quote || completion_append_character.to_s
858
+ @completion_state = CompletionState::PERFECT_MATCH
859
+ elsif @config.show_all_if_ambiguous
860
+ menu(candidates)
861
+ @completion_state = CompletionState::PERFECT_MATCH
892
862
  else
893
- @completion_state = CompletionState::MENU
894
- perform_completion(list, true) if @config.show_all_if_ambiguous
895
- end
896
- if not just_show_list and target < completed
897
- @buffer_of_lines[@line_index] = (preposing + completed + completion_append_character.to_s + postposing).split("\n")[@line_index] || String.new(encoding: @encoding)
898
- line_to_pointer = (preposing + completed + completion_append_character.to_s).split("\n")[@line_index] || String.new(encoding: @encoding)
899
- @byte_pointer = line_to_pointer.bytesize
863
+ @completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
900
864
  end
865
+ @perfect_matched = completed
866
+ else
867
+ @completion_state = CompletionState::MENU
868
+ menu(candidates) if @config.show_all_if_ambiguous
901
869
  end
870
+ @buffer_of_lines[@line_index] = (preposing + completed + append_character + postposing).split("\n")[@line_index] || String.new(encoding: encoding)
871
+ line_to_pointer = (preposing + completed + append_character).split("\n")[@line_index] || String.new(encoding: encoding)
872
+ @byte_pointer = line_to_pointer.bytesize
902
873
  end
903
874
 
904
875
  def dialog_proc_scope_completion_journey_data
@@ -927,8 +898,8 @@ class Reline::LineEditor
927
898
  end
928
899
 
929
900
  private def retrieve_completion_journey_state
930
- preposing, target, postposing = retrieve_completion_block
931
- list = call_completion_proc
901
+ preposing, target, postposing, quote = retrieve_completion_block
902
+ list = call_completion_proc(preposing, target, postposing, quote)
932
903
  return unless list.is_a?(Array)
933
904
 
934
905
  candidates = list.select{ |item| item.start_with?(target) }
@@ -941,28 +912,36 @@ class Reline::LineEditor
941
912
  )
942
913
  end
943
914
 
944
- private def run_for_operators(key, method_symbol, &block)
915
+ private def run_for_operators(key, method_symbol)
916
+ # Reject multibyte input (converted to ed_insert) in vi_command mode
917
+ return if method_symbol == :ed_insert && @config.editing_mode_is?(:vi_command) && !@waiting_proc
918
+
919
+ if ARGUMENT_DIGIT_METHODS.include?(method_symbol) && !@waiting_proc
920
+ wrap_method_call(method_symbol, key, false)
921
+ return
922
+ end
923
+
945
924
  if @vi_waiting_operator
946
- if VI_MOTIONS.include?(method_symbol)
925
+ if @waiting_proc || VI_MOTIONS.include?(method_symbol)
947
926
  old_byte_pointer = @byte_pointer
948
927
  @vi_arg = (@vi_arg || 1) * @vi_waiting_operator_arg
949
- block.(true)
928
+ wrap_method_call(method_symbol, key, true)
950
929
  unless @waiting_proc
951
930
  byte_pointer_diff = @byte_pointer - old_byte_pointer
952
931
  @byte_pointer = old_byte_pointer
953
- method_obj = method(@vi_waiting_operator)
954
- wrap_method_call(@vi_waiting_operator, method_obj, byte_pointer_diff)
932
+ __send__(@vi_waiting_operator, byte_pointer_diff)
955
933
  cleanup_waiting
956
934
  end
957
935
  else
958
936
  # Ignores operator when not motion is given.
959
- block.(false)
937
+ wrap_method_call(method_symbol, key, false)
960
938
  cleanup_waiting
961
939
  end
962
- @vi_arg = nil
963
940
  else
964
- block.(false)
941
+ wrap_method_call(method_symbol, key, false)
965
942
  end
943
+ @vi_arg = nil
944
+ @kill_ring.process
966
945
  end
967
946
 
968
947
  private def argumentable?(method_obj)
@@ -975,20 +954,23 @@ class Reline::LineEditor
975
954
  method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :inclusive }
976
955
  end
977
956
 
978
- def wrap_method_call(method_symbol, method_obj, key, with_operator = false)
979
- if @config.editing_mode_is?(:emacs, :vi_insert) and @vi_waiting_operator.nil?
980
- not_insertion = method_symbol != :ed_insert
981
- process_insert(force: not_insertion)
957
+ def wrap_method_call(method_symbol, key, with_operator)
958
+ if @waiting_proc
959
+ @waiting_proc.call(key)
960
+ return
982
961
  end
962
+
963
+ return unless respond_to?(method_symbol, true)
964
+ method_obj = method(method_symbol)
983
965
  if @vi_arg and argumentable?(method_obj)
984
- if with_operator and inclusive?(method_obj)
985
- method_obj.(key, arg: @vi_arg, inclusive: true)
966
+ if inclusive?(method_obj)
967
+ method_obj.(key, arg: @vi_arg, inclusive: with_operator)
986
968
  else
987
969
  method_obj.(key, arg: @vi_arg)
988
970
  end
989
971
  else
990
- if with_operator and inclusive?(method_obj)
991
- method_obj.(key, inclusive: true)
972
+ if inclusive?(method_obj)
973
+ method_obj.(key, inclusive: with_operator)
992
974
  else
993
975
  method_obj.(key)
994
976
  end
@@ -1003,90 +985,20 @@ class Reline::LineEditor
1003
985
  @drop_terminate_spaces = false
1004
986
  end
1005
987
 
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
- method_obj = method(@vi_waiting_operator)
1016
- wrap_method_call(@vi_waiting_operator, method_obj, byte_pointer_diff)
1017
- cleanup_waiting
1018
- end
1019
- @kill_ring.process
1020
- return
1021
- end
988
+ ARGUMENT_DIGIT_METHODS = %i[ed_digit vi_zero ed_argument_digit]
989
+ VI_WAITING_ACCEPT_METHODS = %i[vi_change_meta vi_delete_meta vi_yank ed_insert ed_argument_digit]
1022
990
 
1023
- if method_symbol and respond_to?(method_symbol, true)
1024
- method_obj = method(method_symbol)
991
+ private def process_key(key, method_symbol)
992
+ if @waiting_proc
993
+ cleanup_waiting unless key.size == 1
1025
994
  end
1026
- if method_symbol and key.is_a?(Symbol)
1027
- if @vi_arg and argumentable?(method_obj)
1028
- run_for_operators(key, method_symbol) do |with_operator|
1029
- wrap_method_call(method_symbol, method_obj, key, with_operator)
1030
- end
1031
- else
1032
- wrap_method_call(method_symbol, method_obj, key) if method_obj
1033
- end
1034
- @kill_ring.process
1035
- if @vi_arg
1036
- @vi_arg = nil
1037
- end
1038
- elsif @vi_arg
1039
- if key.chr =~ /[0-9]/
1040
- ed_argument_digit(key)
1041
- else
1042
- if argumentable?(method_obj)
1043
- run_for_operators(key, method_symbol) do |with_operator|
1044
- wrap_method_call(method_symbol, method_obj, key, with_operator)
1045
- end
1046
- elsif method_obj
1047
- wrap_method_call(method_symbol, method_obj, key)
1048
- else
1049
- ed_insert(key) unless @config.editing_mode_is?(:vi_command)
1050
- end
1051
- @kill_ring.process
1052
- if @vi_arg
1053
- @vi_arg = nil
1054
- end
1055
- end
1056
- elsif method_obj
1057
- if method_symbol == :ed_argument_digit
1058
- wrap_method_call(method_symbol, method_obj, key)
1059
- else
1060
- run_for_operators(key, method_symbol) do |with_operator|
1061
- wrap_method_call(method_symbol, method_obj, key, with_operator)
1062
- end
1063
- end
1064
- @kill_ring.process
1065
- else
1066
- ed_insert(key) unless @config.editing_mode_is?(:vi_command)
995
+ if @vi_waiting_operator
996
+ cleanup_waiting unless VI_WAITING_ACCEPT_METHODS.include?(method_symbol) || VI_MOTIONS.include?(method_symbol)
1067
997
  end
1068
- end
1069
998
 
1070
- private def normal_char(key)
1071
- @multibyte_buffer << key.combined_char
1072
- if @multibyte_buffer.size > 1
1073
- if @multibyte_buffer.dup.force_encoding(@encoding).valid_encoding?
1074
- process_key(@multibyte_buffer.dup.force_encoding(@encoding), nil)
1075
- @multibyte_buffer.clear
1076
- else
1077
- # invalid
1078
- return
1079
- end
1080
- else # single byte
1081
- return if key.char >= 128 # maybe, first byte of multi byte
1082
- method_symbol = @config.editing_mode.get_method(key.combined_char)
1083
- process_key(key.combined_char, method_symbol)
1084
- @multibyte_buffer.clear
1085
- end
1086
- if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
1087
- byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer)
1088
- @byte_pointer -= byte_size
1089
- end
999
+ process_insert(force: method_symbol != :ed_insert)
1000
+
1001
+ run_for_operators(key, method_symbol)
1090
1002
  end
1091
1003
 
1092
1004
  def update(key)
@@ -1100,25 +1012,22 @@ class Reline::LineEditor
1100
1012
  end
1101
1013
 
1102
1014
  def input_key(key)
1103
- save_old_buffer
1015
+ old_buffer_of_lines = @buffer_of_lines.dup
1104
1016
  @config.reset_oneshot_key_bindings
1105
- @dialogs.each do |dialog|
1106
- if key.char.instance_of?(Symbol) and key.char == dialog.name
1107
- return
1108
- end
1109
- end
1110
1017
  if key.char.nil?
1111
1018
  process_insert(force: true)
1112
1019
  @eof = buffer_empty?
1113
1020
  finish
1114
1021
  return
1115
1022
  end
1023
+ return if @dialogs.any? { |dialog| dialog.name == key.method_symbol }
1024
+
1116
1025
  @completion_occurs = false
1117
1026
 
1118
- if key.char.is_a?(Symbol)
1119
- process_key(key.char, key.char)
1120
- else
1121
- normal_char(key)
1027
+ process_key(key.char, key.method_symbol)
1028
+ if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
1029
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer)
1030
+ @byte_pointer -= byte_size
1122
1031
  end
1123
1032
 
1124
1033
  @prev_action_state, @next_action_state = @next_action_state, NullActionState
@@ -1128,15 +1037,16 @@ class Reline::LineEditor
1128
1037
  @completion_journey_state = nil
1129
1038
  end
1130
1039
 
1131
- push_input_lines unless @undoing
1132
- @undoing = false
1040
+ modified = old_buffer_of_lines != @buffer_of_lines
1041
+
1042
+ push_undo_redo(modified) unless @restoring
1043
+ @restoring = false
1133
1044
 
1134
1045
  if @in_pasting
1135
1046
  clear_dialogs
1136
1047
  return
1137
1048
  end
1138
1049
 
1139
- modified = @old_buffer_of_lines != @buffer_of_lines
1140
1050
  if !@completion_occurs && modified && !@config.disable_completion && @config.autocompletion
1141
1051
  # Auto complete starts only when edited
1142
1052
  process_insert(force: true)
@@ -1145,26 +1055,17 @@ class Reline::LineEditor
1145
1055
  modified
1146
1056
  end
1147
1057
 
1148
- def save_old_buffer
1149
- @old_buffer_of_lines = @buffer_of_lines.dup
1150
- end
1151
-
1152
- def push_input_lines
1153
- if @old_buffer_of_lines == @buffer_of_lines
1154
- @input_lines[@input_lines_position] = [@buffer_of_lines.dup, @byte_pointer, @line_index]
1058
+ MAX_UNDO_REDO_HISTORY_SIZE = 100
1059
+ def push_undo_redo(modified)
1060
+ if modified
1061
+ @undo_redo_history = @undo_redo_history[0..@undo_redo_index]
1062
+ @undo_redo_history.push([@buffer_of_lines.dup, @byte_pointer, @line_index])
1063
+ if @undo_redo_history.size > MAX_UNDO_REDO_HISTORY_SIZE
1064
+ @undo_redo_history.shift
1065
+ end
1066
+ @undo_redo_index = @undo_redo_history.size - 1
1155
1067
  else
1156
- @input_lines = @input_lines[0..@input_lines_position]
1157
- @input_lines_position += 1
1158
- @input_lines.push([@buffer_of_lines.dup, @byte_pointer, @line_index])
1159
- end
1160
- trim_input_lines
1161
- end
1162
-
1163
- MAX_INPUT_LINES = 100
1164
- def trim_input_lines
1165
- if @input_lines.size > MAX_INPUT_LINES
1166
- @input_lines.shift
1167
- @input_lines_position -= 1
1068
+ @undo_redo_history[@undo_redo_index] = [@buffer_of_lines.dup, @byte_pointer, @line_index]
1168
1069
  end
1169
1070
  end
1170
1071
 
@@ -1178,9 +1079,8 @@ class Reline::LineEditor
1178
1079
  end
1179
1080
  end
1180
1081
 
1181
- def call_completion_proc
1182
- result = retrieve_completion_block(true)
1183
- pre, target, post = result
1082
+ def call_completion_proc(pre, target, post, quote)
1083
+ Reline.core.instance_variable_set(:@completion_quote_character, quote)
1184
1084
  result = call_completion_proc_with_checking_args(pre, target, post)
1185
1085
  Reline.core.instance_variable_set(:@completion_quote_character, nil)
1186
1086
  result
@@ -1244,84 +1144,32 @@ class Reline::LineEditor
1244
1144
  process_auto_indent
1245
1145
  end
1246
1146
 
1247
- def set_current_lines(lines, byte_pointer = nil, line_index = 0)
1248
- cursor = current_byte_pointer_cursor
1249
- @buffer_of_lines = lines
1250
- @line_index = line_index
1251
- if byte_pointer
1252
- @byte_pointer = byte_pointer
1253
- else
1254
- calculate_nearest_cursor(cursor)
1255
- end
1256
- process_auto_indent
1257
- end
1258
-
1259
- def retrieve_completion_block(set_completion_quote_character = false)
1260
- if Reline.completer_word_break_characters.empty?
1261
- word_break_regexp = nil
1262
- else
1263
- word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
1264
- end
1265
- if Reline.completer_quote_characters.empty?
1266
- quote_characters_regexp = nil
1267
- else
1268
- quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
1269
- end
1270
- before = current_line.byteslice(0, @byte_pointer)
1271
- rest = nil
1272
- break_pointer = nil
1147
+ def retrieve_completion_block
1148
+ quote_characters = Reline.completer_quote_characters
1149
+ before = current_line.byteslice(0, @byte_pointer).grapheme_clusters
1273
1150
  quote = nil
1274
- closing_quote = nil
1275
- escaped_quote = nil
1276
- i = 0
1277
- while i < @byte_pointer do
1278
- slice = current_line.byteslice(i, @byte_pointer - i)
1279
- unless slice.valid_encoding?
1280
- i += 1
1281
- next
1282
- end
1283
- if quote and slice.start_with?(closing_quote)
1284
- quote = nil
1285
- i += 1
1286
- rest = nil
1287
- elsif quote and slice.start_with?(escaped_quote)
1288
- # skip
1289
- i += 2
1290
- elsif quote_characters_regexp and slice =~ quote_characters_regexp # find new "
1291
- rest = $'
1292
- quote = $&
1293
- closing_quote = /(?!\\)#{Regexp.escape(quote)}/
1294
- escaped_quote = /\\#{Regexp.escape(quote)}/
1295
- i += 1
1296
- break_pointer = i - 1
1297
- elsif word_break_regexp and not quote and slice =~ word_break_regexp
1298
- rest = $'
1299
- i += 1
1300
- before = current_line.byteslice(i, @byte_pointer - i)
1301
- break_pointer = i
1302
- else
1303
- i += 1
1304
- end
1305
- end
1306
- postposing = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
1307
- if rest
1308
- preposing = current_line.byteslice(0, break_pointer)
1309
- target = rest
1310
- if set_completion_quote_character and quote
1311
- Reline.core.instance_variable_set(:@completion_quote_character, quote)
1312
- if postposing !~ /(?!\\)#{Regexp.escape(quote)}/ # closing quote
1313
- insert_text(quote)
1151
+ # Calculate closing quote when cursor is at the end of the line
1152
+ if current_line.bytesize == @byte_pointer && !quote_characters.empty?
1153
+ escaped = false
1154
+ before.each do |c|
1155
+ if escaped
1156
+ escaped = false
1157
+ next
1158
+ elsif c == '\\'
1159
+ escaped = true
1160
+ elsif quote
1161
+ quote = nil if c == quote
1162
+ elsif quote_characters.include?(c)
1163
+ quote = c
1314
1164
  end
1315
1165
  end
1316
- else
1317
- preposing = ''
1318
- if break_pointer
1319
- preposing = current_line.byteslice(0, break_pointer)
1320
- else
1321
- preposing = ''
1322
- end
1323
- target = before
1324
1166
  end
1167
+
1168
+ word_break_characters = quote_characters + Reline.completer_word_break_characters
1169
+ break_index = before.rindex { |c| word_break_characters.include?(c) || quote_characters.include?(c) } || -1
1170
+ preposing = before.take(break_index + 1).join
1171
+ target = before.drop(break_index + 1).join
1172
+ postposing = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
1325
1173
  lines = whole_lines
1326
1174
  if @line_index > 0
1327
1175
  preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
@@ -1329,7 +1177,7 @@ class Reline::LineEditor
1329
1177
  if (lines.size - 1) > @line_index
1330
1178
  postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
1331
1179
  end
1332
- [preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
1180
+ [preposing.encode(encoding), target.encode(encoding), postposing.encode(encoding), quote&.encode(encoding)]
1333
1181
  end
1334
1182
 
1335
1183
  def confirm_multiline_termination
@@ -1337,16 +1185,14 @@ class Reline::LineEditor
1337
1185
  @confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
1338
1186
  end
1339
1187
 
1340
- def insert_pasted_text(text)
1341
- save_old_buffer
1188
+ def insert_multiline_text(text)
1342
1189
  pre = @buffer_of_lines[@line_index].byteslice(0, @byte_pointer)
1343
1190
  post = @buffer_of_lines[@line_index].byteslice(@byte_pointer..)
1344
- lines = (pre + text.gsub(/\r\n?/, "\n") + post).split("\n", -1)
1191
+ lines = (pre + Reline::Unicode.safe_encode(text, encoding).gsub(/\r\n?/, "\n") + post).split("\n", -1)
1345
1192
  lines << '' if lines.empty?
1346
1193
  @buffer_of_lines[@line_index, 1] = lines
1347
1194
  @line_index += lines.size - 1
1348
1195
  @byte_pointer = @buffer_of_lines[@line_index].bytesize - post.bytesize
1349
- push_input_lines
1350
1196
  end
1351
1197
 
1352
1198
  def insert_text(text)
@@ -1386,7 +1232,7 @@ class Reline::LineEditor
1386
1232
  last += current_line.bytesize if last < 0
1387
1233
  first += current_line.bytesize if first < 0
1388
1234
  range = range.exclude_end? ? first...last : first..last
1389
- line = current_line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(@encoding)
1235
+ line = current_line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(encoding)
1390
1236
  set_current_line(line)
1391
1237
  else
1392
1238
  set_current_line(current_line.byteslice(0, start))
@@ -1460,10 +1306,11 @@ class Reline::LineEditor
1460
1306
  @completion_occurs = move_completed_list(:down)
1461
1307
  else
1462
1308
  @completion_journey_state = nil
1463
- result = call_completion_proc
1309
+ pre, target, post, quote = retrieve_completion_block
1310
+ result = call_completion_proc(pre, target, post, quote)
1464
1311
  if result.is_a?(Array)
1465
1312
  @completion_occurs = true
1466
- perform_completion(result, false)
1313
+ perform_completion(pre, target, post, quote, result)
1467
1314
  end
1468
1315
  end
1469
1316
  end
@@ -1511,21 +1358,11 @@ class Reline::LineEditor
1511
1358
  # digit or if the existing argument is already greater than a
1512
1359
  # million.
1513
1360
  # GNU Readline:: +self-insert+ (a, b, A, 1, !, …) Insert yourself.
1514
- private def ed_insert(key)
1515
- if key.instance_of?(String)
1516
- begin
1517
- key.encode(Encoding::UTF_8)
1518
- rescue Encoding::UndefinedConversionError
1519
- return
1520
- end
1521
- str = key
1522
- else
1523
- begin
1524
- key.chr.encode(Encoding::UTF_8)
1525
- rescue Encoding::UndefinedConversionError
1526
- return
1527
- end
1528
- str = key.chr
1361
+ private def ed_insert(str)
1362
+ begin
1363
+ str.encode(Encoding::UTF_8)
1364
+ rescue Encoding::UndefinedConversionError
1365
+ return
1529
1366
  end
1530
1367
  if @in_pasting
1531
1368
  @continuous_insertion_buffer << str
@@ -1536,24 +1373,26 @@ class Reline::LineEditor
1536
1373
 
1537
1374
  insert_text(str)
1538
1375
  end
1539
- alias_method :ed_digit, :ed_insert
1540
1376
  alias_method :self_insert, :ed_insert
1541
1377
 
1542
- private def ed_quoted_insert(str, arg: 1)
1543
- @waiting_proc = proc { |key|
1544
- arg.times do
1545
- if key == "\C-j".ord or key == "\C-m".ord
1546
- key_newline(key)
1547
- elsif key == 0
1548
- # Ignore NUL.
1549
- else
1550
- ed_insert(key)
1551
- end
1378
+ private def ed_digit(key)
1379
+ if @vi_arg
1380
+ ed_argument_digit(key)
1381
+ else
1382
+ ed_insert(key)
1383
+ end
1384
+ end
1385
+
1386
+ private def insert_raw_char(str, arg: 1)
1387
+ arg.times do
1388
+ if str == "\C-j" or str == "\C-m"
1389
+ key_newline(str)
1390
+ elsif str != "\0"
1391
+ # Ignore NUL.
1392
+ ed_insert(str)
1552
1393
  end
1553
- @waiting_proc = nil
1554
- }
1394
+ end
1555
1395
  end
1556
- alias_method :quoted_insert, :ed_quoted_insert
1557
1396
 
1558
1397
  private def ed_next_char(key, arg: 1)
1559
1398
  byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
@@ -1582,14 +1421,21 @@ class Reline::LineEditor
1582
1421
  alias_method :backward_char, :ed_prev_char
1583
1422
 
1584
1423
  private def vi_first_print(key)
1585
- @byte_pointer, = Reline::Unicode.vi_first_print(current_line)
1424
+ @byte_pointer = Reline::Unicode.vi_first_print(current_line)
1586
1425
  end
1587
1426
 
1588
1427
  private def ed_move_to_beg(key)
1589
1428
  @byte_pointer = 0
1590
1429
  end
1591
1430
  alias_method :beginning_of_line, :ed_move_to_beg
1592
- alias_method :vi_zero, :ed_move_to_beg
1431
+
1432
+ private def vi_zero(key)
1433
+ if @vi_arg
1434
+ ed_argument_digit(key)
1435
+ else
1436
+ ed_move_to_beg(key)
1437
+ end
1438
+ end
1593
1439
 
1594
1440
  private def ed_move_to_end(key)
1595
1441
  @byte_pointer = current_line.bytesize
@@ -1597,27 +1443,22 @@ class Reline::LineEditor
1597
1443
  alias_method :end_of_line, :ed_move_to_end
1598
1444
 
1599
1445
  private def generate_searcher(search_key)
1600
- search_word = String.new(encoding: @encoding)
1601
- multibyte_buf = String.new(encoding: 'ASCII-8BIT')
1446
+ search_word = String.new(encoding: encoding)
1602
1447
  hit_pointer = nil
1603
1448
  lambda do |key|
1604
1449
  search_again = false
1605
1450
  case key
1606
- when "\C-h".ord, "\C-?".ord
1451
+ when "\C-h", "\C-?"
1607
1452
  grapheme_clusters = search_word.grapheme_clusters
1608
1453
  if grapheme_clusters.size > 0
1609
1454
  grapheme_clusters.pop
1610
1455
  search_word = grapheme_clusters.join
1611
1456
  end
1612
- when "\C-r".ord, "\C-s".ord
1457
+ when "\C-r", "\C-s"
1613
1458
  search_again = true if search_key == key
1614
1459
  search_key = key
1615
1460
  else
1616
- multibyte_buf << key
1617
- if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
1618
- search_word << multibyte_buf.dup.force_encoding(@encoding)
1619
- multibyte_buf.clear
1620
- end
1461
+ search_word << key
1621
1462
  end
1622
1463
  hit = nil
1623
1464
  if not search_word.empty? and @line_backup_in_history&.include?(search_word)
@@ -1630,10 +1471,10 @@ class Reline::LineEditor
1630
1471
  end
1631
1472
  if @history_pointer
1632
1473
  case search_key
1633
- when "\C-r".ord
1474
+ when "\C-r"
1634
1475
  history_pointer_base = 0
1635
1476
  history = Reline::HISTORY[0..(@history_pointer - 1)]
1636
- when "\C-s".ord
1477
+ when "\C-s"
1637
1478
  history_pointer_base = @history_pointer + 1
1638
1479
  history = Reline::HISTORY[(@history_pointer + 1)..-1]
1639
1480
  end
@@ -1643,10 +1484,10 @@ class Reline::LineEditor
1643
1484
  end
1644
1485
  elsif @history_pointer
1645
1486
  case search_key
1646
- when "\C-r".ord
1487
+ when "\C-r"
1647
1488
  history_pointer_base = 0
1648
1489
  history = Reline::HISTORY[0..@history_pointer]
1649
- when "\C-s".ord
1490
+ when "\C-s"
1650
1491
  history_pointer_base = @history_pointer
1651
1492
  history = Reline::HISTORY[@history_pointer..-1]
1652
1493
  end
@@ -1655,11 +1496,11 @@ class Reline::LineEditor
1655
1496
  history = Reline::HISTORY
1656
1497
  end
1657
1498
  case search_key
1658
- when "\C-r".ord
1499
+ when "\C-r"
1659
1500
  hit_index = history.rindex { |item|
1660
1501
  item.include?(search_word)
1661
1502
  }
1662
- when "\C-s".ord
1503
+ when "\C-s"
1663
1504
  hit_index = history.index { |item|
1664
1505
  item.include?(search_word)
1665
1506
  }
@@ -1670,9 +1511,9 @@ class Reline::LineEditor
1670
1511
  end
1671
1512
  end
1672
1513
  case search_key
1673
- when "\C-r".ord
1514
+ when "\C-r"
1674
1515
  prompt_name = 'reverse-i-search'
1675
- when "\C-s".ord
1516
+ when "\C-s"
1676
1517
  prompt_name = 'i-search'
1677
1518
  end
1678
1519
  prompt_name = "failed #{prompt_name}" unless hit
@@ -1681,57 +1522,28 @@ class Reline::LineEditor
1681
1522
  end
1682
1523
 
1683
1524
  private def incremental_search_history(key)
1684
- unless @history_pointer
1685
- @line_backup_in_history = whole_buffer
1686
- end
1525
+ backup = @buffer_of_lines.dup, @line_index, @byte_pointer, @history_pointer, @line_backup_in_history
1687
1526
  searcher = generate_searcher(key)
1688
1527
  @searching_prompt = "(reverse-i-search)`': "
1689
- termination_keys = ["\C-j".ord]
1690
- termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators
1528
+ termination_keys = ["\C-j"]
1529
+ termination_keys.concat(@config.isearch_terminators.chars) if @config.isearch_terminators
1691
1530
  @waiting_proc = ->(k) {
1692
- case k
1693
- when *termination_keys
1694
- if @history_pointer
1695
- buffer = Reline::HISTORY[@history_pointer]
1696
- else
1697
- buffer = @line_backup_in_history
1698
- end
1699
- @buffer_of_lines = buffer.split("\n")
1700
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1701
- @line_index = @buffer_of_lines.size - 1
1531
+ if k == "\C-g"
1532
+ # cancel search and restore buffer
1533
+ @buffer_of_lines, @line_index, @byte_pointer, @history_pointer, @line_backup_in_history = backup
1702
1534
  @searching_prompt = nil
1703
1535
  @waiting_proc = nil
1704
- @byte_pointer = 0
1705
- when "\C-g".ord
1706
- @buffer_of_lines = @line_backup_in_history.split("\n")
1707
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1708
- @line_index = @buffer_of_lines.size - 1
1709
- move_history(nil, line: :end, cursor: :end, save_buffer: false)
1536
+ elsif !termination_keys.include?(k) && (k.match?(/[[:print:]]/) || k == "\C-h" || k == "\C-?" || k == "\C-r" || k == "\C-s")
1537
+ search_word, prompt_name, hit_pointer = searcher.call(k)
1538
+ Reline.last_incremental_search = search_word
1539
+ @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
1540
+ @searching_prompt += ': ' unless @is_multiline
1541
+ move_history(hit_pointer, line: :end, cursor: :end) if hit_pointer
1542
+ else
1543
+ # terminaton_keys and other keys will terminalte
1544
+ move_history(@history_pointer, line: :end, cursor: :start)
1710
1545
  @searching_prompt = nil
1711
1546
  @waiting_proc = nil
1712
- @byte_pointer = 0
1713
- else
1714
- chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
1715
- if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == "\C-?".ord or k == "\C-r".ord or k == "\C-s".ord
1716
- search_word, prompt_name, hit_pointer = searcher.call(k)
1717
- Reline.last_incremental_search = search_word
1718
- @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
1719
- @searching_prompt += ': ' unless @is_multiline
1720
- move_history(hit_pointer, line: :end, cursor: :end, save_buffer: false) if hit_pointer
1721
- else
1722
- if @history_pointer
1723
- line = Reline::HISTORY[@history_pointer]
1724
- else
1725
- line = @line_backup_in_history
1726
- end
1727
- @line_backup_in_history = whole_buffer
1728
- @buffer_of_lines = line.split("\n")
1729
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1730
- @line_index = @buffer_of_lines.size - 1
1731
- @searching_prompt = nil
1732
- @waiting_proc = nil
1733
- @byte_pointer = 0
1734
- end
1735
1547
  end
1736
1548
  }
1737
1549
  end
@@ -1786,14 +1598,14 @@ class Reline::LineEditor
1786
1598
  end
1787
1599
  alias_method :history_search_forward, :ed_search_next_history
1788
1600
 
1789
- private def move_history(history_pointer, line:, cursor:, save_buffer: true)
1601
+ private def move_history(history_pointer, line:, cursor:)
1790
1602
  history_pointer ||= Reline::HISTORY.size
1791
1603
  return if history_pointer < 0 || history_pointer > Reline::HISTORY.size
1792
1604
  old_history_pointer = @history_pointer || Reline::HISTORY.size
1793
1605
  if old_history_pointer == Reline::HISTORY.size
1794
- @line_backup_in_history = save_buffer ? whole_buffer : ''
1606
+ @line_backup_in_history = whole_buffer
1795
1607
  else
1796
- Reline::HISTORY[old_history_pointer] = whole_buffer if save_buffer
1608
+ Reline::HISTORY[old_history_pointer] = whole_buffer
1797
1609
  end
1798
1610
  if history_pointer == Reline::HISTORY.size
1799
1611
  buf = @line_backup_in_history
@@ -1803,7 +1615,7 @@ class Reline::LineEditor
1803
1615
  @history_pointer = history_pointer
1804
1616
  end
1805
1617
  @buffer_of_lines = buf.split("\n")
1806
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1618
+ @buffer_of_lines = [String.new(encoding: encoding)] if @buffer_of_lines.empty?
1807
1619
  @line_index = line == :start ? 0 : line == :end ? @buffer_of_lines.size - 1 : line
1808
1620
  @byte_pointer = cursor == :start ? 0 : cursor == :end ? current_line.bytesize : cursor
1809
1621
  end
@@ -1853,17 +1665,10 @@ class Reline::LineEditor
1853
1665
  finish
1854
1666
  end
1855
1667
  else
1856
- if @line_index == (@buffer_of_lines.size - 1)
1857
- if confirm_multiline_termination
1858
- finish
1859
- else
1860
- key_newline(key)
1861
- end
1862
- else
1863
- # should check confirm_multiline_termination to finish?
1864
- @line_index = @buffer_of_lines.size - 1
1865
- @byte_pointer = current_line.bytesize
1668
+ if @line_index == @buffer_of_lines.size - 1 && confirm_multiline_termination
1866
1669
  finish
1670
+ else
1671
+ key_newline(key)
1867
1672
  end
1868
1673
  end
1869
1674
  else
@@ -1871,6 +1676,11 @@ class Reline::LineEditor
1871
1676
  end
1872
1677
  end
1873
1678
 
1679
+ private def ed_force_submit(_key)
1680
+ process_insert(force: true)
1681
+ finish
1682
+ end
1683
+
1874
1684
  private def em_delete_prev_char(key, arg: 1)
1875
1685
  arg.times do
1876
1686
  if @byte_pointer == 0 and @line_index > 0
@@ -1937,7 +1747,7 @@ class Reline::LineEditor
1937
1747
  alias_method :kill_whole_line, :em_kill_line
1938
1748
 
1939
1749
  private def em_delete(key)
1940
- if buffer_empty? and key == "\C-d".ord
1750
+ if buffer_empty? and key == "\C-d"
1941
1751
  @eof = true
1942
1752
  finish
1943
1753
  elsif @byte_pointer < current_line.bytesize
@@ -1955,9 +1765,11 @@ class Reline::LineEditor
1955
1765
  if current_line.empty? or @byte_pointer < current_line.bytesize
1956
1766
  em_delete(key)
1957
1767
  elsif !@config.autocompletion # show completed list
1958
- result = call_completion_proc
1768
+ pre, target, post, quote = retrieve_completion_block
1769
+ result = call_completion_proc(pre, target, post, quote)
1959
1770
  if result.is_a?(Array)
1960
- perform_completion(result, true)
1771
+ candidates = filter_normalize_candidates(target, result)
1772
+ menu(candidates)
1961
1773
  end
1962
1774
  end
1963
1775
  end
@@ -1982,15 +1794,14 @@ class Reline::LineEditor
1982
1794
  private def ed_clear_screen(key)
1983
1795
  Reline::IOGate.clear_screen
1984
1796
  @screen_size = Reline::IOGate.get_screen_size
1985
- @rendered_screen.lines = []
1986
1797
  @rendered_screen.base_y = 0
1987
- @rendered_screen.cursor_y = 0
1798
+ clear_rendered_screen_cache
1988
1799
  end
1989
1800
  alias_method :clear_screen, :ed_clear_screen
1990
1801
 
1991
1802
  private def em_next_word(key)
1992
1803
  if current_line.bytesize > @byte_pointer
1993
- byte_size, _ = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1804
+ byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1994
1805
  @byte_pointer += byte_size
1995
1806
  end
1996
1807
  end
@@ -1998,7 +1809,7 @@ class Reline::LineEditor
1998
1809
 
1999
1810
  private def ed_prev_word(key)
2000
1811
  if @byte_pointer > 0
2001
- byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
1812
+ byte_size = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
2002
1813
  @byte_pointer -= byte_size
2003
1814
  end
2004
1815
  end
@@ -2006,7 +1817,7 @@ class Reline::LineEditor
2006
1817
 
2007
1818
  private def em_delete_next_word(key)
2008
1819
  if current_line.bytesize > @byte_pointer
2009
- byte_size, _ = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1820
+ byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
2010
1821
  line, word = byteslice!(current_line, @byte_pointer, byte_size)
2011
1822
  set_current_line(line)
2012
1823
  @kill_ring.append(word)
@@ -2016,7 +1827,7 @@ class Reline::LineEditor
2016
1827
 
2017
1828
  private def ed_delete_prev_word(key)
2018
1829
  if @byte_pointer > 0
2019
- byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
1830
+ byte_size = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
2020
1831
  line, word = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
2021
1832
  set_current_line(line, @byte_pointer - byte_size)
2022
1833
  @kill_ring.append(word, true)
@@ -2056,7 +1867,7 @@ class Reline::LineEditor
2056
1867
 
2057
1868
  private def em_capitol_case(key)
2058
1869
  if current_line.bytesize > @byte_pointer
2059
- byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(current_line, @byte_pointer)
1870
+ byte_size, new_str = Reline::Unicode.em_forward_word_with_capitalization(current_line, @byte_pointer)
2060
1871
  before = current_line.byteslice(0, @byte_pointer)
2061
1872
  after = current_line.byteslice((@byte_pointer + byte_size)..-1)
2062
1873
  set_current_line(before + new_str + after, @byte_pointer + new_str.bytesize)
@@ -2066,7 +1877,7 @@ class Reline::LineEditor
2066
1877
 
2067
1878
  private def em_lower_case(key)
2068
1879
  if current_line.bytesize > @byte_pointer
2069
- byte_size, = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1880
+ byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
2070
1881
  part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2071
1882
  mbchar =~ /[A-Z]/ ? mbchar.downcase : mbchar
2072
1883
  }.join
@@ -2079,7 +1890,7 @@ class Reline::LineEditor
2079
1890
 
2080
1891
  private def em_upper_case(key)
2081
1892
  if current_line.bytesize > @byte_pointer
2082
- byte_size, = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1893
+ byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
2083
1894
  part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2084
1895
  mbchar =~ /[a-z]/ ? mbchar.upcase : mbchar
2085
1896
  }.join
@@ -2092,7 +1903,7 @@ class Reline::LineEditor
2092
1903
 
2093
1904
  private def em_kill_region(key)
2094
1905
  if @byte_pointer > 0
2095
- byte_size, _ = Reline::Unicode.em_big_backward_word(current_line, @byte_pointer)
1906
+ byte_size = Reline::Unicode.em_big_backward_word(current_line, @byte_pointer)
2096
1907
  line, deleted = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
2097
1908
  set_current_line(line, @byte_pointer - byte_size)
2098
1909
  @kill_ring.append(deleted, true)
@@ -2123,7 +1934,7 @@ class Reline::LineEditor
2123
1934
 
2124
1935
  private def vi_next_word(key, arg: 1)
2125
1936
  if current_line.bytesize > @byte_pointer
2126
- byte_size, _ = Reline::Unicode.vi_forward_word(current_line, @byte_pointer, @drop_terminate_spaces)
1937
+ byte_size = Reline::Unicode.vi_forward_word(current_line, @byte_pointer, @drop_terminate_spaces)
2127
1938
  @byte_pointer += byte_size
2128
1939
  end
2129
1940
  arg -= 1
@@ -2132,7 +1943,7 @@ class Reline::LineEditor
2132
1943
 
2133
1944
  private def vi_prev_word(key, arg: 1)
2134
1945
  if @byte_pointer > 0
2135
- byte_size, _ = Reline::Unicode.vi_backward_word(current_line, @byte_pointer)
1946
+ byte_size = Reline::Unicode.vi_backward_word(current_line, @byte_pointer)
2136
1947
  @byte_pointer -= byte_size
2137
1948
  end
2138
1949
  arg -= 1
@@ -2141,7 +1952,7 @@ class Reline::LineEditor
2141
1952
 
2142
1953
  private def vi_end_word(key, arg: 1, inclusive: false)
2143
1954
  if current_line.bytesize > @byte_pointer
2144
- byte_size, _ = Reline::Unicode.vi_forward_end_word(current_line, @byte_pointer)
1955
+ byte_size = Reline::Unicode.vi_forward_end_word(current_line, @byte_pointer)
2145
1956
  @byte_pointer += byte_size
2146
1957
  end
2147
1958
  arg -= 1
@@ -2156,7 +1967,7 @@ class Reline::LineEditor
2156
1967
 
2157
1968
  private def vi_next_big_word(key, arg: 1)
2158
1969
  if current_line.bytesize > @byte_pointer
2159
- byte_size, _ = Reline::Unicode.vi_big_forward_word(current_line, @byte_pointer)
1970
+ byte_size = Reline::Unicode.vi_big_forward_word(current_line, @byte_pointer)
2160
1971
  @byte_pointer += byte_size
2161
1972
  end
2162
1973
  arg -= 1
@@ -2165,7 +1976,7 @@ class Reline::LineEditor
2165
1976
 
2166
1977
  private def vi_prev_big_word(key, arg: 1)
2167
1978
  if @byte_pointer > 0
2168
- byte_size, _ = Reline::Unicode.vi_big_backward_word(current_line, @byte_pointer)
1979
+ byte_size = Reline::Unicode.vi_big_backward_word(current_line, @byte_pointer)
2169
1980
  @byte_pointer -= byte_size
2170
1981
  end
2171
1982
  arg -= 1
@@ -2174,7 +1985,7 @@ class Reline::LineEditor
2174
1985
 
2175
1986
  private def vi_end_big_word(key, arg: 1, inclusive: false)
2176
1987
  if current_line.bytesize > @byte_pointer
2177
- byte_size, _ = Reline::Unicode.vi_big_forward_end_word(current_line, @byte_pointer)
1988
+ byte_size = Reline::Unicode.vi_big_forward_end_word(current_line, @byte_pointer)
2178
1989
  @byte_pointer += byte_size
2179
1990
  end
2180
1991
  arg -= 1
@@ -2259,9 +2070,11 @@ class Reline::LineEditor
2259
2070
  line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff)
2260
2071
  elsif byte_pointer_diff < 0
2261
2072
  line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2073
+ else
2074
+ return
2262
2075
  end
2263
2076
  copy_for_vi(cut)
2264
- set_current_line(line || '', @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0))
2077
+ set_current_line(line, @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0))
2265
2078
  end
2266
2079
 
2267
2080
  private def vi_yank(key, arg: nil)
@@ -2280,6 +2093,8 @@ class Reline::LineEditor
2280
2093
  cut = current_line.byteslice(@byte_pointer, byte_pointer_diff)
2281
2094
  elsif byte_pointer_diff < 0
2282
2095
  cut = current_line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2096
+ else
2097
+ return
2283
2098
  end
2284
2099
  copy_for_vi(cut)
2285
2100
  end
@@ -2325,7 +2140,7 @@ class Reline::LineEditor
2325
2140
  }
2326
2141
  system("#{ENV['EDITOR']} #{path}")
2327
2142
  @buffer_of_lines = File.read(path).split("\n")
2328
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2143
+ @buffer_of_lines = [String.new(encoding: encoding)] if @buffer_of_lines.empty?
2329
2144
  @line_index = 0
2330
2145
  finish
2331
2146
  end
@@ -2350,20 +2165,9 @@ class Reline::LineEditor
2350
2165
  end
2351
2166
 
2352
2167
  private def ed_argument_digit(key)
2353
- if @vi_arg.nil?
2354
- if key.chr.to_i.zero?
2355
- if key.anybits?(0b10000000)
2356
- unescaped_key = key ^ 0b10000000
2357
- unless unescaped_key.chr.to_i.zero?
2358
- @vi_arg = unescaped_key.chr.to_i
2359
- end
2360
- end
2361
- else
2362
- @vi_arg = key.chr.to_i
2363
- end
2364
- else
2365
- @vi_arg = @vi_arg * 10 + key.chr.to_i
2366
- end
2168
+ # key is expected to be `ESC digit` or `digit`
2169
+ num = key[/\d/].to_i
2170
+ @vi_arg = (@vi_arg || 0) * 10 + num
2367
2171
  end
2368
2172
 
2369
2173
  private def vi_to_column(key, arg: 0)
@@ -2382,7 +2186,7 @@ class Reline::LineEditor
2382
2186
  before = current_line.byteslice(0, @byte_pointer)
2383
2187
  remaining_point = @byte_pointer + byte_size
2384
2188
  after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
2385
- set_current_line(before + k.chr + after)
2189
+ set_current_line(before + k + after)
2386
2190
  @waiting_proc = nil
2387
2191
  elsif arg > 1
2388
2192
  byte_size = 0
@@ -2392,7 +2196,7 @@ class Reline::LineEditor
2392
2196
  before = current_line.byteslice(0, @byte_pointer)
2393
2197
  remaining_point = @byte_pointer + byte_size
2394
2198
  after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
2395
- replaced = k.chr * arg
2199
+ replaced = k * arg
2396
2200
  set_current_line(before + replaced + after, @byte_pointer + replaced.bytesize)
2397
2201
  @waiting_proc = nil
2398
2202
  end
@@ -2408,11 +2212,6 @@ class Reline::LineEditor
2408
2212
  end
2409
2213
 
2410
2214
  private def search_next_char(key, arg, need_prev_char: false, inclusive: false)
2411
- if key.instance_of?(String)
2412
- inputed_char = key
2413
- else
2414
- inputed_char = key.chr
2415
- end
2416
2215
  prev_total = nil
2417
2216
  total = nil
2418
2217
  found = false
@@ -2423,7 +2222,7 @@ class Reline::LineEditor
2423
2222
  width = Reline::Unicode.get_mbchar_width(mbchar)
2424
2223
  total = [mbchar.bytesize, width]
2425
2224
  else
2426
- if inputed_char == mbchar
2225
+ if key == mbchar
2427
2226
  arg -= 1
2428
2227
  if arg.zero?
2429
2228
  found = true
@@ -2460,11 +2259,6 @@ class Reline::LineEditor
2460
2259
  end
2461
2260
 
2462
2261
  private def search_prev_char(key, arg, need_next_char = false)
2463
- if key.instance_of?(String)
2464
- inputed_char = key
2465
- else
2466
- inputed_char = key.chr
2467
- end
2468
2262
  prev_total = nil
2469
2263
  total = nil
2470
2264
  found = false
@@ -2475,7 +2269,7 @@ class Reline::LineEditor
2475
2269
  width = Reline::Unicode.get_mbchar_width(mbchar)
2476
2270
  total = [mbchar.bytesize, width]
2477
2271
  else
2478
- if inputed_char == mbchar
2272
+ if key == mbchar
2479
2273
  arg -= 1
2480
2274
  if arg.zero?
2481
2275
  found = true
@@ -2527,24 +2321,23 @@ class Reline::LineEditor
2527
2321
  @config.editing_mode = :vi_insert
2528
2322
  end
2529
2323
 
2530
- private def undo(_key)
2531
- @undoing = true
2324
+ private def move_undo_redo(direction)
2325
+ @restoring = true
2326
+ return unless (0..@undo_redo_history.size - 1).cover?(@undo_redo_index + direction)
2532
2327
 
2533
- return if @input_lines_position <= 0
2328
+ @undo_redo_index += direction
2329
+ buffer_of_lines, byte_pointer, line_index = @undo_redo_history[@undo_redo_index]
2330
+ @buffer_of_lines = buffer_of_lines.dup
2331
+ @line_index = line_index
2332
+ @byte_pointer = byte_pointer
2333
+ end
2534
2334
 
2535
- @input_lines_position -= 1
2536
- target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position]
2537
- set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y)
2335
+ private def undo(_key)
2336
+ move_undo_redo(-1)
2538
2337
  end
2539
2338
 
2540
2339
  private def redo(_key)
2541
- @undoing = true
2542
-
2543
- return if @input_lines_position >= @input_lines.size - 1
2544
-
2545
- @input_lines_position += 1
2546
- target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position]
2547
- set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y)
2340
+ move_undo_redo(+1)
2548
2341
  end
2549
2342
 
2550
2343
  private def prev_action_state_value(type)
@@ -2554,4 +2347,8 @@ class Reline::LineEditor
2554
2347
  private def set_next_action_state(type, value)
2555
2348
  @next_action_state = [type, value]
2556
2349
  end
2350
+
2351
+ private def re_read_init_file(_key)
2352
+ @config.reload
2353
+ end
2557
2354
  end