reline 0.3.3 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -1
- data/lib/reline/line_editor.rb +165 -222
- data/lib/reline/terminfo.rb +1 -15
- data/lib/reline/unicode.rb +26 -34
- data/lib/reline/version.rb +1 -1
- data/lib/reline.rb +7 -3
- 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: 3654e31f9c5d1aa879ae40dfb28b8de68f2e443fac87856f2387a22d61a5b4e7
|
4
|
+
data.tar.gz: 0fdccab5b7cfd91274e3e4544dcca69a442176cc85e1eba382da3ffd02f9ac30
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9277033707e4a1531e8590ae6a76019c21c60da22f69ea333dcc5c3d3610499303ed7892308b6ae3b495237738344baac7a21c547a19ff77f53fecae29be9a14
|
7
|
+
data.tar.gz: 5f3f225d7b1ee1f0ab067d8a897366a3d3b922dcedb74ae3ec980e387ad3b520d8ab808f70d89e109d06f7e5017ab6406d30e25ed2b6678b3fc8d6c4d5cf6930
|
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
|
@@ -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, :
|
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.
|
646
|
-
|
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
|
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
|
-
|
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 =
|
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 =
|
691
|
-
if
|
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
|
-
|
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 * ((
|
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
|
-
|
808
|
+
scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i
|
713
809
|
else
|
714
|
-
|
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
|
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
|
-
|
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 - (
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
871
|
-
|
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.
|
881
|
-
|
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
|
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('') }
|
data/lib/reline/terminfo.rb
CHANGED
@@ -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
|
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
|
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)
|
@@ -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.
|
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
|
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.
|
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.
|