reline 0.0.1 → 0.0.6
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 +2 -0
- data/lib/reline.rb +329 -334
- data/lib/reline/ansi.rb +28 -11
- data/lib/reline/config.rb +4 -5
- data/lib/reline/general_io.rb +4 -1
- data/lib/reline/key_actor/vi_command.rb +1 -1
- data/lib/reline/key_actor/vi_insert.rb +1 -1
- data/lib/reline/key_stroke.rb +1 -1
- data/lib/reline/line_editor.rb +181 -76
- data/lib/reline/unicode/east_asian_width.rb +1 -1
- data/lib/reline/version.rb +1 -1
- data/lib/reline/windows.rb +109 -65
- metadata +4 -4
    
        data/lib/reline/ansi.rb
    CHANGED
    
    | @@ -7,7 +7,11 @@ class Reline::ANSI | |
| 7 7 | 
             
                [27, 91, 51, 126] => :key_delete,     # Del
         | 
| 8 8 | 
             
                [27, 91, 49, 126] => :ed_move_to_beg, # Home
         | 
| 9 9 | 
             
                [27, 91, 52, 126] => :ed_move_to_end, # End
         | 
| 10 | 
            -
             | 
| 10 | 
            +
                [27, 91, 72] => :ed_move_to_beg,      # Home
         | 
| 11 | 
            +
                [27, 91, 70] => :ed_move_to_end,      # End
         | 
| 12 | 
            +
                [27, 32] => :em_set_mark,             # M-<space>
         | 
| 13 | 
            +
                [24, 24] => :em_exchange_mark,        # C-x C-x TODO also add Windows
         | 
| 14 | 
            +
              }
         | 
| 11 15 |  | 
| 12 16 | 
             
              @@input = STDIN
         | 
| 13 17 | 
             
              def self.input=(val)
         | 
| @@ -24,20 +28,22 @@ class Reline::ANSI | |
| 24 28 | 
             
                unless @@buf.empty?
         | 
| 25 29 | 
             
                  return @@buf.shift
         | 
| 26 30 | 
             
                end
         | 
| 27 | 
            -
                 | 
| 28 | 
            -
                loop do
         | 
| 29 | 
            -
                  result = select([@@input], [], [], 0.1)
         | 
| 30 | 
            -
                  next if result.nil?
         | 
| 31 | 
            -
                  c = @@input.read(1)
         | 
| 32 | 
            -
                  break
         | 
| 33 | 
            -
                end
         | 
| 34 | 
            -
                c&.ord
         | 
| 31 | 
            +
                @@input.getbyte
         | 
| 35 32 | 
             
              end
         | 
| 36 33 |  | 
| 37 34 | 
             
              def self.ungetc(c)
         | 
| 38 35 | 
             
                @@buf.unshift(c)
         | 
| 39 36 | 
             
              end
         | 
| 40 37 |  | 
| 38 | 
            +
              def self.retrieve_keybuffer
         | 
| 39 | 
            +
                  result = select([@@input], [], [], 0.001)
         | 
| 40 | 
            +
                  return if result.nil?
         | 
| 41 | 
            +
                  str = @@input.read_nonblock(1024)
         | 
| 42 | 
            +
                  str.bytes.each do |c|
         | 
| 43 | 
            +
                    @@buf.push(c)
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
             | 
| 41 47 | 
             
              def self.get_screen_size
         | 
| 42 48 | 
             
                @@input.winsize
         | 
| 43 49 | 
             
              rescue Errno::ENOTTY
         | 
| @@ -106,13 +112,23 @@ class Reline::ANSI | |
| 106 112 | 
             
                print "\e[1;1H"
         | 
| 107 113 | 
             
              end
         | 
| 108 114 |  | 
| 115 | 
            +
              @@old_winch_handler = nil
         | 
| 116 | 
            +
              def self.set_winch_handler(&handler)
         | 
| 117 | 
            +
                @@old_winch_handler = Signal.trap('WINCH', &handler)
         | 
| 118 | 
            +
              end
         | 
| 119 | 
            +
             | 
| 109 120 | 
             
              def self.prep
         | 
| 121 | 
            +
                retrieve_keybuffer
         | 
| 110 122 | 
             
                int_handle = Signal.trap('INT', 'IGNORE')
         | 
| 111 123 | 
             
                otio = `stty -g`.chomp
         | 
| 112 124 | 
             
                setting = ' -echo -icrnl cbreak'
         | 
| 113 | 
            -
                 | 
| 125 | 
            +
                stty = `stty -a`
         | 
| 126 | 
            +
                if /-parenb\b/ =~ stty
         | 
| 114 127 | 
             
                  setting << ' pass8'
         | 
| 115 128 | 
             
                end
         | 
| 129 | 
            +
                if /\bdsusp *=/ =~ stty
         | 
| 130 | 
            +
                  setting << ' dsusp undef'
         | 
| 131 | 
            +
                end
         | 
| 116 132 | 
             
                setting << ' -ixoff'
         | 
| 117 133 | 
             
                `stty #{setting}`
         | 
| 118 134 | 
             
                Signal.trap('INT', int_handle)
         | 
| @@ -121,7 +137,8 @@ class Reline::ANSI | |
| 121 137 |  | 
| 122 138 | 
             
              def self.deprep(otio)
         | 
