reline 0.2.7 → 0.3.0
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/lib/reline/ansi.rb +64 -22
- data/lib/reline/config.rb +34 -3
- data/lib/reline/general_io.rb +3 -1
- data/lib/reline/key_actor/emacs.rb +1 -1
- data/lib/reline/key_stroke.rb +64 -14
- data/lib/reline/line_editor.rb +630 -72
- data/lib/reline/terminfo.rb +12 -4
- data/lib/reline/unicode.rb +42 -3
- data/lib/reline/version.rb +1 -1
- data/lib/reline/windows.rb +151 -49
- data/lib/reline.rb +127 -22
- metadata +2 -2
    
        data/lib/reline/terminfo.rb
    CHANGED
    
    | @@ -1,5 +1,13 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
            require 'fiddle | 
| 1 | 
            +
            begin
         | 
| 2 | 
            +
              require 'fiddle'
         | 
| 3 | 
            +
              require 'fiddle/import'
         | 
| 4 | 
            +
            rescue LoadError
         | 
| 5 | 
            +
              module Reline::Terminfo
         | 
| 6 | 
            +
                def self.curses_dl
         | 
| 7 | 
            +
                  false
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
            end
         | 
| 3 11 |  | 
| 4 12 | 
             
            module Reline::Terminfo
         | 
| 5 13 | 
             
              extend Fiddle::Importer
         | 
| @@ -50,7 +58,7 @@ module Reline::Terminfo | |
| 50 58 | 
             
                @curses_dl = nil if @curses_dl == false
         | 
| 51 59 | 
             
                @curses_dl
         | 
| 52 60 | 
             
              end
         | 
| 53 | 
            -
            end
         | 
| 61 | 
            +
            end if not Reline.const_defined?(:Terminfo) or not Reline::Terminfo.respond_to?(:curses_dl)
         | 
| 54 62 |  | 
| 55 63 | 
             
            module Reline::Terminfo
         | 
| 56 64 | 
             
              dlload curses_dl
         | 
| @@ -71,7 +79,7 @@ module Reline::Terminfo | |
| 71 79 | 
             
              def self.setupterm(term, fildes)
         | 
| 72 80 | 
             
                errret_int = String.new("\x00" * 8, encoding: 'ASCII-8BIT')
         | 
| 73 81 | 
             
                ret = @setupterm.(term, fildes, errret_int)
         | 
| 74 | 
            -
                errret = errret_int. | 
| 82 | 
            +
                errret = errret_int.unpack1('i')
         | 
| 75 83 | 
             
                case ret
         | 
| 76 84 | 
             
                when 0 # OK
         | 
| 77 85 | 
             
                  0
         | 
    
        data/lib/reline/unicode.rb
    CHANGED
    
    | @@ -79,6 +79,8 @@ class Reline::Unicode | |
| 79 79 |  | 
| 80 80 | 
             
              require 'reline/unicode/east_asian_width'
         | 
| 81 81 |  | 
| 82 | 
            +
              HalfwidthDakutenHandakuten = /[\u{FF9E}\u{FF9F}]/
         | 
| 83 | 
            +
             | 
| 82 84 | 
             
              MBCharWidthRE = /
         | 
