reline 0.3.3 → 0.3.5

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: 3654e31f9c5d1aa879ae40dfb28b8de68f2e443fac87856f2387a22d61a5b4e7
4
+ data.tar.gz: 0fdccab5b7cfd91274e3e4544dcca69a442176cc85e1eba382da3ffd02f9ac30
5
5
  SHA512:
6
- metadata.gz: cb456e06f2cef817fe3ae8fa27e9666587f2b3175cdbc97714ab3638d5b11bf606595d772c4e100fc21d9f50ed6e55fb6b833c28bad6b4ae3c8838d2418e75f5
7
- data.tar.gz: 6f3eb2f3feee78d7b4754c069a7d6c0d5d29af100bcb493bab2696c9b610c86de4fb3b201d624b6d00f218f1257a526d0c29bf95c79a1ed3daf117f0c95a2768
6
+ metadata.gz: 9277033707e4a1531e8590ae6a76019c21c60da22f69ea333dcc5c3d3610499303ed7892308b6ae3b495237738344baac7a21c547a19ff77f53fecae29be9a14
7
+ data.tar.gz: 5f3f225d7b1ee1f0ab067d8a897366a3d3b922dcedb74ae3ec980e387ad3b520d8ab808f70d89e109d06f7e5017ab6406d30e25ed2b6678b3fc8d6c4d5cf6930
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
@@ -571,6 +562,16 @@ class Reline::LineEditor
571
562
  @line_editor.instance_variable_get(:@screen_size).last
572
563
  end
573
564
 
565
+ def screen_height
566
+ @line_editor.instance_variable_get(:@screen_size).first
567
+ end
568
+
569
+ def preferred_dialog_height
570
+ rest_height = @line_editor.instance_variable_get(:@rest_height)
571
+ scroll_partial_screen = @line_editor.instance_variable_get(:@scroll_partial_screen) || 0
572
+ [cursor_pos.y - scroll_partial_screen, rest_height, (screen_height + 6) / 5].max
573
+ end
574
+
574
575
  def completion_journey_data
575
576
  @line_editor.instance_variable_get(:@completion_journey_data)
576
577
  end
@@ -586,7 +587,7 @@ class Reline::LineEditor
586
587
 
587
588
  class Dialog
588
589
  attr_reader :name, :contents, :width
589
- attr_accessor :scroll_top, :scrollbar_pos, :pointer, :column, :vertical_offset, :lines_backup, :trap_key
590
+ attr_accessor :scroll_top, :pointer, :column, :vertical_offset, :trap_key
590
591
 
591
592
  def initialize(name, config, proc_scope)
592
593
  @name = name
@@ -642,9 +643,12 @@ class Reline::LineEditor
642
643
 
643
644
  DIALOG_DEFAULT_HEIGHT = 20
644
645
  private def render_dialog(cursor_column)
645
- @dialogs.each do |dialog|
646
- render_each_dialog(dialog, cursor_column)
646
+ changes = @dialogs.map do |dialog|
647
+ old_dialog = dialog.dup
648
+ update_each_dialog(dialog, cursor_column)
649
+ [old_dialog, dialog]
647
650
  end
651
+ render_dialog_changes(changes, cursor_column)
648
652
  end
649
653
 
650
654
  private def padding_space_with_escape_sequences(str, width)
@@ -654,9 +658,112 @@ class Reline::LineEditor
654
658
  str + (' ' * padding_width)
655
659
  end
656
660
 
