reline 0.1.2 → 0.1.7

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.
@@ -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