reline 0.5.10 → 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 = '▄'
@@ -219,10 +221,9 @@ class Reline::LineEditor
219
221
  @eof
220
222
  end
221
223
 
222
- def reset_variables(prompt = '', encoding:)
224
+ def reset_variables(prompt = '')
223
225
  @prompt = prompt.gsub("\n", "\\n")
224
226
  @mark_pointer = nil
225
- @encoding = encoding
226
227
  @is_multiline = false
227
228
  @finished = false
228
229
  @history_pointer = nil
@@ -239,7 +240,7 @@ class Reline::LineEditor
239
240
  @searching_prompt = nil
240
241
  @just_cursor_moving = false
241
242
  @eof = false
242
- @continuous_insertion_buffer = String.new(encoding: @encoding)
243
+ @continuous_insertion_buffer = String.new(encoding: encoding)
243
244
  @scroll_partial_screen = 0
244
245
  @drop_terminate_spaces = false
245
246
  @in_pasting = false
@@ -249,9 +250,9 @@ class Reline::LineEditor
249
250
  @resized = false
250
251
  @cache = {}
251
252
  @rendered_screen = RenderedScreen.new(base_y: 0, lines: [], cursor_y: 0)
252
- @input_lines = [[[""], 0, 0]]
253
- @input_lines_position = 0
254
- @undoing = false
253
+ @undo_redo_history = [[[""], 0, 0]]
254
+ @undo_redo_index = 0
255
+ @restoring = false
255
256
  @prev_action_state = NullActionState
256
257
  @next_action_state = NullActionState
257
258
  reset_line
@@ -259,11 +260,10 @@ class Reline::LineEditor
259
260
 
260
261
  def reset_line
261
262
  @byte_pointer = 0
262
- @buffer_of_lines = [String.new(encoding: @encoding)]
263
+ @buffer_of_lines = [String.new(encoding: encoding)]
263
264
  @line_index = 0
264
265
  @cache.clear
265
266
  @line_backup_in_history = nil
266
- @multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
267
267
  end
268
268
 
269
269
  def multiline_on
@@ -275,7 +275,7 @@ class Reline::LineEditor
275
275
  end
276
276
 
277
277
  private def insert_new_line(cursor_line, next_line)
278
- @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))
279
279
  @buffer_of_lines[@line_index] = cursor_line
280
280
  @line_index += 1
281
281
  @byte_pointer = 0
@@ -297,8 +297,8 @@ class Reline::LineEditor
297
297
  end
298
298
  end
299
299
 
300
- private def split_by_width(str, max_width, offset: 0)
301
- 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)
302
302
  end
303
303
 
304
304
  def current_byte_pointer_cursor
@@ -388,8 +388,8 @@ class Reline::LineEditor
388
388
  if (cached = cached_wraps[[prompt, line]])
389
389
  next cached
390
390
  end
391
- *wrapped_prompts, code_line_prompt = split_by_width(prompt, width).first.compact
392
- 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))
393
393
  wrapped_prompts.map { |p| [p, ''] } + [[code_line_prompt, wrapped_lines.first]] + wrapped_lines.drop(1).map { |c| ['', c] }
394
394
  end
395
395
  end
@@ -413,7 +413,7 @@ class Reline::LineEditor
413
413
  # do nothing
414
414
  elsif level == :blank
415
415
  Reline::IOGate.move_cursor_column base_x
416
- @output.write "#{Reline::IOGate.reset_color_sequence}#{' ' * width}"
416
+ Reline::IOGate.write "#{Reline::IOGate.reset_color_sequence}#{' ' * width}"
417
417
  else
418
418
  x, w, content = new_items[level]
419
419
  cover_begin = base_x != 0 && new_levels[base_x - 1] == level
@@ -423,7 +423,7 @@ class Reline::LineEditor
423
423
  content, pos = Reline::Unicode.take_mbchar_range(content, base_x - x, width, cover_begin: cover_begin, cover_end: cover_end, padding: true)
424
424
  end
425
425
  Reline::IOGate.move_cursor_column x + pos
426
- @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}"
427
427
  end
428
428
  base_x += width
429
429
  end
@@ -436,8 +436,8 @@ class Reline::LineEditor
436
436
  # Calculate cursor position in word wrapped content.
437
437
  def wrapped_cursor_position
438
438
  prompt_width = calculate_width(prompt_list[@line_index], true)
439
- line_before_cursor = whole_lines[@line_index].byteslice(0, @byte_pointer)
440
- 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)
441
441
  wrapped_cursor_y = wrapped_prompt_and_input_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1
442
442
  wrapped_cursor_x = calculate_width(wrapped_line_before_cursor.last)
443
443
  [wrapped_cursor_x, wrapped_cursor_y]
@@ -459,18 +459,23 @@ class Reline::LineEditor
459
459
  end
460
460
 
461
461
  def render_finished
462
- render_differential([], 0, 0)
463
- lines = @buffer_of_lines.size.times.map do |i|
464
- line = prompt_list[i] + modified_lines[i]
465
- wrapped_lines, = split_by_width(line, screen_width)
466
- 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
467
470
  end
468
- @output.puts lines.map { |l| "#{l}\r\n" }.join
469
471
  end
470
472
 
471
473
  def print_nomultiline_prompt
474
+ Reline::IOGate.disable_auto_linewrap(true) if Reline::IOGate.win?
472
475
  # Readline's test `TestRelineAsReadline#test_readline` requires first output to be prompt, not cursor reset escape sequence.
473
- @output.write @prompt if @prompt && !@is_multiline
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?
474
479
  end
475
480
 
476
481
  def render
@@ -499,13 +504,16 @@ class Reline::LineEditor
499
504
  end
500
505
  end
501
506
 
502
- render_differential new_lines, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top
507
+ Reline::IOGate.buffered_output do
508
+ render_differential new_lines, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top
509
+ end
503
510
  end
504
511
 
505
512
  # Reflects lines to be rendered and new cursor position to the screen
506
513
  # by calculating the difference from the previous render.
507
514
 
508
515
  private def render_differential(new_lines, new_cursor_x, new_cursor_y)
516
+ Reline::IOGate.disable_auto_linewrap(true) if Reline::IOGate.win?
509
517
  rendered_lines = @rendered_screen.lines
