reline 0.3.2 → 0.3.4

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.
@@ -52,6 +52,7 @@ class Reline::LineEditor
52
52
  MenuInfo = Struct.new('MenuInfo', :target, :list)
53
53
 
54
54
  PROMPT_LIST_CACHE_TIMEOUT = 0.5
55
+ MINIMUM_SCROLLBAR_HEIGHT = 1
55
56
 
56
57
  def initialize(config, encoding)
57
58
  @config = config
@@ -93,7 +94,7 @@ class Reline::LineEditor
93
94
  mode_string
94
95
  end
95
96
 
96
- private def check_multiline_prompt(buffer)
97
+ private def check_multiline_prompt(buffer, force_recalc: false)
97
98
  if @vi_arg
98
99
  prompt = "(arg: #{@vi_arg}) "
99
100
  @rerender_all = true
@@ -103,7 +104,7 @@ class Reline::LineEditor
103
104
  else
104
105
  prompt = @prompt
105
106
  end
106
- if simplified_rendering?
107
+ if simplified_rendering? && !force_recalc
107
108
  mode_string = check_mode_string
108
109
  prompt = mode_string + prompt if mode_string
109
110
  return [prompt, calculate_width(prompt, true), [prompt] * buffer.size]
@@ -219,7 +220,7 @@ class Reline::LineEditor
219
220
 
220
221
  def set_signal_handlers
221
222
  @old_trap = Signal.trap('INT') {
222
- clear_dialog
223
+ clear_dialog(0)
223
224
  if @scroll_partial_screen
224
225
  move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
225
226
  else
@@ -238,21 +239,10 @@ class Reline::LineEditor
238
239
  @old_trap.call if @old_trap.respond_to?(:call)
239
240
  end
240
241
  }
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
247
- end
248
242
  end
249
243
 
250
244
  def finalize
251
245
  Signal.trap('INT', @old_trap)
252
- begin
253
- Signal.trap('TSTP', @old_tstp_trap)
254
- rescue ArgumentError
255
- end
256
246
  end
257
247
 
258
248
  def eof?
@@ -293,6 +283,7 @@ class Reline::LineEditor
293
283
  @in_pasting = false
294
284
  @auto_indent_proc = nil
295
285
  @dialogs = []
286
+ @previous_rendered_dialog_y = 0
296
287
  @last_key = nil
297
288
  @resized = false
298
289
  reset_line
@@ -439,6 +430,7 @@ class Reline::LineEditor
439
430
  @menu_info = nil
440
431
  end
441
432
  prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
433
+ cursor_column = (prompt_width + @cursor) % @screen_size.last
442
434
  if @cleared
443
435
  clear_screen_buffer(prompt, prompt_list, prompt_width)
444
436
  @cleared = false
@@ -449,34 +441,30 @@ class Reline::LineEditor
449
441
  Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
450
442
  Reline::IOGate.move_cursor_column(0)
451
443
  @scroll_partial_screen = nil
452
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
453
- if @previous_line_index
454
- new_lines = whole_lines(index: @previous_line_index, line: @line)
455
- else
456
- new_lines = whole_lines
457
- end
444
+ new_lines = whole_lines
445
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
458
446
  modify_lines(new_lines).each_with_index do |line, index|
459
- @output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\n"
447
+ @output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\r\n"
460
448
  Reline::IOGate.erase_after_cursor
461
449
  end
462
450
  @output.flush
463
- clear_dialog
451
+ clear_dialog(cursor_column)
464
452
  return
465
453
  end
466
454
  new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
467
455
  rendered = false
468
456
  if @add_newline_to_end_of_buffer
469
- clear_dialog_with_content
470
- rerender_added_newline(prompt, prompt_width)
457
+ clear_dialog_with_trap_key(cursor_column)
458
+ rerender_added_newline(prompt, prompt_width, prompt_list)
471
459
  @add_newline_to_end_of_buffer = false
472
460
  else
473
461
  if @just_cursor_moving and not @rerender_all
474
- clear_dialog_with_content
462
+ clear_dialog_with_trap_key(cursor_column)
475
463
  rendered = just_move_cursor
476
464
  @just_cursor_moving = false
477
465
  return
478
466
  elsif @previous_line_index or new_highest_in_this != @highest_in_this
479
- clear_dialog_with_content
467
+ clear_dialog_with_trap_key(cursor_column)
480
468
  rerender_changed_current_line
481
469
  @previous_line_index = nil
482
470
  rendered = true
@@ -490,13 +478,9 @@ class Reline::LineEditor
490
478
  if @is_multiline