| 123 139 | 
             
                int_handle = Signal.trap('INT', 'IGNORE')
         | 
| 124 | 
            -
                 | 
| 140 | 
            +
                system("stty #{otio}", err: File::NULL)
         | 
| 125 141 | 
             
                Signal.trap('INT', int_handle)
         | 
| 142 | 
            +
                Signal.trap('WINCH', @@old_winch_handler) if @@old_winch_handler
         | 
| 126 143 | 
             
              end
         | 
| 127 144 | 
             
            end
         | 
    
        data/lib/reline/config.rb
    CHANGED
    
    | @@ -126,20 +126,19 @@ class Reline::Config | |
| 126 126 | 
             
                  no += 1
         | 
| 127 127 |  | 
| 128 128 | 
             
                  line = line.chomp.lstrip
         | 
| 129 | 
            -
                  if line | 
| 129 | 
            +
                  if line.start_with?('$')
         | 
| 130 130 | 
             
                    handle_directive(line[1..-1], file, no)
         | 
| 131 131 | 
             
                    next
         | 
| 132 132 | 
             
                  end
         | 
| 133 133 |  | 
| 134 134 | 
             
                  next if @skip_section
         | 
| 135 135 |  | 
| 136 | 
            -
                   | 
| 136 | 
            +
                  case line
         | 
| 137 | 
            +
                  when /^set +([^ ]+) +([^ ]+)/i
         | 
| 137 138 | 
             
                    var, value = $1.downcase, $2.downcase
         | 
| 138 139 | 
             
                    bind_variable(var, value)
         | 
| 139 140 | 
             
                    next
         | 
| 140 | 
            -
                   | 
| 141 | 
            -
             | 
| 142 | 
            -
                  if line =~ /\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/
         | 
| 141 | 
            +
                  when /\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/o
         | 
| 143 142 | 
             
                    key, func_name = $1, $2
         | 
| 144 143 | 
             
                    keystroke, func = bind_key(key, func_name)
         | 
| 145 144 | 
             
                    next unless keystroke
         | 
    
        data/lib/reline/general_io.rb
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            require 'timeout'
         | 
| 2 2 |  | 
| 3 3 | 
             
            class Reline::GeneralIO
         | 
| 4 | 
            -
              RAW_KEYSTROKE_CONFIG = {} | 
| 4 | 
            +
              RAW_KEYSTROKE_CONFIG = {}
         | 
| 5 5 |  | 
| 6 6 | 
             
              @@buf = []
         | 
| 7 7 |  | 
| @@ -56,6 +56,9 @@ class Reline::GeneralIO | |
| 56 56 | 
             
              def self.set_screen_size(rows, columns)
         | 
| 57 57 | 
             
              end
         | 
| 58 58 |  | 
| 59 | 
            +
              def self.set_winch_handler(&handler)
         | 
| 60 | 
            +
              end
         | 
| 61 | 
            +
             | 
| 59 62 | 
             
              def self.prep
         | 
| 60 63 | 
             
              end
         | 
| 61 64 |  | 
    
        data/lib/reline/key_stroke.rb
    CHANGED
    
    | @@ -32,7 +32,7 @@ class Reline::KeyStroke | |
| 32 32 | 
             
              end
         | 
| 33 33 |  | 
| 34 34 | 
             
              def expand(input)
         | 
