reline 0.2.8.pre.8 → 0.3.0

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