Class: Reline::Windows
Defined Under Namespace
Classes: KeyEventRecord, Win32API
Constant Summary collapse
- VK_RETURN =
0x0D
- VK_MENU =
ALT key
0x12
- VK_LMENU =
0xA4
- VK_CONTROL =
0x11
- VK_SHIFT =
0x10
- VK_DIVIDE =
0x6F
- KEY_EVENT =
0x01
- WINDOW_BUFFER_SIZE_EVENT =
0x04
- CAPSLOCK_ON =
0x0080
- ENHANCED_KEY =
0x0100
- LEFT_ALT_PRESSED =
0x0002
- LEFT_CTRL_PRESSED =
0x0008
- NUMLOCK_ON =
0x0020
- RIGHT_ALT_PRESSED =
0x0001
- RIGHT_CTRL_PRESSED =
0x0004
- SCROLLLOCK_ON =
0x0040
- SHIFT_PRESSED =
0x0010
- VK_TAB =
0x09
- VK_END =
0x23
- VK_HOME =
0x24
- VK_LEFT =
0x25
- VK_UP =
0x26
- VK_RIGHT =
0x27
- VK_DOWN =
0x28
- VK_DELETE =
0x2E
- STD_INPUT_HANDLE =
-10
- STD_OUTPUT_HANDLE =
-11
- FILE_TYPE_PIPE =
0x0003
- FILE_NAME_INFO =
2
- ENABLE_WRAP_AT_EOL_OUTPUT =
2
- ENABLE_VIRTUAL_TERMINAL_PROCESSING =
4
- KEY_MAP =
[ # It's treated as Meta+Enter on Windows. [ { control_keys: :CTRL, virtual_key_code: 0x0D }, "\e\r".bytes ], [ { control_keys: :SHIFT, virtual_key_code: 0x0D }, "\e\r".bytes ], # It's treated as Meta+Space on Windows. [ { control_keys: :CTRL, char_code: 0x20 }, "\e ".bytes ], # Emulate getwch() key sequences. [ { control_keys: [], virtual_key_code: VK_UP }, [0, 72] ], [ { control_keys: [], virtual_key_code: VK_DOWN }, [0, 80] ], [ { control_keys: [], virtual_key_code: VK_RIGHT }, [0, 77] ], [ { control_keys: [], virtual_key_code: VK_LEFT }, [0, 75] ], [ { control_keys: [], virtual_key_code: VK_DELETE }, [0, 83] ], [ { control_keys: [], virtual_key_code: VK_HOME }, [0, 71] ], [ { control_keys: [], virtual_key_code: VK_END }, [0, 79] ], # Emulate ANSI key sequence. [ { control_keys: :SHIFT, virtual_key_code: VK_TAB }, [27, 91, 90] ], ]
- ALTERNATIVE_CSBI =
[80, 24, 0, 0, 7, 0, 0, 79, 23].freeze
Constants inherited from IO
Instance Method Summary collapse
- #check_input_event ⇒ Object
- #clear_screen ⇒ Object
- #cursor_pos ⇒ Object
- #deprep(otio) ⇒ Object
- #disable_auto_linewrap(setting = true, &block) ⇒ Object
- #empty_buffer? ⇒ Boolean
- #encoding ⇒ Object
- #erase_after_cursor ⇒ Object
- #get_console_screen_buffer_info ⇒ Object
- #get_screen_size ⇒ Object
- #getc(_timeout_second) ⇒ Object
- #hide_cursor ⇒ Object
- #in_pasting? ⇒ Boolean
-
#initialize ⇒ Windows
constructor
A new instance of Windows.
- #move_cursor_column(val) ⇒ Object
- #move_cursor_down(val) ⇒ Object
- #move_cursor_up(val) ⇒ Object
-
#msys_tty?(io = @hConsoleInputHandle) ⇒ Boolean
if @legacy_console setconsolemode(getconsolemode() | ENABLE_VIRTUAL_TERMINAL_PROCESSING) @legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0) end.
- #prep ⇒ Object
- #process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state) ⇒ Object
-
#scroll_down(x) ⇒ Object
This only works when the cursor is at the bottom of the scroll range For more details, see github.com/ruby/reline/pull/577#issuecomment-1646679623.
- #set_default_key_bindings(config) ⇒ Object
- #set_screen_size(rows, columns) ⇒ Object
- #set_winch_handler(&handler) ⇒ Object
- #show_cursor ⇒ Object
- #ungetc(c) ⇒ Object
- #win? ⇒ Boolean
- #win_legacy_console? ⇒ Boolean
- #with_raw_input ⇒ Object
Methods inherited from IO
decide_io_gate, #dumb?, #reset_color_sequence
Constructor Details
#initialize ⇒ Windows
Returns a new instance of Windows.
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# File 'lib/reline/io/windows.rb', line 4 def initialize @input_buf = [] @output_buf = [] @output = STDOUT @hsg = nil @getwch = Win32API.new('msvcrt', '_getwch', [], 'I') @kbhit = Win32API.new('msvcrt', '_kbhit', [], 'I') @GetKeyState = Win32API.new('user32', 'GetKeyState', ['L'], 'L') @GetConsoleScreenBufferInfo = Win32API.new('kernel32', 'GetConsoleScreenBufferInfo', ['L', 'P'], 'L') @SetConsoleCursorPosition = Win32API.new('kernel32', 'SetConsoleCursorPosition', ['L', 'L'], 'L') @GetStdHandle = Win32API.new('kernel32', 'GetStdHandle', ['L'], 'L') @FillConsoleOutputCharacter = Win32API.new('kernel32', 'FillConsoleOutputCharacter', ['L', 'L', 'L', 'L', 'P'], 'L') @ScrollConsoleScreenBuffer = Win32API.new('kernel32', 'ScrollConsoleScreenBuffer', ['L', 'P', 'P', 'L', 'P'], 'L') @hConsoleHandle = @GetStdHandle.call(STD_OUTPUT_HANDLE) @hConsoleInputHandle = @GetStdHandle.call(STD_INPUT_HANDLE) @GetNumberOfConsoleInputEvents = Win32API.new('kernel32', 'GetNumberOfConsoleInputEvents', ['L', 'P'], 'L') @ReadConsoleInputW = Win32API.new('kernel32', 'ReadConsoleInputW', ['L', 'P', 'L', 'P'], 'L') @GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L') @GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I') @FillConsoleOutputAttribute = Win32API.new('kernel32', 'FillConsoleOutputAttribute', ['L', 'L', 'L', 'L', 'P'], 'L') @SetConsoleCursorInfo = Win32API.new('kernel32', 'SetConsoleCursorInfo', ['L', 'P'], 'L') @GetConsoleMode = Win32API.new('kernel32', 'GetConsoleMode', ['L', 'P'], 'L') @SetConsoleMode = Win32API.new('kernel32', 'SetConsoleMode', ['L', 'L'], 'L') @WaitForSingleObject = Win32API.new('kernel32', 'WaitForSingleObject', ['L', 'L'], 'L') @legacy_console = getconsolemode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 end |
Instance Method Details
#check_input_event ⇒ Object
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 |
# File 'lib/reline/io/windows.rb', line 270 def check_input_event num_of_events = 0.chr * 8 while @output_buf.empty? Reline.core.line_editor.handle_signal if @WaitForSingleObject.(@hConsoleInputHandle, 100) != 0 # max 0.1 sec # prevent for background consolemode change @legacy_console = getconsolemode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 next end next if @GetNumberOfConsoleInputEvents.(@hConsoleInputHandle, num_of_events) == 0 or num_of_events.unpack1('L') == 0 input_records = 0.chr * 20 * 80 read_event = 0.chr * 4 if @ReadConsoleInputW.(@hConsoleInputHandle, input_records, 80, read_event) != 0 read_events = read_event.unpack1('L') 0.upto(read_events) do |idx| input_record = input_records[idx * 20, 20] event = input_record[0, 2].unpack1('s*') case event when WINDOW_BUFFER_SIZE_EVENT @winch_handler.() when KEY_EVENT key_down = input_record[4, 4].unpack1('l*') repeat_count = input_record[8, 2].unpack1('s*') virtual_key_code = input_record[10, 2].unpack1('s*') virtual_scan_code = input_record[12, 2].unpack1('s*') char_code = input_record[14, 2].unpack1('S*') control_key_state = input_record[16, 2].unpack1('S*') is_key_down = key_down.zero? ? false : true if is_key_down process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state) end end end end end end |
#clear_screen ⇒ Object
414 415 416 417 418 419 420 421 422 423 424 425 426 427 |
# File 'lib/reline/io/windows.rb', line 414 def clear_screen if @legacy_console width, _, _, _, attributes, _, top, _, bottom = get_console_screen_buffer_info return unless width fill_length = width * (bottom - top + 1) screen_topleft = top * 65536 written = 0.chr * 4 call_with_console_handle(@FillConsoleOutputCharacter, 0x20, fill_length, screen_topleft, written) call_with_console_handle(@FillConsoleOutputAttribute, attributes, fill_length, screen_topleft, written) call_with_console_handle(@SetConsoleCursorPosition, screen_topleft) else @output.write "\e[2J" "\e[H" end end |
#cursor_pos ⇒ Object
363 364 365 366 |
# File 'lib/reline/io/windows.rb', line 363 def cursor_pos _, _, x, y, _, _, top, = get_console_screen_buffer_info || ALTERNATIVE_CSBI Reline::CursorPos.new(x, y - top) end |
#deprep(otio) ⇒ Object
456 457 458 |
# File 'lib/reline/io/windows.rb', line 456 def deprep(otio) # do nothing end |
#disable_auto_linewrap(setting = true, &block) ⇒ Object
460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 |
# File 'lib/reline/io/windows.rb', line 460 def disable_auto_linewrap(setting = true, &block) mode = getconsolemode if 0 == (mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) if block begin setconsolemode(mode & ~ENABLE_WRAP_AT_EOL_OUTPUT) block.call ensure setconsolemode(mode | ENABLE_WRAP_AT_EOL_OUTPUT) end else if setting setconsolemode(mode & ~ENABLE_WRAP_AT_EOL_OUTPUT) else setconsolemode(mode | ENABLE_WRAP_AT_EOL_OUTPUT) end end else block.call if block end end |
#empty_buffer? ⇒ Boolean
324 325 326 327 328 329 330 331 332 |
# File 'lib/reline/io/windows.rb', line 324 def empty_buffer? if not @output_buf.empty? false elsif @kbhit.call == 0 true else false end end |
#encoding ⇒ Object
34 35 36 |
# File 'lib/reline/io/windows.rb', line 34 def encoding Encoding::UTF_8 end |
#erase_after_cursor ⇒ Object
398 399 400 401 402 403 404 |
# File 'lib/reline/io/windows.rb', line 398 def erase_after_cursor width, _, x, y, attributes, = get_console_screen_buffer_info return unless x written = 0.chr * 4 call_with_console_handle(@FillConsoleOutputCharacter, 0x20, width - x, y * 65536 + x, written) call_with_console_handle(@FillConsoleOutputAttribute, attributes, width - x, y * 65536 + x, written) end |
#get_console_screen_buffer_info ⇒ Object
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 |
# File 'lib/reline/io/windows.rb', line 334 def get_console_screen_buffer_info # CONSOLE_SCREEN_BUFFER_INFO # [ 0,2] dwSize.X # [ 2,2] dwSize.Y # [ 4,2] dwCursorPositions.X # [ 6,2] dwCursorPositions.Y # [ 8,2] wAttributes # [10,2] srWindow.Left # [12,2] srWindow.Top # [14,2] srWindow.Right # [16,2] srWindow.Bottom # [18,2] dwMaximumWindowSize.X # [20,2] dwMaximumWindowSize.Y csbi = 0.chr * 22 if call_with_console_handle(@GetConsoleScreenBufferInfo, csbi) != 0 # returns [width, height, x, y, attributes, left, top, right, bottom] csbi.unpack("s9") else return nil end end |
#get_screen_size ⇒ Object
358 359 360 361 |
# File 'lib/reline/io/windows.rb', line 358 def get_screen_size width, _, _, _, _, _, top, _, bottom = get_console_screen_buffer_info || ALTERNATIVE_CSBI [bottom - top + 1, width] end |
#getc(_timeout_second) ⇒ Object
311 312 313 314 |
# File 'lib/reline/io/windows.rb', line 311 def getc(_timeout_second) check_input_event @output_buf.shift end |
#hide_cursor ⇒ Object
433 434 435 436 437 438 |
# File 'lib/reline/io/windows.rb', line 433 def hide_cursor size = 100 visible = 0 # 0 means false cursor_info = [size, visible].pack('Li') call_with_console_handle(@SetConsoleCursorInfo, cursor_info) end |
#in_pasting? ⇒ Boolean
320 321 322 |
# File 'lib/reline/io/windows.rb', line 320 def in_pasting? not empty_buffer? end |
#move_cursor_column(val) ⇒ Object
368 369 370 371 |
# File 'lib/reline/io/windows.rb', line 368 def move_cursor_column(val) _, _, _, y, = get_console_screen_buffer_info call_with_console_handle(@SetConsoleCursorPosition, y * 65536 + val) if y end |
#move_cursor_down(val) ⇒ Object
385 386 387 388 389 390 391 392 393 394 395 396 |
# File 'lib/reline/io/windows.rb', line 385 def move_cursor_down(val) if val > 0 _, _, x, y, _, _, top, _, bottom = get_console_screen_buffer_info return unless y screen_height = bottom - top y = (y - top) + val y = screen_height if y > screen_height call_with_console_handle(@SetConsoleCursorPosition, (y + top) * 65536 + x) elsif val < 0 move_cursor_up(-val) end end |
#move_cursor_up(val) ⇒ Object
373 374 375 376 377 378 379 380 381 382 383 |
# File 'lib/reline/io/windows.rb', line 373 def move_cursor_up(val) if val > 0 _, _, x, y, _, _, top, = get_console_screen_buffer_info return unless y y = (y - top) - val y = 0 if y < 0 call_with_console_handle(@SetConsoleCursorPosition, (y + top) * 65536 + x) elsif val < 0 move_cursor_down(-val) end end |
#msys_tty?(io = @hConsoleInputHandle) ⇒ Boolean
if @legacy_console
setconsolemode(getconsolemode() | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
@legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0)
end
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/reline/io/windows.rb', line 188 def msys_tty?(io = @hConsoleInputHandle) # check if fd is a pipe if @GetFileType.call(io) != FILE_TYPE_PIPE return false end bufsize = 1024 p_buffer = "\0" * bufsize res = @GetFileInformationByHandleEx.call(io, FILE_NAME_INFO, p_buffer, bufsize - 2) return false if res == 0 # get pipe name: p_buffer layout is: # struct _FILE_NAME_INFO { # DWORD FileNameLength; # WCHAR FileName[1]; # } FILE_NAME_INFO len = p_buffer[0, 4].unpack1("L") name = p_buffer[4, len].encode(Encoding::UTF_8, Encoding::UTF_16LE, invalid: :replace) # Check if this could be a MSYS2 pty pipe ('\msys-XXXX-ptyN-XX') # or a cygwin pty pipe ('\cygwin-XXXX-ptyN-XX') name =~ /(msys-|cygwin-).*-pty/ ? true : false end |
#prep ⇒ Object
451 452 453 454 |
# File 'lib/reline/io/windows.rb', line 451 def prep # do nothing nil end |
#process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state) ⇒ Object
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 |
# File 'lib/reline/io/windows.rb', line 233 def process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state) # high-surrogate if 0xD800 <= char_code and char_code <= 0xDBFF @hsg = char_code return end # low-surrogate if 0xDC00 <= char_code and char_code <= 0xDFFF if @hsg char_code = 0x10000 + (@hsg - 0xD800) * 0x400 + char_code - 0xDC00 @hsg = nil else # no high-surrogate. ignored. return end else # ignore high-surrogate without low-surrogate if there @hsg = nil end key = KeyEventRecord.new(virtual_key_code, char_code, control_key_state) match = KEY_MAP.find { |args,| key.match?(**args) } unless match.nil? @output_buf.concat(match.last) return end # no char, only control keys return if key.char_code == 0 and key.control_keys.any? @output_buf.push("\e".ord) if key.control_keys.include?(:ALT) and !key.control_keys.include?(:CTRL) @output_buf.concat(key.char.bytes) end |
#scroll_down(x) ⇒ Object
This only works when the cursor is at the bottom of the scroll range For more details, see github.com/ruby/reline/pull/577#issuecomment-1646679623
408 409 410 411 412 |
# File 'lib/reline/io/windows.rb', line 408 def scroll_down(x) return if x.zero? # We use `\n` instead of CSI + S because CSI + S would cause https://github.com/ruby/reline/issues/576 @output.write "\n" * x end |
#set_default_key_bindings(config) ⇒ Object
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/reline/io/windows.rb', line 46 def set_default_key_bindings(config) { [224, 72] => :ed_prev_history, # ↑ [224, 80] => :ed_next_history, # ↓ [224, 77] => :ed_next_char, # → [224, 75] => :ed_prev_char, # ← [224, 83] => :key_delete, # Del [224, 71] => :ed_move_to_beg, # Home [224, 79] => :ed_move_to_end, # End [ 0, 41] => :ed_unassigned, # input method on/off [ 0, 72] => :ed_prev_history, # ↑ [ 0, 80] => :ed_next_history, # ↓ [ 0, 77] => :ed_next_char, # → [ 0, 75] => :ed_prev_char, # ← [ 0, 83] => :key_delete, # Del [ 0, 71] => :ed_move_to_beg, # Home [ 0, 79] => :ed_move_to_end # End }.each_pair do |key, func| config.add_default_key_binding_by_keymap(:emacs, key, func) config.add_default_key_binding_by_keymap(:vi_insert, key, func) config.add_default_key_binding_by_keymap(:vi_command, key, func) end { [27, 32] => :em_set_mark, # M-<space> [24, 24] => :em_exchange_mark, # C-x C-x }.each_pair do |key, func| config.add_default_key_binding_by_keymap(:emacs, key, func) end # Emulate ANSI key sequence. { [27, 91, 90] => :completion_journey_up, # S-Tab }.each_pair do |key, func| config.add_default_key_binding_by_keymap(:emacs, key, func) config.add_default_key_binding_by_keymap(:vi_insert, key, func) end end |
#set_screen_size(rows, columns) ⇒ Object
429 430 431 |
# File 'lib/reline/io/windows.rb', line 429 def set_screen_size(rows, columns) raise NotImplementedError end |
#set_winch_handler(&handler) ⇒ Object
447 448 449 |
# File 'lib/reline/io/windows.rb', line 447 def set_winch_handler(&handler) @winch_handler = handler end |
#show_cursor ⇒ Object
440 441 442 443 444 445 |
# File 'lib/reline/io/windows.rb', line 440 def show_cursor size = 100 visible = 1 # 1 means true cursor_info = [size, visible].pack('Li') call_with_console_handle(@SetConsoleCursorInfo, cursor_info) end |
#ungetc(c) ⇒ Object
316 317 318 |
# File 'lib/reline/io/windows.rb', line 316 def ungetc(c) @output_buf.unshift(c) end |
#win? ⇒ Boolean
38 39 40 |
# File 'lib/reline/io/windows.rb', line 38 def win? true end |
#win_legacy_console? ⇒ Boolean
42 43 44 |
# File 'lib/reline/io/windows.rb', line 42 def win_legacy_console? @legacy_console end |
#with_raw_input ⇒ Object
307 308 309 |
# File 'lib/reline/io/windows.rb', line 307 def with_raw_input yield end |