510
518
  cursor_y = @rendered_screen.cursor_y
511
519
  if new_lines != rendered_lines
@@ -534,8 +542,11 @@ class Reline::LineEditor
534
542
  Reline::IOGate.show_cursor
535
543
  end
536
544
  Reline::IOGate.move_cursor_column new_cursor_x
545
+ new_cursor_y = new_cursor_y.clamp(0, screen_height - 1)
537
546
  Reline::IOGate.move_cursor_down new_cursor_y - cursor_y
538
547
  @rendered_screen.cursor_y = new_cursor_y
548
+ ensure
549
+ Reline::IOGate.disable_auto_linewrap(false) if Reline::IOGate.win?
539
550
  end
540
551
 
541
552
  private def clear_rendered_screen_cache
@@ -570,8 +581,9 @@ class Reline::LineEditor
570
581
  @context
571
582
  end
572
583
 
573
- def retrieve_completion_block(set_completion_quote_character = false)
574
- @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]
575
587
  end
576
588
 
577
589
  def call_completion_proc_with_checking_args(pre, target, post)
@@ -791,98 +803,73 @@ class Reline::LineEditor
791
803
  @config.editing_mode
792
804
  end
793
805
 
794
- private def menu(_target, list)
806
+ private def menu(list)
795
807
  @menu_info = MenuInfo.new(list)
796
808
  end
797
809
 
798
- private def complete_internal_proc(list, is_menu)
799
- preposing, target, postposing = retrieve_completion_block
800
- list = list.select { |i|
801
- if i and not Encoding.compatible?(target.encoding, i.encoding)
802
- 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
803
819
  end
820
+
804
821
  if @config.completion_ignore_case
805
- i&.downcase&.start_with?(target.downcase)
822
+ item.downcase.start_with?(target)
806
823
  else
807
- i&.start_with?(target)
808
- end
809
- }.uniq
810
- if is_menu
811
- menu(target, list)
812
- return nil
813
- end
814
- completed = list.inject { |memo, item|
815
- begin
816
- memo_mbchars = memo.unicode_normalize.grapheme_clusters
817
- item_mbchars = item.unicode_normalize.grapheme_clusters
818
- rescue Encoding::CompatibilityError
819
- memo_mbchars = memo.grapheme_clusters
820
- item_mbchars = item.grapheme_clusters
821
- end
822
- size = [memo_mbchars.size, item_mbchars.size].min
823
- result = +''
824
- size.times do |i|
825
- if @config.completion_ignore_case
826
- if memo_mbchars[i].casecmp?(item_mbchars[i])
827
- result << memo_mbchars[i]
828
- else
829
- break
830
- end
831
- else
832
- if memo_mbchars[i] == item_mbchars[i]
833
- result << memo_mbchars[i]
834
- else
835
- break
836
- end
837
- end
824
+ item.start_with?(target)
838
825
  end
839
- result
840
- }
841
- [target, preposing, completed, postposing]
826
+ end.map do |item|
827
+ item.unicode_normalize
828
+ rescue Encoding::CompatibilityError
829
+ item
830
+ end.uniq
842
831
  end
843
832
 
844
- 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
+
845
836
  case @completion_state
846
- when CompletionState::NORMAL
847
- @completion_state = CompletionState::COMPLETION
848
837
  when CompletionState::PERFECT_MATCH
849
- @dig_perfect_match_proc&.(@perfect_matched)
850
- end
851
- if just_show_list
852
- is_menu = true
853
- elsif @completion_state == CompletionState::MENU
854
- is_menu = true
855
- elsif @completion_state == CompletionState::MENU_WITH_PERFECT_MATCH
856
- is_menu = true
857
- else
858
- is_menu = false
859
- end
860
- result = complete_internal_proc(list, is_menu)
861
- 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)
862
847
  @completion_state = CompletionState::PERFECT_MATCH
848
+ return
863
849
  end
864
- return if result.nil?
865
- target, preposing, completed, postposing = result
866
- return if completed.nil?
867
- if target <= completed and (@completion_state == CompletionState::COMPLETION)
868
- if list.include?(completed)
869
- if list.one?
870
- @completion_state = CompletionState::PERFECT_MATCH
871
- else
872
- @completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
873
- perform_completion(list, true) if @config.show_all_if_ambiguous
874
- end
875
- @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
876
862
  else
877
- @completion_state = CompletionState::MENU
878
- perform_completion(list, true) if @config.show_all_if_ambiguous
879
- end
880
- if not just_show_list and target < completed
881
- @buffer_of_lines[@line_index] = (preposing + completed + completion_append_character.to_s + postposing).split("\n")[@line_index] || String.new(encoding: @encoding)
882
- line_to_pointer = (preposing + completed + completion_append_character.to_s).split("\n")[@line_index] || String.new(encoding: @encoding)
883
- @byte_pointer = line_to_pointer.bytesize
863
+ @completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
884
864
  end
865
+ @perfect_matched = completed
866
+ else
867
+ @completion_state = CompletionState::MENU
868
+ menu(candidates) if @config.show_all_if_ambiguous
885
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
886
873
  end
887
874
 
888
875
  def dialog_proc_scope_completion_journey_data
@@ -911,8 +898,8 @@ class Reline::LineEditor
911
898
  end
912
899
 
913
900
  private def retrieve_completion_journey_state
914
- preposing, target, postposing = retrieve_completion_block
915
- list = call_completion_proc
901
+ preposing, target, postposing, quote = retrieve_completion_block
902
+ list = call_completion_proc(preposing, target, postposing, quote)
916
903
  return unless list.is_a?(Array)
917
904
 
918
905
  candidates = list.select{ |item| item.start_with?(target) }
@@ -925,28 +912,36 @@ class Reline::LineEditor
925
912
  )
926
913
  end
927
914
 
928
- 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
+
929
924
  if @vi_waiting_operator
930
- if VI_MOTIONS.include?(method_symbol)
925
+ if @waiting_proc || VI_MOTIONS.include?(method_symbol)
931
926
  old_byte_pointer = @byte_pointer
932
927
  @vi_arg = (@vi_arg || 1) * @vi_waiting_operator_arg
933
- block.(true)
928
+ wrap_method_call(method_symbol, key, true)
934
929
  unless @waiting_proc
