reline 0.3.0 → 0.3.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -48,10 +48,11 @@ class Reline::LineEditor
48
48
  PERFECT_MATCH = :perfect_match
49
49
  end
50
50
 
51
- CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer)
52
- MenuInfo = Struct.new('MenuInfo', :target, :list)
51
+ CompletionJourneyData = Struct.new(:preposing, :postposing, :list, :pointer)
52
+ MenuInfo = Struct.new(: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
@@ -59,6 +60,10 @@ class Reline::LineEditor
59
60
  reset_variables(encoding: encoding)
60
61
  end
61
62
 
63
+ def io_gate
64
+ Reline::IOGate
65
+ end
66
+
62
67
  def set_pasting_state(in_pasting)
63
68
  @in_pasting = in_pasting
64
69
  end
@@ -93,7 +98,7 @@ class Reline::LineEditor
93
98
  mode_string
94
99
  end
95
100
 
96
- private def check_multiline_prompt(buffer)
101
+ private def check_multiline_prompt(buffer, force_recalc: false)
97
102
  if @vi_arg
98
103
  prompt = "(arg: #{@vi_arg}) "
99
104
  @rerender_all = true
@@ -103,7 +108,7 @@ class Reline::LineEditor
103
108
  else
104
109
  prompt = @prompt
105
110
  end
106
- if simplified_rendering?
111
+ if simplified_rendering? && !force_recalc
107
112
  mode_string = check_mode_string
108
113
  prompt = mode_string + prompt if mode_string
109
114
  return [prompt, calculate_width(prompt, true), [prompt] * buffer.size]
@@ -219,7 +224,7 @@ class Reline::LineEditor
219
224
 
220
225
  def set_signal_handlers
221
226
  @old_trap = Signal.trap('INT') {
222
- clear_dialog
227
+ clear_dialog(0)
223
228
  if @scroll_partial_screen
224
229
  move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
225
230
  else
@@ -238,21 +243,10 @@ class Reline::LineEditor
238
243
  @old_trap.call if @old_trap.respond_to?(:call)
239
244
  end
240
245
  }
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
246
  end
249
247
 
250
248
  def finalize
251
249
  Signal.trap('INT', @old_trap)
252
- begin
253
- Signal.trap('TSTP', @old_tstp_trap)
254
- rescue ArgumentError
255
- end
256
250
  end
257
251
 
258
252
  def eof?
@@ -293,6 +287,7 @@ class Reline::LineEditor
293
287
  @in_pasting = false
294
288
  @auto_indent_proc = nil
295
289
  @dialogs = []
290
+ @previous_rendered_dialog_y = 0
296
291
  @last_key = nil
297
292
  @resized = false
298
293
  reset_line
@@ -439,6 +434,7 @@ class Reline::LineEditor
439
434
  @menu_info = nil
440
435
  end
441
436
  prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
437
+ cursor_column = (prompt_width + @cursor) % @screen_size.last
442
438
  if @cleared
443
439
  clear_screen_buffer(prompt, prompt_list, prompt_width)
444
440
  @cleared = false
@@ -449,32 +445,30 @@ class Reline::LineEditor
449
445
  Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
450
446
  Reline::IOGate.move_cursor_column(0)
451
447
  @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
448
+ new_lines = whole_lines
449
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
458
450
  modify_lines(new_lines).each_with_index do |line, index|
459
- @output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\n"
451
+ @output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\r\n"
460
452
  Reline::IOGate.erase_after_cursor
461
453
  end
462
454
  @output.flush
463
- clear_dialog
455
+ clear_dialog(cursor_column)
464
456
  return
465
457
  end
466
458
  new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
467
459
  rendered = false
468
460
  if @add_newline_to_end_of_buffer
469
- rerender_added_newline(prompt, prompt_width)
461
+ clear_dialog_with_trap_key(cursor_column)
462
+ rerender_added_newline(prompt, prompt_width, prompt_list)
470
463
  @add_newline_to_end_of_buffer = false
471
464
  else
472
465
  if @just_cursor_moving and not @rerender_all
466
+ clear_dialog_with_trap_key(cursor_column)
473
467
  rendered = just_move_cursor
474
- render_dialog((prompt_width + @cursor) % @screen_size.last)
475
468
  @just_cursor_moving = false
476
469
  return
477
470
  elsif @previous_line_index or new_highest_in_this != @highest_in_this
471
+ clear_dialog_with_trap_key(cursor_column)
478
472
  rerender_changed_current_line
479
473
  @previous_line_index = nil
480
474
  rendered = true
@@ -488,13 +482,9 @@ class Reline::LineEditor
488
482
  if @is_multiline
489
483
  if finished?
490
484
  # Always rerender on finish because output_modifier_proc may return a different output.
491
- if @previous_line_index
492
- new_lines = whole_lines(index: @previous_line_index, line: @line)
493
- else
494
- new_lines = whole_lines
495
- end
485
+ new_lines = whole_lines
496
486
  line = modify_lines(new_lines)[@line_index]
497
- clear_dialog
487
+ clear_dialog(cursor_column)
498
488
  prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
499
489
  render_partial(prompt, prompt_width, line, @first_line_started_from)
500
490
  move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
@@ -507,7 +497,7 @@ class Reline::LineEditor
507
497
  prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
508
498
  render_partial(prompt, prompt_width, line, @first_line_started_from)
509
499
  end
510
- render_dialog((prompt_width + @cursor) % @screen_size.last)
500
+ render_dialog(cursor_column)
511
501
  end
512
502
  @buffer_of_lines[@line_index] = @line
513
503
  @rest_height = 0 if @scroll_partial_screen
@@ -576,6 +566,16 @@ class Reline::LineEditor
576
566
  @line_editor.instance_variable_get(:@screen_size).last
577
567
  end
578
568
 
569
+ def screen_height
570
+ @line_editor.instance_variable_get(:@screen_size).first
571
+ end
572
+
573
+ def preferred_dialog_height
574
+ rest_height = @line_editor.instance_variable_get(:@rest_height)
575
+ scroll_partial_screen = @line_editor.instance_variable_get(:@scroll_partial_screen) || 0
576
+ [cursor_pos.y - scroll_partial_screen, rest_height, (screen_height + 6) / 5].max
577
+ end
578
+
579
579
  def completion_journey_data
580
580
  @line_editor.instance_variable_get(:@completion_journey_data)
581
581
  end
@@ -591,7 +591,7 @@ class Reline::LineEditor
591
591
 
592
592
  class Dialog
593
593
  attr_reader :name, :contents, :width
594
- attr_accessor :scroll_top, :scrollbar_pos, :pointer, :column, :vertical_offset, :lines_backup, :trap_key
594
+ attr_accessor :scroll_top, :pointer, :column, :vertical_offset, :trap_key
595
595
 
596
596
  def initialize(name, config, proc_scope)
597
597
  @name = name
@@ -647,18 +647,127 @@ class Reline::LineEditor
647
647
 
648
648
  DIALOG_DEFAULT_HEIGHT = 20
649
649
  private def render_dialog(cursor_column)
650
- @dialogs.each do |dialog|
651
- render_each_dialog(dialog, cursor_column)
650
+ changes = @dialogs.map do |dialog|
651
+ old_dialog = dialog.dup
652
+ update_each_dialog(dialog, cursor_column)
653
+ [old_dialog, dialog]
652
654
  end
655
+ render_dialog_changes(changes, cursor_column)
653
656
  end
654
657
 
655
658
  private def padding_space_with_escape_sequences(str, width)
656
- str + (' ' * (width - calculate_width(str, true)))
659
+ padding_width = width - calculate_width(str, true)
660
+ # padding_width should be only positive value. But macOS and Alacritty returns negative value.
661
+ padding_width = 0 if padding_width < 0
662
+ str + (' ' * padding_width)
663
+ end
664
+
665
+ private def range_subtract(base_ranges, subtract_ranges)
666
+ indices = base_ranges.flat_map(&:to_a).uniq.sort - subtract_ranges.flat_map(&:to_a)
667
+ chunks = indices.chunk_while { |a, b| a + 1 == b }
668
+ chunks.map { |a| a.first...a.last + 1 }
669
+ end
670
+
671
+ private def dialog_range(dialog, dialog_y)
672
+ x_range = dialog.column...dialog.column + dialog.width
673
+ y_range = dialog_y + dialog.vertical_offset...dialog_y + dialog.vertical_offset + dialog.contents.size
674
+ [x_range, y_range]
675
+ end
676
+
677
+ private def render_dialog_changes(changes, cursor_column)
678
+ # Collect x-coordinate range and content of previous and current dialogs for each line
679
+ old_dialog_ranges = {}
680
+ new_dialog_ranges = {}
681
+ new_dialog_contents = {}
682
+ changes.each do |old_dialog, new_dialog|
683
+ if old_dialog.contents
684
+ x_range, y_range = dialog_range(old_dialog, @previous_rendered_dialog_y)
685
+ y_range.each do |y|
686
+ (old_dialog_ranges[y] ||= []) << x_range
687
+ end
688
+ end
689
+ if new_dialog.contents
690
+ x_range, y_range = dialog_range(new_dialog, @first_line_started_from + @started_from)
691
+ y_range.each do |y|
692
+ (new_dialog_ranges[y] ||= []) << x_range
693
+ (new_dialog_contents[y] ||= []) << [x_range, new_dialog.contents[y - y_range.begin]]
694
+ end
695
+ end
696
+ end
697
+ return if old_dialog_ranges.empty? && new_dialog_ranges.empty?
698
+
699
+ # Calculate x-coordinate ranges to restore text that was hidden behind dialogs for each line
700
+ ranges_to_restore = {}
701
+ subtract_cache = {}
702
+ old_dialog_ranges.each do |y, old_x_ranges|
703
+ new_x_ranges = new_dialog_ranges[y] || []
704
+ ranges = subtract_cache[[old_x_ranges, new_x_ranges]] ||= range_subtract(old_x_ranges, new_x_ranges)
705
+ ranges_to_restore[y] = ranges if ranges.any?
706
+ end
707
+
708
+ # Create visual_lines for restoring text hidden behind dialogs
709
+ if ranges_to_restore.any?
710
+ lines = whole_lines
711
+ prompt, _prompt_width, prompt_list = check_multiline_prompt(lines, force_recalc: true)
712
+ modified_lines = modify_lines(lines, force_recalc: true)
713
+ visual_lines = []
714
+ modified_lines.each_with_index { |l, i|
715
+ pr = prompt_list ? prompt_list[i] : prompt
716
+ vl, = split_by_width(pr + l, @screen_size.last)
717
+ vl.compact!
718
+ visual_lines.concat(vl)
719
+ }
720
+ end
721
+
722
+ # Clear and rerender all dialogs line by line
723
+ Reline::IOGate.hide_cursor
724
+ ymin, ymax = (ranges_to_restore.keys + new_dialog_ranges.keys).minmax
725
+ scroll_partial_screen = @scroll_partial_screen || 0
726
+ screen_y_range = scroll_partial_screen..(scroll_partial_screen + @screen_height - 1)
727
+ ymin = ymin.clamp(screen_y_range.begin, screen_y_range.end)
728
+ ymax = ymax.clamp(screen_y_range.begin, screen_y_range.end)
729
+ dialog_y = @first_line_started_from + @started_from
730
+ cursor_y = dialog_y
731
+ if @highest_in_all <= ymax
732
+ scroll_down(ymax - cursor_y)
733
+ move_cursor_up(ymax - cursor_y)
734
+ end
735
+ (ymin..ymax).each do |y|
736
+ move_cursor_down(y - cursor_y)
737
+ cursor_y = y
738
+ new_x_ranges = new_dialog_ranges[y]
739
+ restore_ranges = ranges_to_restore[y]
740
+ # Restore text that was hidden behind dialogs
741
+ if restore_ranges
742
+ line = visual_lines[y] || ''
743
+ restore_ranges.each do |range|
744
+ col = range.begin
745
+ width = range.end - range.begin
746
+ s = padding_space_with_escape_sequences(Reline::Unicode.take_range(line, col, width), width)
747
+ Reline::IOGate.move_cursor_column(col)
748
+ @output.write "\e[0m#{s}\e[0m"
749
+ end
750
+ max_column = [calculate_width(line, true), new_x_ranges&.map(&:end)&.max || 0].max
751
+ if max_column < restore_ranges.map(&:end).max
752
+ Reline::IOGate.move_cursor_column(max_column)
753
+ Reline::IOGate.erase_after_cursor
754
+ end
755
+ end
756
+ # Render dialog contents
757
+ new_dialog_contents[y]&.each do |x_range, content|
758
+ Reline::IOGate.move_cursor_column(x_range.begin)
759
+ @output.write "\e[0m#{content}\e[0m"
760
+ end
761
+ end
762
+ move_cursor_up(cursor_y - dialog_y)
763
+ Reline::IOGate.move_cursor_column(cursor_column)
764
+ Reline::IOGate.show_cursor
765
+
766
+ @previous_rendered_dialog_y = dialog_y
657
767
  end
658
768
 
659
- private def render_each_dialog(dialog, cursor_column)
769
+ private def update_each_dialog(dialog, cursor_column)
660
770
  if @in_pasting
661
- clear_each_dialog(dialog)
662
771
  dialog.contents = nil
663
772
  dialog.trap_key = nil
664
773
  return
@@ -666,29 +775,20 @@ class Reline::LineEditor
666
775
  dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
667
776
  dialog_render_info = dialog.call(@last_key)
668
777
  if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty?
669
- dialog.lines_backup = {
670
- lines: modify_lines(whole_lines),
671
- line_index: @line_index,
672
- first_line_started_from: @first_line_started_from,
673
- started_from: @started_from,
674
- byte_pointer: @byte_pointer
675
- }
676
- clear_each_dialog(dialog)
677
778
  dialog.contents = nil
678
779
  dialog.trap_key = nil
679
780
  return
680
781
  end
681
- old_dialog = dialog.clone
682
- dialog.contents = dialog_render_info.contents
782
+ contents = dialog_render_info.contents
683
783
  pointer = dialog.pointer
684
784
  if dialog_render_info.width
685
785
  dialog.width = dialog_render_info.width
686
786
  else
687
- dialog.width = dialog.contents.map { |l| calculate_width(l, true) }.max
787
+ dialog.width = contents.map { |l| calculate_width(l, true) }.max
688
788
  end
689
789
  height = dialog_render_info.height || DIALOG_DEFAULT_HEIGHT
690
- height = dialog.contents.size if dialog.contents.size < height
691
- if dialog.contents.size > height
790
+ height = contents.size if contents.size < height
791
+ if contents.size > height
692
792
  if dialog.pointer
693
793
  if dialog.pointer < 0
694
794
  dialog.scroll_top = 0
@@ -698,24 +798,24 @@ class Reline::LineEditor
698
798
  dialog.scroll_top = dialog.pointer
699
799
  end
700
800
  pointer = dialog.pointer - dialog.scroll_top
801
+ else
802
+ dialog.scroll_top = 0
701
803
  end
702
- dialog.contents = dialog.contents[dialog.scroll_top, height]
703
- end
704
- if dialog.contents and dialog.scroll_top >= dialog.contents.size
705
- dialog.scroll_top = dialog.contents.size - height
804
+ contents = contents[dialog.scroll_top, height]
706
805
  end
707
806
  if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
708
807
  bar_max_height = height * 2
709
808
  moving_distance = (dialog_render_info.contents.size - height) * 2
710
809
  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
810
+ bar_height = (bar_max_height * ((contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
811
+ bar_height = MINIMUM_SCROLLBAR_HEIGHT if bar_height < MINIMUM_SCROLLBAR_HEIGHT
812
+ scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i
713
813
  else
714
- dialog.scrollbar_pos = nil
814
+ scrollbar_pos = nil
715
815
  end
716
816
  upper_space = @first_line_started_from - @started_from
717
817
  dialog.column = dialog_render_info.pos.x
718
- dialog.width += @block_elem_width if dialog.scrollbar_pos
818
+ dialog.width += @block_elem_width if scrollbar_pos
719
819
  diff = (dialog.column + dialog.width) - (@screen_size.last)
720
820
  if diff > 0
721
821
  dialog.column -= diff
@@ -725,200 +825,54 @@ class Reline::LineEditor
725
825
  elsif upper_space >= height
726
826
  dialog.vertical_offset = dialog_render_info.pos.y - height
727
827
  else
728
- if (@rest_height - dialog_render_info.pos.y) < height
729
- scroll_down(height + dialog_render_info.pos.y)
730
- move_cursor_up(height + dialog_render_info.pos.y)
731
- end
732
828
  dialog.vertical_offset = dialog_render_info.pos.y + 1
733
829
  end
734
- Reline::IOGate.hide_cursor
735
830
  if dialog.column < 0
736
831
  dialog.column = 0
737
832
  dialog.width = @screen_size.last
738
833
  end
739
- reset_dialog(dialog, old_dialog)
740
- move_cursor_down(dialog.vertical_offset)
741
- Reline::IOGate.move_cursor_column(dialog.column)
742
- dialog.contents.each_with_index do |item, i|
834
+ dialog.contents = contents.map.with_index do |item, i|
743
835
  if i == pointer
744
- bg_color = '45'
836
+ fg_color = dialog_render_info.pointer_fg_color
837
+ bg_color = dialog_render_info.pointer_bg_color
745
838
  else
746
- if dialog_render_info.bg_color
747
- bg_color = dialog_render_info.bg_color
748
- else
749
- bg_color = '46'
750
- end
839
+ fg_color = dialog_render_info.fg_color
840
+ bg_color = dialog_render_info.bg_color
751
841
  end
752
- str_width = dialog.width - (dialog.scrollbar_pos.nil? ? 0 : @block_elem_width)
842
+ str_width = dialog.width - (scrollbar_pos.nil? ? 0 : @block_elem_width)
753
843
  str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, str_width), str_width)
754
- @output.write "\e[#{bg_color}m#{str}"
755
- if dialog.scrollbar_pos and (dialog.scrollbar_pos != old_dialog.scrollbar_pos or dialog.column != old_dialog.column)
756
- @output.write "\e[37m"
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
761
- str += ''
762
- elsif dialog.scrollbar_pos <= (i * 2 + 1) and (i * 2) < (dialog.scrollbar_pos + bar_height)
763
- @output.write @lower_half_block
844
+ colored_content = "\e[#{bg_color}m\e[#{fg_color}m#{str}"
845
+ if scrollbar_pos
846
+ color_seq = "\e[37m"
847
+ if scrollbar_pos <= (i * 2) and (i * 2 + 1) < (scrollbar_pos + bar_height)
848
+ colored_content + color_seq + @full_block
849
+ elsif scrollbar_pos <= (i * 2) and (i * 2) < (scrollbar_pos + bar_height)
850
+ colored_content + color_seq + @upper_half_block
851
+ elsif scrollbar_pos <= (i * 2 + 1) and (i * 2) < (scrollbar_pos + bar_height)
852
+ colored_content + color_seq + @lower_half_block
764
853
  else
765
- @output.write ' ' * @block_elem_width
854
+ colored_content + color_seq + ' ' * @block_elem_width
766
855
  end
856
+ else
857
+ colored_content
767
858
  end
768
- @output.write "\e[0m"
769
- Reline::IOGate.move_cursor_column(dialog.column)
770
- move_cursor_down(1) if i < (dialog.contents.size - 1)
771
859
  end
772
- Reline::IOGate.move_cursor_column(cursor_column)
773
- move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
774
- Reline::IOGate.show_cursor
775
- dialog.lines_backup = {
776
- lines: modify_lines(whole_lines),
777
- line_index: @line_index,
778
- first_line_started_from: @first_line_started_from,
779
- started_from: @started_from,
780
- byte_pointer: @byte_pointer
781
- }
782
860
  end
783
861
 
784
- private def reset_dialog(dialog, old_dialog)
785
- return if dialog.lines_backup.nil? or old_dialog.contents.nil?
786
- prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
787
- visual_lines = []
788
- visual_start = nil
789
- dialog.lines_backup[:lines].each_with_index { |l, i|
790
- pr = prompt_list ? prompt_list[i] : prompt
791
- vl, _ = split_by_width(pr + l, @screen_size.last)
792
- vl.compact!
793
- if i == dialog.lines_backup[:line_index]
794
- visual_start = visual_lines.size + dialog.lines_backup[:started_from]
795
- end
796
- visual_lines.concat(vl)
797
- }
798
- old_y = dialog.lines_backup[:first_line_started_from] + dialog.lines_backup[:started_from]
799
- y = @first_line_started_from + @started_from
800
- y_diff = y - old_y
801
- if (old_y + old_dialog.vertical_offset) < (y + dialog.vertical_offset)
802
- # rerender top
803
- move_cursor_down(old_dialog.vertical_offset - y_diff)
804
- start = visual_start + old_dialog.vertical_offset
805
- line_num = dialog.vertical_offset - old_dialog.vertical_offset
806
- line_num.times do |i|
807
- Reline::IOGate.move_cursor_column(old_dialog.column)
808
- if visual_lines[start + i].nil?
809
- s = ' ' * old_dialog.width
810
- else
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)
813
- end
814
- @output.write "\e[0m#{s}\e[0m"
815
- move_cursor_down(1) if i < (line_num - 1)
816
- end
817
- move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
818
- end
819
- if (old_y + old_dialog.vertical_offset + old_dialog.contents.size) > (y + dialog.vertical_offset + dialog.contents.size)
820
- # rerender bottom
821
- move_cursor_down(dialog.vertical_offset + dialog.contents.size - y_diff)
822
- start = visual_start + dialog.vertical_offset + dialog.contents.size
823
- line_num = (old_dialog.vertical_offset + old_dialog.contents.size) - (dialog.vertical_offset + dialog.contents.size)
824
- line_num.times do |i|
825
- Reline::IOGate.move_cursor_column(old_dialog.column)
826
- if visual_lines[start + i].nil?
827
- s = ' ' * old_dialog.width
828
- else
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)
831
- end
832
- @output.write "\e[0m#{s}\e[0m"
833
- move_cursor_down(1) if i < (line_num - 1)
834
- end
835
- move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
836
- end
837
- if old_dialog.column < dialog.column
838
- # rerender left
839
- move_cursor_down(old_dialog.vertical_offset - y_diff)
840
- width = dialog.column - old_dialog.column
841
- start = visual_start + old_dialog.vertical_offset
842
- line_num = old_dialog.contents.size
843
- line_num.times do |i|
844
- Reline::IOGate.move_cursor_column(old_dialog.column)
845
- if visual_lines[start + i].nil?
846
- s = ' ' * width
847
- else
848
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, width)
849
- s = padding_space_with_escape_sequences(s, dialog.width)
850
- end
851
- @output.write "\e[0m#{s}\e[0m"
852
- move_cursor_down(1) if i < (line_num - 1)
853
- end
854
- move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
855
- end
856
- if (old_dialog.column + old_dialog.width) > (dialog.column + dialog.width)
857
- # rerender right
858
- move_cursor_down(old_dialog.vertical_offset + y_diff)
859
- width = (old_dialog.column + old_dialog.width) - (dialog.column + dialog.width)
860
- start = visual_start + old_dialog.vertical_offset
861
- line_num = old_dialog.contents.size
862
- line_num.times do |i|
863
- Reline::IOGate.move_cursor_column(old_dialog.column + dialog.width)
864
- if visual_lines[start + i].nil?
865
- s = ' ' * width
866
- else
867
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column + dialog.width, width)
868
- rerender_width = old_dialog.width - dialog.width
869
- s = padding_space_with_escape_sequences(s, rerender_width)
870
- end
871
- Reline::IOGate.move_cursor_column(dialog.column + dialog.width)
872
- @output.write "\e[0m#{s}\e[0m"
873
- move_cursor_down(1) if i < (line_num - 1)
874
- end
875
- move_cursor_up(old_dialog.vertical_offset + line_num - 1 + y_diff)
862
+ private def clear_dialog(cursor_column)
863
+ changes = @dialogs.map do |dialog|
864
+ old_dialog = dialog.dup
865
+ dialog.contents = nil
866
+ [old_dialog, dialog]
876
867
  end
