reline 0.1.5 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,23 +9,48 @@ class Reline::Windows
9
9
  true
10
10
  end
11
11
 
12
- RAW_KEYSTROKE_CONFIG = {
13
- [224, 72] => :ed_prev_history, # ↑
14
- [224, 80] => :ed_next_history, # ↓
15
- [224, 77] => :ed_next_char, # →
16
- [224, 75] => :ed_prev_char, # ←
17
- [224, 83] => :key_delete, # Del
18
- [224, 71] => :ed_move_to_beg, # Home
19
- [224, 79] => :ed_move_to_end, # End
20
- [ 0, 41] => :ed_unassigned, # input method on/off
21
- [ 0, 72] => :ed_prev_history, #
22
- [ 0, 80] => :ed_next_history, #
23
- [ 0, 77] => :ed_next_char, #
24
- [ 0, 75] => :ed_prev_char, #
25
- [ 0, 83] => :key_delete, # Del
26
- [ 0, 71] => :ed_move_to_beg, # Home
27
- [ 0, 79] => :ed_move_to_end # End
28
- }
12
+ def self.win_legacy_console?
13
+ @@legacy_console
14
+ end
15
+
16
+ def self.set_default_key_bindings(config)
17
+ {
18
+ [224, 72] => :ed_prev_history, #
19
+ [224, 80] => :ed_next_history, #
20
+ [224, 77] => :ed_next_char, #
21
+ [224, 75] => :ed_prev_char, #
22
+ [224, 83] => :key_delete, # Del
23
+ [224, 71] => :ed_move_to_beg, # Home
24
+ [224, 79] => :ed_move_to_end, # End
25
+ [ 0, 41] => :ed_unassigned, # input method on/off
26
+ [ 0, 72] => :ed_prev_history, #
27
+ [ 0, 80] => :ed_next_history, #
28
+ [ 0, 77] => :ed_next_char, # →
29
+ [ 0, 75] => :ed_prev_char, # ←
30
+ [ 0, 83] => :key_delete, # Del
31
+ [ 0, 71] => :ed_move_to_beg, # Home
32
+ [ 0, 79] => :ed_move_to_end # End
33
+ }.each_pair do |key, func|
34
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
35
+ config.add_default_key_binding_by_keymap(:vi_insert, key, func)
36
+ config.add_default_key_binding_by_keymap(:vi_command, key, func)
37
+ end
38
+
39
+ {
40
+ [27, 32] => :em_set_mark, # M-<space>
41
+ [24, 24] => :em_exchange_mark, # C-x C-x
42
+ }.each_pair do |key, func|
43
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
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
53
+ end
29
54
 
30
55
  if defined? JRUBY_VERSION
31
56
  require 'win32api'
@@ -69,13 +94,37 @@ class Reline::Windows
69
94
  end
70
95
  end
71
96
 
97
+ VK_RETURN = 0x0D
72
98
  VK_MENU = 0x12
73
99
  VK_LMENU = 0xA4
74
100
  VK_CONTROL = 0x11
75
101
  VK_SHIFT = 0x10
102
+ VK_DIVIDE = 0x6F
103
+
104
+ KEY_EVENT = 0x01
105
+ WINDOW_BUFFER_SIZE_EVENT = 0x04
106
+
107
+ CAPSLOCK_ON = 0x0080
108
+ ENHANCED_KEY = 0x0100
109
+ LEFT_ALT_PRESSED = 0x0002
110
+ LEFT_CTRL_PRESSED = 0x0008
111
+ NUMLOCK_ON = 0x0020
112
+ RIGHT_ALT_PRESSED = 0x0001
113
+ RIGHT_CTRL_PRESSED = 0x0004
114
+ SCROLLLOCK_ON = 0x0040
115
+ SHIFT_PRESSED = 0x0010
116
+
117
+ VK_TAB = 0x09
118
+ VK_END = 0x23
119
+ VK_HOME = 0x24
120
+ VK_LEFT = 0x25
121
+ VK_UP = 0x26
122
+ VK_RIGHT = 0x27
123
+ VK_DOWN = 0x28
124
+ VK_DELETE = 0x2E
125
+
76
126
  STD_INPUT_HANDLE = -10
77
127
  STD_OUTPUT_HANDLE = -11
78
- WINDOW_BUFFER_SIZE_EVENT = 0x04
79
128
  FILE_TYPE_PIPE = 0x0003
80
129
  FILE_NAME_INFO = 2
81
130
  @@getwch = Win32API.new('msvcrt', '_getwch', [], 'I')
