reline 0.2.8.pre.9 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,6 +5,7 @@ require 'tempfile'
5
5
 
6
6
  class Reline::LineEditor
7
7
  # TODO: undo
8
+ # TODO: Use "private alias_method" idiom after drop Ruby 2.5.
8
9
  attr_reader :line
9
10
  attr_reader :byte_pointer
10
11
  attr_accessor :confirm_multiline_termination_proc
@@ -92,7 +93,7 @@ class Reline::LineEditor
92
93
  mode_string
93
94
  end
94
95
 
95
- private def check_multiline_prompt(buffer, prompt)
96
+ private def check_multiline_prompt(buffer)
96
97
  if @vi_arg
97
98
  prompt = "(arg: #{@vi_arg}) "
98
99
  @rerender_all = true
@@ -120,7 +121,7 @@ class Reline::LineEditor
120
121
  if use_cached_prompt_list
121
122
  prompt_list = @cached_prompt_list
122
123
  else
123
- prompt_list = @cached_prompt_list = @prompt_proc.(buffer)
124
+ prompt_list = @cached_prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") }
124
125
  @prompt_cache_time = Time.now.to_f
125
126
  end
126
127
  prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
@@ -150,6 +151,73 @@ class Reline::LineEditor
150
151
  @screen_size = Reline::IOGate.get_screen_size
151
152
  @screen_height = @screen_size.first
152
153
  reset_variables(prompt, encoding: encoding)
154
+ Reline::IOGate.set_winch_handler do
155
+ @resized = true
156
+ end
157
+ if ENV.key?('RELINE_ALT_SCROLLBAR')
158
+ @full_block = '::'
159
+ @upper_half_block = "''"
160
+ @lower_half_block = '..'
161
+ @block_elem_width = 2
162
+ elsif Reline::IOGate.win?
163
+ @full_block = '█'
164
+ @upper_half_block = '▀'
165
+ @lower_half_block = '▄'
166
+ @block_elem_width = 1
167
+ elsif @encoding == Encoding::UTF_8
168
+ @full_block = '█'
169
+ @upper_half_block = '▀'
170
+ @lower_half_block = '▄'
171
+ @block_elem_width = Reline::Unicode.calculate_width('█')
172
+ else
173
+ @full_block = '::'
174
+ @upper_half_block = "''"
175
+ @lower_half_block = '..'
176
+ @block_elem_width = 2
177
+ end
178
+ end
179
+
180
+ def resize
181
+ return unless @resized
182
+ @resized = false
183
+ @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
184
+ old_screen_size = @screen_size
185
+ @screen_size = Reline::IOGate.get_screen_size
186
+ @screen_height = @screen_size.first
187
+ if old_screen_size.last < @screen_size.last # columns increase
188
+ @rerender_all = true
189
+ rerender
190
+ else
191
+ back = 0
192
+ new_buffer = whole_lines
193
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer)
194
+ new_buffer.each_with_index do |line, index|
195
+ prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
196
+ width = prompt_width + calculate_width(line)
197
+ height = calculate_height_by_width(width)
198
+ back += height
199
+ end
200
+ @highest_in_all = back
201
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
202
+ @first_line_started_from =
203
+ if @line_index.zero?
204
+ 0
205
+ else
206
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
207
+ end
208
+ if @prompt_proc
209
+ prompt = prompt_list[@line_index]
210
+ prompt_width = calculate_width(prompt, true)
211
+ end
212
+ calculate_nearest_cursor
213
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
214
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
215
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
216
+ @rerender_all = true
217
+ end
218
+ end
219
+
220
+ def set_signal_handlers
153
221
  @old_trap = Signal.trap('INT') {
154
222
  clear_dialog
155
223
  if @scroll_partial_screen
@@ -167,7 +235,7 @@ class Reline::LineEditor
167
235
  when 'EXIT'
168
236
  exit
169
237
  else
170
- @old_trap.call
238
+ @old_trap.call if @old_trap.respond_to?(:call)
171
239
  end
172
240
  }
173
241
  begin
@@ -177,44 +245,6 @@ class Reline::LineEditor
177
245
  }
178
246
  rescue ArgumentError
179
247
  end
