reline 0.1.3 → 0.1.8

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.
@@ -17,7 +17,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
17
17
  # 7 ^G
18
18
  :ed_unassigned,
19
19
  # 8 ^H
20
- :ed_delete_prev_char,
20
+ :ed_unassigned,
21
21
  # 9 ^I
22
22
  :ed_unassigned,
23
23
  # 10 ^J
@@ -37,7 +37,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
37
37
  # 17 ^Q
38
38
  :ed_ignore,
39
39
  # 18 ^R
40
- :ed_search_prev_history,
40
+ :vi_search_prev,
41
41
  # 19 ^S
42
42
  :ed_ignore,
43
43
  # 20 ^T
@@ -151,7 +151,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
151
151
  # 74 J
152
152
  :vi_join_lines,
153
153
  # 75 K
154
- :ed_search_prev_history,
154
+ :vi_search_prev,
155
155
  # 76 L
156
156
  :ed_unassigned,
157
157
  # 77 M
@@ -255,7 +255,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
255
255
  # 126 ~
256
256
  :vi_change_case,
257
257
  # 127 ^?
258
- :ed_delete_prev_char,
258
+ :ed_unassigned,
259
259
  # 128 M-^@
260
260
  :ed_unassigned,
261
261
  # 129 M-^A
@@ -37,9 +37,9 @@ class Reline::KeyActor::ViInsert < Reline::KeyActor::Base
37
37
  # 17 ^Q
38
38
  :ed_ignore,
39
39
  # 18 ^R
40
- :ed_search_prev_history,
40
+ :vi_search_prev,
41
41
  # 19 ^S
42
- :ed_search_next_history,
42
+ :vi_search_next,
43
43
  # 20 ^T
44
44
  :ed_insert,
45
45
  # 21 ^U
@@ -42,6 +42,8 @@ class Reline::KeyStroke
42
42
  expand(expand(rhs_bytes) + expand(input.drop(lhs.size)))
43
43
  when Symbol
44
44
  [rhs] + expand(input.drop(lhs.size))
45
+ when Array
46
+ rhs
45
47
  end
46
48
  end
47
49
 
@@ -2,7 +2,6 @@ require 'reline/kill_ring'
2
2
  require 'reline/unicode'
3
3
 
4
4
  require 'tempfile'
5
- require 'pathname'
6
5
 
7
6
  class Reline::LineEditor
8
7
  # TODO: undo
@@ -51,18 +50,20 @@ class Reline::LineEditor
51
50
  CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer)
52
51
  MenuInfo = Struct.new('MenuInfo', :target, :list)
53
52
 
54
- CSI_REGEXP = /\e\[[\d;]*[ABCDEFGHJKSTfminsuhl]/
55
- OSC_REGEXP = /\e\]\d+(?:;[^;]+)*\a/
56
- NON_PRINTING_START = "\1"
57
- NON_PRINTING_END = "\2"
58
- WIDTH_SCANNER = /\G(?:#{NON_PRINTING_START}|#{NON_PRINTING_END}|#{CSI_REGEXP}|#{OSC_REGEXP}|\X)/
59
-
60
53
  def initialize(config, encoding)
61
54
  @config = config
62
55
  @completion_append_character = ''
63
56
  reset_variables(encoding: encoding)
64
57
  end
65
58
 
59
+ def simplified_rendering?
60
+ if finished?
61
+ false
62
+ else
63
+ not @rerender_all and not finished? and Reline::IOGate.in_pasting?
64
+ end
65
+ end
66
+
66
67
  private def check_multiline_prompt(buffer, prompt)
67
68
  if @vi_arg
68
69
  prompt = "(arg: #{@vi_arg}) "
@@ -73,14 +74,39 @@ class Reline::LineEditor
73
74
  else
74
75
  prompt = @prompt
75
76
  end
77
+ return [prompt, calculate_width(prompt, true), [prompt] * buffer.size] if simplified_rendering?
76
78
  if @prompt_proc
77
79
  prompt_list = @prompt_proc.(buffer)
78
80
  prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
81
+ if @config.show_mode_in_prompt
82
+ if @config.editing_mode_is?(:vi_command)
83
+ mode_icon = @config.vi_cmd_mode_icon
84
+ elsif @config.editing_mode_is?(:vi_insert)
85
+ mode_icon = @config.vi_ins_mode_icon
86
+ elsif @config.editing_mode_is?(:emacs)
87
+ mode_icon = @config.emacs_mode_string
88
+ else
89
+ mode_icon = '?'
90
+ end
91
+ prompt_list.map!{ |pr| mode_icon + pr }
92
+ end
79
93
  prompt = prompt_list[@line_index]
80
94
  prompt_width = calculate_width(prompt, true)
81
95
  [prompt, prompt_width, prompt_list]
82
96
  else
83
97
  prompt_width = calculate_width(prompt, true)
98
+ if @config.show_mode_in_prompt
99
+ if @config.editing_mode_is?(:vi_command)
100
+ mode_icon = @config.vi_cmd_mode_icon
101
+ elsif @config.editing_mode_is?(:vi_insert)
102
+ mode_icon = @config.vi_ins_mode_icon
103
+ elsif @config.editing_mode_is?(:emacs)
104
+ mode_icon = @config.emacs_mode_string
105
+ else
106
+ mode_icon = '?'
107
+ end
108
+ prompt = mode_icon + prompt
109
+ end
84
110
  [prompt, prompt_width, nil]
85
111
  end
86
112
  end
@@ -116,7 +142,7 @@ class Reline::LineEditor
116
142
  if @line_index.zero?
117
143
  0
118
144
  else
119
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list)
145
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
120
146
  end
