reline 0.2.8.pre.8 → 0.3.0

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,7 +151,74 @@ 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)
153
- @old_trap = Signal.trap(:INT) {
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
221
+ @old_trap = Signal.trap('INT') {
154
222
  clear_dialog
155
223
  if @scroll_partial_screen
156
224
  move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
@@ -167,50 +235,24 @@ 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
- Reline::IOGate.set_winch_handler do
174
- @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
175
- old_screen_size = @screen_size
176
- @screen_size = Reline::IOGate.get_screen_size
177
- @screen_height = @screen_size.first
178
- if old_screen_size.last < @screen_size.last # columns increase
179
- @rerender_all = true
180
- rerender
181
- else
182
- back = 0
183
- new_buffer = whole_lines
184
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
185
- new_buffer.each_with_index do |line, index|
186
- prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
187
- width = prompt_width + calculate_width(line)
188
- height = calculate_height_by_width(width)
189
- back += height
190
- end
191
- @highest_in_all = back
192
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
193
- @first_line_started_from =
194
- if @line_index.zero?
195
- 0
196
- else
197
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
198
- end
199
- if @prompt_proc
200
- prompt = prompt_list[@line_index]
201
- prompt_width = calculate_width(prompt, true)
202
- end
203
- calculate_nearest_cursor
204
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
205
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
206
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
207
- @rerender_all = true
208
- end
241
+ begin
242
+ @old_tstp_trap = Signal.trap('TSTP') {
243
+ Reline::IOGate.ungetc("\C-z".ord)
244
+ @old_tstp_trap.call if @old_tstp_trap.respond_to?(:call)
245
+ }
246
+ rescue ArgumentError
209
247
  end
210
248
  end
211
249
 
212
250
  def finalize
213
- Signal.trap('SIGINT', @old_trap)
251
+ Signal.trap('INT', @old_trap)
252
+ begin
253
+ Signal.trap('TSTP', @old_tstp_trap)
254
+ rescue ArgumentError
255
+ end
214
256
  end
215
257
 
216
258
  def eof?
@@ -218,7 +260,7 @@ class Reline::LineEditor
218
260
  end
219
261
 
220
262
  def reset_variables(prompt = '', encoding:)
221
- @prompt = prompt
263
+ @prompt = prompt.gsub("\n", "\\n")
222
264
  @mark_pointer = nil
223
265
  @encoding = encoding
224
266
  @is_multiline = false
@@ -252,6 +294,7 @@ class Reline::LineEditor
252
294
  @auto_indent_proc = nil
253
295
  @dialogs = []
254
296
  @last_key = nil
297
+ @resized = false
255
298
  reset_line
256
299
  end
257
300
 
@@ -395,7 +438,7 @@ class Reline::LineEditor
395
438
  show_menu
396
439
  @menu_info = nil
397
440
  end
398
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
441
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
399
442
  if @cleared
400
443
  clear_screen_buffer(prompt, prompt_list, prompt_width)
401
444
  @cleared = false
@@ -406,7 +449,7 @@ class Reline::LineEditor
406
449
  Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
407
450
  Reline::IOGate.move_cursor_column(0)
408
451
  @scroll_partial_screen = nil
409
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
452
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
410
453
  if @previous_line_index
411
454
  new_lines = whole_lines(index: @previous_line_index, line: @line)
412
455
  else
@@ -452,7 +495,7 @@ class Reline::LineEditor
452
495
  end
453
496
  line = modify_lines(new_lines)[@line_index]
454
497
  clear_dialog
455
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
498
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
456
499
  render_partial(prompt, prompt_width, line, @first_line_started_from)
457
500
  move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
458
501
  scroll_down(1)
@@ -461,7 +504,7 @@ class Reline::LineEditor
461
504
  else
462
505
  if not rendered and not @in_pasting
463
506
  line = modify_lines(whole_lines)[@line_index]
464
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
507
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
465
508
  render_partial(prompt, prompt_width, line, @first_line_started_from)
466
509
  end
467
510
  render_dialog((prompt_width + @cursor) % @screen_size.last)
@@ -548,7 +591,7 @@ class Reline::LineEditor
548
591
 
549
592
  class Dialog
550
593
  attr_reader :name, :contents, :width
551
- attr_accessor :scroll_top, :column, :vertical_offset, :lines_backup, :trap_key
594
+ attr_accessor :scroll_top, :scrollbar_pos, :pointer, :column, :vertical_offset, :lines_backup, :trap_key
552
595
 
553
596
  def initialize(name, config, proc_scope)
554
597
  @name = name
@@ -556,6 +599,7 @@ class Reline::LineEditor
556
599
  @proc_scope = proc_scope
557
600
  @width = nil
558
601
  @scroll_top = 0
602
+ @trap_key = nil
559
603
  end
560
604
 
561
605
  def set_cursor_pos(col, row)
@@ -593,11 +637,15 @@ class Reline::LineEditor
593
637
  end
594
638
 
595
639
  def add_dialog_proc(name, p, context = nil)
596
- return if @dialogs.any? { |d| d.name == name }
597
- @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
598
646
  end
599
647
 
600
- DIALOG_HEIGHT = 20
648
+ DIALOG_DEFAULT_HEIGHT = 20
601
649
  private def render_dialog(cursor_column)
602
650
  @dialogs.each do |dialog|
603
651
  render_each_dialog(dialog, cursor_column)
@@ -610,6 +658,7 @@ class Reline::LineEditor
610
658
 
611
659
  private def render_each_dialog(dialog, cursor_column)
612
660
  if @in_pasting
661
+ clear_each_dialog(dialog)
613
662
  dialog.contents = nil
614
663
  dialog.trap_key = nil
615
664
  return
@@ -631,56 +680,65 @@ class Reline::LineEditor
631
680
  end
632
681
  old_dialog = dialog.clone
633
682
  dialog.contents = dialog_render_info.contents
634
- pointer = dialog_render_info.pointer
683
+ pointer = dialog.pointer
635
684
  if dialog_render_info.width
636
685
  dialog.width = dialog_render_info.width
637
686
  else
638
687
  dialog.width = dialog.contents.map { |l| calculate_width(l, true) }.max
639
688
  end
640
- height = dialog_render_info.height || DIALOG_HEIGHT
689
+ height = dialog_render_info.height || DIALOG_DEFAULT_HEIGHT
641
690
  height = dialog.contents.size if dialog.contents.size < height
642
691
  if dialog.contents.size > height
643
- if dialog_render_info.pointer
644
- if dialog_render_info.pointer < 0
692
+ if dialog.pointer
693
+ if dialog.pointer < 0
645
694
  dialog.scroll_top = 0
646
- elsif (dialog_render_info.pointer - dialog.scroll_top) >= (height - 1)
647
- dialog.scroll_top = dialog_render_info.pointer - (height - 1)
648
- elsif (dialog_render_info.pointer - dialog.scroll_top) < 0
649
- 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
650
699
  end
651
- pointer = dialog_render_info.pointer - dialog.scroll_top
700
+ pointer = dialog.pointer - dialog.scroll_top
652
701
  end
653
702
  dialog.contents = dialog.contents[dialog.scroll_top, height]
654
703
  end
704
+ if dialog.contents and dialog.scroll_top >= dialog.contents.size
705
+ dialog.scroll_top = dialog.contents.size - height
706
+ end
707
+ if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
708
+ bar_max_height = height * 2
709
+ moving_distance = (dialog_render_info.contents.size - height) * 2
710
+ position_ratio = dialog.scroll_top.zero? ? 0.0 : ((dialog.scroll_top * 2).to_f / moving_distance)
711
+ bar_height = (bar_max_height * ((dialog.contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
712
+ dialog.scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i
713
+ else
714
+ dialog.scrollbar_pos = nil
715
+ end
655
716
  upper_space = @first_line_started_from - @started_from
656
- lower_space = @highest_in_all - @first_line_started_from - @started_from - 1
657
717
  dialog.column = dialog_render_info.pos.x
658
- 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)
659
720
  if diff > 0
660
721
  dialog.column -= diff
661
722
  end
662
- if (lower_space + @rest_height - dialog_render_info.pos.y) >= height
723
+ if (@rest_height - dialog_render_info.pos.y) >= height
663
724
  dialog.vertical_offset = dialog_render_info.pos.y + 1
664
725
  elsif upper_space >= height
665
726
  dialog.vertical_offset = dialog_render_info.pos.y - height
666
727
  else
667
- if (lower_space + @rest_height - dialog_render_info.pos.y) < height
728
+ if (@rest_height - dialog_render_info.pos.y) < height
668
729
  scroll_down(height + dialog_render_info.pos.y)
669
730
  move_cursor_up(height + dialog_render_info.pos.y)
670
731
  end
671
732
  dialog.vertical_offset = dialog_render_info.pos.y + 1
672
733
  end
673
734
  Reline::IOGate.hide_cursor
735
+ if dialog.column < 0
736
+ dialog.column = 0
737
+ dialog.width = @screen_size.last
738
+ end
674
739
  reset_dialog(dialog, old_dialog)
675
740
  move_cursor_down(dialog.vertical_offset)
676
741
  Reline::IOGate.move_cursor_column(dialog.column)
677
- if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
678
- bar_max_height = height * 2
679
- moving_distance = (dialog_render_info.contents.size - height) * 2
680
- position_ratio = dialog.scroll_top.zero? ? 0.0 : ((dialog.scroll_top * 2).to_f / moving_distance)
681
- bar_height = (bar_max_height * ((dialog.contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
682
- position = ((bar_max_height - bar_height) * position_ratio).floor.to_i
683
- end
684
742
  dialog.contents.each_with_index do |item, i|
685
743
  if i == pointer
686
744
  bg_color = '45'
@@ -691,27 +749,26 @@ class Reline::LineEditor
691
749
  bg_color = '46'
692
750
  end
693
751
  end
694
- str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, dialog.width), dialog.width)
752
+ str_width = dialog.width - (dialog.scrollbar_pos.nil? ? 0 : @block_elem_width)
753
+ str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, str_width), str_width)
695
754
  @output.write "\e[#{bg_color}m#{str}"
696
- if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
755
+ if dialog.scrollbar_pos and (dialog.scrollbar_pos != old_dialog.scrollbar_pos or dialog.column != old_dialog.column)
697
756
  @output.write "\e[37m"
698
- if position <= (i * 2) and (i * 2 + 1) < (position + bar_height)
699
- @output.write '█'
700
- elsif position <= (i * 2) and (i * 2) < (position + bar_height)
701
- @output.write '▀'
757
+ if dialog.scrollbar_pos <= (i * 2) and (i * 2 + 1) < (dialog.scrollbar_pos + bar_height)
758
+ @output.write @full_block
759
+ elsif dialog.scrollbar_pos <= (i * 2) and (i * 2) < (dialog.scrollbar_pos + bar_height)
760
+ @output.write @upper_half_block
702
761
  str += ''
703
- elsif position <= (i * 2 + 1) and (i * 2) < (position + bar_height)
704
- @output.write '▄'
762
+ elsif dialog.scrollbar_pos <= (i * 2 + 1) and (i * 2) < (dialog.scrollbar_pos + bar_height)
763
+ @output.write @lower_half_block
705
764
  else
706
- @output.write ' '
765
+ @output.write ' ' * @block_elem_width
707
766
  end
708
- @output.write "\e[39m"
709
767
  end
710
- @output.write "\e[49m"
768
+ @output.write "\e[0m"
711
769
  Reline::IOGate.move_cursor_column(dialog.column)
712
770
  move_cursor_down(1) if i < (dialog.contents.size - 1)
713
771
  end
714
- dialog.width += 1 if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
715
772
  Reline::IOGate.move_cursor_column(cursor_column)
716
773
  move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
717
774
  Reline::IOGate.show_cursor
@@ -726,7 +783,7 @@ class Reline::LineEditor
726
783
 
727
784
  private def reset_dialog(dialog, old_dialog)
728
785
  return if dialog.lines_backup.nil? or old_dialog.contents.nil?
729
- 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])
730
787
  visual_lines = []
731
788
  visual_start = nil
732
789
  dialog.lines_backup[:lines].each_with_index { |l, i|
@@ -749,12 +806,12 @@ class Reline::LineEditor
749
806
  line_num.times do |i|
750
807
  Reline::IOGate.move_cursor_column(old_dialog.column)
751
808
  if visual_lines[start + i].nil?
752
- s = ' ' * dialog.width
809
+ s = ' ' * old_dialog.width
753
810
  else
754
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, dialog.width)
755
- s = padding_space_with_escape_sequences(s, dialog.width)
811
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
812
+ s = padding_space_with_escape_sequences(s, old_dialog.width)
756
813
  end
757
- @output.write "\e[39m\e[49m#{s}\e[39m\e[49m"
814
+ @output.write "\e[0m#{s}\e[0m"
758
815
  move_cursor_down(1) if i < (line_num - 1)
759
816
  end
760
817
  move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
@@ -767,12 +824,12 @@ class Reline::LineEditor
767
824
  line_num.times do |i|
768
825
  Reline::IOGate.move_cursor_column(old_dialog.column)
769
826
  if visual_lines[start + i].nil?
770
- s = ' ' * dialog.width
827
+ s = ' ' * old_dialog.width
771
828
  else
772
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, dialog.width)
773
- s = padding_space_with_escape_sequences(s, dialog.width)
829
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
830
+ s = padding_space_with_escape_sequences(s, old_dialog.width)
774
831
  end