180
- Reline::IOGate.set_winch_handler do
181
- @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
182
- old_screen_size = @screen_size
183
- @screen_size = Reline::IOGate.get_screen_size
184
- @screen_height = @screen_size.first
185
- if old_screen_size.last < @screen_size.last # columns increase
186
- @rerender_all = true
187
- rerender
188
- else
189
- back = 0
190
- new_buffer = whole_lines
191
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
192
- new_buffer.each_with_index do |line, index|
193
- prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
194
- width = prompt_width + calculate_width(line)
195
- height = calculate_height_by_width(width)
196
- back += height
197
- end
198
- @highest_in_all = back
199
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
200
- @first_line_started_from =
201
- if @line_index.zero?
202
- 0
203
- else
204
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
205
- end
206
- if @prompt_proc
207
- prompt = prompt_list[@line_index]
208
- prompt_width = calculate_width(prompt, true)
209
- end
210
- calculate_nearest_cursor
211
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
212
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
213
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
214
- @rerender_all = true
215
- end
216
- end
217
- @block_elem_width = Reline::Unicode.calculate_width('█')
218
248
  end
219
249
 
220
250
  def finalize
@@ -230,7 +260,7 @@ class Reline::LineEditor
230
260
  end
231
261
 
232
262
  def reset_variables(prompt = '', encoding:)
233
- @prompt = prompt
263
+ @prompt = prompt.gsub("\n", "\\n")
234
264
  @mark_pointer = nil
235
265
  @encoding = encoding
236
266
  @is_multiline = false
@@ -264,6 +294,7 @@ class Reline::LineEditor
264
294
  @auto_indent_proc = nil
265
295
  @dialogs = []
266
296
  @last_key = nil
297
+ @resized = false
267
298
  reset_line
268
299
  end
269
300
 
@@ -407,7 +438,7 @@ class Reline::LineEditor
407
438
  show_menu
408
439
  @menu_info = nil
409
440
  end
410
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
441
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
411
442
  if @cleared
412
443
  clear_screen_buffer(prompt, prompt_list, prompt_width)
413
444
  @cleared = false
@@ -418,7 +449,7 @@ class Reline::LineEditor
418
449
  Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
419
450
  Reline::IOGate.move_cursor_column(0)
420
451
  @scroll_partial_screen = nil
421
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
452
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
422
453
  if @previous_line_index
423
454
  new_lines = whole_lines(index: @previous_line_index, line: @line)
424
455
  else
@@ -464,7 +495,7 @@ class Reline::LineEditor
464
495
  end
465
496
  line = modify_lines(new_lines)[@line_index]
466
497
  clear_dialog
467
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
498
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
468
499
  render_partial(prompt, prompt_width, line, @first_line_started_from)
469
500
  move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
470
501
  scroll_down(1)
@@ -473,7 +504,7 @@ class Reline::LineEditor
473
504
  else
474
505
  if not rendered and not @in_pasting
475
506
  line = modify_lines(whole_lines)[@line_index]
476
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
507
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
477
508
  render_partial(prompt, prompt_width, line, @first_line_started_from)
478
509
  end
479
510
  render_dialog((prompt_width + @cursor) % @screen_size.last)
@@ -560,7 +591,7 @@ class Reline::LineEditor
560
591
 
561
592
  class Dialog
562
593
  attr_reader :name, :contents, :width
563
- attr_accessor :scroll_top, :scrollbar_pos, :column, :vertical_offset, :lines_backup, :trap_key
594
+ attr_accessor :scroll_top, :scrollbar_pos, :pointer, :column, :vertical_offset, :lines_backup, :trap_key
564
595
 
565
596
  def initialize(name, config, proc_scope)
566
597
  @name = name
@@ -568,6 +599,7 @@ class Reline::LineEditor
568
599
  @proc_scope = proc_scope
569
600
  @width = nil
570
601
  @scroll_top = 0
602
+ @trap_key = nil
571
603
  end
572
604
 
573
605
  def set_cursor_pos(col, row)
@@ -605,11 +637,15 @@ class Reline::LineEditor
605
637
  end
606
638
 
607
639
  def add_dialog_proc(name, p, context = nil)
