# class Reline::Windows

### Constants

CAPSLOCK_ON
ENABLE_VIRTUAL_TERMINAL_PROCESSING
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

### Public Class Methods

check_input_event() click to toggle source

    # File lib/reline/windows.rb, line 257
    def self.check_input_event
      num_of_events = 0.chr * 8
      while @@output_buf.empty?
        Reline.core.line_editor.resize
        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() click to toggle source

    # File lib/reline/windows.rb, line 413
    def self.clear_screen
      if @@legacy_console
        return unless csbi = get_console_screen_buffer_info
        buffer_width, _buffer_lines, attributes, window_top, window_bottom = csbi.unpack('ss@8S@12sx2s')
        fill_length = buffer_width * (window_bottom - window_top + 1)
        screen_topleft = window_top * 65536
        written = 0.chr * 4
        @@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, fill_length, screen_topleft, written)
        @@FillConsoleOutputAttribute.call(@@hConsoleHandle, attributes, fill_length, screen_topleft, written)
        @@SetConsoleCursorPosition.call(@@hConsoleHandle, screen_topleft)
      else
        @@output.write "\e[2J" "\e[H"
      end
    end

cursor_pos() click to toggle source

    # File lib/reline/windows.rb, line 342
    def self.cursor_pos
      unless csbi = get_console_screen_buffer_info
        return Reline::CursorPos.new(0, 0)
      end
      x = csbi[4, 2].unpack1('s')
      y = csbi[6, 2].unpack1('s')
      Reline::CursorPos.new(x, y)
    end

deprep(otio) click to toggle source

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

empty_buffer?() click to toggle source

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

encoding() click to toggle source

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

erase_after_cursor() click to toggle source

    # File lib/reline/windows.rb, line 377
    def self.erase_after_cursor
      return unless csbi = get_console_screen_buffer_info
      attributes = csbi[8, 2].unpack1('S')
      cursor = csbi[4, 4].unpack1('L')
      written = 0.chr * 4
      @@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, get_screen_size.last - cursor_pos.x, cursor, written)
      @@FillConsoleOutputAttribute.call(@@hConsoleHandle, attributes, get_screen_size.last - cursor_pos.x, cursor, written)
    end

get_console_screen_buffer_info() click to toggle source

    # File lib/reline/windows.rb, line 317
    def self.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
      return if @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi) == 0
      csbi
    end

get_screen_size() click to toggle source

    # File lib/reline/windows.rb, line 335
    def self.get_screen_size
      unless csbi = get_console_screen_buffer_info
        return [1, 1]
      end
      csbi[0, 4].unpack('SS').reverse
    end

getc() click to toggle source

    # File lib/reline/windows.rb, line 294
    def self.getc
      check_input_event
      @@output_buf.shift
    end

hide_cursor() click to toggle source

    # File lib/reline/windows.rb, line 432
    def self.hide_cursor
      size = 100
      visible = 0 # 0 means false
      cursor_info = [size, visible].pack('Li')
      @@SetConsoleCursorInfo.call(@@hConsoleHandle, cursor_info)
    end

in_pasting?() click to toggle source

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

move_cursor_column(val) click to toggle source

    # File lib/reline/windows.rb, line 351
    def self.move_cursor_column(val)
      @@SetConsoleCursorPosition.call(@@hConsoleHandle, cursor_pos.y * 65536 + val)
    end

move_cursor_down(val) click to toggle source

    # File lib/reline/windows.rb, line 365
    def self.move_cursor_down(val)
      if val > 0
        return unless csbi = get_console_screen_buffer_info
        screen_height = get_screen_size.first
        y = cursor_pos.y + val
        y = screen_height - 1 if y > (screen_height - 1)
        @@SetConsoleCursorPosition.call(@@hConsoleHandle, (cursor_pos.y + val) * 65536 + cursor_pos.x)
      elsif val < 0
        move_cursor_up(-val)
      end
    end

move_cursor_up(val) click to toggle source

    # File lib/reline/windows.rb, line 355
    def self.move_cursor_up(val)
      if val > 0
        y = cursor_pos.y - val
        y = 0 if y < 0
        @@SetConsoleCursorPosition.call(@@hConsoleHandle, y * 65536 + cursor_pos.x)
      elsif val < 0
        move_cursor_down(-val)
      end
    end

msys_tty?(io = @@hConsoleInputHandle) click to toggle source

    # File lib/reline/windows.rb, line 173
    def self.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() click to toggle source

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

process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state) click to toggle source

    # File lib/reline/windows.rb, line 220
    def self.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.matches?(**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(val) click to toggle source

    # File lib/reline/windows.rb, line 386
    def self.scroll_down(val)
      return if val < 0
      return unless csbi = get_console_screen_buffer_info
      buffer_width, buffer_lines, x, y, attributes, window_left, window_top, window_bottom = csbi.unpack('ssssSssx2s')
      screen_height = window_bottom - window_top + 1
      val = screen_height if val > screen_height

      if @@legacy_console || window_left != 0
        # unless ENABLE_VIRTUAL_TERMINAL,
        # if srWindow.Left != 0 then it's conhost.exe hosted console
        # and puts "\n" causes horizontal scroll. its glitch.
        # FYI irb write from culumn 1, so this gives no gain.
        scroll_rectangle = [0, val, buffer_width, buffer_lines - val].pack('s4')
        destination_origin = 0 # y * 65536 + x
        fill = [' '.ord, attributes].pack('SS')
        @@ScrollConsoleScreenBuffer.call(@@hConsoleHandle, scroll_rectangle, nil, destination_origin, fill)
      else
        origin_x = x + 1
        origin_y = y - window_top + 1
        @@output.write [
          (origin_y != screen_height) ? "\e[#{screen_height};H" : nil,
          "\n" * val,
          (origin_y != screen_height or !x.zero?) ? "\e[#{origin_y};#{origin_x}H" : nil
        ].join
      end
    end

set_default_key_bindings(config) click to toggle source

    # File lib/reline/windows.rb, line 16
    def self.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) click to toggle source

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

set_winch_handler(&handler) click to toggle source

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

show_cursor() click to toggle source

    # File lib/reline/windows.rb, line 439
    def self.show_cursor
      size = 100
      visible = 1 # 1 means true
      cursor_info = [size, visible].pack('Li')
      @@SetConsoleCursorInfo.call(@@hConsoleHandle, cursor_info)
    end

ungetc(c) click to toggle source

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

win?() click to toggle source

    # File lib/reline/windows.rb, line 8
    def self.win?
      true
    end

win_legacy_console?() click to toggle source

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

### Private Class Methods

getconsolemode() click to toggle source

    # File lib/reline/windows.rb, line 152
                         def self.getconsolemode
      mode = "\000\000\000\000"
      @@GetConsoleMode.call(@@hConsoleHandle, mode)
      mode.unpack1('L')
    end

setconsolemode(mode) click to toggle source

    # File lib/reline/windows.rb, line 158
                         def self.setconsolemode(mode)
      @@SetConsoleMode.call(@@hConsoleHandle, mode)
    end