877
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
868
+ render_dialog_changes(changes, cursor_column)
878
869
  end
879
870
 
880
- private def clear_dialog
871
+ private def clear_dialog_with_trap_key(cursor_column)
872
+ clear_dialog(cursor_column)
881
873
  @dialogs.each do |dialog|
882
- clear_each_dialog(dialog)
883
- end
884
- end
885
-
886
- private def clear_each_dialog(dialog)
887
- dialog.trap_key = nil
888
- return unless dialog.contents
889
- prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
890
- visual_lines = []
891
- visual_lines_under_dialog = []
892
- visual_start = nil
893
- dialog.lines_backup[:lines].each_with_index { |l, i|
894
- pr = prompt_list ? prompt_list[i] : prompt
895
- vl, _ = split_by_width(pr + l, @screen_size.last)
896
- vl.compact!
897
- if i == dialog.lines_backup[:line_index]
898
- visual_start = visual_lines.size + dialog.lines_backup[:started_from] + dialog.vertical_offset
899
- end
900
- visual_lines.concat(vl)
901
- }
902
- visual_lines_under_dialog = visual_lines[visual_start, dialog.contents.size]
903
- visual_lines_under_dialog = [] if visual_lines_under_dialog.nil?
904
- Reline::IOGate.hide_cursor
905
- move_cursor_down(dialog.vertical_offset)
906
- dialog_vertical_size = dialog.contents.size
907
- dialog_vertical_size.times do |i|
908
- if i < visual_lines_under_dialog.size
909
- Reline::IOGate.move_cursor_column(dialog.column)
910
- str = Reline::Unicode.take_range(visual_lines_under_dialog[i], dialog.column, dialog.width)
911
- str = padding_space_with_escape_sequences(str, dialog.width)
912
- @output.write "\e[0m#{str}\e[0m"
913
- else
914
- Reline::IOGate.move_cursor_column(dialog.column)
915
- @output.write "\e[0m#{' ' * dialog.width}\e[0m"
916
- end
917
- move_cursor_down(1) if i < (dialog_vertical_size - 1)
874
+ dialog.trap_key = nil
918
875
  end