775
- @output.write "\e[39m\e[49m#{s}\e[39m\e[49m"
832
+ @output.write "\e[0m#{s}\e[0m"
776
833
  move_cursor_down(1) if i < (line_num - 1)
777
834
  end
778
835
  move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
@@ -791,7 +848,7 @@ class Reline::LineEditor
791
848
  s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, width)
792
849
  s = padding_space_with_escape_sequences(s, dialog.width)
793
850
  end
794
- @output.write "\e[39m\e[49m#{s}\e[39m\e[49m"
851
+ @output.write "\e[0m#{s}\e[0m"
795
852
  move_cursor_down(1) if i < (line_num - 1)
796
853
  end
797
854
  move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
@@ -808,10 +865,11 @@ class Reline::LineEditor
808
865
  s = ' ' * width
809
866
  else
810
867
  s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column + dialog.width, width)
811
- 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)
812
870
  end
813
871
  Reline::IOGate.move_cursor_column(dialog.column + dialog.width)
814
- @output.write "\e[39m\e[49m#{s}\e[39m\e[49m"
872
+ @output.write "\e[0m#{s}\e[0m"
815
873
  move_cursor_down(1) if i < (line_num - 1)
816
874
  end
817
875
  move_cursor_up(old_dialog.vertical_offset + line_num - 1 + y_diff)
