reline 0.3.9 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,19 +1,52 @@
1
1
  require 'fiddle/import'
2
2
 
3
- class Reline::Windows
4
- def self.encoding
3
+ class Reline::Windows < Reline::IO
4
+
5
+ attr_writer :output
6
+
7
+ def initialize
8
+ @input_buf = []
9
+ @output_buf = []
10
+
11
+ @output = STDOUT
12
+ @hsg = nil
13
+ @getwch = Win32API.new('msvcrt', '_getwch', [], 'I')
14
+ @kbhit = Win32API.new('msvcrt', '_kbhit', [], 'I')
15
+ @GetKeyState = Win32API.new('user32', 'GetKeyState', ['L'], 'L')
16
+ @GetConsoleScreenBufferInfo = Win32API.new('kernel32', 'GetConsoleScreenBufferInfo', ['L', 'P'], 'L')
17
+ @SetConsoleCursorPosition = Win32API.new('kernel32', 'SetConsoleCursorPosition', ['L', 'L'], 'L')
18
+ @GetStdHandle = Win32API.new('kernel32', 'GetStdHandle', ['L'], 'L')
19
+ @FillConsoleOutputCharacter = Win32API.new('kernel32', 'FillConsoleOutputCharacter', ['L', 'L', 'L', 'L', 'P'], 'L')
20
+ @ScrollConsoleScreenBuffer = Win32API.new('kernel32', 'ScrollConsoleScreenBuffer', ['L', 'P', 'P', 'L', 'P'], 'L')
21
+ @hConsoleHandle = @GetStdHandle.call(STD_OUTPUT_HANDLE)
22
+ @hConsoleInputHandle = @GetStdHandle.call(STD_INPUT_HANDLE)
23
+ @GetNumberOfConsoleInputEvents = Win32API.new('kernel32', 'GetNumberOfConsoleInputEvents', ['L', 'P'], 'L')
24
+ @ReadConsoleInputW = Win32API.new('kernel32', 'ReadConsoleInputW', ['L', 'P', 'L', 'P'], 'L')
25
+ @GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L')
26
+ @GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I')
27
+ @FillConsoleOutputAttribute = Win32API.new('kernel32', 'FillConsoleOutputAttribute', ['L', 'L', 'L', 'L', 'P'], 'L')
28
+ @SetConsoleCursorInfo = Win32API.new('kernel32', 'SetConsoleCursorInfo', ['L', 'P'], 'L')
29
+
30
+ @GetConsoleMode = Win32API.new('kernel32', 'GetConsoleMode', ['L', 'P'], 'L')
31
+ @SetConsoleMode = Win32API.new('kernel32', 'SetConsoleMode', ['L', 'L'], 'L')
32
+ @WaitForSingleObject = Win32API.new('kernel32', 'WaitForSingleObject', ['L', 'L'], 'L')
33
+
34
+ @legacy_console = getconsolemode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0
35
+ end
36
+
37
+ def encoding
5
38
  Encoding::UTF_8
6
39
  end
7
40
 
8
- def self.win?
41
+ def win?
9
42
  true
10
43
  end
11
44
 
12
- def self.win_legacy_console?
13
- @@legacy_console
45
+ def win_legacy_console?
46
+ @legacy_console
14
47
  end
15
48
 
