reline 0.2.5 → 0.3.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.
- checksums.yaml +4 -4
- data/README.md +46 -0
- data/lib/reline/ansi.rb +153 -62
- data/lib/reline/config.rb +62 -14
- data/lib/reline/general_io.rb +14 -4
- data/lib/reline/key_actor/base.rb +12 -0
- data/lib/reline/key_actor/emacs.rb +1 -1
- data/lib/reline/key_stroke.rb +64 -14
- data/lib/reline/line_editor.rb +638 -74
- data/lib/reline/terminfo.rb +171 -0
- data/lib/reline/unicode.rb +42 -3
- data/lib/reline/version.rb +1 -1
- data/lib/reline/windows.rb +281 -112
- data/lib/reline.rb +150 -35
- metadata +4 -59
    
        data/lib/reline/windows.rb
    CHANGED
    
    | @@ -13,23 +13,44 @@ class Reline::Windows | |
| 13 13 | 
             
                @@legacy_console
         | 
| 14 14 | 
             
              end
         | 
| 15 15 |  | 
| 16 | 
            -
               | 
| 17 | 
            -
                 | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
               | 
| 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
         | 
| 33 54 |  | 
| 34 55 | 
             
              if defined? JRUBY_VERSION
         | 
| 35 56 | 
             
                require 'win32api'
         | 
| @@ -73,13 +94,37 @@ class Reline::Windows | |
| 73 94 | 
             
                end
         | 
| 74 95 | 
             
              end
         | 
| 75 96 |  | 
| 97 | 
            +
              VK_RETURN = 0x0D
         | 
| 76 98 | 
             
              VK_MENU = 0x12
         | 
| 77 99 | 
             
              VK_LMENU = 0xA4
         | 
| 78 100 | 
             
              VK_CONTROL = 0x11
         | 
| 79 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 | 
            +
             | 
| 80 126 | 
             
              STD_INPUT_HANDLE = -10
         | 
| 81 127 | 
             
              STD_OUTPUT_HANDLE = -11
         | 
| 82 | 
            -
              WINDOW_BUFFER_SIZE_EVENT = 0x04
         | 
| 83 128 | 
             
              FILE_TYPE_PIPE = 0x0003
         | 
| 84 129 | 
             
              FILE_NAME_INFO = 2
         | 
| 85 130 | 
             
              @@getwch = Win32API.new('msvcrt', '_getwch', [], 'I')
         | 
| @@ -93,13 +138,15 @@ class Reline::Windows | |
| 93 138 | 
             
              @@hConsoleHandle = @@GetStdHandle.call(STD_OUTPUT_HANDLE)
         | 
| 94 139 | 
             
              @@hConsoleInputHandle = @@GetStdHandle.call(STD_INPUT_HANDLE)
         | 
| 95 140 | 
             
              @@GetNumberOfConsoleInputEvents = Win32API.new('kernel32', 'GetNumberOfConsoleInputEvents', ['L', 'P'], 'L')
         | 
| 96 | 
            -
              @@ | 
| 141 | 
            +
              @@ReadConsoleInputW = Win32API.new('kernel32', 'ReadConsoleInputW', ['L', 'P', 'L', 'P'], 'L')
         | 
| 97 142 | 
             
              @@GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L')
         | 
| 98 143 | 
             
              @@GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I')
         | 
| 99 144 | 
             
              @@FillConsoleOutputAttribute = Win32API.new('kernel32', 'FillConsoleOutputAttribute', ['L', 'L', 'L', 'L', 'P'], 'L')
         | 
| 145 | 
            +
              @@SetConsoleCursorInfo = Win32API.new('kernel32', 'SetConsoleCursorInfo', ['L', 'P'], 'L')
         | 
| 100 146 |  | 
| 101 147 | 
             
              @@GetConsoleMode = Win32API.new('kernel32', 'GetConsoleMode', ['L', 'P'], 'L')
         | 