491
479
  if finished?
492
480
  # Always rerender on finish because output_modifier_proc may return a different output.
493
- if @previous_line_index
494
- new_lines = whole_lines(index: @previous_line_index, line: @line)
495
- else
496
- new_lines = whole_lines
497
- end
481
+ new_lines = whole_lines
498
482
  line = modify_lines(new_lines)[@line_index]
499
- clear_dialog
483
+ clear_dialog(cursor_column)
500
484
  prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
501
485
  render_partial(prompt, prompt_width, line, @first_line_started_from)
502
486
  move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
@@ -509,7 +493,7 @@ class Reline::LineEditor
509
493
  prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
510
494
  render_partial(prompt, prompt_width, line, @first_line_started_from)
511
495
  end
512
- render_dialog((prompt_width + @cursor) % @screen_size.last)
496
+ render_dialog(cursor_column)
513
497
  end
514
498
  @buffer_of_lines[@line_index] = @line
515
499
  @rest_height = 0 if @scroll_partial_screen
@@ -593,7 +577,7 @@ class Reline::LineEditor
593
577
 
594
578
  class Dialog
595
579
  attr_reader :name, :contents, :width
596
- attr_accessor :scroll_top, :scrollbar_pos, :pointer, :column, :vertical_offset, :lines_backup, :trap_key
580
+ attr_accessor :scroll_top, :pointer, :column, :vertical_offset, :trap_key
597
581
 
598
582
  def initialize(name, config, proc_scope)
599
583
  @name = name
@@ -649,9 +633,12 @@ class Reline::LineEditor
649
633
 
650
634
  DIALOG_DEFAULT_HEIGHT = 20
651
635
  private def render_dialog(cursor_column)
652
- @dialogs.each do |dialog|
653
- render_each_dialog(dialog, cursor_column)
636
+ changes = @dialogs.map do |dialog|
637
+ old_dialog = dialog.dup
638
+ update_each_dialog(dialog, cursor_column)
639
+ [old_dialog, dialog]
654
640
  end
641
+ render_dialog_changes(changes, cursor_column)
655
642
  end
656
643
 
657
644
  private def padding_space_with_escape_sequences(str, width)
@@ -661,9 +648,112 @@ class Reline::LineEditor
661
648
  str + (' ' * padding_width)
662
649
  end
663
650
 