608
- return if @dialogs.any? { |d| d.name == name }
609
- @dialogs << Dialog.new(name, @config, DialogProcScope.new(self, @config, p, context))
640
+ dialog = Dialog.new(name, @config, DialogProcScope.new(self, @config, p, context))
641
+ if index = @dialogs.find_index { |d| d.name == name }
642
+ @dialogs[index] = dialog
643
+ else
644
+ @dialogs << dialog
645
+ end
610
646
  end
611
647
 
612
- DIALOG_HEIGHT = 20
648
+ DIALOG_DEFAULT_HEIGHT = 20
613
649
  private def render_dialog(cursor_column)
614
650
  @dialogs.each do |dialog|
615
651
  render_each_dialog(dialog, cursor_column)
@@ -622,6 +658,7 @@ class Reline::LineEditor
622
658
 
623
659
  private def render_each_dialog(dialog, cursor_column)
624
660
  if @in_pasting
661
+ clear_each_dialog(dialog)
625
662
  dialog.contents = nil
626
663
  dialog.trap_key = nil
627
664
  return
@@ -643,27 +680,30 @@ class Reline::LineEditor
643
680
  end
644
681
  old_dialog = dialog.clone
645
682
  dialog.contents = dialog_render_info.contents
646
- pointer = dialog_render_info.pointer
683
+ pointer = dialog.pointer
647
684
  if dialog_render_info.width
648
685
  dialog.width = dialog_render_info.width
649
686
  else
650
687
  dialog.width = dialog.contents.map { |l| calculate_width(l, true) }.max
651
688
  end
652
- height = dialog_render_info.height || DIALOG_HEIGHT
689
+ height = dialog_render_info.height || DIALOG_DEFAULT_HEIGHT
653
690
  height = dialog.contents.size if dialog.contents.size < height
654
691
  if dialog.contents.size > height
655
- if dialog_render_info.pointer
656
- if dialog_render_info.pointer < 0
692
+ if dialog.pointer
693
+ if dialog.pointer < 0
657
694
  dialog.scroll_top = 0
658
- elsif (dialog_render_info.pointer - dialog.scroll_top) >= (height - 1)
659
- dialog.scroll_top = dialog_render_info.pointer - (height - 1)
660
- elsif (dialog_render_info.pointer - dialog.scroll_top) < 0
661
- dialog.scroll_top = dialog_render_info.pointer
695
+ elsif (dialog.pointer - dialog.scroll_top) >= (height - 1)
696
+ dialog.scroll_top = dialog.pointer - (height - 1)
697
+ elsif (dialog.pointer - dialog.scroll_top) < 0
698
+ dialog.scroll_top = dialog.pointer
662
699
  end
663
- pointer = dialog_render_info.pointer - dialog.scroll_top
700
+ pointer = dialog.pointer - dialog.scroll_top
664
701
  end
665
702
  dialog.contents = dialog.contents[dialog.scroll_top, height]
666
703
  end
704
+ if dialog.contents and dialog.scroll_top >= dialog.contents.size
705
+ dialog.scroll_top = dialog.contents.size - height
706
+ end
667
707
  if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
668
708
  bar_max_height = height * 2
669
709
  moving_distance = (dialog_render_info.contents.size - height) * 2
@@ -674,25 +714,28 @@ class Reline::LineEditor
674
714
  dialog.scrollbar_pos = nil
675
715
  end
676
716
  upper_space = @first_line_started_from - @started_from
677
- lower_space = @highest_in_all - @first_line_started_from - @started_from - 1
678
717
  dialog.column = dialog_render_info.pos.x
679
- diff = (dialog.column + dialog.width) - (@screen_size.last - 1)
718
+ dialog.width += @block_elem_width if dialog.scrollbar_pos
719
+ diff = (dialog.column + dialog.width) - (@screen_size.last)
680
720
  if diff > 0
681
721
  dialog.column -= diff
682
722
  end
683
- if (lower_space + @rest_height - dialog_render_info.pos.y) >= height
723
+ if (@rest_height - dialog_render_info.pos.y) >= height
684
724
  dialog.vertical_offset = dialog_render_info.pos.y + 1
685
725
  elsif upper_space >= height
686
726
  dialog.vertical_offset = dialog_render_info.pos.y - height
687
727
  else