| 35 | 
            -
                lhs = key_mapping.keys.select { | | 
| 35 | 
            +
                lhs = key_mapping.keys.select { |item| input.start_with? item }.sort_by(&:size).reverse.first
         | 
| 36 36 | 
             
                return input unless lhs
         | 
| 37 37 | 
             
                rhs = key_mapping[lhs]
         | 
| 38 38 |  | 
    
        data/lib/reline/line_editor.rb
    CHANGED
    
    | @@ -60,15 +60,72 @@ class Reline::LineEditor | |
| 60 60 | 
             
                reset_variables
         | 
| 61 61 | 
             
              end
         | 
| 62 62 |  | 
| 63 | 
            +
              private def check_multiline_prompt(buffer, prompt)
         | 
| 64 | 
            +
                if @vi_arg
         | 
| 65 | 
            +
                  prompt = "(arg: #{@vi_arg}) "
         | 
| 66 | 
            +
                  @rerender_all = true
         | 
| 67 | 
            +
                elsif @searching_prompt
         | 
| 68 | 
            +
                  prompt = @searching_prompt
         | 
| 69 | 
            +
                  @rerender_all = true
         | 
| 70 | 
            +
                else
         | 
| 71 | 
            +
                  prompt = @prompt
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
                if @prompt_proc
         | 
| 74 | 
            +
                  prompt_list = @prompt_proc.(buffer)
         | 
| 75 | 
            +
                  prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
         | 
| 76 | 
            +
                  prompt = prompt_list[@line_index]
         | 
| 77 | 
            +
                  prompt_width = calculate_width(prompt, true)
         | 
| 78 | 
            +
                  [prompt, prompt_width, prompt_list]
         | 
| 79 | 
            +
                else
         | 
| 80 | 
            +
                  prompt_width = calculate_width(prompt, true)
         | 
| 81 | 
            +
                  [prompt, prompt_width, nil]
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
              end
         | 
| 84 | 
            +
             | 
| 63 85 | 
             
              def reset(prompt = '', encoding = Encoding.default_external)
         | 
| 64 86 | 
             
                @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
         | 
| 65 87 | 
             
                @screen_size = Reline::IOGate.get_screen_size
         | 
| 66 88 | 
             
                reset_variables(prompt, encoding)
         | 
| 67 89 | 
             
                @old_trap = Signal.trap('SIGINT') {
         | 
| 68 | 
            -
                  scroll_down(@highest_in_all - @first_line_started_from)
         | 
| 69 | 
            -
                  Reline::IOGate.move_cursor_column(0)
         | 
| 70 90 | 
             
                  @old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
         | 
| 91 | 
            +
                  raise Interrupt
         | 
| 71 92 | 
             
                }
         | 
| 93 | 
            +
                Reline::IOGate.set_winch_handler do
         | 
| 94 | 
            +
                  @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
         | 
| 95 | 
            +
                  old_screen_size = @screen_size
         | 
| 96 | 
            +
                  @screen_size = Reline::IOGate.get_screen_size
         | 
| 97 | 
            +
                  if old_screen_size.last < @screen_size.last # columns increase
         | 
| 98 | 
            +
                    @rerender_all = true
         | 
| 99 | 
            +
                    rerender
         | 
| 100 | 
            +
                  else
         | 
| 101 | 
            +
                    back = 0
         | 
| 102 | 
            +
                    new_buffer = whole_lines
         | 
| 103 | 
            +
                    prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
         | 
| 104 | 
            +
                    new_buffer.each_with_index do |line, index|
         | 
| 105 | 
            +
                      prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
         | 
| 106 | 
            +
                      width = prompt_width + calculate_width(line)
         | 
| 107 | 
            +
                      height = calculate_height_by_width(width)
         | 
| 108 | 
            +
                      back += height
         | 
| 109 | 
            +
                    end
         | 
| 110 | 
            +
                    @highest_in_all = back
         | 
| 111 | 
            +
                    @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
         | 
| 112 | 
            +
                    @first_line_started_from =
         | 
| 113 | 
            +
                      if @line_index.zero?
         | 
| 114 | 
            +
                        0
         | 
| 115 | 
            +
                      else
         | 
| 116 | 
            +
                        calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list)
         | 
| 117 | 
            +
                      end
         | 
| 118 | 
            +
                    if @prompt_proc
         | 
| 119 | 
            +
                      prompt = prompt_list[@line_index]
         | 
| 120 | 
            +
                      prompt_width = calculate_width(prompt, true)
         | 
| 121 | 
            +
                    end
         | 
| 122 | 
            +
                    calculate_nearest_cursor
         | 
| 123 | 
            +
                    @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
         | 
| 124 | 
            +
                    Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
         | 
| 125 | 
            +
                    @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
         | 
| 126 | 
            +
                    @rerender_all = true
         | 
| 127 | 
            +
                  end
         | 
| 128 | 
            +
                end
         | 
| 72 129 | 
             
              end
         | 
| 73 130 |  | 
| 74 131 | 
             
              def finalize
         | 
| @@ -81,6 +138,7 @@ class Reline::LineEditor | |
| 81 138 |  | 
| 82 139 | 
             
              def reset_variables(prompt = '', encoding = Encoding.default_external)
         | 
| 83 140 | 
             
                @prompt = prompt
         | 
| 141 | 
            +
                @mark_pointer = nil
         | 
| 84 142 | 
             
                @encoding = encoding
         | 
| 85 143 | 
             
                @is_multiline = false
         | 
| 86 144 | 
             
                @finished = false
         | 
| @@ -129,6 +187,16 @@ class Reline::LineEditor | |
| 129 187 | 
             
                @is_multiline = false
         | 
| 130 188 | 
             
              end
         | 
| 131 189 |  | 
| 190 | 
            +
              private def calculate_height_by_lines(lines, prompt_list)
         | 
| 191 | 
            +
                result = 0
         | 
| 192 | 
            +
                lines.each_with_index { |line, i|
         | 
| 193 | 
            +
                  prompt = ''
         | 
| 194 | 
            +
                  prompt = prompt_list[i] if prompt_list and prompt_list[i]
         | 
| 195 | 
            +
                  result += calculate_height_by_width(calculate_width(prompt + line))
         | 
| 196 | 
            +
                }
         | 
| 197 | 
            +
                result
         | 
| 198 | 
            +
              end
         | 
| 199 | 
            +
             | 
| 132 200 | 
             
              private def insert_new_line(cursor_line, next_line)
         | 
| 133 201 | 
             
                @line = cursor_line
         | 
| 134 202 | 
             
                @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
         | 
| @@ -241,7 +309,7 @@ class Reline::LineEditor | |
| 241 309 | 
             
                @byte_pointer = new_byte_pointer
         | 
| 242 310 | 
             
              end
         | 
| 243 311 |  | 
| 244 | 
            -
              def rerender | 
| 312 | 
            +
              def rerender
         | 
| 245 313 | 
             
                return if @line.nil?
         | 
| 246 314 | 
             
                if @menu_info
         | 
| 247 315 | 
             
                  scroll_down(@highest_in_all - @first_line_started_from)
         | 