664
- private def render_each_dialog(dialog, cursor_column)
651
+ private def range_subtract(base_ranges, subtract_ranges)
652
+ indices = base_ranges.flat_map(&:to_a).uniq.sort - subtract_ranges.flat_map(&:to_a)
653
+ chunks = indices.chunk_while { |a, b| a + 1 == b }
654
+ chunks.map { |a| a.first...a.last + 1 }
655
+ end
656
+
657
+ private def dialog_range(dialog, dialog_y)
658
+ x_range = dialog.column...dialog.column + dialog.width
659
+ y_range = dialog_y + dialog.vertical_offset...dialog_y + dialog.vertical_offset + dialog.contents.size
660
+ [x_range, y_range]
661
+ end
662
+
663
+ private def render_dialog_changes(changes, cursor_column)
664
+ # Collect x-coordinate range and content of previous and current dialogs for each line
665
+ old_dialog_ranges = {}
666
+ new_dialog_ranges = {}
667
+ new_dialog_contents = {}
668
+ changes.each do |old_dialog, new_dialog|
669
+ if old_dialog.contents
670
+ x_range, y_range = dialog_range(old_dialog, @previous_rendered_dialog_y)
671
+ y_range.each do |y|
672
+ (old_dialog_ranges[y] ||= []) << x_range
673
+ end
674
+ end
675
+ if new_dialog.contents
676
+ x_range, y_range = dialog_range(new_dialog, @first_line_started_from + @started_from)
677
+ y_range.each do |y|
678
+ (new_dialog_ranges[y] ||= []) << x_range
679
+ (new_dialog_contents[y] ||= []) << [x_range, new_dialog.contents[y - y_range.begin]]
680
+ end
681
+ end
682
+ end
683
+ return if old_dialog_ranges.empty? && new_dialog_ranges.empty?
684
+
685
+ # Calculate x-coordinate ranges to restore text that was hidden behind dialogs for each line
686
+ ranges_to_restore = {}
687
+ subtract_cache = {}
688
+ old_dialog_ranges.each do |y, old_x_ranges|
689
+ new_x_ranges = new_dialog_ranges[y] || []
690
+ ranges = subtract_cache[[old_x_ranges, new_x_ranges]] ||= range_subtract(old_x_ranges, new_x_ranges)
691
+ ranges_to_restore[y] = ranges if ranges.any?
692
+ end
693
+
694
+ # Create visual_lines for restoring text hidden behind dialogs
695
+ if ranges_to_restore.any?
696
+ lines = whole_lines
697
+ prompt, _prompt_width, prompt_list = check_multiline_prompt(lines, force_recalc: true)
698
+ modified_lines = modify_lines(lines, force_recalc: true)
699
+ visual_lines = []
700
+ modified_lines.each_with_index { |l, i|
701
+ pr = prompt_list ? prompt_list[i] : prompt
702
+ vl, = split_by_width(pr + l, @screen_size.last)
703
+ vl.compact!
704
+ visual_lines.concat(vl)
705
+ }
706
+ end
707
+
708
+ # Clear and rerender all dialogs line by line
709
+ Reline::IOGate.hide_cursor
710
+ ymin, ymax = (ranges_to_restore.keys + new_dialog_ranges.keys).minmax
711
+ scroll_partial_screen = @scroll_partial_screen || 0
712
+ screen_y_range = scroll_partial_screen..(scroll_partial_screen + @screen_height - 1)
713
+ ymin = ymin.clamp(screen_y_range.begin, screen_y_range.end)
714
+ ymax = ymax.clamp(screen_y_range.begin, screen_y_range.end)
715
+ dialog_y = @first_line_started_from + @started_from
716
+ cursor_y = dialog_y
717
+ if @highest_in_all < ymax
718
+ scroll_down(ymax - cursor_y)
719
+ move_cursor_up(ymax - cursor_y)
720
+ end
721
+ (ymin..ymax).each do |y|
722
+ move_cursor_down(y - cursor_y)
723
+ cursor_y = y
724
+ new_x_ranges = new_dialog_ranges[y]
725
+ restore_ranges = ranges_to_restore[y]
726
+ # Restore text that was hidden behind dialogs
727
+ if restore_ranges
728
+ line = visual_lines[y] || ''
729
+ restore_ranges.each do |range|
730
+ col = range.begin
731
+ width = range.end - range.begin
732
+ s = padding_space_with_escape_sequences(Reline::Unicode.take_range(line, col, width), width)
733
+ Reline::IOGate.move_cursor_column(col)
734
+ @output.write "\e[0m#{s}\e[0m"
735
+ end
736
+ max_column = [calculate_width(line, true), new_x_ranges&.map(&:end)&.max || 0].max
737
+ if max_column < restore_ranges.map(&:end).max
738
+ Reline::IOGate.move_cursor_column(max_column)
739
+ Reline::IOGate.erase_after_cursor
740
+ end
741
+ end
742
+ # Render dialog contents
743
+ new_dialog_contents[y]&.each do |x_range, content|
744
+ Reline::IOGate.move_cursor_column(x_range.begin)
745
+ @output.write "\e[0m#{content}\e[0m"
746
+ end
747
+ end
748
+ move_cursor_up(cursor_y - dialog_y)
749
+ Reline::IOGate.move_cursor_column(cursor_column)
750
+ Reline::IOGate.show_cursor
751
+
752
+ @previous_rendered_dialog_y = dialog_y
753
+ end
754
+
755
+ private def update_each_dialog(dialog, cursor_column)
665
756
  if @in_pasting
666
- clear_each_dialog(dialog)
667
757
  dialog.contents = nil
668
758
  dialog.trap_key = nil
669
759
  return
@@ -671,29 +761,20 @@ class Reline::LineEditor
671
761
  dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
672
762
  dialog_render_info = dialog.call(@last_key)
673
763
  if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty?
674
- dialog.lines_backup = {
675
- lines: modify_lines(whole_lines),
676
- line_index: @line_index,
677
- first_line_started_from: @first_line_started_from,
678
- started_from: @started_from,
679
- byte_pointer: @byte_pointer
680
- }
681
- clear_each_dialog(dialog)
682
764
  dialog.contents = nil
683
765
  dialog.trap_key = nil
684
766
  return
685
767
  end
686
- old_dialog = dialog.clone
687
- dialog.contents = dialog_render_info.contents
768
+ contents = dialog_render_info.contents
688
769
  pointer = dialog.pointer
689
770
  if dialog_render_info.width
690
771
  dialog.width = dialog_render_info.width
691
772
  else
692
- dialog.width = dialog.contents.map { |l| calculate_width(l, true) }.max
773
+ dialog.width = contents.map { |l| calculate_width(l, true) }.max
693
774
  end
