reline 0.3.2 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)