@@ -89,15 +138,39 @@ class Reline::Windows
89
138
  @@hConsoleHandle = @@GetStdHandle.call(STD_OUTPUT_HANDLE)
90
139
  @@hConsoleInputHandle = @@GetStdHandle.call(STD_INPUT_HANDLE)
91
140
  @@GetNumberOfConsoleInputEvents = Win32API.new('kernel32', 'GetNumberOfConsoleInputEvents', ['L', 'P'], 'L')
92
- @@ReadConsoleInput = Win32API.new('kernel32', 'ReadConsoleInput', ['L', 'P', 'L', 'P'], 'L')
141
+ @@ReadConsoleInputW = Win32API.new('kernel32', 'ReadConsoleInputW', ['L', 'P', 'L', 'P'], 'L')
93
142
  @@GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L')
94
143
  @@GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I')
95
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')
150
+ ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
151
+
152
+ private_class_method def self.getconsolemode
153
+ mode = "\000\000\000\000"
154
+ @@GetConsoleMode.call(@@hConsoleHandle, mode)
155
+ mode.unpack1('L')
156
+ end
157
+
158
+ private_class_method def self.setconsolemode(mode)
159
+ @@SetConsoleMode.call(@@hConsoleHandle, mode)
160
+ end
161
+
162
+ @@legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0)
163
+ #if @@legacy_console
164
+ # setconsolemode(getconsolemode() | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
165
+ # @@legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0)
166
+ #end
96
167
 
97
168
  @@input_buf = []
98
169
  @@output_buf = []
99
170
 
100
- def self.msys_tty?(io=@@hConsoleInputHandle)
171
+ @@output = STDOUT
172
+
173
+ def self.msys_tty?(io = @@hConsoleInputHandle)
101
174
  # check if fd is a pipe
102
175
  if @@GetFileType.call(io) != FILE_TYPE_PIPE
103
176
  return false
@@ -113,7 +186,7 @@ class Reline::Windows
113
186
  # DWORD FileNameLength;
114
187
  # WCHAR FileName[1];
115
188
  # } FILE_NAME_INFO
116
- len = p_buffer[0, 4].unpack("L")[0]
189
+ len = p_buffer[0, 4].unpack1("L")
117
190
  name = p_buffer[4, len].encode(Encoding::UTF_8, Encoding::UTF_16LE, invalid: :replace)
118
191
 
119
192
  # Check if this could be a MSYS2 pty pipe ('\msys-XXXX-ptyN-XX')
@@ -121,95 +194,157 @@ class Reline::Windows
121
194
  name =~ /(msys-|cygwin-).*-pty/ ? true : false
122
195
  end
123
196
 
124
- def self.getwch
125
- unless @@input_buf.empty?
126
- return @@input_buf.shift
127
- end
128
- while @@kbhit.call == 0
129
- sleep(0.001)
197
+ KEY_MAP = [
198
+ # It's treated as Meta+Enter on Windows.
199
+ [ { control_keys: :CTRL, virtual_key_code: 0x0D }, "\e\r".bytes ],
200
+ [ { control_keys: :SHIFT, virtual_key_code: 0x0D }, "\e\r".bytes ],
201
+
202
+ # It's treated as Meta+Space on Windows.
203
+ [ { control_keys: :CTRL, char_code: 0x20 }, "\e ".bytes ],
204
+
205
+ # Emulate getwch() key sequences.
206
+ [ { control_keys: [], virtual_key_code: VK_UP }, [0, 72] ],
207
+ [ { control_keys: [], virtual_key_code: VK_DOWN }, [0, 80] ],
208
+ [ { control_keys: [], virtual_key_code: VK_RIGHT }, [0, 77] ],
209
+ [ { control_keys: [], virtual_key_code: VK_LEFT }, [0, 75] ],
210
+ [ { control_keys: [], virtual_key_code: VK_DELETE }, [0, 83] ],
211
+ [ { control_keys: [], virtual_key_code: VK_HOME }, [0, 71] ],
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] ],
216
+ ]
217
+
218
+ @@hsg = nil
219
+
220
+ def self.process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
221
+
222
+ # high-surrogate
223
+ if 0xD800 <= char_code and char_code <= 0xDBFF
224
+ @@hsg = char_code
225
+ return
130
226
  end