919
- move_cursor_up(dialog_vertical_size - 1 + dialog.vertical_offset)
920
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
921
- Reline::IOGate.show_cursor
922
876
  end
923
877
 
924
878
  private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
@@ -954,11 +908,20 @@ class Reline::LineEditor
954
908
  end
955
909
  end
956
910
 
957
- private def rerender_added_newline(prompt, prompt_width)
958
- scroll_down(1)
911
+ private def rerender_added_newline(prompt, prompt_width, prompt_list)
959
912
  @buffer_of_lines[@previous_line_index] = @line
960
913
  @line = @buffer_of_lines[@line_index]
961
- unless @in_pasting
914
+ @previous_line_index = nil
915
+ if @in_pasting
916
+ scroll_down(1)
917
+ else
918
+ lines = whole_lines
919
+ prev_line_prompt = @prompt_proc ? prompt_list[@line_index - 1] : prompt
920
+ prev_line_prompt_width = @prompt_proc ? calculate_width(prev_line_prompt, true) : prompt_width
921
+ prev_line = modify_lines(lines)[@line_index - 1]
922
+ move_cursor_up(@started_from)
923
+ render_partial(prev_line_prompt, prev_line_prompt_width, prev_line, @first_line_started_from + @started_from, with_control: false)
924
+ scroll_down(1)
962
925
  render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