688
- if (lower_space + @rest_height - dialog_render_info.pos.y) < height
728
+ if (@rest_height - dialog_render_info.pos.y) < height
689
729
  scroll_down(height + dialog_render_info.pos.y)
690
730
  move_cursor_up(height + dialog_render_info.pos.y)
691
731
  end
692
732
  dialog.vertical_offset = dialog_render_info.pos.y + 1
693
733
  end
694
734
  Reline::IOGate.hide_cursor
695
- dialog.width += @block_elem_width if dialog.scrollbar_pos
735
+ if dialog.column < 0
736
+ dialog.column = 0
737
+ dialog.width = @screen_size.last
738
+ end
696
739
  reset_dialog(dialog, old_dialog)
697
740
  move_cursor_down(dialog.vertical_offset)
698
741
  Reline::IOGate.move_cursor_column(dialog.column)
@@ -711,19 +754,18 @@ class Reline::LineEditor
711
754
  @output.write "\e[#{bg_color}m#{str}"
712
755
  if dialog.scrollbar_pos and (dialog.scrollbar_pos != old_dialog.scrollbar_pos or dialog.column != old_dialog.column)
713
756
  @output.write "\e[37m"
714
- if dialog.scrollbar_pos <= (i * 2) and (i * 2 + @block_elem_width) < (dialog.scrollbar_pos + bar_height)
715
- @output.write '█'
757
+ if dialog.scrollbar_pos <= (i * 2) and (i * 2 + 1) < (dialog.scrollbar_pos + bar_height)
758
+ @output.write @full_block
716
759
  elsif dialog.scrollbar_pos <= (i * 2) and (i * 2) < (dialog.scrollbar_pos + bar_height)
717
- @output.write '▀'
760
+ @output.write @upper_half_block
718
761
  str += ''
719
- elsif dialog.scrollbar_pos <= (i * 2 + @block_elem_width) and (i * 2) < (dialog.scrollbar_pos + bar_height)
720
- @output.write '▄'
762
+ elsif dialog.scrollbar_pos <= (i * 2 + 1) and (i * 2) < (dialog.scrollbar_pos + bar_height)
763
+ @output.write @lower_half_block
721
764
  else
722
765
  @output.write ' ' * @block_elem_width
723
766
  end
724
- @output.write "\e[39m"
725
767
  end
726
- @output.write "\e[49m"
768
+ @output.write "\e[0m"
727
769
  Reline::IOGate.move_cursor_column(dialog.column)
728
770
  move_cursor_down(1) if i < (dialog.contents.size - 1)
729
771
  end
@@ -741,7 +783,7 @@ class Reline::LineEditor
741
783
 
742
784
  private def reset_dialog(dialog, old_dialog)
743
785
  return if dialog.lines_backup.nil? or old_dialog.contents.nil?
744
- prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
786
+ prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
745
787
  visual_lines = []
746
788
  visual_start = nil
747
789
  dialog.lines_backup[:lines].each_with_index { |l, i|
@@ -769,7 +811,7 @@ class Reline::LineEditor
769
811
  s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
770
812
  s = padding_space_with_escape_sequences(s, old_dialog.width)
771
813
  end
772
- @output.write "\e[39m\e[49m#{s}\e[39m\e[49m"
814
+ @output.write "\e[0m#{s}\e[0m"
773
815
  move_cursor_down(1) if i < (line_num - 1)
774
816
  end
775
817
  move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
@@ -787,7 +829,7 @@ class Reline::LineEditor
787
829
  s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
788
830
  s = padding_space_with_escape_sequences(s, old_dialog.width)
789
831
  end
790
- @output.write "\e[39m\e[49m#{s}\e[39m\e[49m"
832
+ @output.write "\e[0m#{s}\e[0m"
791
833
  move_cursor_down(1) if i < (line_num - 1)
792
834
  end
793
835
  move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
@@ -806,7 +848,7 @@ class Reline::LineEditor
806
848
  s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, width)
807
849
  s = padding_space_with_escape_sequences(s, dialog.width)
808
850
  end
809
- @output.write "\e[39m\e[49m#{s}\e[39m\e[49m"
851
+ @output.write "\e[0m#{s}\e[0m"
810
852
  move_cursor_down(1) if i < (line_num - 1)
