reline 0.3.3 → 0.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +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
|
-
[![
|
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
|
|
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.
|