| 102 148 | 
             
              @@SetConsoleMode = Win32API.new('kernel32', 'SetConsoleMode', ['L', 'L'], 'L')
         | 
| 149 | 
            +
              @@WaitForSingleObject = Win32API.new('kernel32', 'WaitForSingleObject', ['L', 'L'], 'L')
         | 
| 103 150 | 
             
              ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
         | 
| 104 151 |  | 
| 105 152 | 
             
              private_class_method def self.getconsolemode
         | 
| @@ -121,7 +168,9 @@ class Reline::Windows | |
| 121 168 | 
             
              @@input_buf = []
         | 
| 122 169 | 
             
              @@output_buf = []
         | 
| 123 170 |  | 
| 124 | 
            -
               | 
| 171 | 
            +
              @@output = STDOUT
         | 
| 172 | 
            +
             | 
| 173 | 
            +
              def self.msys_tty?(io = @@hConsoleInputHandle)
         | 
| 125 174 | 
             
                # check if fd is a pipe
         | 
| 126 175 | 
             
                if @@GetFileType.call(io) != FILE_TYPE_PIPE
         | 
| 127 176 | 
             
                  return false
         | 
| @@ -137,7 +186,7 @@ class Reline::Windows | |
| 137 186 | 
             
                #     DWORD FileNameLength;
         | 
| 138 187 | 
             
                #     WCHAR FileName[1];
         | 
| 139 188 | 
             
                #   } FILE_NAME_INFO
         | 
| 140 | 
            -
                len = p_buffer[0, 4]. | 
| 189 | 
            +
                len = p_buffer[0, 4].unpack1("L")
         | 
| 141 190 | 
             
                name = p_buffer[4, len].encode(Encoding::UTF_8, Encoding::UTF_16LE, invalid: :replace)
         | 
| 142 191 |  | 
| 143 192 | 
             
                # Check if this could be a MSYS2 pty pipe ('\msys-XXXX-ptyN-XX')
         | 
| @@ -145,78 +194,106 @@ class Reline::Windows | |
| 145 194 | 
             
                name =~ /(msys-|cygwin-).*-pty/ ? true : false
         | 
| 146 195 | 
             
              end
         | 
| 147 196 |  | 
| 148 | 
            -
               | 
| 149 | 
            -
                 | 
| 150 | 
            -
             | 
| 151 | 
            -
                 | 
| 152 | 
            -
             | 
| 153 | 
            -
             | 
| 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
         | 
| 154 226 | 
             
                end
         | 
| 155 | 
            -
                 | 
| 156 | 
            -
             | 
| 157 | 
            -
                  if  | 
| 158 | 
            -
                    @@ | 
| 159 | 
            -
                     | 
| 160 | 
            -
             | 
| 161 | 
            -
                     | 
| 162 | 
            -
             | 
| 163 | 
            -
                  begin
         | 
| 164 | 
            -
                    bytes = ret.chr(Encoding::UTF_8).bytes
         | 
| 165 | 
            -
                    @@input_buf.push(*bytes)
         | 
| 166 | 
            -
                  rescue Encoding::UndefinedConversionError
         | 
| 167 | 
            -
                    @@input_buf << ret
         | 
| 168 | 
            -
                    @@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
         | 
| 169 235 | 
             
                  end
         | 
| 236 | 
            +
                else
         | 
| 237 | 
            +
                  # ignore high-surrogate without low-surrogate if there
         | 
| 238 | 
            +
                  @@hsg = nil
         | 
| 170 239 | 
             
                end
         | 
| 171 | 
            -
             | 
| 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
         | 
| 247 | 
            +
                end
         | 
| 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)
         | 
| 172 255 | 
             
              end
         | 
| 173 256 |  | 
| 174 | 
            -
              def self. | 
| 257 | 
            +
              def self.check_input_event
         | 
| 175 258 | 
             
                num_of_events = 0.chr * 8
         | 
| 176 | 
            -
                while @@ | 