| 83 85 | 
             
                (?<width_2_1>
         | 
| 84 86 | 
             
                  [#{ EscapedChars.map {|c| "\\x%02x" % c.ord }.join }] (?# ^ + char, such as ^M, ^H, ^[, ...)
         | 
| @@ -93,6 +95,12 @@ class Reline::Unicode | |
| 93 95 | 
             
                  #{ EastAsianWidth::TYPE_H }
         | 
| 94 96 | 
             
                | #{ EastAsianWidth::TYPE_NA }
         | 
| 95 97 | 
             
                | #{ EastAsianWidth::TYPE_N }
         | 
| 98 | 
            +
                )(?!#{ HalfwidthDakutenHandakuten })
         | 
| 99 | 
            +
              | (?<width_2_3>
         | 
| 100 | 
            +
                  (?: #{ EastAsianWidth::TYPE_H }
         | 
| 101 | 
            +
                    | #{ EastAsianWidth::TYPE_NA }
         | 
| 102 | 
            +
                    | #{ EastAsianWidth::TYPE_N })
         | 
| 103 | 
            +
                  #{ HalfwidthDakutenHandakuten }
         | 
| 96 104 | 
             
                )
         | 
| 97 105 | 
             
              | (?<ambiguous_width>
         | 
| 98 106 | 
             
                  #{EastAsianWidth::TYPE_A}
         | 
| @@ -101,15 +109,15 @@ class Reline::Unicode | |
| 101 109 |  | 
| 102 110 | 
             
              def self.get_mbchar_width(mbchar)
         | 
| 103 111 | 
             
                ord = mbchar.ord
         | 
| 104 | 
            -
                if (0x00 <= ord and ord <= 0x1F)
         | 
| 112 | 
            +
                if (0x00 <= ord and ord <= 0x1F) # in EscapedPairs
         | 
| 105 113 | 
             
                  return 2
         | 
| 106 | 
            -
                elsif (0x20 <= ord and ord <= 0x7E)
         | 
| 114 | 
            +
                elsif (0x20 <= ord and ord <= 0x7E) # printable ASCII chars
         | 
| 107 115 | 
             
                  return 1
         | 
| 108 116 | 
             
                end
         | 
| 109 117 | 
             
                m = mbchar.encode(Encoding::UTF_8).match(MBCharWidthRE)
         | 
| 110 118 | 
             
                case
         | 
| 111 119 | 
             
                when m.nil? then 1 # TODO should be U+FFFD � REPLACEMENT CHARACTER
         | 
| 112 | 
            -
                when m[:width_2_1], m[:width_2_2] then 2
         | 
| 120 | 
            +
                when m[:width_2_1], m[:width_2_2], m[:width_2_3] then 2
         | 
| 113 121 | 
             
                when m[:width_3] then 3
         | 
| 114 122 | 
             
                when m[:width_0] then 0
         | 
| 115 123 | 
             
                when m[:width_1] then 1
         | 
| @@ -185,6 +193,37 @@ class Reline::Unicode | |
| 185 193 | 
             
                [lines, height]
         | 
| 186 194 | 
             
              end
         | 
| 187 195 |  | 
| 196 | 
            +
              # Take a chunk of a String cut by width with escape sequences.
         | 
| 197 | 
            +
              def self.take_range(str, start_col, max_width, encoding = str.encoding)
         | 
| 198 | 
            +
                chunk = String.new(encoding: encoding)
         | 
| 199 | 
            +
                total_width = 0
         | 
| 200 | 
            +
                rest = str.encode(Encoding::UTF_8)
         | 
| 201 | 
            +
                in_zero_width = false
         | 
| 202 | 
            +
                rest.scan(WIDTH_SCANNER) do |gc|
         | 
| 203 | 
            +
                  case
         | 
| 204 | 
            +
                  when gc[NON_PRINTING_START_INDEX]
         | 
| 205 | 
            +
                    in_zero_width = true
         | 
| 206 | 
            +
                  when gc[NON_PRINTING_END_INDEX]
         | 
| 207 | 
            +
                    in_zero_width = false
         | 
| 208 | 
            +
                  when gc[CSI_REGEXP_INDEX]
         | 
| 209 | 
            +
                    chunk << gc[CSI_REGEXP_INDEX]
         | 
| 210 | 
            +
                  when gc[OSC_REGEXP_INDEX]
         | 
| 211 | 
            +
                    chunk << gc[OSC_REGEXP_INDEX]
         | 
| 212 | 
            +
                  when gc[GRAPHEME_CLUSTER_INDEX]
         | 
| 213 | 
            +
                    gc = gc[GRAPHEME_CLUSTER_INDEX]
         | 
| 214 | 
            +
                    if in_zero_width
         | 
| 215 | 
            +
                      chunk << gc
         | 
| 216 | 
            +
                    else
         | 
| 217 | 
            +
                      mbchar_width = get_mbchar_width(gc)
         | 
| 218 | 
            +
                      total_width += mbchar_width
         | 
| 219 | 
            +
                      break if (start_col + max_width) < total_width
         | 
| 220 | 
            +
                      chunk << gc if start_col < total_width
         | 
| 221 | 
            +
                    end
         | 
| 222 | 
            +
                  end
         | 
| 223 | 
            +
                end
         | 
| 224 | 
            +
                chunk
         | 
| 225 | 
            +
              end
         | 
| 226 | 
            +
             | 
| 188 227 | 
             
              def self.get_next_mbchar_size(line, byte_pointer)
         | 
| 189 228 | 
             
                grapheme = line.byteslice(byte_pointer..-1).grapheme_clusters.first
         | 
| 190 229 | 
             
                grapheme ? grapheme.bytesize : 0
         | 
    
        data/lib/reline/version.rb
    CHANGED
    
    
    
        data/lib/reline/windows.rb
    CHANGED
    
    | @@ -42,6 +42,14 @@ class Reline::Windows | |
| 42 42 | 
             
                }.each_pair do |key, func|
         | 
| 43 43 | 
             
                  config.add_default_key_binding_by_keymap(:emacs, key, func)
         | 
| 44 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
         | 
| 45 53 | 
             
              end
         | 
| 46 54 |  | 
| 47 55 | 
             
              if defined? JRUBY_VERSION
         | 
| @@ -106,6 +114,7 @@ class Reline::Windows | |
| 106 114 | 
             
              SCROLLLOCK_ON = 0x0040
         | 
| 107 115 | 
             
              SHIFT_PRESSED = 0x0010
         | 
| 108 116 |  | 
| 117 | 
            +
              VK_TAB = 0x09
         | 
| 109 118 | 
             
              VK_END = 0x23
         | 
| 110 119 | 
             
              VK_HOME = 0x24
         | 
| 111 120 | 
             
              VK_LEFT = 0x25
         | 
| @@ -133,9 +142,11 @@ class Reline::Windows | |
| 133 142 | 
             
              @@GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L')
         | 
| 134 143 | 
             
              @@GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I')
         | 
| 135 144 | 
             
              @@FillConsoleOutputAttribute = Win32API.new('kernel32', 'FillConsoleOutputAttribute', ['L', 'L', 'L', 'L', 'P'], 'L')
         | 
| 145 | 
            +
              @@SetConsoleCursorInfo = Win32API.new('kernel32', 'SetConsoleCursorInfo', ['L', 'P'], 'L')
         | 
| 136 146 |  | 
| 137 147 | 
             
              @@GetConsoleMode = Win32API.new('kernel32', 'GetConsoleMode', ['L', 'P'], 'L')
         | 
| 138 148 | 
             
              @@SetConsoleMode = Win32API.new('kernel32', 'SetConsoleMode', ['L', 'L'], 'L')
         | 
| 149 | 
            +
              @@WaitForSingleObject = Win32API.new('kernel32', 'WaitForSingleObject', ['L', 'L'], 'L')
         | 
| 139 150 | 
             
              ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
         | 
| 140 151 |  | 
| 141 152 | 
             
              private_class_method def self.getconsolemode
         | 
| @@ -157,7 +168,9 @@ class Reline::Windows | |
| 157 168 | 
             
              @@input_buf = []
         | 
| 158 169 | 
             
              @@output_buf = []
         | 
| 159 170 |  | 
| 160 | 
            -
               | 
| 171 | 
            +
              @@output = STDOUT
         | 
| 172 | 
            +
             | 
| 173 | 
            +
              def self.msys_tty?(io = @@hConsoleInputHandle)
         | 
| 161 174 | 
             
                # check if fd is a pipe
         | 
| 162 175 | 
             
                if @@GetFileType.call(io) != FILE_TYPE_PIPE
         | 
| 163 176 | 
             
                  return false
         | 
| @@ -173,7 +186,7 @@ class Reline::Windows | |
| 173 186 | 
             
                #     DWORD FileNameLength;
         | 
| 174 187 | 
             
                #     WCHAR FileName[1];
         | 
| 175 188 | 
             
                #   } FILE_NAME_INFO
         | 
| 176 | 
            -
                len = p_buffer[0, 4]. | 
| 189 | 
            +
                len = p_buffer[0, 4].unpack1("L")
         | 
| 177 190 | 
             
                name = p_buffer[4, len].encode(Encoding::UTF_8, Encoding::UTF_16LE, invalid: :replace)
         | 
| 178 191 |  | 
| 179 192 | 
             
                # Check if this could be a MSYS2 pty pipe ('\msys-XXXX-ptyN-XX')
         | 
| @@ -197,10 +210,34 @@ class Reline::Windows | |
| 197 210 | 
             
                [ { control_keys: [], virtual_key_code: VK_DELETE }, [0, 83] ],
         | 
| 198 211 | 
             
                [ { control_keys: [], virtual_key_code: VK_HOME },   [0, 71] ],
         | 
| 199 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] ],
         | 
| 200 216 | 
             
              ]
         | 