935
930
  byte_pointer_diff = @byte_pointer - old_byte_pointer
936
931
  @byte_pointer = old_byte_pointer
937
- method_obj = method(@vi_waiting_operator)
938
- wrap_method_call(@vi_waiting_operator, method_obj, byte_pointer_diff)
932
+ __send__(@vi_waiting_operator, byte_pointer_diff)
939
933
  cleanup_waiting
940
934
  end
941
935
  else
942
936
  # Ignores operator when not motion is given.
943
- block.(false)
937
+ wrap_method_call(method_symbol, key, false)
944
938
  cleanup_waiting
945
939
  end
946
- @vi_arg = nil
947
940
  else
948
- block.(false)
941
+ wrap_method_call(method_symbol, key, false)
949
942
  end
943
+ @vi_arg = nil
944
+ @kill_ring.process
950
945
  end
951
946
 
952
947
  private def argumentable?(method_obj)
@@ -959,20 +954,23 @@ class Reline::LineEditor
959
954
  method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :inclusive }
960
955
  end
961
956
 
962
- def wrap_method_call(method_symbol, method_obj, key, with_operator = false)
963
- if @config.editing_mode_is?(:emacs, :vi_insert) and @vi_waiting_operator.nil?
964
- not_insertion = method_symbol != :ed_insert
965
- 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
966
961
  end
962
+
963
+ return unless respond_to?(method_symbol, true)
964
+ method_obj = method(method_symbol)
967
965
  if @vi_arg and argumentable?(method_obj)
968
- if with_operator and inclusive?(method_obj)
969
- method_obj.(key, arg: @vi_arg, inclusive: true)
966
+ if inclusive?(method_obj)
967
+ method_obj.(key, arg: @vi_arg, inclusive: with_operator)
970
968
  else
971
969
  method_obj.(key, arg: @vi_arg)
972
970
  end
973
971
  else
974
- if with_operator and inclusive?(method_obj)
975
- method_obj.(key, inclusive: true)
972
+ if inclusive?(method_obj)
973
+ method_obj.(key, inclusive: with_operator)
976
974
  else
977
975
  method_obj.(key)
978
976
  end
@@ -987,90 +985,20 @@ class Reline::LineEditor
987
985
  @drop_terminate_spaces = false
988
986
  end
989
987
 
990
- private def process_key(key, method_symbol)
991
- if key.is_a?(Symbol)
992
- cleanup_waiting
993
- elsif @waiting_proc
994
- old_byte_pointer = @byte_pointer
995
- @waiting_proc.call(key)
996
- if @vi_waiting_operator
997
- byte_pointer_diff = @byte_pointer - old_byte_pointer
998
- @byte_pointer = old_byte_pointer
999
- method_obj = method(@vi_waiting_operator)
1000
- wrap_method_call(@vi_waiting_operator, method_obj, byte_pointer_diff)
1001
- cleanup_waiting
1002
- end
1003
- @kill_ring.process
1004
- return
1005
- 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]
1006
990
 
1007
- if method_symbol and respond_to?(method_symbol, true)
1008
- method_obj = method(method_symbol)
991
+ private def process_key(key, method_symbol)
992
+ if @waiting_proc
993
+ cleanup_waiting unless key.size == 1
1009
994
  end
1010
- if method_symbol and key.is_a?(Symbol)
1011
- if @vi_arg and argumentable?(method_obj)
1012
- run_for_operators(key, method_symbol) do |with_operator|
1013
- wrap_method_call(method_symbol, method_obj, key, with_operator)
1014
- end
1015
- else
1016
- wrap_method_call(method_symbol, method_obj, key) if method_obj
1017
- end
1018
- @kill_ring.process
1019
- if @vi_arg
1020
- @vi_arg = nil
1021
- end
1022
- elsif @vi_arg
1023
- if key.chr =~ /[0-9]/
1024
- ed_argument_digit(key)
1025
- else
1026
- if argumentable?(method_obj)
1027
- run_for_operators(key, method_symbol) do |with_operator|
1028
- wrap_method_call(method_symbol, method_obj, key, with_operator)
1029
- end
1030
- elsif method_obj
1031
- wrap_method_call(method_symbol, method_obj, key)
1032
- else
1033
- ed_insert(key) unless @config.editing_mode_is?(:vi_command)
1034
- end
1035
- @kill_ring.process
1036
- if @vi_arg
1037
- @vi_arg = nil
1038
- end
1039
- end
1040
- elsif method_obj
1041
- if method_symbol == :ed_argument_digit
1042
- wrap_method_call(method_symbol, method_obj, key)
1043
- else
1044
- run_for_operators(key, method_symbol) do |with_operator|
1045
- wrap_method_call(method_symbol, method_obj, key, with_operator)
1046
- end
1047
- end
1048
- @kill_ring.process
1049
- else
1050
- 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)
1051
997
  end
1052
- end
1053
998
 
1054
- private def normal_char(key)
1055
- @multibyte_buffer << key.combined_char
1056
- if @multibyte_buffer.size > 1
1057
- if @multibyte_buffer.dup.force_encoding(@encoding).valid_encoding?
1058
- process_key(@multibyte_buffer.dup.force_encoding(@encoding), nil)
1059
- @multibyte_buffer.clear
1060
- else
1061
- # invalid
1062
- return
1063
- end
1064
- else # single byte
1065
- return if key.char >= 128 # maybe, first byte of multi byte
1066
- method_symbol = @config.editing_mode.get_method(key.combined_char)
1067
- process_key(key.combined_char, method_symbol)
1068
- @multibyte_buffer.clear
1069
- end
1070
- if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
1071
- byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer)
1072
- @byte_pointer -= byte_size
1073
- end
999
+ process_insert(force: method_symbol != :ed_insert)
1000
+
1001
+ run_for_operators(key, method_symbol)
1074
1002
  end
1075
1003
 
1076
1004
  def update(key)
@@ -1084,25 +1012,22 @@ class Reline::LineEditor
1084
1012
  end
1085
1013
 
1086
1014
  def input_key(key)
1087
- save_old_buffer
1015
+ old_buffer_of_lines = @buffer_of_lines.dup
1088
1016
  @config.reset_oneshot_key_bindings