@@ -828,7 +886,7 @@ class Reline::LineEditor
828
886
  private def clear_each_dialog(dialog)
829
887
  dialog.trap_key = nil
830
888
  return unless dialog.contents
831
- 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])
832
890
  visual_lines = []
833
891
  visual_lines_under_dialog = []
834
892
  visual_start = nil
@@ -851,10 +909,10 @@ class Reline::LineEditor
851
909
  Reline::IOGate.move_cursor_column(dialog.column)
852
910
  str = Reline::Unicode.take_range(visual_lines_under_dialog[i], dialog.column, dialog.width)
853
911
  str = padding_space_with_escape_sequences(str, dialog.width)
854
- @output.write "\e[39m\e[49m#{str}\e[39m\e[49m"
912
+ @output.write "\e[0m#{str}\e[0m"
855
913
  else
856
914
  Reline::IOGate.move_cursor_column(dialog.column)
857
- @output.write "\e[39m\e[49m#{' ' * dialog.width}\e[39m\e[49m"
915
+ @output.write "\e[0m#{' ' * dialog.width}\e[0m"
858
916
  end
859
917
  move_cursor_down(1) if i < (dialog_vertical_size - 1)
860
918
  end
@@ -913,7 +971,7 @@ class Reline::LineEditor
913
971
  end