| 201 217 |  | 
| 218 | 
            +
              @@hsg = nil
         | 
| 219 | 
            +
             | 
| 202 220 | 
             
              def self.process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
         | 
| 203 221 |  | 
| 222 | 
            +
                # high-surrogate
         | 
| 223 | 
            +
                if 0xD800 <= char_code and char_code <= 0xDBFF
         | 
| 224 | 
            +
                  @@hsg = char_code
         | 
| 225 | 
            +
                  return
         | 
| 226 | 
            +
                end
         | 
| 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
         | 
| 235 | 
            +
                  end
         | 
| 236 | 
            +
                else
         | 
| 237 | 
            +
                  # ignore high-surrogate without low-surrogate if there
         | 
| 238 | 
            +
                  @@hsg = nil
         | 
| 239 | 
            +
                end
         | 
| 240 | 
            +
             | 
| 204 241 | 
             
                key = KeyEventRecord.new(virtual_key_code, char_code, control_key_state)
         | 
| 205 242 |  | 
| 206 243 | 
             
                match = KEY_MAP.find { |args,| key.matches?(**args) }
         | 
| @@ -212,30 +249,42 @@ class Reline::Windows | |
| 212 249 | 
             
                # no char, only control keys
         | 
| 213 250 | 
             
                return if key.char_code == 0 and key.control_keys.any?
         | 