1089
- @dialogs.each do |dialog|
1090
- if key.char.instance_of?(Symbol) and key.char == dialog.name
1091
- return
1092
- end
1093
- end
1094
1017
  if key.char.nil?
1095
1018
  process_insert(force: true)
1096
1019
  @eof = buffer_empty?
1097
1020
  finish
1098
1021
  return
1099
1022
  end
1023
+ return if @dialogs.any? { |dialog| dialog.name == key.method_symbol }
1024
+
1100
1025
  @completion_occurs = false
1101
1026
 
1102
- if key.char.is_a?(Symbol)
1103
- process_key(key.char, key.char)
1104
- else
1105
- 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
1106
1031
  end
1107
1032
 
1108
1033
  @prev_action_state, @next_action_state = @next_action_state, NullActionState
@@ -1112,15 +1037,16 @@ class Reline::LineEditor
1112
1037
  @completion_journey_state = nil
1113
1038
  end
1114
1039
 
1115
- push_input_lines unless @undoing
1116
- @undoing = false
1040
+ modified = old_buffer_of_lines != @buffer_of_lines
1041
+
1042
+ push_undo_redo(modified) unless @restoring
1043
+ @restoring = false
1117
1044
 
1118
1045
  if @in_pasting
1119
1046
  clear_dialogs
1120
1047
  return
1121
1048
  end
1122
1049
 
1123
- modified = @old_buffer_of_lines != @buffer_of_lines
1124
1050
  if !@completion_occurs && modified && !@config.disable_completion && @config.autocompletion
1125
1051
  # Auto complete starts only when edited
1126
1052
  process_insert(force: true)
@@ -1129,26 +1055,17 @@ class Reline::LineEditor
1129
1055
  modified
1130
1056
  end
1131
1057
 
1132
- def save_old_buffer
1133
- @old_buffer_of_lines = @buffer_of_lines.dup
1134
- end
1135
-
1136
- def push_input_lines
1137
- if @old_buffer_of_lines == @buffer_of_lines
1138
- @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
1139
1067
  else
1140
- @input_lines = @input_lines[0..@input_lines_position]
1141
- @input_lines_position += 1
1142
- @input_lines.push([@buffer_of_lines.dup, @byte_pointer, @line_index])
1143
- end
1144
- trim_input_lines
1145
- end
1146
-
1147
- MAX_INPUT_LINES = 100
1148
- def trim_input_lines
1149
- if @input_lines.size > MAX_INPUT_LINES
1150
- @input_lines.shift
1151
- @input_lines_position -= 1
1068
+ @undo_redo_history[@undo_redo_index] = [@buffer_of_lines.dup, @byte_pointer, @line_index]
1152
1069
  end
1153
1070
  end
1154
1071
 
@@ -1162,9 +1079,8 @@ class Reline::LineEditor
1162
1079
  end
1163
1080
  end
1164
1081
 
1165
- def call_completion_proc
1166
- result = retrieve_completion_block(true)
1167
- pre, target, post = result
1082
+ def call_completion_proc(pre, target, post, quote)
1083
+ Reline.core.instance_variable_set(:@completion_quote_character, quote)
1168
1084
  result = call_completion_proc_with_checking_args(pre, target, post)
1169
1085
  Reline.core.instance_variable_set(:@completion_quote_character, nil)
1170
1086
  result
@@ -1228,84 +1144,32 @@ class Reline::LineEditor
1228
1144
  process_auto_indent
1229
1145
  end
1230
1146
 
1231
- def set_current_lines(lines, byte_pointer = nil, line_index = 0)
1232
- cursor = current_byte_pointer_cursor
1233
- @buffer_of_lines = lines
1234
- @line_index = line_index
1235
- if byte_pointer
1236
- @byte_pointer = byte_pointer
1237
- else
1238
- calculate_nearest_cursor(cursor)
1239
- end
1240
- process_auto_indent
1241
- end
1242
-
1243
- def retrieve_completion_block(set_completion_quote_character = false)
1244
- if Reline.completer_word_break_characters.empty?
1245
- word_break_regexp = nil
1246
- else
1247
- word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
1248
- end
1249
- if Reline.completer_quote_characters.empty?
1250
- quote_characters_regexp = nil
1251
- else
1252
- quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
1253
- end
1254
- before = current_line.byteslice(0, @byte_pointer)
1255
- rest = nil
1256
- 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
1257
1150
  quote = nil
1258
- closing_quote = nil
1259
- escaped_quote = nil
1260
- i = 0
1261
- while i < @byte_pointer do
1262
- slice = current_line.byteslice(i, @byte_pointer - i)
1263
- unless slice.valid_encoding?
1264
- i += 1
1265
- next
1266
- end
1267
- if quote and slice.start_with?(closing_quote)
1268
- quote = nil
1269
- i += 1
1270
- rest = nil
1271
- elsif quote and slice.start_with?(escaped_quote)
1272
- # skip
1273
- i += 2
1274
- elsif quote_characters_regexp and slice =~ quote_characters_regexp # find new "
1275
- rest = $'
1276
- quote = $&
1277
- closing_quote = /(?!\\)#{Regexp.escape(quote)}/
1278
- escaped_quote = /\\#{Regexp.escape(quote)}/
1279
- i += 1
1280
- break_pointer = i - 1
1281
- elsif word_break_regexp and not quote and slice =~ word_break_regexp
1282
- rest = $'
1283
- i += 1
1284
- before = current_line.byteslice(i, @byte_pointer - i)
1285
- break_pointer = i
1286
- else
1287
- i += 1
1288
- end
1289
- end
1290
- postposing = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
1291
- if rest
1292
- preposing = current_line.byteslice(0, break_pointer)
1293
- target = rest
1294
- if set_completion_quote_character and quote
1295
- Reline.core.instance_variable_set(:@completion_quote_character, quote)
1296
- if postposing !~ /(?!\\)#{Regexp.escape(quote)}/ # closing quote
1297
- 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
1298
1164
  end
1299
1165
  end
1300
- else
1301
- preposing = ''
1302
- if break_pointer
1303
- preposing = current_line.byteslice(0, break_pointer)
1304
- else
1305
- preposing = ''
1306
- end
1307
- target = before
1308
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)
1309
1173
  lines = whole_lines
1310
1174
  if @line_index > 0
1311
1175
  preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
