reline 0.3.3 → 0.3.5

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: 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.