| 214 251 |  | 
| 252 | 
            +
                @@output_buf.push("\e".ord) if key.control_keys.include?(:ALT)
         | 
| 253 | 
            +
             | 
| 215 254 | 
             
                @@output_buf.concat(key.char.bytes)
         | 
| 216 255 | 
             
              end
         | 
| 217 256 |  | 
| 218 257 | 
             
              def self.check_input_event
         | 
| 219 258 | 
             
                num_of_events = 0.chr * 8
         | 
| 220 | 
            -
                while @@output_buf.empty? | 
| 221 | 
            -
                   | 
| 222 | 
            -
                   | 
| 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
         | 
| 223 268 | 
             
                  read_event = 0.chr * 4
         | 
| 224 | 
            -
                  if @@ReadConsoleInputW.(@@hConsoleInputHandle,  | 
| 225 | 
            -
                     | 
| 226 | 
            -
                     | 
| 227 | 
            -
             | 
| 228 | 
            -
                       | 
| 229 | 
            -
             | 
| 230 | 
            -
                       | 
| 231 | 
            -
             | 
| 232 | 
            -
                       | 
| 233 | 
            -
             | 
| 234 | 
            -
             | 
| 235 | 
            -
             | 
| 236 | 
            -
             | 
| 237 | 
            -
             | 
| 238 | 
            -
                         | 
| 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
         | 
| 239 288 | 
             
                      end
         | 
| 240 289 | 
             
                    end
         | 
| 241 290 | 
             
                  end
         | 
| @@ -256,7 +305,7 @@ class Reline::Windows | |
| 256 305 | 
             
              end
         | 
| 257 306 |  | 
| 258 307 | 
             
              def self.empty_buffer?
         | 
| 259 | 
            -
                if not @@ | 
| 308 | 
            +
                if not @@output_buf.empty?
         | 
| 260 309 | 
             
                  false
         | 
| 261 310 | 
             
                elsif @@kbhit.call == 0
         | 
| 262 311 | 
             
                  true
         | 
| @@ -265,17 +314,37 @@ class Reline::Windows | |
| 265 314 | 
             
                end
         | 
| 266 315 | 
             
              end
         | 
| 267 316 |  | 
| 268 | 
            -
              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
         | 
| 269 330 | 
             
                csbi = 0.chr * 22
         | 
| 270 | 
            -
                @@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
         | 
| 271 339 | 
             
                csbi[0, 4].unpack('SS').reverse
         | 
| 272 340 | 
             
              end
         | 
| 273 341 |  | 
| 274 342 | 
             
              def self.cursor_pos
         | 
| 275 | 
            -
                csbi =  | 
| 276 | 
            -
             | 
| 277 | 
            -
                 | 
| 278 | 
            -
                 | 
| 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')
         | 
| 279 348 | 
             
                Reline::CursorPos.new(x, y)
         | 
| 280 349 | 
             
              end
         | 
| 281 350 |  | 
| @@ -295,6 +364,7 @@ class Reline::Windows | |
| 295 364 |  | 
| 296 365 | 
             
              def self.move_cursor_down(val)
         | 
| 297 366 | 
             
                if val > 0
         | 
| 367 | 
            +
                  return unless csbi = get_console_screen_buffer_info
         | 
| 298 368 | 
             
                  screen_height = get_screen_size.first
         | 
| 299 369 | 
             
                  y = cursor_pos.y + val
         | 
| 300 370 | 
             
                  y = screen_height - 1 if y > (screen_height - 1)
         | 
| @@ -305,42 +375,74 @@ class Reline::Windows | |
| 305 375 | 
             
              end
         | 
| 306 376 |  | 
| 307 377 | 
             
              def self.erase_after_cursor
         | 
| 308 | 
            -
                csbi =  | 
| 309 | 
            -
                 | 
| 310 | 
            -
                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')
         | 
| 311 381 | 
             
                written = 0.chr * 4
         | 
| 312 382 | 
             
                @@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, get_screen_size.last - cursor_pos.x, cursor, written)
         | 