@@ -1313,7 +1177,7 @@ class Reline::LineEditor
1313
1177
  if (lines.size - 1) > @line_index
1314
1178
  postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
1315
1179
  end
1316
- [preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
1180
+ [preposing.encode(encoding), target.encode(encoding), postposing.encode(encoding), quote&.encode(encoding)]
1317
1181
  end
1318
1182
 
1319
1183
  def confirm_multiline_termination
@@ -1322,15 +1186,13 @@ class Reline::LineEditor
1322
1186
  end
1323
1187
 
1324
1188
  def insert_multiline_text(text)
1325
- save_old_buffer
1326
1189
  pre = @buffer_of_lines[@line_index].byteslice(0, @byte_pointer)
1327
1190
  post = @buffer_of_lines[@line_index].byteslice(@byte_pointer..)
1328
- 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)
1329
1192
  lines << '' if lines.empty?
1330
1193
  @buffer_of_lines[@line_index, 1] = lines
1331
1194
  @line_index += lines.size - 1
1332
1195
  @byte_pointer = @buffer_of_lines[@line_index].bytesize - post.bytesize
1333
- push_input_lines
1334
1196
  end
1335
1197
 
1336
1198
  def insert_text(text)
@@ -1370,7 +1232,7 @@ class Reline::LineEditor
1370
1232
  last += current_line.bytesize if last < 0
1371
1233
  first += current_line.bytesize if first < 0
1372
1234
  range = range.exclude_end? ? first...last : first..last
1373
- 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)
1374
1236
  set_current_line(line)
1375
1237
  else
1376
1238
  set_current_line(current_line.byteslice(0, start))
@@ -1444,10 +1306,11 @@ class Reline::LineEditor
1444
1306
  @completion_occurs = move_completed_list(:down)
1445
1307
  else
1446
1308
  @completion_journey_state = nil
1447
- result = call_completion_proc
1309
+ pre, target, post, quote = retrieve_completion_block
1310
+ result = call_completion_proc(pre, target, post, quote)
1448
1311
  if result.is_a?(Array)
1449
1312
  @completion_occurs = true
1450
- perform_completion(result, false)
1313
+ perform_completion(pre, target, post, quote, result)
1451
1314
  end
1452
1315
  end
1453
1316
  end
@@ -1495,21 +1358,11 @@ class Reline::LineEditor
1495
1358
  # digit or if the existing argument is already greater than a
1496
1359
  # million.
1497
1360
  # GNU Readline:: +self-insert+ (a, b, A, 1, !, …) Insert yourself.
1498
- private def ed_insert(key)
1499
- if key.instance_of?(String)
1500
- begin
1501
- key.encode(Encoding::UTF_8)
1502
- rescue Encoding::UndefinedConversionError
1503
- return
1504
- end
1505
- str = key
1506
- else
1507
- begin
1508
- key.chr.encode(Encoding::UTF_8)
1509
- rescue Encoding::UndefinedConversionError
1510
- return
1511
- end
1512
- str = key.chr
1361
+ private def ed_insert(str)
1362
+ begin
1363
+ str.encode(Encoding::UTF_8)
1364
+ rescue Encoding::UndefinedConversionError
1365
+ return
1513
1366
  end
1514
1367
  if @in_pasting
1515
1368
  @continuous_insertion_buffer << str
@@ -1520,24 +1373,26 @@ class Reline::LineEditor
1520
1373
 
1521
1374
  insert_text(str)
1522
1375
  end
1523
- alias_method :ed_digit, :ed_insert
1524
1376
  alias_method :self_insert, :ed_insert
1525
1377
 
1526
- private def ed_quoted_insert(str, arg: 1)
1527
- @waiting_proc = proc { |key|
1528
- arg.times do
1529
- if key == "\C-j".ord or key == "\C-m".ord
1530
- key_newline(key)
1531
- elsif key == 0
1532
- # Ignore NUL.
1533
- else
1534
- ed_insert(key)
1535
- 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)
1536
1393
  end
1537
- @waiting_proc = nil
1538
- }
1394
+ end
1539
1395
  end
1540
- alias_method :quoted_insert, :ed_quoted_insert
1541
1396
 
1542
1397
  private def ed_next_char(key, arg: 1)
1543
1398
  byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
@@ -1566,14 +1421,21 @@ class Reline::LineEditor
1566
1421
  alias_method :backward_char, :ed_prev_char
1567
1422
 
1568
1423
  private def vi_first_print(key)
1569
- @byte_pointer, = Reline::Unicode.vi_first_print(current_line)
1424
+ @byte_pointer = Reline::Unicode.vi_first_print(current_line)
1570
1425
  end
1571
1426
 
1572
1427
  private def ed_move_to_beg(key)
1573
1428
  @byte_pointer = 0
1574
1429
  end
1575
1430
  alias_method :beginning_of_line, :ed_move_to_beg
1576
- 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
1577
1439
 
1578
1440
  private def ed_move_to_end(key)
1579
1441
  @byte_pointer = current_line.bytesize
@@ -1581,27 +1443,22 @@ class Reline::LineEditor
1581
1443
  alias_method :end_of_line, :ed_move_to_end
1582
1444
 
1583
1445
  private def generate_searcher(search_key)
1584
- search_word = String.new(encoding: @encoding)
1585
- multibyte_buf = String.new(encoding: 'ASCII-8BIT')
1446
+ search_word = String.new(encoding: encoding)
1586
1447
  hit_pointer = nil
1587
1448
  lambda do |key|
1588
1449
  search_again = false
1589
1450
  case key
1590
- when "\C-h".ord, "\C-?".ord
1451
+ when "\C-h", "\C-?"
1591
1452
  grapheme_clusters = search_word.grapheme_clusters
1592
1453
  if grapheme_clusters.size > 0
1593
1454
  grapheme_clusters.pop
1594
1455
  search_word = grapheme_clusters.join
1595
1456
  end
1596
- when "\C-r".ord, "\C-s".ord
1457
+ when "\C-r", "\C-s"
1597
1458
  search_again = true if search_key == key
1598
1459
  search_key = key
1599
1460
  else
1600
- multibyte_buf << key
1601
- if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
1602
- search_word << multibyte_buf.dup.force_encoding(@encoding)
1603
- multibyte_buf.clear
1604
- end
1461
+ search_word << key
1605
1462
  end