16
- def self.set_default_key_bindings(config)
49
+ def set_default_key_bindings(config)
17
50
  {
18
51
  [224, 72] => :ed_prev_history, # ↑
19
52
  [224, 80] => :ed_next_history, # ↓
@@ -22,7 +55,6 @@ class Reline::Windows
22
55
  [224, 83] => :key_delete, # Del
23
56
  [224, 71] => :ed_move_to_beg, # Home
24
57
  [224, 79] => :ed_move_to_end, # End
25
- [ 0, 41] => :ed_unassigned, # input method on/off
26
58
  [ 0, 72] => :ed_prev_history, # ↑
27
59
  [ 0, 80] => :ed_next_history, # ↓
28
60
  [ 0, 77] => :ed_next_char, # →
@@ -85,7 +117,7 @@ class Reline::Windows
85
117
  def call(*args)
86
118
  import = @proto.split("")
87
119
  args.each_with_index do |x, i|
88
- args[i], = [x == 0 ? nil : x].pack("p").unpack(POINTER_TYPE) if import[i] == "S"
120
+ args[i], = [x == 0 ? nil : +x].pack("p").unpack(POINTER_TYPE) if import[i] == "S"
89
121
  args[i], = [x].pack("I").unpack("i") if import[i] == "I"
90
122
  end
91
123
  ret, = @func.call(*args)
@@ -127,58 +159,43 @@ class Reline::Windows
127
159
  STD_OUTPUT_HANDLE = -11
128
160
  FILE_TYPE_PIPE = 0x0003
129
161
  FILE_NAME_INFO = 2
130
- @@getwch = Win32API.new('msvcrt', '_getwch', [], 'I')
131
- @@kbhit = Win32API.new('msvcrt', '_kbhit', [], 'I')
132
- @@GetKeyState = Win32API.new('user32', 'GetKeyState', ['L'], 'L')
133
- @@GetConsoleScreenBufferInfo = Win32API.new('kernel32', 'GetConsoleScreenBufferInfo', ['L', 'P'], 'L')
134
- @@SetConsoleCursorPosition = Win32API.new('kernel32', 'SetConsoleCursorPosition', ['L', 'L'], 'L')
135
- @@GetStdHandle = Win32API.new('kernel32', 'GetStdHandle', ['L'], 'L')
136
- @@FillConsoleOutputCharacter = Win32API.new('kernel32', 'FillConsoleOutputCharacter', ['L', 'L', 'L', 'L', 'P'], 'L')
137
- @@ScrollConsoleScreenBuffer = Win32API.new('kernel32', 'ScrollConsoleScreenBuffer', ['L', 'P', 'P', 'L', 'P'], 'L')
138
- @@hConsoleHandle = @@GetStdHandle.call(STD_OUTPUT_HANDLE)
139
- @@hConsoleInputHandle = @@GetStdHandle.call(STD_INPUT_HANDLE)
140
- @@GetNumberOfConsoleInputEvents = Win32API.new('kernel32', 'GetNumberOfConsoleInputEvents', ['L', 'P'], 'L')
141
- @@ReadConsoleInputW = Win32API.new('kernel32', 'ReadConsoleInputW', ['L', 'P', 'L', 'P'], 'L')
142
- @@GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L')
143
- @@GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I')
144
- @@FillConsoleOutputAttribute = Win32API.new('kernel32', 'FillConsoleOutputAttribute', ['L', 'L', 'L', 'L', 'P'], 'L')
145
- @@SetConsoleCursorInfo = Win32API.new('kernel32', 'SetConsoleCursorInfo', ['L', 'P'], 'L')
146
-
147
- @@GetConsoleMode = Win32API.new('kernel32', 'GetConsoleMode', ['L', 'P'], 'L')
148
- @@SetConsoleMode = Win32API.new('kernel32', 'SetConsoleMode', ['L', 'L'], 'L')
149
- @@WaitForSingleObject = Win32API.new('kernel32', 'WaitForSingleObject', ['L', 'L'], 'L')
162
+ ENABLE_WRAP_AT_EOL_OUTPUT = 2
150
163
  ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
151
164
 
152
- private_class_method def self.getconsolemode
153
- mode = "\000\000\000\000"
154
- @@GetConsoleMode.call(@@hConsoleHandle, mode)
165
+ # Calling Win32API with console handle is reported to fail after executing some external command.
166
+ # We need to refresh console handle and retry the call again.
167
+ private def call_with_console_handle(win32func, *args)
168
+ val = win32func.call(@hConsoleHandle, *args)
169
+ return val if val != 0
170
+
171
+ @hConsoleHandle = @GetStdHandle.call(STD_OUTPUT_HANDLE)
172
+ win32func.call(@hConsoleHandle, *args)
173
+ end
174
+
175
+ private def getconsolemode
176
+ mode = +"\0\0\0\0"
177
+ call_with_console_handle(@GetConsoleMode, mode)
155
178
  mode.unpack1('L')
156
179
  end
157
180
 
158
- private_class_method def self.setconsolemode(mode)
159
- @@SetConsoleMode.call(@@hConsoleHandle, mode)
181
+ private def setconsolemode(mode)
182
+ call_with_console_handle(@SetConsoleMode, mode)
160
183
  end
161
184
 
162
- @@legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0)
163
- #if @@legacy_console
185
+ #if @legacy_console
164
186
  # setconsolemode(getconsolemode() | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