694
775
  height = dialog_render_info.height || DIALOG_DEFAULT_HEIGHT
695
- height = dialog.contents.size if dialog.contents.size < height
696
- if dialog.contents.size > height
776
+ height = contents.size if contents.size < height
777
+ if contents.size > height
697
778
  if dialog.pointer
698
779
  if dialog.pointer < 0
699
780
  dialog.scroll_top = 0
@@ -703,24 +784,24 @@ class Reline::LineEditor
703
784
  dialog.scroll_top = dialog.pointer
704
785
  end
705
786
  pointer = dialog.pointer - dialog.scroll_top
787
+ else
788
+ dialog.scroll_top = 0
706
789
  end
707
- dialog.contents = dialog.contents[dialog.scroll_top, height]
708
- end
709
- if dialog.contents and dialog.scroll_top >= dialog.contents.size
710
- dialog.scroll_top = dialog.contents.size - height
790
+ contents = contents[dialog.scroll_top, height]
711
791
  end
712
792
  if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
713
793
  bar_max_height = height * 2
714
794
  moving_distance = (dialog_render_info.contents.size - height) * 2
715
795
  position_ratio = dialog.scroll_top.zero? ? 0.0 : ((dialog.scroll_top * 2).to_f / moving_distance)
716
- bar_height = (bar_max_height * ((dialog.contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
717
- dialog.scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i
796
+ bar_height = (bar_max_height * ((contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
797
+ bar_height = MINIMUM_SCROLLBAR_HEIGHT if bar_height < MINIMUM_SCROLLBAR_HEIGHT
798
+ scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i
718
799
  else
719
- dialog.scrollbar_pos = nil
800
+ scrollbar_pos = nil
720
801
  end
721
802
  upper_space = @first_line_started_from - @started_from
722
803
  dialog.column = dialog_render_info.pos.x
723
- dialog.width += @block_elem_width if dialog.scrollbar_pos
804
+ dialog.width += @block_elem_width if scrollbar_pos
724
805
  diff = (dialog.column + dialog.width) - (@screen_size.last)
725
806
  if diff > 0
726
807
  dialog.column -= diff
@@ -730,21 +811,13 @@ class Reline::LineEditor
730
811
  elsif upper_space >= height
731
812
  dialog.vertical_offset = dialog_render_info.pos.y - height
732
813
  else
733
- if (@rest_height - dialog_render_info.pos.y) < height
734
- scroll_down(height + dialog_render_info.pos.y)
735
- move_cursor_up(height + dialog_render_info.pos.y)
736
- end
737
814
  dialog.vertical_offset = dialog_render_info.pos.y + 1
738
815
  end
739
- Reline::IOGate.hide_cursor
740
816
  if dialog.column < 0
741
817
  dialog.column = 0
742
818
  dialog.width = @screen_size.last
743
819
  end
744
- reset_dialog(dialog, old_dialog)
745
- move_cursor_down(dialog.vertical_offset)
746
- Reline::IOGate.move_cursor_column(dialog.column)
747
- dialog.contents.each_with_index do |item, i|
820
+ dialog.contents = contents.map.with_index do |item, i|
748
821
  if i == pointer
749
822
  fg_color = dialog_render_info.pointer_fg_color
750
823
  bg_color = dialog_render_info.pointer_bg_color
@@ -752,185 +825,42 @@ class Reline::LineEditor
752
825
  fg_color = dialog_render_info.fg_color
753
826
  bg_color = dialog_render_info.bg_color
754
827
  end
755
- str_width = dialog.width - (dialog.scrollbar_pos.nil? ? 0 : @block_elem_width)
828
+ str_width = dialog.width - (scrollbar_pos.nil? ? 0 : @block_elem_width)
756
829
  str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, str_width), str_width)
757
- @output.write "\e[#{bg_color}m\e[#{fg_color}m#{str}"
758
- if dialog.scrollbar_pos and (dialog.scrollbar_pos != old_dialog.scrollbar_pos or dialog.column != old_dialog.column)
759
- @output.write "\e[37m"
760
- if dialog.scrollbar_pos <= (i * 2) and (i * 2 + 1) < (dialog.scrollbar_pos + bar_height)
761
- @output.write @full_block
762
- elsif dialog.scrollbar_pos <= (i * 2) and (i * 2) < (dialog.scrollbar_pos + bar_height)
763
- @output.write @upper_half_block
764
- elsif dialog.scrollbar_pos <= (i * 2 + 1) and (i * 2) < (dialog.scrollbar_pos + bar_height)
765
- @output.write @lower_half_block
766
- else
767
- @output.write ' ' * @block_elem_width
768
- end
769
- end
770
- @output.write "\e[0m"
771
- Reline::IOGate.move_cursor_column(dialog.column)
772
- move_cursor_down(1) if i < (dialog.contents.size - 1)
773
- end
774
- Reline::IOGate.move_cursor_column(cursor_column)
775
- move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
776
- Reline::IOGate.show_cursor
777
- dialog.lines_backup = {
778
- lines: modify_lines(whole_lines),
779
- line_index: @line_index,
780
- first_line_started_from: @first_line_started_from,
781
- started_from: @started_from,
782
- byte_pointer: @byte_pointer
783
- }
784
- end
785
-
786
- private def reset_dialog(dialog, old_dialog)
787
- return if dialog.lines_backup.nil? or old_dialog.contents.nil?
788
- prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
789
- visual_lines = []
790
- visual_start = nil
791
- dialog.lines_backup[:lines].each_with_index { |l, i|
792
- pr = prompt_list ? prompt_list[i] : prompt
793
- vl, _ = split_by_width(pr + l, @screen_size.last)
794
- vl.compact!
795
- if i == dialog.lines_backup[:line_index]
796
- visual_start = visual_lines.size + dialog.lines_backup[:started_from]
797
- end
798
- visual_lines.concat(vl)
799
- }
800
- old_y = dialog.lines_backup[:first_line_started_from] + dialog.lines_backup[:started_from]
801
- y = @first_line_started_from + @started_from
802
- y_diff = y - old_y
803
- if (old_y + old_dialog.vertical_offset) < (y + dialog.vertical_offset)
804
- # rerender top
805
- move_cursor_down(old_dialog.vertical_offset - y_diff)
806
- start = visual_start + old_dialog.vertical_offset
807
- line_num = dialog.vertical_offset - old_dialog.vertical_offset
808
- line_num.times do |i|
809
- Reline::IOGate.move_cursor_column(old_dialog.column)
810
- if visual_lines[start + i].nil?
811
- s = ' ' * old_dialog.width
830
+ colored_content = "\e[#{bg_color}m\e[#{fg_color}m#{str}"
831
+ if scrollbar_pos
832
+ color_seq = "\e[37m"
833
+ if scrollbar_pos <= (i * 2) and (i * 2 + 1) < (scrollbar_pos + bar_height)
834
+ colored_content + color_seq + @full_block
835
+ elsif scrollbar_pos <= (i * 2) and (i * 2) < (scrollbar_pos + bar_height)
836
+ colored_content + color_seq + @upper_half_block
837
+ elsif scrollbar_pos <= (i * 2 + 1) and (i * 2) < (scrollbar_pos + bar_height)
838
+ colored_content + color_seq + @lower_half_block
812
839
  else
813
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
814
- s = padding_space_with_escape_sequences(s, old_dialog.width)
840
+ colored_content + color_seq + ' ' * @block_elem_width
815
841
  end
816
- @output.write "\e[0m#{s}\e[0m"
817
- move_cursor_down(1) if i < (line_num - 1)
818
- end
819
- move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
820
- end
821
- if (old_y + old_dialog.vertical_offset + old_dialog.contents.size) > (y + dialog.vertical_offset + dialog.contents.size)
822
- # rerender bottom
823
- move_cursor_down(dialog.vertical_offset + dialog.contents.size - y_diff)
824
- start = visual_start + dialog.vertical_offset + dialog.contents.size
825
- line_num = (old_dialog.vertical_offset + old_dialog.contents.size) - (dialog.vertical_offset + dialog.contents.size)
826
- line_num.times do |i|
827
- Reline::IOGate.move_cursor_column(old_dialog.column)
828
- if visual_lines[start + i].nil?
829
- s = ' ' * old_dialog.width
830
- else
831
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
832
- s = padding_space_with_escape_sequences(s, old_dialog.width)
833
- end
834
- @output.write "\e[0m#{s}\e[0m"
835
- move_cursor_down(1) if i < (line_num - 1)
836
- end
837
- move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
838
- end
839
- if old_dialog.column < dialog.column
840
- # rerender left
841
- move_cursor_down(old_dialog.vertical_offset - y_diff)
842
- width = dialog.column - old_dialog.column
843
- start = visual_start + old_dialog.vertical_offset
844
- line_num = old_dialog.contents.size
845
- line_num.times do |i|
846
- Reline::IOGate.move_cursor_column(old_dialog.column)
847
- if visual_lines[start + i].nil?
848
- s = ' ' * width
849
- else
850
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, width)
851
- s = padding_space_with_escape_sequences(s, dialog.width)
852
- end
853
- @output.write "\e[0m#{s}\e[0m"
854
- move_cursor_down(1) if i < (line_num - 1)
855
- end
856
- move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
857
- end
858
- if (old_dialog.column + old_dialog.width) > (dialog.column + dialog.width)
859
- # rerender right
860
- move_cursor_down(old_dialog.vertical_offset + y_diff)
861
- width = (old_dialog.column + old_dialog.width) - (dialog.column + dialog.width)
862
- start = visual_start + old_dialog.vertical_offset
863
- line_num = old_dialog.contents.size
864
- line_num.times do |i|
865
- Reline::IOGate.move_cursor_column(old_dialog.column + dialog.width)
866
- if visual_lines[start + i].nil?
867
- s = ' ' * width
868
- else
869
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column + dialog.width, width)
870
- rerender_width = old_dialog.width - dialog.width
871
- s = padding_space_with_escape_sequences(s, rerender_width)
872
- end
873
- Reline::IOGate.move_cursor_column(dialog.column + dialog.width)
874
- @output.write "\e[0m#{s}\e[0m"
875
- move_cursor_down(1) if i < (line_num - 1)
842
+ else
843
+ colored_content
876
844
  end
877
- move_cursor_up(old_dialog.vertical_offset + line_num - 1 + y_diff)
878
845
  end
879
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
880
846
  end
881
847
 
882
- private def clear_dialog
883
- @dialogs.each do |dialog|
884
- clear_each_dialog(dialog)
848
+ private def clear_dialog(cursor_column)
849
+ changes = @dialogs.map do |dialog|
850
+ old_dialog = dialog.dup
851
+ dialog.contents = nil
852
+ [old_dialog, dialog]
885
853
  end
854
+ render_dialog_changes(changes, cursor_column)
886
855
  end
887
856
 
888
- private def clear_dialog_with_content
857
+ private def clear_dialog_with_trap_key(cursor_column)
858
+ clear_dialog(cursor_column)
889
859
  @dialogs.each do |dialog|
890
- clear_each_dialog(dialog)
891
- dialog.contents = nil
892
860
  dialog.trap_key = nil
893
861
  end
894
862
  end
895
863
 
896
- private def clear_each_dialog(dialog)
897
- dialog.trap_key = nil
898
- return unless dialog.contents
899
- prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
900
- visual_lines = []
901
- visual_lines_under_dialog = []
902
- visual_start = nil
903
- dialog.lines_backup[:lines].each_with_index { |l, i|
904
- pr = prompt_list ? prompt_list[i] : prompt
905
- vl, _ = split_by_width(pr + l, @screen_size.last)
906
- vl.compact!
907
- if i == dialog.lines_backup[:line_index]
908
- visual_start = visual_lines.size + dialog.lines_backup[:started_from] + dialog.vertical_offset
909
- end
910
- visual_lines.concat(vl)
911
- }
912
- visual_lines_under_dialog = visual_lines[visual_start, dialog.contents.size]
913
- visual_lines_under_dialog = [] if visual_lines_under_dialog.nil?
914
- Reline::IOGate.hide_cursor
915
- move_cursor_down(dialog.vertical_offset)
916
- dialog_vertical_size = dialog.contents.size
917
- dialog_vertical_size.times do |i|
918
- if i < visual_lines_under_dialog.size
919
- Reline::IOGate.move_cursor_column(dialog.column)
920
- str = Reline::Unicode.take_range(visual_lines_under_dialog[i], dialog.column, dialog.width)
921
- str = padding_space_with_escape_sequences(str, dialog.width)
922
- @output.write "\e[0m#{str}\e[0m"
923
- else
924
- Reline::IOGate.move_cursor_column(dialog.column)
925
- @output.write "\e[0m#{' ' * dialog.width}\e[0m"
926
- end
927
- move_cursor_down(1) if i < (dialog_vertical_size - 1)
928
- end
929
- move_cursor_up(dialog_vertical_size - 1 + dialog.vertical_offset)
930
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
931
- Reline::IOGate.show_cursor
932
- end
933
-
934
864
  private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
935
865
  if @screen_height < highest_in_all
936
866
  old_scroll_partial_screen = @scroll_partial_screen
@@ -964,11 +894,20 @@ class Reline::LineEditor
964
894
  end
965
895
  end
966
896
 
967
- private def rerender_added_newline(prompt, prompt_width)
968
- scroll_down(1)
897
+ private def rerender_added_newline(prompt, prompt_width, prompt_list)
969
898
  @buffer_of_lines[@previous_line_index] = @line
970
899
  @line = @buffer_of_lines[@line_index]
971
- unless @in_pasting
900
+ @previous_line_index = nil
901
+ if @in_pasting
902
+ scroll_down(1)
903
+ else
904
+ lines = whole_lines
905
+ prev_line_prompt = @prompt_proc ? prompt_list[@line_index - 1] : prompt
906
+ prev_line_prompt_width = @prompt_proc ? calculate_width(prev_line_prompt, true) : prompt_width
907
+ prev_line = modify_lines(lines)[@line_index - 1]
908
+ move_cursor_up(@started_from)
909
+ render_partial(prev_line_prompt, prev_line_prompt_width, prev_line, @first_line_started_from + @started_from, with_control: false)
910
+ scroll_down(1)
972
911
  render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
973
912
  end
974
913
  @cursor = @cursor_max = calculate_width(@line)
@@ -977,7 +916,6 @@ class Reline::LineEditor
977
916
  @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
978
917
  @first_line_started_from += @started_from + 1
979
918
  @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
980
- @previous_line_index = nil
981
919
  end
982
920
 
983
921
  def just_move_cursor
@@ -990,22 +928,18 @@ class Reline::LineEditor
990
928
  calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
991
929
  end
992
930
  first_line_diff = new_first_line_started_from - @first_line_started_from
993
- new_cursor, new_cursor_max, new_started_from, new_byte_pointer = calculate_nearest_cursor(@buffer_of_lines[@line_index], @cursor, @started_from, @byte_pointer, false)
994
- new_started_from = calculate_height_by_width(prompt_width + new_cursor) - 1
931
+ @cursor, @cursor_max, _, @byte_pointer = calculate_nearest_cursor(@buffer_of_lines[@line_index], @cursor, @started_from, @byte_pointer, false)
932
+ new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
995
933
  calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from)
996
934
  @previous_line_index = nil
935
+ @line = @buffer_of_lines[@line_index]
997
936
  if @rerender_all
998
- @line = @buffer_of_lines[@line_index]
999
937
  rerender_all_lines
1000
938
  @rerender_all = false
1001
939
  true
1002
940
  else
1003
- @line = @buffer_of_lines[@line_index]
1004
941
  @first_line_started_from = new_first_line_started_from
1005
942
  @started_from = new_started_from
1006
- @cursor = new_cursor
1007
- @cursor_max = new_cursor_max
1008
- @byte_pointer = new_byte_pointer
1009
943
  move_cursor_down(first_line_diff + @started_from)
1010
944
  Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
1011
945
  false
@@ -1013,11 +947,7 @@ class Reline::LineEditor
1013
947
  end
1014
948
 
1015
949
  private def rerender_changed_current_line
1016
- if @previous_line_index
1017
- new_lines = whole_lines(index: @previous_line_index, line: @line)
1018
- else
1019
- new_lines = whole_lines
1020
- end
950
+ new_lines = whole_lines
1021
951
  prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
1022
952
  all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
1023
953
  diff = all_height - @highest_in_all
@@ -1238,8 +1168,8 @@ class Reline::LineEditor
1238
1168
  height
1239
1169
  end
1240
1170
 
1241
- private def modify_lines(before)
1242
- return before if before.nil? || before.empty? || simplified_rendering?
1171
+ private def modify_lines(before, force_recalc: false)
1172
+ return before if !force_recalc && (before.nil? || before.empty? || simplified_rendering?)
1243
1173
 
1244
1174
  if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
1245
1175
  after.lines("\n").map { |l| l.chomp('') }
@@ -1371,8 +1301,8 @@ class Reline::LineEditor
1371
1301
  @completion_state = CompletionState::MENU
1372
1302
  end
1373
1303
  if not just_show_list and target < completed
1374
- @line = preposing + completed + completion_append_character.to_s + postposing
1375
- line_to_pointer = preposing + completed + completion_append_character.to_s
1304
+ @line = (preposing + completed + completion_append_character.to_s + postposing).split("\n")[@line_index] || String.new(encoding: @encoding)
1305
+ line_to_pointer = (preposing + completed + completion_append_character.to_s).split("\n").last || String.new(encoding: @encoding)
1376
1306
  @cursor_max = calculate_width(@line)
1377
1307
  @cursor = calculate_width(line_to_pointer)
1378
1308
  @byte_pointer = line_to_pointer.bytesize
@@ -1698,7 +1628,7 @@ class Reline::LineEditor
1698
1628
  return if not @check_new_auto_indent and @previous_line_index # move cursor up or down
1699
1629
  if @check_new_auto_indent and @previous_line_index and @previous_line_index > 0 and @line_index > @previous_line_index
1700
1630
  # Fix indent of a line when a newline is inserted to the next
1701
- new_lines = whole_lines(index: @previous_line_index, line: @line)
1631
+ new_lines = whole_lines
1702
1632
  new_indent = @auto_indent_proc.(new_lines[0..-3].push(''), @line_index - 1, 0, true)
1703
1633
  md = @line.match(/\A */)
1704
1634
  prev_indent = md[0].count(' ')
@@ -1713,23 +1643,20 @@ class Reline::LineEditor
1713
1643
  @line = ' ' * new_indent + @line.lstrip
1714
1644
  end
1715
1645
  end
1716
- if @previous_line_index
1717
- new_lines = whole_lines(index: @previous_line_index, line: @line)
1718
- else
1719
- new_lines = whole_lines
1720
- end
1646
+ new_lines = whole_lines
1721
1647
  new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
1722
- new_indent = @cursor_max if new_indent&.> @cursor_max
1723
1648
  if new_indent&.>= 0
1724
1649
  md = new_lines[@line_index].match(/\A */)
1725
1650
  prev_indent = md[0].count(' ')
1726
1651
  if @check_new_auto_indent
1727
- @buffer_of_lines[@line_index] = ' ' * new_indent + @buffer_of_lines[@line_index].lstrip
1652
+ line = @buffer_of_lines[@line_index] = ' ' * new_indent + @buffer_of_lines[@line_index].lstrip
1728
1653
  @cursor = new_indent
1654
+ @cursor_max = calculate_width(line)
1729
1655
  @byte_pointer = new_indent
1730
1656
  else
1731
1657
  @line = ' ' * new_indent + @line.lstrip
1732
1658
  @cursor += new_indent - prev_indent
1659
+ @cursor_max = calculate_width(@line)
1733
1660
  @byte_pointer += new_indent - prev_indent
1734
1661
  end
1735
1662
  end
@@ -1803,11 +1730,7 @@ class Reline::LineEditor
1803
1730
  target = before
1804
1731
  end
1805
1732
  if @is_multiline
1806
- if @previous_line_index
1807
- lines = whole_lines(index: @previous_line_index, line: @line)
1808
- else
1809
- lines = whole_lines
1810
- end
1733
+ lines = whole_lines
1811
1734
  if @line_index > 0
1812
1735
  preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
1813
1736
  end
@@ -1907,9 +1830,10 @@ class Reline::LineEditor
1907
1830
  @cursor_max = calculate_width(@line)
1908
1831
  end
1909
1832
 
1910
- def whole_lines(index: @line_index, line: @line)
1833
+ def whole_lines
1834
+ index = @previous_line_index || @line_index
1911
1835
  temp_lines = @buffer_of_lines.dup
1912
- temp_lines[index] = line
1836
+ temp_lines[index] = @line
1913
1837
  temp_lines
1914
1838
  end
1915
1839
 
@@ -1917,11 +1841,7 @@ class Reline::LineEditor
1917
1841
  if @buffer_of_lines.size == 1 and @line.nil?
1918
1842
  nil
1919
1843
  else
1920
- if @previous_line_index
1921
- whole_lines(index: @previous_line_index, line: @line).join("\n")
1922
- else
1923
- whole_lines.join("\n")
1924
- end
1844
+ whole_lines.join("\n")
1925
1845
  end
1926
1846
  end
1927
1847
 
@@ -1953,8 +1873,10 @@ class Reline::LineEditor
1953
1873
  end
1954
1874
 
1955
1875
  private def key_delete(key)
1956
- if @config.editing_mode_is?(:vi_insert, :emacs)
1876
+ if @config.editing_mode_is?(:vi_insert)
1957
1877
  ed_delete_next_char(key)
1878
+ elsif @config.editing_mode_is?(:emacs)
1879
+ em_delete(key)
1958
1880
  end
1959
1881
  end
1960
1882
 
@@ -2660,7 +2582,7 @@ class Reline::LineEditor
2660
2582
  alias_method :kill_whole_line, :em_kill_line
2661
2583
 
2662
2584
  private def em_delete(key)
2663
- if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
2585
+ if @line.empty? and (not @is_multiline or @buffer_of_lines.size == 1) and key == "\C-d".ord
2664
2586
  @line = nil
2665
2587
  if @buffer_of_lines.size > 1
2666
2588
  scroll_down(@highest_in_all - @first_line_started_from)