1606
1463
  hit = nil
1607
1464
  if not search_word.empty? and @line_backup_in_history&.include?(search_word)
@@ -1614,10 +1471,10 @@ class Reline::LineEditor
1614
1471
  end
1615
1472
  if @history_pointer
1616
1473
  case search_key
1617
- when "\C-r".ord
1474
+ when "\C-r"
1618
1475
  history_pointer_base = 0
1619
1476
  history = Reline::HISTORY[0..(@history_pointer - 1)]
1620
- when "\C-s".ord
1477
+ when "\C-s"
1621
1478
  history_pointer_base = @history_pointer + 1
1622
1479
  history = Reline::HISTORY[(@history_pointer + 1)..-1]
1623
1480
  end
@@ -1627,10 +1484,10 @@ class Reline::LineEditor
1627
1484
  end
1628
1485
  elsif @history_pointer
1629
1486
  case search_key
1630
- when "\C-r".ord
1487
+ when "\C-r"
1631
1488
  history_pointer_base = 0
1632
1489
  history = Reline::HISTORY[0..@history_pointer]
1633
- when "\C-s".ord
1490
+ when "\C-s"
1634
1491
  history_pointer_base = @history_pointer
1635
1492
  history = Reline::HISTORY[@history_pointer..-1]
1636
1493
  end
@@ -1639,11 +1496,11 @@ class Reline::LineEditor
1639
1496
  history = Reline::HISTORY
1640
1497
  end
1641
1498
  case search_key
1642
- when "\C-r".ord
1499
+ when "\C-r"
1643
1500
  hit_index = history.rindex { |item|
1644
1501
  item.include?(search_word)
1645
1502
  }
1646
- when "\C-s".ord
1503
+ when "\C-s"
1647
1504
  hit_index = history.index { |item|
1648
1505
  item.include?(search_word)
1649
1506
  }
@@ -1654,9 +1511,9 @@ class Reline::LineEditor
1654
1511
  end
1655
1512
  end
1656
1513
  case search_key
1657
- when "\C-r".ord
1514
+ when "\C-r"
1658
1515
  prompt_name = 'reverse-i-search'
1659
- when "\C-s".ord
1516
+ when "\C-s"
1660
1517
  prompt_name = 'i-search'
1661
1518
  end
1662
1519
  prompt_name = "failed #{prompt_name}" unless hit
@@ -1665,57 +1522,28 @@ class Reline::LineEditor
1665
1522
  end
1666
1523
 
1667
1524
  private def incremental_search_history(key)
1668
- unless @history_pointer
1669
- @line_backup_in_history = whole_buffer
1670
- end
1525
+ backup = @buffer_of_lines.dup, @line_index, @byte_pointer, @history_pointer, @line_backup_in_history
1671
1526
  searcher = generate_searcher(key)
1672
1527
  @searching_prompt = "(reverse-i-search)`': "
1673
- termination_keys = ["\C-j".ord]
1674
- 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
1675
1530
  @waiting_proc = ->(k) {
1676
- case k
1677
- when *termination_keys
1678
- if @history_pointer
1679
- buffer = Reline::HISTORY[@history_pointer]
1680
- else
1681
- buffer = @line_backup_in_history
1682
- end
1683
- @buffer_of_lines = buffer.split("\n")
1684
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1685
- @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
1686
1534
  @searching_prompt = nil
1687
1535
  @waiting_proc = nil
1688
- @byte_pointer = 0
1689
- when "\C-g".ord
1690
- @buffer_of_lines = @line_backup_in_history.split("\n")
1691
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1692
- @line_index = @buffer_of_lines.size - 1
1693
- 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)
1694
1545
  @searching_prompt = nil
1695
1546
  @waiting_proc = nil
1696
- @byte_pointer = 0
1697
- else
1698
- chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
1699
- if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == "\C-?".ord or k == "\C-r".ord or k == "\C-s".ord
1700
- search_word, prompt_name, hit_pointer = searcher.call(k)
1701
- Reline.last_incremental_search = search_word
1702
- @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
1703
- @searching_prompt += ': ' unless @is_multiline
1704
- move_history(hit_pointer, line: :end, cursor: :end, save_buffer: false) if hit_pointer
1705
- else
1706
- if @history_pointer
1707
- line = Reline::HISTORY[@history_pointer]
1708
- else
1709
- line = @line_backup_in_history
1710
- end
1711
- @line_backup_in_history = whole_buffer
1712
- @buffer_of_lines = line.split("\n")
1713
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1714
- @line_index = @buffer_of_lines.size - 1
1715
- @searching_prompt = nil
1716
- @waiting_proc = nil
1717
- @byte_pointer = 0
1718
- end
1719
1547
  end
1720
1548
  }
1721
1549
  end
@@ -1770,14 +1598,14 @@ class Reline::LineEditor
1770
1598
  end
1771
1599
  alias_method :history_search_forward, :ed_search_next_history
1772
1600
 
1773
- private def move_history(history_pointer, line:, cursor:, save_buffer: true)
1601
+ private def move_history(history_pointer, line:, cursor:)
1774
1602
  history_pointer ||= Reline::HISTORY.size
1775
1603
  return if history_pointer < 0 || history_pointer > Reline::HISTORY.size
1776
1604
  old_history_pointer = @history_pointer || Reline::HISTORY.size
1777
1605
  if old_history_pointer == Reline::HISTORY.size
1778
- @line_backup_in_history = save_buffer ? whole_buffer : ''
1606
+ @line_backup_in_history = whole_buffer
1779
1607
  else
1780
- Reline::HISTORY[old_history_pointer] = whole_buffer if save_buffer
1608
+ Reline::HISTORY[old_history_pointer] = whole_buffer
1781
1609
  end
1782
1610
  if history_pointer == Reline::HISTORY.size
1783
1611
  buf = @line_backup_in_history
@@ -1787,7 +1615,7 @@ class Reline::LineEditor
1787
1615
  @history_pointer = history_pointer
1788
1616
  end
1789
1617
  @buffer_of_lines = buf.split("\n")
1790
- @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?
1791
1619
  @line_index = line == :start ? 0 : line == :end ? @buffer_of_lines.size - 1 : line