| 177 | 
            -
                   | 
| 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
         | 
| 178 268 | 
             
                  read_event = 0.chr * 4
         | 
| 179 | 
            -
                  if @@ | 
| 180 | 
            -
                     | 
| 181 | 
            -
                     | 
| 182 | 
            -
                       | 
| 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
         | 
| 183 289 | 
             
                    end
         | 
| 184 290 | 
             
                  end
         | 
| 185 291 | 
             
                end
         | 
| 186 | 
            -
             | 
| 187 | 
            -
             | 
| 188 | 
            -
             | 
| 189 | 
            -
                 | 
| 190 | 
            -
                 | 
| 191 | 
            -
                control = (@@GetKeyState.call(VK_CONTROL) & 0x80) != 0
         | 
| 192 | 
            -
                shift = (@@GetKeyState.call(VK_SHIFT) & 0x80) != 0
         | 
| 193 | 
            -
                force_enter = !input.instance_of?(Array) && (control or shift) && input == 0x0D
         | 
| 194 | 
            -
                if force_enter
         | 
| 195 | 
            -
                  # It's treated as Meta+Enter on Windows
         | 
| 196 | 
            -
                  @@output_buf.push("\e".ord)
         | 
| 197 | 
            -
                  @@output_buf.push(input)
         | 
| 198 | 
            -
                else
         | 
| 199 | 
            -
                  case input
         | 
| 200 | 
            -
                  when 0x00
         | 
| 201 | 
            -
                    meta = false
         | 
| 202 | 
            -
                    @@output_buf.push(input)
         | 
| 203 | 
            -
                    input = getwch
         | 
| 204 | 
            -
                    @@output_buf.push(*input)
         | 
| 205 | 
            -
                  when 0xE0
         | 
| 206 | 
            -
                    @@output_buf.push(input)
         | 
| 207 | 
            -
                    input = getwch
         | 
| 208 | 
            -
                    @@output_buf.push(*input)
         | 
| 209 | 
            -
                  when 0x03
         | 
| 210 | 
            -
                    @@output_buf.push(input)
         | 
| 211 | 
            -
                  else
         | 
| 212 | 
            -
                    @@output_buf.push(input)
         | 
| 213 | 
            -
                  end
         | 
| 214 | 
            -
                end
         | 
| 215 | 
            -
                if meta
         | 
| 216 | 
            -
                  "\e".ord
         | 
| 217 | 
            -
                else
         | 
| 218 | 
            -
                  @@output_buf.shift
         | 
| 219 | 
            -
                end
         | 
| 292 | 
            +
              end
         | 
| 293 | 
            +
             | 
| 294 | 
            +
              def self.getc
         | 
| 295 | 
            +
                check_input_event
         | 
| 296 | 
            +
                @@output_buf.shift
         | 
| 220 297 | 
             
              end
         | 
| 221 298 |  | 
| 222 299 | 
             
              def self.ungetc(c)
         | 
| @@ -228,7 +305,7 @@ class Reline::Windows | |
| 228 305 | 
             
              end
         | 
| 229 306 |  | 
| 230 307 | 
             
              def self.empty_buffer?
         | 
| 231 | 
            -
                if not @@ | 
| 308 | 
            +
                if not @@output_buf.empty?
         | 
| 232 309 | 
             
                  false
         | 
| 233 310 | 
             
                elsif @@kbhit.call == 0
         | 
| 234 311 | 
             
                  true
         | 
| @@ -237,17 +314,37 @@ class Reline::Windows | |
| 237 314 | 
             
                end
         | 
| 238 315 | 
             
              end
         | 
| 239 316 |  | 
| 240 | 
            -
              def self. | 
| 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
         | 
| 241 330 | 
             
                csbi = 0.chr * 22
         | 
| 242 | 
            -
                @@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
         | 
| 243 339 | 
             
                csbi[0, 4].unpack('SS').reverse
         | 
