reline 0.3.3 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -1
- data/lib/reline/line_editor.rb +155 -222
- data/lib/reline/unicode.rb +26 -34
- data/lib/reline/version.rb +1 -1
- data/lib/reline.rb +6 -2
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3e5c7a3a92a99dc4e59ae8299c90086a1d5508a7aa7f23579ac9e69a516da22a
|
4
|
+
data.tar.gz: 4779c4eba572d69bda056dafdc974f898678a9a0d40636ffc54836bbcedce2e8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ec283bebd9d93cc46c0587da2d051f29a2f119c785eff4a0d554b019a6f4cc2c9503670e0f073c11ce3399b72ecd2b3cdd4d92315251022b4ef54378bb9eea35
|
7
|
+
data.tar.gz: ffab61dee9ca267ff0015280a0fb0cbd5c76b271867dd86fc1861379af4821929917a467017b1a9b6cd17c92e3bbc74905c7b43865406b5053890bdb42b00007
|
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
[](https://badge.fury.io/rb/reline)
|
2
|
+
[](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
|
|
data/lib/reline/line_editor.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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(
|
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, :
|
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.
|
646
|
-
|
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
|
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
|
-
|
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 =
|
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 =
|
691
|
-
if
|
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
|
-
|
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 * ((
|
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
|
-
|
798
|
+
scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i
|
713
799
|
else
|
714
|
-
|
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
|
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
|
-
|
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 - (
|
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
|
-
|
753
|
-
if
|
754
|
-
|
755
|
-
if
|
756
|
-
|
757
|
-
elsif
|
758
|
-
|
759
|
-
elsif
|
760
|
-
|
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
|
-
|
848
|
-
s = padding_space_with_escape_sequences(s, dialog.width)
|
840
|
+
colored_content + color_seq + ' ' * @block_elem_width
|
849
841
|
end
|
850
|
-
|
851
|
-
|
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.
|
881
|
-
|
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
|
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('') }
|
data/lib/reline/unicode.rb
CHANGED
@@ -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+(?:;[
|
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
|
132
|
+
when non_printing_start
|
138
133
|
in_zero_width = true
|
139
|
-
when
|
134
|
+
when non_printing_end
|
140
135
|
in_zero_width = false
|
141
|
-
when
|
142
|
-
when gc
|
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
|
160
|
+
when non_printing_start
|
167
161
|
in_zero_width = true
|
168
162
|
lines.last << NON_PRINTING_START
|
169
|
-
when
|
163
|
+
when non_printing_end
|
170
164
|
in_zero_width = false
|
171
165
|
lines.last << NON_PRINTING_END
|
172
|
-
when
|
173
|
-
lines.last <<
|
174
|
-
seq <<
|
175
|
-
when
|
176
|
-
lines.last <<
|
177
|
-
seq <<
|
178
|
-
when gc
|
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
|
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
|
202
|
+
when non_printing_start
|
210
203
|
in_zero_width = true
|
211
|
-
when
|
204
|
+
when non_printing_end
|
212
205
|
in_zero_width = false
|
213
|
-
when
|
214
|
-
chunk <<
|
215
|
-
when
|
216
|
-
chunk <<
|
217
|
-
when gc
|
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
|
data/lib/reline/version.rb
CHANGED
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
|
-
|
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.
|
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-
|
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.
|
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.
|