1792
1620
  @byte_pointer = cursor == :start ? 0 : cursor == :end ? current_line.bytesize : cursor
1793
1621
  end
@@ -1837,17 +1665,10 @@ class Reline::LineEditor
1837
1665
  finish
1838
1666
  end
1839
1667
  else
1840
- if @line_index == (@buffer_of_lines.size - 1)
1841
- if confirm_multiline_termination
1842
- finish
1843
- else
1844
- key_newline(key)
1845
- end
1846
- else
1847
- # should check confirm_multiline_termination to finish?
1848
- @line_index = @buffer_of_lines.size - 1
1849
- @byte_pointer = current_line.bytesize
1668
+ if @line_index == @buffer_of_lines.size - 1 && confirm_multiline_termination
1850
1669
  finish
1670
+ else
1671
+ key_newline(key)
1851
1672
  end
1852
1673
  end
1853
1674
  else
@@ -1855,6 +1676,11 @@ class Reline::LineEditor
1855
1676
  end
1856
1677
  end
1857
1678
 
1679
+ private def ed_force_submit(_key)
1680
+ process_insert(force: true)
1681
+ finish
1682
+ end
1683
+
1858
1684
  private def em_delete_prev_char(key, arg: 1)
1859
1685
  arg.times do
1860
1686
  if @byte_pointer == 0 and @line_index > 0
@@ -1921,7 +1747,7 @@ class Reline::LineEditor
1921
1747
  alias_method :kill_whole_line, :em_kill_line
1922
1748
 
1923
1749
  private def em_delete(key)
1924
- if buffer_empty? and key == "\C-d".ord
1750
+ if buffer_empty? and key == "\C-d"
1925
1751
  @eof = true
1926
1752
  finish
1927
1753
  elsif @byte_pointer < current_line.bytesize
@@ -1939,9 +1765,11 @@ class Reline::LineEditor
1939
1765
  if current_line.empty? or @byte_pointer < current_line.bytesize
1940
1766
  em_delete(key)
1941
1767
  elsif !@config.autocompletion # show completed list
1942
- result = call_completion_proc
1768
+ pre, target, post, quote = retrieve_completion_block
1769
+ result = call_completion_proc(pre, target, post, quote)
1943
1770
  if result.is_a?(Array)
1944
- perform_completion(result, true)
1771
+ candidates = filter_normalize_candidates(target, result)
1772
+ menu(candidates)
1945
1773
  end
1946
1774
  end
1947
1775
  end
@@ -1973,7 +1801,7 @@ class Reline::LineEditor
1973
1801
 
1974
1802
  private def em_next_word(key)
1975
1803
  if current_line.bytesize > @byte_pointer
1976
- byte_size, _ = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1804
+ byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1977
1805
  @byte_pointer += byte_size
1978
1806
  end
1979
1807
  end
@@ -1981,7 +1809,7 @@ class Reline::LineEditor
1981
1809
 
1982
1810
  private def ed_prev_word(key)
1983
1811
  if @byte_pointer > 0
1984
- byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
1812
+ byte_size = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
1985
1813
  @byte_pointer -= byte_size
1986
1814
  end
1987
1815
  end
@@ -1989,7 +1817,7 @@ class Reline::LineEditor
1989
1817
 
1990
1818
  private def em_delete_next_word(key)
1991
1819
  if current_line.bytesize > @byte_pointer
1992
- byte_size, _ = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1820
+ byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1993
1821
  line, word = byteslice!(current_line, @byte_pointer, byte_size)
1994
1822
  set_current_line(line)
1995
1823
  @kill_ring.append(word)
@@ -1999,7 +1827,7 @@ class Reline::LineEditor
1999
1827
 
2000
1828
  private def ed_delete_prev_word(key)
2001
1829
  if @byte_pointer > 0
2002
- byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
1830
+ byte_size = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
2003
1831
  line, word = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
2004
1832
  set_current_line(line, @byte_pointer - byte_size)
2005
1833
  @kill_ring.append(word, true)
@@ -2039,7 +1867,7 @@ class Reline::LineEditor
2039
1867
 
2040
1868
  private def em_capitol_case(key)
2041
1869
  if current_line.bytesize > @byte_pointer
2042
- 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)
2043
1871
  before = current_line.byteslice(0, @byte_pointer)
2044
1872
  after = current_line.byteslice((@byte_pointer + byte_size)..-1)
2045
1873
  set_current_line(before + new_str + after, @byte_pointer + new_str.bytesize)
@@ -2049,7 +1877,7 @@ class Reline::LineEditor
2049
1877
 
2050
1878
  private def em_lower_case(key)
2051
1879
  if current_line.bytesize > @byte_pointer
2052
- byte_size, = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1880
+ byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
2053
1881
  part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2054
1882
  mbchar =~ /[A-Z]/ ? mbchar.downcase : mbchar
2055
1883
  }.join
@@ -2062,7 +1890,7 @@ class Reline::LineEditor
2062
1890
 
2063
1891
  private def em_upper_case(key)
2064
1892
  if current_line.bytesize > @byte_pointer
2065
- byte_size, = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1893
+ byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
2066
1894
  part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2067
1895
  mbchar =~ /[a-z]/ ? mbchar.upcase : mbchar
2068
1896
  }.join
@@ -2075,7 +1903,7 @@ class Reline::LineEditor
2075
1903
 
2076
1904
  private def em_kill_region(key)
2077
1905
  if @byte_pointer > 0
2078
- 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)
2079
1907
  line, deleted = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
2080
1908
  set_current_line(line, @byte_pointer - byte_size)
2081
1909
  @kill_ring.append(deleted, true)
@@ -2106,7 +1934,7 @@ class Reline::LineEditor
2106
1934
 
2107
1935
  private def vi_next_word(key, arg: 1)
2108
1936
  if current_line.bytesize > @byte_pointer
2109
- 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)
2110
1938
  @byte_pointer += byte_size
2111
1939
  end
2112
1940
  arg -= 1
@@ -2115,7 +1943,7 @@ class Reline::LineEditor
2115
1943
 
2116
1944
  private def vi_prev_word(key, arg: 1)
2117
1945
  if @byte_pointer > 0
