reline 0.3.3 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b0fe224245c6f66bdfa42b7fd6b0dc4c30f9645f0c1498fd7780ff17db642eec
4
- data.tar.gz: af1b5a53e09f4b2c60ae20e153a4b3043ea3a19a38c58be697067c58f63feec7
3
+ metadata.gz: 3e5c7a3a92a99dc4e59ae8299c90086a1d5508a7aa7f23579ac9e69a516da22a
4
+ data.tar.gz: 4779c4eba572d69bda056dafdc974f898678a9a0d40636ffc54836bbcedce2e8
5
5
  SHA512:
6
- metadata.gz: cb456e06f2cef817fe3ae8fa27e9666587f2b3175cdbc97714ab3638d5b11bf606595d772c4e100fc21d9f50ed6e55fb6b833c28bad6b4ae3c8838d2418e75f5
7
- data.tar.gz: 6f3eb2f3feee78d7b4754c069a7d6c0d5d29af100bcb493bab2696c9b610c86de4fb3b201d624b6d00f218f1257a526d0c29bf95c79a1ed3daf117f0c95a2768
6
+ metadata.gz: ec283bebd9d93cc46c0587da2d051f29a2f119c785eff4a0d554b019a6f4cc2c9503670e0f073c11ce3399b72ecd2b3cdd4d92315251022b4ef54378bb9eea35
7
+ data.tar.gz: ffab61dee9ca267ff0015280a0fb0cbd5c76b271867dd86fc1861379af4821929917a467017b1a9b6cd17c92e3bbc74905c7b43865406b5053890bdb42b00007
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
- [![Build Status](https://travis-ci.com/ruby/reline.svg?branch=master)](https://travis-ci.com/ruby/reline)
1
+ [![Gem Version](https://badge.fury.io/rb/reline.svg)](https://badge.fury.io/rb/reline)
2
+ [![CI](https://github.com/ruby/reline/actions/workflows/reline.yml/badge.svg)](https://github.com/ruby/reline/actions/workflows/reline.yml)
2
3
 
3
4
  This is a screen capture of *IRB improved by Reline*.
4
5
 
@@ -94,7 +94,7 @@ class Reline::LineEditor
94
94
  mode_string
95
95
  end
96
96
 
97
- private def check_multiline_prompt(buffer)
97
+ private def check_multiline_prompt(buffer, force_recalc: false)
98
98
  if @vi_arg
99
99
  prompt = "(arg: #{@vi_arg}) "
100
100
  @rerender_all = true
@@ -104,7 +104,7 @@ class Reline::LineEditor
104
104
  else
105
105
  prompt = @prompt
106
106
  end
107
- if simplified_rendering?
107
+ if simplified_rendering? && !force_recalc
108
108
  mode_string = check_mode_string
109
109
  prompt = mode_string + prompt if mode_string
110
110
  return [prompt, calculate_width(prompt, true), [prompt] * buffer.size]
@@ -220,7 +220,7 @@ class Reline::LineEditor
220
220
 
221
221
  def set_signal_handlers
222
222
  @old_trap = Signal.trap('INT') {
223
- clear_dialog
223
+ clear_dialog(0)
224
224
  if @scroll_partial_screen
225
225
  move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
226
226
  else
@@ -239,21 +239,10 @@ class Reline::LineEditor
239
239
  @old_trap.call if @old_trap.respond_to?(:call)
240
240
  end
241
241
  }
242
- begin
243
- @old_tstp_trap = Signal.trap('TSTP') {
244
- Reline::IOGate.ungetc("\C-z".ord)
245
- @old_tstp_trap.call if @old_tstp_trap.respond_to?(:call)
246
- }
247
- rescue ArgumentError
248
- end
249
242
  end
250
243
 
251
244
  def finalize
252
245
  Signal.trap('INT', @old_trap)
253
- begin
254
- Signal.trap('TSTP', @old_tstp_trap)
255
- rescue ArgumentError
256
- end
257
246
  end
258
247
 
259
248
  def eof?
@@ -294,6 +283,7 @@ class Reline::LineEditor
294
283
  @in_pasting = false
295
284
  @auto_indent_proc = nil
296
285
  @dialogs = []
286
+ @previous_rendered_dialog_y = 0
297
287
  @last_key = nil
298
288
  @resized = false
299
289
  reset_line
@@ -440,6 +430,7 @@ class Reline::LineEditor
440
430
  @menu_info = nil
441
431
  end
442
432
  prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
433
+ cursor_column = (prompt_width + @cursor) % @screen_size.last
443
434
  if @cleared
444
435
  clear_screen_buffer(prompt, prompt_list, prompt_width)
445
436
  @cleared = false
@@ -457,23 +448,23 @@ class Reline::LineEditor
457
448
  Reline::IOGate.erase_after_cursor
458
449
  end
459
450
  @output.flush
460
- clear_dialog
451
+ clear_dialog(cursor_column)
461
452
  return
462
453
  end
463
454
  new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
464
455
  rendered = false
465
456
  if @add_newline_to_end_of_buffer
466
- clear_dialog_with_content
457
+ clear_dialog_with_trap_key(cursor_column)
467
458
  rerender_added_newline(prompt, prompt_width, prompt_list)
468
459
  @add_newline_to_end_of_buffer = false
469
460
  else
470
461
  if @just_cursor_moving and not @rerender_all
471
- clear_dialog_with_content
462
+ clear_dialog_with_trap_key(cursor_column)
472
463
  rendered = just_move_cursor
473
464
  @just_cursor_moving = false
474
465
  return
475
466
  elsif @previous_line_index or new_highest_in_this != @highest_in_this
476
- clear_dialog_with_content
467
+ clear_dialog_with_trap_key(cursor_column)
477
468
  rerender_changed_current_line
478
469
  @previous_line_index = nil
479
470
  rendered = true
@@ -489,7 +480,7 @@ class Reline::LineEditor
489
480
  # Always rerender on finish because output_modifier_proc may return a different output.
490
481
  new_lines = whole_lines
491
482
  line = modify_lines(new_lines)[@line_index]
492
- clear_dialog
483
+ clear_dialog(cursor_column)
493
484
  prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
494
485
  render_partial(prompt, prompt_width, line, @first_line_started_from)
495
486
  move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
@@ -502,7 +493,7 @@ class Reline::LineEditor
502
493
  prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
503
494
  render_partial(prompt, prompt_width, line, @first_line_started_from)
504
495
  end
505
- render_dialog((prompt_width + @cursor) % @screen_size.last)
496
+ render_dialog(cursor_column)
506
497
  end
507
498
  @buffer_of_lines[@line_index] = @line
508
499
  @rest_height = 0 if @scroll_partial_screen
@@ -586,7 +577,7 @@ class Reline::LineEditor
586
577
 
587
578
  class Dialog
588
579
  attr_reader :name, :contents, :width
589
- 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
590
581
 
591
582
  def initialize(name, config, proc_scope)
592
583
  @name = name
@@ -642,9 +633,12 @@ class Reline::LineEditor
642
633
 
643
634
  DIALOG_DEFAULT_HEIGHT = 20
644
635
  private def render_dialog(cursor_column)
645
- @dialogs.each do |dialog|
646
- 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]
647
640
  end
641
+ render_dialog_changes(changes, cursor_column)
648
642
  end
649
643
 
650
644
  private def padding_space_with_escape_sequences(str, width)
@@ -654,9 +648,112 @@ class Reline::LineEditor
654
648
  str + (' ' * padding_width)
655
649
  end
656
650
 
657
- 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)
658
756
  if @in_pasting
659
- clear_each_dialog(dialog)
660
757
  dialog.contents = nil
661
758
  dialog.trap_key = nil
662
759
  return
@@ -664,31 +761,20 @@ class Reline::LineEditor
664
761
  dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
665
762
  dialog_render_info = dialog.call(@last_key)
666
763
  if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty?
667
- lines = whole_lines
668
- dialog.lines_backup = {
669
- unmodified_lines: lines,
670
- lines: modify_lines(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
764
  dialog.contents = nil
678
765
  dialog.trap_key = nil
679
766
  return
680
767
  end
681
- old_dialog = dialog.clone
682
- dialog.contents = dialog_render_info.contents
768
+ contents = dialog_render_info.contents
683
769
  pointer = dialog.pointer
684
770
  if dialog_render_info.width
685
771
  dialog.width = dialog_render_info.width
686
772
  else
687
- dialog.width = dialog.contents.map { |l| calculate_width(l, true) }.max
773
+ dialog.width = contents.map { |l| calculate_width(l, true) }.max
688
774
  end
689
775
  height = dialog_render_info.height || DIALOG_DEFAULT_HEIGHT
690
- height = dialog.contents.size if dialog.contents.size < height
691
- if dialog.contents.size > height
776
+ height = contents.size if contents.size < height
777
+ if contents.size > height
692
778
  if dialog.pointer
693
779
  if dialog.pointer < 0
694
780
  dialog.scroll_top = 0
@@ -701,21 +787,21 @@ class Reline::LineEditor
701
787
  else
702
788
  dialog.scroll_top = 0
703
789
  end
704
- dialog.contents = dialog.contents[dialog.scroll_top, height]
790
+ contents = contents[dialog.scroll_top, height]
705
791
  end
706
792
  if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
707
793
  bar_max_height = height * 2
708
794
  moving_distance = (dialog_render_info.contents.size - height) * 2
709
795
  position_ratio = dialog.scroll_top.zero? ? 0.0 : ((dialog.scroll_top * 2).to_f / moving_distance)
710
- bar_height = (bar_max_height * ((dialog.contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
796
+ bar_height = (bar_max_height * ((contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
711
797
  bar_height = MINIMUM_SCROLLBAR_HEIGHT if bar_height < MINIMUM_SCROLLBAR_HEIGHT
712
- dialog.scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i
798
+ scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i
713
799
  else
714
- dialog.scrollbar_pos = nil
800
+ scrollbar_pos = nil
715
801
  end
716
802
  upper_space = @first_line_started_from - @started_from
717
803
  dialog.column = dialog_render_info.pos.x
718
- dialog.width += @block_elem_width if dialog.scrollbar_pos
804
+ dialog.width += @block_elem_width if scrollbar_pos
719
805
  diff = (dialog.column + dialog.width) - (@screen_size.last)
720
806
  if diff > 0
721
807
  dialog.column -= diff
@@ -725,21 +811,13 @@ class Reline::LineEditor
725
811
  elsif upper_space >= height
726
812
  dialog.vertical_offset = dialog_render_info.pos.y - height
727
813
  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
814
  dialog.vertical_offset = dialog_render_info.pos.y + 1
733
815
  end
734
- Reline::IOGate.hide_cursor
735
816
  if dialog.column < 0
736
817
  dialog.column = 0
737
818
  dialog.width = @screen_size.last
738
819
  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|
820
+ dialog.contents = contents.map.with_index do |item, i|
743
821
  if i == pointer
744
822
  fg_color = dialog_render_info.pointer_fg_color
745
823
  bg_color = dialog_render_info.pointer_bg_color
@@ -747,187 +825,42 @@ class Reline::LineEditor
747
825
  fg_color = dialog_render_info.fg_color
748
826
  bg_color = dialog_render_info.bg_color
749
827
  end
750
- str_width = dialog.width - (dialog.scrollbar_pos.nil? ? 0 : @block_elem_width)
828
+ str_width = dialog.width - (scrollbar_pos.nil? ? 0 : @block_elem_width)
751
829
  str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, str_width), str_width)
752
- @output.write "\e[#{bg_color}m\e[#{fg_color}m#{str}"
753
- if dialog.scrollbar_pos
754
- @output.write "\e[37m"
755
- if dialog.scrollbar_pos <= (i * 2) and (i * 2 + 1) < (dialog.scrollbar_pos + bar_height)
756
- @output.write @full_block
757
- elsif dialog.scrollbar_pos <= (i * 2) and (i * 2) < (dialog.scrollbar_pos + bar_height)
758
- @output.write @upper_half_block
759
- elsif dialog.scrollbar_pos <= (i * 2 + 1) and (i * 2) < (dialog.scrollbar_pos + bar_height)
760
- @output.write @lower_half_block
761
- else
762
- @output.write ' ' * @block_elem_width
763
- end
764
- end
765
- @output.write "\e[0m"
766
- Reline::IOGate.move_cursor_column(dialog.column)
767
- move_cursor_down(1) if i < (dialog.contents.size - 1)
768
- end
769
- Reline::IOGate.move_cursor_column(cursor_column)
770
- move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
771
- Reline::IOGate.show_cursor
772
- lines = whole_lines
773
- dialog.lines_backup = {
774
- unmodified_lines: lines,
775
- lines: modify_lines(lines),
776
- line_index: @line_index,
777
- first_line_started_from: @first_line_started_from,
778
- started_from: @started_from,
779
- byte_pointer: @byte_pointer
780
- }
781
- end
782
-
783
- private def reset_dialog(dialog, old_dialog)
784
- return if dialog.lines_backup.nil? or old_dialog.contents.nil?
785
- prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:unmodified_lines])
786
- visual_lines = []
787
- visual_start = nil
788
- dialog.lines_backup[:lines].each_with_index { |l, i|
789
- pr = prompt_list ? prompt_list[i] : prompt
790
- vl, _ = split_by_width(pr + l, @screen_size.last)
791
- vl.compact!
792
- if i == dialog.lines_backup[:line_index]
793
- visual_start = visual_lines.size + dialog.lines_backup[:started_from]
794
- end
795
- visual_lines.concat(vl)
796
- }
797
- old_y = dialog.lines_backup[:first_line_started_from] + dialog.lines_backup[:started_from]
798
- y = @first_line_started_from + @started_from
799
- y_diff = y - old_y
800
- if (old_y + old_dialog.vertical_offset) < (y + dialog.vertical_offset)
801
- # rerender top
802
- move_cursor_down(old_dialog.vertical_offset - y_diff)
803
- start = visual_start + old_dialog.vertical_offset
804
- line_num = dialog.vertical_offset - old_dialog.vertical_offset
805
- line_num.times do |i|
806
- Reline::IOGate.move_cursor_column(old_dialog.column)
807
- if visual_lines[start + i].nil?
808
- s = ' ' * old_dialog.width
809
- else
810
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
811
- s = padding_space_with_escape_sequences(s, old_dialog.width)
812
- end
813
- @output.write "\e[0m#{s}\e[0m"
814
- move_cursor_down(1) if i < (line_num - 1)
815
- end
816
- move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
817
- end
818
- if (old_y + old_dialog.vertical_offset + old_dialog.contents.size) > (y + dialog.vertical_offset + dialog.contents.size)
819
- # rerender bottom
820
- move_cursor_down(dialog.vertical_offset + dialog.contents.size - y_diff)
821
- start = visual_start + dialog.vertical_offset + dialog.contents.size
822
- line_num = (old_dialog.vertical_offset + old_dialog.contents.size) - (dialog.vertical_offset + dialog.contents.size)
823
- line_num.times do |i|
824
- Reline::IOGate.move_cursor_column(old_dialog.column)
825
- if visual_lines[start + i].nil?
826
- s = ' ' * old_dialog.width
827
- else
828
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
829
- s = padding_space_with_escape_sequences(s, old_dialog.width)
830
- end
831
- @output.write "\e[0m#{s}\e[0m"
832
- move_cursor_down(1) if i < (line_num - 1)
833
- end
834
- move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
835
- end
836
- if old_dialog.column < dialog.column
837
- # rerender left
838
- move_cursor_down(old_dialog.vertical_offset - y_diff)
839
- width = dialog.column - old_dialog.column
840
- start = visual_start + old_dialog.vertical_offset
841
- line_num = old_dialog.contents.size
842
- line_num.times do |i|
843
- Reline::IOGate.move_cursor_column(old_dialog.column)
844
- if visual_lines[start + i].nil?
845
- s = ' ' * 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
846
839
  else
847
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, width)
848
- s = padding_space_with_escape_sequences(s, dialog.width)
840
+ colored_content + color_seq + ' ' * @block_elem_width
849
841
  end
850
- @output.write "\e[0m#{s}\e[0m"
851
- move_cursor_down(1) if i < (line_num - 1)
852
- end
853
- move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
854
- end
855
- if (old_dialog.column + old_dialog.width) > (dialog.column + dialog.width)
856
- # rerender right
857
- move_cursor_down(old_dialog.vertical_offset + y_diff)
858
- width = (old_dialog.column + old_dialog.width) - (dialog.column + dialog.width)
859
- start = visual_start + old_dialog.vertical_offset
860
- line_num = old_dialog.contents.size
861
- line_num.times do |i|
862
- Reline::IOGate.move_cursor_column(old_dialog.column + dialog.width)
863
- if visual_lines[start + i].nil?
864
- s = ' ' * width
865
- else
866
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column + dialog.width, width)
867
- rerender_width = old_dialog.width - dialog.width
868
- s = padding_space_with_escape_sequences(s, rerender_width)
869
- end
870
- Reline::IOGate.move_cursor_column(dialog.column + dialog.width)
871
- @output.write "\e[0m#{s}\e[0m"
872
- move_cursor_down(1) if i < (line_num - 1)
842
+ else
843
+ colored_content
873
844
  end
874
- move_cursor_up(old_dialog.vertical_offset + line_num - 1 + y_diff)
875
845
  end
876
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
877
846
  end
878
847
 
879
- private def clear_dialog
880
- @dialogs.each do |dialog|
881
- 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]
882
853
  end
854
+ render_dialog_changes(changes, cursor_column)
883
855
  end
884
856
 
885
- private def clear_dialog_with_content
857
+ private def clear_dialog_with_trap_key(cursor_column)
858
+ clear_dialog(cursor_column)
886
859
  @dialogs.each do |dialog|
887
- clear_each_dialog(dialog)
888
- dialog.contents = nil
889
860
  dialog.trap_key = nil
890
861
  end
891
862
  end
892
863
 
893
- private def clear_each_dialog(dialog)
894
- dialog.trap_key = nil
895
- return unless dialog.contents
896
- prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:unmodified_lines])
897
- visual_lines = []
898
- visual_lines_under_dialog = []
899
- visual_start = nil
900
- dialog.lines_backup[:lines].each_with_index { |l, i|
901
- pr = prompt_list ? prompt_list[i] : prompt
902
- vl, _ = split_by_width(pr + l, @screen_size.last)
903
- vl.compact!
904
- if i == dialog.lines_backup[:line_index]
905
- visual_start = visual_lines.size + dialog.lines_backup[:started_from] + dialog.vertical_offset
906
- end
907
- visual_lines.concat(vl)
908
- }
909
- visual_lines_under_dialog = visual_lines[visual_start, dialog.contents.size]
910
- visual_lines_under_dialog = [] if visual_lines_under_dialog.nil?
911
- Reline::IOGate.hide_cursor
912
- move_cursor_down(dialog.vertical_offset)
913
- dialog_vertical_size = dialog.contents.size
914
- dialog_vertical_size.times do |i|
915
- if i < visual_lines_under_dialog.size
916
- Reline::IOGate.move_cursor_column(dialog.column)
917
- str = Reline::Unicode.take_range(visual_lines_under_dialog[i], dialog.column, dialog.width)
918
- str = padding_space_with_escape_sequences(str, dialog.width)
919
- @output.write "\e[0m#{str}\e[0m"
920
- else
921
- Reline::IOGate.move_cursor_column(dialog.column)
922
- @output.write "\e[0m#{' ' * dialog.width}\e[0m"
923
- end
924
- move_cursor_down(1) if i < (dialog_vertical_size - 1)
925
- end
926
- move_cursor_up(dialog_vertical_size - 1 + dialog.vertical_offset)
927
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
928
- Reline::IOGate.show_cursor
929
- end
930
-
931
864
  private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
932
865
  if @screen_height < highest_in_all
933
866
  old_scroll_partial_screen = @scroll_partial_screen
@@ -1235,8 +1168,8 @@ class Reline::LineEditor
1235
1168
  height
1236
1169
  end
1237
1170
 
1238
- private def modify_lines(before)
1239
- 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?)
1240
1173
 
1241
1174
  if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
1242
1175
  after.lines("\n").map { |l| l.chomp('') }
@@ -38,13 +38,8 @@ class Reline::Unicode
38
38
  NON_PRINTING_START = "\1"
39
39
  NON_PRINTING_END = "\2"
40
40
  CSI_REGEXP = /\e\[[\d;]*[ABCDEFGHJKSTfminsuhl]/
41
- OSC_REGEXP = /\e\]\d+(?:;[^;]+)*\a/
41
+ OSC_REGEXP = /\e\]\d+(?:;[^;\a\e]+)*(?:\a|\e\\)/
42
42
  WIDTH_SCANNER = /\G(?:(#{NON_PRINTING_START})|(#{NON_PRINTING_END})|(#{CSI_REGEXP})|(#{OSC_REGEXP})|(\X))/o
43
- NON_PRINTING_START_INDEX = 0
44
- NON_PRINTING_END_INDEX = 1
45
- CSI_REGEXP_INDEX = 2
46
- OSC_REGEXP_INDEX = 3
47
- GRAPHEME_CLUSTER_INDEX = 4
48
43
 
49
44
  def self.get_mbchar_byte_size_by_first_char(c)
50
45
  # Checks UTF-8 character byte size
@@ -132,15 +127,14 @@ class Reline::Unicode
132
127
  width = 0
133
128
  rest = str.encode(Encoding::UTF_8)
134
129
  in_zero_width = false
135
- rest.scan(WIDTH_SCANNER) do |gc|
130
+ rest.scan(WIDTH_SCANNER) do |non_printing_start, non_printing_end, csi, osc, gc|
136
131
  case
137
- when gc[NON_PRINTING_START_INDEX]
132
+ when non_printing_start
138
133
  in_zero_width = true
139
- when gc[NON_PRINTING_END_INDEX]
134
+ when non_printing_end
140
135
  in_zero_width = false
141
- when gc[CSI_REGEXP_INDEX], gc[OSC_REGEXP_INDEX]
142
- when gc[GRAPHEME_CLUSTER_INDEX]
143
- gc = gc[GRAPHEME_CLUSTER_INDEX]
136
+ when csi, osc
137
+ when gc
144
138
  unless in_zero_width
145
139
  width += get_mbchar_width(gc)
146
140
  end
@@ -161,22 +155,21 @@ class Reline::Unicode
161
155
  rest = str.encode(Encoding::UTF_8)
162
156
  in_zero_width = false
163
157
  seq = String.new(encoding: encoding)
164
- rest.scan(WIDTH_SCANNER) do |gc|
158
+ rest.scan(WIDTH_SCANNER) do |non_printing_start, non_printing_end, csi, osc, gc|
165
159
  case
166
- when gc[NON_PRINTING_START_INDEX]
160
+ when non_printing_start
167
161
  in_zero_width = true
168
162
  lines.last << NON_PRINTING_START
169
- when gc[NON_PRINTING_END_INDEX]
163
+ when non_printing_end
170
164
  in_zero_width = false
171
165
  lines.last << NON_PRINTING_END
172
- when gc[CSI_REGEXP_INDEX]
173
- lines.last << gc[CSI_REGEXP_INDEX]
174
- seq << gc[CSI_REGEXP_INDEX]
175
- when gc[OSC_REGEXP_INDEX]
176
- lines.last << gc[OSC_REGEXP_INDEX]
177
- seq << gc[OSC_REGEXP_INDEX]
178
- when gc[GRAPHEME_CLUSTER_INDEX]
179
- gc = gc[GRAPHEME_CLUSTER_INDEX]
166
+ when csi
167
+ lines.last << csi
168
+ seq << csi
169
+ when osc
170
+ lines.last << osc
171
+ seq << osc
172
+ when gc
180
173
  unless in_zero_width
181
174
  mbchar_width = get_mbchar_width(gc)
182
175
  if (width += mbchar_width) > max_width
@@ -199,23 +192,22 @@ class Reline::Unicode
199
192
  end
200
193
 
201
194
  # Take a chunk of a String cut by width with escape sequences.
202
- def self.take_range(str, start_col, max_width, encoding = str.encoding)
203
- chunk = String.new(encoding: encoding)
195
+ def self.take_range(str, start_col, max_width)
196
+ chunk = String.new(encoding: str.encoding)
204
197
  total_width = 0
205
198
  rest = str.encode(Encoding::UTF_8)
206
199
  in_zero_width = false
207
- rest.scan(WIDTH_SCANNER) do |gc|
200
+ rest.scan(WIDTH_SCANNER) do |non_printing_start, non_printing_end, csi, osc, gc|
208
201
  case
209
- when gc[NON_PRINTING_START_INDEX]
202
+ when non_printing_start
210
203
  in_zero_width = true
211
- when gc[NON_PRINTING_END_INDEX]
204
+ when non_printing_end
212
205
  in_zero_width = false
213
- when gc[CSI_REGEXP_INDEX]
214
- chunk << gc[CSI_REGEXP_INDEX]
215
- when gc[OSC_REGEXP_INDEX]
216
- chunk << gc[OSC_REGEXP_INDEX]
217
- when gc[GRAPHEME_CLUSTER_INDEX]
218
- gc = gc[GRAPHEME_CLUSTER_INDEX]
206
+ when csi
207
+ chunk << csi
208
+ when osc
209
+ chunk << osc
210
+ when gc
219
211
  if in_zero_width
220
212
  chunk << gc
221
213
  else
@@ -1,3 +1,3 @@
1
1
  module Reline
2
- VERSION = '0.3.3'
2
+ VERSION = '0.3.4'
3
3
  end
data/lib/reline.rb CHANGED
@@ -166,9 +166,13 @@ module Reline
166
166
 
167
167
  DialogProc = Struct.new(:dialog_proc, :context)
168
168
  def add_dialog_proc(name_sym, p, context = nil)
169
- raise ArgumentError unless p.respond_to?(:call) or p.nil?
170
169
  raise ArgumentError unless name_sym.instance_of?(Symbol)
171
- @dialog_proc_list[name_sym] = DialogProc.new(p, context)
170
+ if p.nil?
171
+ @dialog_proc_list.delete(name_sym)
172
+ else
173
+ raise ArgumentError unless p.respond_to?(:call)
174
+ @dialog_proc_list[name_sym] = DialogProc.new(p, context)
175
+ end
172
176
  end
173
177
 
174
178
  def dialog_proc(name_sym)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - aycabta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-03-27 00:00:00.000000000 Z
11
+ date: 2023-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: io-console
@@ -72,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
72
72
  - !ruby/object:Gem::Version
73
73
  version: '0'
74
74
  requirements: []
75
- rubygems_version: 3.4.8
75
+ rubygems_version: 3.4.13
76
76
  signing_key:
77
77
  specification_version: 4
78
78
  summary: Alternative GNU Readline or Editline implementation by pure Ruby.