811
853
  end
812
854
  move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
@@ -823,10 +865,11 @@ class Reline::LineEditor
823
865
  s = ' ' * width
824
866
  else
825
867
  s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column + dialog.width, width)
826
- s = padding_space_with_escape_sequences(s, dialog.width)
868
+ rerender_width = old_dialog.width - dialog.width
869
+ s = padding_space_with_escape_sequences(s, rerender_width)
827
870
  end
828
871
  Reline::IOGate.move_cursor_column(dialog.column + dialog.width)
829
- @output.write "\e[39m\e[49m#{s}\e[39m\e[49m"
872
+ @output.write "\e[0m#{s}\e[0m"
830
873
  move_cursor_down(1) if i < (line_num - 1)
831
874
  end
832
875
  move_cursor_up(old_dialog.vertical_offset + line_num - 1 + y_diff)
@@ -843,7 +886,7 @@ class Reline::LineEditor
843
886
  private def clear_each_dialog(dialog)
844
887
  dialog.trap_key = nil
845
888
  return unless dialog.contents
846
- prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
889
+ prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
847
890
  visual_lines = []
848
891
  visual_lines_under_dialog = []
849
892
  visual_start = nil
@@ -866,10 +909,10 @@ class Reline::LineEditor
866
909
  Reline::IOGate.move_cursor_column(dialog.column)
867
910
  str = Reline::Unicode.take_range(visual_lines_under_dialog[i], dialog.column, dialog.width)
868
911
  str = padding_space_with_escape_sequences(str, dialog.width)
869
- @output.write "\e[39m\e[49m#{str}\e[39m\e[49m"
912
+ @output.write "\e[0m#{str}\e[0m"
870
913
  else
871
914
  Reline::IOGate.move_cursor_column(dialog.column)
872
- @output.write "\e[39m\e[49m#{' ' * dialog.width}\e[39m\e[49m"
915
+ @output.write "\e[0m#{' ' * dialog.width}\e[0m"
873
916
  end
874
917
  move_cursor_down(1) if i < (dialog_vertical_size - 1)
875
918
  end
@@ -928,7 +971,7 @@ class Reline::LineEditor
928
971
  end
929
972
 
930
973
  def just_move_cursor
931
- prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines, prompt)
974
+ prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines)
932
975
  move_cursor_up(@started_from)
933
976
  new_first_line_started_from =
934
977
  if @line_index.zero?
@@ -965,7 +1008,7 @@ class Reline::LineEditor
965
1008
  else
966
1009
  new_lines = whole_lines
967
1010
  end
968
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
1011
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
969
1012
  all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
970
1013
  diff = all_height - @highest_in_all
971
1014
  move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
@@ -1012,7 +1055,7 @@ class Reline::LineEditor
1012
1055
  Reline::IOGate.move_cursor_column(0)
1013
1056
  back = 0
1014
1057
  new_buffer = whole_lines
1015
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
1058
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer)
1016
1059
  new_buffer.each_with_index do |line, index|
1017
1060
  prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
1018
1061
  width = prompt_width + calculate_width(line)
@@ -1402,7 +1445,10 @@ class Reline::LineEditor
1402
1445
  end
1403
1446
  @waiting_operator_proc = nil
1404
1447
  @waiting_operator_vi_arg = nil
1405
- @vi_arg = nil
1448
+ if @vi_arg
1449
+ @rerender_all = true
1450
+ @vi_arg = nil
1451
+ end
1406
1452
  else
1407
1453
  block.(false)
1408
1454
  end
@@ -1453,7 +1499,10 @@ class Reline::LineEditor
1453
1499
  wrap_method_call(method_symbol, method_obj, key) if method_obj
1454
1500
  end
1455
1501
  @kill_ring.process
1456
- @vi_arg = nil
1502
+ if @vi_arg
1503
+ @rerender_al = true
1504
+ @vi_arg = nil
1505
+ end
1457
1506
  elsif @vi_arg
1458
1507
  if key.chr =~ /[0-9]/
1459
1508
  ed_argument_digit(key)
@@ -1470,7 +1519,10 @@ class Reline::LineEditor
1470
1519
  ed_insert(key) unless @config.editing_mode_is?(:vi_command)