2118
- byte_size, _ = Reline::Unicode.vi_backward_word(current_line, @byte_pointer)
1946
+ byte_size = Reline::Unicode.vi_backward_word(current_line, @byte_pointer)
2119
1947
  @byte_pointer -= byte_size
2120
1948
  end
2121
1949
  arg -= 1
@@ -2124,7 +1952,7 @@ class Reline::LineEditor
2124
1952
 
2125
1953
  private def vi_end_word(key, arg: 1, inclusive: false)
2126
1954
  if current_line.bytesize > @byte_pointer
2127
- 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)
2128
1956
  @byte_pointer += byte_size
2129
1957
  end
2130
1958
  arg -= 1
@@ -2139,7 +1967,7 @@ class Reline::LineEditor
2139
1967
 
2140
1968
  private def vi_next_big_word(key, arg: 1)
2141
1969
  if current_line.bytesize > @byte_pointer
2142
- 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)
2143
1971
  @byte_pointer += byte_size
2144
1972
  end
2145
1973
  arg -= 1
@@ -2148,7 +1976,7 @@ class Reline::LineEditor
2148
1976
 
2149
1977
  private def vi_prev_big_word(key, arg: 1)
2150
1978
  if @byte_pointer > 0
2151
- 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)
2152
1980
  @byte_pointer -= byte_size
2153
1981
  end
2154
1982
  arg -= 1
@@ -2157,7 +1985,7 @@ class Reline::LineEditor
2157
1985
 
2158
1986
  private def vi_end_big_word(key, arg: 1, inclusive: false)
2159
1987
  if current_line.bytesize > @byte_pointer
2160
- 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)
2161
1989
  @byte_pointer += byte_size
2162
1990
  end
2163
1991
  arg -= 1
@@ -2312,7 +2140,7 @@ class Reline::LineEditor
2312
2140
  }
2313
2141
  system("#{ENV['EDITOR']} #{path}")
2314
2142
  @buffer_of_lines = File.read(path).split("\n")
2315
- @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?
2316
2144
  @line_index = 0
2317
2145
  finish
2318
2146
  end
@@ -2337,20 +2165,9 @@ class Reline::LineEditor
2337
2165
  end
2338
2166
 
2339
2167
  private def ed_argument_digit(key)
2340
- if @vi_arg.nil?
2341
- if key.chr.to_i.zero?
2342
- if key.anybits?(0b10000000)
2343
- unescaped_key = key ^ 0b10000000
2344
- unless unescaped_key.chr.to_i.zero?
2345
- @vi_arg = unescaped_key.chr.to_i
2346
- end
2347
- end
2348
- else
2349
- @vi_arg = key.chr.to_i
2350
- end
2351
- else
2352
- @vi_arg = @vi_arg * 10 + key.chr.to_i
2353
- 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
2354
2171
  end
2355
2172
 
2356
2173
  private def vi_to_column(key, arg: 0)
@@ -2369,7 +2186,7 @@ class Reline::LineEditor
2369
2186
  before = current_line.byteslice(0, @byte_pointer)
2370
2187
  remaining_point = @byte_pointer + byte_size
2371
2188
  after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
2372
- set_current_line(before + k.chr + after)
2189
+ set_current_line(before + k + after)
2373
2190
  @waiting_proc = nil
2374
2191
  elsif arg > 1
2375
2192
  byte_size = 0
@@ -2379,7 +2196,7 @@ class Reline::LineEditor
2379
2196
  before = current_line.byteslice(0, @byte_pointer)
2380
2197
  remaining_point = @byte_pointer + byte_size
2381
2198
  after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
2382
- replaced = k.chr * arg
2199
+ replaced = k * arg
2383
2200
  set_current_line(before + replaced + after, @byte_pointer + replaced.bytesize)
2384
2201
  @waiting_proc = nil
2385
2202
  end
@@ -2395,11 +2212,6 @@ class Reline::LineEditor
2395
2212
  end
2396
2213
 
2397
2214
  private def search_next_char(key, arg, need_prev_char: false, inclusive: false)
2398
- if key.instance_of?(String)
2399
- inputed_char = key
2400
- else
2401
- inputed_char = key.chr
2402
- end
2403
2215
  prev_total = nil
2404
2216
  total = nil
2405
2217
  found = false
@@ -2410,7 +2222,7 @@ class Reline::LineEditor
2410
2222
  width = Reline::Unicode.get_mbchar_width(mbchar)
2411
2223
  total = [mbchar.bytesize, width]
2412
2224
  else
2413
- if inputed_char == mbchar
2225
+ if key == mbchar
2414
2226
  arg -= 1
2415
2227
  if arg.zero?
2416
2228
  found = true
@@ -2447,11 +2259,6 @@ class Reline::LineEditor
2447
2259
  end
2448
2260
 
2449
2261
  private def search_prev_char(key, arg, need_next_char = false)
2450
- if key.instance_of?(String)
2451
- inputed_char = key
2452
- else
2453
- inputed_char = key.chr
2454
- end
2455
2262
  prev_total = nil
2456
2263
  total = nil
2457
2264
  found = false
@@ -2462,7 +2269,7 @@ class Reline::LineEditor
2462
2269
  width = Reline::Unicode.get_mbchar_width(mbchar)
2463
2270
  total = [mbchar.bytesize, width]
2464
2271
  else
2465
- if inputed_char == mbchar
2272
+ if key == mbchar
2466
2273
  arg -= 1
2467
2274
  if arg.zero?
2468
2275
  found = true
@@ -2514,24 +2321,23 @@ class Reline::LineEditor
2514
2321
  @config.editing_mode = :vi_insert
2515
2322
  end
2516
2323
 
2517
- private def undo(_key)
2518
- @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)
2519
2327
 
2520
- 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
2521
2334
 
2522
- @input_lines_position -= 1
2523
- target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position]
2524
- set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y)
2335
+ private def undo(_key)
2336
+ move_undo_redo(-1)
2525
2337
  end
2526
2338
 
2527
2339
  private def redo(_key)
2528
- @undoing = true
2529
-
2530
- return if @input_lines_position >= @input_lines.size - 1
2531
-
2532
- @input_lines_position += 1
2533
- target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position]
2534
- set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y)
2340
+ move_undo_redo(+1)
2535
2341
  end
2536
2342
 
2537
2343
  private def prev_action_state_value(type)