reline 0.1.2 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,14 @@
1
1
  require 'timeout'
2
2
 
3
3
  class Reline::GeneralIO
4
+ def self.encoding
5
+ RUBY_PLATFORM =~ /mswin|mingw/ ? Encoding::UTF_8 : Encoding::default_external
6
+ end
7
+
8
+ def self.win?
9
+ false
10
+ end
11
+
4
12
  RAW_KEYSTROKE_CONFIG = {}
5
13
 
6
14
  @@buf = []
@@ -59,6 +67,10 @@ class Reline::GeneralIO
59
67
  def self.set_winch_handler(&handler)
60
68
  end
61
69
 
70
+ def self.in_pasting?
71
+ false
72
+ end
73
+
62
74
  def self.prep
63
75
  end
64
76
 
@@ -19,7 +19,7 @@ class Reline::History < Array
19
19
 
20
20
  def []=(index, val)
21
21
  index = check_index(index)
22
- super(index, String.new(val, encoding: Encoding::default_external))
22
+ super(index, String.new(val, encoding: Reline.encoding_system_needs))
23
23
  end
24
24
 
25
25
  def concat(*val)
@@ -29,27 +29,47 @@ class Reline::History < Array
29
29
  end
30
30
 
31
31
  def push(*val)
32
- diff = size + val.size - @config.history_size
33
- if diff > 0
34
- if diff <= size
35
- shift(diff)
36
- else
37
- diff -= size
38
- clear
39
- val.shift(diff)
32
+ # If history_size is zero, all histories are dropped.
33
+ return self if @config.history_size.zero?
34
+ # If history_size is negative, history size is unlimited.
35
+ if @config.history_size.positive?
36
+ diff = size + val.size - @config.history_size
37
+ if diff > 0
38
+ if diff <= size
39
+ shift(diff)
40
+ else
41
+ diff -= size
42
+ clear
43
+ val.shift(diff)
44
+ end
40
45
  end
41
46
  end
42
- super(*(val.map{ |v| String.new(v, encoding: Encoding::default_external) }))
47
+ super(*(val.map{ |v|
48
+ String.new(v, encoding: Reline.encoding_system_needs)
49
+ }))
43
50
  end
44
51
 
45
52
  def <<(val)
46
- shift if size + 1 > @config.history_size
47
- super(String.new(val, encoding: Encoding::default_external))
53
+ # If history_size is zero, all histories are dropped.
54
+ return self if @config.history_size.zero?
55
+ # If history_size is negative, history size is unlimited.
56
+ if @config.history_size.positive?
57
+ shift if size + 1 > @config.history_size
58
+ end
59
+ super(String.new(val, encoding: Reline.encoding_system_needs))
48
60
  end
49
61
 
50
62
  private def check_index(index)
51
63
  index += size if index < 0
52
- raise RangeError.new("index=<#{index}>") if index < -@config.history_size or @config.history_size < index
64
+ if index < -2147483648 or 2147483647 < index
65
+ raise RangeError.new("integer #{index} too big to convert to `int'")
66
+ end
67
+ # If history_size is negative, history size is unlimited.
68
+ if @config.history_size.positive?
69
+ if index < -@config.history_size or @config.history_size < index
70
+ raise RangeError.new("index=<#{index}>")
71
+ end
72
+ end
53
73
  raise IndexError.new("index=<#{index}>") if index < 0 or size <= index
54
74
  index
55
75
  end
@@ -37,9 +37,9 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
37
37
  # 17 ^Q
38
38
  :ed_quoted_insert,
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_transpose_chars,
45
45
  # 21 ^U
@@ -413,11 +413,11 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
413
413
  # 205 M-M
414
414
  :ed_unassigned,
415
415
  # 206 M-N
416
- :ed_search_next_history,
416
+ :vi_search_next,
417
417
  # 207 M-O
418
418
  :ed_sequence_lead_in,
419
419
  # 208 M-P
420
- :ed_search_prev_history,
420
+ :vi_search_prev,
421
421
  # 209 M-Q