914
972
 
915
973
  def just_move_cursor
916
- prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines, prompt)
974
+ prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines)
917
975
  move_cursor_up(@started_from)
918
976
  new_first_line_started_from =
919
977
  if @line_index.zero?
@@ -950,7 +1008,7 @@ class Reline::LineEditor
950
1008
  else
951
1009
  new_lines = whole_lines
952
1010
  end
953
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
1011
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
954
1012
  all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
955
1013
  diff = all_height - @highest_in_all
956
1014
  move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
@@ -997,7 +1055,7 @@ class Reline::LineEditor
997
1055
  Reline::IOGate.move_cursor_column(0)
998
1056
  back = 0
999
1057
  new_buffer = whole_lines
1000
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
1058
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer)
1001
1059
  new_buffer.each_with_index do |line, index|
1002
1060
  prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
1003
1061
  width = prompt_width + calculate_width(line)
@@ -1204,7 +1262,7 @@ class Reline::LineEditor
1204
1262
  height = render_partial(prompt, prompt_width, line, back, with_control: false)
1205
1263
  end
1206
1264
  if index < (@buffer_of_lines.size - 1)
1207
- move_cursor_down(height)
1265
+ move_cursor_down(1)
1208
1266
  back += height
1209
1267
  end
1210
1268
  end