121
147
  if @prompt_proc
122
148
  prompt = prompt_list[@line_index]
@@ -161,6 +187,7 @@ class Reline::LineEditor
161
187
  @searching_prompt = nil
162
188
  @first_char = true
163
189
  @eof = false
190
+ @continuous_insertion_buffer = String.new(encoding: @encoding)
164
191
  reset_line
165
192
  end
166
193
 
@@ -190,10 +217,10 @@ class Reline::LineEditor
190
217
  @is_multiline = false
191
218
  end
192
219
 
193
- private def calculate_height_by_lines(lines, prompt_list)
220
+ private def calculate_height_by_lines(lines, prompt)
194
221
  result = 0
222
+ prompt_list = prompt.is_a?(Array) ? prompt : nil
195
223
  lines.each_with_index { |line, i|
196
- prompt = ''
197
224
  prompt = prompt_list[i] if prompt_list and prompt_list[i]
198
225
  result += calculate_height_by_width(calculate_width(prompt, true) + calculate_width(line))
199
226
  }
@@ -211,40 +238,8 @@ class Reline::LineEditor
211
238
  width.div(@screen_size.last) + 1
212
239
  end
213
240
 
214
- private def split_by_width(prompt, str, max_width)
215
- lines = [String.new(encoding: @encoding)]
216
- height = 1
217
- width = 0
218
- rest = "#{prompt}#{str}".encode(Encoding::UTF_8)
219
- in_zero_width = false
220
- rest.scan(WIDTH_SCANNER) do |gc|
221
- case gc
222
- when NON_PRINTING_START
223
- in_zero_width = true
224
- when NON_PRINTING_END
225
- in_zero_width = false
226
- when CSI_REGEXP, OSC_REGEXP
227
- lines.last << gc
228
- else
229
- unless in_zero_width
230
- mbchar_width = Reline::Unicode.get_mbchar_width(gc)
231
- if (width += mbchar_width) > max_width
232
- width = mbchar_width
233
- lines << nil
234
- lines << String.new(encoding: @encoding)
235
- height += 1
236
- end
237
- end
238
- lines.last << gc
239
- end
240
- end
241
- # The cursor moves to next line in first
242
- if width == max_width
243
- lines << nil
244
- lines << String.new(encoding: @encoding)
245
- height += 1
246
- end
247
- [lines, height]
241
+ private def split_by_width(str, max_width)
242
+ Reline::Unicode.split_by_width(str, max_width, @encoding)
248
243
  end
249
244
 
250
245
  private def scroll_down(val)
@@ -312,6 +307,11 @@ class Reline::LineEditor
312
307
  @byte_pointer = new_byte_pointer
313
308
  end
314
309
 
310
+ def rerender_all
311
+ @rerender_all = true
312
+ rerender
313
+ end
314
+
315
315
  def rerender
316
316
  return if @line.nil?
317
317
  if @menu_info
@@ -358,7 +358,7 @@ class Reline::LineEditor
358
358
  new_lines = whole_lines
359
359
  end
360
360
  prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
