reline 0.2.7 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,13 @@
1
- require 'fiddle'
2
- require 'fiddle/import'
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.unpack('i')[0]
82
+ errret = errret_int.unpack1('i')
75
83
  case ret
76
84
  when 0 # OK
77
85
  0
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Reline
2
- VERSION = '0.2.7'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -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
- def self.msys_tty?(io=@@hConsoleInputHandle)
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].unpack("L")[0]
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? #or true
221
- next if @@GetNumberOfConsoleInputEvents.(@@hConsoleInputHandle, num_of_events) == 0 or num_of_events.unpack('L').first == 0
222
- input_record = 0.chr * 18
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, input_record, 1, read_event) != 0
225
- event = input_record[0, 2].unpack('s*').first
226
- case event
227
- when WINDOW_BUFFER_SIZE_EVENT
228
- @@winch_handler.()
229
- when KEY_EVENT
230
- key_down = input_record[4, 4].unpack('l*').first
231
- repeat_count = input_record[8, 2].unpack('s*').first
232
- virtual_key_code = input_record[10, 2].unpack('s*').first
233
- virtual_scan_code = input_record[12, 2].unpack('s*').first
234
- char_code = input_record[14, 2].unpack('S*').first
235
- control_key_state = input_record[16, 2].unpack('S*').first
236
- is_key_down = key_down.zero? ? false : true
237
- if is_key_down
238
- process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
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 @@input_buf.empty?
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.get_screen_size
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 = 0.chr * 22
276
- @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi)
277
- x = csbi[4, 2].unpack('s*').first
278
- y = csbi[6, 2].unpack('s*').first
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 = 0.chr * 24
309
- @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi)
310
- cursor = csbi[4, 4].unpack('L').first
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, 0, get_screen_size.last - cursor_pos.x, cursor, written)
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.zero?
318
- screen_height = get_screen_size.first
319
- val = screen_height - 1 if val > (screen_height - 1)
320
- scroll_rectangle = [0, val, get_screen_size.last, get_screen_size.first].pack('s4')
321
- destination_origin = 0 # y * 65536 + x
322
- fill = [' '.ord, 0].pack('SS')
323
- @@ScrollConsoleScreenBuffer.call(@@hConsoleHandle, scroll_rectangle, nil, destination_origin, fill)
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
- csbi = 0.chr * 22
328
- return if @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi) == 0
329
- buffer_width = csbi[0, 2].unpack('S').first
330
- attributes = csbi[8, 2].unpack('S').first
331
- _window_left, window_top, _window_right, window_bottom = *csbi[10,8].unpack('S*')
332
- fill_length = buffer_width * (window_bottom - window_top + 1)
333
- screen_topleft = window_top * 65536
334
- written = 0.chr * 4
335
- @@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, fill_length, screen_topleft, written)
336
- @@FillConsoleOutputAttribute.call(@@hConsoleHandle, attributes, fill_length, screen_topleft, written)
337
- @@SetConsoleCursorPosition.call(@@hConsoleHandle, screen_topleft)
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
- begin
307
- succ_c = nil
308
- Timeout.timeout(keyseq_timeout / 1000.0) {
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.2.7
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-08-12 00:00:00.000000000 Z
11
+ date: 2021-12-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: io-console