422
422
  :ed_unassigned,
423
423
  # 210 M-R
@@ -477,11 +477,11 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
477
477
  # 237 M-m
478
478
  :ed_unassigned,
479
479
  # 238 M-n
480
- :ed_search_next_history,
480
+ :vi_search_next,
481
481
  # 239 M-o
482
482
  :ed_unassigned,
483
483
  # 240 M-p
484
- :ed_search_prev_history,
484
+ :vi_search_prev,
485
485
  # 241 M-q
486
486
  :ed_unassigned,
487
487
  # 242 M-r
@@ -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
@@ -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,16 +50,18 @@ 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
- def initialize(config)
53
+ def initialize(config, encoding)
61
54
  @config = config
62
55
  @completion_append_character = ''
63
- reset_variables
56
+ reset_variables(encoding: encoding)
57
+ end
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
64
65
  end
65
66
 
66
67
  private def check_multiline_prompt(buffer, prompt)
@@ -73,22 +74,47 @@ 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
87
113
 
88
- def reset(prompt = '', encoding = Encoding.default_external)
114
+ def reset(prompt = '', encoding:)
89
115
  @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
90
116
  @screen_size = Reline::IOGate.get_screen_size
91
- reset_variables(prompt, encoding)
117
+ reset_variables(prompt, encoding: encoding)
92
118
  @old_trap = Signal.trap('SIGINT') {
93
119
  @old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
94
120
  raise Interrupt
@@ -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]
@@ -139,7 +165,7 @@ class Reline::LineEditor
139
165
  @eof
140
166
  end
141
167
 
142
- def reset_variables(prompt = '', encoding = Encoding.default_external)
168
+ def reset_variables(prompt = '', encoding:)
143
169
  @prompt = prompt
144
170
  @mark_pointer = nil
145
171
  @encoding = encoding
@@ -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,14 +307,19 @@ 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
318
318
  scroll_down(@highest_in_all - @first_line_started_from)
319
319
  @rerender_all = true
320
- @menu_info.list.each do |item|
320
+ @menu_info.list.sort!.each do |item|
321
321
  Reline::IOGate.move_cursor_column(0)
322
- @output.print item
322
+ @output.write item
323
323
  @output.flush
324
324
  scroll_down(1)
325
325
  end
@@ -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,12 +507,30 @@ 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
- Reline::IOGate.erase_after_cursor
511
- move_cursor_down(1)
512
- Reline::IOGate.move_cursor_column(0)
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
522
+ else
523
+ Reline::IOGate.erase_after_cursor
524
+ move_cursor_down(1)
525
+ Reline::IOGate.move_cursor_column(0)
526
+ end
513
527
  next
514
528
  end
515
- @output.print line
529
+ @output.write line
530
+ if Reline::IOGate.win? and calculate_width(line, true) == Reline::IOGate.get_screen_size.last
531
+ # A newline is automatically inserted if a character is rendered at eol on command prompt.
532
+ @rest_height -= 1 if @rest_height > 0
533
+ end
516
534
  @output.flush
517
535
  if @first_prompt
518
536
  @first_prompt = false
@@ -520,22 +538,25 @@ class Reline::LineEditor
520
538
  end
521
539
  end
522
540
  Reline::IOGate.erase_after_cursor
541
+ Reline::IOGate.move_cursor_column(0)
523
542
  if with_control
524
- move_cursor_up(height - 1)
543
+ # Just after rendring, so the cursor is on the last line.
525
544
  if finished?
526
- 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)
527
550
  end
528
- move_cursor_down(@started_from)
529
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
530
551
  end
531
552
  height
532
553
  end
533
554
 
534
555
  private def modify_lines(before)
535
- return before if before.nil? || before.empty?
556
+ return before if before.nil? || before.empty? || simplified_rendering?
536
557
 
537
558
  if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
538
- after.lines(chomp: true)
559
+ after.lines("\n").map { |l| l.chomp('') }
539
560
  else
540
561
  before
541
562
  end
@@ -560,7 +581,7 @@ class Reline::LineEditor
560
581
  else
