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