963
926
  end
964
927
  @cursor = @cursor_max = calculate_width(@line)
@@ -967,7 +930,6 @@ class Reline::LineEditor
967
930
  @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
968
931
  @first_line_started_from += @started_from + 1
969
932
  @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
970
- @previous_line_index = nil
971
933
  end
972
934
 
973
935
  def just_move_cursor
@@ -980,22 +942,18 @@ class Reline::LineEditor
980
942
  calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
981
943
  end
982
944
  first_line_diff = new_first_line_started_from - @first_line_started_from
983
- 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)
984
- new_started_from = calculate_height_by_width(prompt_width + new_cursor) - 1
945
+ @cursor, @cursor_max, _, @byte_pointer = calculate_nearest_cursor(@buffer_of_lines[@line_index], @cursor, @started_from, @byte_pointer, false)
946
+ new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
985
947
  calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from)
986
948
  @previous_line_index = nil
949
+ @line = @buffer_of_lines[@line_index]
987
950
  if @rerender_all
988
- @line = @buffer_of_lines[@line_index]
989
951
  rerender_all_lines
990
952
  @rerender_all = false
991
953
  true
992
954
  else
993
- @line = @buffer_of_lines[@line_index]
994
955
  @first_line_started_from = new_first_line_started_from
995
956
  @started_from = new_started_from
