reline 0.3.2 → 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/ansi.rb +9 -2
- data/lib/reline/config.rb +1 -1
- data/lib/reline/general_io.rb +4 -0
- data/lib/reline/line_editor.rb +196 -274
- data/lib/reline/unicode/east_asian_width.rb +88 -56
- data/lib/reline/unicode.rb +30 -33
- data/lib/reline/version.rb +1 -1
- data/lib/reline/windows.rb +4 -0
- data/lib/reline.rb +20 -13
- metadata +3 -3
data/lib/reline/line_editor.rb
CHANGED
@@ -52,6 +52,7 @@ class Reline::LineEditor
|
|
52
52
|
MenuInfo = Struct.new('MenuInfo', :target, :list)
|
53
53
|
|
54
54
|
PROMPT_LIST_CACHE_TIMEOUT = 0.5
|
55
|
+
MINIMUM_SCROLLBAR_HEIGHT = 1
|
55
56
|
|
56
57
|
def initialize(config, encoding)
|
57
58
|
@config = config
|
@@ -93,7 +94,7 @@ class Reline::LineEditor
|
|
93
94
|
mode_string
|
94
95
|
end
|
95
96
|
|
96
|
-
private def check_multiline_prompt(buffer)
|
97
|
+
private def check_multiline_prompt(buffer, force_recalc: false)
|
97
98
|
if @vi_arg
|
98
99
|
prompt = "(arg: #{@vi_arg}) "
|
99
100
|
@rerender_all = true
|
@@ -103,7 +104,7 @@ class Reline::LineEditor
|
|
103
104
|
else
|
104
105
|
prompt = @prompt
|
105
106
|
end
|
106
|
-
if simplified_rendering?
|
107
|
+
if simplified_rendering? && !force_recalc
|
107
108
|
mode_string = check_mode_string
|
108
109
|
prompt = mode_string + prompt if mode_string
|
109
110
|
return [prompt, calculate_width(prompt, true), [prompt] * buffer.size]
|
@@ -219,7 +220,7 @@ class Reline::LineEditor
|
|
219
220
|
|
220
221
|
def set_signal_handlers
|
221
222
|
@old_trap = Signal.trap('INT') {
|
222
|
-
clear_dialog
|
223
|
+
clear_dialog(0)
|
223
224
|
if @scroll_partial_screen
|
224
225
|
move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
|
225
226
|
else
|
@@ -238,21 +239,10 @@ class Reline::LineEditor
|
|
238
239
|
@old_trap.call if @old_trap.respond_to?(:call)
|
239
240
|
end
|
240
241
|
}
|
241
|
-
begin
|
242
|
-
@old_tstp_trap = Signal.trap('TSTP') {
|
243
|
-
Reline::IOGate.ungetc("\C-z".ord)
|
244
|
-
@old_tstp_trap.call if @old_tstp_trap.respond_to?(:call)
|
245
|
-
}
|
246
|
-
rescue ArgumentError
|
247
|
-
end
|
248
242
|
end
|
249
243
|
|
250
244
|
def finalize
|
251
245
|
Signal.trap('INT', @old_trap)
|
252
|
-
begin
|
253
|
-
Signal.trap('TSTP', @old_tstp_trap)
|
254
|
-
rescue ArgumentError
|
255
|
-
end
|
256
246
|
end
|
257
247
|
|
258
248
|
def eof?
|
@@ -293,6 +283,7 @@ class Reline::LineEditor
|
|
293
283
|
@in_pasting = false
|
294
284
|
@auto_indent_proc = nil
|
295
285
|
@dialogs = []
|
286
|
+
@previous_rendered_dialog_y = 0
|
296
287
|
@last_key = nil
|
297
288
|
@resized = false
|
298
289
|
reset_line
|
@@ -439,6 +430,7 @@ class Reline::LineEditor
|
|
439
430
|
@menu_info = nil
|
440
431
|
end
|
441
432
|
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
|
433
|
+
cursor_column = (prompt_width + @cursor) % @screen_size.last
|
442
434
|
if @cleared
|
443
435
|
clear_screen_buffer(prompt, prompt_list, prompt_width)
|
444
436
|
@cleared = false
|
@@ -449,34 +441,30 @@ class Reline::LineEditor
|
|
449
441
|
Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
|
450
442
|
Reline::IOGate.move_cursor_column(0)
|
451
443
|
@scroll_partial_screen = nil
|
452
|
-
|
453
|
-
|
454
|
-
new_lines = whole_lines(index: @previous_line_index, line: @line)
|
455
|
-
else
|
456
|
-
new_lines = whole_lines
|
457
|
-
end
|
444
|
+
new_lines = whole_lines
|
445
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
|
458
446
|
modify_lines(new_lines).each_with_index do |line, index|
|
459
|
-
@output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\n"
|
447
|
+
@output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\r\n"
|
460
448
|
Reline::IOGate.erase_after_cursor
|
461
449
|
end
|
462
450
|
@output.flush
|
463
|
-
clear_dialog
|
451
|
+
clear_dialog(cursor_column)
|
464
452
|
return
|
465
453
|
end
|
466
454
|
new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
|
467
455
|
rendered = false
|
468
456
|
if @add_newline_to_end_of_buffer
|
469
|
-
|
470
|
-
rerender_added_newline(prompt, prompt_width)
|
457
|
+
clear_dialog_with_trap_key(cursor_column)
|
458
|
+
rerender_added_newline(prompt, prompt_width, prompt_list)
|
471
459
|
@add_newline_to_end_of_buffer = false
|
472
460
|
else
|
473
461
|
if @just_cursor_moving and not @rerender_all
|
474
|
-
|
462
|
+
clear_dialog_with_trap_key(cursor_column)
|
475
463
|
rendered = just_move_cursor
|
476
464
|
@just_cursor_moving = false
|
477
465
|
return
|
478
466
|
elsif @previous_line_index or new_highest_in_this != @highest_in_this
|
479
|
-
|
467
|
+
clear_dialog_with_trap_key(cursor_column)
|
480
468
|
rerender_changed_current_line
|
481
469
|
@previous_line_index = nil
|
482
470
|
rendered = true
|
@@ -490,13 +478,9 @@ class Reline::LineEditor
|
|
490
478
|
if @is_multiline
|
491
479
|
if finished?
|
492
480
|
# Always rerender on finish because output_modifier_proc may return a different output.
|
493
|
-
|
494
|
-
new_lines = whole_lines(index: @previous_line_index, line: @line)
|
495
|
-
else
|
496
|
-
new_lines = whole_lines
|
497
|
-
end
|
481
|
+
new_lines = whole_lines
|
498
482
|
line = modify_lines(new_lines)[@line_index]
|
499
|
-
clear_dialog
|
483
|
+
clear_dialog(cursor_column)
|
500
484
|
prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
|
501
485
|
render_partial(prompt, prompt_width, line, @first_line_started_from)
|
502
486
|
move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
|
@@ -509,7 +493,7 @@ class Reline::LineEditor
|
|
509
493
|
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
|
510
494
|
render_partial(prompt, prompt_width, line, @first_line_started_from)
|
511
495
|
end
|
512
|
-
render_dialog(
|
496
|
+
render_dialog(cursor_column)
|
513
497
|
end
|
514
498
|
@buffer_of_lines[@line_index] = @line
|
515
499
|
@rest_height = 0 if @scroll_partial_screen
|
@@ -593,7 +577,7 @@ class Reline::LineEditor
|
|
593
577
|
|
594
578
|
class Dialog
|
595
579
|
attr_reader :name, :contents, :width
|
596
|
-
attr_accessor :scroll_top, :
|
580
|
+
attr_accessor :scroll_top, :pointer, :column, :vertical_offset, :trap_key
|
597
581
|
|
598
582
|
def initialize(name, config, proc_scope)
|
599
583
|
@name = name
|
@@ -649,9 +633,12 @@ class Reline::LineEditor
|
|
649
633
|
|
650
634
|
DIALOG_DEFAULT_HEIGHT = 20
|
651
635
|
private def render_dialog(cursor_column)
|
652
|
-
@dialogs.
|
653
|
-
|
636
|
+
changes = @dialogs.map do |dialog|
|
637
|
+
old_dialog = dialog.dup
|
638
|
+
update_each_dialog(dialog, cursor_column)
|
639
|
+
[old_dialog, dialog]
|
654
640
|
end
|
641
|
+
render_dialog_changes(changes, cursor_column)
|
655
642
|
end
|
656
643
|
|
657
644
|
private def padding_space_with_escape_sequences(str, width)
|
@@ -661,9 +648,112 @@ class Reline::LineEditor
|
|
661
648
|
str + (' ' * padding_width)
|
662
649
|
end
|
663
650
|
|
664
|
-
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)
|
665
756
|
if @in_pasting
|
666
|
-
clear_each_dialog(dialog)
|
667
757
|
dialog.contents = nil
|
668
758
|
dialog.trap_key = nil
|
669
759
|
return
|
@@ -671,29 +761,20 @@ class Reline::LineEditor
|
|
671
761
|
dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
|
672
762
|
dialog_render_info = dialog.call(@last_key)
|
673
763
|
if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty?
|
674
|
-
dialog.lines_backup = {
|
675
|
-
lines: modify_lines(whole_lines),
|
676
|
-
line_index: @line_index,
|
677
|
-
first_line_started_from: @first_line_started_from,
|
678
|
-
started_from: @started_from,
|
679
|
-
byte_pointer: @byte_pointer
|
680
|
-
}
|
681
|
-
clear_each_dialog(dialog)
|
682
764
|
dialog.contents = nil
|
683
765
|
dialog.trap_key = nil
|
684
766
|
return
|
685
767
|
end
|
686
|
-
|
687
|
-
dialog.contents = dialog_render_info.contents
|
768
|
+
contents = dialog_render_info.contents
|
688
769
|
pointer = dialog.pointer
|
689
770
|
if dialog_render_info.width
|
690
771
|
dialog.width = dialog_render_info.width
|
691
772
|
else
|
692
|
-
dialog.width =
|
773
|
+
dialog.width = contents.map { |l| calculate_width(l, true) }.max
|
693
774
|
end
|
694
775
|
height = dialog_render_info.height || DIALOG_DEFAULT_HEIGHT
|
695
|
-
height =
|
696
|
-
if
|
776
|
+
height = contents.size if contents.size < height
|
777
|
+
if contents.size > height
|
697
778
|
if dialog.pointer
|
698
779
|
if dialog.pointer < 0
|
699
780
|
dialog.scroll_top = 0
|
@@ -703,24 +784,24 @@ class Reline::LineEditor
|
|
703
784
|
dialog.scroll_top = dialog.pointer
|
704
785
|
end
|
705
786
|
pointer = dialog.pointer - dialog.scroll_top
|
787
|
+
else
|
788
|
+
dialog.scroll_top = 0
|
706
789
|
end
|
707
|
-
|
708
|
-
end
|
709
|
-
if dialog.contents and dialog.scroll_top >= dialog.contents.size
|
710
|
-
dialog.scroll_top = dialog.contents.size - height
|
790
|
+
contents = contents[dialog.scroll_top, height]
|
711
791
|
end
|
712
792
|
if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
|
713
793
|
bar_max_height = height * 2
|
714
794
|
moving_distance = (dialog_render_info.contents.size - height) * 2
|
715
795
|
position_ratio = dialog.scroll_top.zero? ? 0.0 : ((dialog.scroll_top * 2).to_f / moving_distance)
|
716
|
-
bar_height = (bar_max_height * ((
|
717
|
-
|
796
|
+
bar_height = (bar_max_height * ((contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
|
797
|
+
bar_height = MINIMUM_SCROLLBAR_HEIGHT if bar_height < MINIMUM_SCROLLBAR_HEIGHT
|
798
|
+
scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i
|
718
799
|
else
|
719
|
-
|
800
|
+
scrollbar_pos = nil
|
720
801
|
end
|
721
802
|
upper_space = @first_line_started_from - @started_from
|
722
803
|
dialog.column = dialog_render_info.pos.x
|
723
|
-
dialog.width += @block_elem_width if
|
804
|
+
dialog.width += @block_elem_width if scrollbar_pos
|
724
805
|
diff = (dialog.column + dialog.width) - (@screen_size.last)
|
725
806
|
if diff > 0
|
726
807
|
dialog.column -= diff
|
@@ -730,21 +811,13 @@ class Reline::LineEditor
|
|
730
811
|
elsif upper_space >= height
|
731
812
|
dialog.vertical_offset = dialog_render_info.pos.y - height
|
732
813
|
else
|
733
|
-
if (@rest_height - dialog_render_info.pos.y) < height
|
734
|
-
scroll_down(height + dialog_render_info.pos.y)
|
735
|
-
move_cursor_up(height + dialog_render_info.pos.y)
|
736
|
-
end
|
737
814
|
dialog.vertical_offset = dialog_render_info.pos.y + 1
|
738
815
|
end
|
739
|
-
Reline::IOGate.hide_cursor
|
740
816
|
if dialog.column < 0
|
741
817
|
dialog.column = 0
|
742
818
|
dialog.width = @screen_size.last
|
743
819
|
end
|
744
|
-
|
745
|
-
move_cursor_down(dialog.vertical_offset)
|
746
|
-
Reline::IOGate.move_cursor_column(dialog.column)
|
747
|
-
dialog.contents.each_with_index do |item, i|
|
820
|
+
dialog.contents = contents.map.with_index do |item, i|
|
748
821
|
if i == pointer
|
749
822
|
fg_color = dialog_render_info.pointer_fg_color
|
750
823
|
bg_color = dialog_render_info.pointer_bg_color
|
@@ -752,185 +825,42 @@ class Reline::LineEditor
|
|
752
825
|
fg_color = dialog_render_info.fg_color
|
753
826
|
bg_color = dialog_render_info.bg_color
|
754
827
|
end
|
755
|
-
str_width = dialog.width - (
|
828
|
+
str_width = dialog.width - (scrollbar_pos.nil? ? 0 : @block_elem_width)
|
756
829
|
str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, str_width), str_width)
|
757
|
-
|
758
|
-
if
|
759
|
-
|
760
|
-
if
|
761
|
-
|
762
|
-
elsif
|
763
|
-
|
764
|
-
elsif
|
765
|
-
|
766
|
-
else
|
767
|
-
@output.write ' ' * @block_elem_width
|
768
|
-
end
|
769
|
-
end
|
770
|
-
@output.write "\e[0m"
|
771
|
-
Reline::IOGate.move_cursor_column(dialog.column)
|
772
|
-
move_cursor_down(1) if i < (dialog.contents.size - 1)
|
773
|
-
end
|
774
|
-
Reline::IOGate.move_cursor_column(cursor_column)
|
775
|
-
move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
|
776
|
-
Reline::IOGate.show_cursor
|
777
|
-
dialog.lines_backup = {
|
778
|
-
lines: modify_lines(whole_lines),
|
779
|
-
line_index: @line_index,
|
780
|
-
first_line_started_from: @first_line_started_from,
|
781
|
-
started_from: @started_from,
|
782
|
-
byte_pointer: @byte_pointer
|
783
|
-
}
|
784
|
-
end
|
785
|
-
|
786
|
-
private def reset_dialog(dialog, old_dialog)
|
787
|
-
return if dialog.lines_backup.nil? or old_dialog.contents.nil?
|
788
|
-
prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
|
789
|
-
visual_lines = []
|
790
|
-
visual_start = nil
|
791
|
-
dialog.lines_backup[:lines].each_with_index { |l, i|
|
792
|
-
pr = prompt_list ? prompt_list[i] : prompt
|
793
|
-
vl, _ = split_by_width(pr + l, @screen_size.last)
|
794
|
-
vl.compact!
|
795
|
-
if i == dialog.lines_backup[:line_index]
|
796
|
-
visual_start = visual_lines.size + dialog.lines_backup[:started_from]
|
797
|
-
end
|
798
|
-
visual_lines.concat(vl)
|
799
|
-
}
|
800
|
-
old_y = dialog.lines_backup[:first_line_started_from] + dialog.lines_backup[:started_from]
|
801
|
-
y = @first_line_started_from + @started_from
|
802
|
-
y_diff = y - old_y
|
803
|
-
if (old_y + old_dialog.vertical_offset) < (y + dialog.vertical_offset)
|
804
|
-
# rerender top
|
805
|
-
move_cursor_down(old_dialog.vertical_offset - y_diff)
|
806
|
-
start = visual_start + old_dialog.vertical_offset
|
807
|
-
line_num = dialog.vertical_offset - old_dialog.vertical_offset
|
808
|
-
line_num.times do |i|
|
809
|
-
Reline::IOGate.move_cursor_column(old_dialog.column)
|
810
|
-
if visual_lines[start + i].nil?
|
811
|
-
s = ' ' * old_dialog.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
|
812
839
|
else
|
813
|
-
|
814
|
-
s = padding_space_with_escape_sequences(s, old_dialog.width)
|
840
|
+
colored_content + color_seq + ' ' * @block_elem_width
|
815
841
|
end
|
816
|
-
|
817
|
-
|
818
|
-
end
|
819
|
-
move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
|
820
|
-
end
|
821
|
-
if (old_y + old_dialog.vertical_offset + old_dialog.contents.size) > (y + dialog.vertical_offset + dialog.contents.size)
|
822
|
-
# rerender bottom
|
823
|
-
move_cursor_down(dialog.vertical_offset + dialog.contents.size - y_diff)
|
824
|
-
start = visual_start + dialog.vertical_offset + dialog.contents.size
|
825
|
-
line_num = (old_dialog.vertical_offset + old_dialog.contents.size) - (dialog.vertical_offset + dialog.contents.size)
|
826
|
-
line_num.times do |i|
|
827
|
-
Reline::IOGate.move_cursor_column(old_dialog.column)
|
828
|
-
if visual_lines[start + i].nil?
|
829
|
-
s = ' ' * old_dialog.width
|
830
|
-
else
|
831
|
-
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
|
832
|
-
s = padding_space_with_escape_sequences(s, old_dialog.width)
|
833
|
-
end
|
834
|
-
@output.write "\e[0m#{s}\e[0m"
|
835
|
-
move_cursor_down(1) if i < (line_num - 1)
|
836
|
-
end
|
837
|
-
move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
|
838
|
-
end
|
839
|
-
if old_dialog.column < dialog.column
|
840
|
-
# rerender left
|
841
|
-
move_cursor_down(old_dialog.vertical_offset - y_diff)
|
842
|
-
width = dialog.column - old_dialog.column
|
843
|
-
start = visual_start + old_dialog.vertical_offset
|
844
|
-
line_num = old_dialog.contents.size
|
845
|
-
line_num.times do |i|
|
846
|
-
Reline::IOGate.move_cursor_column(old_dialog.column)
|
847
|
-
if visual_lines[start + i].nil?
|
848
|
-
s = ' ' * width
|
849
|
-
else
|
850
|
-
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, width)
|
851
|
-
s = padding_space_with_escape_sequences(s, dialog.width)
|
852
|
-
end
|
853
|
-
@output.write "\e[0m#{s}\e[0m"
|
854
|
-
move_cursor_down(1) if i < (line_num - 1)
|
855
|
-
end
|
856
|
-
move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
|
857
|
-
end
|
858
|
-
if (old_dialog.column + old_dialog.width) > (dialog.column + dialog.width)
|
859
|
-
# rerender right
|
860
|
-
move_cursor_down(old_dialog.vertical_offset + y_diff)
|
861
|
-
width = (old_dialog.column + old_dialog.width) - (dialog.column + dialog.width)
|
862
|
-
start = visual_start + old_dialog.vertical_offset
|
863
|
-
line_num = old_dialog.contents.size
|
864
|
-
line_num.times do |i|
|
865
|
-
Reline::IOGate.move_cursor_column(old_dialog.column + dialog.width)
|
866
|
-
if visual_lines[start + i].nil?
|
867
|
-
s = ' ' * width
|
868
|
-
else
|
869
|
-
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column + dialog.width, width)
|
870
|
-
rerender_width = old_dialog.width - dialog.width
|
871
|
-
s = padding_space_with_escape_sequences(s, rerender_width)
|
872
|
-
end
|
873
|
-
Reline::IOGate.move_cursor_column(dialog.column + dialog.width)
|
874
|
-
@output.write "\e[0m#{s}\e[0m"
|
875
|
-
move_cursor_down(1) if i < (line_num - 1)
|
842
|
+
else
|
843
|
+
colored_content
|
876
844
|
end
|
877
|
-
move_cursor_up(old_dialog.vertical_offset + line_num - 1 + y_diff)
|
878
845
|
end
|
879
|
-
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
880
846
|
end
|
881
847
|
|
882
|
-
private def clear_dialog
|
883
|
-
@dialogs.
|
884
|
-
|
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]
|
885
853
|
end
|
854
|
+
render_dialog_changes(changes, cursor_column)
|
886
855
|
end
|
887
856
|
|
888
|
-
private def
|
857
|
+
private def clear_dialog_with_trap_key(cursor_column)
|
858
|
+
clear_dialog(cursor_column)
|
889
859
|
@dialogs.each do |dialog|
|
890
|
-
clear_each_dialog(dialog)
|
891
|
-
dialog.contents = nil
|
892
860
|
dialog.trap_key = nil
|
893
861
|
end
|
894
862
|
end
|
895
863
|
|
896
|
-
private def clear_each_dialog(dialog)
|
897
|
-
dialog.trap_key = nil
|
898
|
-
return unless dialog.contents
|
899
|
-
prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
|
900
|
-
visual_lines = []
|
901
|
-
visual_lines_under_dialog = []
|
902
|
-
visual_start = nil
|
903
|
-
dialog.lines_backup[:lines].each_with_index { |l, i|
|
904
|
-
pr = prompt_list ? prompt_list[i] : prompt
|
905
|
-
vl, _ = split_by_width(pr + l, @screen_size.last)
|
906
|
-
vl.compact!
|
907
|
-
if i == dialog.lines_backup[:line_index]
|
908
|
-
visual_start = visual_lines.size + dialog.lines_backup[:started_from] + dialog.vertical_offset
|
909
|
-
end
|
910
|
-
visual_lines.concat(vl)
|
911
|
-
}
|
912
|
-
visual_lines_under_dialog = visual_lines[visual_start, dialog.contents.size]
|
913
|
-
visual_lines_under_dialog = [] if visual_lines_under_dialog.nil?
|
914
|
-
Reline::IOGate.hide_cursor
|
915
|
-
move_cursor_down(dialog.vertical_offset)
|
916
|
-
dialog_vertical_size = dialog.contents.size
|
917
|
-
dialog_vertical_size.times do |i|
|
918
|
-
if i < visual_lines_under_dialog.size
|
919
|
-
Reline::IOGate.move_cursor_column(dialog.column)
|
920
|
-
str = Reline::Unicode.take_range(visual_lines_under_dialog[i], dialog.column, dialog.width)
|
921
|
-
str = padding_space_with_escape_sequences(str, dialog.width)
|
922
|
-
@output.write "\e[0m#{str}\e[0m"
|
923
|
-
else
|
924
|
-
Reline::IOGate.move_cursor_column(dialog.column)
|
925
|
-
@output.write "\e[0m#{' ' * dialog.width}\e[0m"
|
926
|
-
end
|
927
|
-
move_cursor_down(1) if i < (dialog_vertical_size - 1)
|
928
|
-
end
|
929
|
-
move_cursor_up(dialog_vertical_size - 1 + dialog.vertical_offset)
|
930
|
-
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
931
|
-
Reline::IOGate.show_cursor
|
932
|
-
end
|
933
|
-
|
934
864
|
private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
|
935
865
|
if @screen_height < highest_in_all
|
936
866
|
old_scroll_partial_screen = @scroll_partial_screen
|
@@ -964,11 +894,20 @@ class Reline::LineEditor
|
|
964
894
|
end
|
965
895
|
end
|
966
896
|
|
967
|
-
private def rerender_added_newline(prompt, prompt_width)
|
968
|
-
scroll_down(1)
|
897
|
+
private def rerender_added_newline(prompt, prompt_width, prompt_list)
|
969
898
|
@buffer_of_lines[@previous_line_index] = @line
|
970
899
|
@line = @buffer_of_lines[@line_index]
|
971
|
-
|
900
|
+
@previous_line_index = nil
|
901
|
+
if @in_pasting
|
902
|
+
scroll_down(1)
|
903
|
+
else
|
904
|
+
lines = whole_lines
|
905
|
+
prev_line_prompt = @prompt_proc ? prompt_list[@line_index - 1] : prompt
|
906
|
+
prev_line_prompt_width = @prompt_proc ? calculate_width(prev_line_prompt, true) : prompt_width
|
907
|
+
prev_line = modify_lines(lines)[@line_index - 1]
|
908
|
+
move_cursor_up(@started_from)
|
909
|
+
render_partial(prev_line_prompt, prev_line_prompt_width, prev_line, @first_line_started_from + @started_from, with_control: false)
|
910
|
+
scroll_down(1)
|
972
911
|
render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
|
973
912
|
end
|
974
913
|
@cursor = @cursor_max = calculate_width(@line)
|
@@ -977,7 +916,6 @@ class Reline::LineEditor
|
|
977
916
|
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
|
978
917
|
@first_line_started_from += @started_from + 1
|
979
918
|
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
980
|
-
@previous_line_index = nil
|
981
919
|
end
|
982
920
|
|
983
921
|
def just_move_cursor
|
@@ -990,22 +928,18 @@ class Reline::LineEditor
|
|
990
928
|
calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
|
991
929
|
end
|
992
930
|
first_line_diff = new_first_line_started_from - @first_line_started_from
|
993
|
-
|
994
|
-
new_started_from = calculate_height_by_width(prompt_width +
|
931
|
+
@cursor, @cursor_max, _, @byte_pointer = calculate_nearest_cursor(@buffer_of_lines[@line_index], @cursor, @started_from, @byte_pointer, false)
|
932
|
+
new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
995
933
|
calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from)
|
996
934
|
@previous_line_index = nil
|
935
|
+
@line = @buffer_of_lines[@line_index]
|
997
936
|
if @rerender_all
|
998
|
-
@line = @buffer_of_lines[@line_index]
|
999
937
|
rerender_all_lines
|
1000
938
|
@rerender_all = false
|
1001
939
|
true
|
1002
940
|
else
|
1003
|
-
@line = @buffer_of_lines[@line_index]
|
1004
941
|
@first_line_started_from = new_first_line_started_from
|
1005
942
|
@started_from = new_started_from
|
1006
|
-
@cursor = new_cursor
|
1007
|
-
@cursor_max = new_cursor_max
|
1008
|
-
@byte_pointer = new_byte_pointer
|
1009
943
|
move_cursor_down(first_line_diff + @started_from)
|
1010
944
|
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
1011
945
|
false
|
@@ -1013,11 +947,7 @@ class Reline::LineEditor
|
|
1013
947
|
end
|
1014
948
|
|
1015
949
|
private def rerender_changed_current_line
|
1016
|
-
|
1017
|
-
new_lines = whole_lines(index: @previous_line_index, line: @line)
|
1018
|
-
else
|
1019
|
-
new_lines = whole_lines
|
1020
|
-
end
|
950
|
+
new_lines = whole_lines
|
1021
951
|
prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
|
1022
952
|
all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
|
1023
953
|
diff = all_height - @highest_in_all
|
@@ -1238,8 +1168,8 @@ class Reline::LineEditor
|
|
1238
1168
|
height
|
1239
1169
|
end
|
1240
1170
|
|
1241
|
-
private def modify_lines(before)
|
1242
|
-
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?)
|
1243
1173
|
|
1244
1174
|
if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
|
1245
1175
|
after.lines("\n").map { |l| l.chomp('') }
|
@@ -1371,8 +1301,8 @@ class Reline::LineEditor
|
|
1371
1301
|
@completion_state = CompletionState::MENU
|
1372
1302
|
end
|
1373
1303
|
if not just_show_list and target < completed
|
1374
|
-
@line = preposing + completed + completion_append_character.to_s + postposing
|
1375
|
-
line_to_pointer = preposing + completed + completion_append_character.to_s
|
1304
|
+
@line = (preposing + completed + completion_append_character.to_s + postposing).split("\n")[@line_index] || String.new(encoding: @encoding)
|
1305
|
+
line_to_pointer = (preposing + completed + completion_append_character.to_s).split("\n").last || String.new(encoding: @encoding)
|
1376
1306
|
@cursor_max = calculate_width(@line)
|
1377
1307
|
@cursor = calculate_width(line_to_pointer)
|
1378
1308
|
@byte_pointer = line_to_pointer.bytesize
|
@@ -1698,7 +1628,7 @@ class Reline::LineEditor
|
|
1698
1628
|
return if not @check_new_auto_indent and @previous_line_index # move cursor up or down
|
1699
1629
|
if @check_new_auto_indent and @previous_line_index and @previous_line_index > 0 and @line_index > @previous_line_index
|
1700
1630
|
# Fix indent of a line when a newline is inserted to the next
|
1701
|
-
new_lines = whole_lines
|
1631
|
+
new_lines = whole_lines
|
1702
1632
|
new_indent = @auto_indent_proc.(new_lines[0..-3].push(''), @line_index - 1, 0, true)
|
1703
1633
|
md = @line.match(/\A */)
|
1704
1634
|
prev_indent = md[0].count(' ')
|
@@ -1713,23 +1643,20 @@ class Reline::LineEditor
|
|
1713
1643
|
@line = ' ' * new_indent + @line.lstrip
|
1714
1644
|
end
|
1715
1645
|
end
|
1716
|
-
|
1717
|
-
new_lines = whole_lines(index: @previous_line_index, line: @line)
|
1718
|
-
else
|
1719
|
-
new_lines = whole_lines
|
1720
|
-
end
|
1646
|
+
new_lines = whole_lines
|
1721
1647
|
new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
|
1722
|
-
new_indent = @cursor_max if new_indent&.> @cursor_max
|
1723
1648
|
if new_indent&.>= 0
|
1724
1649
|
md = new_lines[@line_index].match(/\A */)
|
1725
1650
|
prev_indent = md[0].count(' ')
|
1726
1651
|
if @check_new_auto_indent
|
1727
|
-
@buffer_of_lines[@line_index] = ' ' * new_indent + @buffer_of_lines[@line_index].lstrip
|
1652
|
+
line = @buffer_of_lines[@line_index] = ' ' * new_indent + @buffer_of_lines[@line_index].lstrip
|
1728
1653
|
@cursor = new_indent
|
1654
|
+
@cursor_max = calculate_width(line)
|
1729
1655
|
@byte_pointer = new_indent
|
1730
1656
|
else
|
1731
1657
|
@line = ' ' * new_indent + @line.lstrip
|
1732
1658
|
@cursor += new_indent - prev_indent
|
1659
|
+
@cursor_max = calculate_width(@line)
|
1733
1660
|
@byte_pointer += new_indent - prev_indent
|
1734
1661
|
end
|
1735
1662
|
end
|
@@ -1803,11 +1730,7 @@ class Reline::LineEditor
|
|
1803
1730
|
target = before
|
1804
1731
|
end
|
1805
1732
|
if @is_multiline
|
1806
|
-
|
1807
|
-
lines = whole_lines(index: @previous_line_index, line: @line)
|
1808
|
-
else
|
1809
|
-
lines = whole_lines
|
1810
|
-
end
|
1733
|
+
lines = whole_lines
|
1811
1734
|
if @line_index > 0
|
1812
1735
|
preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
|
1813
1736
|
end
|
@@ -1907,9 +1830,10 @@ class Reline::LineEditor
|
|
1907
1830
|
@cursor_max = calculate_width(@line)
|
1908
1831
|
end
|
1909
1832
|
|
1910
|
-
def whole_lines
|
1833
|
+
def whole_lines
|
1834
|
+
index = @previous_line_index || @line_index
|
1911
1835
|
temp_lines = @buffer_of_lines.dup
|
1912
|
-
temp_lines[index] = line
|
1836
|
+
temp_lines[index] = @line
|
1913
1837
|
temp_lines
|
1914
1838
|
end
|
1915
1839
|
|
@@ -1917,11 +1841,7 @@ class Reline::LineEditor
|
|
1917
1841
|
if @buffer_of_lines.size == 1 and @line.nil?
|
1918
1842
|
nil
|
1919
1843
|
else
|
1920
|
-
|
1921
|
-
whole_lines(index: @previous_line_index, line: @line).join("\n")
|
1922
|
-
else
|
1923
|
-
whole_lines.join("\n")
|
1924
|
-
end
|
1844
|
+
whole_lines.join("\n")
|
1925
1845
|
end
|
1926
1846
|
end
|
1927
1847
|
|
@@ -1953,8 +1873,10 @@ class Reline::LineEditor
|
|
1953
1873
|
end
|
1954
1874
|
|
1955
1875
|
private def key_delete(key)
|
1956
|
-
if @config.editing_mode_is?(:vi_insert
|
1876
|
+
if @config.editing_mode_is?(:vi_insert)
|
1957
1877
|
ed_delete_next_char(key)
|
1878
|
+
elsif @config.editing_mode_is?(:emacs)
|
1879
|
+
em_delete(key)
|
1958
1880
|
end
|
1959
1881
|
end
|
1960
1882
|
|
@@ -2660,7 +2582,7 @@ class Reline::LineEditor
|
|
2660
2582
|
alias_method :kill_whole_line, :em_kill_line
|
2661
2583
|
|
2662
2584
|
private def em_delete(key)
|
2663
|
-
if
|
2585
|
+
if @line.empty? and (not @is_multiline or @buffer_of_lines.size == 1) and key == "\C-d".ord
|
2664
2586
|
@line = nil
|
2665
2587
|
if @buffer_of_lines.size > 1
|
2666
2588
|
scroll_down(@highest_in_all - @first_line_started_from)
|