@@ -1387,7 +1445,10 @@ class Reline::LineEditor
1387
1445
  end
1388
1446
  @waiting_operator_proc = nil
1389
1447
  @waiting_operator_vi_arg = nil
1390
- @vi_arg = nil
1448
+ if @vi_arg
1449
+ @rerender_all = true
1450
+ @vi_arg = nil
1451
+ end
1391
1452
  else
1392
1453
  block.(false)
1393
1454
  end
@@ -1438,7 +1499,10 @@ class Reline::LineEditor
1438
1499
  wrap_method_call(method_symbol, method_obj, key) if method_obj
1439
1500
  end
1440
1501
  @kill_ring.process
1441
- @vi_arg = nil
1502
+ if @vi_arg
1503
+ @rerender_al = true
1504
+ @vi_arg = nil
1505
+ end
1442
1506
  elsif @vi_arg
1443
1507
  if key.chr =~ /[0-9]/
1444
1508
  ed_argument_digit(key)
@@ -1455,7 +1519,10 @@ class Reline::LineEditor
1455
1519
  ed_insert(key) unless @config.editing_mode_is?(:vi_command)
1456
1520
  end
1457
1521
  @kill_ring.process
1458
- @vi_arg = nil
1522
+ if @vi_arg
1523
+ @rerender_all = true
1524
+ @vi_arg = nil
1525
+ end
1459
1526
  end
1460
1527
  elsif @waiting_proc
1461
1528
  @waiting_proc.(key)
@@ -1894,6 +1961,8 @@ class Reline::LineEditor
1894
1961
  end
1895
1962
  end
1896
1963
 
1964
+ # Editline:: +ed-unassigned+ This editor command always results in an error.
1965
+ # GNU Readline:: There is no corresponding macro.
1897
1966
  private def ed_unassigned(key) end # do nothing
1898
1967
 
1899
1968
  private def process_insert(force: false)
@@ -1911,6 +1980,19 @@ class Reline::LineEditor
1911
1980
  @continuous_insertion_buffer.clear