361
- all_height = calculate_height_by_lines(new_lines, prompt_list)
361
+ all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
362
362
  diff = all_height - @highest_in_all
363
363
  move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
364
364
  if diff > 0
@@ -398,7 +398,7 @@ class Reline::LineEditor
398
398
  if @line_index.zero?
399
399
  0
400
400
  else
401
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list)
401
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
402
402
  end
403
403
  if @prompt_proc
404
404
  prompt = prompt_list[@line_index]
@@ -457,7 +457,7 @@ class Reline::LineEditor
457
457
  if @line_index.zero?
458
458
  0
459
459
  else
460
- calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list)
460
+ calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
461
461
  end
462
462
  @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
463
463
  move_cursor_down(@first_line_started_from + @started_from)
@@ -488,7 +488,7 @@ class Reline::LineEditor
488
488
  end
489
489
 
490
490
  private def render_partial(prompt, prompt_width, line_to_render, with_control = true)
491
- visual_lines, height = split_by_width(prompt, line_to_render.nil? ? '' : line_to_render, @screen_size.last)
491
+ visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
492
492
  if with_control
493
493
  if height > @highest_in_this
494
494
  diff = height - @highest_in_this
@@ -507,8 +507,18 @@ class Reline::LineEditor
507
507
  Reline::IOGate.move_cursor_column(0)
508
508
  visual_lines.each_with_index do |line, index|
509
509
  if line.nil?
510
- if Reline::IOGate.win? and calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
511
- # A newline is automatically inserted if a character is rendered at eol on command prompt.
510
+ if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
511
+ # reaches the end of line
512
+ if Reline::IOGate.win?
513
+ # A newline is automatically inserted if a character is rendered at
514
+ # eol on command prompt.
515
+ else
516
+ # When the cursor is at the end of the line and erases characters
517
+ # after the cursor, some terminals delete the character at the
518
+ # cursor position.
519
+ move_cursor_down(1)
520
+ Reline::IOGate.move_cursor_column(0)
521
+ end
512
522
  else
513
523
  Reline::IOGate.erase_after_cursor
514
524
  move_cursor_down(1)
@@ -528,22 +538,25 @@ class Reline::LineEditor
528
538
  end
529
539
  end
530
540
  Reline::IOGate.erase_after_cursor
541
+ Reline::IOGate.move_cursor_column(0)
531
542
  if with_control
532
- move_cursor_up(height - 1)
543
+ # Just after rendring, so the cursor is on the last line.
533
544
  if finished?
534
- move_cursor_down(@started_from)
545
+ Reline::IOGate.move_cursor_column(0)
546
+ else
547
+ # Moves up from bottom of lines to the cursor position.
548
+ move_cursor_up(height - 1 - @started_from)
549
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
535
550
  end
536
- move_cursor_down(@started_from)
537
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
538
551
  end
539
552
  height
540
553
  end
541
554
 
542
555
  private def modify_lines(before)
543
- return before if before.nil? || before.empty?
556
+ return before if before.nil? || before.empty? || simplified_rendering?
544
557
 
545
558
  if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
546
- after.lines(chomp: true)
559
+ after.lines("\n").map { |l| l.chomp('') }
547
560
  else
548
561
  before
549
562
  end
@@ -568,7 +581,7 @@ class Reline::LineEditor
568
581
  else
569
582
  i&.start_with?(target)
570
583
  end
571
- }
584
+ }.uniq
572
585
  if is_menu
573
586
  menu(target, list)
574
587
  return nil
@@ -685,7 +698,7 @@ class Reline::LineEditor
685
698
  if @waiting_operator_proc
686
699
  if VI_MOTIONS.include?(method_symbol)
687
700
  old_cursor, old_byte_pointer = @cursor, @byte_pointer
688
- block.()
701
+ block.(true)
689
702
  unless @waiting_proc
690
703
  cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
691
704
  @cursor, @byte_pointer = old_cursor, old_byte_pointer
@@ -693,27 +706,54 @@ class Reline::LineEditor
693
706
  else
694
707
  old_waiting_proc = @waiting_proc
695
708
  old_waiting_operator_proc = @waiting_operator_proc
