reline 0.3.0 → 0.3.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|