561
582
  i&.start_with?(target)
562
583
  end
563
- }
584
+ }.uniq
564
585
  if is_menu
565
586
  menu(target, list)
566
587
  return nil
@@ -708,6 +729,18 @@ class Reline::LineEditor
708
729
  method_obj and method_obj.parameters.length != 1
709
730
  end
710
731
 
732
+ def wrap_method_call(method_symbol, method_obj, key)
733
+ if @config.editing_mode_is?(:emacs, :vi_insert) and @waiting_proc.nil? and @waiting_operator_proc.nil?
734
+ not_insertion = method_symbol != :ed_insert
735
+ process_insert(force: not_insertion)
736
+ end
737
+ if @vi_arg
738
+ method_obj.(key, arg: @vi_arg)
739
+ else
740
+ method_obj.(key)
741
+ end
742
+ end
743
+
711
744
  private def process_key(key, method_symbol)
712
745
  if method_symbol and respond_to?(method_symbol, true)
713
746
  method_obj = method(method_symbol)
@@ -717,10 +750,10 @@ class Reline::LineEditor
717
750
  if method_symbol and key.is_a?(Symbol)
718
751
  if @vi_arg and argumentable?(method_obj)
719
752
  run_for_operators(key, method_symbol) do
720
- method_obj.(key, arg: @vi_arg)
753
+ wrap_method_call(method_symbol, method_obj, key)
721
754
  end
722
755
  else
723
- method_obj&.(key)
756
+ wrap_method_call(method_symbol, method_obj, key) if method_obj
724
757
  end
725
758
  @kill_ring.process
726
759
  @vi_arg = nil
@@ -730,12 +763,12 @@ class Reline::LineEditor
730
763
  else
731
764
  if argumentable?(method_obj)
732
765
  run_for_operators(key, method_symbol) do
733
- method_obj.(key, arg: @vi_arg)
766
+ wrap_method_call(method_symbol, method_obj, key)
734
767
  end
735
768
  elsif @waiting_proc
736
769
  @waiting_proc.(key)
737
770
  elsif method_obj
738
- method_obj.(key)
771
+ wrap_method_call(method_symbol, method_obj, key)
739
772
  else
740
773
  ed_insert(key)
741
774
  end
@@ -747,10 +780,10 @@ class Reline::LineEditor
747
780
  @kill_ring.process
748
781
  elsif method_obj
749
782
  if method_symbol == :ed_argument_digit
750
- method_obj.(key)
783
+ wrap_method_call(method_symbol, method_obj, key)
751
784
  else
752
785
  run_for_operators(key, method_symbol) do
753
- method_obj.(key)
786
+ wrap_method_call(method_symbol, method_obj, key)
754
787
  end
755
788
  end
756
789
  @kill_ring.process
@@ -812,6 +845,7 @@ class Reline::LineEditor
812
845
  result = call_completion_proc
813
846
  if result.is_a?(Array)
814
847
  completion_occurs = true
848
+ process_insert
815
849
  complete(result)
816
850
  end
817
851
  end
@@ -820,6 +854,7 @@ class Reline::LineEditor
820
854
  result = call_completion_proc
821
855
  if result.is_a?(Array)
822
856
  completion_occurs = true
857
+ process_insert
823
858
  move_completed_list(result, "\C-p".ord == key.char ? :up : :down)
824
859
  end
825
860
  end
@@ -831,7 +866,7 @@ class Reline::LineEditor
831
866
  unless completion_occurs
832
867
  @completion_state = CompletionState::NORMAL
833
868
  end
834
- if @is_multiline and @auto_indent_proc
869
+ if @is_multiline and @auto_indent_proc and not simplified_rendering?
835
870
  process_auto_indent
836
871
  end
837
872
  end
@@ -905,7 +940,6 @@ class Reline::LineEditor
905
940
  quote = nil
906
941
  i += 1
907
942
  rest = nil
908
- break_pointer = nil
909
943
  elsif quote and slice.start_with?(escaped_quote)
910
944
  # skip
911
945
  i += 2