996
- @cursor = new_cursor
997
- @cursor_max = new_cursor_max
998
- @byte_pointer = new_byte_pointer
999
957
  move_cursor_down(first_line_diff + @started_from)
1000
958
  Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
1001
959
  false
@@ -1003,11 +961,7 @@ class Reline::LineEditor
1003
961
  end
1004
962
 
1005
963
  private def rerender_changed_current_line
1006
- if @previous_line_index
1007
- new_lines = whole_lines(index: @previous_line_index, line: @line)
1008
- else
1009
- new_lines = whole_lines
1010
- end
964
+ new_lines = whole_lines
1011
965
  prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
1012
966
  all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
1013
967
  diff = all_height - @highest_in_all
@@ -1228,8 +1182,8 @@ class Reline::LineEditor
1228
1182
  height
1229
1183
  end
1230
1184
 
1231
- private def modify_lines(before)
1232
- return before if before.nil? || before.empty? || simplified_rendering?
1185
+ private def modify_lines(before, force_recalc: false)
1186
+ return before if !force_recalc && (before.nil? || before.empty? || simplified_rendering?)
1233
1187
 
1234
1188
  if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
1235
1189
  after.lines("\n").map { |l| l.chomp('') }
@@ -1361,8 +1315,8 @@ class Reline::LineEditor
1361
1315
  @completion_state = CompletionState::MENU