| @@ -249,32 +317,18 @@ class Reline::LineEditor | |
| 249 317 | 
             
                  @menu_info.list.each do |item|
         | 
| 250 318 | 
             
                    Reline::IOGate.move_cursor_column(0)
         | 
| 251 319 | 
             
                    @output.print item
         | 
| 320 | 
            +
                    @output.flush
         | 
| 252 321 | 
             
                    scroll_down(1)
         | 
| 253 322 | 
             
                  end
         | 
| 254 323 | 
             
                  scroll_down(@highest_in_all - 1)
         | 
| 255 324 | 
             
                  move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
         | 
| 256 325 | 
             
                  @menu_info = nil
         | 
| 257 326 | 
             
                end
         | 
| 258 | 
            -
                 | 
| 259 | 
            -
                  prompt = "(arg: #{@vi_arg}) "
         | 
| 260 | 
            -
                  prompt_width = calculate_width(prompt)
         | 
| 261 | 
            -
                elsif @searching_prompt
         | 
| 262 | 
            -
                  prompt = @searching_prompt
         | 
| 263 | 
            -
                  prompt_width = calculate_width(prompt)
         | 
| 264 | 
            -
                else
         | 
| 265 | 
            -
                  prompt = @prompt
         | 
| 266 | 
            -
                  prompt_width = calculate_width(prompt, true)
         | 
| 267 | 
            -
                end
         | 
| 327 | 
            +
                prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
         | 
| 268 328 | 
             
                if @cleared
         | 
| 269 329 | 
             
                  Reline::IOGate.clear_screen
         | 
| 270 330 | 
             
                  @cleared = false
         | 
| 271 331 | 
             
                  back = 0
         | 
| 272 | 
            -
                  prompt_list = nil
         | 
| 273 | 
            -
                  if @prompt_proc
         | 
| 274 | 
            -
                    prompt_list = @prompt_proc.(whole_lines)
         | 
| 275 | 
            -
                    prompt = prompt_list[@line_index]
         | 
| 276 | 
            -
                    prompt_width = calculate_width(prompt, true)
         | 
| 277 | 
            -
                  end
         | 
| 278 332 | 
             
                  modify_lines(whole_lines).each_with_index do |line, index|
         | 
| 279 333 | 
             
                    if @prompt_proc
         | 
| 280 334 | 
             
                      pr = prompt_list[index]
         | 
| @@ -300,15 +354,8 @@ class Reline::LineEditor | |
| 300 354 | 
             
                  else
         | 
| 301 355 | 
             
                    new_lines = whole_lines
         | 
| 302 356 | 
             
                  end
         | 
| 303 | 
            -
                  prompt_list =  | 
| 304 | 
            -
                   | 
| 305 | 
            -
                    prompt_list = @prompt_proc.(new_lines)
         | 
| 306 | 
            -
                    prompt = prompt_list[@line_index]
         | 
| 307 | 
            -
                    prompt_width = calculate_width(prompt, true)
         | 
| 308 | 
            -
                  end
         | 
| 309 | 
            -
                  all_height = new_lines.inject(0) { |result, line|
         | 
| 310 | 
            -
                    result + calculate_height_by_width(prompt_width + calculate_width(line)) # TODO prompt_list
         | 
| 311 | 
            -
                  }
         | 
| 357 | 
            +
                  prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
         | 
| 358 | 
            +
                  all_height = calculate_height_by_lines(new_lines, prompt_list)
         | 
| 312 359 | 
             
                  diff = all_height - @highest_in_all
         | 
| 313 360 | 
             
                  move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
         | 
| 314 361 | 
             
                  if diff > 0
         | 
| @@ -348,9 +395,7 @@ class Reline::LineEditor | |
| 348 395 | 
             
                    if @line_index.zero?
         | 
| 349 396 | 
             
                      0
         | 
| 350 397 | 
             
                    else
         | 
| 351 | 
            -
                      @buffer_of_lines[0..(@line_index - 1)] | 
| 352 | 
            -
                        result + calculate_height_by_width(prompt_width + calculate_width(line)) # TODO prompt_list
         | 
| 353 | 
            -
                      }
         | 
| 398 | 
            +
                      calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list)
         | 
| 354 399 | 
             
                    end
         | 
| 355 400 | 
             
                  if @prompt_proc
         | 
| 356 401 | 
             
                    prompt = prompt_list[@line_index]
         | 
| @@ -369,12 +414,7 @@ class Reline::LineEditor | |
| 369 414 | 
             
                  Reline::IOGate.move_cursor_column(0)
         | 
| 370 415 | 
             
                  back = 0
         | 
| 371 416 | 
             
                  new_buffer = whole_lines
         | 
| 372 | 
            -
                  prompt_list =  | 
| 373 | 
            -
                  if @prompt_proc
         | 
| 374 | 
            -
                    prompt_list = @prompt_proc.(new_buffer)
         | 
| 375 | 
            -
                    prompt = prompt_list[@line_index]
         | 
| 376 | 
            -
                    prompt_width = calculate_width(prompt, true)
         | 
| 377 | 
            -
                  end
         | 
| 417 | 
            +
                  prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
         | 
| 378 418 | 
             
                  new_buffer.each_with_index do |line, index|
         | 
| 379 419 | 
             
                    prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
         | 