@@ -915,7 +949,7 @@ class Reline::LineEditor
915
949
  closing_quote = /(?!\\)#{Regexp.escape(quote)}/
916
950
  escaped_quote = /\\#{Regexp.escape(quote)}/
917
951
  i += 1
918
- break_pointer = i
952
+ break_pointer = i - 1
919
953
  elsif not quote and slice =~ word_break_regexp
920
954
  rest = $'
921
955
  i += 1
@@ -937,6 +971,11 @@ class Reline::LineEditor
937
971
  end
938
972
  else
939
973
  preposing = ''
974
+ if break_pointer
975
+ preposing = @line.byteslice(0, break_pointer)
976
+ else
977
+ preposing = ''
978
+ end
940
979
  target = before
941
980
  end
942
981
  [preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
@@ -1029,6 +1068,7 @@ class Reline::LineEditor
1029
1068
 
1030
1069
  def finish
1031
1070
  @finished = true
1071
+ @rerender_all = true
1032
1072
  @config.reset
1033
1073
  end
1034
1074
 
@@ -1046,29 +1086,7 @@ class Reline::LineEditor
1046
1086
  end
1047
1087
 
1048
1088
  private def calculate_width(str, allow_escape_code = false)
1049
- if allow_escape_code
1050
- width = 0
1051
- rest = str.encode(Encoding::UTF_8)
1052
- in_zero_width = false
1053
- rest.scan(WIDTH_SCANNER) do |gc|
1054
- case gc
1055
- when NON_PRINTING_START
1056
- in_zero_width = true
1057
- when NON_PRINTING_END
1058
- in_zero_width = false
1059
- when CSI_REGEXP, OSC_REGEXP
1060
- else
1061
- unless in_zero_width
1062
- width += Reline::Unicode.get_mbchar_width(gc)
1063
- end
1064
- end
1065
- end
1066
- width
1067
- else
1068
- str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |w, gc|
1069
- w + Reline::Unicode.get_mbchar_width(gc)
1070
- }
1071
- end
1089
+ Reline::Unicode.calculate_width(str, allow_escape_code)
1072
1090
  end
1073
1091
 
1074
1092
  private def key_delete(key)
@@ -1089,28 +1107,55 @@ class Reline::LineEditor
1089
1107
 
1090
1108
  private def ed_unassigned(key) end # do nothing
1091
1109
 
1110
+ private def process_insert(force: false)
1111
+ return if @continuous_insertion_buffer.empty? or (Reline::IOGate.in_pasting? and not force)
1112
+ width = Reline::Unicode.calculate_width(@continuous_insertion_buffer)
1113
+ bytesize = @continuous_insertion_buffer.bytesize
1114
+ if @cursor == @cursor_max
1115
+ @line += @continuous_insertion_buffer
1116
+ else
1117
+ @line = byteinsert(@line, @byte_pointer, @continuous_insertion_buffer)
1118
+ end
1119
+ @byte_pointer += bytesize
1120
+ @cursor += width
1121
+ @cursor_max += width
1122
+ @continuous_insertion_buffer.clear
1123
+ end
1124
+
1092
1125
  private def ed_insert(key)
1126
+ str = nil
1127
+ width = nil
1128
+ bytesize = nil
1093
1129
  if key.instance_of?(String)
1094
- width = Reline::Unicode.get_mbchar_width(key)
1095
- if @cursor == @cursor_max
1096
- @line += key
1097
- else
1098
- @line = byteinsert(@line, @byte_pointer, key)
1130
+ begin
1131
+ key.encode(Encoding::UTF_8)
1132
+ rescue Encoding::UndefinedConversionError
1133
+ return
1099
1134
  end
1100
- @byte_pointer += key.bytesize
1101
- @cursor += width
1102
- @cursor_max += width
1135
+ str = key
1136
+ bytesize = key.bytesize
1103
1137
  else