1362
1316
  end
1363
1317
  if not just_show_list and target < completed
1364
- @line = preposing + completed + completion_append_character.to_s + postposing
1365
- line_to_pointer = preposing + completed + completion_append_character.to_s
1318
+ @line = (preposing + completed + completion_append_character.to_s + postposing).split("\n")[@line_index] || String.new(encoding: @encoding)
1319
+ line_to_pointer = (preposing + completed + completion_append_character.to_s).split("\n").last || String.new(encoding: @encoding)
1366
1320
  @cursor_max = calculate_width(@line)
1367
1321
  @cursor = calculate_width(line_to_pointer)
1368
1322
  @byte_pointer = line_to_pointer.bytesize
@@ -1420,7 +1374,7 @@ class Reline::LineEditor
1420
1374
  if @waiting_operator_proc
1421
1375
  if VI_MOTIONS.include?(method_symbol)
1422
1376
  old_cursor, old_byte_pointer = @cursor, @byte_pointer
1423
- @vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg > 1
1377
+ @vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg&.> 1
1424
1378
  block.(true)
1425
1379
  unless @waiting_proc
1426
1380
  cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
@@ -1560,11 +1514,13 @@ class Reline::LineEditor
1560
1514
  return if key.char >= 128 # maybe, first byte of multi byte
