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.
@@ -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