1104
- if @cursor == @cursor_max
1105
- @line += key.chr
1106
- else
1107
- @line = byteinsert(@line, @byte_pointer, key.chr)
1138
+ begin
1139
+ key.chr.encode(Encoding::UTF_8)
1140
+ rescue Encoding::UndefinedConversionError
1141
+ return
1108
1142
  end
1109
- width = Reline::Unicode.get_mbchar_width(key.chr)
1110
- @byte_pointer += 1
1111
- @cursor += width
1112
- @cursor_max += width
1143
+ str = key.chr
1144
+ bytesize = 1
1113
1145
  end
1146
+ if Reline::IOGate.in_pasting?
1147
+ @continuous_insertion_buffer << str
1148
+ return
1149
+ end
1150
+ width = Reline::Unicode.get_mbchar_width(str)
1151
+ if @cursor == @cursor_max
1152
+ @line += str
1153
+ else
1154
+ @line = byteinsert(@line, @byte_pointer, str)
1155
+ end
1156
+ @byte_pointer += bytesize
1157
+ @cursor += width
1158
+ @cursor_max += width
1114
1159
  end
1115
1160
  alias_method :ed_digit, :ed_insert
1116
1161
  alias_method :self_insert, :ed_insert
@@ -1236,7 +1281,7 @@ class Reline::LineEditor
1236
1281
  if search_word.empty? and Reline.last_incremental_search
1237
1282
  search_word = Reline.last_incremental_search
1238
1283
  end
1239
- if @history_pointer # TODO
1284
+ if @history_pointer
1240
1285
  case prev_search_key
1241
1286
  when "\C-r".ord
1242
1287
  history_pointer_base = 0
@@ -1308,7 +1353,7 @@ class Reline::LineEditor
1308
1353
  end
1309
1354
  end
1310
1355
 
1311
- private def search_history(key)
1356
+ private def incremental_search_history(key)
1312
1357
  unless @history_pointer
1313
1358
  if @is_multiline
1314
1359
  @line_backup_in_history = whole_buffer
@@ -1389,15 +1434,114 @@ class Reline::LineEditor
1389
1434
  }
1390
1435
  end
1391
1436
 
1392
- private def ed_search_prev_history(key)
1393
- search_history(key)
1437
+ private def vi_search_prev(key)
1438
+ incremental_search_history(key)
1439
+ end
1440
+ alias_method :reverse_search_history, :vi_search_prev
1441
+
1442
+ private def vi_search_next(key)
1443
+ incremental_search_history(key)
1444
+ end
1445
+ alias_method :forward_search_history, :vi_search_next
1446
+
1447
+ private def ed_search_prev_history(key, arg: 1)
1448
+ history = nil
1449
+ h_pointer = nil
1450
+ line_no = nil
1451
+ substr = @line.slice(0, @byte_pointer)
1452
+ if @history_pointer.nil?
1453
+ return if not @line.empty? and substr.empty?
1454
+ history = Reline::HISTORY
1455
+ elsif @history_pointer.zero?
1456
+ history = nil
1457
+ h_pointer = nil
1458
+ else
1459
+ history = Reline::HISTORY.slice(0, @history_pointer)
1460
+ end
1461
+ return if history.nil?
1462
+ if @is_multiline
1463
+ h_pointer = history.rindex { |h|
1464
+ h.split("\n").each_with_index { |l, i|
1465
+ if l.start_with?(substr)
1466
+ line_no = i
1467
+ break
1468
+ end
1469
+ }
1470
+ not line_no.nil?
1471
+ }
1472
+ else
1473
+ h_pointer = history.rindex { |l|
1474
+ l.start_with?(substr)
1475
+ }
1476
+ end
1477
+ return if h_pointer.nil?
1478
+ @history_pointer = h_pointer
1479
+ if @is_multiline
1480
+ @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1481
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1482
+ @line_index = line_no
1483
+ @line = @buffer_of_lines.last
1484
+ @rerender_all = true
1485
+ else
1486
+ @line = Reline::HISTORY[@history_pointer]
1487
+ end
1488
+ @cursor_max = calculate_width(@line)
1489
+ arg -= 1
1490
+ ed_search_prev_history(key, arg: arg) if arg > 0
1394
1491
  end