165
- # @@legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0)
187
+ # @legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0)
166
188
  #end
167
189
 
168
- @@input_buf = []
169
- @@output_buf = []
170
-
171
- @@output = STDOUT
172
-
173
- def self.msys_tty?(io = @@hConsoleInputHandle)
190
+ def msys_tty?(io = @hConsoleInputHandle)
174
191
  # check if fd is a pipe
175
- if @@GetFileType.call(io) != FILE_TYPE_PIPE
192
+ if @GetFileType.call(io) != FILE_TYPE_PIPE
176
193
  return false
177
194
  end
178
195
 
179
196
  bufsize = 1024
180
197
  p_buffer = "\0" * bufsize
181
- res = @@GetFileInformationByHandleEx.call(io, FILE_NAME_INFO, p_buffer, bufsize - 2)
198
+ res = @GetFileInformationByHandleEx.call(io, FILE_NAME_INFO, p_buffer, bufsize - 2)
182
199
  return false if res == 0
183
200
 
184
201
  # get pipe name: p_buffer layout is:
@@ -215,65 +232,63 @@ class Reline::Windows
215
232
  [ { control_keys: :SHIFT, virtual_key_code: VK_TAB }, [27, 91, 90] ],
216
233
  ]
217
234
 
218
- @@hsg = nil
219
-
220
- def self.process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
235
+ def process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
221
236
 
222
237
  # high-surrogate
223
238
  if 0xD800 <= char_code and char_code <= 0xDBFF
224
- @@hsg = char_code
239
+ @hsg = char_code
225
240
  return
226
241
  end
227
242
  # low-surrogate
228
243
  if 0xDC00 <= char_code and char_code <= 0xDFFF
229
- if @@hsg
230
- char_code = 0x10000 + (@@hsg - 0xD800) * 0x400 + char_code - 0xDC00
231
- @@hsg = nil
244
+ if @hsg
245
+ char_code = 0x10000 + (@hsg - 0xD800) * 0x400 + char_code - 0xDC00
246
+ @hsg = nil
232
247
  else
233
248
  # no high-surrogate. ignored.
234
249
  return
235
250
  end
236
251
  else
237
252
  # ignore high-surrogate without low-surrogate if there
238
- @@hsg = nil
253
+ @hsg = nil
239
254
  end
240
255
 
241
256
  key = KeyEventRecord.new(virtual_key_code, char_code, control_key_state)
242
257
 
243
- match = KEY_MAP.find { |args,| key.matches?(**args) }
258
+ match = KEY_MAP.find { |args,| key.match?(**args) }
244
259
  unless match.nil?
245
- @@output_buf.concat(match.last)
260
+ @output_buf.concat(match.last)
246
261
  return
247
262
  end
248
263
 
249
264
  # no char, only control keys
250
265
  return if key.char_code == 0 and key.control_keys.any?
251
266
 
252
- @@output_buf.push("\e".ord) if key.control_keys.include?(:ALT) and !key.control_keys.include?(:CTRL)
267
+ @output_buf.push("\e".ord) if key.control_keys.include?(:ALT) and !key.control_keys.include?(:CTRL)
253
268
 
254
- @@output_buf.concat(key.char.bytes)
269
+ @output_buf.concat(key.char.bytes)
255
270
  end
256
271
 
257
- def self.check_input_event
272
+ def check_input_event
258
273
  num_of_events = 0.chr * 8
259
- while @@output_buf.empty?
260
- Reline.core.line_editor.resize
261
- if @@WaitForSingleObject.(@@hConsoleInputHandle, 100) != 0 # max 0.1 sec
274
+ while @output_buf.empty?
275
+ Reline.core.line_editor.handle_signal
276
+ if @WaitForSingleObject.(@hConsoleInputHandle, 100) != 0 # max 0.1 sec
262
277
  # prevent for background consolemode change
263
- @@legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0)
278
+ @legacy_console = getconsolemode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0
264
279
  next
265
280
  end
266
- next if @@GetNumberOfConsoleInputEvents.(@@hConsoleInputHandle, num_of_events) == 0 or num_of_events.unpack1('L') == 0
281
+ next if @GetNumberOfConsoleInputEvents.(@hConsoleInputHandle, num_of_events) == 0 or num_of_events.unpack1('L') == 0
267
282
  input_records = 0.chr * 20 * 80