1561
1515
  method_symbol = @config.editing_mode.get_method(key.combined_char)
1562
1516
  if key.with_meta and method_symbol == :ed_unassigned
1563
- # split ESC + key
1564
- method_symbol = @config.editing_mode.get_method("\e".ord)
1565
- process_key("\e".ord, method_symbol)
1566
- method_symbol = @config.editing_mode.get_method(key.char)
1567
- process_key(key.char, method_symbol)
1517
+ if @config.editing_mode_is?(:vi_command, :vi_insert)
1518
+ # split ESC + key in vi mode
1519
+ method_symbol = @config.editing_mode.get_method("\e".ord)
1520
+ process_key("\e".ord, method_symbol)
1521
+ method_symbol = @config.editing_mode.get_method(key.char)
1522
+ process_key(key.char, method_symbol)
1523
+ end
1568
1524
  else
1569
1525
  process_key(key.combined_char, method_symbol)
1570
1526
  end
@@ -1688,14 +1644,14 @@ class Reline::LineEditor
1688
1644
  return if not @check_new_auto_indent and @previous_line_index # move cursor up or down
1689
1645
  if @check_new_auto_indent and @previous_line_index and @previous_line_index > 0 and @line_index > @previous_line_index
1690
1646
  # Fix indent of a line when a newline is inserted to the next
1691
- new_lines = whole_lines(index: @previous_line_index, line: @line)
1647
+ new_lines = whole_lines
1692
1648
  new_indent = @auto_indent_proc.(new_lines[0..-3].push(''), @line_index - 1, 0, true)
1693
1649
  md = @line.match(/\A */)
1694
1650
  prev_indent = md[0].count(' ')
1695
1651
  @line = ' ' * new_indent + @line.lstrip
1696
1652
 
1697
1653
  new_indent = nil
1698
- result = @auto_indent_proc.(new_lines[0..-2], @line_index - 1, (new_lines[-2].size + 1), false)
1654
+ result = @auto_indent_proc.(new_lines[0..-2], @line_index - 1, (new_lines[@line_index - 1].bytesize + 1), false)
1699
1655
  if result