1395
- alias_method :reverse_search_history, :ed_search_prev_history
1492
+ alias_method :history_search_backward, :ed_search_prev_history
1396
1493
 
1397
- private def ed_search_next_history(key)
1398
- search_history(key)
1494
+ private def ed_search_next_history(key, arg: 1)
1495
+ substr = @line.slice(0, @byte_pointer)
1496
+ if @history_pointer.nil?
1497
+ return
1498
+ elsif @history_pointer == (Reline::HISTORY.size - 1) and not substr.empty?
1499
+ return
1500
+ end
1501
+ history = Reline::HISTORY.slice((@history_pointer + 1)..-1)
1502
+ h_pointer = nil
1503
+ line_no = nil
1504
+ if @is_multiline
1505
+ h_pointer = history.index { |h|
1506
+ h.split("\n").each_with_index { |l, i|
1507
+ if l.start_with?(substr)
1508
+ line_no = i
1509
+ break
1510
+ end
1511
+ }
1512
+ not line_no.nil?
1513
+ }
1514
+ else
1515
+ h_pointer = history.index { |l|
1516
+ l.start_with?(substr)
1517
+ }
1518
+ end
1519
+ h_pointer += @history_pointer + 1 if h_pointer and @history_pointer
1520
+ return if h_pointer.nil? and not substr.empty?
1521
+ @history_pointer = h_pointer
1522
+ if @is_multiline
1523
+ if @history_pointer.nil? and substr.empty?
1524
+ @buffer_of_lines = []
1525
+ @line_index = 0
1526
+ else
1527
+ @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1528
+ @line_index = line_no
1529
+ end
1530
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1531
+ @line = @buffer_of_lines.last
1532
+ @rerender_all = true
1533
+ else
1534
+ if @history_pointer.nil? and substr.empty?
1535
+ @line = ''
1536
+ else
1537
+ @line = Reline::HISTORY[@history_pointer]
1538
+ end
1539
+ end
1540
+ @cursor_max = calculate_width(@line)
1541
+ arg -= 1
1542
+ ed_search_next_history(key, arg: arg) if arg > 0
1399
1543
  end
1400
- alias_method :forward_search_history, :ed_search_next_history
1544
+ alias_method :history_search_forward, :ed_search_next_history
1401
1545
 
1402
1546
  private def ed_prev_history(key, arg: 1)
1403
1547
  if @is_multiline and @line_index > 0
@@ -1497,6 +1641,7 @@ class Reline::LineEditor
1497
1641
  end
1498
1642
 
1499
1643
  private def ed_newline(key)
1644
+ process_insert(force: true)
1500
1645
  if @is_multiline
1501
1646
  if @config.editing_mode_is?(:vi_command)
1502
1647
  if @line_index < (@buffer_of_lines.size - 1)
@@ -1876,6 +2021,16 @@ class Reline::LineEditor
1876
2021
  end
1877
2022
  end
1878
2023
 
2024
+ private def vi_insert_at_bol(key)
2025
+ ed_move_to_beg(key)
2026
+ @config.editing_mode = :vi_insert
2027
+ end
2028
+
2029
+ private def vi_add_at_eol(key)
2030
+ ed_move_to_end(key)
2031
+ @config.editing_mode = :vi_insert
2032
+ end
2033
+
1879
2034
  private def ed_delete_prev_char(key, arg: 1)
1880
2035
  deleted = ''
1881
2036
  arg.times do
@@ -1898,6 +2053,18 @@ class Reline::LineEditor
1898
2053
  end
1899
2054
 
1900
2055
  private def vi_change_meta(key)
2056
+ @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2057
+ if byte_pointer_diff > 0
2058
+ @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
2059
+ elsif byte_pointer_diff < 0
2060
+ @line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2061
+ end
2062
+ copy_for_vi(cut)
2063
+ @cursor += cursor_diff if cursor_diff < 0
2064
+ @cursor_max -= cursor_diff.abs
2065
+ @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2066
+ @config.editing_mode = :vi_insert
2067
+ }
1901
2068
  end