| 244 340 | 
             
              end
         | 
| 245 341 |  | 
| 246 342 | 
             
              def self.cursor_pos
         | 
| 247 | 
            -
                csbi =  | 
| 248 | 
            -
             | 
| 249 | 
            -
                 | 
| 250 | 
            -
                 | 
| 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')
         | 
| 251 348 | 
             
                Reline::CursorPos.new(x, y)
         | 
| 252 349 | 
             
              end
         | 
| 253 350 |  | 
| @@ -267,6 +364,7 @@ class Reline::Windows | |
| 267 364 |  | 
| 268 365 | 
             
              def self.move_cursor_down(val)
         | 
| 269 366 | 
             
                if val > 0
         | 
| 367 | 
            +
                  return unless csbi = get_console_screen_buffer_info
         | 
| 270 368 | 
             
                  screen_height = get_screen_size.first
         | 
| 271 369 | 
             
                  y = cursor_pos.y + val
         | 
| 272 370 | 
             
                  y = screen_height - 1 if y > (screen_height - 1)
         | 
| @@ -277,42 +375,74 @@ class Reline::Windows | |
| 277 375 | 
             
              end
         | 
| 278 376 |  | 
| 279 377 | 
             
              def self.erase_after_cursor
         | 
| 280 | 
            -
                csbi =  | 
| 281 | 
            -
                 | 
| 282 | 
            -
                cursor = csbi[4, 4]. | 
| 378 | 
            +
                return unless csbi = get_console_screen_buffer_info
         | 
| 379 | 
            +
                attributes = csbi[8, 2].unpack1('S')
         | 
| 380 | 
            +
                cursor = csbi[4, 4].unpack1('L')
         | 
| 283 381 | 
             
                written = 0.chr * 4
         | 
| 284 382 | 
             
                @@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, get_screen_size.last - cursor_pos.x, cursor, written)
         | 
| 285 | 
            -
                @@FillConsoleOutputAttribute.call(@@hConsoleHandle,  | 
| 383 | 
            +
                @@FillConsoleOutputAttribute.call(@@hConsoleHandle, attributes, get_screen_size.last - cursor_pos.x, cursor, written)
         | 
| 286 384 | 
             
              end
         | 
| 287 385 |  | 
| 288 386 | 
             
              def self.scroll_down(val)
         | 
| 289 | 
            -
                return if val | 
| 290 | 
            -
                 | 
| 291 | 
            -
                 | 
| 292 | 
            -
                 | 
| 293 | 
            -
                 | 
| 294 | 
            -
             | 
| 295 | 
            -
                @@ | 
| 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
         | 
| 296 411 | 
             
              end
         | 
| 297 412 |  | 
| 298 413 | 
             
              def self.clear_screen
         | 
| 299 | 
            -
                 | 
| 300 | 
            -
             | 
| 301 | 
            -
             | 
| 302 | 
            -
             | 
| 303 | 
            -
             | 
| 304 | 
            -
             | 
| 305 | 
            -
             | 
| 306 | 
            -
             | 
| 307 | 
            -
             | 
| 308 | 
            -
                 | 
| 309 | 
            -
             | 
| 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
         | 
| 310 426 | 
             
              end
         | 
| 311 427 |  | 
| 312 428 | 
             
              def self.set_screen_size(rows, columns)
         | 
| 313 429 | 
             
                raise NotImplementedError
         | 
| 314 430 | 
             
              end
         | 
| 315 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 | 
            +
             | 
| 316 446 | 
             
              def self.set_winch_handler(&handler)
         | 
| 317 447 | 
             
                @@winch_handler = handler
         | 
| 318 448 | 
             
              end
         | 
| @@ -325,4 +455,43 @@ class Reline::Windows | |
| 325 455 | 
             
              def self.deprep(otio)
         | 
| 326 456 | 
             
                # do nothing
         | 
| 327 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
         | 
| 328 497 | 
             
            end
         |