268
283
  read_event = 0.chr * 4
269
- if @@ReadConsoleInputW.(@@hConsoleInputHandle, input_records, 80, read_event) != 0
284
+ if @ReadConsoleInputW.(@hConsoleInputHandle, input_records, 80, read_event) != 0
270
285
  read_events = read_event.unpack1('L')
271
286
  0.upto(read_events) do |idx|
272
287
  input_record = input_records[idx * 20, 20]
273
288
  event = input_record[0, 2].unpack1('s*')
274
289
  case event
275
290
  when WINDOW_BUFFER_SIZE_EVENT
276
- @@winch_handler.()
291
+ @winch_handler.()
277
292
  when KEY_EVENT
278
293
  key_down = input_record[4, 4].unpack1('l*')
279
294
  repeat_count = input_record[8, 2].unpack1('s*')
@@ -291,34 +306,42 @@ class Reline::Windows
291
306
  end
292
307
  end
293
308
 
294
- def self.with_raw_input
309
+ def with_raw_input
295
310
  yield
296
311
  end
297
312
 
298
- def self.getc(_timeout_second)
313
+ def write(string)
314
+ @output.write(string)
315
+ end
316
+
317
+ def buffered_output
318
+ yield
319
+ end
320
+
321
+ def getc(_timeout_second)
299
322
  check_input_event
300
- @@output_buf.shift
323
+ @output_buf.shift
301
324
  end
302
325
 
303
- def self.ungetc(c)
304
- @@output_buf.unshift(c)
326
+ def ungetc(c)
327
+ @output_buf.unshift(c)
305
328
  end
306
329
 
307
- def self.in_pasting?
308
- not self.empty_buffer?
330
+ def in_pasting?
331
+ not empty_buffer?
309
332
  end
310
333
 
311
- def self.empty_buffer?
312
- if not @@output_buf.empty?
334
+ def empty_buffer?
335
+ if not @output_buf.empty?
313
336
  false
314
- elsif @@kbhit.call == 0
337
+ elsif @kbhit.call == 0
315
338
  true
316
339
  else
317
340
  false
318
341
  end
319
342
  end
320
343
 
321
- def self.get_console_screen_buffer_info
344
+ def get_console_screen_buffer_info
322
345
  # CONSOLE_SCREEN_BUFFER_INFO
323
346
  # [ 0,2] dwSize.X
324
347
  # [ 2,2] dwSize.Y
@@ -332,134 +355,140 @@ class Reline::Windows
332
355
  # [18,2] dwMaximumWindowSize.X
333
356
  # [20,2] dwMaximumWindowSize.Y
334
357
  csbi = 0.chr * 22
335
- return if @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi) == 0
336
- csbi
358
+ if call_with_console_handle(@GetConsoleScreenBufferInfo, csbi) != 0
359
+ # returns [width, height, x, y, attributes, left, top, right, bottom]
360
+ csbi.unpack("s9")
361
+ else
362
+ return nil
363
+ end
337
364
  end
338
365
 
339
- def self.get_screen_size
340
- unless csbi = get_console_screen_buffer_info
341
- return [1, 1]
342
- end
343
- csbi[0, 4].unpack('SS').reverse
366
+ ALTERNATIVE_CSBI = [80, 24, 0, 0, 7, 0, 0, 79, 23].freeze
367
+
368
+ def get_screen_size
369
+ width, _, _, _, _, _, top, _, bottom = get_console_screen_buffer_info || ALTERNATIVE_CSBI
370
+ [bottom - top + 1, width]
344
371
  end
345
372
 
346
- def self.cursor_pos
347
- unless csbi = get_console_screen_buffer_info
348
- return Reline::CursorPos.new(0, 0)
349
- end
350
- x = csbi[4, 2].unpack1('s')
351
- y = csbi[6, 2].unpack1('s')
352
- Reline::CursorPos.new(x, y)
373
+ def cursor_pos
374
+ _, _, x, y, _, _, top, = get_console_screen_buffer_info || ALTERNATIVE_CSBI
375
+ Reline::CursorPos.new(x, y - top)
353
376
  end
354
377
 