| 380 420 | 
             
                    width = prompt_width + calculate_width(line)
         | 
| @@ -414,9 +454,7 @@ class Reline::LineEditor | |
| 414 454 | 
             
                    if @line_index.zero?
         | 
| 415 455 | 
             
                      0
         | 
| 416 456 | 
             
                    else
         | 
| 417 | 
            -
                      new_buffer[0..(@line_index - 1)] | 
| 418 | 
            -
                        result + calculate_height_by_width(prompt_width + calculate_width(line)) # TODO prompt_list
         | 
| 419 | 
            -
                      }
         | 
| 457 | 
            +
                      calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list)
         | 
| 420 458 | 
             
                    end
         | 
| 421 459 | 
             
                  @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
         | 
| 422 460 | 
             
                  move_cursor_down(@first_line_started_from + @started_from)
         | 
| @@ -426,12 +464,7 @@ class Reline::LineEditor | |
| 426 464 | 
             
                end
         | 
| 427 465 | 
             
                line = modify_lines(whole_lines)[@line_index]
         | 
| 428 466 | 
             
                if @is_multiline
         | 
| 429 | 
            -
                  prompt_list =  | 
| 430 | 
            -
                  if @prompt_proc
         | 
| 431 | 
            -
                    prompt_list = @prompt_proc.(whole_lines)
         | 
| 432 | 
            -
                    prompt = prompt_list[@line_index]
         | 
| 433 | 
            -
                    prompt_width = calculate_width(prompt, true)
         | 
| 434 | 
            -
                  end
         | 
| 467 | 
            +
                  prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
         | 
| 435 468 | 
             
                  if finished?
         | 
| 436 469 | 
             
                    # Always rerender on finish because output_modifier_proc may return a different output.
         | 
| 437 470 | 
             
                    render_partial(prompt, prompt_width, line)
         | 
| @@ -477,6 +510,7 @@ class Reline::LineEditor | |
| 477 510 | 
             
                    next
         | 
| 478 511 | 
             
                  end
         | 
| 479 512 | 
             
                  @output.print line
         | 
| 513 | 
            +
                  @output.flush
         | 
| 480 514 | 
             
                  if @first_prompt
         | 
| 481 515 | 
             
                    @first_prompt = false
         | 
| 482 516 | 
             
                    @pre_input_hook&.call
         | 
| @@ -619,9 +653,9 @@ class Reline::LineEditor | |
| 619 653 | 
             
                    else
         | 
| 620 654 | 
             
                      old_waiting_proc = @waiting_proc
         | 
| 621 655 | 
             
                      old_waiting_operator_proc = @waiting_operator_proc
         | 
