reline 0.3.2 → 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.
@@ -0,0 +1,364 @@
1
+ require 'io/console'
2
+ require 'io/wait'
3
+
4
+ class Reline::ANSI < Reline::IO
5
+ CAPNAME_KEY_BINDINGS = {
6
+ 'khome' => :ed_move_to_beg,
7
+ 'kend' => :ed_move_to_end,
8
+ 'kdch1' => :key_delete,
9
+ 'kpp' => :ed_search_prev_history,
10
+ 'knp' => :ed_search_next_history,
11
+ 'kcuu1' => :ed_prev_history,
12
+ 'kcud1' => :ed_next_history,
13
+ 'kcuf1' => :ed_next_char,
14
+ 'kcub1' => :ed_prev_char,
15
+ }
16
+
17
+ ANSI_CURSOR_KEY_BINDINGS = {
18
+ # Up
19
+ 'A' => [:ed_prev_history, {}],
20
+ # Down
21
+ 'B' => [:ed_next_history, {}],
22
+ # Right
23
+ 'C' => [:ed_next_char, { ctrl: :em_next_word, meta: :em_next_word }],
24
+ # Left
25
+ 'D' => [:ed_prev_char, { ctrl: :ed_prev_word, meta: :ed_prev_word }],
26
+ # End
27
+ 'F' => [:ed_move_to_end, {}],
28
+ # Home
29
+ 'H' => [:ed_move_to_beg, {}],
30
+ }
31
+
32
+ if Reline::Terminfo.enabled?
33
+ Reline::Terminfo.setupterm(0, 2)
34
+ end
35
+
36
+ def initialize
37
+ @input = STDIN
38
+ @output = STDOUT
39
+ @buf = []
40
+ @old_winch_handler = nil
41
+ end
42
+
43
+ def encoding
44
+ Encoding.default_external
45
+ end
46
+
47
+ def set_default_key_bindings(config, allow_terminfo: true)
48
+ set_bracketed_paste_key_bindings(config)
49
+ set_default_key_bindings_ansi_cursor(config)
50
+ if allow_terminfo && Reline::Terminfo.enabled?
51
+ set_default_key_bindings_terminfo(config)
52
+ else
53
+ set_default_key_bindings_comprehensive_list(config)
54
+ end
55
+ {
56
+ [27, 91, 90] => :completion_journey_up, # S-Tab
57
+ }.each_pair do |key, func|
58
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
59
+ config.add_default_key_binding_by_keymap(:vi_insert, key, func)
60
+ end
61
+ {
62
+ # default bindings
63
+ [27, 32] => :em_set_mark, # M-<space>
64
+ [24, 24] => :em_exchange_mark, # C-x C-x
65
+ }.each_pair do |key, func|
66
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
67
+ end
68
+ end
69
+
70
+ def set_bracketed_paste_key_bindings(config)
71
+ [:emacs, :vi_insert, :vi_command].each do |keymap|
72
+ config.add_default_key_binding_by_keymap(keymap, START_BRACKETED_PASTE.bytes, :bracketed_paste_start)
73
+ end
74
+ end
75
+
76
+ def set_default_key_bindings_ansi_cursor(config)
77
+ ANSI_CURSOR_KEY_BINDINGS.each do |char, (default_func, modifiers)|
78
+ bindings = [["\e[#{char}", default_func]] # CSI + char
79
+ if modifiers[:ctrl]
80
+ # CSI + ctrl_key_modifier + char
81
+ bindings << ["\e[1;5#{char}", modifiers[:ctrl]]
82
+ end
83
+ if modifiers[:meta]
84
+ # CSI + meta_key_modifier + char
85
+ bindings << ["\e[1;3#{char}", modifiers[:meta]]
86
+ # Meta(ESC) + CSI + char
87
+ bindings << ["\e\e[#{char}", modifiers[:meta]]
88
+ end
89
+ bindings.each do |sequence, func|
90
+ key = sequence.bytes
91
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
92
+ config.add_default_key_binding_by_keymap(:vi_insert, key, func)
93
+ config.add_default_key_binding_by_keymap(:vi_command, key, func)
94
+ end
95
+ end
96
+ end
97
+
98
+ def set_default_key_bindings_terminfo(config)
99
+ key_bindings = CAPNAME_KEY_BINDINGS.map do |capname, key_binding|
100
+ begin
101
+ key_code = Reline::Terminfo.tigetstr(capname)
102
+ [ key_code.bytes, key_binding ]
103
+ rescue Reline::Terminfo::TerminfoError
104
+ # capname is undefined
105
+ end
106
+ end.compact.to_h
107
+
108
+ key_bindings.each_pair do |key, func|
109
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
110
+ config.add_default_key_binding_by_keymap(:vi_insert, key, func)
111
+ config.add_default_key_binding_by_keymap(:vi_command, key, func)
112
+ end
113
+ end
114
+
115
+ def set_default_key_bindings_comprehensive_list(config)
116
+ {
117
+ # xterm
118
+ [27, 91, 51, 126] => :key_delete, # kdch1
119
+ [27, 91, 53, 126] => :ed_search_prev_history, # kpp
120
+ [27, 91, 54, 126] => :ed_search_next_history, # knp
121
+
122
+ # Console (80x25)
123
+ [27, 91, 49, 126] => :ed_move_to_beg, # Home
124
+ [27, 91, 52, 126] => :ed_move_to_end, # End
125
+
126
+ # KDE
127
+ # Del is 0x08
128
+ [27, 71, 65] => :ed_prev_history, # ↑
129
+ [27, 71, 66] => :ed_next_history, # ↓
130
+ [27, 71, 67] => :ed_next_char, # →
131
+ [27, 71, 68] => :ed_prev_char, # ←
132
+
133
+ # urxvt / exoterm
134
+ [27, 91, 55, 126] => :ed_move_to_beg, # Home
135
+ [27, 91, 56, 126] => :ed_move_to_end, # End
136
+
137
+ # GNOME
138
+ [27, 79, 72] => :ed_move_to_beg, # Home
139
+ [27, 79, 70] => :ed_move_to_end, # End
140
+ # Del is 0x08
141
+ # Arrow keys are the same of KDE
142
+
143
+ [27, 79, 65] => :ed_prev_history, # ↑
144
+ [27, 79, 66] => :ed_next_history, # ↓
145
+ [27, 79, 67] => :ed_next_char, # →
146
+ [27, 79, 68] => :ed_prev_char, # ←
147
+ }.each_pair do |key, func|
148
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
149
+ config.add_default_key_binding_by_keymap(:vi_insert, key, func)
150
+ config.add_default_key_binding_by_keymap(:vi_command, key, func)
151
+ end
152
+ end
153
+
154
+ def input=(val)
155
+ @input = val
156
+ end
157
+
158
+ def output=(val)
159
+ @output = val
160
+ end
161
+
162
+ def with_raw_input
163
+ if @input.tty?
164
+ @input.raw(intr: true) { yield }
165
+ else
166
+ yield
167
+ end
168
+ end
169
+
170
+ def inner_getc(timeout_second)
171
+ unless @buf.empty?
172
+ return @buf.shift
173
+ end
174
+ until @input.wait_readable(0.01)
175
+ timeout_second -= 0.01
176
+ return nil if timeout_second <= 0
177
+
178
+ Reline.core.line_editor.handle_signal
179
+ end
180
+ c = @input.getbyte
181
+ (c == 0x16 && @input.tty? && @input.raw(min: 0, time: 0, &:getbyte)) || c
182
+ rescue Errno::EIO
183
+ # Maybe the I/O has been closed.
184
+ nil
185
+ end
186
+
187
+ START_BRACKETED_PASTE = String.new("\e[200~", encoding: Encoding::ASCII_8BIT)
188
+ END_BRACKETED_PASTE = String.new("\e[201~", encoding: Encoding::ASCII_8BIT)
189
+ def read_bracketed_paste
190
+ buffer = String.new(encoding: Encoding::ASCII_8BIT)
191
+ until buffer.end_with?(END_BRACKETED_PASTE)
192
+ c = inner_getc(Float::INFINITY)
193
+ break unless c
194
+ buffer << c
195
+ end
196
+ string = buffer.delete_suffix(END_BRACKETED_PASTE).force_encoding(encoding)
197
+ string.valid_encoding? ? string : ''
198
+ end
199
+
200
+ # if the usage expects to wait indefinitely, use Float::INFINITY for timeout_second
201
+ def getc(timeout_second)
202
+ inner_getc(timeout_second)
203
+ end
204
+
205
+ def in_pasting?
206
+ not empty_buffer?
207
+ end
208
+
209
+ def empty_buffer?
210
+ unless @buf.empty?
211
+ return false
212
+ end
213
+ !@input.wait_readable(0)
214
+ end
215
+
216
+ def ungetc(c)
217
+ @buf.unshift(c)
218
+ end
219
+
220
+ def retrieve_keybuffer
221
+ begin
222
+ return unless @input.wait_readable(0.001)
223
+ str = @input.read_nonblock(1024)
224
+ str.bytes.each do |c|
225
+ @buf.push(c)
226
+ end
227
+ rescue EOFError
228
+ end
229
+ end
230
+
231
+ def get_screen_size
232
+ s = @input.winsize
233
+ return s if s[0] > 0 && s[1] > 0
234
+ s = [ENV["LINES"].to_i, ENV["COLUMNS"].to_i]
235
+ return s if s[0] > 0 && s[1] > 0
236
+ [24, 80]
237
+ rescue Errno::ENOTTY, Errno::ENODEV
238
+ [24, 80]
239
+ end
240
+
241
+ def set_screen_size(rows, columns)
242
+ @input.winsize = [rows, columns]
243
+ self
244
+ rescue Errno::ENOTTY, Errno::ENODEV
245
+ self
246
+ end
247
+
248
+ def cursor_pos
249
+ if both_tty?
250
+ res = +''
251
+ m = nil
252
+ @input.raw do |stdin|
253
+ @output << "\e[6n"
254
+ @output.flush
255
+ loop do
256
+ c = stdin.getc
257
+ next if c.nil?
258
+ res << c
259
+ m = res.match(/\e\[(?<row>\d+);(?<column>\d+)R/)
260
+ break if m
261
+ end
262
+ (m.pre_match + m.post_match).chars.reverse_each do |ch|
263
+ stdin.ungetc ch
264
+ end
265
+ end
266
+ column = m[:column].to_i - 1
267
+ row = m[:row].to_i - 1
268
+ else
269
+ begin
270
+ buf = @output.pread(@output.pos, 0)
271
+ row = buf.count("\n")
272
+ column = buf.rindex("\n") ? (buf.size - buf.rindex("\n")) - 1 : 0
273
+ rescue Errno::ESPIPE, IOError
274
+ # Just returns column 1 for ambiguous width because this I/O is not
275
+ # tty and can't seek.
276
+ row = 0
277
+ column = 1
278
+ end
279
+ end
280
+ Reline::CursorPos.new(column, row)
281
+ end
282
+
283
+ def both_tty?
284
+ @input.tty? && @output.tty?
285
+ end
286
+
287
+ def move_cursor_column(x)
288
+ @output.write "\e[#{x + 1}G"
289
+ end
290
+
291
+ def move_cursor_up(x)
292
+ if x > 0
293
+ @output.write "\e[#{x}A"
294
+ elsif x < 0
295
+ move_cursor_down(-x)
296
+ end
297
+ end
298
+
299
+ def move_cursor_down(x)
300
+ if x > 0
301
+ @output.write "\e[#{x}B"
302
+ elsif x < 0
303
+ move_cursor_up(-x)
304
+ end
305
+ end
306
+
307
+ def hide_cursor
308
+ seq = "\e[?25l"
309
+ if Reline::Terminfo.enabled? && Reline::Terminfo.term_supported?
310
+ begin
311
+ seq = Reline::Terminfo.tigetstr('civis')
312
+ rescue Reline::Terminfo::TerminfoError
313
+ # civis is undefined
314
+ end
315
+ end
316
+ @output.write seq
317
+ end
318
+
319
+ def show_cursor
320
+ seq = "\e[?25h"
321
+ if Reline::Terminfo.enabled? && Reline::Terminfo.term_supported?
322
+ begin
323
+ seq = Reline::Terminfo.tigetstr('cnorm')
324
+ rescue Reline::Terminfo::TerminfoError
325
+ # cnorm is undefined
326
+ end
327
+ end
328
+ @output.write seq
329
+ end
330
+
331
+ def erase_after_cursor
332
+ @output.write "\e[K"
333
+ end
334
+
335
+ # This only works when the cursor is at the bottom of the scroll range
336
+ # For more details, see https://github.com/ruby/reline/pull/577#issuecomment-1646679623
337
+ def scroll_down(x)
338
+ return if x.zero?
339
+ # We use `\n` instead of CSI + S because CSI + S would cause https://github.com/ruby/reline/issues/576
340
+ @output.write "\n" * x
341
+ end
342
+
343
+ def clear_screen
344
+ @output.write "\e[2J"
345
+ @output.write "\e[1;1H"
346
+ end
347
+
348
+ def set_winch_handler(&handler)
349
+ @old_winch_handler = Signal.trap('WINCH', &handler)
350
+ end
351
+
352
+ def prep
353
+ # Enable bracketed paste
354
+ @output.write "\e[?2004h" if Reline.core.config.enable_bracketed_paste && both_tty?
355
+ retrieve_keybuffer
356
+ nil
357
+ end
358
+
359
+ def deprep(otio)
360
+ # Disable bracketed paste
361
+ @output.write "\e[?2004l" if Reline.core.config.enable_bracketed_paste && both_tty?
362
+ Signal.trap('WINCH', @old_winch_handler) if @old_winch_handler
363
+ end
364
+ end
@@ -0,0 +1,106 @@
1
+ require 'io/wait'
2
+
3
+ class Reline::Dumb < Reline::IO
4
+ RESET_COLOR = '' # Do not send color reset sequence
5
+
6
+ def initialize(encoding: nil)
7
+ @input = STDIN
8
+ @buf = []
9
+ @pasting = false
10
+ @encoding = encoding
11
+ @screen_size = [24, 80]
12
+ end
13
+
14
+ def dumb?
15
+ true
16
+ end
17
+
18
+ def encoding
19
+ if @encoding
20
+ @encoding
21
+ elsif RUBY_PLATFORM =~ /mswin|mingw/
22
+ Encoding::UTF_8
23
+ else
24
+ Encoding::default_external
25
+ end
26
+ end
27
+
28
+ def set_default_key_bindings(_)
29
+ end
30
+
31
+ def input=(val)
32
+ @input = val
33
+ end
34
+
35
+ def with_raw_input
36
+ yield
37
+ end
38
+
39
+ def getc(_timeout_second)
40
+ unless @buf.empty?
41
+ return @buf.shift
42
+ end
43
+ c = nil
44
+ loop do
45
+ Reline.core.line_editor.handle_signal
46
+ result = @input.wait_readable(0.1)
47
+ next if result.nil?
48
+ c = @input.read(1)
49
+ break
50
+ end
51
+ c&.ord
52
+ end
53
+
54
+ def ungetc(c)
55
+ @buf.unshift(c)
56
+ end
57
+
58
+ def get_screen_size
59
+ @screen_size
60
+ end
61
+
62
+ def cursor_pos
63
+ Reline::CursorPos.new(1, 1)
64
+ end
65
+
66
+ def hide_cursor
67
+ end
68
+
69
+ def show_cursor
70
+ end
71
+
72
+ def move_cursor_column(val)
73
+ end
74
+
75
+ def move_cursor_up(val)
76
+ end
77
+
78
+ def move_cursor_down(val)
79
+ end
80
+
81
+ def erase_after_cursor
82
+ end
83
+
84
+ def scroll_down(val)
85
+ end
86
+
87
+ def clear_screen
88
+ end
89
+
90
+ def set_screen_size(rows, columns)
91
+ @screen_size = [rows, columns]
92
+ end
93
+
94
+ def set_winch_handler(&handler)
95
+ end
96
+
97
+ def in_pasting?
98
+ @pasting
99
+ end
100
+
101
+ def prep
102
+ end
103
+
104
+ def deprep(otio)
105
+ end
106
+ end