355
- def self.move_cursor_column(val)
356
- @@SetConsoleCursorPosition.call(@@hConsoleHandle, cursor_pos.y * 65536 + val)
378
+ def move_cursor_column(val)
379
+ _, _, _, y, = get_console_screen_buffer_info
380
+ call_with_console_handle(@SetConsoleCursorPosition, y * 65536 + val) if y
357
381
  end
358
382
 
359
- def self.move_cursor_up(val)
383
+ def move_cursor_up(val)
360
384
  if val > 0
361
- y = cursor_pos.y - val
385
+ _, _, x, y, _, _, top, = get_console_screen_buffer_info
386
+ return unless y
387
+ y = (y - top) - val
362
388
  y = 0 if y < 0
363
- @@SetConsoleCursorPosition.call(@@hConsoleHandle, y * 65536 + cursor_pos.x)
389
+ call_with_console_handle(@SetConsoleCursorPosition, (y + top) * 65536 + x)
364
390
  elsif val < 0
365
391
  move_cursor_down(-val)
366
392
  end
367
393
  end
368
394
 
369
- def self.move_cursor_down(val)
395
+ def move_cursor_down(val)
370
396
  if val > 0
371
- return unless csbi = get_console_screen_buffer_info
372
- screen_height = get_screen_size.first
373
- y = cursor_pos.y + val
374
- y = screen_height - 1 if y > (screen_height - 1)
375
- @@SetConsoleCursorPosition.call(@@hConsoleHandle, (cursor_pos.y + val) * 65536 + cursor_pos.x)
397
+ _, _, x, y, _, _, top, _, bottom = get_console_screen_buffer_info
398
+ return unless y
399
+ screen_height = bottom - top
400
+ y = (y - top) + val
401
+ y = screen_height if y > screen_height
402
+ call_with_console_handle(@SetConsoleCursorPosition, (y + top) * 65536 + x)
376
403
  elsif val < 0
377
404
  move_cursor_up(-val)
378
405
  end
379
406
  end
380
407
 
381
- def self.erase_after_cursor
382
- return unless csbi = get_console_screen_buffer_info
383
- attributes = csbi[8, 2].unpack1('S')
384
- cursor = csbi[4, 4].unpack1('L')
408
+ def erase_after_cursor
409
+ width, _, x, y, attributes, = get_console_screen_buffer_info
410
+ return unless x
385
411
  written = 0.chr * 4
386
- @@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, get_screen_size.last - cursor_pos.x, cursor, written)
387
- @@FillConsoleOutputAttribute.call(@@hConsoleHandle, attributes, get_screen_size.last - cursor_pos.x, cursor, written)
388
- end
389
-
390
- def self.scroll_down(val)
391
- return if val < 0
392
- return unless csbi = get_console_screen_buffer_info
393
- buffer_width, buffer_lines, x, y, attributes, window_left, window_top, window_bottom = csbi.unpack('ssssSssx2s')
394
- screen_height = window_bottom - window_top + 1
395
- val = screen_height if val > screen_height
396
-
397
- if @@legacy_console || window_left != 0
398
- # unless ENABLE_VIRTUAL_TERMINAL,
399
- # if srWindow.Left != 0 then it's conhost.exe hosted console
400
- # and puts "\n" causes horizontal scroll. its glitch.
401
- # FYI irb write from culumn 1, so this gives no gain.
402
- scroll_rectangle = [0, val, buffer_width, buffer_lines - val].pack('s4')
403
- destination_origin = 0 # y * 65536 + x
404
- fill = [' '.ord, attributes].pack('SS')
405
- @@ScrollConsoleScreenBuffer.call(@@hConsoleHandle, scroll_rectangle, nil, destination_origin, fill)
406
- else
407
- origin_x = x + 1
408
- origin_y = y - window_top + 1
409
- @@output.write [
410
- (origin_y != screen_height) ? "\e[#{screen_height};H" : nil,
411
- "\n" * val,
412
- (origin_y != screen_height or !x.zero?) ? "\e[#{origin_y};#{origin_x}H" : nil
413
- ].join
414
- end
412
+ call_with_console_handle(@FillConsoleOutputCharacter, 0x20, width - x, y * 65536 + x, written)
413
+ call_with_console_handle(@FillConsoleOutputAttribute, attributes, width - x, y * 65536 + x, written)
414
+ end
415
+
416
+ # This only works when the cursor is at the bottom of the scroll range
417
+ # For more details, see https://github.com/ruby/reline/pull/577#issuecomment-1646679623
418
+ def scroll_down(x)
419
+ return if x.zero?
420
+ # We use `\n` instead of CSI + S because CSI + S would cause https://github.com/ruby/reline/issues/576
421
+ @output.write "\n" * x
415
422
  end