1912
1981
  end
1913
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.
1914
1996
  private def ed_insert(key)
1915
1997
  str = nil
1916
1998
  width = nil
@@ -1947,8 +2029,16 @@ class Reline::LineEditor
1947
2029
  last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1948
2030
  @byte_pointer += bytesize
1949
2031
  last_mbchar = @line.byteslice((@byte_pointer - bytesize - last_byte_size), last_byte_size)
1950
- if last_byte_size != 0 and (last_mbchar + str).grapheme_clusters.size == 1
1951
- 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
1952
2042
  end
1953
2043
  @cursor += width
1954
2044
  @cursor_max += width
@@ -1961,6 +2051,8 @@ class Reline::LineEditor
1961
2051
  arg.times do
1962
2052
  if key == "\C-j".ord or key == "\C-m".ord
1963
2053
  key_newline(key)
2054
+ elsif key == 0
2055
+ # Ignore NUL.
1964
2056
  else
1965
2057
  ed_insert(key)
1966
2058
  end
@@ -2395,6 +2487,7 @@ class Reline::LineEditor
2395
2487
  arg -= 1
2396
2488
  ed_prev_history(key, arg: arg) if arg > 0
2397
2489
  end
2490
+ alias_method :previous_history, :ed_prev_history
2398
2491
 
2399
2492
  private def ed_next_history(key, arg: 1)
2400
2493
  if @is_multiline and @line_index < (@buffer_of_lines.size - 1)
@@ -2442,6 +2535,7 @@ class Reline::LineEditor
2442
2535
  arg -= 1
2443
2536
  ed_next_history(key, arg: arg) if arg > 0
2444
2537
  end
2538
+ alias_method :next_history, :ed_next_history
2445
2539
 
2446
2540
  private def ed_newline(key)
2447
2541
  process_insert(force: true)
@@ -2476,7 +2570,7 @@ class Reline::LineEditor
2476
2570
  end
2477
2571
  end
2478
2572
 
2479
- private def em_delete_prev_char(key)
2573
+ private def em_delete_prev_char(key, arg: 1)
2480
2574
  if @is_multiline and @cursor == 0 and @line_index > 0
2481
2575
  @buffer_of_lines[@line_index] = @line
2482
2576
  @cursor = calculate_width(@buffer_of_lines[@line_index - 1])
@@ -2494,9 +2588,16 @@ class Reline::LineEditor
2494
2588
  @cursor -= width
2495
2589
  @cursor_max -= width
2496
2590
  end
2591
+ arg -= 1
2592
+ em_delete_prev_char(key, arg: arg) if arg > 0
2497
2593
  end
2498
2594
  alias_method :backward_delete_char, :em_delete_prev_char
2499
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.
2500
2601
  private def ed_kill_line(key)
2501
2602
  if @line.bytesize > @byte_pointer
2502
2603
  @line, deleted = byteslice!(@line, @byte_pointer, @line.bytesize - @byte_pointer)
@@ -2513,8 +2614,14 @@ class Reline::LineEditor
2513
2614
  @rest_height += 1
2514
2615
  end
2515
2616
  end
2617
+ alias_method :kill_line, :ed_kill_line
2516
2618
 
2517
- 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)
2518
2625
  if @byte_pointer > 0
2519
2626
  @line, deleted = byteslice!(@line, 0, @byte_pointer)
2520
2627
  @byte_pointer = 0
@@ -2523,7 +2630,22 @@ class Reline::LineEditor
2523
2630
  @cursor = 0
2524
2631
  end
2525
2632
  end
2526
- 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
2527
2649
 
2528
2650
  private def em_delete(key)
2529
2651
  if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
@@ -3028,7 +3150,14 @@ class Reline::LineEditor
3028
3150
 
3029
3151
  private def ed_argument_digit(key)
3030
3152
  if @vi_arg.nil?
3031
- 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
3032
3161
  @vi_arg = key.chr.to_i
3033
3162
  end
3034
3163
  else