1700
1656
  new_indent = result
1701
1657
  end
@@ -1703,23 +1659,20 @@ class Reline::LineEditor
1703
1659
  @line = ' ' * new_indent + @line.lstrip
1704
1660
  end
1705
1661
  end
1706
- if @previous_line_index
1707
- new_lines = whole_lines(index: @previous_line_index, line: @line)
1708
- else
1709
- new_lines = whole_lines
1710
- end
1662
+ new_lines = whole_lines
1711
1663
  new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
1712
- new_indent = @cursor_max if new_indent&.> @cursor_max
1713
1664
  if new_indent&.>= 0
1714
1665
  md = new_lines[@line_index].match(/\A */)
1715
1666
  prev_indent = md[0].count(' ')
1716
1667
  if @check_new_auto_indent
1717
- @buffer_of_lines[@line_index] = ' ' * new_indent + @buffer_of_lines[@line_index].lstrip
1668
+ line = @buffer_of_lines[@line_index] = ' ' * new_indent + @buffer_of_lines[@line_index].lstrip
1718
1669
  @cursor = new_indent
1670
+ @cursor_max = calculate_width(line)
1719
1671
  @byte_pointer = new_indent
1720
1672
  else
1721
1673
  @line = ' ' * new_indent + @line.lstrip
1722
1674
  @cursor += new_indent - prev_indent
1675
+ @cursor_max = calculate_width(@line)
1723
1676
  @byte_pointer += new_indent - prev_indent
1724
1677
  end
1725
1678
  end
@@ -1793,11 +1746,7 @@ class Reline::LineEditor
1793
1746
  target = before
1794
1747
  end
1795
1748
  if @is_multiline
1796
- if @previous_line_index
1797
- lines = whole_lines(index: @previous_line_index, line: @line)
1798
- else
1799
- lines = whole_lines
1800
- end
1749
+ lines = whole_lines
1801
1750
  if @line_index > 0
1802
1751
  preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
1803
1752
  end
@@ -1897,9 +1846,10 @@ class Reline::LineEditor
1897
1846
  @cursor_max = calculate_width(@line)
1898
1847
  end
1899
1848
 
1900
- def whole_lines(index: @line_index, line: @line)
1849
+ def whole_lines
1850
+ index = @previous_line_index || @line_index
1901
1851
  temp_lines = @buffer_of_lines.dup
1902
- temp_lines[index] = line
1852
+ temp_lines[index] = @line
1903
1853
  temp_lines
1904
1854
  end
1905
1855
 
@@ -1907,11 +1857,7 @@ class Reline::LineEditor
1907
1857
  if @buffer_of_lines.size == 1 and @line.nil?
1908
1858
  nil
1909
1859
  else
1910
- if @previous_line_index
1911
- whole_lines(index: @previous_line_index, line: @line).join("\n")
1912
- else
1913
- whole_lines.join("\n")
1914
- end
1860
+ whole_lines.join("\n")
1915
1861
  end
1916
1862
  end
1917
1863
 
@@ -1943,8 +1889,10 @@ class Reline::LineEditor
1943
1889
  end
1944
1890
 
1945
1891
  private def key_delete(key)
1946
- if @config.editing_mode_is?(:vi_insert, :emacs)
1892
+ if @config.editing_mode_is?(:vi_insert)
1947
1893
  ed_delete_next_char(key)
1894
+ elsif @config.editing_mode_is?(:emacs)
1895
+ em_delete(key)
1948
1896
  end
1949
1897
  end
1950
1898
 
@@ -2223,6 +2171,8 @@ class Reline::LineEditor
2223
2171
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2224
2172
  @line_index = @buffer_of_lines.size - 1
2225
2173
  @line = @buffer_of_lines.last
2174
+ @byte_pointer = @line.bytesize
2175
+ @cursor = @cursor_max = calculate_width(@line)
2226
2176
  @rerender_all = true
2227
2177
  @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
2228
2178
  else
@@ -2648,7 +2598,7 @@ class Reline::LineEditor
2648
2598
  alias_method :kill_whole_line, :em_kill_line
2649
2599
 
2650
2600
  private def em_delete(key)
2651
- if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
2601
+ if @line.empty? and (not @is_multiline or @buffer_of_lines.size == 1) and key == "\C-d".ord
2652
2602
  @line = nil
2653
2603
  if @buffer_of_lines.size > 1
2654
2604
  scroll_down(@highest_in_all - @first_line_started_from)
@@ -3342,4 +3292,7 @@ class Reline::LineEditor
3342
3292
  @mark_pointer = new_pointer
3343
3293
  end
3344
3294
  alias_method :exchange_point_and_mark, :em_exchange_mark
3295
+
3296
+ private def em_meta_next(key)
3297
+ end
3345
3298
  end