reline 0.3.9 → 0.6.1
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 +8 -1
- data/lib/reline/config.rb +95 -123
- data/lib/reline/face.rb +199 -0
- data/lib/reline/history.rb +4 -4
- data/lib/reline/io/ansi.rb +318 -0
- data/lib/reline/io/dumb.rb +120 -0
- data/lib/reline/{windows.rb → io/windows.rb} +182 -153
- data/lib/reline/io.rb +55 -0
- data/lib/reline/key_actor/base.rb +27 -9
- data/lib/reline/key_actor/composite.rb +17 -0
- data/lib/reline/key_actor/emacs.rb +103 -103
- data/lib/reline/key_actor/vi_command.rb +188 -188
- data/lib/reline/key_actor/vi_insert.rb +144 -144
- data/lib/reline/key_actor.rb +1 -0
- data/lib/reline/key_stroke.rb +75 -104
- data/lib/reline/kill_ring.rb +2 -2
- data/lib/reline/line_editor.rb +1175 -2121
- data/lib/reline/unicode/east_asian_width.rb +1289 -1192
- data/lib/reline/unicode.rb +218 -445
- data/lib/reline/version.rb +1 -1
- data/lib/reline.rb +143 -224
- metadata +13 -11
- data/lib/reline/ansi.rb +0 -363
- data/lib/reline/general_io.rb +0 -116
- data/lib/reline/terminfo.rb +0 -160
@@ -0,0 +1,318 @@
|
|
1
|
+
require 'io/console'
|
2
|
+
require 'io/wait'
|
3
|
+
|
4
|
+
class Reline::ANSI < Reline::IO
|
5
|
+
ANSI_CURSOR_KEY_BINDINGS = {
|
6
|
+
# Up
|
7
|
+
'A' => [:ed_prev_history, {}],
|
8
|
+
# Down
|
9
|
+
'B' => [:ed_next_history, {}],
|
10
|
+
# Right
|
11
|
+
'C' => [:ed_next_char, { ctrl: :em_next_word, meta: :em_next_word }],
|
12
|
+
# Left
|
13
|
+
'D' => [:ed_prev_char, { ctrl: :ed_prev_word, meta: :ed_prev_word }],
|
14
|
+
# End
|
15
|
+
'F' => [:ed_move_to_end, {}],
|
16
|
+
# Home
|
17
|
+
'H' => [:ed_move_to_beg, {}],
|
18
|
+
}
|
19
|
+
|
20
|
+
attr_writer :input, :output
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@input = STDIN
|
24
|
+
@output = STDOUT
|
25
|
+
@buf = []
|
26
|
+
@output_buffer = nil
|
27
|
+
@old_winch_handler = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def encoding
|
31
|
+
@input.external_encoding || Encoding.default_external
|
32
|
+
rescue IOError
|
33
|
+
# STDIN.external_encoding raises IOError in Ruby <= 3.0 when STDIN is closed
|
34
|
+
Encoding.default_external
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_default_key_bindings(config)
|
38
|
+
set_bracketed_paste_key_bindings(config)
|
39
|
+
set_default_key_bindings_ansi_cursor(config)
|
40
|
+
set_default_key_bindings_comprehensive_list(config)
|
41
|
+
{
|
42
|
+
[27, 91, 90] => :completion_journey_up, # S-Tab
|
43
|
+
}.each_pair do |key, func|
|
44
|
+
config.add_default_key_binding_by_keymap(:emacs, key, func)
|
45
|
+
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
|
46
|
+
end
|
47
|
+
{
|
48
|
+
# default bindings
|
49
|
+
[27, 32] => :em_set_mark, # M-<space>
|
50
|
+
[24, 24] => :em_exchange_mark, # C-x C-x
|
51
|
+
}.each_pair do |key, func|
|
52
|
+
config.add_default_key_binding_by_keymap(:emacs, key, func)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def set_bracketed_paste_key_bindings(config)
|
57
|
+
[:emacs, :vi_insert, :vi_command].each do |keymap|
|
58
|
+
config.add_default_key_binding_by_keymap(keymap, START_BRACKETED_PASTE.bytes, :bracketed_paste_start)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def set_default_key_bindings_ansi_cursor(config)
|
63
|
+
ANSI_CURSOR_KEY_BINDINGS.each do |char, (default_func, modifiers)|
|
64
|
+
bindings = [
|
65
|
+
["\e[#{char}", default_func], # CSI + char
|
66
|
+
["\eO#{char}", default_func] # SS3 + char, application cursor key mode
|
67
|
+
]
|
68
|
+
if modifiers[:ctrl]
|
69
|
+
# CSI + ctrl_key_modifier + char
|
70
|
+
bindings << ["\e[1;5#{char}", modifiers[:ctrl]]
|
71
|
+
end
|
72
|
+
if modifiers[:meta]
|
73
|
+
# CSI + meta_key_modifier + char
|
74
|
+
bindings << ["\e[1;3#{char}", modifiers[:meta]]
|
75
|
+
# Meta(ESC) + CSI + char
|
76
|
+
bindings << ["\e\e[#{char}", modifiers[:meta]]
|
77
|
+
end
|
78
|
+
bindings.each do |sequence, func|
|
79
|
+
key = sequence.bytes
|
80
|
+
config.add_default_key_binding_by_keymap(:emacs, key, func)
|
81
|
+
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
|
82
|
+
config.add_default_key_binding_by_keymap(:vi_command, key, func)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def set_default_key_bindings_comprehensive_list(config)
|
88
|
+
{
|
89
|
+
# xterm
|
90
|
+
[27, 91, 51, 126] => :key_delete, # kdch1
|
91
|
+
[27, 91, 53, 126] => :ed_search_prev_history, # kpp
|
92
|
+
[27, 91, 54, 126] => :ed_search_next_history, # knp
|
93
|
+
|
94
|
+
# Console (80x25)
|
95
|
+
[27, 91, 49, 126] => :ed_move_to_beg, # Home
|
96
|
+
[27, 91, 52, 126] => :ed_move_to_end, # End
|
97
|
+
|
98
|
+
# urxvt / exoterm
|
99
|
+
[27, 91, 55, 126] => :ed_move_to_beg, # Home
|
100
|
+
[27, 91, 56, 126] => :ed_move_to_end, # End
|
101
|
+
}.each_pair do |key, func|
|
102
|
+
config.add_default_key_binding_by_keymap(:emacs, key, func)
|
103
|
+
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
|
104
|
+
config.add_default_key_binding_by_keymap(:vi_command, key, func)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def with_raw_input
|
109
|
+
if @input.tty?
|
110
|
+
@input.raw(intr: true) { yield }
|
111
|
+
else
|
112
|
+
yield
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def inner_getc(timeout_second)
|
117
|
+
unless @buf.empty?
|
118
|
+
return @buf.shift
|
119
|
+
end
|
120
|
+
until @input.wait_readable(0.01)
|
121
|
+
timeout_second -= 0.01
|
122
|
+
return nil if timeout_second <= 0
|
123
|
+
|
124
|
+
Reline.core.line_editor.handle_signal
|
125
|
+
end
|
126
|
+
c = @input.getbyte
|
127
|
+
(c == 0x16 && @input.tty? && @input.raw(min: 0, time: 0, &:getbyte)) || c
|
128
|
+
rescue Errno::EIO
|
129
|
+
# Maybe the I/O has been closed.
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
|
133
|
+
START_BRACKETED_PASTE = String.new("\e[200~", encoding: Encoding::ASCII_8BIT)
|
134
|
+
END_BRACKETED_PASTE = String.new("\e[201~", encoding: Encoding::ASCII_8BIT)
|
135
|
+
def read_bracketed_paste
|
136
|
+
buffer = String.new(encoding: Encoding::ASCII_8BIT)
|
137
|
+
until buffer.end_with?(END_BRACKETED_PASTE)
|
138
|
+
c = inner_getc(Float::INFINITY)
|
139
|
+
break unless c
|
140
|
+
buffer << c
|
141
|
+
end
|
142
|
+
string = buffer.delete_suffix(END_BRACKETED_PASTE).force_encoding(encoding)
|
143
|
+
string.valid_encoding? ? string : ''
|
144
|
+
end
|
145
|
+
|
146
|
+
# if the usage expects to wait indefinitely, use Float::INFINITY for timeout_second
|
147
|
+
def getc(timeout_second)
|
148
|
+
inner_getc(timeout_second)
|
149
|
+
end
|
150
|
+
|
151
|
+
def in_pasting?
|
152
|
+
not empty_buffer?
|
153
|
+
end
|
154
|
+
|
155
|
+
def empty_buffer?
|
156
|
+
unless @buf.empty?
|
157
|
+
return false
|
158
|
+
end
|
159
|
+
!@input.wait_readable(0)
|
160
|
+
end
|
161
|
+
|
162
|
+
def ungetc(c)
|
163
|
+
@buf.unshift(c)
|
164
|
+
end
|
165
|
+
|
166
|
+
def retrieve_keybuffer
|
167
|
+
begin
|
168
|
+
return unless @input.wait_readable(0.001)
|
169
|
+
str = @input.read_nonblock(1024)
|
170
|
+
str.bytes.each do |c|
|
171
|
+
@buf.push(c)
|
172
|
+
end
|
173
|
+
rescue EOFError
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def get_screen_size
|
178
|
+
s = @input.winsize
|
179
|
+
return s if s[0] > 0 && s[1] > 0
|
180
|
+
s = [ENV["LINES"].to_i, ENV["COLUMNS"].to_i]
|
181
|
+
return s if s[0] > 0 && s[1] > 0
|
182
|
+
[24, 80]
|
183
|
+
rescue Errno::ENOTTY, Errno::ENODEV
|
184
|
+
[24, 80]
|
185
|
+
end
|
186
|
+
|
187
|
+
def set_screen_size(rows, columns)
|
188
|
+
@input.winsize = [rows, columns]
|
189
|
+
self
|
190
|
+
rescue Errno::ENOTTY, Errno::ENODEV
|
191
|
+
self
|
192
|
+
end
|
193
|
+
|
194
|
+
private def cursor_pos_internal(timeout:)
|
195
|
+
match = nil
|
196
|
+
@input.raw do |stdin|
|
197
|
+
@output << "\e[6n"
|
198
|
+
@output.flush
|
199
|
+
timeout_at = Time.now + timeout
|
200
|
+
buf = +''
|
201
|
+
while (wait = timeout_at - Time.now) > 0 && stdin.wait_readable(wait)
|
202
|
+
buf << stdin.readpartial(1024)
|
203
|
+
if (match = buf.match(/\e\[(?<row>\d+);(?<column>\d+)R/))
|
204
|
+
buf = match.pre_match + match.post_match
|
205
|
+
break
|
206
|
+
end
|
207
|
+
end
|
208
|
+
@buf.concat buf.bytes
|
209
|
+
end
|
210
|
+
[match[:column].to_i - 1, match[:row].to_i - 1] if match
|
211
|
+
end
|
212
|
+
|
213
|
+
def cursor_pos
|
214
|
+
col, row = cursor_pos_internal(timeout: 0.5) if both_tty?
|
215
|
+
Reline::CursorPos.new(col || 0, row || 0)
|
216
|
+
end
|
217
|
+
|
218
|
+
def both_tty?
|
219
|
+
@input.tty? && @output.tty?
|
220
|
+
end
|
221
|
+
|
222
|
+
def write(string)
|
223
|
+
if @output_buffer
|
224
|
+
@output_buffer << string
|
225
|
+
else
|
226
|
+
@output.write(string)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def buffered_output
|
231
|
+
@output_buffer = +''
|
232
|
+
yield
|
233
|
+
@output.write(@output_buffer)
|
234
|
+
ensure
|
235
|
+
@output_buffer = nil
|
236
|
+
end
|
237
|
+
|
238
|
+
def move_cursor_column(x)
|
239
|
+
write "\e[#{x + 1}G"
|
240
|
+
end
|
241
|
+
|
242
|
+
def move_cursor_up(x)
|
243
|
+
if x > 0
|
244
|
+
write "\e[#{x}A"
|
245
|
+
elsif x < 0
|
246
|
+
move_cursor_down(-x)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def move_cursor_down(x)
|
251
|
+
if x > 0
|
252
|
+
write "\e[#{x}B"
|
253
|
+
elsif x < 0
|
254
|
+
move_cursor_up(-x)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def hide_cursor
|
259
|
+
write "\e[?25l"
|
260
|
+
end
|
261
|
+
|
262
|
+
def show_cursor
|
263
|
+
write "\e[?25h"
|
264
|
+
end
|
265
|
+
|
266
|
+
def erase_after_cursor
|
267
|
+
write "\e[K"
|
268
|
+
end
|
269
|
+
|
270
|
+
# This only works when the cursor is at the bottom of the scroll range
|
271
|
+
# For more details, see https://github.com/ruby/reline/pull/577#issuecomment-1646679623
|
272
|
+
def scroll_down(x)
|
273
|
+
return if x.zero?
|
274
|
+
# We use `\n` instead of CSI + S because CSI + S would cause https://github.com/ruby/reline/issues/576
|
275
|
+
write "\n" * x
|
276
|
+
end
|
277
|
+
|
278
|
+
def clear_screen
|
279
|
+
write "\e[2J"
|
280
|
+
write "\e[1;1H"
|
281
|
+
end
|
282
|
+
|
283
|
+
def set_winch_handler(&handler)
|
284
|
+
@old_winch_handler = Signal.trap('WINCH') do |arg|
|
285
|
+
handler.call
|
286
|
+
@old_winch_handler.call(arg) if @old_winch_handler.respond_to?(:call)
|
287
|
+
end
|
288
|
+
@old_cont_handler = Signal.trap('CONT') do |arg|
|
289
|
+
@input.raw!(intr: true) if @input.tty?
|
290
|
+
# Rerender the screen. Note that screen size might be changed while suspended.
|
291
|
+
handler.call
|
292
|
+
@old_cont_handler.call(arg) if @old_cont_handler.respond_to?(:call)
|
293
|
+
end
|
294
|
+
rescue ArgumentError
|
295
|
+
# Signal.trap may raise an ArgumentError if the platform doesn't support the signal.
|
296
|
+
end
|
297
|
+
|
298
|
+
def read_single_char(keyseq_timeout)
|
299
|
+
# Disable intr to read `C-c` `C-z` `C-\` for quoted insert
|
300
|
+
@input.raw(intr: false) do
|
301
|
+
super
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def prep
|
306
|
+
# Enable bracketed paste
|
307
|
+
write "\e[?2004h" if Reline.core.config.enable_bracketed_paste && both_tty?
|
308
|
+
retrieve_keybuffer
|
309
|
+
nil
|
310
|
+
end
|
311
|
+
|
312
|
+
def deprep(otio)
|
313
|
+
# Disable bracketed paste
|
314
|
+
write "\e[?2004l" if Reline.core.config.enable_bracketed_paste && both_tty?
|
315
|
+
Signal.trap('WINCH', @old_winch_handler) if @old_winch_handler
|
316
|
+
Signal.trap('CONT', @old_cont_handler) if @old_cont_handler
|
317
|
+
end
|
318
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'io/wait'
|
2
|
+
|
3
|
+
class Reline::Dumb < Reline::IO
|
4
|
+
RESET_COLOR = '' # Do not send color reset sequence
|
5
|
+
|
6
|
+
attr_writer :output
|
7
|
+
|
8
|
+
def initialize(encoding: nil)
|
9
|
+
@input = STDIN
|
10
|
+
@output = STDOUT
|
11
|
+
@buf = []
|
12
|
+
@pasting = false
|
13
|
+
@encoding = encoding
|
14
|
+
@screen_size = [24, 80]
|
15
|
+
end
|
16
|
+
|
17
|
+
def dumb?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def encoding
|
22
|
+
if @encoding
|
23
|
+
@encoding
|
24
|
+
elsif RUBY_PLATFORM =~ /mswin|mingw/
|
25
|
+
Encoding::UTF_8
|
26
|
+
else
|
27
|
+
@input.external_encoding || Encoding.default_external
|
28
|
+
end
|
29
|
+
rescue IOError
|
30
|
+
# STDIN.external_encoding raises IOError in Ruby <= 3.0 when STDIN is closed
|
31
|
+
Encoding.default_external
|
32
|
+
end
|
33
|
+
|
34
|
+
def set_default_key_bindings(_)
|
35
|
+
end
|
36
|
+
|
37
|
+
def input=(val)
|
38
|
+
@input = val
|
39
|
+
end
|
40
|
+
|
41
|
+
def with_raw_input
|
42
|
+
yield
|
43
|
+
end
|
44
|
+
|
45
|
+
def write(string)
|
46
|
+
@output.write(string)
|
47
|
+
end
|
48
|
+
|
49
|
+
def buffered_output
|
50
|
+
yield
|
51
|
+
end
|
52
|
+
|
53
|
+
def getc(_timeout_second)
|
54
|
+
unless @buf.empty?
|
55
|
+
return @buf.shift
|
56
|
+
end
|
57
|
+
c = nil
|
58
|
+
loop do
|
59
|
+
Reline.core.line_editor.handle_signal
|
60
|
+
result = @input.wait_readable(0.1)
|
61
|
+
next if result.nil?
|
62
|
+
c = @input.read(1)
|
63
|
+
break
|
64
|
+
end
|
65
|
+
c&.ord
|
66
|
+
end
|
67
|
+
|
68
|
+
def ungetc(c)
|
69
|
+
@buf.unshift(c)
|
70
|
+
end
|
71
|
+
|
72
|
+
def get_screen_size
|
73
|
+
@screen_size
|
74
|
+
end
|
75
|
+
|
76
|
+
def cursor_pos
|
77
|
+
Reline::CursorPos.new(0, 0)
|
78
|
+
end
|
79
|
+
|
80
|
+
def hide_cursor
|
81
|
+
end
|
82
|
+
|
83
|
+
def show_cursor
|
84
|
+
end
|
85
|
+
|
86
|
+
def move_cursor_column(val)
|
87
|
+
end
|
88
|
+
|
89
|
+
def move_cursor_up(val)
|
90
|
+
end
|
91
|
+
|
92
|
+
def move_cursor_down(val)
|
93
|
+
end
|
94
|
+
|
95
|
+
def erase_after_cursor
|
96
|
+
end
|
97
|
+
|
98
|
+
def scroll_down(val)
|
99
|
+
end
|
100
|
+
|
101
|
+
def clear_screen
|
102
|
+
end
|
103
|
+
|
104
|
+
def set_screen_size(rows, columns)
|
105
|
+
@screen_size = [rows, columns]
|
106
|
+
end
|
107
|
+
|
108
|
+
def set_winch_handler(&handler)
|
109
|
+
end
|
110
|
+
|
111
|
+
def in_pasting?
|
112
|
+
@pasting
|
113
|
+
end
|
114
|
+
|
115
|
+
def prep
|
116
|
+
end
|
117
|
+
|
118
|
+
def deprep(otio)
|
119
|
+
end
|
120
|
+
end
|