1902
2069
 
1903
2070
  private def vi_delete_meta(key)
@@ -1978,7 +2145,7 @@ class Reline::LineEditor
1978
2145
  fp.path
1979
2146
  }
1980
2147
  system("#{ENV['EDITOR']} #{path}")
1981
- @line = Pathname.new(path).read
2148
+ @line = File.read(path)
1982
2149
  finish
1983
2150
  end
1984
2151
 
@@ -2063,12 +2230,17 @@ class Reline::LineEditor
2063
2230
  @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg) }
2064
2231
  end
2065
2232
 
2066
- private def search_next_char(key, arg)
2233
+ private def vi_to_next_char(key, arg: 1)
2234
+ @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, true) }
2235
+ end
2236
+
2237
+ private def search_next_char(key, arg, need_prev_char = false)
2067
2238
  if key.instance_of?(String)
2068
2239
  inputed_char = key
2069
2240
  else
2070
2241
  inputed_char = key.chr
2071
2242
  end
2243
+ prev_total = nil
2072
2244
  total = nil
2073
2245
  found = false
2074
2246
  @line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
@@ -2086,13 +2258,66 @@ class Reline::LineEditor
2086
2258
  end
2087
2259
  end
2088
2260
  width = Reline::Unicode.get_mbchar_width(mbchar)
2261
+ prev_total = total
2089
2262
  total = [total.first + mbchar.bytesize, total.last + width]
2090
2263
  end
2091
2264
  end
2092
- if found and total
2265
+ if not need_prev_char and found and total
2093
2266
  byte_size, width = total
2094
2267
  @byte_pointer += byte_size
2095
2268
  @cursor += width
2269
+ elsif need_prev_char and found and prev_total
2270
+ byte_size, width = prev_total
2271
+ @byte_pointer += byte_size
2272
+ @cursor += width
2273
+ end
2274
+ @waiting_proc = nil
2275
+ end
2276
+
2277
+ private def vi_prev_char(key, arg: 1)
2278
+ @waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg) }
2279
+ end
2280
+
2281
+ private def vi_to_prev_char(key, arg: 1)
2282
+ @waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg, true) }
2283
+ end
2284
+
2285
+ private def search_prev_char(key, arg, need_next_char = false)
2286
+ if key.instance_of?(String)
2287
+ inputed_char = key
2288
+ else
2289
+ inputed_char = key.chr
2290
+ end
2291
+ prev_total = nil
2292
+ total = nil
2293
+ found = false
2294
+ @line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar|
2295
+ # total has [byte_size, cursor]
2296
+ unless total
2297
+ # skip cursor point
2298
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2299
+ total = [mbchar.bytesize, width]
2300
+ else
2301
+ if inputed_char == mbchar
2302
+ arg -= 1
2303
+ if arg.zero?
2304
+ found = true
2305
+ break
2306
+ end
2307
+ end
2308
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2309
+ prev_total = total
2310
+ total = [total.first + mbchar.bytesize, total.last + width]
2311
+ end
2312
+ end
2313
+ if not need_next_char and found and total
2314
+ byte_size, width = total
2315
+ @byte_pointer -= byte_size
2316
+ @cursor -= width
2317
+ elsif need_next_char and found and prev_total
2318
+ byte_size, width = prev_total
2319
+ @byte_pointer -= byte_size
2320
+ @cursor -= width
2096
2321
  end
2097
2322
  @waiting_proc = nil
2098
2323
  end
@@ -2120,7 +2345,6 @@ class Reline::LineEditor
2120
2345
  new_pointer = [@byte_pointer, @line_index]
2121
2346
  @previous_line_index = @line_index
2122
2347
  @byte_pointer, @line_index = @mark_pointer
2123
- @byte_pointer, @line_index = @mark_pointer
2124
2348
  @cursor = calculate_width(@line.byteslice(0, @byte_pointer))
2125
2349
  @cursor_max = calculate_width(@line)
2126
2350
  @mark_pointer = new_pointer