709
+ current_waiting_operator_proc = @waiting_operator_proc
696
710
  @waiting_proc = proc { |k|
697
711
  old_cursor, old_byte_pointer = @cursor, @byte_pointer
698
712
  old_waiting_proc.(k)
699
713
  cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
700
714
  @cursor, @byte_pointer = old_cursor, old_byte_pointer
701
- @waiting_operator_proc.(cursor_diff, byte_pointer_diff)
715
+ current_waiting_operator_proc.(cursor_diff, byte_pointer_diff)
702
716
  @waiting_operator_proc = old_waiting_operator_proc
703
717
  }
704
718
  end
705
719
  else
706
720
  # Ignores operator when not motion is given.
707
- block.()
721
+ block.(false)
708
722
  end
709
723
  @waiting_operator_proc = nil
710
724
  else
711
- block.()
725
+ block.(false)
712
726
  end
713
727
  end
714
728
 
715
729
  private def argumentable?(method_obj)
716
- method_obj and method_obj.parameters.length != 1
730
+ method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :arg }
731
+ end
732
+
733
+ private def inclusive?(method_obj)
734
+ # If a motion method with the keyword argument "inclusive" follows the
735
+ # operator, it must contain the character at the cursor position.
736
+ method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :inclusive }
737
+ end
738
+
739
+ def wrap_method_call(method_symbol, method_obj, key, with_operator = false)
740
+ if @config.editing_mode_is?(:emacs, :vi_insert) and @waiting_proc.nil? and @waiting_operator_proc.nil?
741
+ not_insertion = method_symbol != :ed_insert
742
+ process_insert(force: not_insertion)
743
+ end
744
+ if @vi_arg and argumentable?(method_obj)
745
+ if with_operator and inclusive?(method_obj)
746
+ method_obj.(key, arg: @vi_arg, inclusive: true)
747
+ else
748
+ method_obj.(key, arg: @vi_arg)
749
+ end
750
+ else
751
+ if with_operator and inclusive?(method_obj)
752
+ method_obj.(key, inclusive: true)
753
+ else
754
+ method_obj.(key)
755
+ end
756
+ end
717
757
  end
718
758
 
719
759
  private def process_key(key, method_symbol)
@@ -724,11 +764,11 @@ class Reline::LineEditor
724
764
  end
725
765
  if method_symbol and key.is_a?(Symbol)
726
766
  if @vi_arg and argumentable?(method_obj)
727
- run_for_operators(key, method_symbol) do
728
- method_obj.(key, arg: @vi_arg)
767
+ run_for_operators(key, method_symbol) do |with_operator|
768
+ wrap_method_call(method_symbol, method_obj, key, with_operator)
729
769
  end
730
770
  else
731
- method_obj&.(key)
771
+ wrap_method_call(method_symbol, method_obj, key) if method_obj
732
772
  end
733
773
  @kill_ring.process
734
774
  @vi_arg = nil
@@ -737,15 +777,15 @@ class Reline::LineEditor
737
777
  ed_argument_digit(key)
738
778
  else
739
779
  if argumentable?(method_obj)
740
- run_for_operators(key, method_symbol) do
741
- method_obj.(key, arg: @vi_arg)
780
+ run_for_operators(key, method_symbol) do |with_operator|
781
+ wrap_method_call(method_symbol, method_obj, key, with_operator)
742
782
  end
743
783
  elsif @waiting_proc
744
784
  @waiting_proc.(key)
745
785
  elsif method_obj
746
- method_obj.(key)
786
+ wrap_method_call(method_symbol, method_obj, key)
747
787
  else
748
- ed_insert(key)
788
+ ed_insert(key) unless @config.editing_mode_is?(:vi_command)
749
789
  end
750
790
  @kill_ring.process
751
791
  @vi_arg = nil
@@ -755,15 +795,15 @@ class Reline::LineEditor
755
795
  @kill_ring.process
756
796
  elsif method_obj
757
797
  if method_symbol == :ed_argument_digit
758
- method_obj.(key)
798
+ wrap_method_call(method_symbol, method_obj, key)
759
799
  else
760
- run_for_operators(key, method_symbol) do
761
- method_obj.(key)
800
+ run_for_operators(key, method_symbol) do |with_operator|
801
+ wrap_method_call(method_symbol, method_obj, key, with_operator)
762
802
  end
763
803
  end
764
804
  @kill_ring.process
765
805
  else
766
- ed_insert(key)
806
+ ed_insert(key) unless @config.editing_mode_is?(:vi_command)
767
807
  end
768
808
  end
769
809
 