1471
1520
  end
1472
1521
  @kill_ring.process
1473
- @vi_arg = nil
1522
+ if @vi_arg
1523
+ @rerender_all = true
1524
+ @vi_arg = nil
1525
+ end
1474
1526
  end
1475
1527
  elsif @waiting_proc
1476
1528
  @waiting_proc.(key)
@@ -1909,6 +1961,8 @@ class Reline::LineEditor
1909
1961
  end
1910
1962
  end
1911
1963
 
1964
+ # Editline:: +ed-unassigned+ This editor command always results in an error.
1965
+ # GNU Readline:: There is no corresponding macro.
1912
1966
  private def ed_unassigned(key) end # do nothing
1913
1967
 
1914
1968
  private def process_insert(force: false)
@@ -1926,6 +1980,19 @@ class Reline::LineEditor
1926
1980
  @continuous_insertion_buffer.clear
1927
1981
  end
1928
1982
 
1983
+ # Editline:: +ed-insert+ (vi input: almost all; emacs: printable characters)
1984
+ # In insert mode, insert the input character left of the cursor
1985
+ # position. In replace mode, overwrite the character at the
1986
+ # cursor and move the cursor to the right by one character
1987
+ # position. Accept an argument to do this repeatedly. It is an
1988
+ # error if the input character is the NUL character (+Ctrl-@+).
1989
+ # Failure to enlarge the edit buffer also results in an error.
1990
+ # Editline:: +ed-digit+ (emacs: 0 to 9) If in argument input mode, append
1991
+ # the input digit to the argument being read. Otherwise, call
1992
+ # +ed-insert+. It is an error if the input character is not a
1993
+ # digit or if the existing argument is already greater than a
1994
+ # million.
1995
+ # GNU Readline:: +self-insert+ (a, b, A, 1, !, …) Insert yourself.
1929
1996
  private def ed_insert(key)
1930
1997
  str = nil
1931
1998
  width = nil
@@ -1962,8 +2029,16 @@ class Reline::LineEditor
1962
2029
  last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1963
2030
  @byte_pointer += bytesize
1964
2031
  last_mbchar = @line.byteslice((@byte_pointer - bytesize - last_byte_size), last_byte_size)
1965
- if last_byte_size != 0 and (last_mbchar + str).grapheme_clusters.size == 1
1966
- width = 0
2032
+ combined_char = last_mbchar + str
2033
+ if last_byte_size != 0 and combined_char.grapheme_clusters.size == 1
2034
+ # combined char
2035
+ last_mbchar_width = Reline::Unicode.get_mbchar_width(last_mbchar)
2036
+ combined_char_width = Reline::Unicode.get_mbchar_width(combined_char)
2037
+ if combined_char_width > last_mbchar_width
2038
+ width = combined_char_width - last_mbchar_width
2039
+ else
2040
+ width = 0
2041
+ end
1967
2042
  end
1968
2043
  @cursor += width
1969
2044
  @cursor_max += width
@@ -1976,6 +2051,8 @@ class Reline::LineEditor
1976
2051
  arg.times do
1977
2052
  if key == "\C-j".ord or key == "\C-m".ord
1978
2053
  key_newline(key)
2054
+ elsif key == 0
2055
+ # Ignore NUL.
1979
2056
  else
1980
2057
  ed_insert(key)
1981
2058
  end
@@ -2410,6 +2487,7 @@ class Reline::LineEditor
2410
2487
  arg -= 1
2411
2488
  ed_prev_history(key, arg: arg) if arg > 0
2412
2489
  end
2490
+ alias_method :previous_history, :ed_prev_history
2413
2491
 
2414
2492
  private def ed_next_history(key, arg: 1)
2415
2493
  if @is_multiline and @line_index < (@buffer_of_lines.size - 1)
@@ -2457,6 +2535,7 @@ class Reline::LineEditor
2457
2535
  arg -= 1
2458
2536
  ed_next_history(key, arg: arg) if arg > 0
2459
2537
  end
2538
+ alias_method :next_history, :ed_next_history
2460
2539
 
2461
2540
  private def ed_newline(key)
2462
2541
  process_insert(force: true)
