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