416
423
 
417
- def self.clear_screen
418
- if @@legacy_console
419
- return unless csbi = get_console_screen_buffer_info
420
- buffer_width, _buffer_lines, attributes, window_top, window_bottom = csbi.unpack('ss@8S@12sx2s')
421
- fill_length = buffer_width * (window_bottom - window_top + 1)
422
- screen_topleft = window_top * 65536
424
+ def clear_screen
425
+ if @legacy_console
426
+ width, _, _, _, attributes, _, top, _, bottom = get_console_screen_buffer_info
427
+ return unless width
428
+ fill_length = width * (bottom - top + 1)
429
+ screen_topleft = top * 65536
423
430
  written = 0.chr * 4
424
- @@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, fill_length, screen_topleft, written)
425
- @@FillConsoleOutputAttribute.call(@@hConsoleHandle, attributes, fill_length, screen_topleft, written)
426
- @@SetConsoleCursorPosition.call(@@hConsoleHandle, screen_topleft)
431
+ call_with_console_handle(@FillConsoleOutputCharacter, 0x20, fill_length, screen_topleft, written)
432
+ call_with_console_handle(@FillConsoleOutputAttribute, attributes, fill_length, screen_topleft, written)
433
+ call_with_console_handle(@SetConsoleCursorPosition, screen_topleft)
427
434
  else
428
- @@output.write "\e[2J" "\e[H"
435
+ @output.write "\e[2J" "\e[H"
429
436
  end
430
437
  end
431
438
 
432
- def self.set_screen_size(rows, columns)
439
+ def set_screen_size(rows, columns)
433
440
  raise NotImplementedError
434
441
  end
435
442
 
436
- def self.hide_cursor
443
+ def hide_cursor
437
444
  size = 100
438
445
  visible = 0 # 0 means false
439
446
  cursor_info = [size, visible].pack('Li')
440
- @@SetConsoleCursorInfo.call(@@hConsoleHandle, cursor_info)
447
+ call_with_console_handle(@SetConsoleCursorInfo, cursor_info)
441
448
  end
442
449
 
443
- def self.show_cursor
450
+ def show_cursor
444
451
  size = 100
445
452
  visible = 1 # 1 means true
446
453
  cursor_info = [size, visible].pack('Li')
447
- @@SetConsoleCursorInfo.call(@@hConsoleHandle, cursor_info)
454
+ call_with_console_handle(@SetConsoleCursorInfo, cursor_info)
448
455
  end
449
456
 
450
- def self.set_winch_handler(&handler)
451
- @@winch_handler = handler
457
+ def set_winch_handler(&handler)
458
+ @winch_handler = handler
452
459
  end
453
460
 
454
- def self.prep
461
+ def prep
455
462
  # do nothing
456
463
  nil
457
464
  end
458
465
 
459
- def self.deprep(otio)
466
+ def deprep(otio)
460
467
  # do nothing
461
468
  end
462
469
 
470
+ def disable_auto_linewrap(setting = true, &block)
471
+ mode = getconsolemode
472
+ if 0 == (mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)
473
+ if block
474
+ begin
475
+ setconsolemode(mode & ~ENABLE_WRAP_AT_EOL_OUTPUT)
476
+ block.call
477
+ ensure
478
+ setconsolemode(mode | ENABLE_WRAP_AT_EOL_OUTPUT)
479
+ end
480
+ else
481
+ if setting
482
+ setconsolemode(mode & ~ENABLE_WRAP_AT_EOL_OUTPUT)
483
+ else
484
+ setconsolemode(mode | ENABLE_WRAP_AT_EOL_OUTPUT)
485
+ end
486
+ end
487
+ else
488
+ block.call if block
489
+ end
490
+ end
491
+
463
492
  class KeyEventRecord
464
493
 
465
494
  attr_reader :virtual_key_code, :char_code, :control_key_state, :control_keys
