reline 0.2.4 → 0.2.8.pre.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 +119 -56
- data/lib/reline/config.rb +30 -13
- data/lib/reline/general_io.rb +11 -3
- data/lib/reline/key_actor/base.rb +12 -0
- data/lib/reline/line_editor.rb +395 -23
- data/lib/reline/terminfo.rb +126 -0
- data/lib/reline/unicode.rb +30 -0
- data/lib/reline/version.rb +1 -1
- data/lib/reline/windows.rb +164 -80
- data/lib/reline.rb +78 -13
- metadata +6 -62
- data/lib/reline/line_editor.rb.orig +0 -2696
| @@ -0,0 +1,126 @@ | |
| 1 | 
            +
            require 'fiddle'
         | 
| 2 | 
            +
            require 'fiddle/import'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Reline::Terminfo
         | 
| 5 | 
            +
              extend Fiddle::Importer
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              class TerminfoError < StandardError; end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              def self.curses_dl_files
         | 
| 10 | 
            +
                case RUBY_PLATFORM
         | 
| 11 | 
            +
                when /mingw/, /mswin/
         | 
| 12 | 
            +
                  # aren't supported
         | 
| 13 | 
            +
                  []
         | 
| 14 | 
            +
                when /cygwin/
         | 
| 15 | 
            +
                  %w[cygncursesw-10.dll cygncurses-10.dll]
         | 
| 16 | 
            +
                when /darwin/
         | 
| 17 | 
            +
                  %w[libncursesw.dylib libcursesw.dylib libncurses.dylib libcurses.dylib]
         | 
| 18 | 
            +
                else
         | 
| 19 | 
            +
                  %w[libncursesw.so libcursesw.so libncurses.so libcurses.so]
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              @curses_dl = false
         | 
| 24 | 
            +
              def self.curses_dl
         | 
| 25 | 
            +
                return @curses_dl unless @curses_dl == false
         | 
| 26 | 
            +
                if RUBY_VERSION >= '3.0.0'
         | 
| 27 | 
            +
                  # Gem module isn't defined in test-all of the Ruby repository, and
         | 
| 28 | 
            +
                  # Fiddle in Ruby 3.0.0 or later supports Fiddle::TYPE_VARIADIC.
         | 
| 29 | 
            +
                  fiddle_supports_variadic = true
         | 
| 30 | 
            +
                elsif Fiddle.const_defined?(:VERSION) and Gem::Version.create(Fiddle::VERSION) >= Gem::Version.create('1.0.1')
         | 
| 31 | 
            +
                  # Fiddle::TYPE_VARIADIC is supported from Fiddle 1.0.1.
         | 
| 32 | 
            +
                  fiddle_supports_variadic = true
         | 
| 33 | 
            +
                else
         | 
| 34 | 
            +
                  fiddle_supports_variadic = false
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
                if fiddle_supports_variadic and not Fiddle.const_defined?(:TYPE_VARIADIC)
         | 
| 37 | 
            +
                  # If the libffi version is not 3.0.5 or higher, there isn't TYPE_VARIADIC.
         | 
| 38 | 
            +
                  fiddle_supports_variadic = false
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
                if fiddle_supports_variadic
         | 
| 41 | 
            +
                  curses_dl_files.each do |curses_name|
         | 
| 42 | 
            +
                    result = Fiddle::Handle.new(curses_name)
         | 
| 43 | 
            +
                  rescue Fiddle::DLError
         | 
| 44 | 
            +
                    next
         | 
| 45 | 
            +
                  else
         | 
| 46 | 
            +
                    @curses_dl = result
         | 
| 47 | 
            +
                    break
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
                @curses_dl = nil if @curses_dl == false
         | 
| 51 | 
            +
                @curses_dl
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
            end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            module Reline::Terminfo
         | 
| 56 | 
            +
              dlload curses_dl
         | 