@@ -820,6 +860,7 @@ class Reline::LineEditor
820
860
  result = call_completion_proc
821
861
  if result.is_a?(Array)
822
862
  completion_occurs = true
863
+ process_insert
823
864
  complete(result)
824
865
  end
825
866
  end
@@ -828,6 +869,7 @@ class Reline::LineEditor
828
869
  result = call_completion_proc
829
870
  if result.is_a?(Array)
830
871
  completion_occurs = true
872
+ process_insert
831
873
  move_completed_list(result, "\C-p".ord == key.char ? :up : :down)
832
874
  end
833
875
  end
@@ -839,7 +881,7 @@ class Reline::LineEditor
839
881
  unless completion_occurs
840
882
  @completion_state = CompletionState::NORMAL
841
883
  end
842
- if @is_multiline and @auto_indent_proc
884
+ if @is_multiline and @auto_indent_proc and not simplified_rendering?
843
885
  process_auto_indent
844
886
  end
845
887
  end
@@ -1041,6 +1083,7 @@ class Reline::LineEditor
1041
1083
 
1042
1084
  def finish
1043
1085
  @finished = true
1086
+ @rerender_all = true
1044
1087
  @config.reset
1045
1088
  end
1046
1089
 
@@ -1058,29 +1101,7 @@ class Reline::LineEditor
1058
1101
  end
1059
1102
 
1060
1103
  private def calculate_width(str, allow_escape_code = false)
1061
- if allow_escape_code
1062
- width = 0
1063
- rest = str.encode(Encoding::UTF_8)
1064
- in_zero_width = false
1065
- rest.scan(WIDTH_SCANNER) do |gc|
1066
- case gc
1067
- when NON_PRINTING_START
1068
- in_zero_width = true
1069
- when NON_PRINTING_END
1070
- in_zero_width = false
1071
- when CSI_REGEXP, OSC_REGEXP
1072
- else
1073
- unless in_zero_width
1074
- width += Reline::Unicode.get_mbchar_width(gc)
1075
- end
1076
- end
1077
- end
1078
- width
1079
- else
1080
- str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |w, gc|
1081
- w + Reline::Unicode.get_mbchar_width(gc)
1082
- }
1083
- end
1104
+ Reline::Unicode.calculate_width(str, allow_escape_code)
1084
1105
  end
1085
1106
 
1086
1107
  private def key_delete(key)
@@ -1101,38 +1122,57 @@ class Reline::LineEditor
1101
1122
 
1102
1123
  private def ed_unassigned(key) end # do nothing
1103
1124
 
1125
+ private def process_insert(force: false)
1126
+ return if @continuous_insertion_buffer.empty? or (Reline::IOGate.in_pasting? and not force)
1127
+ width = Reline::Unicode.calculate_width(@continuous_insertion_buffer)
1128
+ bytesize = @continuous_insertion_buffer.bytesize
1129
+ if @cursor == @cursor_max
1130
+ @line += @continuous_insertion_buffer
1131
+ else
1132
+ @line = byteinsert(@line, @byte_pointer, @continuous_insertion_buffer)
1133
+ end
1134
+ @byte_pointer += bytesize
1135
+ @cursor += width
1136
+ @cursor_max += width
1137
+ @continuous_insertion_buffer.clear
1138
+ end
1139
+
1104
1140
  private def ed_insert(key)
1141
+ str = nil
1142
+ width = nil
1143
+ bytesize = nil
1105
1144
  if key.instance_of?(String)
1106
1145
  begin
1107
1146
  key.encode(Encoding::UTF_8)
1108
1147
  rescue Encoding::UndefinedConversionError
1109
1148
  return
1110
1149
  end
1111
- width = Reline::Unicode.get_mbchar_width(key)
1112
- if @cursor == @cursor_max
1113
- @line += key
1114
- else
1115
- @line = byteinsert(@line, @byte_pointer, key)
1116
- end
1117
- @byte_pointer += key.bytesize
1118
- @cursor += width
1119
- @cursor_max += width
1150
+ str = key
1151
+ bytesize = key.bytesize
1120
1152
  else
1121
1153
  begin
1122
1154
  key.chr.encode(Encoding::UTF_8)
1123
1155
  rescue Encoding::UndefinedConversionError
1124
1156
  return
1125
1157
  end