@@ -489,7 +518,7 @@ class Reline::Windows
489
518
  # Verifies if the arguments match with this key event.
490
519
  # Nil arguments are ignored, but at least one must be passed as non-nil.
491
520
  # To verify that no control keys were pressed, pass an empty array: `control_keys: []`.
492
- def matches?(control_keys: nil, virtual_key_code: nil, char_code: nil)
521
+ def match?(control_keys: nil, virtual_key_code: nil, char_code: nil)
493
522
  raise ArgumentError, 'No argument was passed to match key event' if control_keys.nil? && virtual_key_code.nil? && char_code.nil?
494
523
 
495
524
  (control_keys.nil? || [*control_keys].sort == @control_keys) &&
data/lib/reline/io.rb ADDED
@@ -0,0 +1,55 @@
1
+
2
+ module Reline
3
+ class IO
4
+ RESET_COLOR = "\e[0m"
5
+
6
+ def self.decide_io_gate
7
+ if ENV['TERM'] == 'dumb'
8
+ Reline::Dumb.new
9
+ else
10
+ require 'reline/io/ansi'
11
+
12
+ case RbConfig::CONFIG['host_os']
13
+ when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
14
+ require 'reline/io/windows'
15
+ io = Reline::Windows.new
16
+ if io.msys_tty?
17
+ Reline::ANSI.new
18
+ else
19
+ io
20
+ end
21
+ else
22
+ Reline::ANSI.new
23
+ end
24
+ end
25
+ end
26
+
27
+ def dumb?
28
+ false
29
+ end
30
+
31
+ def win?
32
+ false
33
+ end
34
+
35
+ def reset_color_sequence
36
+ self.class::RESET_COLOR
37
+ end
38
+
39
+ # Read a single encoding valid character from the input.
40
+ def read_single_char(keyseq_timeout)
41
+ buffer = String.new(encoding: Encoding::ASCII_8BIT)
42
+ loop do
43
+ timeout = buffer.empty? ? Float::INFINITY : keyseq_timeout
44
+ c = getc(timeout)
45
+ return unless c
46
+
47
+ buffer << c
48
+ encoded = buffer.dup.force_encoding(encoding)
49
+ return encoded if encoded.valid_encoding?
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ require 'reline/io/dumb'
@@ -1,19 +1,37 @@
1
1
  class Reline::KeyActor::Base
2
- MAPPING = Array.new(256)
2
+ def initialize(mappings = nil)
3
+ @matching_bytes = {}
4
+ @key_bindings = {}
5
+ add_mappings(mappings) if mappings
6
+ end
7
+
8
+ def add_mappings(mappings)
9
+ add([27], :ed_ignore)
10
+ 128.times do |key|
11
+ func = mappings[key]
12
+ meta_func = mappings[key | 0b10000000]
13
+ add([key], func) if func
14
+ add([27, key], meta_func) if meta_func
15
+ end
16
+ end
3
17
 
4
- def get_method(key)
5
- self.class::MAPPING[key]
18
+ def add(key, func)
19
+ (1...key.size).each do |size|
20
+ @matching_bytes[key.take(size)] = true
21
+ end
22
+ @key_bindings[key] = func
6
23
  end
7
24
 
8
- def initialize
9
- @default_key_bindings = {}
25
+ def matching?(key)
26
+ @matching_bytes[key]
10
27
  end
11
28
 
12
- def default_key_bindings
13
- @default_key_bindings
29
+ def get(key)
30
+ @key_bindings[key]
14
31
  end
15
32
 
16
- def reset_default_key_bindings
17
- @default_key_bindings.clear
33
+ def clear
34
+ @matching_bytes.clear
35
+ @key_bindings.clear
18
36
  end
19
37
  end
@@ -0,0 +1,17 @@
1
+ class Reline::KeyActor::Composite
2
+ def initialize(key_actors)
3
+ @key_actors = key_actors
4
+ end
5
+
6
+ def matching?(key)
7
+ @key_actors.any? { |key_actor| key_actor.matching?(key) }
8
+ end
9
+
10
+ def get(key)
11
+ @key_actors.each do |key_actor|
12
+ func = key_actor.get(key)
13
+ return func if func
14
+ end
15
+ nil
16
+ end
17
+ end