657
- private def render_each_dialog(dialog, cursor_column)
661
+ private def range_subtract(base_ranges, subtract_ranges)
662
+ indices = base_ranges.flat_map(&:to_a).uniq.sort - subtract_ranges.flat_map(&:to_a)
663
+ chunks = indices.chunk_while { |a, b| a + 1 == b }
664
+ chunks.map { |a| a.first...a.last + 1 }
665
+ end
666
+
667
+ private def dialog_range(dialog, dialog_y)
668
+ x_range = dialog.column...dialog.column + dialog.width
669
+ y_range = dialog_y + dialog.vertical_offset...dialog_y + dialog.vertical_offset + dialog.contents.size
670
+ [x_range, y_range]
671
+ end
672
+
673
+ private def render_dialog_changes(changes, cursor_column)
674
+ # Collect x-coordinate range and content of previous and current dialogs for each line
675
+ old_dialog_ranges = {}
676
+ new_dialog_ranges = {}
677
+ new_dialog_contents = {}
678
+ changes.each do |old_dialog, new_dialog|
679
+ if old_dialog.contents
680
+ x_range, y_range = dialog_range(old_dialog, @previous_rendered_dialog_y)
681
+ y_range.each do |y|
682
+ (old_dialog_ranges[y] ||= []) << x_range
683
+ end
684
+ end
685
+ if new_dialog.contents
686
+ x_range, y_range = dialog_range(new_dialog, @first_line_started_from + @started_from)
687
+ y_range.each do |y|
688
+ (new_dialog_ranges[y] ||= []) << x_range
689
+ (new_dialog_contents[y] ||= []) << [x_range, new_dialog.contents[y - y_range.begin]]
690
+ end
691
+ end
692
+ end
693
+ return if old_dialog_ranges.empty? && new_dialog_ranges.empty?
694
+
695
+ # Calculate x-coordinate ranges to restore text that was hidden behind dialogs for each line
696
+ ranges_to_restore = {}
697
+ subtract_cache = {}
698
+ old_dialog_ranges.each do |y, old_x_ranges|
699
+ new_x_ranges = new_dialog_ranges[y] || []
700
+ ranges = subtract_cache[[old_x_ranges, new_x_ranges]] ||= range_subtract(old_x_ranges, new_x_ranges)
701
+ ranges_to_restore[y] = ranges if ranges.any?
702
+ end
703
+
704
+ # Create visual_lines for restoring text hidden behind dialogs
705
+ if ranges_to_restore.any?
706
+ lines = whole_lines
707
+ prompt, _prompt_width, prompt_list = check_multiline_prompt(lines, force_recalc: true)
708
+ modified_lines = modify_lines(lines, force_recalc: true)
709
+ visual_lines = []
710
+ modified_lines.each_with_index { |l, i|
711
+ pr = prompt_list ? prompt_list[i] : prompt
712
+ vl, = split_by_width(pr + l, @screen_size.last)
713
+ vl.compact!
714
+ visual_lines.concat(vl)
715
+ }
716
+ end
717
+
718
+ # Clear and rerender all dialogs line by line
719
+ Reline::IOGate.hide_cursor
720
+ ymin, ymax = (ranges_to_restore.keys + new_dialog_ranges.keys).minmax
721
+ scroll_partial_screen = @scroll_partial_screen || 0
722
+ screen_y_range = scroll_partial_screen..(scroll_partial_screen + @screen_height - 1)
723
+ ymin = ymin.clamp(screen_y_range.begin, screen_y_range.end)
724
+ ymax = ymax.clamp(screen_y_range.begin, screen_y_range.end)
725
+ dialog_y = @first_line_started_from + @started_from
726
+ cursor_y = dialog_y
727
+ if @highest_in_all <= ymax
728
+ scroll_down(ymax - cursor_y)
729
+ move_cursor_up(ymax - cursor_y)
730
+ end
731
+ (ymin..ymax).each do |y|
732
+ move_cursor_down(y - cursor_y)
733
+ cursor_y = y
734
+ new_x_ranges = new_dialog_ranges[y]
735
+ restore_ranges = ranges_to_restore[y]
736
+ # Restore text that was hidden behind dialogs
737
+ if restore_ranges
738
+ line = visual_lines[y] || ''
739
+ restore_ranges.each do |range|
740
+ col = range.begin
741
+ width = range.end - range.begin
742
+ s = padding_space_with_escape_sequences(Reline::Unicode.take_range(line, col, width), width)
743
+ Reline::IOGate.move_cursor_column(col)
744
+ @output.write "\e[0m#{s}\e[0m"
745
+ end
746
+ max_column = [calculate_width(line, true), new_x_ranges&.map(&:end)&.max || 0].max
747
+ if max_column < restore_ranges.map(&:end).max
748
+ Reline::IOGate.move_cursor_column(max_column)
749
+ Reline::IOGate.erase_after_cursor
750
+ end
751
+ end
752
+ # Render dialog contents
753
+ new_dialog_contents[y]&.each do |x_range, content|
754
+ Reline::IOGate.move_cursor_column(x_range.begin)
755
+ @output.write "\e[0m#{content}\e[0m"
756
+ end
757
+ end
758
+ move_cursor_up(cursor_y - dialog_y)
759
+ Reline::IOGate.move_cursor_column(cursor_column)
760
+ Reline::IOGate.show_cursor
761
+
762
+ @previous_rendered_dialog_y = dialog_y
763
+ end
764
+
765
+ private def update_each_dialog(dialog, cursor_column)
658
766
  if @in_pasting
