reline 0.3.3 → 0.3.4

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