# class Reline::Windows

### Constants

ALTERNATIVE_CSBI
CAPSLOCK_ON
ENABLE_VIRTUAL_TERMINAL_PROCESSING
ENABLE_WRAP_AT_EOL_OUTPUT
ENHANCED_KEY
FILE_NAME_INFO
FILE_TYPE_PIPE
KEY_EVENT
KEY_MAP
LEFT_ALT_PRESSED
LEFT_CTRL_PRESSED
NUMLOCK_ON
RIGHT_ALT_PRESSED
RIGHT_CTRL_PRESSED
SCROLLLOCK_ON
SHIFT_PRESSED
STD_INPUT_HANDLE
STD_OUTPUT_HANDLE
VK_CONTROL
VK_DELETE
VK_DIVIDE
VK_DOWN
VK_END
VK_HOME
VK_LEFT
VK_LMENU
VK_MENU
VK_RETURN
VK_RIGHT
VK_SHIFT
VK_TAB
VK_UP
WINDOW_BUFFER_SIZE_EVENT

### Attributes

output [W]

### Public Class Methods

new ()
Source

    # File lib/reline/io/windows.rb, line 7
    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

### Public Instance Methods

buffered_output () { || ... }
Source

    # File lib/reline/io/windows.rb, line 317
    def buffered_output
      yield
    end

check_input_event ()
Source

    # File lib/reline/io/windows.rb, line 272
    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 ()
Source

    # File lib/reline/io/windows.rb, line 424
    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 ()
Source

    # File lib/reline/io/windows.rb, line 373
    def cursor_pos
      _, _, x, y, _, _, top, = get_console_screen_buffer_info || ALTERNATIVE_CSBI
      Reline::CursorPos.new(x, y - top)
    end

deprep (otio)
Source

    # File lib/reline/io/windows.rb, line 466
    def deprep(otio)
      # do nothing
    end

disable_auto_linewrap (setting = true, &block)
Source

    # File lib/reline/io/windows.rb, line 470
    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? ()
Source

    # File lib/reline/io/windows.rb, line 334
    def empty_buffer?
      if not @output_buf.empty?
        false
      elsif @kbhit.call == 0
        true
      else
        false
      end
    end

encoding ()
Source

    # File lib/reline/io/windows.rb, line 37
    def encoding
      Encoding::UTF_8
    end

erase_after_cursor ()
Source

    # File lib/reline/io/windows.rb, line 408
    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 ()
Source

    # File lib/reline/io/windows.rb, line 344
    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 ()
Source

    # File lib/reline/io/windows.rb, line 368
    def get_screen_size
      width, _, _, _, _, _, top, _, bottom = get_console_screen_buffer_info || ALTERNATIVE_CSBI
      [bottom - top + 1, width]
    end

getc (_timeout_second)
Source

    # File lib/reline/io/windows.rb, line 321
    def getc(_timeout_second)
      check_input_event
      @output_buf.shift
    end

hide_cursor ()
Source

    # File lib/reline/io/windows.rb, line 443
    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? ()
Source

    # File lib/reline/io/windows.rb, line 330
    def in_pasting?
      not empty_buffer?
    end

move_cursor_column (val)
Source

    # File lib/reline/io/windows.rb, line 378
    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)
Source

    # File lib/reline/io/windows.rb, line 395
    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)
Source

    # File lib/reline/io/windows.rb, line 383
    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)
Source

    # File lib/reline/io/windows.rb, line 190
    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

if @legacy_console

    setconsolemode(getconsolemode() | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
    @legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0)

end

prep ()
Source

    # File lib/reline/io/windows.rb, line 461
    def prep
      # do nothing
      nil
    end

process_key_event (repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
Source

    # File lib/reline/io/windows.rb, line 235
    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)
Source

    # File lib/reline/io/windows.rb, line 418
    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

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)
Source

    # File lib/reline/io/windows.rb, line 49
    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, 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)
Source

    # File lib/reline/io/windows.rb, line 439
    def set_screen_size(rows, columns)
      raise NotImplementedError
    end

set_winch_handler (&handler)
Source

    # File lib/reline/io/windows.rb, line 457
    def set_winch_handler(&handler)
      @winch_handler = handler
    end

show_cursor ()
Source

    # File lib/reline/io/windows.rb, line 450
    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)
Source

    # File lib/reline/io/windows.rb, line 326
    def ungetc(c)
      @output_buf.unshift(c)
    end

win? ()
Source

    # File lib/reline/io/windows.rb, line 41
    def win?
      true
    end

win_legacy_console? ()
Source

    # File lib/reline/io/windows.rb, line 45
    def win_legacy_console?
      @legacy_console
    end

with_raw_input () { || ... }
Source

    # File lib/reline/io/windows.rb, line 309
    def with_raw_input
      yield
    end

write (string)
Source

    # File lib/reline/io/windows.rb, line 313
    def write(string)
      @output.write(string)
    end

### Private Instance Methods

call_with_console_handle (win32func, *args)
Source

    # File lib/reline/io/windows.rb, line 167
            def call_with_console_handle(win32func, *args)
      val = win32func.call(@hConsoleHandle, *args)
      return val if val != 0

      @hConsoleHandle = @GetStdHandle.call(STD_OUTPUT_HANDLE)
      win32func.call(@hConsoleHandle, *args)
    end

Calling `Win32API` with console handle is reported to fail after executing some external command. We need to refresh console handle and retry the call again.

getconsolemode ()
Source

    # File lib/reline/io/windows.rb, line 175
            def getconsolemode
      mode = +"\0\0\0\0"
      call_with_console_handle(@GetConsoleMode, mode)
      mode.unpack1('L')
    end

setconsolemode (mode)
Source

    # File lib/reline/io/windows.rb, line 181
            def setconsolemode(mode)
      call_with_console_handle(@SetConsoleMode, mode)
    end