659
- clear_each_dialog(dialog)
660
767
  dialog.contents = nil
661
768
  dialog.trap_key = nil
662
769
  return
@@ -664,31 +771,20 @@ class Reline::LineEditor
664
771
  dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
665
772
  dialog_render_info = dialog.call(@last_key)
666
773
  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
774
  dialog.contents = nil
678
775
  dialog.trap_key = nil
679
776
  return
680
777
  end
681
- old_dialog = dialog.clone
682
- dialog.contents = dialog_render_info.contents
778
+ contents = dialog_render_info.contents
683
779
  pointer = dialog.pointer
684
780
  if dialog_render_info.width
685
781
  dialog.width = dialog_render_info.width
686
782
  else
687
- dialog.width = dialog.contents.map { |l| calculate_width(l, true) }.max
783
+ dialog.width = contents.map { |l| calculate_width(l, true) }.max
688
784
  end
689
785
  height = dialog_render_info.height || DIALOG_DEFAULT_HEIGHT
690
- height = dialog.contents.size if dialog.contents.size < height
691
- if dialog.contents.size > height
786
+ height = contents.size if contents.size < height
787
+ if contents.size > height
692
788
  if dialog.pointer
693
789
  if dialog.pointer < 0
694
790
  dialog.scroll_top = 0
@@ -701,21 +797,21 @@ class Reline::LineEditor
701
797
  else
702
798
  dialog.scroll_top = 0
703
799
  end
704
- dialog.contents = dialog.contents[dialog.scroll_top, height]
800
+ contents = contents[dialog.scroll_top, height]
705
801
  end
706
802
  if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
707
803
  bar_max_height = height * 2
708
804
  moving_distance = (dialog_render_info.contents.size - height) * 2
709
805
  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
806
+ bar_height = (bar_max_height * ((contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
711
807
  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
808
+ scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i
713
809
  else
714
- dialog.scrollbar_pos = nil
810
+ scrollbar_pos = nil
715
811
  end
716
812
  upper_space = @first_line_started_from - @started_from
717
813
  dialog.column = dialog_render_info.pos.x
718
- dialog.width += @block_elem_width if dialog.scrollbar_pos
814
+ dialog.width += @block_elem_width if scrollbar_pos
719
815
  diff = (dialog.column + dialog.width) - (@screen_size.last)
720
816
  if diff > 0
721
817
  dialog.column -= diff
@@ -725,21 +821,13 @@ class Reline::LineEditor
725
821
  elsif upper_space >= height
726
822
  dialog.vertical_offset = dialog_render_info.pos.y - height
727
823
  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
824
  dialog.vertical_offset = dialog_render_info.pos.y + 1
733
825
  end
734
- Reline::IOGate.hide_cursor
735
826
  if dialog.column < 0
736
827
  dialog.column = 0
737
828
  dialog.width = @screen_size.last
738
829
  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|
830
+ dialog.contents = contents.map.with_index do |item, i|
743
831
  if i == pointer
744
832
  fg_color = dialog_render_info.pointer_fg_color
745
833
  bg_color = dialog_render_info.pointer_bg_color
@@ -747,187 +835,42 @@ class Reline::LineEditor
747
835
  fg_color = dialog_render_info.fg_color
748
836
  bg_color = dialog_render_info.bg_color
749
837
  end
750
- str_width = dialog.width - (dialog.scrollbar_pos.nil? ? 0 : @block_elem_width)
838
+ str_width = dialog.width - (scrollbar_pos.nil? ? 0 : @block_elem_width)
751
839
  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
846
- 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)
849
- 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
840
+ colored_content = "\e[#{bg_color}m\e[#{fg_color}m#{str}"
841
+ if scrollbar_pos
842
+ color_seq = "\e[37m"
843
+ if scrollbar_pos <= (i * 2) and (i * 2 + 1) < (scrollbar_pos + bar_height)
844
+ colored_content + color_seq + @full_block
845
+ elsif scrollbar_pos <= (i * 2) and (i * 2) < (scrollbar_pos + bar_height)
846
+ colored_content + color_seq + @upper_half_block
847
+ elsif scrollbar_pos <= (i * 2 + 1) and (i * 2) < (scrollbar_pos + bar_height)
848
+ colored_content + color_seq + @lower_half_block
865
849
  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)
850
+ colored_content + color_seq + ' ' * @block_elem_width
869
851
  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)