131
- until @@kbhit.call == 0
132
- ret = @@getwch.call
133
- if ret == 0 or ret == 0xE0
134
- @@input_buf << ret
135
- ret = @@getwch.call
136
- @@input_buf << ret
137
- return @@input_buf.shift
138
- end
139
- begin
140
- bytes = ret.chr(Encoding::UTF_8).bytes
141
- @@input_buf.push(*bytes)
142
- rescue Encoding::UndefinedConversionError
143
- @@input_buf << ret
144
- @@input_buf << @@getwch.call if ret == 224
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
145
235
  end
236
+ else
237
+ # ignore high-surrogate without low-surrogate if there
238
+ @@hsg = nil
239
+ end
240
+
241
+ key = KeyEventRecord.new(virtual_key_code, char_code, control_key_state)
242
+
243
+ match = KEY_MAP.find { |args,| key.matches?(**args) }
244
+ unless match.nil?
245
+ @@output_buf.concat(match.last)
246
+ return
146
247
  end
147
- @@input_buf.shift
248
+
249
+ # no char, only control keys
250
+ return if key.char_code == 0 and key.control_keys.any?
251
+
252
+ @@output_buf.push("\e".ord) if key.control_keys.include?(:ALT)
253
+
254
+ @@output_buf.concat(key.char.bytes)
148
255
  end
149
256
 
150
- def self.getc
257
+ def self.check_input_event
151
258
  num_of_events = 0.chr * 8
152
- while @@GetNumberOfConsoleInputEvents.(@@hConsoleInputHandle, num_of_events) != 0 and num_of_events.unpack('L').first > 0
153
- 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
154
268
  read_event = 0.chr * 4
155
- if @@ReadConsoleInput.(@@hConsoleInputHandle, input_record, 1, read_event) != 0
156
- event = input_record[0, 2].unpack('s*').first
157
- if event == WINDOW_BUFFER_SIZE_EVENT
158
- @@winch_handler.()
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
288
+ end
159
289
  end
160
290
  end
161
291
  end
162
- unless @@output_buf.empty?
163
- return @@output_buf.shift
164
- end
165
- input = getwch
166
- meta = (@@GetKeyState.call(VK_LMENU) & 0x80) != 0
167
- control = (@@GetKeyState.call(VK_CONTROL) & 0x80) != 0
168
- shift = (@@GetKeyState.call(VK_SHIFT) & 0x80) != 0
169
- force_enter = !input.instance_of?(Array) && (control or shift) && input == 0x0D
170
- if force_enter
171
- # It's treated as Meta+Enter on Windows
172
- @@output_buf.push("\e".ord)
173
- @@output_buf.push(input)
174
- else
175
- case input
176
- when 0x00
177
- meta = false
178
- @@output_buf.push(input)
179
- input = getwch
180
- @@output_buf.push(*input)
181
- when 0xE0
182
- @@output_buf.push(input)
183
- input = getwch
184
- @@output_buf.push(*input)
185
- when 0x03
186
- @@output_buf.push(input)
187
- else
188
- @@output_buf.push(input)
189
- end
190
- end
191
- if meta
192
- "\e".ord
193
- else
194
- @@output_buf.shift
195
- end
292
+ end
293
+
294
+ def self.getc
295
+ check_input_event
296
+ @@output_buf.shift
196
297
  end
197
298
 
198
299
  def self.ungetc(c)
199
300
  @@output_buf.unshift(c)
200
301
  end
201
302
 
202
- def self.get_screen_size
303
+ def self.in_pasting?
304
+ not self.empty_buffer?
305
+ end
306
+
307
+ def self.empty_buffer?
308
+ if not @@output_buf.empty?
309
+ false
310
+ elsif @@kbhit.call == 0
311
+ true
312
+ else
313
+ false
314
+ end
315
+ end
316
+
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
203
330
  csbi = 0.chr * 22
204
- @@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
205
339
  csbi[0, 4].unpack('SS').reverse
206
340
  end
207
341
 
208
342
  def self.cursor_pos
209
- csbi = 0.chr * 22
210
- @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi)
211
- x = csbi[4, 2].unpack('s*').first
212
- 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')
213
348
  Reline::CursorPos.new(x, y)
214
349
  end
215
350
 
@@ -219,7 +354,9 @@ class Reline::Windows
219
354
 
220
355
  def self.move_cursor_up(val)
221
356
  if val > 0
222
- @@SetConsoleCursorPosition.call(@@hConsoleHandle, (cursor_pos.y - val) * 65536 + cursor_pos.x)
357
+ y = cursor_pos.y - val
358
+ y = 0 if y < 0
359
+ @@SetConsoleCursorPosition.call(@@hConsoleHandle, y * 65536 + cursor_pos.x)
223
360
  elsif val < 0