@@ -2491,7 +2570,7 @@ class Reline::LineEditor
2491
2570
  end
2492
2571
  end
2493
2572
 
2494
- private def em_delete_prev_char(key)
2573
+ private def em_delete_prev_char(key, arg: 1)
2495
2574
  if @is_multiline and @cursor == 0 and @line_index > 0
2496
2575
  @buffer_of_lines[@line_index] = @line
2497
2576
  @cursor = calculate_width(@buffer_of_lines[@line_index - 1])
@@ -2509,9 +2588,16 @@ class Reline::LineEditor
2509
2588
  @cursor -= width
2510
2589
  @cursor_max -= width
2511
2590
  end
2591
+ arg -= 1
2592
+ em_delete_prev_char(key, arg: arg) if arg > 0
2512
2593
  end
2513
2594
  alias_method :backward_delete_char, :em_delete_prev_char
2514
2595
 
2596
+ # Editline:: +ed-kill-line+ (vi command: +D+, +Ctrl-K+; emacs: +Ctrl-K+,
2597
+ # +Ctrl-U+) + Kill from the cursor to the end of the line.
2598
+ # GNU Readline:: +kill-line+ (+C-k+) Kill the text from point to the end of
2599
+ # the line. With a negative numeric argument, kill backward
2600
+ # from the cursor to the beginning of the current line.
2515
2601
  private def ed_kill_line(key)
2516
2602
  if @line.bytesize > @byte_pointer
2517
2603
  @line, deleted = byteslice!(@line, @byte_pointer, @line.bytesize - @byte_pointer)
@@ -2528,8 +2614,14 @@ class Reline::LineEditor
2528
2614
  @rest_height += 1
2529
2615
  end
2530
2616
  end
2617
+ alias_method :kill_line, :ed_kill_line
2531
2618
 
2532
- private def em_kill_line(key)
2619
+ # Editline:: +vi-kill-line-prev+ (vi: +Ctrl-U+) Delete the string from the
2620
+ # beginning of the edit buffer to the cursor and save it to the
2621
+ # cut buffer.
2622
+ # GNU Readline:: +unix-line-discard+ (+C-u+) Kill backward from the cursor
2623
+ # to the beginning of the current line.
2624
+ private def vi_kill_line_prev(key)
2533
2625
  if @byte_pointer > 0
2534
2626
  @line, deleted = byteslice!(@line, 0, @byte_pointer)
2535
2627
  @byte_pointer = 0
@@ -2538,7 +2630,22 @@ class Reline::LineEditor
2538
2630
  @cursor = 0
2539
2631
  end
2540
2632
  end
2541
- alias_method :kill_line, :em_kill_line
2633
+ alias_method :unix_line_discard, :vi_kill_line_prev
2634
+
2635
+ # Editline:: +em-kill-line+ (not bound) Delete the entire contents of the
2636
+ # edit buffer and save it to the cut buffer. +vi-kill-line-prev+
2637
+ # GNU Readline:: +kill-whole-line+ (not bound) Kill all characters on the
2638
+ # current line, no matter where point is.
2639
+ private def em_kill_line(key)
2640
+ if @line.size > 0
2641
+ @kill_ring.append(@line.dup, true)
2642
+ @line.clear
2643
+ @byte_pointer = 0
2644
+ @cursor_max = 0
2645
+ @cursor = 0
2646
+ end
2647
+ end
2648
+ alias_method :kill_whole_line, :em_kill_line
2542
2649
 
2543
2650
  private def em_delete(key)
2544
2651
  if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
@@ -3043,7 +3150,14 @@ class Reline::LineEditor
3043
3150
 
3044
3151
  private def ed_argument_digit(key)
3045
3152
  if @vi_arg.nil?
3046
- unless key.chr.to_i.zero?
3153
+ if key.chr.to_i.zero?
3154
+ if key.anybits?(0b10000000)
3155
+ unescaped_key = key ^ 0b10000000
3156
+ unless unescaped_key.chr.to_i.zero?
3157
+ @vi_arg = unescaped_key.chr.to_i
3158
+ end
3159
+ end
3160
+ else
3047
3161
  @vi_arg = key.chr.to_i
3048
3162
  end
3049
3163
  else