reline 0.3.0 → 0.3.6
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 +25 -1
- data/lib/reline/ansi.rb +11 -4
- data/lib/reline/config.rb +17 -11
- data/lib/reline/general_io.rb +10 -0
- data/lib/reline/key_actor/emacs.rb +1 -1
- data/lib/reline/key_stroke.rb +50 -7
- data/lib/reline/line_editor.rb +235 -282
- data/lib/reline/terminfo.rb +44 -18
- data/lib/reline/unicode/east_asian_width.rb +88 -56
- data/lib/reline/unicode.rb +30 -53
- data/lib/reline/version.rb +1 -1
- data/lib/reline/windows.rb +7 -3
- data/lib/reline.rb +115 -80
- metadata +4 -5
- data/lib/reline/sibori.rb +0 -170
data/lib/reline/line_editor.rb
CHANGED
@@ -48,10 +48,11 @@ class Reline::LineEditor
|
|
48
48
|
PERFECT_MATCH = :perfect_match
|
49
49
|
end
|
50
50
|
|
51
|
-
CompletionJourneyData = Struct.new(
|
52
|
-
MenuInfo = Struct.new(
|
51
|
+
CompletionJourneyData = Struct.new(:preposing, :postposing, :list, :pointer)
|
52
|
+
MenuInfo = Struct.new(: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
|
@@ -59,6 +60,10 @@ class Reline::LineEditor
|
|
59
60
|
reset_variables(encoding: encoding)
|
60
61
|
end
|
61
62
|
|
63
|
+
def io_gate
|
64
|
+
Reline::IOGate
|
65
|
+
end
|
66
|
+
|
62
67
|
def set_pasting_state(in_pasting)
|
63
68
|
@in_pasting = in_pasting
|
64
69
|
end
|
@@ -93,7 +98,7 @@ class Reline::LineEditor
|
|
93
98
|
mode_string
|
94
99
|
end
|
95
100
|
|
96
|
-
private def check_multiline_prompt(buffer)
|
101
|
+
private def check_multiline_prompt(buffer, force_recalc: false)
|
97
102
|
if @vi_arg
|
98
103
|
prompt = "(arg: #{@vi_arg}) "
|
99
104
|
@rerender_all = true
|
@@ -103,7 +108,7 @@ class Reline::LineEditor
|
|
103
108
|
else
|
104
109
|
prompt = @prompt
|
105
110
|
end
|
106
|
-
if simplified_rendering?
|
111
|
+
if simplified_rendering? && !force_recalc
|
107
112
|
mode_string = check_mode_string
|
108
113
|
prompt = mode_string + prompt if mode_string
|
109
114
|
return [prompt, calculate_width(prompt, true), [prompt] * buffer.size]
|
@@ -219,7 +224,7 @@ class Reline::LineEditor
|
|
219
224
|
|
220
225
|
def set_signal_handlers
|
221
226
|
@old_trap = Signal.trap('INT') {
|
222
|
-
clear_dialog
|
227
|
+
clear_dialog(0)
|
223
228
|
if @scroll_partial_screen
|
224
229
|
move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
|
225
230
|
else
|
@@ -238,21 +243,10 @@ class Reline::LineEditor
|
|
238
243
|
@old_trap.call if @old_trap.respond_to?(:call)
|
239
244
|
end
|
240
245
|
}
|
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
246
|
end
|
249
247
|
|
250
248
|
def finalize
|
251
249
|
Signal.trap('INT', @old_trap)
|
252
|
-
begin
|
253
|
-
Signal.trap('TSTP', @old_tstp_trap)
|
254
|
-
rescue ArgumentError
|
255
|
-
end
|
256
250
|
end
|
257
251
|
|
258
252
|
def eof?
|
@@ -293,6 +287,7 @@ class Reline::LineEditor
|
|
293
287
|
@in_pasting = false
|
294
288
|
@auto_indent_proc = nil
|
295
289
|
@dialogs = []
|
290
|
+
@previous_rendered_dialog_y = 0
|
296
291
|
@last_key = nil
|
297
292
|
@resized = false
|
298
293
|
reset_line
|
@@ -439,6 +434,7 @@ class Reline::LineEditor
|
|
439
434
|
@menu_info = nil
|
440
435
|
end
|
441
436
|
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
|
437
|
+
cursor_column = (prompt_width + @cursor) % @screen_size.last
|
442
438
|
if @cleared
|
443
439
|
clear_screen_buffer(prompt, prompt_list, prompt_width)
|
444
440
|
@cleared = false
|
@@ -449,32 +445,30 @@ class Reline::LineEditor
|
|
449
445
|
Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
|
450
446
|
Reline::IOGate.move_cursor_column(0)
|
451
447
|
@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
|
448
|
+
new_lines = whole_lines
|
449
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
|
458
450
|
modify_lines(new_lines).each_with_index do |line, index|
|
459
|
-
@output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\n"
|
451
|
+
@output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\r\n"
|
460
452
|
Reline::IOGate.erase_after_cursor
|
461
453
|
end
|
462
454
|
@output.flush
|
463
|
-
clear_dialog
|
455
|
+
clear_dialog(cursor_column)
|
464
456
|
return
|
465
457
|
end
|
466
458
|
new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
|
467
459
|
rendered = false
|
468
460
|
if @add_newline_to_end_of_buffer
|
469
|
-
|
461
|
+
clear_dialog_with_trap_key(cursor_column)
|
462
|
+
rerender_added_newline(prompt, prompt_width, prompt_list)
|
470
463
|
@add_newline_to_end_of_buffer = false
|
471
464
|
else
|
472
465
|
if @just_cursor_moving and not @rerender_all
|
466
|
+
clear_dialog_with_trap_key(cursor_column)
|
473
467
|
rendered = just_move_cursor
|
474
|
-
render_dialog((prompt_width + @cursor) % @screen_size.last)
|
475
468
|
@just_cursor_moving = false
|
476
469
|
return
|
477
470
|
elsif @previous_line_index or new_highest_in_this != @highest_in_this
|
471
|
+
clear_dialog_with_trap_key(cursor_column)
|
478
472
|
rerender_changed_current_line
|
479
473
|
@previous_line_index = nil
|
480
474
|
rendered = true
|
@@ -488,13 +482,9 @@ class Reline::LineEditor
|
|
488
482
|
if @is_multiline
|
489
483
|
if finished?
|
490
484
|
# Always rerender on finish because output_modifier_proc may return a different output.
|
491
|
-
|
492
|
-
new_lines = whole_lines(index: @previous_line_index, line: @line)
|
493
|
-
else
|
494
|
-
new_lines = whole_lines
|
495
|
-
end
|
485
|
+
new_lines = whole_lines
|
496
486
|
line = modify_lines(new_lines)[@line_index]
|
497
|
-
clear_dialog
|
487
|
+
clear_dialog(cursor_column)
|
498
488
|
prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
|
499
489
|
render_partial(prompt, prompt_width, line, @first_line_started_from)
|
500
490
|
move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
|
@@ -507,7 +497,7 @@ class Reline::LineEditor
|
|
507
497
|
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
|
508
498
|
render_partial(prompt, prompt_width, line, @first_line_started_from)
|
509
499
|
end
|
510
|
-
render_dialog(
|
500
|
+
render_dialog(cursor_column)
|
511
501
|
end
|
512
502
|
@buffer_of_lines[@line_index] = @line
|
513
503
|
@rest_height = 0 if @scroll_partial_screen
|
@@ -576,6 +566,16 @@ class Reline::LineEditor
|
|
576
566
|
@line_editor.instance_variable_get(:@screen_size).last
|
577
567
|
end
|
578
568
|
|
569
|
+
def screen_height
|
570
|
+
@line_editor.instance_variable_get(:@screen_size).first
|
571
|
+
end
|
572
|
+
|
573
|
+
def preferred_dialog_height
|
574
|
+
rest_height = @line_editor.instance_variable_get(:@rest_height)
|
575
|
+
scroll_partial_screen = @line_editor.instance_variable_get(:@scroll_partial_screen) || 0
|
576
|
+
[cursor_pos.y - scroll_partial_screen, rest_height, (screen_height + 6) / 5].max
|
577
|
+
end
|
578
|
+
|
579
579
|
def completion_journey_data
|
580
580
|
@line_editor.instance_variable_get(:@completion_journey_data)
|
581
581
|
end
|
@@ -591,7 +591,7 @@ class Reline::LineEditor
|
|
591
591
|
|
592
592
|
class Dialog
|
593
593
|
attr_reader :name, :contents, :width
|
594
|
-
attr_accessor :scroll_top, :
|
594
|
+
attr_accessor :scroll_top, :pointer, :column, :vertical_offset, :trap_key
|
595
595
|
|
596
596
|
def initialize(name, config, proc_scope)
|
597
597
|
@name = name
|
@@ -647,18 +647,127 @@ class Reline::LineEditor
|
|
647
647
|
|
648
648
|
DIALOG_DEFAULT_HEIGHT = 20
|
649
649
|
private def render_dialog(cursor_column)
|
650
|
-
@dialogs.
|
651
|
-
|
650
|
+
changes = @dialogs.map do |dialog|
|
651
|
+
old_dialog = dialog.dup
|
652
|
+
update_each_dialog(dialog, cursor_column)
|
653
|
+
[old_dialog, dialog]
|
652
654
|
end
|
655
|
+
render_dialog_changes(changes, cursor_column)
|
653
656
|
end
|
654
657
|
|
655
658
|
private def padding_space_with_escape_sequences(str, width)
|
656
|
-
|
659
|
+
padding_width = width - calculate_width(str, true)
|
660
|
+
# padding_width should be only positive value. But macOS and Alacritty returns negative value.
|
661
|
+
padding_width = 0 if padding_width < 0
|
662
|
+
str + (' ' * padding_width)
|
663
|
+
end
|
664
|
+
|
665
|
+
private def range_subtract(base_ranges, subtract_ranges)
|
666
|
+
indices = base_ranges.flat_map(&:to_a).uniq.sort - subtract_ranges.flat_map(&:to_a)
|
667
|
+
chunks = indices.chunk_while { |a, b| a + 1 == b }
|
668
|
+
chunks.map { |a| a.first...a.last + 1 }
|
669
|
+
end
|
670
|
+
|
671
|
+
private def dialog_range(dialog, dialog_y)
|
672
|
+
x_range = dialog.column...dialog.column + dialog.width
|
673
|
+
y_range = dialog_y + dialog.vertical_offset...dialog_y + dialog.vertical_offset + dialog.contents.size
|
674
|
+
[x_range, y_range]
|
675
|
+
end
|
676
|
+
|
677
|
+
private def render_dialog_changes(changes, cursor_column)
|
678
|
+
# Collect x-coordinate range and content of previous and current dialogs for each line
|
679
|
+
old_dialog_ranges = {}
|
680
|
+
new_dialog_ranges = {}
|
681
|
+
new_dialog_contents = {}
|
682
|
+
changes.each do |old_dialog, new_dialog|
|
683
|
+
if old_dialog.contents
|
684
|
+
x_range, y_range = dialog_range(old_dialog, @previous_rendered_dialog_y)
|
685
|
+
y_range.each do |y|
|
686
|
+
(old_dialog_ranges[y] ||= []) << x_range
|
687
|
+
end
|
688
|
+
end
|
689
|
+
if new_dialog.contents
|
690
|
+
x_range, y_range = dialog_range(new_dialog, @first_line_started_from + @started_from)
|
691
|
+
y_range.each do |y|
|
692
|
+
(new_dialog_ranges[y] ||= []) << x_range
|
693
|
+
(new_dialog_contents[y] ||= []) << [x_range, new_dialog.contents[y - y_range.begin]]
|
694
|
+
end
|
695
|
+
end
|
696
|
+
end
|
697
|
+
return if old_dialog_ranges.empty? && new_dialog_ranges.empty?
|
698
|
+
|
699
|
+
# Calculate x-coordinate ranges to restore text that was hidden behind dialogs for each line
|
700
|
+
ranges_to_restore = {}
|
701
|
+
subtract_cache = {}
|
702
|
+
old_dialog_ranges.each do |y, old_x_ranges|
|
703
|
+
new_x_ranges = new_dialog_ranges[y] || []
|
704
|
+
ranges = subtract_cache[[old_x_ranges, new_x_ranges]] ||= range_subtract(old_x_ranges, new_x_ranges)
|
705
|
+
ranges_to_restore[y] = ranges if ranges.any?
|
706
|
+
end
|
707
|
+
|
708
|
+
# Create visual_lines for restoring text hidden behind dialogs
|
709
|
+
if ranges_to_restore.any?
|
710
|
+
lines = whole_lines
|
711
|
+
prompt, _prompt_width, prompt_list = check_multiline_prompt(lines, force_recalc: true)
|
712
|
+
modified_lines = modify_lines(lines, force_recalc: true)
|
713
|
+
visual_lines = []
|
714
|
+
modified_lines.each_with_index { |l, i|
|
715
|
+
pr = prompt_list ? prompt_list[i] : prompt
|
716
|
+
vl, = split_by_width(pr + l, @screen_size.last)
|
717
|
+
vl.compact!
|
718
|
+
visual_lines.concat(vl)
|
719
|
+
}
|
720
|
+
end
|
721
|
+
|
722
|
+
# Clear and rerender all dialogs line by line
|
723
|
+
Reline::IOGate.hide_cursor
|
724
|
+
ymin, ymax = (ranges_to_restore.keys + new_dialog_ranges.keys).minmax
|
725
|
+
scroll_partial_screen = @scroll_partial_screen || 0
|
726
|
+
screen_y_range = scroll_partial_screen..(scroll_partial_screen + @screen_height - 1)
|
727
|
+
ymin = ymin.clamp(screen_y_range.begin, screen_y_range.end)
|
728
|
+
ymax = ymax.clamp(screen_y_range.begin, screen_y_range.end)
|
729
|
+
dialog_y = @first_line_started_from + @started_from
|
730
|
+
cursor_y = dialog_y
|
731
|
+
if @highest_in_all <= ymax
|
732
|
+
scroll_down(ymax - cursor_y)
|
733
|
+
move_cursor_up(ymax - cursor_y)
|
734
|
+
end
|
735
|
+
(ymin..ymax).each do |y|
|
736
|
+
move_cursor_down(y - cursor_y)
|
737
|
+
cursor_y = y
|
738
|
+
new_x_ranges = new_dialog_ranges[y]
|
739
|
+
restore_ranges = ranges_to_restore[y]
|
740
|
+
# Restore text that was hidden behind dialogs
|
741
|
+
if restore_ranges
|
742
|
+
line = visual_lines[y] || ''
|
743
|
+
restore_ranges.each do |range|
|
744
|
+
col = range.begin
|
745
|
+
width = range.end - range.begin
|
746
|
+
s = padding_space_with_escape_sequences(Reline::Unicode.take_range(line, col, width), width)
|
747
|
+
Reline::IOGate.move_cursor_column(col)
|
748
|
+
@output.write "\e[0m#{s}\e[0m"
|
749
|
+
end
|
750
|
+
max_column = [calculate_width(line, true), new_x_ranges&.map(&:end)&.max || 0].max
|
751
|
+
if max_column < restore_ranges.map(&:end).max
|
752
|
+
Reline::IOGate.move_cursor_column(max_column)
|
753
|
+
Reline::IOGate.erase_after_cursor
|
754
|
+
end
|
755
|
+
end
|
756
|
+
# Render dialog contents
|
757
|
+
new_dialog_contents[y]&.each do |x_range, content|
|
758
|
+
Reline::IOGate.move_cursor_column(x_range.begin)
|
759
|
+
@output.write "\e[0m#{content}\e[0m"
|
760
|
+
end
|
761
|
+
end
|
762
|
+
move_cursor_up(cursor_y - dialog_y)
|
763
|
+
Reline::IOGate.move_cursor_column(cursor_column)
|
764
|
+
Reline::IOGate.show_cursor
|
765
|
+
|
766
|
+
@previous_rendered_dialog_y = dialog_y
|
657
767
|
end
|
658
768
|
|
659
|
-
private def
|
769
|
+
private def update_each_dialog(dialog, cursor_column)
|
660
770
|
if @in_pasting
|
661
|
-
clear_each_dialog(dialog)
|
662
771
|
dialog.contents = nil
|
663
772
|
dialog.trap_key = nil
|
664
773
|
return
|
@@ -666,29 +775,20 @@ class Reline::LineEditor
|
|
666
775
|
dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
|
667
776
|
dialog_render_info = dialog.call(@last_key)
|
668
777
|
if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty?
|
669
|
-
dialog.lines_backup = {
|
670
|
-
lines: modify_lines(whole_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
778
|
dialog.contents = nil
|
678
779
|
dialog.trap_key = nil
|
679
780
|
return
|
680
781
|
end
|
681
|
-
|
682
|
-
dialog.contents = dialog_render_info.contents
|
782
|
+
contents = dialog_render_info.contents
|
683
783
|
pointer = dialog.pointer
|
684
784
|
if dialog_render_info.width
|
685
785
|
dialog.width = dialog_render_info.width
|
686
786
|
else
|
687
|
-
dialog.width =
|
787
|
+
dialog.width = contents.map { |l| calculate_width(l, true) }.max
|
688
788
|
end
|
689
789
|
height = dialog_render_info.height || DIALOG_DEFAULT_HEIGHT
|
690
|
-
height =
|
691
|
-
if
|
790
|
+
height = contents.size if contents.size < height
|
791
|
+
if contents.size > height
|
692
792
|
if dialog.pointer
|
693
793
|
if dialog.pointer < 0
|
694
794
|
dialog.scroll_top = 0
|
@@ -698,24 +798,24 @@ class Reline::LineEditor
|
|
698
798
|
dialog.scroll_top = dialog.pointer
|
699
799
|
end
|
700
800
|
pointer = dialog.pointer - dialog.scroll_top
|
801
|
+
else
|
802
|
+
dialog.scroll_top = 0
|
701
803
|
end
|
702
|
-
|
703
|
-
end
|
704
|
-
if dialog.contents and dialog.scroll_top >= dialog.contents.size
|
705
|
-
dialog.scroll_top = dialog.contents.size - height
|
804
|
+
contents = contents[dialog.scroll_top, height]
|
706
805
|
end
|
707
806
|
if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
|
708
807
|
bar_max_height = height * 2
|
709
808
|
moving_distance = (dialog_render_info.contents.size - height) * 2
|
710
809
|
position_ratio = dialog.scroll_top.zero? ? 0.0 : ((dialog.scroll_top * 2).to_f / moving_distance)
|
711
|
-
bar_height = (bar_max_height * ((
|
712
|
-
|
810
|
+
bar_height = (bar_max_height * ((contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
|
811
|
+
bar_height = MINIMUM_SCROLLBAR_HEIGHT if bar_height < MINIMUM_SCROLLBAR_HEIGHT
|
812
|
+
scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i
|
713
813
|
else
|
714
|
-
|
814
|
+
scrollbar_pos = nil
|
715
815
|
end
|
716
816
|
upper_space = @first_line_started_from - @started_from
|
717
817
|
dialog.column = dialog_render_info.pos.x
|
718
|
-
dialog.width += @block_elem_width if
|
818
|
+
dialog.width += @block_elem_width if scrollbar_pos
|
719
819
|
diff = (dialog.column + dialog.width) - (@screen_size.last)
|
720
820
|
if diff > 0
|
721
821
|
dialog.column -= diff
|
@@ -725,200 +825,54 @@ class Reline::LineEditor
|
|
725
825
|
elsif upper_space >= height
|
726
826
|
dialog.vertical_offset = dialog_render_info.pos.y - height
|
727
827
|
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
828
|
dialog.vertical_offset = dialog_render_info.pos.y + 1
|
733
829
|
end
|
734
|
-
Reline::IOGate.hide_cursor
|
735
830
|
if dialog.column < 0
|
736
831
|
dialog.column = 0
|
737
832
|
dialog.width = @screen_size.last
|
738
833
|
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|
|
834
|
+
dialog.contents = contents.map.with_index do |item, i|
|
743
835
|
if i == pointer
|
744
|
-
|
836
|
+
fg_color = dialog_render_info.pointer_fg_color
|
837
|
+
bg_color = dialog_render_info.pointer_bg_color
|
745
838
|
else
|
746
|
-
|
747
|
-
|
748
|
-
else
|
749
|
-
bg_color = '46'
|
750
|
-
end
|
839
|
+
fg_color = dialog_render_info.fg_color
|
840
|
+
bg_color = dialog_render_info.bg_color
|
751
841
|
end
|
752
|
-
str_width = dialog.width - (
|
842
|
+
str_width = dialog.width - (scrollbar_pos.nil? ? 0 : @block_elem_width)
|
753
843
|
str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, str_width), str_width)
|
754
|
-
|
755
|
-
if
|
756
|
-
|
757
|
-
if
|
758
|
-
|
759
|
-
elsif
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
@output.write @lower_half_block
|
844
|
+
colored_content = "\e[#{bg_color}m\e[#{fg_color}m#{str}"
|
845
|
+
if scrollbar_pos
|
846
|
+
color_seq = "\e[37m"
|
847
|
+
if scrollbar_pos <= (i * 2) and (i * 2 + 1) < (scrollbar_pos + bar_height)
|
848
|
+
colored_content + color_seq + @full_block
|
849
|
+
elsif scrollbar_pos <= (i * 2) and (i * 2) < (scrollbar_pos + bar_height)
|
850
|
+
colored_content + color_seq + @upper_half_block
|
851
|
+
elsif scrollbar_pos <= (i * 2 + 1) and (i * 2) < (scrollbar_pos + bar_height)
|
852
|
+
colored_content + color_seq + @lower_half_block
|
764
853
|
else
|
765
|
-
|
854
|
+
colored_content + color_seq + ' ' * @block_elem_width
|
766
855
|
end
|
856
|
+
else
|
857
|
+
colored_content
|
767
858
|
end
|
768
|
-
@output.write "\e[0m"
|
769
|
-
Reline::IOGate.move_cursor_column(dialog.column)
|
770
|
-
move_cursor_down(1) if i < (dialog.contents.size - 1)
|
771
859
|
end
|
772
|
-
Reline::IOGate.move_cursor_column(cursor_column)
|
773
|
-
move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
|
774
|
-
Reline::IOGate.show_cursor
|
775
|
-
dialog.lines_backup = {
|
776
|
-
lines: modify_lines(whole_lines),
|
777
|
-
line_index: @line_index,
|
778
|
-
first_line_started_from: @first_line_started_from,
|
779
|
-
started_from: @started_from,
|
780
|
-
byte_pointer: @byte_pointer
|
781
|
-
}
|
782
860
|
end
|
783
861
|
|
784
|
-
private def
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
dialog.lines_backup[:lines].each_with_index { |l, i|
|
790
|
-
pr = prompt_list ? prompt_list[i] : prompt
|
791
|
-
vl, _ = split_by_width(pr + l, @screen_size.last)
|
792
|
-
vl.compact!
|
793
|
-
if i == dialog.lines_backup[:line_index]
|
794
|
-
visual_start = visual_lines.size + dialog.lines_backup[:started_from]
|
795
|
-
end
|
796
|
-
visual_lines.concat(vl)
|
797
|
-
}
|
798
|
-
old_y = dialog.lines_backup[:first_line_started_from] + dialog.lines_backup[:started_from]
|
799
|
-
y = @first_line_started_from + @started_from
|
800
|
-
y_diff = y - old_y
|
801
|
-
if (old_y + old_dialog.vertical_offset) < (y + dialog.vertical_offset)
|
802
|
-
# rerender top
|
803
|
-
move_cursor_down(old_dialog.vertical_offset - y_diff)
|
804
|
-
start = visual_start + old_dialog.vertical_offset
|
805
|
-
line_num = dialog.vertical_offset - old_dialog.vertical_offset
|
806
|
-
line_num.times do |i|
|
807
|
-
Reline::IOGate.move_cursor_column(old_dialog.column)
|
808
|
-
if visual_lines[start + i].nil?
|
809
|
-
s = ' ' * old_dialog.width
|
810
|
-
else
|
811
|
-
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
|
812
|
-
s = padding_space_with_escape_sequences(s, old_dialog.width)
|
813
|
-
end
|
814
|
-
@output.write "\e[0m#{s}\e[0m"
|
815
|
-
move_cursor_down(1) if i < (line_num - 1)
|
816
|
-
end
|
817
|
-
move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
|
818
|
-
end
|
819
|
-
if (old_y + old_dialog.vertical_offset + old_dialog.contents.size) > (y + dialog.vertical_offset + dialog.contents.size)
|
820
|
-
# rerender bottom
|
821
|
-
move_cursor_down(dialog.vertical_offset + dialog.contents.size - y_diff)
|
822
|
-
start = visual_start + dialog.vertical_offset + dialog.contents.size
|
823
|
-
line_num = (old_dialog.vertical_offset + old_dialog.contents.size) - (dialog.vertical_offset + dialog.contents.size)
|
824
|
-
line_num.times do |i|
|
825
|
-
Reline::IOGate.move_cursor_column(old_dialog.column)
|
826
|
-
if visual_lines[start + i].nil?
|
827
|
-
s = ' ' * old_dialog.width
|
828
|
-
else
|
829
|
-
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
|
830
|
-
s = padding_space_with_escape_sequences(s, old_dialog.width)
|
831
|
-
end
|
832
|
-
@output.write "\e[0m#{s}\e[0m"
|
833
|
-
move_cursor_down(1) if i < (line_num - 1)
|
834
|
-
end
|
835
|
-
move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
|
836
|
-
end
|
837
|
-
if old_dialog.column < dialog.column
|
838
|
-
# rerender left
|
839
|
-
move_cursor_down(old_dialog.vertical_offset - y_diff)
|
840
|
-
width = dialog.column - old_dialog.column
|
841
|
-
start = visual_start + old_dialog.vertical_offset
|
842
|
-
line_num = old_dialog.contents.size
|
843
|
-
line_num.times do |i|
|
844
|
-
Reline::IOGate.move_cursor_column(old_dialog.column)
|
845
|
-
if visual_lines[start + i].nil?
|
846
|
-
s = ' ' * width
|
847
|
-
else
|
848
|
-
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, width)
|
849
|
-
s = padding_space_with_escape_sequences(s, dialog.width)
|
850
|
-
end
|
851
|
-
@output.write "\e[0m#{s}\e[0m"
|
852
|
-
move_cursor_down(1) if i < (line_num - 1)
|
853
|
-
end
|
854
|
-
move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
|
855
|
-
end
|
856
|
-
if (old_dialog.column + old_dialog.width) > (dialog.column + dialog.width)
|
857
|
-
# rerender right
|
858
|
-
move_cursor_down(old_dialog.vertical_offset + y_diff)
|
859
|
-
width = (old_dialog.column + old_dialog.width) - (dialog.column + dialog.width)
|
860
|
-
start = visual_start + old_dialog.vertical_offset
|
861
|
-
line_num = old_dialog.contents.size
|
862
|
-
line_num.times do |i|
|
863
|
-
Reline::IOGate.move_cursor_column(old_dialog.column + dialog.width)
|
864
|
-
if visual_lines[start + i].nil?
|
865
|
-
s = ' ' * width
|
866
|
-
else
|
867
|
-
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column + dialog.width, width)
|
868
|
-
rerender_width = old_dialog.width - dialog.width
|
869
|
-
s = padding_space_with_escape_sequences(s, rerender_width)
|
870
|
-
end
|
871
|
-
Reline::IOGate.move_cursor_column(dialog.column + dialog.width)
|
872
|
-
@output.write "\e[0m#{s}\e[0m"
|
873
|
-
move_cursor_down(1) if i < (line_num - 1)
|
874
|
-
end
|
875
|
-
move_cursor_up(old_dialog.vertical_offset + line_num - 1 + y_diff)
|
862
|
+
private def clear_dialog(cursor_column)
|
863
|
+
changes = @dialogs.map do |dialog|
|
864
|
+
old_dialog = dialog.dup
|
865
|
+
dialog.contents = nil
|
866
|
+
[old_dialog, dialog]
|
876
867
|
end
|
877
|
-
|
868
|
+
render_dialog_changes(changes, cursor_column)
|
878
869
|
end
|
879
870
|
|
880
|
-
private def
|
871
|
+
private def clear_dialog_with_trap_key(cursor_column)
|
872
|
+
clear_dialog(cursor_column)
|
881
873
|
@dialogs.each do |dialog|
|
882
|
-
|
883
|
-
end
|
884
|
-
end
|
885
|
-
|
886
|
-
private def clear_each_dialog(dialog)
|
887
|
-
dialog.trap_key = nil
|
888
|
-
return unless dialog.contents
|
889
|
-
prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
|
890
|
-
visual_lines = []
|
891
|
-
visual_lines_under_dialog = []
|
892
|
-
visual_start = nil
|
893
|
-
dialog.lines_backup[:lines].each_with_index { |l, i|
|
894
|
-
pr = prompt_list ? prompt_list[i] : prompt
|
895
|
-
vl, _ = split_by_width(pr + l, @screen_size.last)
|
896
|
-
vl.compact!
|
897
|
-
if i == dialog.lines_backup[:line_index]
|
898
|
-
visual_start = visual_lines.size + dialog.lines_backup[:started_from] + dialog.vertical_offset
|
899
|
-
end
|
900
|
-
visual_lines.concat(vl)
|
901
|
-
}
|
902
|
-
visual_lines_under_dialog = visual_lines[visual_start, dialog.contents.size]
|
903
|
-
visual_lines_under_dialog = [] if visual_lines_under_dialog.nil?
|
904
|
-
Reline::IOGate.hide_cursor
|
905
|
-
move_cursor_down(dialog.vertical_offset)
|
906
|
-
dialog_vertical_size = dialog.contents.size
|
907
|
-
dialog_vertical_size.times do |i|
|
908
|
-
if i < visual_lines_under_dialog.size
|
909
|
-
Reline::IOGate.move_cursor_column(dialog.column)
|
910
|
-
str = Reline::Unicode.take_range(visual_lines_under_dialog[i], dialog.column, dialog.width)
|
911
|
-
str = padding_space_with_escape_sequences(str, dialog.width)
|
912
|
-
@output.write "\e[0m#{str}\e[0m"
|
913
|
-
else
|
914
|
-
Reline::IOGate.move_cursor_column(dialog.column)
|
915
|
-
@output.write "\e[0m#{' ' * dialog.width}\e[0m"
|
916
|
-
end
|
917
|
-
move_cursor_down(1) if i < (dialog_vertical_size - 1)
|
874
|
+
dialog.trap_key = nil
|
918
875
|
end
|
919
|
-
move_cursor_up(dialog_vertical_size - 1 + dialog.vertical_offset)
|
920
|
-
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
921
|
-
Reline::IOGate.show_cursor
|
922
876
|
end
|
923
877
|
|
924
878
|
private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
|
@@ -954,11 +908,20 @@ class Reline::LineEditor
|
|
954
908
|
end
|
955
909
|
end
|
956
910
|
|
957
|
-
private def rerender_added_newline(prompt, prompt_width)
|
958
|
-
scroll_down(1)
|
911
|
+
private def rerender_added_newline(prompt, prompt_width, prompt_list)
|
959
912
|
@buffer_of_lines[@previous_line_index] = @line
|
960
913
|
@line = @buffer_of_lines[@line_index]
|
961
|
-
|
914
|
+
@previous_line_index = nil
|
915
|
+
if @in_pasting
|
916
|
+
scroll_down(1)
|
917
|
+
else
|
918
|
+
lines = whole_lines
|
919
|
+
prev_line_prompt = @prompt_proc ? prompt_list[@line_index - 1] : prompt
|
920
|
+
prev_line_prompt_width = @prompt_proc ? calculate_width(prev_line_prompt, true) : prompt_width
|
921
|
+
prev_line = modify_lines(lines)[@line_index - 1]
|
922
|
+
move_cursor_up(@started_from)
|
923
|
+
render_partial(prev_line_prompt, prev_line_prompt_width, prev_line, @first_line_started_from + @started_from, with_control: false)
|
924
|
+
scroll_down(1)
|
962
925
|
render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
|
963
926
|
end
|
964
927
|
@cursor = @cursor_max = calculate_width(@line)
|
@@ -967,7 +930,6 @@ class Reline::LineEditor
|
|
967
930
|
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
|
968
931
|
@first_line_started_from += @started_from + 1
|
969
932
|
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
970
|
-
@previous_line_index = nil
|
971
933
|
end
|
972
934
|
|
973
935
|
def just_move_cursor
|
@@ -980,22 +942,18 @@ class Reline::LineEditor
|
|
980
942
|
calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
|
981
943
|
end
|
982
944
|
first_line_diff = new_first_line_started_from - @first_line_started_from
|
983
|
-
|
984
|
-
new_started_from = calculate_height_by_width(prompt_width +
|
945
|
+
@cursor, @cursor_max, _, @byte_pointer = calculate_nearest_cursor(@buffer_of_lines[@line_index], @cursor, @started_from, @byte_pointer, false)
|
946
|
+
new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
985
947
|
calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from)
|
986
948
|
@previous_line_index = nil
|
949
|
+
@line = @buffer_of_lines[@line_index]
|
987
950
|
if @rerender_all
|
988
|
-
@line = @buffer_of_lines[@line_index]
|
989
951
|
rerender_all_lines
|
990
952
|
@rerender_all = false
|
991
953
|
true
|
992
954
|
else
|
993
|
-
@line = @buffer_of_lines[@line_index]
|
994
955
|
@first_line_started_from = new_first_line_started_from
|
995
956
|
@started_from = new_started_from
|
996
|
-
@cursor = new_cursor
|
997
|
-
@cursor_max = new_cursor_max
|
998
|
-
@byte_pointer = new_byte_pointer
|
999
957
|
move_cursor_down(first_line_diff + @started_from)
|
1000
958
|
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
1001
959
|
false
|
@@ -1003,11 +961,7 @@ class Reline::LineEditor
|
|
1003
961
|
end
|
1004
962
|
|
1005
963
|
private def rerender_changed_current_line
|
1006
|
-
|
1007
|
-
new_lines = whole_lines(index: @previous_line_index, line: @line)
|
1008
|
-
else
|
1009
|
-
new_lines = whole_lines
|
1010
|
-
end
|
964
|
+
new_lines = whole_lines
|
1011
965
|
prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
|
1012
966
|
all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
|
1013
967
|
diff = all_height - @highest_in_all
|
@@ -1228,8 +1182,8 @@ class Reline::LineEditor
|
|
1228
1182
|
height
|
1229
1183
|
end
|
1230
1184
|
|
1231
|
-
private def modify_lines(before)
|
1232
|
-
return before if before.nil? || before.empty? || simplified_rendering?
|
1185
|
+
private def modify_lines(before, force_recalc: false)
|
1186
|
+
return before if !force_recalc && (before.nil? || before.empty? || simplified_rendering?)
|
1233
1187
|
|
1234
1188
|
if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
|
1235
1189
|
after.lines("\n").map { |l| l.chomp('') }
|
@@ -1361,8 +1315,8 @@ class Reline::LineEditor
|
|
1361
1315
|
@completion_state = CompletionState::MENU
|
1362
1316
|
end
|
1363
1317
|
if not just_show_list and target < completed
|
1364
|
-
@line = preposing + completed + completion_append_character.to_s + postposing
|
1365
|
-
line_to_pointer = preposing + completed + completion_append_character.to_s
|
1318
|
+
@line = (preposing + completed + completion_append_character.to_s + postposing).split("\n")[@line_index] || String.new(encoding: @encoding)
|
1319
|
+
line_to_pointer = (preposing + completed + completion_append_character.to_s).split("\n").last || String.new(encoding: @encoding)
|
1366
1320
|
@cursor_max = calculate_width(@line)
|
1367
1321
|
@cursor = calculate_width(line_to_pointer)
|
1368
1322
|
@byte_pointer = line_to_pointer.bytesize
|
@@ -1420,7 +1374,7 @@ class Reline::LineEditor
|
|
1420
1374
|
if @waiting_operator_proc
|
1421
1375
|
if VI_MOTIONS.include?(method_symbol)
|
1422
1376
|
old_cursor, old_byte_pointer = @cursor, @byte_pointer
|
1423
|
-
@vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg
|
1377
|
+
@vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg&.> 1
|
1424
1378
|
block.(true)
|
1425
1379
|
unless @waiting_proc
|
1426
1380
|
cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
|
@@ -1560,11 +1514,13 @@ class Reline::LineEditor
|
|
1560
1514
|
return if key.char >= 128 # maybe, first byte of multi byte
|
1561
1515
|
method_symbol = @config.editing_mode.get_method(key.combined_char)
|
1562
1516
|
if key.with_meta and method_symbol == :ed_unassigned
|
1563
|
-
|
1564
|
-
|
1565
|
-
|
1566
|
-
|
1567
|
-
|
1517
|
+
if @config.editing_mode_is?(:vi_command, :vi_insert)
|
1518
|
+
# split ESC + key in vi mode
|
1519
|
+
method_symbol = @config.editing_mode.get_method("\e".ord)
|
1520
|
+
process_key("\e".ord, method_symbol)
|
1521
|
+
method_symbol = @config.editing_mode.get_method(key.char)
|
1522
|
+
process_key(key.char, method_symbol)
|
1523
|
+
end
|
1568
1524
|
else
|
1569
1525
|
process_key(key.combined_char, method_symbol)
|
1570
1526
|
end
|
@@ -1688,14 +1644,14 @@ class Reline::LineEditor
|
|
1688
1644
|
return if not @check_new_auto_indent and @previous_line_index # move cursor up or down
|
1689
1645
|
if @check_new_auto_indent and @previous_line_index and @previous_line_index > 0 and @line_index > @previous_line_index
|
1690
1646
|
# Fix indent of a line when a newline is inserted to the next
|
1691
|
-
new_lines = whole_lines
|
1647
|
+
new_lines = whole_lines
|
1692
1648
|
new_indent = @auto_indent_proc.(new_lines[0..-3].push(''), @line_index - 1, 0, true)
|
1693
1649
|
md = @line.match(/\A */)
|
1694
1650
|
prev_indent = md[0].count(' ')
|
1695
1651
|
@line = ' ' * new_indent + @line.lstrip
|
1696
1652
|
|
1697
1653
|
new_indent = nil
|
1698
|
-
result = @auto_indent_proc.(new_lines[0..-2], @line_index - 1, (new_lines[-
|
1654
|
+
result = @auto_indent_proc.(new_lines[0..-2], @line_index - 1, (new_lines[@line_index - 1].bytesize + 1), false)
|
1699
1655
|
if result
|
1700
1656
|
new_indent = result
|
1701
1657
|
end
|
@@ -1703,23 +1659,20 @@ class Reline::LineEditor
|
|
1703
1659
|
@line = ' ' * new_indent + @line.lstrip
|
1704
1660
|
end
|
1705
1661
|
end
|
1706
|
-
|
1707
|
-
new_lines = whole_lines(index: @previous_line_index, line: @line)
|
1708
|
-
else
|
1709
|
-
new_lines = whole_lines
|
1710
|
-
end
|
1662
|
+
new_lines = whole_lines
|
1711
1663
|
new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
|
1712
|
-
new_indent = @cursor_max if new_indent&.> @cursor_max
|
1713
1664
|
if new_indent&.>= 0
|
1714
1665
|
md = new_lines[@line_index].match(/\A */)
|
1715
1666
|
prev_indent = md[0].count(' ')
|
1716
1667
|
if @check_new_auto_indent
|
1717
|
-
@buffer_of_lines[@line_index] = ' ' * new_indent + @buffer_of_lines[@line_index].lstrip
|
1668
|
+
line = @buffer_of_lines[@line_index] = ' ' * new_indent + @buffer_of_lines[@line_index].lstrip
|
1718
1669
|
@cursor = new_indent
|
1670
|
+
@cursor_max = calculate_width(line)
|
1719
1671
|
@byte_pointer = new_indent
|
1720
1672
|
else
|
1721
1673
|
@line = ' ' * new_indent + @line.lstrip
|
1722
1674
|
@cursor += new_indent - prev_indent
|
1675
|
+
@cursor_max = calculate_width(@line)
|
1723
1676
|
@byte_pointer += new_indent - prev_indent
|
1724
1677
|
end
|
1725
1678
|
end
|
@@ -1793,11 +1746,7 @@ class Reline::LineEditor
|
|
1793
1746
|
target = before
|
1794
1747
|
end
|
1795
1748
|
if @is_multiline
|
1796
|
-
|
1797
|
-
lines = whole_lines(index: @previous_line_index, line: @line)
|
1798
|
-
else
|
1799
|
-
lines = whole_lines
|
1800
|
-
end
|
1749
|
+
lines = whole_lines
|
1801
1750
|
if @line_index > 0
|
1802
1751
|
preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
|
1803
1752
|
end
|
@@ -1897,9 +1846,10 @@ class Reline::LineEditor
|
|
1897
1846
|
@cursor_max = calculate_width(@line)
|
1898
1847
|
end
|
1899
1848
|
|
1900
|
-
def whole_lines
|
1849
|
+
def whole_lines
|
1850
|
+
index = @previous_line_index || @line_index
|
1901
1851
|
temp_lines = @buffer_of_lines.dup
|
1902
|
-
temp_lines[index] = line
|
1852
|
+
temp_lines[index] = @line
|
1903
1853
|
temp_lines
|
1904
1854
|
end
|
1905
1855
|
|
@@ -1907,11 +1857,7 @@ class Reline::LineEditor
|
|
1907
1857
|
if @buffer_of_lines.size == 1 and @line.nil?
|
1908
1858
|
nil
|
1909
1859
|
else
|
1910
|
-
|
1911
|
-
whole_lines(index: @previous_line_index, line: @line).join("\n")
|
1912
|
-
else
|
1913
|
-
whole_lines.join("\n")
|
1914
|
-
end
|
1860
|
+
whole_lines.join("\n")
|
1915
1861
|
end
|
1916
1862
|
end
|
1917
1863
|
|
@@ -1943,8 +1889,10 @@ class Reline::LineEditor
|
|
1943
1889
|
end
|
1944
1890
|
|
1945
1891
|
private def key_delete(key)
|
1946
|
-
if @config.editing_mode_is?(:vi_insert
|
1892
|
+
if @config.editing_mode_is?(:vi_insert)
|
1947
1893
|
ed_delete_next_char(key)
|
1894
|
+
elsif @config.editing_mode_is?(:emacs)
|
1895
|
+
em_delete(key)
|
1948
1896
|
end
|
1949
1897
|
end
|
1950
1898
|
|
@@ -2223,6 +2171,8 @@ class Reline::LineEditor
|
|
2223
2171
|
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
2224
2172
|
@line_index = @buffer_of_lines.size - 1
|
2225
2173
|
@line = @buffer_of_lines.last
|
2174
|
+
@byte_pointer = @line.bytesize
|
2175
|
+
@cursor = @cursor_max = calculate_width(@line)
|
2226
2176
|
@rerender_all = true
|
2227
2177
|
@searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
|
2228
2178
|
else
|
@@ -2648,7 +2598,7 @@ class Reline::LineEditor
|
|
2648
2598
|
alias_method :kill_whole_line, :em_kill_line
|
2649
2599
|
|
2650
2600
|
private def em_delete(key)
|
2651
|
-
if
|
2601
|
+
if @line.empty? and (not @is_multiline or @buffer_of_lines.size == 1) and key == "\C-d".ord
|
2652
2602
|
@line = nil
|
2653
2603
|
if @buffer_of_lines.size > 1
|
2654
2604
|
scroll_down(@highest_in_all - @first_line_started_from)
|
@@ -3342,4 +3292,7 @@ class Reline::LineEditor
|
|
3342
3292
|
@mark_pointer = new_pointer
|
3343
3293
|
end
|
3344
3294
|
alias_method :exchange_point_and_mark, :em_exchange_mark
|
3295
|
+
|
3296
|
+
private def em_meta_next(key)
|
3297
|
+
end
|
3345
3298
|
end
|