| 313 | 
            -
                @@FillConsoleOutputAttribute.call(@@hConsoleHandle,  | 
| 383 | 
            +
                @@FillConsoleOutputAttribute.call(@@hConsoleHandle, attributes, get_screen_size.last - cursor_pos.x, cursor, written)
         | 
| 314 384 | 
             
              end
         | 
| 315 385 |  | 
| 316 386 | 
             
              def self.scroll_down(val)
         | 
| 317 | 
            -
                return if val | 
| 318 | 
            -
                 | 
| 319 | 
            -
                 | 
| 320 | 
            -
                 | 
| 321 | 
            -
                 | 
| 322 | 
            -
             | 
| 323 | 
            -
                @@ | 
| 387 | 
            +
                return if val < 0
         | 
| 388 | 
            +
                return unless csbi = get_console_screen_buffer_info
         | 
| 389 | 
            +
                buffer_width, x, y, buffer_lines, 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
         | 
| 324 411 | 
             
              end
         | 
| 325 412 |  | 
| 326 413 | 
             
              def self.clear_screen
         | 
| 327 | 
            -
                 | 
| 328 | 
            -
             | 
| 329 | 
            -
             | 
| 330 | 
            -
             | 
| 331 | 
            -
             | 
| 332 | 
            -
             | 
| 333 | 
            -
             | 
| 334 | 
            -
             | 
| 335 | 
            -
             | 
| 336 | 
            -
                 | 
| 337 | 
            -
             | 
| 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
         | 
| 338 426 | 
             
              end
         | 
| 339 427 |  | 
| 340 428 | 
             
              def self.set_screen_size(rows, columns)
         | 
| 341 429 | 
             
                raise NotImplementedError
         | 
| 342 430 | 
             
              end
         | 
| 343 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 | 
            +
             | 
| 344 446 | 
             
              def self.set_winch_handler(&handler)
         | 
| 345 447 | 
             
                @@winch_handler = handler
         | 
| 346 448 | 
             
              end
         | 
    
        data/lib/reline.rb
    CHANGED
    
    | @@ -16,8 +16,24 @@ module Reline | |
| 16 16 |  | 
| 17 17 | 
             
              class ConfigEncodingConversionError < StandardError; end
         | 
| 18 18 |  | 
| 19 | 
            -
              Key = Struct.new('Key', :char, :combined_char, :with_meta)
         | 
| 19 | 
            +
              Key = Struct.new('Key', :char, :combined_char, :with_meta) do
         | 
| 20 | 
            +
                def match?(other)
         | 
| 21 | 
            +
                  case other
         | 
| 22 | 
            +
                  when Reline::Key
         | 
| 23 | 
            +
                    (other.char.nil? or char.nil? or char == other.char) and
         | 
| 24 | 
            +
                    (other.combined_char.nil? or combined_char.nil? or combined_char == other.combined_char) and
         | 
| 25 | 
            +
                    (other.with_meta.nil? or with_meta.nil? or with_meta == other.with_meta)
         | 
| 26 | 
            +
                  when Integer, Symbol
         | 
| 27 | 
            +
                    (combined_char and combined_char == other) or
         | 
| 28 | 
            +
                    (combined_char.nil? and char and char == other)
         | 
| 29 | 
            +
                  else
         | 
| 30 | 
            +
                    false
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
                alias_method :==, :match?
         | 
| 34 | 
            +
              end
         | 
| 20 35 | 
             
              CursorPos = Struct.new(:x, :y)
         | 
| 36 | 
            +
              DialogRenderInfo = Struct.new(:pos, :contents, :bg_color, :width, :height, :scrollbar, keyword_init: true)
         | 
| 21 37 |  | 
| 22 38 | 
             
              class Core
         | 
| 23 39 | 
             
                ATTR_READER_NAMES = %i(
         | 
| @@ -44,6 +60,7 @@ module Reline | |
| 44 60 |  | 
| 45 61 | 
             
                def initialize
         | 
| 46 62 | 
             
                  self.output = STDOUT
         | 
| 63 | 
            +
                  @dialog_proc_list = {}
         | 
| 47 64 | 
             
                  yield self
         | 
| 48 65 | 
             
                  @completion_quote_character = nil
         | 
| 49 66 | 
             
                  @bracketed_paste_finished = false
         | 
| @@ -106,6 +123,14 @@ module Reline | |
| 106 123 | 
             
                  @completion_proc = p
         | 
| 107 124 | 
             
                end
         | 
| 108 125 |  | 
| 126 | 
            +
                def autocompletion
         | 
| 127 | 
            +
                  @config.autocompletion
         | 
| 128 | 
            +
                end
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                def autocompletion=(val)
         | 
| 131 | 
            +
                  @config.autocompletion = val
         | 
| 132 | 
            +
                end
         | 
| 133 | 
            +
             | 
| 109 134 | 
             
                def output_modifier_proc=(p)
         | 
| 110 135 | 
             
                  raise ArgumentError unless p.respond_to?(:call) or p.nil?
         | 
| 111 136 | 
             
                  @output_modifier_proc = p
         | 
| @@ -130,6 +155,17 @@ module Reline | |
| 130 155 | 
             
                  @dig_perfect_match_proc = p
         | 
| 131 156 | 
             
                end
         | 
| 132 157 |  | 
| 158 | 
            +
                DialogProc = Struct.new(:dialog_proc, :context)
         | 
| 159 | 
            +
                def add_dialog_proc(name_sym, p, context = nil)
         | 
| 160 | 
            +
                  raise ArgumentError unless p.respond_to?(:call) or p.nil?
         | 
| 161 | 
            +
                  raise ArgumentError unless name_sym.instance_of?(Symbol)
         | 
| 162 | 
            +
                  @dialog_proc_list[name_sym] = DialogProc.new(p, context)
         | 
| 163 | 
            +
                end
         | 
| 164 | 
            +
             | 
| 165 | 
            +
                def dialog_proc(name_sym)
         | 
| 166 | 
            +
                  @dialog_proc_list[name_sym]
         | 
| 167 | 
            +
                end
         | 
| 168 | 
            +
             | 
| 133 169 | 
             
                def input=(val)
         | 
| 134 170 | 
             
                  raise TypeError unless val.respond_to?(:getc) or val.nil?
         | 
| 135 171 | 
             
                  if val.respond_to?(:getc)
         | 
| @@ -171,6 +207,46 @@ module Reline | |
| 171 207 | 
             
                  Reline::IOGate.get_screen_size
         | 
| 172 208 | 
             
                end
         | 
| 173 209 |  | 
| 210 | 
            +
                Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE = ->() {
         | 
| 211 | 
            +
                  # autocomplete
         | 
| 212 | 
            +
                  return nil unless config.autocompletion
         | 
| 213 | 
            +
                  if just_cursor_moving and completion_journey_data.nil?
         | 
| 214 | 
            +
                    # Auto complete starts only when edited
         | 
| 215 | 
            +
                    return nil
         | 
| 216 | 
            +
                  end
         | 
| 217 | 
            +
                  pre, target, post = retrieve_completion_block(true)
         | 
| 218 | 
            +
                  if target.nil? or target.empty? or (completion_journey_data&.pointer == -1 and target.size <= 3)
         | 
| 219 | 
            +
                    return nil
         | 
| 220 | 
            +
                  end
         | 
| 221 | 
            +
                  if completion_journey_data and completion_journey_data.list
         | 
| 222 | 
            +
                    result = completion_journey_data.list.dup
         | 
| 223 | 
            +
                    result.shift
         | 
| 224 | 
            +
                    pointer = completion_journey_data.pointer - 1
         | 
| 225 | 
            +
                  else
         | 
| 226 | 
            +
                    result = call_completion_proc_with_checking_args(pre, target, post)
         | 
| 227 | 
            +
                    pointer = nil
         | 
| 228 | 
            +
                  end
         | 
| 229 | 
            +
                  if result and result.size == 1 and result[0] == target and pointer != 0
         | 
| 230 | 
            +
                    result = nil
         | 
| 231 | 
            +
                  end
         | 
| 232 | 
            +
                  target_width = Reline::Unicode.calculate_width(target)
         | 
| 233 | 
            +
                  x = cursor_pos.x - target_width
         | 
| 234 | 
            +
                  if x < 0
         | 
| 235 | 
            +
                    x = screen_width + x
         | 
| 236 | 
            +
                    y = -1
         | 
| 237 | 
            +
                  else
         | 
| 238 | 
            +
                    y = 0
         | 
| 239 | 
            +
                  end
         | 
| 240 | 
            +
                  cursor_pos_to_render = Reline::CursorPos.new(x, y)
         | 
| 241 | 
            +
                  if context and context.is_a?(Array)
         | 
| 242 | 
            +
                    context.clear
         | 
| 243 | 
            +
                    context.push(cursor_pos_to_render, result, pointer, dialog)
         | 
| 244 | 
            +
                  end
         | 
| 245 | 
            +
                  dialog.pointer = pointer
         | 
| 246 | 
            +
                  DialogRenderInfo.new(pos: cursor_pos_to_render, contents: result, scrollbar: true, height: 15)
         | 
| 247 | 
            +
                }
         | 
| 248 | 
            +
                Reline::DEFAULT_DIALOG_CONTEXT = Array.new
         | 
| 249 | 
            +
             | 
| 174 250 | 
             
                def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
         | 
| 175 251 | 
             
                  unless confirm_multiline_termination
         | 
| 176 252 | 
             
                    raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
         | 
| @@ -230,6 +306,9 @@ module Reline | |
| 230 306 | 
             
                  line_editor.auto_indent_proc = auto_indent_proc
         | 
| 231 307 | 
             
                  line_editor.dig_perfect_match_proc = dig_perfect_match_proc
         | 
| 232 308 | 
             
                  line_editor.pre_input_hook = pre_input_hook
         | 
| 309 | 
            +
                  @dialog_proc_list.each_pair do |name_sym, d|
         | 
| 310 | 
            +
                    line_editor.add_dialog_proc(name_sym, d.dialog_proc, d.context)
         | 
| 311 | 
            +
                  end
         | 
| 233 312 |  | 
| 234 313 | 
             
                  unless config.test_mode
         | 
| 235 314 | 
             
                    config.read
         | 
| @@ -240,6 +319,7 @@ module Reline | |
| 240 319 | 
             
                  line_editor.rerender
         | 
| 241 320 |  | 
| 242 321 | 
             
                  begin
         | 
| 322 | 
            +
                    line_editor.set_signal_handlers
         | 
| 243 323 | 
             
                    prev_pasting_state = false
         | 
| 244 324 | 
             
                    loop do
         | 
| 245 325 | 
             
                      prev_pasting_state = Reline::IOGate.in_pasting?
         | 
| @@ -268,6 +348,11 @@ module Reline | |
| 268 348 | 
             
                    line_editor.finalize
         | 
| 269 349 | 
             
                    Reline::IOGate.deprep(otio)
         | 
| 270 350 | 
             
                    raise e
         | 
| 351 | 
            +
                  rescue Exception
         | 
| 352 | 
            +
                    # Including Interrupt
         | 
| 353 | 
            +
                    line_editor.finalize
         | 
| 354 | 
            +
                    Reline::IOGate.deprep(otio)
         | 
| 355 | 
            +
                    raise
         | 
| 271 356 | 
             
                  end
         | 
| 272 357 |  | 
| 273 358 | 
             
                  line_editor.finalize
         | 
| @@ -303,25 +388,9 @@ module Reline | |
| 303 388 | 
             
                      break
         | 
| 304 389 | 
             
                    when :matching
         | 
| 305 390 | 
             
                      if buffer.size == 1
         | 
| 306 | 
            -
                         | 
| 307 | 
            -
             | 
| 308 | 
            -
             | 
| 309 | 
            -
                            succ_c = Reline::IOGate.getc
         | 
| 310 | 
            -
                          }
         | 
| 311 | 
            -
                        rescue Timeout::Error # cancel matching only when first byte
         | 
| 312 | 
            -
                          block.([Reline::Key.new(c, c, false)])
         | 
| 313 | 
            -
                          break
         | 
| 314 | 
            -
                        else
         | 
| 315 | 
            -
                          if key_stroke.match_status(buffer.dup.push(succ_c)) == :unmatched
         | 
| 316 | 
            -
                            if c == "\e".ord
         | 
| 317 | 
            -
                              block.([Reline::Key.new(succ_c, succ_c | 0b10000000, true)])
         | 
| 318 | 
            -
                            else
         | 
| 319 | 
            -
                              block.([Reline::Key.new(c, c, false), Reline::Key.new(succ_c, succ_c, false)])
         | 
| 320 | 
            -
                            end
         | 
| 321 | 
            -
                            break
         | 
| 322 | 
            -
                          else
         | 
| 323 | 
            -
                            Reline::IOGate.ungetc(succ_c)
         | 
| 324 | 
            -
                          end
         | 
| 391 | 
            +
                        case read_2nd_character_of_key_sequence(keyseq_timeout, buffer, c, block)
         | 
| 392 | 
            +
                        when :break then break
         | 
| 393 | 
            +
                        when :next  then next
         | 
| 325 394 | 
             
                        end
         | 
| 326 395 | 
             
                      end
         | 
| 327 396 | 
             
                    when :unmatched
         | 
| @@ -338,6 +407,38 @@ module Reline | |
| 338 407 | 
             
                  end
         | 
| 339 408 | 
             
                end
         | 
| 340 409 |  | 
| 410 | 
            +
                private def read_2nd_character_of_key_sequence(keyseq_timeout, buffer, c, block)
         | 
| 411 | 
            +
                  begin
         | 
| 412 | 
            +
                    succ_c = nil
         | 
| 413 | 
            +
                    Timeout.timeout(keyseq_timeout / 1000.0) {
         | 
| 414 | 
            +
                      succ_c = Reline::IOGate.getc
         | 
| 415 | 
            +
                    }
         | 
| 416 | 
            +
                  rescue Timeout::Error # cancel matching only when first byte
         | 
| 417 | 
            +
                    block.([Reline::Key.new(c, c, false)])
         | 
| 418 | 
            +
                    return :break
         | 
| 419 | 
            +
                  else
         | 
| 420 | 
            +
                    case key_stroke.match_status(buffer.dup.push(succ_c))
         | 
| 421 | 
            +
                    when :unmatched
         | 
| 422 | 
            +
                      if c == "\e".ord
         | 
| 423 | 
            +
                        block.([Reline::Key.new(succ_c, succ_c | 0b10000000, true)])
         | 
| 424 | 
            +
                      else
         | 
| 425 | 
            +
                        block.([Reline::Key.new(c, c, false), Reline::Key.new(succ_c, succ_c, false)])
         | 
| 426 | 
            +
                      end
         | 
| 427 | 
            +
                      return :break
         | 
| 428 | 
            +
                    when :matching
         | 
| 429 | 
            +
                      Reline::IOGate.ungetc(succ_c)
         | 
| 430 | 
            +
                      return :next
         | 
| 431 | 
            +
                    when :matched
         | 
| 432 | 
            +
                      buffer << succ_c
         | 
| 433 | 
            +
                      expanded = key_stroke.expand(buffer).map{ |expanded_c|
         | 
| 434 | 
            +
                        Reline::Key.new(expanded_c, expanded_c, false)
         | 
| 435 | 
            +
                      }
         | 
| 436 | 
            +
                      block.(expanded)
         | 
| 437 | 
            +
                      return :break
         | 
| 438 | 
            +
                    end
         | 
| 439 | 
            +
                  end
         | 
| 440 | 
            +
                end
         | 
| 441 | 
            +
             | 
| 341 442 | 
             
                private def read_escaped_key(keyseq_timeout, c, block)
         | 
| 342 443 | 
             
                  begin
         | 
| 343 444 | 
             
                    escaped_c = nil
         | 
| @@ -366,7 +467,7 @@ module Reline | |
| 366 467 |  | 
| 367 468 | 
             
                private def may_req_ambiguous_char_width
         | 
| 368 469 | 
             
                  @ambiguous_width = 2 if Reline::IOGate == Reline::GeneralIO or STDOUT.is_a?(File)
         | 
| 369 | 
            -
                  return if @ambiguous_width
         | 
| 470 | 
            +
                  return if defined? @ambiguous_width
         | 
| 370 471 | 
             
                  Reline::IOGate.move_cursor_column(0)
         | 
| 371 472 | 
             
                  begin
         | 
| 372 473 | 
             
                    output.write "\u{25bd}"
         | 
| @@ -389,7 +490,7 @@ module Reline | |
| 389 490 | 
             
              #--------------------------------------------------------
         | 
| 390 491 |  | 
| 391 492 | 
             
              (Core::ATTR_READER_NAMES).each { |name|
         | 
| 392 | 
            -
                def_single_delegators :core, "#{name}", "#{name}="
         | 
| 493 | 
            +
                def_single_delegators :core, :"#{name}", :"#{name}="
         | 
| 393 494 | 
             
              }
         | 
| 394 495 | 
             
              def_single_delegators :core, :input=, :output=
         | 
| 395 496 | 
             
              def_single_delegators :core, :vi_editing_mode, :emacs_editing_mode
         | 
| @@ -424,6 +525,9 @@ module Reline | |
| 424 525 | 
             
              def_single_delegators :core, :ambiguous_width
         | 
| 425 526 | 
             
              def_single_delegators :core, :last_incremental_search
         | 
| 426 527 | 
             
              def_single_delegators :core, :last_incremental_search=
         | 
| 528 | 
            +
              def_single_delegators :core, :add_dialog_proc
         | 
| 529 | 
            +
              def_single_delegators :core, :dialog_proc
         | 
| 530 | 
            +
              def_single_delegators :core, :autocompletion, :autocompletion=
         | 
| 427 531 |  | 
| 428 532 | 
             
              def_single_delegators :core, :readmultiline
         | 
| 429 533 | 
             
              def_instance_delegators self, :readmultiline
         | 
| @@ -445,6 +549,7 @@ module Reline | |
| 445 549 | 
             
                  core.completer_quote_characters = '"\''
         | 
| 446 550 | 
             
                  core.filename_quote_characters = ""
         | 
| 447 551 | 
             
                  core.special_prefixes = ""
         | 
| 552 | 
            +
                  core.add_dialog_proc(:autocomplete, Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE, Reline::DEFAULT_DIALOG_CONTEXT)
         | 
| 448 553 | 
             
                }
         | 
| 449 554 | 
             
              end
         | 
| 450 555 |  | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: reline
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.3.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - aycabta
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2021- | 
| 11 | 
            +
            date: 2021-12-24 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: io-console
         |