reline 0.2.7 → 0.3.0
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/lib/reline/ansi.rb +64 -22
- data/lib/reline/config.rb +34 -3
- data/lib/reline/general_io.rb +3 -1
- data/lib/reline/key_actor/emacs.rb +1 -1
- data/lib/reline/key_stroke.rb +64 -14
- data/lib/reline/line_editor.rb +630 -72
- data/lib/reline/terminfo.rb +12 -4
- data/lib/reline/unicode.rb +42 -3
- data/lib/reline/version.rb +1 -1
- data/lib/reline/windows.rb +151 -49
- data/lib/reline.rb +127 -22
- metadata +2 -2
data/lib/reline/terminfo.rb
CHANGED
@@ -1,5 +1,13 @@
|
|
1
|
-
|
2
|
-
require 'fiddle
|
1
|
+
begin
|
2
|
+
require 'fiddle'
|
3
|
+
require 'fiddle/import'
|
4
|
+
rescue LoadError
|
5
|
+
module Reline::Terminfo
|
6
|
+
def self.curses_dl
|
7
|
+
false
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
3
11
|
|
4
12
|
module Reline::Terminfo
|
5
13
|
extend Fiddle::Importer
|
@@ -50,7 +58,7 @@ module Reline::Terminfo
|
|
50
58
|
@curses_dl = nil if @curses_dl == false
|
51
59
|
@curses_dl
|
52
60
|
end
|
53
|
-
end
|
61
|
+
end if not Reline.const_defined?(:Terminfo) or not Reline::Terminfo.respond_to?(:curses_dl)
|
54
62
|
|
55
63
|
module Reline::Terminfo
|
56
64
|
dlload curses_dl
|
@@ -71,7 +79,7 @@ module Reline::Terminfo
|
|
71
79
|
def self.setupterm(term, fildes)
|
72
80
|
errret_int = String.new("\x00" * 8, encoding: 'ASCII-8BIT')
|
73
81
|
ret = @setupterm.(term, fildes, errret_int)
|
74
|
-
errret = errret_int.
|
82
|
+
errret = errret_int.unpack1('i')
|
75
83
|
case ret
|
76
84
|
when 0 # OK
|
77
85
|
0
|
data/lib/reline/unicode.rb
CHANGED
@@ -79,6 +79,8 @@ class Reline::Unicode
|
|
79
79
|
|
80
80
|
require 'reline/unicode/east_asian_width'
|
81
81
|
|
82
|
+
HalfwidthDakutenHandakuten = /[\u{FF9E}\u{FF9F}]/
|
83
|
+
|
82
84
|
MBCharWidthRE = /
|
83
85
|
(?<width_2_1>
|
84
86
|
[#{ EscapedChars.map {|c| "\\x%02x" % c.ord }.join }] (?# ^ + char, such as ^M, ^H, ^[, ...)
|
@@ -93,6 +95,12 @@ class Reline::Unicode
|
|
93
95
|
#{ EastAsianWidth::TYPE_H }
|
94
96
|
| #{ EastAsianWidth::TYPE_NA }
|
95
97
|
| #{ EastAsianWidth::TYPE_N }
|
98
|
+
)(?!#{ HalfwidthDakutenHandakuten })
|
99
|
+
| (?<width_2_3>
|
100
|
+
(?: #{ EastAsianWidth::TYPE_H }
|
101
|
+
| #{ EastAsianWidth::TYPE_NA }
|
102
|
+
| #{ EastAsianWidth::TYPE_N })
|
103
|
+
#{ HalfwidthDakutenHandakuten }
|
96
104
|
)
|
97
105
|
| (?<ambiguous_width>
|
98
106
|
#{EastAsianWidth::TYPE_A}
|
@@ -101,15 +109,15 @@ class Reline::Unicode
|
|
101
109
|
|
102
110
|
def self.get_mbchar_width(mbchar)
|
103
111
|
ord = mbchar.ord
|
104
|
-
if (0x00 <= ord and ord <= 0x1F)
|
112
|
+
if (0x00 <= ord and ord <= 0x1F) # in EscapedPairs
|
105
113
|
return 2
|
106
|
-
elsif (0x20 <= ord and ord <= 0x7E)
|
114
|
+
elsif (0x20 <= ord and ord <= 0x7E) # printable ASCII chars
|
107
115
|
return 1
|
108
116
|
end
|
109
117
|
m = mbchar.encode(Encoding::UTF_8).match(MBCharWidthRE)
|
110
118
|
case
|
111
119
|
when m.nil? then 1 # TODO should be U+FFFD � REPLACEMENT CHARACTER
|
112
|
-
when m[:width_2_1], m[:width_2_2] then 2
|
120
|
+
when m[:width_2_1], m[:width_2_2], m[:width_2_3] then 2
|
113
121
|
when m[:width_3] then 3
|
114
122
|
when m[:width_0] then 0
|
115
123
|
when m[:width_1] then 1
|
@@ -185,6 +193,37 @@ class Reline::Unicode
|
|
185
193
|
[lines, height]
|
186
194
|
end
|
187
195
|
|
196
|
+
# Take a chunk of a String cut by width with escape sequences.
|
197
|
+
def self.take_range(str, start_col, max_width, encoding = str.encoding)
|
198
|
+
chunk = String.new(encoding: encoding)
|
199
|
+
total_width = 0
|
200
|
+
rest = str.encode(Encoding::UTF_8)
|
201
|
+
in_zero_width = false
|
202
|
+
rest.scan(WIDTH_SCANNER) do |gc|
|
203
|
+
case
|
204
|
+
when gc[NON_PRINTING_START_INDEX]
|
205
|
+
in_zero_width = true
|
206
|
+
when gc[NON_PRINTING_END_INDEX]
|
207
|
+
in_zero_width = false
|
208
|
+
when gc[CSI_REGEXP_INDEX]
|
209
|
+
chunk << gc[CSI_REGEXP_INDEX]
|
210
|
+
when gc[OSC_REGEXP_INDEX]
|
211
|
+
chunk << gc[OSC_REGEXP_INDEX]
|
212
|
+
when gc[GRAPHEME_CLUSTER_INDEX]
|
213
|
+
gc = gc[GRAPHEME_CLUSTER_INDEX]
|
214
|
+
if in_zero_width
|
215
|
+
chunk << gc
|
216
|
+
else
|
217
|
+
mbchar_width = get_mbchar_width(gc)
|
218
|
+
total_width += mbchar_width
|
219
|
+
break if (start_col + max_width) < total_width
|
220
|
+
chunk << gc if start_col < total_width
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
chunk
|
225
|
+
end
|
226
|
+
|
188
227
|
def self.get_next_mbchar_size(line, byte_pointer)
|
189
228
|
grapheme = line.byteslice(byte_pointer..-1).grapheme_clusters.first
|
190
229
|
grapheme ? grapheme.bytesize : 0
|
data/lib/reline/version.rb
CHANGED
data/lib/reline/windows.rb
CHANGED
@@ -42,6 +42,14 @@ class Reline::Windows
|
|
42
42
|
}.each_pair do |key, func|
|
43
43
|
config.add_default_key_binding_by_keymap(:emacs, key, func)
|
44
44
|
end
|
45
|
+
|
46
|
+
# Emulate ANSI key sequence.
|
47
|
+
{
|
48
|
+
[27, 91, 90] => :completion_journey_up, # S-Tab
|
49
|
+
}.each_pair do |key, func|
|
50
|
+
config.add_default_key_binding_by_keymap(:emacs, key, func)
|
51
|
+
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
|
52
|
+
end
|
45
53
|
end
|
46
54
|
|
47
55
|
if defined? JRUBY_VERSION
|
@@ -106,6 +114,7 @@ class Reline::Windows
|
|
106
114
|
SCROLLLOCK_ON = 0x0040
|
107
115
|
SHIFT_PRESSED = 0x0010
|
108
116
|
|
117
|
+
VK_TAB = 0x09
|
109
118
|
VK_END = 0x23
|
110
119
|
VK_HOME = 0x24
|
111
120
|
VK_LEFT = 0x25
|
@@ -133,9 +142,11 @@ class Reline::Windows
|
|
133
142
|
@@GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L')
|
134
143
|
@@GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I')
|
135
144
|
@@FillConsoleOutputAttribute = Win32API.new('kernel32', 'FillConsoleOutputAttribute', ['L', 'L', 'L', 'L', 'P'], 'L')
|
145
|
+
@@SetConsoleCursorInfo = Win32API.new('kernel32', 'SetConsoleCursorInfo', ['L', 'P'], 'L')
|
136
146
|
|
137
147
|
@@GetConsoleMode = Win32API.new('kernel32', 'GetConsoleMode', ['L', 'P'], 'L')
|
138
148
|
@@SetConsoleMode = Win32API.new('kernel32', 'SetConsoleMode', ['L', 'L'], 'L')
|
149
|
+
@@WaitForSingleObject = Win32API.new('kernel32', 'WaitForSingleObject', ['L', 'L'], 'L')
|
139
150
|
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
|
140
151
|
|
141
152
|
private_class_method def self.getconsolemode
|
@@ -157,7 +168,9 @@ class Reline::Windows
|
|
157
168
|
@@input_buf = []
|
158
169
|
@@output_buf = []
|
159
170
|
|
160
|
-
|
171
|
+
@@output = STDOUT
|
172
|
+
|
173
|
+
def self.msys_tty?(io = @@hConsoleInputHandle)
|
161
174
|
# check if fd is a pipe
|
162
175
|
if @@GetFileType.call(io) != FILE_TYPE_PIPE
|
163
176
|
return false
|
@@ -173,7 +186,7 @@ class Reline::Windows
|
|
173
186
|
# DWORD FileNameLength;
|
174
187
|
# WCHAR FileName[1];
|
175
188
|
# } FILE_NAME_INFO
|
176
|
-
len = p_buffer[0, 4].
|
189
|
+
len = p_buffer[0, 4].unpack1("L")
|
177
190
|
name = p_buffer[4, len].encode(Encoding::UTF_8, Encoding::UTF_16LE, invalid: :replace)
|
178
191
|
|
179
192
|
# Check if this could be a MSYS2 pty pipe ('\msys-XXXX-ptyN-XX')
|
@@ -197,10 +210,34 @@ class Reline::Windows
|
|
197
210
|
[ { control_keys: [], virtual_key_code: VK_DELETE }, [0, 83] ],
|
198
211
|
[ { control_keys: [], virtual_key_code: VK_HOME }, [0, 71] ],
|
199
212
|
[ { control_keys: [], virtual_key_code: VK_END }, [0, 79] ],
|
213
|
+
|
214
|
+
# Emulate ANSI key sequence.
|
215
|
+
[ { control_keys: :SHIFT, virtual_key_code: VK_TAB }, [27, 91, 90] ],
|
200
216
|
]
|
201
217
|
|
218
|
+
@@hsg = nil
|
219
|
+
|
202
220
|
def self.process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
|
203
221
|
|
222
|
+
# high-surrogate
|
223
|
+
if 0xD800 <= char_code and char_code <= 0xDBFF
|
224
|
+
@@hsg = char_code
|
225
|
+
return
|
226
|
+
end
|
227
|
+
# low-surrogate
|
228
|
+
if 0xDC00 <= char_code and char_code <= 0xDFFF
|
229
|
+
if @@hsg
|
230
|
+
char_code = 0x10000 + (@@hsg - 0xD800) * 0x400 + char_code - 0xDC00
|
231
|
+
@@hsg = nil
|
232
|
+
else
|
233
|
+
# no high-surrogate. ignored.
|
234
|
+
return
|
235
|
+
end
|
236
|
+
else
|
237
|
+
# ignore high-surrogate without low-surrogate if there
|
238
|
+
@@hsg = nil
|
239
|
+
end
|
240
|
+
|
204
241
|
key = KeyEventRecord.new(virtual_key_code, char_code, control_key_state)
|
205
242
|
|
206
243
|
match = KEY_MAP.find { |args,| key.matches?(**args) }
|
@@ -212,30 +249,42 @@ class Reline::Windows
|
|
212
249
|
# no char, only control keys
|
213
250
|
return if key.char_code == 0 and key.control_keys.any?
|
214
251
|
|
252
|
+
@@output_buf.push("\e".ord) if key.control_keys.include?(:ALT)
|
253
|
+
|
215
254
|
@@output_buf.concat(key.char.bytes)
|
216
255
|
end
|
217
256
|
|
218
257
|
def self.check_input_event
|
219
258
|
num_of_events = 0.chr * 8
|
220
|
-
while @@output_buf.empty?
|
221
|
-
|
222
|
-
|
259
|
+
while @@output_buf.empty?
|
260
|
+
Reline.core.line_editor.resize
|
261
|
+
if @@WaitForSingleObject.(@@hConsoleInputHandle, 100) != 0 # max 0.1 sec
|
262
|
+
# prevent for background consolemode change
|
263
|
+
@@legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0)
|
264
|
+
next
|
265
|
+
end
|
266
|
+
next if @@GetNumberOfConsoleInputEvents.(@@hConsoleInputHandle, num_of_events) == 0 or num_of_events.unpack1('L') == 0
|
267
|
+
input_records = 0.chr * 20 * 80
|
223
268
|
read_event = 0.chr * 4
|
224
|
-
if @@ReadConsoleInputW.(@@hConsoleInputHandle,
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
269
|
+
if @@ReadConsoleInputW.(@@hConsoleInputHandle, input_records, 80, read_event) != 0
|
270
|
+
read_events = read_event.unpack1('L')
|
271
|
+
0.upto(read_events) do |idx|
|
272
|
+
input_record = input_records[idx * 20, 20]
|
273
|
+
event = input_record[0, 2].unpack1('s*')
|
274
|
+
case event
|
275
|
+
when WINDOW_BUFFER_SIZE_EVENT
|
276
|
+
@@winch_handler.()
|
277
|
+
when KEY_EVENT
|
278
|
+
key_down = input_record[4, 4].unpack1('l*')
|
279
|
+
repeat_count = input_record[8, 2].unpack1('s*')
|
280
|
+
virtual_key_code = input_record[10, 2].unpack1('s*')
|
281
|
+
virtual_scan_code = input_record[12, 2].unpack1('s*')
|
282
|
+
char_code = input_record[14, 2].unpack1('S*')
|
283
|
+
control_key_state = input_record[16, 2].unpack1('S*')
|
284
|
+
is_key_down = key_down.zero? ? false : true
|
285
|
+
if is_key_down
|
286
|
+
process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
|
287
|
+
end
|
239
288
|
end
|
240
289
|
end
|
241
290
|
end
|
@@ -256,7 +305,7 @@ class Reline::Windows
|
|
256
305
|
end
|
257
306
|
|
258
307
|
def self.empty_buffer?
|
259
|
-
if not @@
|
308
|
+
if not @@output_buf.empty?
|
260
309
|
false
|
261
310
|
elsif @@kbhit.call == 0
|
262
311
|
true
|
@@ -265,17 +314,37 @@ class Reline::Windows
|
|
265
314
|
end
|
266
315
|
end
|
267
316
|
|
268
|
-
def self.
|
317
|
+
def self.get_console_screen_buffer_info
|
318
|
+
# CONSOLE_SCREEN_BUFFER_INFO
|
319
|
+
# [ 0,2] dwSize.X
|
320
|
+
# [ 2,2] dwSize.Y
|
321
|
+
# [ 4,2] dwCursorPositions.X
|
322
|
+
# [ 6,2] dwCursorPositions.Y
|
323
|
+
# [ 8,2] wAttributes
|
324
|
+
# [10,2] srWindow.Left
|
325
|
+
# [12,2] srWindow.Top
|
326
|
+
# [14,2] srWindow.Right
|
327
|
+
# [16,2] srWindow.Bottom
|
328
|
+
# [18,2] dwMaximumWindowSize.X
|
329
|
+
# [20,2] dwMaximumWindowSize.Y
|
269
330
|
csbi = 0.chr * 22
|
270
|
-
@@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi)
|
331
|
+
return if @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi) == 0
|
332
|
+
csbi
|
333
|
+
end
|
334
|
+
|
335
|
+
def self.get_screen_size
|
336
|
+
unless csbi = get_console_screen_buffer_info
|
337
|
+
return [1, 1]
|
338
|
+
end
|
271
339
|
csbi[0, 4].unpack('SS').reverse
|
272
340
|
end
|
273
341
|
|
274
342
|
def self.cursor_pos
|
275
|
-
csbi =
|
276
|
-
|
277
|
-
|
278
|
-
|
343
|
+
unless csbi = get_console_screen_buffer_info
|
344
|
+
return Reline::CursorPos.new(0, 0)
|
345
|
+
end
|
346
|
+
x = csbi[4, 2].unpack1('s')
|
347
|
+
y = csbi[6, 2].unpack1('s')
|
279
348
|
Reline::CursorPos.new(x, y)
|
280
349
|
end
|
281
350
|
|
@@ -295,6 +364,7 @@ class Reline::Windows
|
|
295
364
|
|
296
365
|
def self.move_cursor_down(val)
|
297
366
|
if val > 0
|
367
|
+
return unless csbi = get_console_screen_buffer_info
|
298
368
|
screen_height = get_screen_size.first
|
299
369
|
y = cursor_pos.y + val
|
300
370
|
y = screen_height - 1 if y > (screen_height - 1)
|
@@ -305,42 +375,74 @@ class Reline::Windows
|
|
305
375
|
end
|
306
376
|
|
307
377
|
def self.erase_after_cursor
|
308
|
-
csbi =
|
309
|
-
|
310
|
-
cursor = csbi[4, 4].
|
378
|
+
return unless csbi = get_console_screen_buffer_info
|
379
|
+
attributes = csbi[8, 2].unpack1('S')
|
380
|
+
cursor = csbi[4, 4].unpack1('L')
|
311
381
|
written = 0.chr * 4
|
312
382
|
@@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, get_screen_size.last - cursor_pos.x, cursor, written)
|
313
|
-
@@FillConsoleOutputAttribute.call(@@hConsoleHandle,
|
383
|
+
@@FillConsoleOutputAttribute.call(@@hConsoleHandle, attributes, get_screen_size.last - cursor_pos.x, cursor, written)
|
314
384
|
end
|
315
385
|
|
316
386
|
def self.scroll_down(val)
|
317
|
-
return if val
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
@@
|
387
|
+
return if val < 0
|
388
|
+
return unless csbi = get_console_screen_buffer_info
|
389
|
+
buffer_width, x, y, buffer_lines, attributes, window_left, window_top, window_bottom = csbi.unpack('ssssSssx2s')
|
390
|
+
screen_height = window_bottom - window_top + 1
|
391
|
+
val = screen_height if val > screen_height
|
392
|
+
|
393
|
+
if @@legacy_console || window_left != 0
|
394
|
+
# unless ENABLE_VIRTUAL_TERMINAL,
|
395
|
+
# if srWindow.Left != 0 then it's conhost.exe hosted console
|
396
|
+
# and puts "\n" causes horizontal scroll. its glitch.
|
397
|
+
# FYI irb write from culumn 1, so this gives no gain.
|
398
|
+
scroll_rectangle = [0, val, buffer_width, buffer_lines - val].pack('s4')
|
399
|
+
destination_origin = 0 # y * 65536 + x
|
400
|
+
fill = [' '.ord, attributes].pack('SS')
|
401
|
+
@@ScrollConsoleScreenBuffer.call(@@hConsoleHandle, scroll_rectangle, nil, destination_origin, fill)
|
402
|
+
else
|
403
|
+
origin_x = x + 1
|
404
|
+
origin_y = y - window_top + 1
|
405
|
+
@@output.write [
|
406
|
+
(origin_y != screen_height) ? "\e[#{screen_height};H" : nil,
|
407
|
+
"\n" * val,
|
408
|
+
(origin_y != screen_height or !x.zero?) ? "\e[#{origin_y};#{origin_x}H" : nil
|
409
|
+
].join
|
410
|
+
end
|
324
411
|
end
|
325
412
|
|
326
413
|
def self.clear_screen
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
414
|
+
if @@legacy_console
|
415
|
+
return unless csbi = get_console_screen_buffer_info
|
416
|
+
buffer_width, _buffer_lines, attributes, window_top, window_bottom = csbi.unpack('ss@8S@12sx2s')
|
417
|
+
fill_length = buffer_width * (window_bottom - window_top + 1)
|
418
|
+
screen_topleft = window_top * 65536
|
419
|
+
written = 0.chr * 4
|
420
|
+
@@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, fill_length, screen_topleft, written)
|
421
|
+
@@FillConsoleOutputAttribute.call(@@hConsoleHandle, attributes, fill_length, screen_topleft, written)
|
422
|
+
@@SetConsoleCursorPosition.call(@@hConsoleHandle, screen_topleft)
|
423
|
+
else
|
424
|
+
@@output.write "\e[2J" "\e[H"
|
425
|
+
end
|
338
426
|
end
|
339
427
|
|
340
428
|
def self.set_screen_size(rows, columns)
|
341
429
|
raise NotImplementedError
|
342
430
|
end
|
343
431
|
|
432
|
+
def self.hide_cursor
|
433
|
+
size = 100
|
434
|
+
visible = 0 # 0 means false
|
435
|
+
cursor_info = [size, visible].pack('Li')
|
436
|
+
@@SetConsoleCursorInfo.call(@@hConsoleHandle, cursor_info)
|
437
|
+
end
|
438
|
+
|
439
|
+
def self.show_cursor
|
440
|
+
size = 100
|
441
|
+
visible = 1 # 1 means true
|
442
|
+
cursor_info = [size, visible].pack('Li')
|
443
|
+
@@SetConsoleCursorInfo.call(@@hConsoleHandle, cursor_info)
|
444
|
+
end
|
445
|
+
|
344
446
|
def self.set_winch_handler(&handler)
|
345
447
|
@@winch_handler = handler
|
346
448
|
end
|
data/lib/reline.rb
CHANGED
@@ -16,8 +16,24 @@ module Reline
|
|
16
16
|
|
17
17
|
class ConfigEncodingConversionError < StandardError; end
|
18
18
|
|
19
|
-
Key = Struct.new('Key', :char, :combined_char, :with_meta)
|
19
|
+
Key = Struct.new('Key', :char, :combined_char, :with_meta) do
|
20
|
+
def match?(other)
|
21
|
+
case other
|
22
|
+
when Reline::Key
|
23
|
+
(other.char.nil? or char.nil? or char == other.char) and
|
24
|
+
(other.combined_char.nil? or combined_char.nil? or combined_char == other.combined_char) and
|
25
|
+
(other.with_meta.nil? or with_meta.nil? or with_meta == other.with_meta)
|
26
|
+
when Integer, Symbol
|
27
|
+
(combined_char and combined_char == other) or
|
28
|
+
(combined_char.nil? and char and char == other)
|
29
|
+
else
|
30
|
+
false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
alias_method :==, :match?
|
34
|
+
end
|
20
35
|
CursorPos = Struct.new(:x, :y)
|
36
|
+
DialogRenderInfo = Struct.new(:pos, :contents, :bg_color, :width, :height, :scrollbar, keyword_init: true)
|
21
37
|
|
22
38
|
class Core
|
23
39
|
ATTR_READER_NAMES = %i(
|
@@ -44,6 +60,7 @@ module Reline
|
|
44
60
|
|
45
61
|
def initialize
|
46
62
|
self.output = STDOUT
|
63
|
+
@dialog_proc_list = {}
|
47
64
|
yield self
|
48
65
|
@completion_quote_character = nil
|
49
66
|
@bracketed_paste_finished = false
|
@@ -106,6 +123,14 @@ module Reline
|
|
106
123
|
@completion_proc = p
|
107
124
|
end
|
108
125
|
|
126
|
+
def autocompletion
|
127
|
+
@config.autocompletion
|
128
|
+
end
|
129
|
+
|
130
|
+
def autocompletion=(val)
|
131
|
+
@config.autocompletion = val
|
132
|
+
end
|
133
|
+
|
109
134
|
def output_modifier_proc=(p)
|
110
135
|
raise ArgumentError unless p.respond_to?(:call) or p.nil?
|
111
136
|
@output_modifier_proc = p
|
@@ -130,6 +155,17 @@ module Reline
|
|
130
155
|
@dig_perfect_match_proc = p
|
131
156
|
end
|
132
157
|
|
158
|
+
DialogProc = Struct.new(:dialog_proc, :context)
|
159
|
+
def add_dialog_proc(name_sym, p, context = nil)
|
160
|
+
raise ArgumentError unless p.respond_to?(:call) or p.nil?
|
161
|
+
raise ArgumentError unless name_sym.instance_of?(Symbol)
|
162
|
+
@dialog_proc_list[name_sym] = DialogProc.new(p, context)
|
163
|
+
end
|
164
|
+
|
165
|
+
def dialog_proc(name_sym)
|
166
|
+
@dialog_proc_list[name_sym]
|
167
|
+
end
|
168
|
+
|
133
169
|
def input=(val)
|
134
170
|
raise TypeError unless val.respond_to?(:getc) or val.nil?
|
135
171
|
if val.respond_to?(:getc)
|
@@ -171,6 +207,46 @@ module Reline
|
|
171
207
|
Reline::IOGate.get_screen_size
|
172
208
|
end
|
173
209
|
|
210
|
+
Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE = ->() {
|
211
|
+
# autocomplete
|
212
|
+
return nil unless config.autocompletion
|
213
|
+
if just_cursor_moving and completion_journey_data.nil?
|
214
|
+
# Auto complete starts only when edited
|
215
|
+
return nil
|
216
|
+
end
|
217
|
+
pre, target, post = retrieve_completion_block(true)
|
218
|
+
if target.nil? or target.empty? or (completion_journey_data&.pointer == -1 and target.size <= 3)
|
219
|
+
return nil
|
220
|
+
end
|
221
|
+
if completion_journey_data and completion_journey_data.list
|
222
|
+
result = completion_journey_data.list.dup
|
223
|
+
result.shift
|
224
|
+
pointer = completion_journey_data.pointer - 1
|
225
|
+
else
|
226
|
+
result = call_completion_proc_with_checking_args(pre, target, post)
|
227
|
+
pointer = nil
|
228
|
+
end
|
229
|
+
if result and result.size == 1 and result[0] == target and pointer != 0
|
230
|
+
result = nil
|
231
|
+
end
|
232
|
+
target_width = Reline::Unicode.calculate_width(target)
|
233
|
+
x = cursor_pos.x - target_width
|
234
|
+
if x < 0
|
235
|
+
x = screen_width + x
|
236
|
+
y = -1
|
237
|
+
else
|
238
|
+
y = 0
|
239
|
+
end
|
240
|
+
cursor_pos_to_render = Reline::CursorPos.new(x, y)
|
241
|
+
if context and context.is_a?(Array)
|
242
|
+
context.clear
|
243
|
+
context.push(cursor_pos_to_render, result, pointer, dialog)
|
244
|
+
end
|
245
|
+
dialog.pointer = pointer
|
246
|
+
DialogRenderInfo.new(pos: cursor_pos_to_render, contents: result, scrollbar: true, height: 15)
|
247
|
+
}
|
248
|
+
Reline::DEFAULT_DIALOG_CONTEXT = Array.new
|
249
|
+
|
174
250
|
def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
|
175
251
|
unless confirm_multiline_termination
|
176
252
|
raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
|
@@ -230,6 +306,9 @@ module Reline
|
|
230
306
|
line_editor.auto_indent_proc = auto_indent_proc
|
231
307
|
line_editor.dig_perfect_match_proc = dig_perfect_match_proc
|
232
308
|
line_editor.pre_input_hook = pre_input_hook
|
309
|
+
@dialog_proc_list.each_pair do |name_sym, d|
|
310
|
+
line_editor.add_dialog_proc(name_sym, d.dialog_proc, d.context)
|
311
|
+
end
|
233
312
|
|
234
313
|
unless config.test_mode
|
235
314
|
config.read
|
@@ -240,6 +319,7 @@ module Reline
|
|
240
319
|
line_editor.rerender
|
241
320
|
|
242
321
|
begin
|
322
|
+
line_editor.set_signal_handlers
|
243
323
|
prev_pasting_state = false
|
244
324
|
loop do
|
245
325
|
prev_pasting_state = Reline::IOGate.in_pasting?
|
@@ -268,6 +348,11 @@ module Reline
|
|
268
348
|
line_editor.finalize
|
269
349
|
Reline::IOGate.deprep(otio)
|
270
350
|
raise e
|
351
|
+
rescue Exception
|
352
|
+
# Including Interrupt
|
353
|
+
line_editor.finalize
|
354
|
+
Reline::IOGate.deprep(otio)
|
355
|
+
raise
|
271
356
|
end
|
272
357
|
|
273
358
|
line_editor.finalize
|
@@ -303,25 +388,9 @@ module Reline
|
|
303
388
|
break
|
304
389
|
when :matching
|
305
390
|
if buffer.size == 1
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
succ_c = Reline::IOGate.getc
|
310
|
-
}
|
311
|
-
rescue Timeout::Error # cancel matching only when first byte
|
312
|
-
block.([Reline::Key.new(c, c, false)])
|
313
|
-
break
|
314
|
-
else
|
315
|
-
if key_stroke.match_status(buffer.dup.push(succ_c)) == :unmatched
|
316
|
-
if c == "\e".ord
|
317
|
-
block.([Reline::Key.new(succ_c, succ_c | 0b10000000, true)])
|
318
|
-
else
|
319
|
-
block.([Reline::Key.new(c, c, false), Reline::Key.new(succ_c, succ_c, false)])
|
320
|
-
end
|
321
|
-
break
|
322
|
-
else
|
323
|
-
Reline::IOGate.ungetc(succ_c)
|
324
|
-
end
|
391
|
+
case read_2nd_character_of_key_sequence(keyseq_timeout, buffer, c, block)
|
392
|
+
when :break then break
|
393
|
+
when :next then next
|
325
394
|
end
|
326
395
|
end
|
327
396
|
when :unmatched
|
@@ -338,6 +407,38 @@ module Reline
|
|
338
407
|
end
|
339
408
|
end
|
340
409
|
|
410
|
+
private def read_2nd_character_of_key_sequence(keyseq_timeout, buffer, c, block)
|
411
|
+
begin
|
412
|
+
succ_c = nil
|
413
|
+
Timeout.timeout(keyseq_timeout / 1000.0) {
|
414
|
+
succ_c = Reline::IOGate.getc
|
415
|
+
}
|
416
|
+
rescue Timeout::Error # cancel matching only when first byte
|
417
|
+
block.([Reline::Key.new(c, c, false)])
|
418
|
+
return :break
|
419
|
+
else
|
420
|
+
case key_stroke.match_status(buffer.dup.push(succ_c))
|
421
|
+
when :unmatched
|
422
|
+
if c == "\e".ord
|
423
|
+
block.([Reline::Key.new(succ_c, succ_c | 0b10000000, true)])
|
424
|
+
else
|
425
|
+
block.([Reline::Key.new(c, c, false), Reline::Key.new(succ_c, succ_c, false)])
|
426
|
+
end
|
427
|
+
return :break
|
428
|
+
when :matching
|
429
|
+
Reline::IOGate.ungetc(succ_c)
|
430
|
+
return :next
|
431
|
+
when :matched
|
432
|
+
buffer << succ_c
|
433
|
+
expanded = key_stroke.expand(buffer).map{ |expanded_c|
|
434
|
+
Reline::Key.new(expanded_c, expanded_c, false)
|
435
|
+
}
|
436
|
+
block.(expanded)
|
437
|
+
return :break
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
341
442
|
private def read_escaped_key(keyseq_timeout, c, block)
|
342
443
|
begin
|
343
444
|
escaped_c = nil
|
@@ -366,7 +467,7 @@ module Reline
|
|
366
467
|
|
367
468
|
private def may_req_ambiguous_char_width
|
368
469
|
@ambiguous_width = 2 if Reline::IOGate == Reline::GeneralIO or STDOUT.is_a?(File)
|
369
|
-
return if @ambiguous_width
|
470
|
+
return if defined? @ambiguous_width
|
370
471
|
Reline::IOGate.move_cursor_column(0)
|
371
472
|
begin
|
372
473
|
output.write "\u{25bd}"
|
@@ -389,7 +490,7 @@ module Reline
|
|
389
490
|
#--------------------------------------------------------
|
390
491
|
|
391
492
|
(Core::ATTR_READER_NAMES).each { |name|
|
392
|
-
def_single_delegators :core, "#{name}", "#{name}="
|
493
|
+
def_single_delegators :core, :"#{name}", :"#{name}="
|
393
494
|
}
|
394
495
|
def_single_delegators :core, :input=, :output=
|
395
496
|
def_single_delegators :core, :vi_editing_mode, :emacs_editing_mode
|
@@ -424,6 +525,9 @@ module Reline
|
|
424
525
|
def_single_delegators :core, :ambiguous_width
|
425
526
|
def_single_delegators :core, :last_incremental_search
|
426
527
|
def_single_delegators :core, :last_incremental_search=
|
528
|
+
def_single_delegators :core, :add_dialog_proc
|
529
|
+
def_single_delegators :core, :dialog_proc
|
530
|
+
def_single_delegators :core, :autocompletion, :autocompletion=
|
427
531
|
|
428
532
|
def_single_delegators :core, :readmultiline
|
429
533
|
def_instance_delegators self, :readmultiline
|
@@ -445,6 +549,7 @@ module Reline
|
|
445
549
|
core.completer_quote_characters = '"\''
|
446
550
|
core.filename_quote_characters = ""
|
447
551
|
core.special_prefixes = ""
|
552
|
+
core.add_dialog_proc(:autocomplete, Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE, Reline::DEFAULT_DIALOG_CONTEXT)
|
448
553
|
}
|
449
554
|
end
|
450
555
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reline
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- aycabta
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-12-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: io-console
|