| 622 | 
            -
                      @waiting_proc = proc { | | 
| 656 | 
            +
                      @waiting_proc = proc { |k|
         | 
| 623 657 | 
             
                        old_cursor, old_byte_pointer = @cursor, @byte_pointer
         | 
| 624 | 
            -
                        old_waiting_proc.( | 
| 658 | 
            +
                        old_waiting_proc.(k)
         | 
| 625 659 | 
             
                        cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
         | 
| 626 660 | 
             
                        @cursor, @byte_pointer = old_cursor, old_byte_pointer
         | 
| 627 661 | 
             
                        @waiting_operator_proc.(cursor_diff, byte_pointer_diff)
         | 
| @@ -732,7 +766,7 @@ class Reline::LineEditor | |
| 732 766 | 
             
              end
         | 
| 733 767 |  | 
| 734 768 | 
             
              def input_key(key)
         | 
| 735 | 
            -
                if key. | 
| 769 | 
            +
                if key.char.nil?
         | 
| 736 770 | 
             
                  if @first_char
         | 
| 737 771 | 
             
                    @line = nil
         | 
| 738 772 | 
             
                  end
         | 
| @@ -772,6 +806,26 @@ class Reline::LineEditor | |
| 772 806 |  | 
| 773 807 | 
             
              private def process_auto_indent
         | 
| 774 808 | 
             
                return if not @check_new_auto_indent and @previous_line_index # move cursor up or down
         | 
| 809 | 
            +
                if @check_new_auto_indent and @previous_line_index and @previous_line_index > 0 and @line_index > @previous_line_index
         | 
| 810 | 
            +
                  # Fix indent of a line when a newline is inserted to the next
         | 
| 811 | 
            +
                  new_lines = whole_lines(index: @previous_line_index, line: @line)
         | 
| 812 | 
            +
                  new_indent = @auto_indent_proc.(new_lines[0..-3].push(''), @line_index - 1, 0, true)
         | 
| 813 | 
            +
                  md = @line.match(/\A */)
         | 
| 814 | 
            +
                  prev_indent = md[0].count(' ')
         | 
| 815 | 
            +
                  @line = ' ' * new_indent + @line.lstrip
         | 
| 816 | 
            +
             | 
| 817 | 
            +
                  new_indent = nil
         | 
| 818 | 
            +
                  (new_lines[-2].size + 1).times do |n|
         | 
| 819 | 
            +
                    result = @auto_indent_proc.(new_lines[0..-2], @line_index - 1, n, false)
         | 
| 820 | 
            +
                    if result
         | 
| 821 | 
            +
                      new_indent = result
         | 
| 822 | 
            +
                      break
         | 
| 823 | 
            +
                    end
         | 
| 824 | 
            +
                  end
         | 
| 825 | 
            +
                  if new_indent&.>= 0
         | 
| 826 | 
            +
                    @line = ' ' * new_indent + @line.lstrip
         | 
| 827 | 
            +
                  end
         | 
| 828 | 
            +
                end
         | 
| 775 829 | 
             
                if @previous_line_index
         | 
| 776 830 | 
             
                  new_lines = whole_lines(index: @previous_line_index, line: @line)
         | 
| 777 831 | 
             
                else
         | 
| @@ -801,17 +855,25 @@ class Reline::LineEditor | |
| 801 855 | 
             
                rest = nil
         | 
| 802 856 | 
             
                break_pointer = nil
         | 
| 803 857 | 
             
                quote = nil
         | 
| 858 | 
            +
                closing_quote = nil
         | 
| 859 | 
            +
                escaped_quote = nil
         | 
| 804 860 | 
             
                i = 0
         | 
| 805 861 | 
             
                while i < @byte_pointer do
         | 
| 806 862 | 
             
                  slice = @line.byteslice(i, @byte_pointer - i)
         | 
| 807 | 
            -
                   | 
| 863 | 
            +
                  unless slice.valid_encoding?
         | 
| 864 | 
            +
                    i += 1
         | 
| 865 | 
            +
                    next
         | 
| 866 | 
            +
                  end
         | 
| 867 | 
            +
                  if quote and slice.start_with?(closing_quote)
         | 
| 808 868 | 
             
                    quote = nil
         | 
| 809 869 | 
             
                    i += 1
         | 
| 810 | 
            -
                  elsif quote and slice.start_with?( | 
| 870 | 
            +
                  elsif quote and slice.start_with?(escaped_quote)
         | 
| 811 871 | 
             
                    # skip
         | 
| 812 872 | 
             
                    i += 2
         | 
| 813 873 | 
             
                  elsif slice =~ quote_characters_regexp # find new "
         | 
| 814 874 | 
             
                    quote = $&
         | 
| 875 | 
            +
                    closing_quote = /(?!\\)#{Regexp.escape(quote)}/
         | 
| 876 | 
            +
                    escaped_quote = /\\#{Regexp.escape(quote)}/
         | 
| 815 877 | 
             
                    i += 1
         | 
| 816 878 | 
             
                  elsif not quote and slice =~ word_break_regexp
         | 
| 817 879 | 
             
                    rest = $'
         | 
| @@ -839,11 +901,7 @@ class Reline::LineEditor | |
| 839 901 | 
             
                else
         | 
| 840 902 | 
             
                  temp_buffer[@line_index] = @line
         | 
| 841 903 | 
             
                end
         | 
| 842 | 
            -
                 | 
| 843 | 
            -
                  @confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
         | 
| 844 | 
            -
                else
         | 
| 845 | 
            -
                  false
         | 
| 846 | 
            -
                end
         | 
| 904 | 
            +
                @confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
         | 
| 847 905 | 
             
              end
         | 
| 848 906 |  | 
| 849 907 | 
             
              def insert_text(text)
         | 
| @@ -959,8 +1017,8 @@ class Reline::LineEditor | |
| 959 1017 | 
             
                  end
         | 
| 960 1018 | 
             
                  width
         | 
| 961 1019 | 
             
                else
         | 
| 962 | 
            -
                  str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { | | 
| 963 | 
            -
                     | 
| 1020 | 
            +
                  str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |w, gc|
         | 
| 1021 | 
            +
                    w + Reline::Unicode.get_mbchar_width(gc)
         | 
| 964 1022 | 
             
                  }
         | 
| 965 1023 | 
             
                end
         | 
| 966 1024 | 
             
              end
         | 
| @@ -981,6 +1039,8 @@ class Reline::LineEditor | |
| 981 1039 | 
             
                end
         | 
| 982 1040 | 
             
              end
         | 
| 983 1041 |  | 
| 1042 | 
            +
              private def ed_unassigned(key) end # do nothing
         | 
| 1043 | 
            +
             | 
| 984 1044 | 
             
              private def ed_insert(key)
         | 
| 985 1045 | 
             
                if key.instance_of?(String)
         | 
| 986 1046 | 
             
                  width = Reline::Unicode.get_mbchar_width(key)
         | 
| @@ -1085,7 +1145,11 @@ class Reline::LineEditor | |
| 1085 1145 | 
             
              alias_method :end_of_line, :ed_move_to_end
         | 
| 1086 1146 |  | 
| 1087 1147 | 
             
              private def ed_search_prev_history(key)
         | 
| 1088 | 
            -
                 | 
| 1148 | 
            +
                if @is_multiline
         | 
| 1149 | 
            +
                  @line_backup_in_history = whole_buffer
         | 
| 1150 | 
            +
                else
         | 
| 1151 | 
            +
                  @line_backup_in_history = @line
         | 
| 1152 | 
            +
                end
         | 
| 1089 1153 | 
             
                searcher = Fiber.new do
         | 
| 1090 1154 | 
             
                  search_word = String.new(encoding: @encoding)
         | 
| 1091 1155 | 
             
                  multibyte_buf = String.new(encoding: 'ASCII-8BIT')
         | 
| @@ -1120,18 +1184,32 @@ class Reline::LineEditor | |
| 1120 1184 | 
             
                      end
         | 
| 1121 1185 | 
             
                    end
         | 
| 1122 1186 | 
             
                    if hit
         | 
| 1123 | 
            -
                      @ | 
| 1124 | 
            -
             | 
| 1187 | 
            +
                      if @is_multiline
         | 
| 1188 | 
            +
                        @buffer_of_lines = hit.split("\n")
         | 
| 1189 | 
            +
                        @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
         | 
| 1190 | 
            +
                        @line_index = @buffer_of_lines.size - 1
         | 
| 1191 | 
            +
                        @line = @buffer_of_lines.last
         | 
| 1192 | 
            +
                        @rerender_all = true
         | 
| 1193 | 
            +
                        @searching_prompt = "(reverse-i-search)`%s'" % [search_word]
         | 
| 1194 | 
            +
                      else
         | 
| 1195 | 
            +
                        @line = hit
         | 
| 1196 | 
            +
                        @searching_prompt = "(reverse-i-search)`%s': %s" % [search_word, hit]
         | 
| 1197 | 
            +
                      end
         | 
| 1125 1198 | 
             
                      last_hit = hit
         | 
| 1126 1199 | 
             
                    else
         | 
| 1127 | 
            -
                      @ | 
| 1200 | 
            +
                      if @is_multiline
         | 
| 1201 | 
            +
                        @rerender_all = true
         | 
| 1202 | 
            +
                        @searching_prompt = "(failed reverse-i-search)`%s'" % [search_word]
         | 
| 1203 | 
            +
                      else
         | 
| 1204 | 
            +
                        @searching_prompt = "(failed reverse-i-search)`%s': %s" % [search_word, last_hit]
         | 
| 1205 | 
            +
                      end
         | 
| 1128 1206 | 
             
                    end
         | 
| 1129 1207 | 
             
                  end
         | 
| 1130 1208 | 
             
                end
         | 
| 1131 1209 | 
             
                searcher.resume
         | 
| 1132 1210 | 
             
                @searching_prompt = "(reverse-i-search)`': "
         | 
| 1133 | 
            -
                @waiting_proc = ->( | 
| 1134 | 
            -
                  case  | 
| 1211 | 
            +
                @waiting_proc = ->(k) {
         | 
| 1212 | 
            +
                  case k
         | 
| 1135 1213 | 
             
                  when "\C-j".ord, "\C-?".ord
         | 
| 1136 1214 | 
             
                    if @history_pointer
         | 
| 1137 1215 | 
             
                      @line = Reline::HISTORY[@history_pointer]
         | 
| @@ -1151,14 +1229,25 @@ class Reline::LineEditor | |
| 1151 1229 | 
             
                    @cursor_max = calculate_width(@line)
         | 
| 1152 1230 | 
             
                    @cursor = @byte_pointer = 0
         | 
| 1153 1231 | 
             
                  else
         | 
| 1154 | 
            -
                    chr =  | 
| 1155 | 
            -
                    if chr.match?(/[[:print:]]/)
         | 
| 1156 | 
            -
                      searcher.resume( | 
| 1232 | 
            +
                    chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
         | 
| 1233 | 
            +
                    if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == 127
         | 
| 1234 | 
            +
                      searcher.resume(k)
         | 
| 1157 1235 | 
             
                    else
         | 
| 1158 1236 | 
             
                      if @history_pointer
         | 
| 1159 | 
            -
                         | 
| 1237 | 
            +
                        line = Reline::HISTORY[@history_pointer]
         | 
| 1238 | 
            +
                      else
         | 
| 1239 | 
            +
                        line = @line_backup_in_history
         | 
| 1240 | 
            +
                      end
         | 
| 1241 | 
            +
                      if @is_multiline
         | 
| 1242 | 
            +
                        @line_backup_in_history = whole_buffer
         | 
| 1243 | 
            +
                        @buffer_of_lines = line.split("\n")
         | 
| 1244 | 
            +
                        @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
         | 
| 1245 | 
            +
                        @line_index = @buffer_of_lines.size - 1
         | 
| 1246 | 
            +
                        @line = @buffer_of_lines.last
         | 
| 1247 | 
            +
                        @rerender_all = true
         | 
| 1160 1248 | 
             
                      else
         | 
| 1161 | 
            -
                        @ | 
| 1249 | 
            +
                        @line_backup_in_history = @line
         | 
| 1250 | 
            +
                        @line = line
         | 
| 1162 1251 | 
             
                      end
         | 
| 1163 1252 | 
             
                      @searching_prompt = nil
         | 
| 1164 1253 | 
             
                      @waiting_proc = nil
         | 
| @@ -1211,7 +1300,7 @@ class Reline::LineEditor | |
| 1211 1300 | 
             
                    @line = Reline::HISTORY[@history_pointer]
         | 
| 1212 1301 | 
             
                  end
         | 
| 1213 1302 | 
             
                end
         | 
| 1214 | 
            -
                if @config.editing_mode_is?(:emacs)
         | 
| 1303 | 
            +
                if @config.editing_mode_is?(:emacs, :vi_insert)
         | 
| 1215 1304 | 
             
                  @cursor_max = @cursor = calculate_width(@line)
         | 
| 1216 1305 | 
             
                  @byte_pointer = @line.bytesize
         | 
| 1217 1306 | 
             
                elsif @config.editing_mode_is?(:vi_command)
         | 
| @@ -1258,7 +1347,7 @@ class Reline::LineEditor | |
| 1258 1347 | 
             
                  end
         | 
| 1259 1348 | 
             
                end
         | 
| 1260 1349 | 
             
                @line = '' unless @line
         | 
| 1261 | 
            -
                if @config.editing_mode_is?(:emacs)
         | 
| 1350 | 
            +
                if @config.editing_mode_is?(:emacs, :vi_insert)
         | 
| 1262 1351 | 
             
                  @cursor_max = @cursor = calculate_width(@line)
         | 
| 1263 1352 | 
             
                  @byte_pointer = @line.bytesize
         | 
| 1264 1353 | 
             
                elsif @config.editing_mode_is?(:vi_command)
         | 
| @@ -1697,8 +1786,8 @@ class Reline::LineEditor | |
| 1697 1786 | 
             
              end
         | 
| 1698 1787 |  | 
| 1699 1788 | 
             
              private def ed_delete_next_char(key, arg: 1)
         | 
| 1700 | 
            -
                 | 
| 1701 | 
            -
             | 
| 1789 | 
            +
                byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
         | 
| 1790 | 
            +
                unless @line.empty? || byte_size == 0
         | 
| 1702 1791 | 
             
                  @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
         | 
| 1703 1792 | 
             
                  copy_for_vi(mbchar)
         | 
| 1704 1793 | 
             
                  width = Reline::Unicode.get_mbchar_width(mbchar)
         | 
| @@ -1795,13 +1884,13 @@ class Reline::LineEditor | |
| 1795 1884 | 
             
              end
         | 
| 1796 1885 |  | 
| 1797 1886 | 
             
              private def vi_replace_char(key, arg: 1)
         | 
| 1798 | 
            -
                @waiting_proc = ->( | 
| 1887 | 
            +
                @waiting_proc = ->(k) {
         | 
| 1799 1888 | 
             
                  if arg == 1
         | 
| 1800 1889 | 
             
                    byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
         | 
| 1801 1890 | 
             
                    before = @line.byteslice(0, @byte_pointer)
         | 
| 1802 1891 | 
             
                    remaining_point = @byte_pointer + byte_size
         | 
| 1803 1892 | 
             
                    after = @line.byteslice(remaining_point, @line.size - remaining_point)
         | 
| 1804 | 
            -
                    @line = before +  | 
| 1893 | 
            +
                    @line = before + k.chr + after
         | 
| 1805 1894 | 
             
                    @cursor_max = calculate_width(@line)
         | 
| 1806 1895 | 
             
                    @waiting_proc = nil
         | 
| 1807 1896 | 
             
                  elsif arg > 1
         | 
| @@ -1812,7 +1901,7 @@ class Reline::LineEditor | |
| 1812 1901 | 
             
                    before = @line.byteslice(0, @byte_pointer)
         | 
| 1813 1902 | 
             
                    remaining_point = @byte_pointer + byte_size
         | 
| 1814 1903 | 
             
                    after = @line.byteslice(remaining_point, @line.size - remaining_point)
         | 
| 1815 | 
            -
                    replaced =  | 
| 1904 | 
            +
                    replaced = k.chr * arg
         | 
| 1816 1905 | 
             
                    @line = before + replaced + after
         | 
| 1817 1906 | 
             
                    @byte_pointer += replaced.bytesize
         | 
| 1818 1907 | 
             
                    @cursor += calculate_width(replaced)
         | 
| @@ -1873,4 +1962,20 @@ class Reline::LineEditor | |
| 1873 1962 | 
             
                arg -= 1
         | 
| 1874 1963 | 
             
                vi_join_lines(key, arg: arg) if arg > 0
         | 
| 1875 1964 | 
             
              end
         | 
| 1965 | 
            +
             | 
| 1966 | 
            +
              private def em_set_mark(key)
         | 
| 1967 | 
            +
                @mark_pointer = [@byte_pointer, @line_index]
         | 
| 1968 | 
            +
              end
         | 
| 1969 | 
            +
              alias_method :set_mark, :em_set_mark
         | 
| 1970 | 
            +
             | 
| 1971 | 
            +
              private def em_exchange_mark(key)
         | 
| 1972 | 
            +
                new_pointer = [@byte_pointer, @line_index]
         | 
| 1973 | 
            +
                @previous_line_index = @line_index
         | 
| 1974 | 
            +
                @byte_pointer, @line_index = @mark_pointer
         | 
| 1975 | 
            +
                @byte_pointer, @line_index = @mark_pointer
         | 
| 1976 | 
            +
                @cursor = calculate_width(@line.byteslice(0, @byte_pointer))
         | 
| 1977 | 
            +
                @cursor_max = calculate_width(@line)
         | 
| 1978 | 
            +
                @mark_pointer = new_pointer
         | 
| 1979 | 
            +
              end
         | 
| 1980 | 
            +
              alias_method :exchange_point_and_mark, :em_exchange_mark
         | 
| 1876 1981 | 
             
            end
         |