reline 0.5.7 → 0.5.9
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 +1 -1
- data/lib/reline/config.rb +30 -27
- data/lib/reline/{ansi.rb → io/ansi.rb} +88 -86
- data/lib/reline/io/dumb.rb +106 -0
- data/lib/reline/{windows.rb → io/windows.rb} +102 -102
- data/lib/reline/io.rb +41 -0
- data/lib/reline/key_actor/base.rb +22 -6
- data/lib/reline/key_actor/composite.rb +17 -0
- data/lib/reline/key_actor/emacs.rb +3 -3
- data/lib/reline/key_actor/vi_command.rb +2 -2
- data/lib/reline/key_actor/vi_insert.rb +2 -2
- data/lib/reline/key_actor.rb +1 -0
- data/lib/reline/key_stroke.rb +65 -104
- data/lib/reline/line_editor.rb +61 -45
- data/lib/reline/terminfo.rb +5 -0
- data/lib/reline/version.rb +1 -1
- data/lib/reline.rb +35 -123
- metadata +7 -5
- data/lib/reline/general_io.rb +0 -111
data/lib/reline/key_stroke.rb
CHANGED
@@ -7,138 +7,99 @@ class Reline::KeyStroke
|
|
7
7
|
@config = config
|
8
8
|
end
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
result
|
19
|
-
}
|
20
|
-
end
|
10
|
+
# Input exactly matches to a key sequence
|
11
|
+
MATCHING = :matching
|
12
|
+
# Input partially matches to a key sequence
|
13
|
+
MATCHED = :matched
|
14
|
+
# Input matches to a key sequence and the key sequence is a prefix of another key sequence
|
15
|
+
MATCHING_MATCHED = :matching_matched
|
16
|
+
# Input does not match to any key sequence
|
17
|
+
UNMATCHED = :unmatched
|
21
18
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
-
i = 0
|
26
|
-
loop do
|
27
|
-
my_c = compressed_me[i]
|
28
|
-
other_c = compressed_other[i]
|
29
|
-
other_is_last = (i + 1) == compressed_other.size
|
30
|
-
me_is_last = (i + 1) == compressed_me.size
|
31
|
-
if my_c != other_c
|
32
|
-
if other_c == "\e".ord and other_is_last and my_c.is_a?(Reline::Key) and my_c.with_meta
|
33
|
-
return true
|
34
|
-
else
|
35
|
-
return false
|
36
|
-
end
|
37
|
-
elsif other_is_last
|
38
|
-
return true
|
39
|
-
elsif me_is_last
|
40
|
-
return false
|
41
|
-
end
|
42
|
-
i += 1
|
43
|
-
end
|
44
|
-
end
|
19
|
+
def match_status(input)
|
20
|
+
matching = key_mapping.matching?(input)
|
21
|
+
matched = key_mapping.get(input)
|
45
22
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
compressed_me = compress_meta_key(me)
|
50
|
-
compressed_other = compress_meta_key(other)
|
51
|
-
compressed_me.size == compressed_other.size and [compressed_me, compressed_other].transpose.all?{ |i| equal?(i[0], i[1]) }
|
52
|
-
when Integer
|
53
|
-
if other.is_a?(Reline::Key)
|
54
|
-
if other.combined_char == "\e".ord
|
55
|
-
false
|
56
|
-
else
|
57
|
-
other.combined_char == me
|
58
|
-
end
|
59
|
-
else
|
60
|
-
me == other
|
61
|
-
end
|
62
|
-
when Reline::Key
|
63
|
-
if other.is_a?(Integer)
|
64
|
-
me.combined_char == other
|
65
|
-
else
|
66
|
-
me == other
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
23
|
+
# FIXME: Workaround for single byte. remove this after MAPPING is merged into KeyActor.
|
24
|
+
matched ||= input.size == 1
|
25
|
+
matching ||= input == [ESC_BYTE]
|
70
26
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
:matched
|
27
|
+
if matching && matched
|
28
|
+
MATCHING_MATCHED
|
29
|
+
elsif matching
|
30
|
+
MATCHING
|
31
|
+
elsif matched
|
32
|
+
MATCHED
|
33
|
+
elsif input[0] == ESC_BYTE
|
34
|
+
match_unknown_escape_sequence(input, vi_mode: @config.editing_mode_is?(:vi_insert, :vi_command))
|
35
|
+
elsif input.size == 1
|
36
|
+
MATCHED
|
82
37
|
else
|
83
|
-
|
38
|
+
UNMATCHED
|
84
39
|
end
|
85
40
|
end
|
86
41
|
|
87
42
|
def expand(input)
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
return [:ed_unassigned] + expand(input.drop(size))
|
94
|
-
when :matching
|
95
|
-
return [:ed_unassigned]
|
96
|
-
else
|
97
|
-
return input
|
98
|
-
end
|
43
|
+
matched_bytes = nil
|
44
|
+
(1..input.size).each do |i|
|
45
|
+
bytes = input.take(i)
|
46
|
+
status = match_status(bytes)
|
47
|
+
matched_bytes = bytes if status == MATCHED || status == MATCHING_MATCHED
|
99
48
|
end
|
100
|
-
|
49
|
+
return [[], []] unless matched_bytes
|
101
50
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
51
|
+
func = key_mapping.get(matched_bytes)
|
52
|
+
if func.is_a?(Array)
|
53
|
+
keys = func.map { |c| Reline::Key.new(c, c, false) }
|
54
|
+
elsif func
|
55
|
+
keys = [Reline::Key.new(func, func, false)]
|
56
|
+
elsif matched_bytes.size == 1
|
57
|
+
keys = [Reline::Key.new(matched_bytes.first, matched_bytes.first, false)]
|
58
|
+
elsif matched_bytes.size == 2 && matched_bytes[0] == ESC_BYTE
|
59
|
+
keys = [Reline::Key.new(matched_bytes[1], matched_bytes[1] | 0b10000000, true)]
|
60
|
+
else
|
61
|
+
keys = []
|
110
62
|
end
|
63
|
+
|
64
|
+
[keys, input.drop(matched_bytes.size)]
|
111
65
|
end
|
112
66
|
|
113
67
|
private
|
114
68
|
|
115
69
|
# returns match status of CSI/SS3 sequence and matched length
|
116
|
-
def match_unknown_escape_sequence(input)
|
70
|
+
def match_unknown_escape_sequence(input, vi_mode: false)
|
117
71
|
idx = 0
|
118
|
-
return
|
72
|
+
return UNMATCHED unless input[idx] == ESC_BYTE
|
119
73
|
idx += 1
|
120
74
|
idx += 1 if input[idx] == ESC_BYTE
|
121
75
|
|
122
76
|
case input[idx]
|
123
77
|
when nil
|
124
|
-
|
78
|
+
if idx == 1 # `ESC`
|
79
|
+
return MATCHING_MATCHED
|
80
|
+
else # `ESC ESC`
|
81
|
+
return MATCHING
|
82
|
+
end
|
125
83
|
when 91 # == '['.ord
|
126
|
-
# CSI sequence
|
84
|
+
# CSI sequence `ESC [ ... char`
|
127
85
|
idx += 1
|
128
86
|
idx += 1 while idx < input.size && CSI_PARAMETER_BYTES_RANGE.cover?(input[idx])
|
129
87
|
idx += 1 while idx < input.size && CSI_INTERMEDIATE_BYTES_RANGE.cover?(input[idx])
|
130
|
-
input[idx] ? [:matched, idx + 1] : [:matching, nil]
|
131
88
|
when 79 # == 'O'.ord
|
132
|
-
# SS3 sequence
|
133
|
-
|
89
|
+
# SS3 sequence `ESC O char`
|
90
|
+
idx += 1
|
134
91
|
else
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
92
|
+
# `ESC char` or `ESC ESC char`
|
93
|
+
return UNMATCHED if vi_mode
|
94
|
+
end
|
95
|
+
|
96
|
+
case input.size
|
97
|
+
when idx
|
98
|
+
MATCHING
|
99
|
+
when idx + 1
|
100
|
+
MATCHED
|
101
|
+
else
|
102
|
+
UNMATCHED
|
142
103
|
end
|
143
104
|
end
|
144
105
|
|
data/lib/reline/line_editor.rb
CHANGED
@@ -45,6 +45,7 @@ class Reline::LineEditor
|
|
45
45
|
RenderedScreen = Struct.new(:base_y, :lines, :cursor_y, keyword_init: true)
|
46
46
|
|
47
47
|
CompletionJourneyState = Struct.new(:line_index, :pre, :target, :post, :list, :pointer)
|
48
|
+
NullActionState = [nil, nil].freeze
|
48
49
|
|
49
50
|
class MenuInfo
|
50
51
|
attr_reader :list
|
@@ -237,7 +238,6 @@ class Reline::LineEditor
|
|
237
238
|
@perfect_matched = nil
|
238
239
|
@menu_info = nil
|
239
240
|
@searching_prompt = nil
|
240
|
-
@first_char = true
|
241
241
|
@just_cursor_moving = false
|
242
242
|
@eof = false
|
243
243
|
@continuous_insertion_buffer = String.new(encoding: @encoding)
|
@@ -250,8 +250,11 @@ class Reline::LineEditor
|
|
250
250
|
@resized = false
|
251
251
|
@cache = {}
|
252
252
|
@rendered_screen = RenderedScreen.new(base_y: 0, lines: [], cursor_y: 0)
|
253
|
-
@
|
253
|
+
@input_lines = [[[""], 0, 0]]
|
254
|
+
@input_lines_position = 0
|
254
255
|
@undoing = false
|
256
|
+
@prev_action_state = NullActionState
|
257
|
+
@next_action_state = NullActionState
|
255
258
|
reset_line
|
256
259
|
end
|
257
260
|
|
@@ -411,7 +414,7 @@ class Reline::LineEditor
|
|
411
414
|
# do nothing
|
412
415
|
elsif level == :blank
|
413
416
|
Reline::IOGate.move_cursor_column base_x
|
414
|
-
@output.write "#{Reline::IOGate
|
417
|
+
@output.write "#{Reline::IOGate.reset_color_sequence}#{' ' * width}"
|
415
418
|
else
|
416
419
|
x, w, content = new_items[level]
|
417
420
|
cover_begin = base_x != 0 && new_levels[base_x - 1] == level
|
@@ -421,7 +424,7 @@ class Reline::LineEditor
|
|
421
424
|
content, pos = Reline::Unicode.take_mbchar_range(content, base_x - x, width, cover_begin: cover_begin, cover_end: cover_end, padding: true)
|
422
425
|
end
|
423
426
|
Reline::IOGate.move_cursor_column x + pos
|
424
|
-
@output.write "#{Reline::IOGate
|
427
|
+
@output.write "#{Reline::IOGate.reset_color_sequence}#{content}#{Reline::IOGate.reset_color_sequence}"
|
425
428
|
end
|
426
429
|
base_x += width
|
427
430
|
end
|
@@ -683,10 +686,8 @@ class Reline::LineEditor
|
|
683
686
|
@trap_key.each do |t|
|
684
687
|
@config.add_oneshot_key_binding(t, @name)
|
685
688
|
end
|
686
|
-
|
689
|
+
else
|
687
690
|
@config.add_oneshot_key_binding(@trap_key, @name)
|
688
|
-
elsif @trap_key.is_a?(Integer) or @trap_key.is_a?(Reline::Key)
|
689
|
-
@config.add_oneshot_key_binding([@trap_key], @name)
|
690
691
|
end
|
691
692
|
end
|
692
693
|
dialog_render_info
|
@@ -1079,17 +1080,7 @@ class Reline::LineEditor
|
|
1079
1080
|
else # single byte
|
1080
1081
|
return if key.char >= 128 # maybe, first byte of multi byte
|
1081
1082
|
method_symbol = @config.editing_mode.get_method(key.combined_char)
|
1082
|
-
|
1083
|
-
if @config.editing_mode_is?(:vi_command, :vi_insert)
|
1084
|
-
# split ESC + key in vi mode
|
1085
|
-
method_symbol = @config.editing_mode.get_method("\e".ord)
|
1086
|
-
process_key("\e".ord, method_symbol)
|
1087
|
-
method_symbol = @config.editing_mode.get_method(key.char)
|
1088
|
-
process_key(key.char, method_symbol)
|
1089
|
-
end
|
1090
|
-
else
|
1091
|
-
process_key(key.combined_char, method_symbol)
|
1092
|
-
end
|
1083
|
+
process_key(key.combined_char, method_symbol)
|
1093
1084
|
@multibyte_buffer.clear
|
1094
1085
|
end
|
1095
1086
|
if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
|
@@ -1118,13 +1109,10 @@ class Reline::LineEditor
|
|
1118
1109
|
end
|
1119
1110
|
if key.char.nil?
|
1120
1111
|
process_insert(force: true)
|
1121
|
-
|
1122
|
-
@eof = true
|
1123
|
-
end
|
1112
|
+
@eof = buffer_empty?
|
1124
1113
|
finish
|
1125
1114
|
return
|
1126
1115
|
end
|
1127
|
-
@first_char = false
|
1128
1116
|
@completion_occurs = false
|
1129
1117
|
|
1130
1118
|
if key.char.is_a?(Symbol)
|
@@ -1132,12 +1120,15 @@ class Reline::LineEditor
|
|
1132
1120
|
else
|
1133
1121
|
normal_char(key)
|
1134
1122
|
end
|
1123
|
+
|
1124
|
+
@prev_action_state, @next_action_state = @next_action_state, NullActionState
|
1125
|
+
|
1135
1126
|
unless @completion_occurs
|
1136
1127
|
@completion_state = CompletionState::NORMAL
|
1137
1128
|
@completion_journey_state = nil
|
1138
1129
|
end
|
1139
1130
|
|
1140
|
-
|
1131
|
+
push_input_lines unless @undoing
|
1141
1132
|
@undoing = false
|
1142
1133
|
|
1143
1134
|
if @in_pasting
|
@@ -1156,21 +1147,24 @@ class Reline::LineEditor
|
|
1156
1147
|
|
1157
1148
|
def save_old_buffer
|
1158
1149
|
@old_buffer_of_lines = @buffer_of_lines.dup
|
1159
|
-
@old_byte_pointer = @byte_pointer.dup
|
1160
|
-
@old_line_index = @line_index.dup
|
1161
1150
|
end
|
1162
1151
|
|
1163
|
-
def
|
1164
|
-
if @old_buffer_of_lines
|
1165
|
-
@
|
1152
|
+
def push_input_lines
|
1153
|
+
if @old_buffer_of_lines == @buffer_of_lines
|
1154
|
+
@input_lines[@input_lines_position] = [@buffer_of_lines.dup, @byte_pointer, @line_index]
|
1155
|
+
else
|
1156
|
+
@input_lines = @input_lines[0..@input_lines_position]
|
1157
|
+
@input_lines_position += 1
|
1158
|
+
@input_lines.push([@buffer_of_lines.dup, @byte_pointer, @line_index])
|
1166
1159
|
end
|
1167
|
-
|
1160
|
+
trim_input_lines
|
1168
1161
|
end
|
1169
1162
|
|
1170
|
-
|
1171
|
-
def
|
1172
|
-
if @
|
1173
|
-
@
|
1163
|
+
MAX_INPUT_LINES = 100
|
1164
|
+
def trim_input_lines
|
1165
|
+
if @input_lines.size > MAX_INPUT_LINES
|
1166
|
+
@input_lines.shift
|
1167
|
+
@input_lines_position -= 1
|
1174
1168
|
end
|
1175
1169
|
end
|
1176
1170
|
|
@@ -1352,7 +1346,7 @@ class Reline::LineEditor
|
|
1352
1346
|
@buffer_of_lines[@line_index, 1] = lines
|
1353
1347
|
@line_index += lines.size - 1
|
1354
1348
|
@byte_pointer = @buffer_of_lines[@line_index].bytesize - post.bytesize
|
1355
|
-
|
1349
|
+
push_input_lines
|
1356
1350
|
end
|
1357
1351
|
|
1358
1352
|
def insert_text(text)
|
@@ -1411,6 +1405,10 @@ class Reline::LineEditor
|
|
1411
1405
|
whole_lines.join("\n")
|
1412
1406
|
end
|
1413
1407
|
|
1408
|
+
private def buffer_empty?
|
1409
|
+
current_line.empty? and @buffer_of_lines.size == 1
|
1410
|
+
end
|
1411
|
+
|
1414
1412
|
def finished?
|
1415
1413
|
@finished
|
1416
1414
|
end
|
@@ -1759,29 +1757,31 @@ class Reline::LineEditor
|
|
1759
1757
|
end
|
1760
1758
|
|
1761
1759
|
private def ed_search_prev_history(key, arg: 1)
|
1762
|
-
substr = current_line.byteslice(0, @byte_pointer)
|
1760
|
+
substr = prev_action_state_value(:search_history) == :empty ? '' : current_line.byteslice(0, @byte_pointer)
|
1763
1761
|
return if @history_pointer == 0
|
1764
1762
|
return if @history_pointer.nil? && substr.empty? && !current_line.empty?
|
1765
1763
|
|
1766
1764
|
history_range = 0...(@history_pointer || Reline::HISTORY.size)
|
1767
1765
|
h_pointer, line_index = search_history(substr, history_range.reverse_each)
|
1768
1766
|
return unless h_pointer
|
1769
|
-
move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer)
|
1767
|
+
move_history(h_pointer, line: line_index || :start, cursor: substr.empty? ? :end : @byte_pointer)
|
1770
1768
|
arg -= 1
|
1769
|
+
set_next_action_state(:search_history, :empty) if substr.empty?
|
1771
1770
|
ed_search_prev_history(key, arg: arg) if arg > 0
|
1772
1771
|
end
|
1773
1772
|
alias_method :history_search_backward, :ed_search_prev_history
|
1774
1773
|
|
1775
1774
|
private def ed_search_next_history(key, arg: 1)
|
1776
|
-
substr = current_line.byteslice(0, @byte_pointer)
|
1775
|
+
substr = prev_action_state_value(:search_history) == :empty ? '' : current_line.byteslice(0, @byte_pointer)
|
1777
1776
|
return if @history_pointer.nil?
|
1778
1777
|
|
1779
1778
|
history_range = @history_pointer + 1...Reline::HISTORY.size
|
1780
1779
|
h_pointer, line_index = search_history(substr, history_range)
|
1781
1780
|
return if h_pointer.nil? and not substr.empty?
|
1782
1781
|
|
1783
|
-
move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer)
|
1782
|
+
move_history(h_pointer, line: line_index || :start, cursor: substr.empty? ? :end : @byte_pointer)
|
1784
1783
|
arg -= 1
|
1784
|
+
set_next_action_state(:search_history, :empty) if substr.empty?
|
1785
1785
|
ed_search_next_history(key, arg: arg) if arg > 0
|
1786
1786
|
end
|
1787
1787
|
alias_method :history_search_forward, :ed_search_next_history
|
@@ -1937,7 +1937,7 @@ class Reline::LineEditor
|
|
1937
1937
|
alias_method :kill_whole_line, :em_kill_line
|
1938
1938
|
|
1939
1939
|
private def em_delete(key)
|
1940
|
-
if
|
1940
|
+
if buffer_empty? and key == "\C-d".ord
|
1941
1941
|
@eof = true
|
1942
1942
|
finish
|
1943
1943
|
elsif @byte_pointer < current_line.bytesize
|
@@ -2285,8 +2285,7 @@ class Reline::LineEditor
|
|
2285
2285
|
end
|
2286
2286
|
|
2287
2287
|
private def vi_list_or_eof(key)
|
2288
|
-
if
|
2289
|
-
set_current_line('', 0)
|
2288
|
+
if buffer_empty?
|
2290
2289
|
@eof = true
|
2291
2290
|
finish
|
2292
2291
|
else
|
@@ -2529,13 +2528,30 @@ class Reline::LineEditor
|
|
2529
2528
|
end
|
2530
2529
|
|
2531
2530
|
private def undo(_key)
|
2532
|
-
|
2531
|
+
@undoing = true
|
2532
|
+
|
2533
|
+
return if @input_lines_position <= 0
|
2534
|
+
|
2535
|
+
@input_lines_position -= 1
|
2536
|
+
target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position]
|
2537
|
+
set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y)
|
2538
|
+
end
|
2533
2539
|
|
2540
|
+
private def redo(_key)
|
2534
2541
|
@undoing = true
|
2535
2542
|
|
2536
|
-
|
2537
|
-
|
2543
|
+
return if @input_lines_position >= @input_lines.size - 1
|
2544
|
+
|
2545
|
+
@input_lines_position += 1
|
2546
|
+
target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position]
|
2547
|
+
set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y)
|
2548
|
+
end
|
2549
|
+
|
2550
|
+
private def prev_action_state_value(type)
|
2551
|
+
@prev_action_state[0] == type ? @prev_action_state[1] : nil
|
2552
|
+
end
|
2538
2553
|
|
2539
|
-
|
2554
|
+
private def set_next_action_state(type, value)
|
2555
|
+
@next_action_state = [type, value]
|
2540
2556
|
end
|
2541
2557
|
end
|
data/lib/reline/terminfo.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
begin
|
2
|
+
# Ignore warning `Add fiddle to your Gemfile or gemspec` in Ruby 3.4.
|
3
|
+
# terminfo.rb and ansi.rb supports fiddle unavailable environment.
|
4
|
+
verbose, $VERBOSE = $VERBOSE, nil
|
2
5
|
require 'fiddle'
|
3
6
|
require 'fiddle/import'
|
4
7
|
rescue LoadError
|
@@ -7,6 +10,8 @@ rescue LoadError
|
|
7
10
|
false
|
8
11
|
end
|
9
12
|
end
|
13
|
+
ensure
|
14
|
+
$VERBOSE = verbose
|
10
15
|
end
|
11
16
|
|
12
17
|
module Reline::Terminfo
|
data/lib/reline/version.rb
CHANGED