1126
- if @cursor == @cursor_max
1127
- @line += key.chr
1128
- else
1129
- @line = byteinsert(@line, @byte_pointer, key.chr)
1130
- end
1131
- width = Reline::Unicode.get_mbchar_width(key.chr)
1132
- @byte_pointer += 1
1133
- @cursor += width
1134
- @cursor_max += width
1158
+ str = key.chr
1159
+ bytesize = 1
1135
1160
  end
1161
+ if Reline::IOGate.in_pasting?
1162
+ @continuous_insertion_buffer << str
1163
+ return
1164
+ elsif not @continuous_insertion_buffer.empty?
1165
+ process_insert
1166
+ end
1167
+ width = Reline::Unicode.get_mbchar_width(str)
1168
+ if @cursor == @cursor_max
1169
+ @line += str
1170
+ else
1171
+ @line = byteinsert(@line, @byte_pointer, str)
1172
+ end
1173
+ @byte_pointer += bytesize
1174
+ @cursor += width
1175
+ @cursor_max += width
1136
1176
  end
1137
1177
  alias_method :ed_digit, :ed_insert
1138
1178
  alias_method :self_insert, :ed_insert
@@ -1189,6 +1229,7 @@ class Reline::LineEditor
1189
1229
  arg -= 1
1190
1230
  ed_prev_char(key, arg: arg) if arg > 0
1191
1231
  end
1232
+ alias_method :backward_char, :ed_prev_char
1192
1233
 
1193
1234
  private def vi_first_print(key)
1194
1235
  @byte_pointer, @cursor = Reline::Unicode.vi_first_print(@line)
@@ -1258,7 +1299,7 @@ class Reline::LineEditor
1258
1299
  if search_word.empty? and Reline.last_incremental_search
1259
1300
  search_word = Reline.last_incremental_search
1260
1301
  end
1261
- if @history_pointer # TODO
1302
+ if @history_pointer
1262
1303
  case prev_search_key
1263
1304
  when "\C-r".ord
1264
1305
  history_pointer_base = 0
@@ -1330,7 +1371,7 @@ class Reline::LineEditor
1330
1371
  end
1331
1372
  end
1332
1373
 
1333
- private def search_history(key)
1374
+ private def incremental_search_history(key)
1334
1375
  unless @history_pointer
1335
1376
  if @is_multiline
1336
1377
  @line_backup_in_history = whole_buffer
@@ -1411,15 +1452,114 @@ class Reline::LineEditor
1411
1452
  }
1412
1453
  end
1413
1454
 
1414
- private def ed_search_prev_history(key)
1415
- search_history(key)
1455
+ private def vi_search_prev(key)
1456
+ incremental_search_history(key)
1416
1457
  end
1417
- alias_method :reverse_search_history, :ed_search_prev_history
1458
+ alias_method :reverse_search_history, :vi_search_prev
1418
1459
 
1419
- private def ed_search_next_history(key)
1420
- search_history(key)
1460
+ private def vi_search_next(key)
1461
+ incremental_search_history(key)
1421
1462
  end