| 57 | 
            +
              #extern 'int setupterm(char *term, int fildes, int *errret)'
         | 
| 58 | 
            +
              @setupterm = Fiddle::Function.new(curses_dl['setupterm'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
         | 
| 59 | 
            +
              #extern 'char *tigetstr(char *capname)'
         | 
| 60 | 
            +
              @tigetstr = Fiddle::Function.new(curses_dl['tigetstr'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_VOIDP)
         | 
| 61 | 
            +
              begin
         | 
| 62 | 
            +
                #extern 'char *tiparm(const char *str, ...)'
         | 
| 63 | 
            +
                @tiparm = Fiddle::Function.new(curses_dl['tiparm'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VARIADIC], Fiddle::TYPE_VOIDP)
         | 
| 64 | 
            +
              rescue Fiddle::DLError
         | 
| 65 | 
            +
                # OpenBSD lacks tiparm
         | 
| 66 | 
            +
                #extern 'char *tparm(const char *str, ...)'
         | 
| 67 | 
            +
                @tiparm = Fiddle::Function.new(curses_dl['tparm'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VARIADIC], Fiddle::TYPE_VOIDP)
         | 
| 68 | 
            +
              end
         | 
| 69 | 
            +
              # TODO: add int tigetflag(char *capname) and int tigetnum(char *capname)
         | 
| 70 | 
            +
             | 
| 71 | 
            +
              def self.setupterm(term, fildes)
         | 
| 72 | 
            +
                errret_int = String.new("\x00" * 8, encoding: 'ASCII-8BIT')
         | 
| 73 | 
            +
                ret = @setupterm.(term, fildes, errret_int)
         | 
| 74 | 
            +
                errret = errret_int.unpack('i')[0]
         | 
| 75 | 
            +
                case ret
         | 
| 76 | 
            +
                when 0 # OK
         | 
| 77 | 
            +
                  0
         | 
| 78 | 
            +
                when -1 # ERR
         | 
| 79 | 
            +
                  case errret
         | 
| 80 | 
            +
                  when 1
         | 
| 81 | 
            +
                    raise TerminfoError.new('The terminal is hardcopy, cannot be used for curses applications.')
         | 
| 82 | 
            +
                  when 0
         | 
| 83 | 
            +
                    raise TerminfoError.new('The terminal could not be found, or that it is a generic type, having too little information for curses applications to run.')
         | 
| 84 | 
            +
                  when -1
         | 
| 85 | 
            +
                    raise TerminfoError.new('The terminfo database could not be found.')
         | 
| 86 | 
            +
                  else # unknown
         | 
| 87 | 
            +
                    -1
         | 
| 88 | 
            +
                  end
         | 
| 89 | 
            +
                else # unknown
         | 
| 90 | 
            +
                  -2
         | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
              end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
              class StringWithTiparm < String
         | 
| 95 | 
            +
                def tiparm(*args) # for method chain
         | 
| 96 | 
            +
                  Reline::Terminfo.tiparm(self, *args)
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
              end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
              def self.tigetstr(capname)
         | 
| 101 | 
            +
                capability = @tigetstr.(capname)
         | 
| 102 | 
            +
                case capability.to_i
         | 
| 103 | 
            +
                when 0, -1
         | 
| 104 | 
            +
                  raise TerminfoError, "can't find capability: #{capname}"
         | 
| 105 | 
            +
                end
         | 
| 106 | 
            +
                StringWithTiparm.new(capability.to_s)
         | 
| 107 | 
            +
              end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
              def self.tiparm(str, *args)
         | 
| 110 | 
            +
                new_args = []
         | 
| 111 | 
            +
                args.each do |a|
         | 
| 112 | 
            +
                  new_args << Fiddle::TYPE_INT << a
         | 
| 113 | 
            +
                end
         | 
| 114 | 
            +
                @tiparm.(str, *new_args).to_s
         | 
| 115 | 
            +
              end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
              def self.enabled?
         | 
| 118 | 
            +
                true
         | 
| 119 | 
            +
              end
         | 
| 120 | 
            +
            end if Reline::Terminfo.curses_dl
         | 
| 121 | 
            +
             | 
| 122 | 
            +
            module Reline::Terminfo
         | 
| 123 | 
            +
              def self.enabled?
         | 
| 124 | 
            +
                false
         | 
| 125 | 
            +
              end
         | 
| 126 | 
            +
            end unless Reline::Terminfo.curses_dl
         | 
    
        data/lib/reline/unicode.rb
    CHANGED
    
    | @@ -185,6 +185,36 @@ class Reline::Unicode | |
| 185 185 | 
             
                [lines, height]
         | 
| 186 186 | 
             
              end
         | 
| 187 187 |  | 
| 188 | 
            +
              # Take a chunk of a String with escape sequences.
         | 
| 189 | 
            +
              def self.take_range(str, col, length, encoding = str.encoding)
         | 
| 190 | 
            +
                chunk = String.new(encoding: encoding)
         | 
| 191 | 
            +
                width = 0
         | 
| 192 | 
            +
                rest = str.encode(Encoding::UTF_8)
         | 
| 193 | 
            +
                in_zero_width = false
         | 
| 194 | 
            +
                rest.scan(WIDTH_SCANNER) do |gc|
         | 
| 195 | 
            +
                  case
         | 
| 196 | 
            +
                  when gc[NON_PRINTING_START_INDEX]
         | 
| 197 | 
            +
                    in_zero_width = true
         | 
| 198 | 
            +
                  when gc[NON_PRINTING_END_INDEX]
         | 
| 199 | 
            +
                    in_zero_width = false
         | 
| 200 | 
            +
                  when gc[CSI_REGEXP_INDEX]
         | 
| 201 | 
            +
                    chunk << gc[CSI_REGEXP_INDEX]
         | 
| 202 | 
            +
                  when gc[OSC_REGEXP_INDEX]
         | 
| 203 | 
            +
                    chunk << gc[OSC_REGEXP_INDEX]
         | 
| 204 | 
            +
                  when gc[GRAPHEME_CLUSTER_INDEX]
         | 
| 205 | 
            +
                    gc = gc[GRAPHEME_CLUSTER_INDEX]
         | 
| 206 | 
            +
                    if in_zero_width
         | 
| 207 | 
            +
                      chunk << gc
         | 
| 208 | 
            +
                    else
         | 
| 209 | 
            +
                      width = get_mbchar_width(gc)
         | 
| 210 | 
            +
                      break if (width + length) <= col
         | 
| 211 | 
            +
                      chunk << gc if col <= width
         | 
| 212 | 
            +
                    end
         | 
| 213 | 
            +
                  end
         | 
| 214 | 
            +
                end
         | 
| 215 | 
            +
                chunk
         | 
| 216 | 
            +
              end
         | 
| 217 | 
            +
             | 
| 188 218 | 
             
              def self.get_next_mbchar_size(line, byte_pointer)
         | 
| 189 219 | 
             
                grapheme = line.byteslice(byte_pointer..-1).grapheme_clusters.first
         | 
| 190 220 | 
             
                grapheme ? grapheme.bytesize : 0
         | 
    
        data/lib/reline/version.rb
    CHANGED
    
    
    
        data/lib/reline/windows.rb
    CHANGED
    
    | @@ -13,23 +13,36 @@ 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 | 
            +
              end
         | 
| 33 46 |  | 
| 34 47 | 
             
              if defined? JRUBY_VERSION
         | 
| 35 48 | 
             
                require 'win32api'
         | 
| @@ -73,13 +86,36 @@ class Reline::Windows | |
| 73 86 | 
             
                end
         | 
| 74 87 | 
             
              end
         | 
| 75 88 |  | 
| 89 | 
            +
              VK_RETURN = 0x0D
         | 
| 76 90 | 
             
              VK_MENU = 0x12
         | 
| 77 91 | 
             
              VK_LMENU = 0xA4
         | 
| 78 92 | 
             
              VK_CONTROL = 0x11
         | 
| 79 93 | 
             
              VK_SHIFT = 0x10
         | 
| 94 | 
            +
              VK_DIVIDE = 0x6F
         | 
| 95 | 
            +
             | 
| 96 | 
            +
              KEY_EVENT = 0x01
         | 
| 97 | 
            +
              WINDOW_BUFFER_SIZE_EVENT = 0x04
         | 
| 98 | 
            +
             | 
| 99 | 
            +
              CAPSLOCK_ON = 0x0080
         | 
| 100 | 
            +
              ENHANCED_KEY = 0x0100
         | 
| 101 | 
            +
              LEFT_ALT_PRESSED = 0x0002
         | 
| 102 | 
            +
              LEFT_CTRL_PRESSED = 0x0008
         | 
| 103 | 
            +
              NUMLOCK_ON = 0x0020
         | 
| 104 | 
            +
              RIGHT_ALT_PRESSED = 0x0001
         | 
| 105 | 
            +
              RIGHT_CTRL_PRESSED = 0x0004
         | 
| 106 | 
            +
              SCROLLLOCK_ON = 0x0040
         | 
| 107 | 
            +
              SHIFT_PRESSED = 0x0010
         | 
| 108 | 
            +
             | 
| 109 | 
            +
              VK_END = 0x23
         | 
| 110 | 
            +
              VK_HOME = 0x24
         | 
| 111 | 
            +
              VK_LEFT = 0x25
         | 
| 112 | 
            +
              VK_UP = 0x26
         | 
| 113 | 
            +
              VK_RIGHT = 0x27
         | 
| 114 | 
            +
              VK_DOWN = 0x28
         | 
| 115 | 
            +
              VK_DELETE = 0x2E
         | 
| 116 | 
            +
             | 
| 80 117 | 
             
              STD_INPUT_HANDLE = -10
         | 
| 81 118 | 
             
              STD_OUTPUT_HANDLE = -11
         | 
| 82 | 
            -
              WINDOW_BUFFER_SIZE_EVENT = 0x04
         | 
| 83 119 | 
             
              FILE_TYPE_PIPE = 0x0003
         | 
| 84 120 | 
             
              FILE_NAME_INFO = 2
         | 
| 85 121 | 
             
              @@getwch = Win32API.new('msvcrt', '_getwch', [], 'I')
         | 
| @@ -93,13 +129,15 @@ class Reline::Windows | |
| 93 129 | 
             
              @@hConsoleHandle = @@GetStdHandle.call(STD_OUTPUT_HANDLE)
         | 
| 94 130 | 
             
              @@hConsoleInputHandle = @@GetStdHandle.call(STD_INPUT_HANDLE)
         | 
| 95 131 | 
             
              @@GetNumberOfConsoleInputEvents = Win32API.new('kernel32', 'GetNumberOfConsoleInputEvents', ['L', 'P'], 'L')
         | 
| 96 | 
            -
              @@ | 
| 132 | 
            +
              @@ReadConsoleInputW = Win32API.new('kernel32', 'ReadConsoleInputW', ['L', 'P', 'L', 'P'], 'L')
         | 
| 97 133 | 
             
              @@GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L')
         | 
| 98 134 | 
             
              @@GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I')
         | 
| 99 135 | 
             
              @@FillConsoleOutputAttribute = Win32API.new('kernel32', 'FillConsoleOutputAttribute', ['L', 'L', 'L', 'L', 'P'], 'L')
         | 
| 136 | 
            +
              @@SetConsoleCursorInfo = Win32API.new('kernel32', 'SetConsoleCursorInfo', ['L', 'P'], 'L')
         | 
| 100 137 |  | 
| 101 138 | 
             
              @@GetConsoleMode = Win32API.new('kernel32', 'GetConsoleMode', ['L', 'P'], 'L')
         | 
| 102 139 | 
             
              @@SetConsoleMode = Win32API.new('kernel32', 'SetConsoleMode', ['L', 'L'], 'L')
         | 
| 140 | 
            +
              @@WaitForSingleObject = Win32API.new('kernel32', 'WaitForSingleObject', ['L', 'L'], 'L')
         | 
| 103 141 | 
             
              ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
         | 
| 104 142 |  | 
| 105 143 | 
             
              private_class_method def self.getconsolemode
         | 
| @@ -145,78 +183,71 @@ class Reline::Windows | |
| 145 183 | 
             
                name =~ /(msys-|cygwin-).*-pty/ ? true : false
         | 
| 146 184 | 
             
              end
         | 
| 147 185 |  | 
| 148 | 
            -
               | 
| 149 | 
            -
                 | 
| 150 | 
            -
             | 
| 151 | 
            -
                 | 
| 152 | 
            -
             | 
| 153 | 
            -
             | 
| 154 | 
            -
                 | 
| 155 | 
            -
             | 
| 156 | 
            -
             | 
| 157 | 
            -
             | 
| 158 | 
            -
             | 
| 159 | 
            -
             | 
| 160 | 
            -
             | 
| 161 | 
            -
             | 
| 162 | 
            -
             | 
| 163 | 
            -
             | 
| 164 | 
            -
             | 
| 165 | 
            -
             | 
| 166 | 
            -
             | 
| 167 | 
            -
             | 
| 168 | 
            -
             | 
| 169 | 
            -
             | 
| 186 | 
            +
              KEY_MAP = [
         | 
| 187 | 
            +
                # It's treated as Meta+Enter on Windows.
         | 
| 188 | 
            +
                [ { control_keys: :CTRL,  virtual_key_code: 0x0D }, "\e\r".bytes ],
         | 
| 189 | 
            +
                [ { control_keys: :SHIFT, virtual_key_code: 0x0D }, "\e\r".bytes ],
         | 
| 190 | 
            +
             | 
| 191 | 
            +
                # It's treated as Meta+Space on Windows.
         | 
| 192 | 
            +
                [ { control_keys: :CTRL,  char_code: 0x20 }, "\e ".bytes ],
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                # Emulate getwch() key sequences.
         | 
| 195 | 
            +
                [ { control_keys: [], virtual_key_code: VK_UP },     [0, 72] ],
         | 
| 196 | 
            +
                [ { control_keys: [], virtual_key_code: VK_DOWN },   [0, 80] ],
         | 
| 197 | 
            +
                [ { control_keys: [], virtual_key_code: VK_RIGHT },  [0, 77] ],
         | 
| 198 | 
            +
                [ { control_keys: [], virtual_key_code: VK_LEFT },   [0, 75] ],
         | 
| 199 | 
            +
                [ { control_keys: [], virtual_key_code: VK_DELETE }, [0, 83] ],
         | 
| 200 | 
            +
                [ { control_keys: [], virtual_key_code: VK_HOME },   [0, 71] ],
         | 
| 201 | 
            +
                [ { control_keys: [], virtual_key_code: VK_END },    [0, 79] ],
         | 
| 202 | 
            +
              ]
         | 
| 203 | 
            +
             | 
| 204 | 
            +
              def self.process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
         | 
| 205 | 
            +
             | 
| 206 | 
            +
                key = KeyEventRecord.new(virtual_key_code, char_code, control_key_state)
         | 
| 207 | 
            +
             | 
| 208 | 
            +
                match = KEY_MAP.find { |args,| key.matches?(**args) }
         | 
| 209 | 
            +
                unless match.nil?
         | 
| 210 | 
            +
                  @@output_buf.concat(match.last)
         | 
| 211 | 
            +
                  return
         | 
| 170 212 | 
             
                end
         | 
| 171 | 
            -
             | 
| 213 | 
            +
             | 
| 214 | 
            +
                # no char, only control keys
         | 
| 215 | 
            +
                return if key.char_code == 0 and key.control_keys.any?
         | 
| 216 | 
            +
             | 
| 217 | 
            +
                @@output_buf.concat(key.char.bytes)
         | 
| 172 218 | 
             
              end
         | 
| 173 219 |  | 
| 174 | 
            -
              def self. | 
| 220 | 
            +
              def self.check_input_event
         | 
| 175 221 | 
             
                num_of_events = 0.chr * 8
         | 
| 176 | 
            -
                while @@ | 
| 222 | 
            +
                while @@output_buf.empty? #or true
         | 
| 223 | 
            +
                  next if @@WaitForSingleObject.(@@hConsoleInputHandle, 100) != 0 # max 0.1 sec
         | 
| 224 | 
            +
                  next if @@GetNumberOfConsoleInputEvents.(@@hConsoleInputHandle, num_of_events) == 0 or num_of_events.unpack('L').first == 0
         | 
| 177 225 | 
             
                  input_record = 0.chr * 18
         | 
| 178 226 | 
             
                  read_event = 0.chr * 4
         | 
| 179 | 
            -
                  if @@ | 
| 227 | 
            +
                  if @@ReadConsoleInputW.(@@hConsoleInputHandle, input_record, 1, read_event) != 0
         | 
| 180 228 | 
             
                    event = input_record[0, 2].unpack('s*').first
         | 
| 181 | 
            -
                     | 
| 229 | 
            +
                    case event
         | 
| 230 | 
            +
                    when WINDOW_BUFFER_SIZE_EVENT
         | 
| 182 231 | 
             
                      @@winch_handler.()
         | 
| 232 | 
            +
                    when KEY_EVENT
         | 
| 233 | 
            +
                      key_down = input_record[4, 4].unpack('l*').first
         | 
| 234 | 
            +
                      repeat_count = input_record[8, 2].unpack('s*').first
         | 
| 235 | 
            +
                      virtual_key_code = input_record[10, 2].unpack('s*').first
         | 
| 236 | 
            +
                      virtual_scan_code = input_record[12, 2].unpack('s*').first
         | 
| 237 | 
            +
                      char_code = input_record[14, 2].unpack('S*').first
         | 
| 238 | 
            +
                      control_key_state = input_record[16, 2].unpack('S*').first
         | 
| 239 | 
            +
                      is_key_down = key_down.zero? ? false : true
         | 
| 240 | 
            +
                      if is_key_down
         | 
| 241 | 
            +
                        process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
         | 
| 242 | 
            +
                      end
         | 
| 183 243 | 
             
                    end
         | 
| 184 244 | 
             
                  end
         | 
| 185 245 | 
             
                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
         | 
| 246 | 
            +
              end
         | 
| 247 | 
            +
             | 
| 248 | 
            +
              def self.getc
         | 
| 249 | 
            +
                check_input_event
         | 
| 250 | 
            +
                @@output_buf.shift
         | 
| 220 251 | 
             
              end
         | 
| 221 252 |  | 
| 222 253 | 
             
              def self.ungetc(c)
         | 
| @@ -313,6 +344,20 @@ class Reline::Windows | |
| 313 344 | 
             
                raise NotImplementedError
         | 
| 314 345 | 
             
              end
         | 
| 315 346 |  | 
| 347 | 
            +
              def self.hide_cursor
         | 
| 348 | 
            +
                size = 100
         | 
| 349 | 
            +
                visible = 0 # 0 means false
         | 
| 350 | 
            +
                cursor_info = [size, visible].pack('Li')
         | 
| 351 | 
            +
                @@SetConsoleCursorInfo.call(@@hConsoleHandle, cursor_info)
         | 
| 352 | 
            +
              end
         | 
| 353 | 
            +
             | 
| 354 | 
            +
              def self.show_cursor
         | 
| 355 | 
            +
                size = 100
         | 
| 356 | 
            +
                visible = 1 # 1 means true
         | 
| 357 | 
            +
                cursor_info = [size, visible].pack('Li')
         | 
| 358 | 
            +
                @@SetConsoleCursorInfo.call(@@hConsoleHandle, cursor_info)
         | 
| 359 | 
            +
              end
         | 
| 360 | 
            +
             | 
| 316 361 | 
             
              def self.set_winch_handler(&handler)
         | 
| 317 362 | 
             
                @@winch_handler = handler
         | 
| 318 363 | 
             
              end
         | 
| @@ -325,4 +370,43 @@ class Reline::Windows | |
| 325 370 | 
             
              def self.deprep(otio)
         | 
| 326 371 | 
             
                # do nothing
         | 
| 327 372 | 
             
              end
         | 
| 373 | 
            +
             | 
| 374 | 
            +
              class KeyEventRecord
         | 
| 375 | 
            +
             | 
| 376 | 
            +
                attr_reader :virtual_key_code, :char_code, :control_key_state, :control_keys
         | 
| 377 | 
            +
             | 
| 378 | 
            +
                def initialize(virtual_key_code, char_code, control_key_state)
         | 
| 379 | 
            +
                  @virtual_key_code = virtual_key_code
         | 
| 380 | 
            +
                  @char_code = char_code
         | 
| 381 | 
            +
                  @control_key_state = control_key_state
         | 
| 382 | 
            +
                  @enhanced = control_key_state & ENHANCED_KEY != 0
         | 
| 383 | 
            +
             | 
| 384 | 
            +
                  (@control_keys = []).tap do |control_keys|
         | 
| 385 | 
            +
                    # symbols must be sorted to make comparison is easier later on
         | 
| 386 | 
            +
                    control_keys << :ALT   if control_key_state & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) != 0
         | 
| 387 | 
            +
                    control_keys << :CTRL  if control_key_state & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) != 0
         | 
| 388 | 
            +
                    control_keys << :SHIFT if control_key_state & SHIFT_PRESSED != 0
         | 
| 389 | 
            +
                  end.freeze
         | 
| 390 | 
            +
                end
         | 
| 391 | 
            +
             | 
| 392 | 
            +
                def char
         | 
| 393 | 
            +
                  @char_code.chr(Encoding::UTF_8)
         | 
| 394 | 
            +
                end
         | 
| 395 | 
            +
             | 
| 396 | 
            +
                def enhanced?
         | 
| 397 | 
            +
                  @enhanced
         | 
| 398 | 
            +
                end
         | 
| 399 | 
            +
             | 
| 400 | 
            +
                # Verifies if the arguments match with this key event.
         | 
| 401 | 
            +
                # Nil arguments are ignored, but at least one must be passed as non-nil.
         | 
| 402 | 
            +
                # To verify that no control keys were pressed, pass an empty array: `control_keys: []`.
         | 
| 403 | 
            +
                def matches?(control_keys: nil, virtual_key_code: nil, char_code: nil)
         | 
| 404 | 
            +
                  raise ArgumentError, 'No argument was passed to match key event' if control_keys.nil? && virtual_key_code.nil? && char_code.nil?
         | 
| 405 | 
            +
             | 
| 406 | 
            +
                  (control_keys.nil? || [*control_keys].sort == @control_keys) &&
         | 
| 407 | 
            +
                  (virtual_key_code.nil? || @virtual_key_code == virtual_key_code) &&
         | 
| 408 | 
            +
                  (char_code.nil? || char_code == @char_code)
         | 
| 409 | 
            +
                end
         | 
| 410 | 
            +
             | 
| 411 | 
            +
              end
         | 
| 328 412 | 
             
            end
         |