852
+ else
853
+ colored_content
873
854
  end
874
- move_cursor_up(old_dialog.vertical_offset + line_num - 1 + y_diff)
875
855
  end
876
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
877
856
  end
878
857
 
879
- private def clear_dialog
880
- @dialogs.each do |dialog|
881
- clear_each_dialog(dialog)
858
+ private def clear_dialog(cursor_column)
859
+ changes = @dialogs.map do |dialog|
860
+ old_dialog = dialog.dup
861
+ dialog.contents = nil
862
+ [old_dialog, dialog]
882
863
  end
864
+ render_dialog_changes(changes, cursor_column)
883
865
  end
884
866
 
885
- private def clear_dialog_with_content
867
+ private def clear_dialog_with_trap_key(cursor_column)
868
+ clear_dialog(cursor_column)
886
869
  @dialogs.each do |dialog|
887
- clear_each_dialog(dialog)
888
- dialog.contents = nil
889
870
  dialog.trap_key = nil
890
871
  end
891
872
  end
892
873
 
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
874
  private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
932
875
  if @screen_height < highest_in_all
933
876
  old_scroll_partial_screen = @scroll_partial_screen
@@ -1235,8 +1178,8 @@ class Reline::LineEditor
1235
1178
  height
1236
1179
  end
1237
1180
 
1238
- private def modify_lines(before)
1239
- return before if before.nil? || before.empty? || simplified_rendering?
1181
+ private def modify_lines(before, force_recalc: false)
1182
+ return before if !force_recalc && (before.nil? || before.empty? || simplified_rendering?)
1240
1183
 
1241
1184
  if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
1242
1185
  after.lines("\n").map { |l| l.chomp('') }
@@ -31,21 +31,7 @@ module Reline::Terminfo
31
31
  @curses_dl = false
32
32
  def self.curses_dl
33
33
  return @curses_dl unless @curses_dl == false
34
- if RUBY_VERSION >= '3.0.0'
35
- # Gem module isn't defined in test-all of the Ruby repository, and
36
- # Fiddle in Ruby 3.0.0 or later supports Fiddle::TYPE_VARIADIC.
37
- fiddle_supports_variadic = true
38
- elsif Fiddle.const_defined?(:VERSION,false) and Gem::Version.create(Fiddle::VERSION) >= Gem::Version.create('1.0.1')
39
- # Fiddle::TYPE_VARIADIC is supported from Fiddle 1.0.1.
40
- fiddle_supports_variadic = true
41
- else
42
- fiddle_supports_variadic = false
43
- end
44
- if fiddle_supports_variadic and not Fiddle.const_defined?(:TYPE_VARIADIC)
45
- # If the libffi version is not 3.0.5 or higher, there isn't TYPE_VARIADIC.
46
- fiddle_supports_variadic = false
47
- end
48
- if fiddle_supports_variadic
34
+ if Fiddle.const_defined?(:TYPE_VARIADIC)
49
35
  curses_dl_files.each do |curses_name|
50
36
  result = Fiddle::Handle.new(curses_name)
51
37
  rescue Fiddle::DLError
@@ -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.5'
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)
@@ -256,7 +260,7 @@ module Reline
256
260
  pos: cursor_pos_to_render,
257
261
  contents: result,
258
262
  scrollbar: true,
259
- height: 15,
263
+ height: [15, preferred_dialog_height].min,
260
264
  bg_color: 46,
261
265
  pointer_bg_color: 45,
262
266
  fg_color: 37,
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.5
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-06-03 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.