1422
- alias_method :forward_search_history, :ed_search_next_history
1463
+ alias_method :forward_search_history, :vi_search_next
1464
+
1465
+ private def ed_search_prev_history(key, arg: 1)
1466
+ history = nil
1467
+ h_pointer = nil
1468
+ line_no = nil
1469
+ substr = @line.slice(0, @byte_pointer)
1470
+ if @history_pointer.nil?
1471
+ return if not @line.empty? and substr.empty?
1472
+ history = Reline::HISTORY
1473
+ elsif @history_pointer.zero?
1474
+ history = nil
1475
+ h_pointer = nil
1476
+ else
1477
+ history = Reline::HISTORY.slice(0, @history_pointer)
1478
+ end
1479
+ return if history.nil?
1480
+ if @is_multiline
1481
+ h_pointer = history.rindex { |h|
1482
+ h.split("\n").each_with_index { |l, i|
1483
+ if l.start_with?(substr)
1484
+ line_no = i
1485
+ break
1486
+ end
1487
+ }
1488
+ not line_no.nil?
1489
+ }
1490
+ else
1491
+ h_pointer = history.rindex { |l|
1492
+ l.start_with?(substr)
1493
+ }
1494
+ end
1495
+ return if h_pointer.nil?
1496
+ @history_pointer = h_pointer
1497
+ if @is_multiline
1498
+ @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1499
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1500
+ @line_index = line_no
1501
+ @line = @buffer_of_lines.last
1502
+ @rerender_all = true
1503
+ else
1504
+ @line = Reline::HISTORY[@history_pointer]
1505
+ end
1506
+ @cursor_max = calculate_width(@line)
1507
+ arg -= 1
1508
+ ed_search_prev_history(key, arg: arg) if arg > 0
1509
+ end
1510
+ alias_method :history_search_backward, :ed_search_prev_history
1511
+
1512
+ private def ed_search_next_history(key, arg: 1)
1513
+ substr = @line.slice(0, @byte_pointer)
1514
+ if @history_pointer.nil?
1515
+ return
1516
+ elsif @history_pointer == (Reline::HISTORY.size - 1) and not substr.empty?
1517
+ return
1518
+ end
1519
+ history = Reline::HISTORY.slice((@history_pointer + 1)..-1)
1520
+ h_pointer = nil
1521
+ line_no = nil
1522
+ if @is_multiline
1523
+ h_pointer = history.index { |h|
1524
+ h.split("\n").each_with_index { |l, i|
1525
+ if l.start_with?(substr)
1526
+ line_no = i
1527
+ break
1528
+ end
1529
+ }
1530
+ not line_no.nil?
1531
+ }
1532
+ else
1533
+ h_pointer = history.index { |l|
1534
+ l.start_with?(substr)
1535
+ }
1536
+ end
1537
+ h_pointer += @history_pointer + 1 if h_pointer and @history_pointer
1538
+ return if h_pointer.nil? and not substr.empty?
1539
+ @history_pointer = h_pointer
1540
+ if @is_multiline
1541
+ if @history_pointer.nil? and substr.empty?
1542
+ @buffer_of_lines = []
1543
+ @line_index = 0
1544
+ else
1545
+ @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1546
+ @line_index = line_no
1547
+ end
1548
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1549
+ @line = @buffer_of_lines.last
1550
+ @rerender_all = true
1551
+ else
1552
+ if @history_pointer.nil? and substr.empty?
1553
+ @line = ''
1554
+ else
1555
+ @line = Reline::HISTORY[@history_pointer]
1556
+ end
1557
+ end
1558
+ @cursor_max = calculate_width(@line)
1559
+ arg -= 1
1560
+ ed_search_next_history(key, arg: arg) if arg > 0
1561
+ end
1562
+ alias_method :history_search_forward, :ed_search_next_history
1423
1563
 
1424
1564
  private def ed_prev_history(key, arg: 1)
1425
1565
  if @is_multiline and @line_index > 0
@@ -1519,6 +1659,7 @@ class Reline::LineEditor
1519
1659
  end
1520
1660
 
1521
1661
  private def ed_newline(key)
1662
+ process_insert(force: true)
1522
1663
  if @is_multiline
1523
1664
  if @config.editing_mode_is?(:vi_command)
1524
1665
  if @line_index < (@buffer_of_lines.size - 1)
@@ -1816,7 +1957,7 @@ class Reline::LineEditor
1816
1957
  ed_prev_char(key)
1817
1958
  @config.editing_mode = :vi_command
1818
1959
  end
1819
- alias_method :backward_char, :ed_prev_char
1960
+ alias_method :vi_movement_mode, :vi_command_mode
1820
1961
 
1821
1962
  private def vi_next_word(key, arg: 1)
1822
1963
  if @line.bytesize > @byte_pointer
@@ -1838,13 +1979,22 @@ class Reline::LineEditor
1838
1979
  vi_prev_word(key, arg: arg) if arg > 0
1839
1980
  end
1840
1981
 
1841
- private def vi_end_word(key, arg: 1)
1982
+ private def vi_end_word(key, arg: 1, inclusive: false)
1842
1983
  if @line.bytesize > @byte_pointer
1843
1984
  byte_size, width = Reline::Unicode.vi_forward_end_word(@line, @byte_pointer)
1844
1985
  @byte_pointer += byte_size
1845
1986
  @cursor += width
1846
1987
  end
1847
1988
  arg -= 1
1989
+ if inclusive and arg.zero?
1990
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
1991
+ if byte_size > 0
1992
+ c = @line.byteslice(@byte_pointer, byte_size)
1993
+ width = Reline::Unicode.get_mbchar_width(c)
1994
+ @byte_pointer += byte_size
1995
+ @cursor += width
1996
+ end
1997
+ end
1848
1998
  vi_end_word(key, arg: arg) if arg > 0