224
361
  move_cursor_down(-val)
225
362
  end
@@ -227,6 +364,10 @@ class Reline::Windows
227
364
 
228
365
  def self.move_cursor_down(val)
229
366
  if val > 0
367
+ return unless csbi = get_console_screen_buffer_info
368
+ screen_height = get_screen_size.first
369
+ y = cursor_pos.y + val
370
+ y = screen_height - 1 if y > (screen_height - 1)
230
371
  @@SetConsoleCursorPosition.call(@@hConsoleHandle, (cursor_pos.y + val) * 65536 + cursor_pos.x)
231
372
  elsif val < 0
232
373
  move_cursor_up(-val)
@@ -234,39 +375,74 @@ class Reline::Windows
234
375
  end
235
376
 
236
377
  def self.erase_after_cursor
237
- csbi = 0.chr * 24
238
- @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi)
239
- 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')
240
381
  written = 0.chr * 4
241
382
  @@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, get_screen_size.last - cursor_pos.x, cursor, written)
383
+ @@FillConsoleOutputAttribute.call(@@hConsoleHandle, attributes, get_screen_size.last - cursor_pos.x, cursor, written)
242
384
  end
243
385
 
244
386
  def self.scroll_down(val)
245
- return if val.zero?
246
- scroll_rectangle = [0, val, get_screen_size.last, get_screen_size.first].pack('s4')
247
- destination_origin = 0 # y * 65536 + x
248
- fill = [' '.ord, 0].pack('SS')
249
- @@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, buffer_lines, x, y, 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
250
411
  end
251
412
 
252
413
  def self.clear_screen
253
- csbi = 0.chr * 22
254
- return if @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi) == 0
255
- buffer_width = csbi[0, 2].unpack('S').first
256
- attributes = csbi[8, 2].unpack('S').first
257
- _window_left, window_top, _window_right, window_bottom = *csbi[10,8].unpack('S*')
258
- fill_length = buffer_width * (window_bottom - window_top + 1)
259
- screen_topleft = window_top * 65536
260
- written = 0.chr * 4
261
- @@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, fill_length, screen_topleft, written)
262
- @@FillConsoleOutputAttribute.call(@@hConsoleHandle, attributes, fill_length, screen_topleft, written)
263
- @@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
264
426
  end
265
427
 
266
428
  def self.set_screen_size(rows, columns)
267
429
  raise NotImplementedError
268
430
  end
269
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
+
270
446
  def self.set_winch_handler(&handler)
271
447
  @@winch_handler = handler
272
448
  end
@@ -279,4 +455,43 @@ class Reline::Windows
279
455
  def self.deprep(otio)
280
456
  # do nothing
281
457
  end
458
+
459
+ class KeyEventRecord
460
+
461
+ attr_reader :virtual_key_code, :char_code, :control_key_state, :control_keys
462
+
463
+ def initialize(virtual_key_code, char_code, control_key_state)
464
+ @virtual_key_code = virtual_key_code
465
+ @char_code = char_code
466
+ @control_key_state = control_key_state
467
+ @enhanced = control_key_state & ENHANCED_KEY != 0
468
+
469
+ (@control_keys = []).tap do |control_keys|
470
+ # symbols must be sorted to make comparison is easier later on
471
+ control_keys << :ALT if control_key_state & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) != 0
472
+ control_keys << :CTRL if control_key_state & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) != 0
473
+ control_keys << :SHIFT if control_key_state & SHIFT_PRESSED != 0
474
+ end.freeze
475
+ end
476
+
477
+ def char
478
+ @char_code.chr(Encoding::UTF_8)
479
+ end
480
+
481
+ def enhanced?
482
+ @enhanced
483
+ end
484
+
485
+ # Verifies if the arguments match with this key event.
486
+ # Nil arguments are ignored, but at least one must be passed as non-nil.
487
+ # To verify that no control keys were pressed, pass an empty array: `control_keys: []`.
488
+ def matches?(control_keys: nil, virtual_key_code: nil, char_code: nil)
489
+ raise ArgumentError, 'No argument was passed to match key event' if control_keys.nil? && virtual_key_code.nil? && char_code.nil?
490
+
491
+ (control_keys.nil? || [*control_keys].sort == @control_keys) &&
492
+ (virtual_key_code.nil? || @virtual_key_code == virtual_key_code) &&
493
+ (char_code.nil? || char_code == @char_code)
494
+ end
495
+
496
+ end
282
497
  end