reline 0.3.2 → 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/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)
|