1849
1999
  end
1850
2000
 
@@ -1868,13 +2018,22 @@ class Reline::LineEditor
1868
2018
  vi_prev_big_word(key, arg: arg) if arg > 0
1869
2019
  end
1870
2020
 
1871
- private def vi_end_big_word(key, arg: 1)
2021
+ private def vi_end_big_word(key, arg: 1, inclusive: false)
1872
2022
  if @line.bytesize > @byte_pointer
1873
2023
  byte_size, width = Reline::Unicode.vi_big_forward_end_word(@line, @byte_pointer)
1874
2024
  @byte_pointer += byte_size
1875
2025
  @cursor += width
1876
2026
  end
1877
2027
  arg -= 1
2028
+ if inclusive and arg.zero?
2029
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2030
+ if byte_size > 0
2031
+ c = @line.byteslice(@byte_pointer, byte_size)
2032
+ width = Reline::Unicode.get_mbchar_width(c)
2033
+ @byte_pointer += byte_size
2034
+ @cursor += width
2035
+ end
2036
+ end
1878
2037
  vi_end_big_word(key, arg: arg) if arg > 0
1879
2038
  end
1880
2039
 
@@ -1959,6 +2118,14 @@ class Reline::LineEditor
1959
2118
  end
1960
2119
 
1961
2120
  private def vi_yank(key)
2121
+ @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2122
+ if byte_pointer_diff > 0
2123
+ cut = @line.byteslice(@byte_pointer, byte_pointer_diff)
2124
+ elsif byte_pointer_diff < 0
2125
+ cut = @line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2126
+ end
2127
+ copy_for_vi(cut)
2128
+ }
1962
2129
  end
1963
2130
 
1964
2131
  private def vi_list_or_eof(key)
@@ -2022,7 +2189,7 @@ class Reline::LineEditor
2022
2189
  fp.path
2023
2190
  }
2024
2191
  system("#{ENV['EDITOR']} #{path}")
2025
- @line = Pathname.new(path).read
2192
+ @line = File.read(path)
2026
2193
  finish
2027
2194
  end
2028
2195
 
@@ -2103,15 +2270,15 @@ class Reline::LineEditor
2103
2270
  }
2104
2271
  end
2105
2272
 
2106
- private def vi_next_char(key, arg: 1)
2107
- @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg) }
2273
+ private def vi_next_char(key, arg: 1, inclusive: false)
2274
+ @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, inclusive: inclusive) }
2108
2275
  end
2109
2276
 
2110
- private def vi_to_next_char(key, arg: 1)
2111
- @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, true) }
2277
+ private def vi_to_next_char(key, arg: 1, inclusive: false)
2278
+ @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, need_prev_char: true, inclusive: inclusive) }
2112
2279
  end
2113
2280
 
2114
- private def search_next_char(key, arg, need_prev_char = false)
2281
+ private def search_next_char(key, arg, need_prev_char: false, inclusive: false)
2115
2282
  if key.instance_of?(String)
2116
2283
  inputed_char = key
2117
2284
  else
@@ -2148,6 +2315,15 @@ class Reline::LineEditor
2148
2315
  @byte_pointer += byte_size
2149
2316
  @cursor += width
2150
2317
  end
2318
+ if inclusive
2319
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2320
+ if byte_size > 0
2321
+ c = @line.byteslice(@byte_pointer, byte_size)
2322
+ width = Reline::Unicode.get_mbchar_width(c)
2323
+ @byte_pointer += byte_size
2324
+ @cursor += width
2325
+ end
2326
+ end
2151
2327
  @waiting_proc = nil
2152
2328
  end
2153
2329
 
@@ -2219,10 +2395,10 @@ class Reline::LineEditor
2219
2395
  alias_method :set_mark, :em_set_mark
2220
2396
 
2221
2397
  private def em_exchange_mark(key)
2398
+ return unless @mark_pointer
2222
2399
  new_pointer = [@byte_pointer, @line_index]
2223
2400
  @previous_line_index = @line_index
2224
2401
  @byte_pointer, @line_index = @mark_pointer
2225
- @byte_pointer, @line_index = @mark_pointer
2226
2402
  @cursor = calculate_width(@line.byteslice(0, @byte_pointer))
2227
2403
  @cursor_max = calculate_width(@line)
2228
2404
  @mark_pointer = new_pointer