reline 0.5.10 → 0.5.11
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/config.rb +22 -26
- data/lib/reline/history.rb +3 -3
- data/lib/reline/io/ansi.rb +25 -49
- data/lib/reline/io/dumb.rb +2 -2
- data/lib/reline/io/windows.rb +66 -59
- data/lib/reline/line_editor.rb +76 -88
- data/lib/reline/unicode.rb +21 -5
- data/lib/reline/version.rb +1 -1
- data/lib/reline.rb +4 -4
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d2e17ec5d6de83746c38c5e0764f7fa1ecda40f01c18b9e8cdf769e4bf2a155e
|
4
|
+
data.tar.gz: 7ce06466fae4c8115bb507141998d322595b9e924dfc1ad3001871265088a680
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 31e90ffea6ac235aad5c2c243fbd41163142f94e044ed9f4980860263fab97154857483755dcacd259fa9b578b1c29be5b878cd7f883f18e7b92380cb5865ef3
|
7
|
+
data.tar.gz: 54b91a893c536b3e6b5c984c0fc2b053b8978ab586ae361446fb603c4eecfb0240a99c45b25e4996ff58d9ef15fa4e33bab5cef9cb4aef3dc7eab85998af3711
|
data/lib/reline/config.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
class Reline::Config
|
2
2
|
attr_reader :test_mode
|
3
3
|
|
4
|
-
KEYSEQ_PATTERN = /\\(?:C|Control)-[A-Za-z_]|\\(?:M|Meta)-[0-9A-Za-z_]|\\(?:C|Control)
|
4
|
+
KEYSEQ_PATTERN = /\\(?:C|Control)-[A-Za-z_]|\\(?:M|Meta)-[0-9A-Za-z_]|\\(?:C|Control)-\\(?:M|Meta)-[A-Za-z_]|\\(?:M|Meta)-\\(?:C|Control)-[A-Za-z_]|\\e|\\[\\\"\'abdfnrtv]|\\\d{1,3}|\\x\h{1,2}|./
|
5
5
|
|
6
6
|
class InvalidInputrc < RuntimeError
|
7
7
|
attr_accessor :file, :lineno
|
@@ -194,13 +194,14 @@ class Reline::Config
|
|
194
194
|
# value ignores everything after a space, raw_value does not.
|
195
195
|
var, value, raw_value = $1.downcase, $2.partition(' ').first, $2
|
196
196
|
bind_variable(var, value, raw_value)
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
197
|
+
when /^\s*(?:M|Meta)-([a-zA-Z_])\s*:\s*(.*)\s*$/o
|
198
|
+
bind_key("\"\\M-#$1\"", $2)
|
199
|
+
when /^\s*(?:C|Control)-([a-zA-Z_])\s*:\s*(.*)\s*$/o
|
200
|
+
bind_key("\"\\C-#$1\"", $2)
|
201
|
+
when /^\s*(?:(?:C|Control)-(?:M|Meta)|(?:M|Meta)-(?:C|Control))-([a-zA-Z_])\s*:\s*(.*)\s*$/o
|
202
|
+
bind_key("\"\\M-\\C-#$1\"", $2)
|
203
|
+
when /^\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/o
|
204
|
+
bind_key($1, $2)
|
204
205
|
end
|
205
206
|
end
|
206
207
|
unless if_stack.empty?
|
@@ -310,7 +311,12 @@ class Reline::Config
|
|
310
311
|
parse_keyseq(str).map { |c| c.chr(Reline.encoding_system_needs) }.join
|
311
312
|
end
|
312
313
|
|
313
|
-
def bind_key(key,
|
314
|
+
def bind_key(key, value)
|
315
|
+
keystroke, func = parse_key_binding(key, value)
|
316
|
+
@additional_key_bindings[@keymap_label].add(@keymap_prefix + keystroke, func) if keystroke
|
317
|
+
end
|
318
|
+
|
319
|
+
def parse_key_binding(key, func_name)
|
314
320
|
if key =~ /\A"(.*)"\z/
|
315
321
|
keyseq = parse_keyseq($1)
|
316
322
|
else
|
@@ -319,27 +325,19 @@ class Reline::Config
|
|
319
325
|
if func_name =~ /"(.*)"/
|
320
326
|
func = parse_keyseq($1)
|
321
327
|
else
|
322
|
-
func = func_name.tr(?-, ?_).to_sym # It must be macro.
|
328
|
+
func = func_name.split.first.tr(?-, ?_).to_sym # It must be macro.
|
323
329
|
end
|
324
330
|
[keyseq, func]
|
325
331
|
end
|
326
332
|
|
327
333
|
def key_notation_to_code(notation)
|
328
334
|
case notation
|
335
|
+
when /(?:\\(?:C|Control)-\\(?:M|Meta)|\\(?:M|Meta)-\\(?:C|Control))-([A-Za-z_])/
|
336
|
+
[?\e.ord, $1.ord % 32]
|
329
337
|
when /\\(?:C|Control)-([A-Za-z_])/
|
330
|
-
(
|
338
|
+
($1.upcase.ord % 32)
|
331
339
|
when /\\(?:M|Meta)-([0-9A-Za-z_])/
|
332
|
-
|
333
|
-
case $1
|
334
|
-
when /[0-9]/
|
335
|
-
?\M-0.bytes.first + (modified_key.ord - ?0.ord)
|
336
|
-
when /[A-Z]/
|
337
|
-
?\M-A.bytes.first + (modified_key.ord - ?A.ord)
|
338
|
-
when /[a-z]/
|
339
|
-
?\M-a.bytes.first + (modified_key.ord - ?a.ord)
|
340
|
-
end
|
341
|
-
when /\\(?:C|Control)-(?:M|Meta)-[A-Za-z_]/, /\\(?:M|Meta)-(?:C|Control)-[A-Za-z_]/
|
342
|
-
# 129 M-^A
|
340
|
+
[?\e.ord, $1.ord]
|
343
341
|
when /\\(\d{1,3})/ then $1.to_i(8) # octal
|
344
342
|
when /\\x(\h{1,2})/ then $1.to_i(16) # hexadecimal
|
345
343
|
when "\\e" then ?\e.ord
|
@@ -359,11 +357,9 @@ class Reline::Config
|
|
359
357
|
end
|
360
358
|
|
361
359
|
def parse_keyseq(str)
|
362
|
-
|
363
|
-
|
364
|
-
ret << key_notation_to_code($&)
|
360
|
+
str.scan(KEYSEQ_PATTERN).flat_map do |notation|
|
361
|
+
key_notation_to_code(notation)
|
365
362
|
end
|
366
|
-
ret
|
367
363
|
end
|
368
364
|
|
369
365
|
def reload
|
data/lib/reline/history.rb
CHANGED
@@ -19,7 +19,7 @@ class Reline::History < Array
|
|
19
19
|
|
20
20
|
def []=(index, val)
|
21
21
|
index = check_index(index)
|
22
|
-
super(index,
|
22
|
+
super(index, Reline::Unicode.safe_encode(val, Reline.encoding_system_needs))
|
23
23
|
end
|
24
24
|
|
25
25
|
def concat(*val)
|
@@ -45,7 +45,7 @@ class Reline::History < Array
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
super(*(val.map{ |v|
|
48
|
-
|
48
|
+
Reline::Unicode.safe_encode(v, Reline.encoding_system_needs)
|
49
49
|
}))
|
50
50
|
end
|
51
51
|
|
@@ -56,7 +56,7 @@ class Reline::History < Array
|
|
56
56
|
if @config.history_size.positive?
|
57
57
|
shift if size + 1 > @config.history_size
|
58
58
|
end
|
59
|
-
super(
|
59
|
+
super(Reline::Unicode.safe_encode(val, Reline.encoding_system_needs))
|
60
60
|
end
|
61
61
|
|
62
62
|
private def check_index(index)
|
data/lib/reline/io/ansi.rb
CHANGED
@@ -41,7 +41,7 @@ class Reline::ANSI < Reline::IO
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def encoding
|
44
|
-
Encoding.default_external
|
44
|
+
@input.external_encoding || Encoding.default_external
|
45
45
|
end
|
46
46
|
|
47
47
|
def set_default_key_bindings(config, allow_terminfo: true)
|
@@ -75,7 +75,10 @@ class Reline::ANSI < Reline::IO
|
|
75
75
|
|
76
76
|
def set_default_key_bindings_ansi_cursor(config)
|
77
77
|
ANSI_CURSOR_KEY_BINDINGS.each do |char, (default_func, modifiers)|
|
78
|
-
bindings = [
|
78
|
+
bindings = [
|
79
|
+
["\e[#{char}", default_func], # CSI + char
|
80
|
+
["\eO#{char}", default_func] # SS3 + char, application cursor key mode
|
81
|
+
]
|
79
82
|
if modifiers[:ctrl]
|
80
83
|
# CSI + ctrl_key_modifier + char
|
81
84
|
bindings << ["\e[1;5#{char}", modifiers[:ctrl]]
|
@@ -123,27 +126,9 @@ class Reline::ANSI < Reline::IO
|
|
123
126
|
[27, 91, 49, 126] => :ed_move_to_beg, # Home
|
124
127
|
[27, 91, 52, 126] => :ed_move_to_end, # End
|
125
128
|
|
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
129
|
# urxvt / exoterm
|
134
130
|
[27, 91, 55, 126] => :ed_move_to_beg, # Home
|
135
131
|
[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
132
|
}.each_pair do |key, func|
|
148
133
|
config.add_default_key_binding_by_keymap(:emacs, key, func)
|
149
134
|
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
|
@@ -245,39 +230,30 @@ class Reline::ANSI < Reline::IO
|
|
245
230
|
self
|
246
231
|
end
|
247
232
|
|
248
|
-
def
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
@
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
break if m
|
261
|
-
end
|
262
|
-
(m.pre_match + m.post_match).chars.reverse_each do |ch|
|
263
|
-
stdin.ungetc ch
|
233
|
+
private def cursor_pos_internal(timeout:)
|
234
|
+
match = nil
|
235
|
+
@input.raw do |stdin|
|
236
|
+
@output << "\e[6n"
|
237
|
+
@output.flush
|
238
|
+
timeout_at = Time.now + timeout
|
239
|
+
buf = +''
|
240
|
+
while (wait = timeout_at - Time.now) > 0 && stdin.wait_readable(wait)
|
241
|
+
buf << stdin.readpartial(1024)
|
242
|
+
if (match = buf.match(/\e\[(?<row>\d+);(?<column>\d+)R/))
|
243
|
+
buf = match.pre_match + match.post_match
|
244
|
+
break
|
264
245
|
end
|
265
246
|
end
|
266
|
-
|
267
|
-
|
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
|
247
|
+
buf.chars.reverse_each do |ch|
|
248
|
+
stdin.ungetc ch
|
278
249
|
end
|
279
250
|
end
|
280
|
-
|
251
|
+
[match[:column].to_i - 1, match[:row].to_i - 1] if match
|
252
|
+
end
|
253
|
+
|
254
|
+
def cursor_pos
|
255
|
+
col, row = cursor_pos_internal(timeout: 0.5) if both_tty?
|
256
|
+
Reline::CursorPos.new(col || 0, row || 0)
|
281
257
|
end
|
282
258
|
|
283
259
|
def both_tty?
|
data/lib/reline/io/dumb.rb
CHANGED
@@ -21,7 +21,7 @@ class Reline::Dumb < Reline::IO
|
|
21
21
|
elsif RUBY_PLATFORM =~ /mswin|mingw/
|
22
22
|
Encoding::UTF_8
|
23
23
|
else
|
24
|
-
Encoding::default_external
|
24
|
+
@input.external_encoding || Encoding::default_external
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
@@ -60,7 +60,7 @@ class Reline::Dumb < Reline::IO
|
|
60
60
|
end
|
61
61
|
|
62
62
|
def cursor_pos
|
63
|
-
Reline::CursorPos.new(
|
63
|
+
Reline::CursorPos.new(0, 0)
|
64
64
|
end
|
65
65
|
|
66
66
|
def hide_cursor
|
data/lib/reline/io/windows.rb
CHANGED
@@ -157,6 +157,7 @@ class Reline::Windows < Reline::IO
|
|
157
157
|
STD_OUTPUT_HANDLE = -11
|
158
158
|
FILE_TYPE_PIPE = 0x0003
|
159
159
|
FILE_NAME_INFO = 2
|
160
|
+
ENABLE_WRAP_AT_EOL_OUTPUT = 2
|
160
161
|
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
|
161
162
|
|
162
163
|
# Calling Win32API with console handle is reported to fail after executing some external command.
|
@@ -170,7 +171,7 @@ class Reline::Windows < Reline::IO
|
|
170
171
|
end
|
171
172
|
|
172
173
|
private def getconsolemode
|
173
|
-
mode = "\
|
174
|
+
mode = +"\0\0\0\0"
|
174
175
|
call_with_console_handle(@GetConsoleMode, mode)
|
175
176
|
mode.unpack1('L')
|
176
177
|
end
|
@@ -252,7 +253,7 @@ class Reline::Windows < Reline::IO
|
|
252
253
|
|
253
254
|
key = KeyEventRecord.new(virtual_key_code, char_code, control_key_state)
|
254
255
|
|
255
|
-
match = KEY_MAP.find { |args,| key.
|
256
|
+
match = KEY_MAP.find { |args,| key.match?(**args) }
|
256
257
|
unless match.nil?
|
257
258
|
@output_buf.concat(match.last)
|
258
259
|
return
|
@@ -344,35 +345,38 @@ class Reline::Windows < Reline::IO
|
|
344
345
|
# [18,2] dwMaximumWindowSize.X
|
345
346
|
# [20,2] dwMaximumWindowSize.Y
|
346
347
|
csbi = 0.chr * 22
|
347
|
-
|
348
|
-
|
348
|
+
if call_with_console_handle(@GetConsoleScreenBufferInfo, csbi) != 0
|
349
|
+
# returns [width, height, x, y, attributes, left, top, right, bottom]
|
350
|
+
csbi.unpack("s9")
|
351
|
+
else
|
352
|
+
return nil
|
353
|
+
end
|
349
354
|
end
|
350
355
|
|
356
|
+
ALTERNATIVE_CSBI = [80, 24, 0, 0, 7, 0, 0, 79, 23].freeze
|
357
|
+
|
351
358
|
def get_screen_size
|
352
|
-
|
353
|
-
|
354
|
-
end
|
355
|
-
csbi[0, 4].unpack('SS').reverse
|
359
|
+
width, _, _, _, _, _, top, _, bottom = get_console_screen_buffer_info || ALTERNATIVE_CSBI
|
360
|
+
[bottom - top + 1, width]
|
356
361
|
end
|
357
362
|
|
358
363
|
def cursor_pos
|
359
|
-
|
360
|
-
|
361
|
-
end
|
362
|
-
x = csbi[4, 2].unpack1('s')
|
363
|
-
y = csbi[6, 2].unpack1('s')
|
364
|
-
Reline::CursorPos.new(x, y)
|
364
|
+
_, _, x, y, _, _, top, = get_console_screen_buffer_info || ALTERNATIVE_CSBI
|
365
|
+
Reline::CursorPos.new(x, y - top)
|
365
366
|
end
|
366
367
|
|
367
368
|
def move_cursor_column(val)
|
368
|
-
|
369
|
+
_, _, _, y, = get_console_screen_buffer_info
|
370
|
+
call_with_console_handle(@SetConsoleCursorPosition, y * 65536 + val) if y
|
369
371
|
end
|
370
372
|
|
371
373
|
def move_cursor_up(val)
|
372
374
|
if val > 0
|
373
|
-
|
375
|
+
_, _, x, y, _, _, top, = get_console_screen_buffer_info
|
376
|
+
return unless y
|
377
|
+
y = (y - top) - val
|
374
378
|
y = 0 if y < 0
|
375
|
-
call_with_console_handle(@SetConsoleCursorPosition, y * 65536 +
|
379
|
+
call_with_console_handle(@SetConsoleCursorPosition, (y + top) * 65536 + x)
|
376
380
|
elsif val < 0
|
377
381
|
move_cursor_down(-val)
|
378
382
|
end
|
@@ -380,58 +384,39 @@ class Reline::Windows < Reline::IO
|
|
380
384
|
|
381
385
|
def move_cursor_down(val)
|
382
386
|
if val > 0
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
y =
|
387
|
-
|
387
|
+
_, _, x, y, _, _, top, _, bottom = get_console_screen_buffer_info
|
388
|
+
return unless y
|
389
|
+
screen_height = bottom - top
|
390
|
+
y = (y - top) + val
|
391
|
+
y = screen_height if y > screen_height
|
392
|
+
call_with_console_handle(@SetConsoleCursorPosition, (y + top) * 65536 + x)
|
388
393
|
elsif val < 0
|
389
394
|
move_cursor_up(-val)
|
390
395
|
end
|
391
396
|
end
|
392
397
|
|
393
398
|
def erase_after_cursor
|
394
|
-
|
395
|
-
|
396
|
-
cursor = csbi[4, 4].unpack1('L')
|
399
|
+
width, _, x, y, attributes, = get_console_screen_buffer_info
|
400
|
+
return unless x
|
397
401
|
written = 0.chr * 4
|
398
|
-
call_with_console_handle(@FillConsoleOutputCharacter, 0x20,
|
399
|
-
call_with_console_handle(@FillConsoleOutputAttribute, attributes,
|
400
|
-
end
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
if @legacy_console || window_left != 0
|
410
|
-
# unless ENABLE_VIRTUAL_TERMINAL,
|
411
|
-
# if srWindow.Left != 0 then it's conhost.exe hosted console
|
412
|
-
# and puts "\n" causes horizontal scroll. its glitch.
|
413
|
-
# FYI irb write from culumn 1, so this gives no gain.
|
414
|
-
scroll_rectangle = [0, val, buffer_width, buffer_lines - val].pack('s4')
|
415
|
-
destination_origin = 0 # y * 65536 + x
|
416
|
-
fill = [' '.ord, attributes].pack('SS')
|
417
|
-
call_with_console_handle(@ScrollConsoleScreenBuffer, scroll_rectangle, nil, destination_origin, fill)
|
418
|
-
else
|
419
|
-
origin_x = x + 1
|
420
|
-
origin_y = y - window_top + 1
|
421
|
-
@output.write [
|
422
|
-
(origin_y != screen_height) ? "\e[#{screen_height};H" : nil,
|
423
|
-
"\n" * val,
|
424
|
-
(origin_y != screen_height or !x.zero?) ? "\e[#{origin_y};#{origin_x}H" : nil
|
425
|
-
].join
|
426
|
-
end
|
402
|
+
call_with_console_handle(@FillConsoleOutputCharacter, 0x20, width - x, y * 65536 + x, written)
|
403
|
+
call_with_console_handle(@FillConsoleOutputAttribute, attributes, width - x, y * 65536 + x, written)
|
404
|
+
end
|
405
|
+
|
406
|
+
# This only works when the cursor is at the bottom of the scroll range
|
407
|
+
# For more details, see https://github.com/ruby/reline/pull/577#issuecomment-1646679623
|
408
|
+
def scroll_down(x)
|
409
|
+
return if x.zero?
|
410
|
+
# We use `\n` instead of CSI + S because CSI + S would cause https://github.com/ruby/reline/issues/576
|
411
|
+
@output.write "\n" * x
|
427
412
|
end
|
428
413
|
|
429
414
|
def clear_screen
|
430
415
|
if @legacy_console
|
431
|
-
|
432
|
-
|
433
|
-
fill_length =
|
434
|
-
screen_topleft =
|
416
|
+
width, _, _, _, attributes, _, top, _, bottom = get_console_screen_buffer_info
|
417
|
+
return unless width
|
418
|
+
fill_length = width * (bottom - top + 1)
|
419
|
+
screen_topleft = top * 65536
|
435
420
|
written = 0.chr * 4
|
436
421
|
call_with_console_handle(@FillConsoleOutputCharacter, 0x20, fill_length, screen_topleft, written)
|
437
422
|
call_with_console_handle(@FillConsoleOutputAttribute, attributes, fill_length, screen_topleft, written)
|
@@ -472,6 +457,28 @@ class Reline::Windows < Reline::IO
|
|
472
457
|
# do nothing
|
473
458
|
end
|
474
459
|
|
460
|
+
def disable_auto_linewrap(setting = true, &block)
|
461
|
+
mode = getconsolemode
|
462
|
+
if 0 == (mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)
|
463
|
+
if block
|
464
|
+
begin
|
465
|
+
setconsolemode(mode & ~ENABLE_WRAP_AT_EOL_OUTPUT)
|
466
|
+
block.call
|
467
|
+
ensure
|
468
|
+
setconsolemode(mode | ENABLE_WRAP_AT_EOL_OUTPUT)
|
469
|
+
end
|
470
|
+
else
|
471
|
+
if setting
|
472
|
+
setconsolemode(mode & ~ENABLE_WRAP_AT_EOL_OUTPUT)
|
473
|
+
else
|
474
|
+
setconsolemode(mode | ENABLE_WRAP_AT_EOL_OUTPUT)
|
475
|
+
end
|
476
|
+
end
|
477
|
+
else
|
478
|
+
block.call if block
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
475
482
|
class KeyEventRecord
|
476
483
|
|
477
484
|
attr_reader :virtual_key_code, :char_code, :control_key_state, :control_keys
|
@@ -501,7 +508,7 @@ class Reline::Windows < Reline::IO
|
|
501
508
|
# Verifies if the arguments match with this key event.
|
502
509
|
# Nil arguments are ignored, but at least one must be passed as non-nil.
|
503
510
|
# To verify that no control keys were pressed, pass an empty array: `control_keys: []`.
|
504
|
-
def
|
511
|
+
def match?(control_keys: nil, virtual_key_code: nil, char_code: nil)
|
505
512
|
raise ArgumentError, 'No argument was passed to match key event' if control_keys.nil? && virtual_key_code.nil? && char_code.nil?
|
506
513
|
|
507
514
|
(control_keys.nil? || [*control_keys].sort == @control_keys) &&
|
data/lib/reline/line_editor.rb
CHANGED
@@ -72,17 +72,21 @@ class Reline::LineEditor
|
|
72
72
|
|
73
73
|
MINIMUM_SCROLLBAR_HEIGHT = 1
|
74
74
|
|
75
|
-
def initialize(config
|
75
|
+
def initialize(config)
|
76
76
|
@config = config
|
77
77
|
@completion_append_character = ''
|
78
78
|
@screen_size = [0, 0] # Should be initialized with actual winsize in LineEditor#reset
|
79
|
-
reset_variables
|
79
|
+
reset_variables
|
80
80
|
end
|
81
81
|
|
82
82
|
def io_gate
|
83
83
|
Reline::IOGate
|
84
84
|
end
|
85
85
|
|
86
|
+
def encoding
|
87
|
+
io_gate.encoding
|
88
|
+
end
|
89
|
+
|
86
90
|
def set_pasting_state(in_pasting)
|
87
91
|
# While pasting, text to be inserted is stored to @continuous_insertion_buffer.
|
88
92
|
# After pasting, this buffer should be force inserted.
|
@@ -136,9 +140,9 @@ class Reline::LineEditor
|
|
136
140
|
end
|
137
141
|
end
|
138
142
|
|
139
|
-
def reset(prompt = ''
|
143
|
+
def reset(prompt = '')
|
140
144
|
@screen_size = Reline::IOGate.get_screen_size
|
141
|
-
reset_variables(prompt
|
145
|
+
reset_variables(prompt)
|
142
146
|
@rendered_screen.base_y = Reline::IOGate.cursor_pos.y
|
143
147
|
if ENV.key?('RELINE_ALT_SCROLLBAR')
|
144
148
|
@full_block = '::'
|
@@ -150,7 +154,7 @@ class Reline::LineEditor
|
|
150
154
|
@upper_half_block = '▀'
|
151
155
|
@lower_half_block = '▄'
|
152
156
|
@block_elem_width = 1
|
153
|
-
elsif
|
157
|
+
elsif encoding == Encoding::UTF_8
|
154
158
|
@full_block = '█'
|
155
159
|
@upper_half_block = '▀'
|
156
160
|
@lower_half_block = '▄'
|
@@ -219,10 +223,9 @@ class Reline::LineEditor
|
|
219
223
|
@eof
|
220
224
|
end
|
221
225
|
|
222
|
-
def reset_variables(prompt = ''
|
226
|
+
def reset_variables(prompt = '')
|
223
227
|
@prompt = prompt.gsub("\n", "\\n")
|
224
228
|
@mark_pointer = nil
|
225
|
-
@encoding = encoding
|
226
229
|
@is_multiline = false
|
227
230
|
@finished = false
|
228
231
|
@history_pointer = nil
|
@@ -239,7 +242,7 @@ class Reline::LineEditor
|
|
239
242
|
@searching_prompt = nil
|
240
243
|
@just_cursor_moving = false
|
241
244
|
@eof = false
|
242
|
-
@continuous_insertion_buffer = String.new(encoding:
|
245
|
+
@continuous_insertion_buffer = String.new(encoding: encoding)
|
243
246
|
@scroll_partial_screen = 0
|
244
247
|
@drop_terminate_spaces = false
|
245
248
|
@in_pasting = false
|
@@ -259,7 +262,7 @@ class Reline::LineEditor
|
|
259
262
|
|
260
263
|
def reset_line
|
261
264
|
@byte_pointer = 0
|
262
|
-
@buffer_of_lines = [String.new(encoding:
|
265
|
+
@buffer_of_lines = [String.new(encoding: encoding)]
|
263
266
|
@line_index = 0
|
264
267
|
@cache.clear
|
265
268
|
@line_backup_in_history = nil
|
@@ -275,7 +278,7 @@ class Reline::LineEditor
|
|
275
278
|
end
|
276
279
|
|
277
280
|
private def insert_new_line(cursor_line, next_line)
|
278
|
-
@buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding:
|
281
|
+
@buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: encoding))
|
279
282
|
@buffer_of_lines[@line_index] = cursor_line
|
280
283
|
@line_index += 1
|
281
284
|
@byte_pointer = 0
|
@@ -298,7 +301,7 @@ class Reline::LineEditor
|
|
298
301
|
end
|
299
302
|
|
300
303
|
private def split_by_width(str, max_width, offset: 0)
|
301
|
-
Reline::Unicode.split_by_width(str, max_width,
|
304
|
+
Reline::Unicode.split_by_width(str, max_width, encoding, offset: offset)
|
302
305
|
end
|
303
306
|
|
304
307
|
def current_byte_pointer_cursor
|
@@ -461,7 +464,7 @@ class Reline::LineEditor
|
|
461
464
|
def render_finished
|
462
465
|
render_differential([], 0, 0)
|
463
466
|
lines = @buffer_of_lines.size.times.map do |i|
|
464
|
-
line = prompt_list[i] + modified_lines[i]
|
467
|
+
line = Reline::Unicode.strip_non_printing_start_end(prompt_list[i]) + modified_lines[i]
|
465
468
|
wrapped_lines, = split_by_width(line, screen_width)
|
466
469
|
wrapped_lines.last.empty? ? "#{line} " : line
|
467
470
|
end
|
@@ -469,8 +472,11 @@ class Reline::LineEditor
|
|
469
472
|
end
|
470
473
|
|
471
474
|
def print_nomultiline_prompt
|
475
|
+
Reline::IOGate.disable_auto_linewrap(true) if Reline::IOGate.win?
|
472
476
|
# Readline's test `TestRelineAsReadline#test_readline` requires first output to be prompt, not cursor reset escape sequence.
|
473
|
-
@output.write @prompt if @prompt && !@is_multiline
|
477
|
+
@output.write Reline::Unicode.strip_non_printing_start_end(@prompt) if @prompt && !@is_multiline
|
478
|
+
ensure
|
479
|
+
Reline::IOGate.disable_auto_linewrap(false) if Reline::IOGate.win?
|
474
480
|
end
|
475
481
|
|
476
482
|
def render
|
@@ -506,6 +512,7 @@ class Reline::LineEditor
|
|
506
512
|
# by calculating the difference from the previous render.
|
507
513
|
|
508
514
|
private def render_differential(new_lines, new_cursor_x, new_cursor_y)
|
515
|
+
Reline::IOGate.disable_auto_linewrap(true) if Reline::IOGate.win?
|
509
516
|
rendered_lines = @rendered_screen.lines
|
510
517
|
cursor_y = @rendered_screen.cursor_y
|
511
518
|
if new_lines != rendered_lines
|
@@ -536,6 +543,8 @@ class Reline::LineEditor
|
|
536
543
|
Reline::IOGate.move_cursor_column new_cursor_x
|
537
544
|
Reline::IOGate.move_cursor_down new_cursor_y - cursor_y
|
538
545
|
@rendered_screen.cursor_y = new_cursor_y
|
546
|
+
ensure
|
547
|
+
Reline::IOGate.disable_auto_linewrap(false) if Reline::IOGate.win?
|
539
548
|
end
|
540
549
|
|
541
550
|
private def clear_rendered_screen_cache
|
@@ -797,7 +806,7 @@ class Reline::LineEditor
|
|
797
806
|
|
798
807
|
private def complete_internal_proc(list, is_menu)
|
799
808
|
preposing, target, postposing = retrieve_completion_block
|
800
|
-
|
809
|
+
candidates = list.select { |i|
|
801
810
|
if i and not Encoding.compatible?(target.encoding, i.encoding)
|
802
811
|
raise Encoding::CompatibilityError, "#{target.encoding.name} is not compatible with #{i.encoding.name}"
|
803
812
|
end
|
@@ -808,10 +817,10 @@ class Reline::LineEditor
|
|
808
817
|
end
|
809
818
|
}.uniq
|
810
819
|
if is_menu
|
811
|
-
menu(target,
|
820
|
+
menu(target, candidates)
|
812
821
|
return nil
|
813
822
|
end
|
814
|
-
completed =
|
823
|
+
completed = candidates.inject { |memo, item|
|
815
824
|
begin
|
816
825
|
memo_mbchars = memo.unicode_normalize.grapheme_clusters
|
817
826
|
item_mbchars = item.unicode_normalize.grapheme_clusters
|
@@ -838,7 +847,8 @@ class Reline::LineEditor
|
|
838
847
|
end
|
839
848
|
result
|
840
849
|
}
|
841
|
-
|
850
|
+
|
851
|
+
[target, preposing, completed, postposing, candidates]
|
842
852
|
end
|
843
853
|
|
844
854
|
private def perform_completion(list, just_show_list)
|
@@ -846,7 +856,11 @@ class Reline::LineEditor
|
|
846
856
|
when CompletionState::NORMAL
|
847
857
|
@completion_state = CompletionState::COMPLETION
|
848
858
|
when CompletionState::PERFECT_MATCH
|
849
|
-
@dig_perfect_match_proc
|
859
|
+
if @dig_perfect_match_proc
|
860
|
+
@dig_perfect_match_proc.(@perfect_matched)
|
861
|
+
else
|
862
|
+
@completion_state = CompletionState::COMPLETION
|
863
|
+
end
|
850
864
|
end
|
851
865
|
if just_show_list
|
852
866
|
is_menu = true
|
@@ -862,24 +876,26 @@ class Reline::LineEditor
|
|
862
876
|
@completion_state = CompletionState::PERFECT_MATCH
|
863
877
|
end
|
864
878
|
return if result.nil?
|
865
|
-
target, preposing, completed, postposing = result
|
879
|
+
target, preposing, completed, postposing, candidates = result
|
866
880
|
return if completed.nil?
|
867
881
|
if target <= completed and (@completion_state == CompletionState::COMPLETION)
|
868
|
-
|
869
|
-
|
882
|
+
append_character = ''
|
883
|
+
if candidates.include?(completed)
|
884
|
+
if candidates.one?
|
885
|
+
append_character = completion_append_character.to_s
|
870
886
|
@completion_state = CompletionState::PERFECT_MATCH
|
871
887
|
else
|
872
888
|
@completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
|
873
|
-
perform_completion(
|
889
|
+
perform_completion(candidates, true) if @config.show_all_if_ambiguous
|
874
890
|
end
|
875
891
|
@perfect_matched = completed
|
876
892
|
else
|
877
893
|
@completion_state = CompletionState::MENU
|
878
|
-
perform_completion(
|
894
|
+
perform_completion(candidates, true) if @config.show_all_if_ambiguous
|
879
895
|
end
|
880
|
-
|
881
|
-
@buffer_of_lines[@line_index] = (preposing + completed +
|
882
|
-
line_to_pointer = (preposing + completed +
|
896
|
+
unless just_show_list
|
897
|
+
@buffer_of_lines[@line_index] = (preposing + completed + append_character + postposing).split("\n")[@line_index] || String.new(encoding: encoding)
|
898
|
+
line_to_pointer = (preposing + completed + append_character).split("\n")[@line_index] || String.new(encoding: encoding)
|
883
899
|
@byte_pointer = line_to_pointer.bytesize
|
884
900
|
end
|
885
901
|
end
|
@@ -1054,8 +1070,8 @@ class Reline::LineEditor
|
|
1054
1070
|
private def normal_char(key)
|
1055
1071
|
@multibyte_buffer << key.combined_char
|
1056
1072
|
if @multibyte_buffer.size > 1
|
1057
|
-
if @multibyte_buffer.dup.force_encoding(
|
1058
|
-
process_key(@multibyte_buffer.dup.force_encoding(
|
1073
|
+
if @multibyte_buffer.dup.force_encoding(encoding).valid_encoding?
|
1074
|
+
process_key(@multibyte_buffer.dup.force_encoding(encoding), nil)
|
1059
1075
|
@multibyte_buffer.clear
|
1060
1076
|
else
|
1061
1077
|
# invalid
|
@@ -1313,7 +1329,7 @@ class Reline::LineEditor
|
|
1313
1329
|
if (lines.size - 1) > @line_index
|
1314
1330
|
postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
|
1315
1331
|
end
|
1316
|
-
[preposing.encode(
|
1332
|
+
[preposing.encode(encoding), target.encode(encoding), postposing.encode(encoding)]
|
1317
1333
|
end
|
1318
1334
|
|
1319
1335
|
def confirm_multiline_termination
|
@@ -1325,7 +1341,7 @@ class Reline::LineEditor
|
|
1325
1341
|
save_old_buffer
|
1326
1342
|
pre = @buffer_of_lines[@line_index].byteslice(0, @byte_pointer)
|
1327
1343
|
post = @buffer_of_lines[@line_index].byteslice(@byte_pointer..)
|
1328
|
-
lines = (pre + text.gsub(/\r\n?/, "\n") + post).split("\n", -1)
|
1344
|
+
lines = (pre + Reline::Unicode.safe_encode(text, encoding).gsub(/\r\n?/, "\n") + post).split("\n", -1)
|
1329
1345
|
lines << '' if lines.empty?
|
1330
1346
|
@buffer_of_lines[@line_index, 1] = lines
|
1331
1347
|
@line_index += lines.size - 1
|
@@ -1370,7 +1386,7 @@ class Reline::LineEditor
|
|
1370
1386
|
last += current_line.bytesize if last < 0
|
1371
1387
|
first += current_line.bytesize if first < 0
|
1372
1388
|
range = range.exclude_end? ? first...last : first..last
|
1373
|
-
line = current_line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(
|
1389
|
+
line = current_line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(encoding)
|
1374
1390
|
set_current_line(line)
|
1375
1391
|
else
|
1376
1392
|
set_current_line(current_line.byteslice(0, start))
|
@@ -1581,7 +1597,7 @@ class Reline::LineEditor
|
|
1581
1597
|
alias_method :end_of_line, :ed_move_to_end
|
1582
1598
|
|
1583
1599
|
private def generate_searcher(search_key)
|
1584
|
-
search_word = String.new(encoding:
|
1600
|
+
search_word = String.new(encoding: encoding)
|
1585
1601
|
multibyte_buf = String.new(encoding: 'ASCII-8BIT')
|
1586
1602
|
hit_pointer = nil
|
1587
1603
|
lambda do |key|
|
@@ -1598,8 +1614,8 @@ class Reline::LineEditor
|
|
1598
1614
|
search_key = key
|
1599
1615
|
else
|
1600
1616
|
multibyte_buf << key
|
1601
|
-
if multibyte_buf.dup.force_encoding(
|
1602
|
-
search_word << multibyte_buf.dup.force_encoding(
|
1617
|
+
if multibyte_buf.dup.force_encoding(encoding).valid_encoding?
|
1618
|
+
search_word << multibyte_buf.dup.force_encoding(encoding)
|
1603
1619
|
multibyte_buf.clear
|
1604
1620
|
end
|
1605
1621
|
end
|
@@ -1665,57 +1681,29 @@ class Reline::LineEditor
|
|
1665
1681
|
end
|
1666
1682
|
|
1667
1683
|
private def incremental_search_history(key)
|
1668
|
-
|
1669
|
-
@line_backup_in_history = whole_buffer
|
1670
|
-
end
|
1684
|
+
backup = @buffer_of_lines.dup, @line_index, @byte_pointer, @history_pointer, @line_backup_in_history
|
1671
1685
|
searcher = generate_searcher(key)
|
1672
1686
|
@searching_prompt = "(reverse-i-search)`': "
|
1673
1687
|
termination_keys = ["\C-j".ord]
|
1674
|
-
termination_keys.concat(@config.isearch_terminators
|
1688
|
+
termination_keys.concat(@config.isearch_terminators.chars.map(&:ord)) if @config.isearch_terminators
|
1675
1689
|
@waiting_proc = ->(k) {
|
1676
|
-
|
1677
|
-
|
1678
|
-
|
1679
|
-
|
1680
|
-
else
|
1681
|
-
buffer = @line_backup_in_history
|
1682
|
-
end
|
1683
|
-
@buffer_of_lines = buffer.split("\n")
|
1684
|
-
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
1685
|
-
@line_index = @buffer_of_lines.size - 1
|
1690
|
+
chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
|
1691
|
+
if k == "\C-g".ord
|
1692
|
+
# cancel search and restore buffer
|
1693
|
+
@buffer_of_lines, @line_index, @byte_pointer, @history_pointer, @line_backup_in_history = backup
|
1686
1694
|
@searching_prompt = nil
|
1687
1695
|
@waiting_proc = nil
|
1688
|
-
|
1689
|
-
|
1690
|
-
|
1691
|
-
@
|
1692
|
-
@
|
1693
|
-
move_history(
|
1696
|
+
elsif !termination_keys.include?(k) && (chr.match?(/[[:print:]]/) || k == "\C-h".ord || k == "\C-?".ord || k == "\C-r".ord || k == "\C-s".ord)
|
1697
|
+
search_word, prompt_name, hit_pointer = searcher.call(k)
|
1698
|
+
Reline.last_incremental_search = search_word
|
1699
|
+
@searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
|
1700
|
+
@searching_prompt += ': ' unless @is_multiline
|
1701
|
+
move_history(hit_pointer, line: :end, cursor: :end) if hit_pointer
|
1702
|
+
else
|
1703
|
+
# terminaton_keys and other keys will terminalte
|
1704
|
+
move_history(@history_pointer, line: :end, cursor: :start)
|
1694
1705
|
@searching_prompt = nil
|
1695
1706
|
@waiting_proc = nil
|
1696
|
-
@byte_pointer = 0
|
1697
|
-
else
|
1698
|
-
chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
|
1699
|
-
if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == "\C-?".ord or k == "\C-r".ord or k == "\C-s".ord
|
1700
|
-
search_word, prompt_name, hit_pointer = searcher.call(k)
|
1701
|
-
Reline.last_incremental_search = search_word
|
1702
|
-
@searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
|
1703
|
-
@searching_prompt += ': ' unless @is_multiline
|
1704
|
-
move_history(hit_pointer, line: :end, cursor: :end, save_buffer: false) if hit_pointer
|
1705
|
-
else
|
1706
|
-
if @history_pointer
|
1707
|
-
line = Reline::HISTORY[@history_pointer]
|
1708
|
-
else
|
1709
|
-
line = @line_backup_in_history
|
1710
|
-
end
|
1711
|
-
@line_backup_in_history = whole_buffer
|
1712
|
-
@buffer_of_lines = line.split("\n")
|
1713
|
-
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
1714
|
-
@line_index = @buffer_of_lines.size - 1
|
1715
|
-
@searching_prompt = nil
|
1716
|
-
@waiting_proc = nil
|
1717
|
-
@byte_pointer = 0
|
1718
|
-
end
|
1719
1707
|
end
|
1720
1708
|
}
|
1721
1709
|
end
|
@@ -1770,14 +1758,14 @@ class Reline::LineEditor
|
|
1770
1758
|
end
|
1771
1759
|
alias_method :history_search_forward, :ed_search_next_history
|
1772
1760
|
|
1773
|
-
private def move_history(history_pointer, line:, cursor
|
1761
|
+
private def move_history(history_pointer, line:, cursor:)
|
1774
1762
|
history_pointer ||= Reline::HISTORY.size
|
1775
1763
|
return if history_pointer < 0 || history_pointer > Reline::HISTORY.size
|
1776
1764
|
old_history_pointer = @history_pointer || Reline::HISTORY.size
|
1777
1765
|
if old_history_pointer == Reline::HISTORY.size
|
1778
|
-
@line_backup_in_history =
|
1766
|
+
@line_backup_in_history = whole_buffer
|
1779
1767
|
else
|
1780
|
-
Reline::HISTORY[old_history_pointer] = whole_buffer
|
1768
|
+
Reline::HISTORY[old_history_pointer] = whole_buffer
|
1781
1769
|
end
|
1782
1770
|
if history_pointer == Reline::HISTORY.size
|
1783
1771
|
buf = @line_backup_in_history
|
@@ -1787,7 +1775,7 @@ class Reline::LineEditor
|
|
1787
1775
|
@history_pointer = history_pointer
|
1788
1776
|
end
|
1789
1777
|
@buffer_of_lines = buf.split("\n")
|
1790
|
-
@buffer_of_lines = [String.new(encoding:
|
1778
|
+
@buffer_of_lines = [String.new(encoding: encoding)] if @buffer_of_lines.empty?
|
1791
1779
|
@line_index = line == :start ? 0 : line == :end ? @buffer_of_lines.size - 1 : line
|
1792
1780
|
@byte_pointer = cursor == :start ? 0 : cursor == :end ? current_line.bytesize : cursor
|
1793
1781
|
end
|
@@ -2312,7 +2300,7 @@ class Reline::LineEditor
|
|
2312
2300
|
}
|
2313
2301
|
system("#{ENV['EDITOR']} #{path}")
|
2314
2302
|
@buffer_of_lines = File.read(path).split("\n")
|
2315
|
-
@buffer_of_lines = [String.new(encoding:
|
2303
|
+
@buffer_of_lines = [String.new(encoding: encoding)] if @buffer_of_lines.empty?
|
2316
2304
|
@line_index = 0
|
2317
2305
|
finish
|
2318
2306
|
end
|
@@ -2396,9 +2384,9 @@ class Reline::LineEditor
|
|
2396
2384
|
|
2397
2385
|
private def search_next_char(key, arg, need_prev_char: false, inclusive: false)
|
2398
2386
|
if key.instance_of?(String)
|
2399
|
-
|
2387
|
+
inputted_char = key
|
2400
2388
|
else
|
2401
|
-
|
2389
|
+
inputted_char = key.chr
|
2402
2390
|
end
|
2403
2391
|
prev_total = nil
|
2404
2392
|
total = nil
|
@@ -2410,7 +2398,7 @@ class Reline::LineEditor
|
|
2410
2398
|
width = Reline::Unicode.get_mbchar_width(mbchar)
|
2411
2399
|
total = [mbchar.bytesize, width]
|
2412
2400
|
else
|
2413
|
-
if
|
2401
|
+
if inputted_char == mbchar
|
2414
2402
|
arg -= 1
|
2415
2403
|
if arg.zero?
|
2416
2404
|
found = true
|
@@ -2448,9 +2436,9 @@ class Reline::LineEditor
|
|
2448
2436
|
|
2449
2437
|
private def search_prev_char(key, arg, need_next_char = false)
|
2450
2438
|
if key.instance_of?(String)
|
2451
|
-
|
2439
|
+
inputted_char = key
|
2452
2440
|
else
|
2453
|
-
|
2441
|
+
inputted_char = key.chr
|
2454
2442
|
end
|
2455
2443
|
prev_total = nil
|
2456
2444
|
total = nil
|
@@ -2462,7 +2450,7 @@ class Reline::LineEditor
|
|
2462
2450
|
width = Reline::Unicode.get_mbchar_width(mbchar)
|
2463
2451
|
total = [mbchar.bytesize, width]
|
2464
2452
|
else
|
2465
|
-
if
|
2453
|
+
if inputted_char == mbchar
|
2466
2454
|
arg -= 1
|
2467
2455
|
if arg.zero?
|
2468
2456
|
found = true
|
data/lib/reline/unicode.rb
CHANGED
@@ -54,6 +54,22 @@ class Reline::Unicode
|
|
54
54
|
}.join
|
55
55
|
end
|
56
56
|
|
57
|
+
def self.safe_encode(str, encoding)
|
58
|
+
# Reline only supports utf-8 convertible string.
|
59
|
+
converted = str.encode(encoding, invalid: :replace, undef: :replace)
|
60
|
+
return converted if str.encoding == Encoding::UTF_8 || converted.encoding == Encoding::UTF_8 || converted.ascii_only?
|
61
|
+
|
62
|
+
# This code is essentially doing the same thing as
|
63
|
+
# `str.encode(utf8, **replace_options).encode(encoding, **replace_options)`
|
64
|
+
# but also avoids unneccesary irreversible encoding conversion.
|
65
|
+
converted.gsub(/\X/) do |c|
|
66
|
+
c.encode(Encoding::UTF_8)
|
67
|
+
c
|
68
|
+
rescue Encoding::UndefinedConversionError
|
69
|
+
'?'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
57
73
|
require 'reline/unicode/east_asian_width'
|
58
74
|
|
59
75
|
def self.get_mbchar_width(mbchar)
|
@@ -116,10 +132,8 @@ class Reline::Unicode
|
|
116
132
|
case
|
117
133
|
when non_printing_start
|
118
134
|
in_zero_width = true
|
119
|
-
lines.last << NON_PRINTING_START
|
120
135
|
when non_printing_end
|
121
136
|
in_zero_width = false
|
122
|
-
lines.last << NON_PRINTING_END
|
123
137
|
when csi
|
124
138
|
lines.last << csi
|
125
139
|
unless in_zero_width
|
@@ -131,7 +145,7 @@ class Reline::Unicode
|
|
131
145
|
end
|
132
146
|
when osc
|
133
147
|
lines.last << osc
|
134
|
-
seq << osc
|
148
|
+
seq << osc unless in_zero_width
|
135
149
|
when gc
|
136
150
|
unless in_zero_width
|
137
151
|
mbchar_width = get_mbchar_width(gc)
|
@@ -154,6 +168,10 @@ class Reline::Unicode
|
|
154
168
|
[lines, height]
|
155
169
|
end
|
156
170
|
|
171
|
+
def self.strip_non_printing_start_end(prompt)
|
172
|
+
prompt.gsub(/\x01([^\x02]*)(?:\x02|\z)/) { $1 }
|
173
|
+
end
|
174
|
+
|
157
175
|
# Take a chunk of a String cut by width with escape sequences.
|
158
176
|
def self.take_range(str, start_col, max_width)
|
159
177
|
take_mbchar_range(str, start_col, max_width).first
|
@@ -173,10 +191,8 @@ class Reline::Unicode
|
|
173
191
|
case
|
174
192
|
when non_printing_start
|
175
193
|
in_zero_width = true
|
176
|
-
chunk << NON_PRINTING_START
|
177
194
|
when non_printing_end
|
178
195
|
in_zero_width = false
|
179
|
-
chunk << NON_PRINTING_END
|
180
196
|
when csi
|
181
197
|
has_csi = true
|
182
198
|
chunk << csi
|
data/lib/reline/version.rb
CHANGED
data/lib/reline.rb
CHANGED
@@ -308,7 +308,7 @@ module Reline
|
|
308
308
|
otio = io_gate.prep
|
309
309
|
|
310
310
|
may_req_ambiguous_char_width
|
311
|
-
line_editor.reset(prompt
|
311
|
+
line_editor.reset(prompt)
|
312
312
|
if multiline
|
313
313
|
line_editor.multiline_on
|
314
314
|
if block_given?
|
@@ -412,7 +412,7 @@ module Reline
|
|
412
412
|
end
|
413
413
|
|
414
414
|
private def may_req_ambiguous_char_width
|
415
|
-
@ambiguous_width =
|
415
|
+
@ambiguous_width = 1 if io_gate.dumb? || !STDIN.tty? || !STDOUT.tty?
|
416
416
|
return if defined? @ambiguous_width
|
417
417
|
io_gate.move_cursor_column(0)
|
418
418
|
begin
|
@@ -421,7 +421,7 @@ module Reline
|
|
421
421
|
# LANG=C
|
422
422
|
@ambiguous_width = 1
|
423
423
|
else
|
424
|
-
@ambiguous_width = io_gate.cursor_pos.x
|
424
|
+
@ambiguous_width = io_gate.cursor_pos.x == 2 ? 2 : 1
|
425
425
|
end
|
426
426
|
io_gate.move_cursor_column(0)
|
427
427
|
io_gate.erase_after_cursor
|
@@ -487,7 +487,7 @@ module Reline
|
|
487
487
|
@core ||= Core.new { |core|
|
488
488
|
core.config = Reline::Config.new
|
489
489
|
core.key_stroke = Reline::KeyStroke.new(core.config)
|
490
|
-
core.line_editor = Reline::LineEditor.new(core.config
|
490
|
+
core.line_editor = Reline::LineEditor.new(core.config)
|
491
491
|
|
492
492
|
core.basic_word_break_characters = " \t\n`><=;|&{("
|
493
493
|
core.completer_word_break_characters = " \t\n`><=;|&{("
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reline
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- aycabta
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2024-
|
10
|
+
date: 2024-11-08 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: io-console
|