reline 0.5.9 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/reline/config.rb +41 -50
- data/lib/reline/face.rb +1 -1
- data/lib/reline/history.rb +3 -3
- data/lib/reline/io/ansi.rb +77 -123
- data/lib/reline/io/dumb.rb +16 -2
- data/lib/reline/io/windows.rb +94 -67
- data/lib/reline/io.rb +14 -0
- data/lib/reline/key_actor/base.rb +10 -4
- data/lib/reline/key_actor/emacs.rb +96 -96
- data/lib/reline/key_actor/vi_command.rb +182 -182
- data/lib/reline/key_actor/vi_insert.rb +137 -137
- data/lib/reline/key_stroke.rb +26 -16
- data/lib/reline/line_editor.rb +331 -534
- data/lib/reline/unicode/east_asian_width.rb +1289 -1192
- data/lib/reline/unicode.rb +155 -436
- data/lib/reline/version.rb +1 -1
- data/lib/reline.rb +46 -34
- metadata +3 -7
- data/lib/reline/terminfo.rb +0 -158
    
        data/lib/reline/line_editor.rb
    CHANGED
    
    | @@ -13,7 +13,6 @@ class Reline::LineEditor | |
| 13 13 | 
             
              attr_accessor :prompt_proc
         | 
| 14 14 | 
             
              attr_accessor :auto_indent_proc
         | 
| 15 15 | 
             
              attr_accessor :dig_perfect_match_proc
         | 
| 16 | 
            -
              attr_writer :output
         | 
| 17 16 |  | 
| 18 17 | 
             
              VI_MOTIONS = %i{
         | 
| 19 18 | 
             
                ed_prev_char
         | 
| @@ -36,7 +35,6 @@ class Reline::LineEditor | |
| 36 35 |  | 
| 37 36 | 
             
              module CompletionState
         | 
| 38 37 | 
             
                NORMAL = :normal
         | 
| 39 | 
            -
                COMPLETION = :completion
         | 
| 40 38 | 
             
                MENU = :menu
         | 
| 41 39 | 
             
                MENU_WITH_PERFECT_MATCH = :menu_with_perfect_match
         | 
| 42 40 | 
             
                PERFECT_MATCH = :perfect_match
         | 
| @@ -72,17 +70,21 @@ class Reline::LineEditor | |
| 72 70 |  | 
| 73 71 | 
             
              MINIMUM_SCROLLBAR_HEIGHT = 1
         | 
| 74 72 |  | 
| 75 | 
            -
              def initialize(config | 
| 73 | 
            +
              def initialize(config)
         | 
| 76 74 | 
             
                @config = config
         | 
| 77 75 | 
             
                @completion_append_character = ''
         | 
| 78 76 | 
             
                @screen_size = [0, 0] # Should be initialized with actual winsize in LineEditor#reset
         | 
| 79 | 
            -
                reset_variables | 
| 77 | 
            +
                reset_variables
         | 
| 80 78 | 
             
              end
         | 
| 81 79 |  | 
| 82 80 | 
             
              def io_gate
         | 
| 83 81 | 
             
                Reline::IOGate
         | 
| 84 82 | 
             
              end
         | 
| 85 83 |  | 
| 84 | 
            +
              def encoding
         | 
| 85 | 
            +
                io_gate.encoding
         | 
| 86 | 
            +
              end
         | 
| 87 | 
            +
             | 
| 86 88 | 
             
              def set_pasting_state(in_pasting)
         | 
| 87 89 | 
             
                # While pasting, text to be inserted is stored to @continuous_insertion_buffer.
         | 
| 88 90 | 
             
                # After pasting, this buffer should be force inserted.
         | 
| @@ -136,9 +138,9 @@ class Reline::LineEditor | |
| 136 138 | 
             
                end
         | 
| 137 139 | 
             
              end
         | 
| 138 140 |  | 
| 139 | 
            -
              def reset(prompt = '' | 
| 141 | 
            +
              def reset(prompt = '')
         | 
| 140 142 | 
             
                @screen_size = Reline::IOGate.get_screen_size
         | 
| 141 | 
            -
                reset_variables(prompt | 
| 143 | 
            +
                reset_variables(prompt)
         | 
| 142 144 | 
             
                @rendered_screen.base_y = Reline::IOGate.cursor_pos.y
         | 
| 143 145 | 
             
                if ENV.key?('RELINE_ALT_SCROLLBAR')
         | 
| 144 146 | 
             
                  @full_block = '::'
         | 
| @@ -150,7 +152,7 @@ class Reline::LineEditor | |
| 150 152 | 
             
                  @upper_half_block = '▀'
         | 
| 151 153 | 
             
                  @lower_half_block = '▄'
         | 
| 152 154 | 
             
                  @block_elem_width = 1
         | 
| 153 | 
            -
                elsif  | 
| 155 | 
            +
                elsif encoding == Encoding::UTF_8
         | 
| 154 156 | 
             
                  @full_block = '█'
         | 
| 155 157 | 
             
                  @upper_half_block = '▀'
         | 
| 156 158 | 
             
                  @lower_half_block = '▄'
         | 
| @@ -176,9 +178,8 @@ class Reline::LineEditor | |
| 176 178 | 
             
                scroll_into_view
         | 
| 177 179 | 
             
                Reline::IOGate.move_cursor_up @rendered_screen.cursor_y
         | 
| 178 180 | 
             
                @rendered_screen.base_y = Reline::IOGate.cursor_pos.y
         | 
| 179 | 
            -
                 | 
| 180 | 
            -
                 | 
| 181 | 
            -
                render_differential
         | 
| 181 | 
            +
                clear_rendered_screen_cache
         | 
| 182 | 
            +
                render
         | 
| 182 183 | 
             
              end
         | 
| 183 184 |  | 
| 184 185 | 
             
              private def handle_interrupted
         | 
| @@ -186,11 +187,11 @@ class Reline::LineEditor | |
| 186 187 |  | 
| 187 188 | 
             
                @interrupted = false
         | 
| 188 189 | 
             
                clear_dialogs
         | 
| 189 | 
            -
                 | 
| 190 | 
            -
                 | 
| 190 | 
            +
                render
         | 
| 191 | 
            +
                cursor_to_bottom_offset = @rendered_screen.lines.size - @rendered_screen.cursor_y
         | 
| 192 | 
            +
                Reline::IOGate.scroll_down cursor_to_bottom_offset
         | 
| 191 193 | 
             
                Reline::IOGate.move_cursor_column 0
         | 
| 192 | 
            -
                 | 
| 193 | 
            -
                @rendered_screen.cursor_y = 0
         | 
| 194 | 
            +
                clear_rendered_screen_cache
         | 
| 194 195 | 
             
                case @old_trap
         | 
| 195 196 | 
             
                when 'DEFAULT', 'SYSTEM_DEFAULT'
         | 
| 196 197 | 
             
                  raise Interrupt
         | 
| @@ -220,10 +221,9 @@ class Reline::LineEditor | |
| 220 221 | 
             
                @eof
         | 
| 221 222 | 
             
              end
         | 
| 222 223 |  | 
| 223 | 
            -
              def reset_variables(prompt = '' | 
| 224 | 
            +
              def reset_variables(prompt = '')
         | 
| 224 225 | 
             
                @prompt = prompt.gsub("\n", "\\n")
         | 
| 225 226 | 
             
                @mark_pointer = nil
         | 
| 226 | 
            -
                @encoding = encoding
         | 
| 227 227 | 
             
                @is_multiline = false
         | 
| 228 228 | 
             
                @finished = false
         | 
| 229 229 | 
             
                @history_pointer = nil
         | 
| @@ -240,7 +240,7 @@ class Reline::LineEditor | |
| 240 240 | 
             
                @searching_prompt = nil
         | 
| 241 241 | 
             
                @just_cursor_moving = false
         | 
| 242 242 | 
             
                @eof = false
         | 
| 243 | 
            -
                @continuous_insertion_buffer = String.new(encoding:  | 
| 243 | 
            +
                @continuous_insertion_buffer = String.new(encoding: encoding)
         | 
| 244 244 | 
             
                @scroll_partial_screen = 0
         | 
| 245 245 | 
             
                @drop_terminate_spaces = false
         | 
| 246 246 | 
             
                @in_pasting = false
         | 
| @@ -250,9 +250,9 @@ class Reline::LineEditor | |
| 250 250 | 
             
                @resized = false
         | 
| 251 251 | 
             
                @cache = {}
         | 
| 252 252 | 
             
                @rendered_screen = RenderedScreen.new(base_y: 0, lines: [], cursor_y: 0)
         | 
| 253 | 
            -
                @ | 
| 254 | 
            -
                @ | 
| 255 | 
            -
                @ | 
| 253 | 
            +
                @undo_redo_history = [[[""], 0, 0]]
         | 
| 254 | 
            +
                @undo_redo_index = 0
         | 
| 255 | 
            +
                @restoring = false
         | 
| 256 256 | 
             
                @prev_action_state = NullActionState
         | 
| 257 257 | 
             
                @next_action_state = NullActionState
         | 
| 258 258 | 
             
                reset_line
         | 
| @@ -260,11 +260,10 @@ class Reline::LineEditor | |
| 260 260 |  | 
| 261 261 | 
             
              def reset_line
         | 
| 262 262 | 
             
                @byte_pointer = 0
         | 
| 263 | 
            -
                @buffer_of_lines = [String.new(encoding:  | 
| 263 | 
            +
                @buffer_of_lines = [String.new(encoding: encoding)]
         | 
| 264 264 | 
             
                @line_index = 0
         | 
| 265 265 | 
             
                @cache.clear
         | 
| 266 266 | 
             
                @line_backup_in_history = nil
         | 
| 267 | 
            -
                @multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
         | 
| 268 267 | 
             
              end
         | 
| 269 268 |  | 
| 270 269 | 
             
              def multiline_on
         | 
| @@ -276,7 +275,7 @@ class Reline::LineEditor | |
| 276 275 | 
             
              end
         | 
| 277 276 |  | 
| 278 277 | 
             
              private def insert_new_line(cursor_line, next_line)
         | 
| 279 | 
            -
                @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding:  | 
| 278 | 
            +
                @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: encoding))
         | 
| 280 279 | 
             
                @buffer_of_lines[@line_index] = cursor_line
         | 
| 281 280 | 
             
                @line_index += 1
         | 
| 282 281 | 
             
                @byte_pointer = 0
         | 
| @@ -298,8 +297,8 @@ class Reline::LineEditor | |
| 298 297 | 
             
                end
         | 
| 299 298 | 
             
              end
         | 
| 300 299 |  | 
| 301 | 
            -
              private def  | 
| 302 | 
            -
                Reline::Unicode. | 
| 300 | 
            +
              private def split_line_by_width(str, max_width, offset: 0)
         | 
| 301 | 
            +
                Reline::Unicode.split_line_by_width(str, max_width, encoding, offset: offset)
         | 
| 303 302 | 
             
              end
         | 
| 304 303 |  | 
| 305 304 | 
             
              def current_byte_pointer_cursor
         | 
| @@ -389,8 +388,8 @@ class Reline::LineEditor | |
| 389 388 | 
             
                    if (cached = cached_wraps[[prompt, line]])
         | 
| 390 389 | 
             
                      next cached
         | 
| 391 390 | 
             
                    end
         | 
| 392 | 
            -
                    *wrapped_prompts, code_line_prompt =  | 
| 393 | 
            -
                    wrapped_lines =  | 
| 391 | 
            +
                    *wrapped_prompts, code_line_prompt = split_line_by_width(prompt, width)
         | 
| 392 | 
            +
                    wrapped_lines = split_line_by_width(line, width, offset: calculate_width(code_line_prompt, true))
         | 
| 394 393 | 
             
                    wrapped_prompts.map { |p| [p, ''] } + [[code_line_prompt, wrapped_lines.first]] + wrapped_lines.drop(1).map { |c| ['', c] }
         | 
| 395 394 | 
             
                  end
         | 
| 396 395 | 
             
                end
         | 
| @@ -414,7 +413,7 @@ class Reline::LineEditor | |
| 414 413 | 
             
                    # do nothing
         | 
| 415 414 | 
             
                  elsif level == :blank
         | 
| 416 415 | 
             
                    Reline::IOGate.move_cursor_column base_x
         | 
| 417 | 
            -
                     | 
| 416 | 
            +
                    Reline::IOGate.write "#{Reline::IOGate.reset_color_sequence}#{' ' * width}"
         | 
| 418 417 | 
             
                  else
         | 
| 419 418 | 
             
                    x, w, content = new_items[level]
         | 
| 420 419 | 
             
                    cover_begin = base_x != 0 && new_levels[base_x - 1] == level
         | 
| @@ -424,7 +423,7 @@ class Reline::LineEditor | |
| 424 423 | 
             
                      content, pos = Reline::Unicode.take_mbchar_range(content, base_x - x, width, cover_begin: cover_begin, cover_end: cover_end, padding: true)
         | 
| 425 424 | 
             
                    end
         | 
| 426 425 | 
             
                    Reline::IOGate.move_cursor_column x + pos
         | 
| 427 | 
            -
                     | 
| 426 | 
            +
                    Reline::IOGate.write "#{Reline::IOGate.reset_color_sequence}#{content}#{Reline::IOGate.reset_color_sequence}"
         | 
| 428 427 | 
             
                  end
         | 
| 429 428 | 
             
                  base_x += width
         | 
| 430 429 | 
             
                end
         | 
| @@ -437,8 +436,8 @@ class Reline::LineEditor | |
| 437 436 | 
             
              # Calculate cursor position in word wrapped content.
         | 
| 438 437 | 
             
              def wrapped_cursor_position
         | 
| 439 438 | 
             
                prompt_width = calculate_width(prompt_list[@line_index], true)
         | 
| 440 | 
            -
                line_before_cursor = whole_lines[@line_index].byteslice(0, @byte_pointer)
         | 
| 441 | 
            -
                wrapped_line_before_cursor =  | 
| 439 | 
            +
                line_before_cursor = Reline::Unicode.escape_for_print(whole_lines[@line_index].byteslice(0, @byte_pointer))
         | 
| 440 | 
            +
                wrapped_line_before_cursor = split_line_by_width(' ' * prompt_width + line_before_cursor, screen_width)
         | 
| 442 441 | 
             
                wrapped_cursor_y = wrapped_prompt_and_input_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1
         | 
| 443 442 | 
             
                wrapped_cursor_x = calculate_width(wrapped_line_before_cursor.last)
         | 
| 444 443 | 
             
                [wrapped_cursor_x, wrapped_cursor_y]
         | 
| @@ -460,49 +459,27 @@ class Reline::LineEditor | |
| 460 459 | 
             
              end
         | 
| 461 460 |  | 
| 462 461 | 
             
              def render_finished
         | 
| 463 | 
            -
                 | 
| 464 | 
            -
             | 
| 465 | 
            -
             | 
| 466 | 
            -
             | 
| 467 | 
            -
             | 
| 468 | 
            -
             | 
| 469 | 
            -
             | 
| 470 | 
            -
             | 
| 471 | 
            -
                num_lines = @rendered_screen.lines.size
         | 
| 472 | 
            -
                return unless num_lines && num_lines >= 1
         | 
| 473 | 
            -
             | 
| 474 | 
            -
                Reline::IOGate.move_cursor_down num_lines - 1
         | 
| 475 | 
            -
                (num_lines - 1).times do
         | 
| 476 | 
            -
                  Reline::IOGate.erase_after_cursor
         | 
| 477 | 
            -
                  Reline::IOGate.move_cursor_up 1
         | 
| 478 | 
            -
                end
         | 
| 479 | 
            -
                Reline::IOGate.erase_after_cursor
         | 
| 480 | 
            -
                @rendered_screen.lines = []
         | 
| 481 | 
            -
                @rendered_screen.cursor_y = 0
         | 
| 482 | 
            -
              end
         | 
| 483 | 
            -
             | 
| 484 | 
            -
              def render_full_content
         | 
| 485 | 
            -
                lines = @buffer_of_lines.size.times.map do |i|
         | 
| 486 | 
            -
                  line = prompt_list[i] + modified_lines[i]
         | 
| 487 | 
            -
                  wrapped_lines, = split_by_width(line, screen_width)
         | 
| 488 | 
            -
                  wrapped_lines.last.empty? ? "#{line} " : line
         | 
| 462 | 
            +
                Reline::IOGate.buffered_output do
         | 
| 463 | 
            +
                  render_differential([], 0, 0)
         | 
| 464 | 
            +
                  lines = @buffer_of_lines.size.times.map do |i|
         | 
| 465 | 
            +
                    line = Reline::Unicode.strip_non_printing_start_end(prompt_list[i]) + modified_lines[i]
         | 
| 466 | 
            +
                    wrapped_lines = split_line_by_width(line, screen_width)
         | 
| 467 | 
            +
                    wrapped_lines.last.empty? ? "#{line} " : line
         | 
| 468 | 
            +
                  end
         | 
| 469 | 
            +
                  Reline::IOGate.write lines.map { |l| "#{l}\r\n" }.join
         | 
| 489 470 | 
             
                end
         | 
| 490 | 
            -
                @output.puts lines.map { |l| "#{l}\r\n" }.join
         | 
| 491 471 | 
             
              end
         | 
| 492 472 |  | 
| 493 | 
            -
              def print_nomultiline_prompt | 
| 494 | 
            -
                 | 
| 495 | 
            -
             | 
| 473 | 
            +
              def print_nomultiline_prompt
         | 
| 474 | 
            +
                Reline::IOGate.disable_auto_linewrap(true) if Reline::IOGate.win?
         | 
| 496 475 | 
             
                # Readline's test `TestRelineAsReadline#test_readline` requires first output to be prompt, not cursor reset escape sequence.
         | 
| 497 | 
            -
                 | 
| 498 | 
            -
             | 
| 499 | 
            -
                 | 
| 476 | 
            +
                Reline::IOGate.write Reline::Unicode.strip_non_printing_start_end(@prompt) if @prompt && !@is_multiline
         | 
| 477 | 
            +
              ensure
         | 
| 478 | 
            +
                Reline::IOGate.disable_auto_linewrap(false) if Reline::IOGate.win?
         | 
| 500 479 | 
             
              end
         | 
| 501 480 |  | 
| 502 | 
            -
              def  | 
| 481 | 
            +
              def render
         | 
| 503 482 | 
             
                wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
         | 
| 504 | 
            -
             | 
| 505 | 
            -
                rendered_lines = @rendered_screen.lines
         | 
| 506 483 | 
             
                new_lines = wrapped_prompt_and_input_lines.flatten(1)[screen_scroll_top, screen_height].map do |prompt, line|
         | 
| 507 484 | 
             
                  prompt_width = Reline::Unicode.calculate_width(prompt, true)
         | 
| 508 485 | 
             
                  [[0, prompt_width, prompt], [prompt_width, Reline::Unicode.calculate_width(line, true), line]]
         | 
| @@ -520,12 +497,24 @@ class Reline::LineEditor | |
| 520 497 | 
             
                  x_range, y_range = dialog_range dialog, wrapped_cursor_y - screen_scroll_top
         | 
| 521 498 | 
             
                  y_range.each do |row|
         | 
| 522 499 | 
             
                    next if row < 0 || row >= screen_height
         | 
| 500 | 
            +
             | 
| 523 501 | 
             
                    dialog_rows = new_lines[row] ||= []
         | 
| 524 502 | 
             
                    # index 0 is for prompt, index 1 is for line, index 2.. is for dialog
         | 
| 525 503 | 
             
                    dialog_rows[index + 2] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
         | 
| 526 504 | 
             
                  end
         | 
| 527 505 | 
             
                end
         | 
| 528 506 |  | 
| 507 | 
            +
                Reline::IOGate.buffered_output do
         | 
| 508 | 
            +
                  render_differential new_lines, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top
         | 
| 509 | 
            +
                end
         | 
| 510 | 
            +
              end
         | 
| 511 | 
            +
             | 
| 512 | 
            +
              # Reflects lines to be rendered and new cursor position to the screen
         | 
| 513 | 
            +
              # by calculating the difference from the previous render.
         | 
| 514 | 
            +
             | 
| 515 | 
            +
              private def render_differential(new_lines, new_cursor_x, new_cursor_y)
         | 
| 516 | 
            +
                Reline::IOGate.disable_auto_linewrap(true) if Reline::IOGate.win?
         | 
| 517 | 
            +
                rendered_lines = @rendered_screen.lines
         | 
| 529 518 | 
             
                cursor_y = @rendered_screen.cursor_y
         | 
| 530 519 | 
             
                if new_lines != rendered_lines
         | 
| 531 520 | 
             
                  # Hide cursor while rendering to avoid cursor flickering.
         | 
| @@ -552,11 +541,17 @@ class Reline::LineEditor | |
| 552 541 | 
             
                  @rendered_screen.lines = new_lines
         | 
| 553 542 | 
             
                  Reline::IOGate.show_cursor
         | 
| 554 543 | 
             
                end
         | 
| 555 | 
            -
                 | 
| 556 | 
            -
                 | 
| 557 | 
            -
                Reline::IOGate.move_cursor_down  | 
| 558 | 
            -
                @rendered_screen.cursor_y =  | 
| 559 | 
            -
             | 
| 544 | 
            +
                Reline::IOGate.move_cursor_column new_cursor_x
         | 
| 545 | 
            +
                new_cursor_y = new_cursor_y.clamp(0, screen_height - 1)
         | 
| 546 | 
            +
                Reline::IOGate.move_cursor_down new_cursor_y - cursor_y
         | 
| 547 | 
            +
                @rendered_screen.cursor_y = new_cursor_y
         | 
| 548 | 
            +
              ensure
         | 
| 549 | 
            +
                Reline::IOGate.disable_auto_linewrap(false) if Reline::IOGate.win?
         | 
| 550 | 
            +
              end
         | 
| 551 | 
            +
             | 
| 552 | 
            +
              private def clear_rendered_screen_cache
         | 
| 553 | 
            +
                @rendered_screen.lines = []
         | 
| 554 | 
            +
                @rendered_screen.cursor_y = 0
         | 
| 560 555 | 
             
              end
         | 
| 561 556 |  | 
| 562 557 | 
             
              def upper_space_height(wrapped_cursor_y)
         | 
| @@ -568,7 +563,7 @@ class Reline::LineEditor | |
| 568 563 | 
             
              end
         | 
| 569 564 |  | 
| 570 565 | 
             
              def rerender
         | 
| 571 | 
            -
                 | 
| 566 | 
            +
                render unless @in_pasting
         | 
| 572 567 | 
             
              end
         | 
| 573 568 |  | 
| 574 569 | 
             
              class DialogProcScope
         | 
| @@ -586,8 +581,9 @@ class Reline::LineEditor | |
| 586 581 | 
             
                  @context
         | 
| 587 582 | 
             
                end
         | 
| 588 583 |  | 
| 589 | 
            -
                def retrieve_completion_block( | 
| 590 | 
            -
                  @line_editor.retrieve_completion_block | 
| 584 | 
            +
                def retrieve_completion_block(_unused = false)
         | 
| 585 | 
            +
                  preposing, target, postposing, _quote = @line_editor.retrieve_completion_block
         | 
| 586 | 
            +
                  [preposing, target, postposing]
         | 
| 591 587 | 
             
                end
         | 
| 592 588 |  | 
| 593 589 | 
             
                def call_completion_proc_with_checking_args(pre, target, post)
         | 
| @@ -807,98 +803,73 @@ class Reline::LineEditor | |
| 807 803 | 
             
                @config.editing_mode
         | 
| 808 804 | 
             
              end
         | 
| 809 805 |  | 
| 810 | 
            -
              private def menu( | 
| 806 | 
            +
              private def menu(list)
         | 
| 811 807 | 
             
                @menu_info = MenuInfo.new(list)
         | 
| 812 808 | 
             
              end
         | 
| 813 809 |  | 
| 814 | 
            -
              private def  | 
| 815 | 
            -
                 | 
| 816 | 
            -
                list | 
| 817 | 
            -
                   | 
| 818 | 
            -
             | 
| 810 | 
            +
              private def filter_normalize_candidates(target, list)
         | 
| 811 | 
            +
                target = target.downcase if @config.completion_ignore_case
         | 
| 812 | 
            +
                list.select do |item|
         | 
| 813 | 
            +
                  next unless item
         | 
| 814 | 
            +
                  unless Encoding.compatible?(target.encoding, item.encoding)
         | 
| 815 | 
            +
                    # Workaround for Readline test
         | 
| 816 | 
            +
                    if defined?(::Readline) && ::Readline == ::Reline
         | 
| 817 | 
            +
                      raise Encoding::CompatibilityError, "incompatible character encodings: #{target.encoding} and #{item.encoding}"
         | 
| 818 | 
            +
                    end
         | 
| 819 819 | 
             
                  end
         | 
| 820 | 
            +
             | 
| 820 821 | 
             
                  if @config.completion_ignore_case
         | 
| 821 | 
            -
                     | 
| 822 | 
            +
                    item.downcase.start_with?(target)
         | 
| 822 823 | 
             
                  else
         | 
| 823 | 
            -
                     | 
| 824 | 
            -
                  end
         | 
| 825 | 
            -
                }.uniq
         | 
| 826 | 
            -
                if is_menu
         | 
| 827 | 
            -
                  menu(target, list)
         | 
| 828 | 
            -
                  return nil
         | 
| 829 | 
            -
                end
         | 
| 830 | 
            -
                completed = list.inject { |memo, item|
         | 
| 831 | 
            -
                  begin
         | 
| 832 | 
            -
                    memo_mbchars = memo.unicode_normalize.grapheme_clusters
         | 
| 833 | 
            -
                    item_mbchars = item.unicode_normalize.grapheme_clusters
         | 
| 834 | 
            -
                  rescue Encoding::CompatibilityError
         | 
| 835 | 
            -
                    memo_mbchars = memo.grapheme_clusters
         | 
| 836 | 
            -
                    item_mbchars = item.grapheme_clusters
         | 
| 837 | 
            -
                  end
         | 
| 838 | 
            -
                  size = [memo_mbchars.size, item_mbchars.size].min
         | 
| 839 | 
            -
                  result = +''
         | 
| 840 | 
            -
                  size.times do |i|
         | 
| 841 | 
            -
                    if @config.completion_ignore_case
         | 
| 842 | 
            -
                      if memo_mbchars[i].casecmp?(item_mbchars[i])
         | 
| 843 | 
            -
                        result << memo_mbchars[i]
         | 
| 844 | 
            -
                      else
         | 
| 845 | 
            -
                        break
         | 
| 846 | 
            -
                      end
         | 
| 847 | 
            -
                    else
         | 
| 848 | 
            -
                      if memo_mbchars[i] == item_mbchars[i]
         | 
| 849 | 
            -
                        result << memo_mbchars[i]
         | 
| 850 | 
            -
                      else
         | 
| 851 | 
            -
                        break
         | 
| 852 | 
            -
                      end
         | 
| 853 | 
            -
                    end
         | 
| 824 | 
            +
                    item.start_with?(target)
         | 
| 854 825 | 
             
                  end
         | 
| 855 | 
            -
             | 
| 856 | 
            -
             | 
| 857 | 
            -
                 | 
| 826 | 
            +
                end.map do |item|
         | 
| 827 | 
            +
                  item.unicode_normalize
         | 
| 828 | 
            +
                rescue Encoding::CompatibilityError
         | 
| 829 | 
            +
                  item
         | 
| 830 | 
            +
                end.uniq
         | 
| 858 831 | 
             
              end
         | 
| 859 832 |  | 
| 860 | 
            -
              private def perform_completion( | 
| 833 | 
            +
              private def perform_completion(preposing, target, postposing, quote, list)
         | 
| 834 | 
            +
                candidates = filter_normalize_candidates(target, list)
         | 
| 835 | 
            +
             | 
| 861 836 | 
             
                case @completion_state
         | 
| 862 | 
            -
                when CompletionState::NORMAL
         | 
| 863 | 
            -
                  @completion_state = CompletionState::COMPLETION
         | 
| 864 837 | 
             
                when CompletionState::PERFECT_MATCH
         | 
| 865 | 
            -
                  @dig_perfect_match_proc | 
| 866 | 
            -
             | 
| 867 | 
            -
             | 
| 868 | 
            -
                   | 
| 869 | 
            -
                 | 
| 870 | 
            -
                   | 
| 871 | 
            -
             | 
| 872 | 
            -
             | 
| 873 | 
            -
             | 
| 874 | 
            -
                  is_menu = false
         | 
| 875 | 
            -
                end
         | 
| 876 | 
            -
                result = complete_internal_proc(list, is_menu)
         | 
| 877 | 
            -
                if @completion_state == CompletionState::MENU_WITH_PERFECT_MATCH
         | 
| 838 | 
            +
                  if @dig_perfect_match_proc
         | 
| 839 | 
            +
                    @dig_perfect_match_proc.call(@perfect_matched)
         | 
| 840 | 
            +
                    return
         | 
| 841 | 
            +
                  end
         | 
| 842 | 
            +
                when CompletionState::MENU
         | 
| 843 | 
            +
                  menu(candidates)
         | 
| 844 | 
            +
                  return
         | 
| 845 | 
            +
                when CompletionState::MENU_WITH_PERFECT_MATCH
         | 
| 846 | 
            +
                  menu(candidates)
         | 
| 878 847 | 
             
                  @completion_state = CompletionState::PERFECT_MATCH
         | 
| 848 | 
            +
                  return
         | 
| 879 849 | 
             
                end
         | 
| 880 | 
            -
             | 
| 881 | 
            -
                 | 
| 882 | 
            -
                return if completed. | 
| 883 | 
            -
             | 
| 884 | 
            -
             | 
| 885 | 
            -
             | 
| 886 | 
            -
             | 
| 887 | 
            -
                     | 
| 888 | 
            -
             | 
| 889 | 
            -
             | 
| 890 | 
            -
                     | 
| 891 | 
            -
                    @ | 
| 850 | 
            +
             | 
| 851 | 
            +
                completed = Reline::Unicode.common_prefix(candidates, ignore_case: @config.completion_ignore_case)
         | 
| 852 | 
            +
                return if completed.empty?
         | 
| 853 | 
            +
             | 
| 854 | 
            +
                append_character = ''
         | 
| 855 | 
            +
                if candidates.include?(completed)
         | 
| 856 | 
            +
                  if candidates.one?
         | 
| 857 | 
            +
                    append_character = quote || completion_append_character.to_s
         | 
| 858 | 
            +
                    @completion_state = CompletionState::PERFECT_MATCH
         | 
| 859 | 
            +
                  elsif @config.show_all_if_ambiguous
         | 
| 860 | 
            +
                    menu(candidates)
         | 
| 861 | 
            +
                    @completion_state = CompletionState::PERFECT_MATCH
         | 
| 892 862 | 
             
                  else
         | 
| 893 | 
            -
                    @completion_state = CompletionState:: | 
| 894 | 
            -
                    perform_completion(list, true) if @config.show_all_if_ambiguous
         | 
| 895 | 
            -
                  end
         | 
| 896 | 
            -
                  if not just_show_list and target < completed
         | 
| 897 | 
            -
                    @buffer_of_lines[@line_index] = (preposing + completed + completion_append_character.to_s + postposing).split("\n")[@line_index] || String.new(encoding: @encoding)
         | 
| 898 | 
            -
                    line_to_pointer = (preposing + completed + completion_append_character.to_s).split("\n")[@line_index] || String.new(encoding: @encoding)
         | 
| 899 | 
            -
                    @byte_pointer = line_to_pointer.bytesize
         | 
| 863 | 
            +
                    @completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
         | 
| 900 864 | 
             
                  end
         | 
| 865 | 
            +
                  @perfect_matched = completed
         | 
| 866 | 
            +
                else
         | 
| 867 | 
            +
                  @completion_state = CompletionState::MENU
         | 
| 868 | 
            +
                  menu(candidates) if @config.show_all_if_ambiguous
         | 
| 901 869 | 
             
                end
         | 
| 870 | 
            +
                @buffer_of_lines[@line_index] = (preposing + completed + append_character + postposing).split("\n")[@line_index] || String.new(encoding: encoding)
         | 
| 871 | 
            +
                line_to_pointer = (preposing + completed + append_character).split("\n")[@line_index] || String.new(encoding: encoding)
         | 
| 872 | 
            +
                @byte_pointer = line_to_pointer.bytesize
         | 
| 902 873 | 
             
              end
         | 
| 903 874 |  | 
| 904 875 | 
             
              def dialog_proc_scope_completion_journey_data
         | 
| @@ -927,8 +898,8 @@ class Reline::LineEditor | |
| 927 898 | 
             
              end
         | 
| 928 899 |  | 
| 929 900 | 
             
              private def retrieve_completion_journey_state
         | 
| 930 | 
            -
                preposing, target, postposing = retrieve_completion_block
         | 
| 931 | 
            -
                list = call_completion_proc
         | 
| 901 | 
            +
                preposing, target, postposing, quote = retrieve_completion_block
         | 
| 902 | 
            +
                list = call_completion_proc(preposing, target, postposing, quote)
         | 
| 932 903 | 
             
                return unless list.is_a?(Array)
         | 
| 933 904 |  | 
| 934 905 | 
             
                candidates = list.select{ |item| item.start_with?(target) }
         | 
| @@ -941,28 +912,36 @@ class Reline::LineEditor | |
| 941 912 | 
             
                )
         | 
| 942 913 | 
             
              end
         | 
| 943 914 |  | 
| 944 | 
            -
              private def run_for_operators(key, method_symbol | 
| 915 | 
            +
              private def run_for_operators(key, method_symbol)
         | 
| 916 | 
            +
                # Reject multibyte input (converted to ed_insert) in vi_command mode
         | 
| 917 | 
            +
                return if method_symbol == :ed_insert && @config.editing_mode_is?(:vi_command) && !@waiting_proc
         | 
| 918 | 
            +
             | 
| 919 | 
            +
                if ARGUMENT_DIGIT_METHODS.include?(method_symbol) && !@waiting_proc
         | 
| 920 | 
            +
                  wrap_method_call(method_symbol, key, false)
         | 
| 921 | 
            +
                  return
         | 
| 922 | 
            +
                end
         | 
| 923 | 
            +
             | 
| 945 924 | 
             
                if @vi_waiting_operator
         | 
| 946 | 
            -
                  if VI_MOTIONS.include?(method_symbol)
         | 
| 925 | 
            +
                  if @waiting_proc || VI_MOTIONS.include?(method_symbol)
         | 
| 947 926 | 
             
                    old_byte_pointer = @byte_pointer
         | 
| 948 927 | 
             
                    @vi_arg = (@vi_arg || 1) * @vi_waiting_operator_arg
         | 
| 949 | 
            -
                     | 
| 928 | 
            +
                    wrap_method_call(method_symbol, key, true)
         | 
| 950 929 | 
             
                    unless @waiting_proc
         | 
| 951 930 | 
             
                      byte_pointer_diff = @byte_pointer - old_byte_pointer
         | 
| 952 931 | 
             
                      @byte_pointer = old_byte_pointer
         | 
| 953 | 
            -
                       | 
| 954 | 
            -
                      wrap_method_call(@vi_waiting_operator, method_obj, byte_pointer_diff)
         | 
| 932 | 
            +
                      __send__(@vi_waiting_operator, byte_pointer_diff)
         | 
| 955 933 | 
             
                      cleanup_waiting
         | 
| 956 934 | 
             
                    end
         | 
| 957 935 | 
             
                  else
         | 
| 958 936 | 
             
                    # Ignores operator when not motion is given.
         | 
| 959 | 
            -
                     | 
| 937 | 
            +
                    wrap_method_call(method_symbol, key, false)
         | 
| 960 938 | 
             
                    cleanup_waiting
         | 
| 961 939 | 
             
                  end
         | 
| 962 | 
            -
                  @vi_arg = nil
         | 
| 963 940 | 
             
                else
         | 
| 964 | 
            -
                   | 
| 941 | 
            +
                  wrap_method_call(method_symbol, key, false)
         | 
| 965 942 | 
             
                end
         | 
| 943 | 
            +
                @vi_arg = nil
         | 
| 944 | 
            +
                @kill_ring.process
         | 
| 966 945 | 
             
              end
         | 
| 967 946 |  | 
| 968 947 | 
             
              private def argumentable?(method_obj)
         | 
| @@ -975,20 +954,23 @@ class Reline::LineEditor | |
| 975 954 | 
             
                method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :inclusive }
         | 
| 976 955 | 
             
              end
         | 
| 977 956 |  | 
| 978 | 
            -
              def wrap_method_call(method_symbol,  | 
| 979 | 
            -
                if @ | 
| 980 | 
            -
                   | 
| 981 | 
            -
                   | 
| 957 | 
            +
              def wrap_method_call(method_symbol, key, with_operator)
         | 
| 958 | 
            +
                if @waiting_proc
         | 
| 959 | 
            +
                  @waiting_proc.call(key)
         | 
| 960 | 
            +
                  return
         | 
| 982 961 | 
             
                end
         | 
| 962 | 
            +
             | 
| 963 | 
            +
                return unless respond_to?(method_symbol, true)
         | 
| 964 | 
            +
                method_obj = method(method_symbol)
         | 
| 983 965 | 
             
                if @vi_arg and argumentable?(method_obj)
         | 
| 984 | 
            -
                  if  | 
| 985 | 
            -
                    method_obj.(key, arg: @vi_arg, inclusive:  | 
| 966 | 
            +
                  if inclusive?(method_obj)
         | 
| 967 | 
            +
                    method_obj.(key, arg: @vi_arg, inclusive: with_operator)
         | 
| 986 968 | 
             
                  else
         | 
| 987 969 | 
             
                    method_obj.(key, arg: @vi_arg)
         | 
| 988 970 | 
             
                  end
         | 
| 989 971 | 
             
                else
         | 
| 990 | 
            -
                  if  | 
| 991 | 
            -
                    method_obj.(key, inclusive:  | 
| 972 | 
            +
                  if inclusive?(method_obj)
         | 
| 973 | 
            +
                    method_obj.(key, inclusive: with_operator)
         | 
| 992 974 | 
             
                  else
         | 
| 993 975 | 
             
                    method_obj.(key)
         | 
| 994 976 | 
             
                  end
         | 
| @@ -1003,90 +985,20 @@ class Reline::LineEditor | |
| 1003 985 | 
             
                @drop_terminate_spaces = false
         | 
| 1004 986 | 
             
              end
         | 
| 1005 987 |  | 
| 1006 | 
            -
               | 
| 1007 | 
            -
             | 
| 1008 | 
            -
                  cleanup_waiting
         | 
| 1009 | 
            -
                elsif @waiting_proc
         | 
| 1010 | 
            -
                  old_byte_pointer = @byte_pointer
         | 
| 1011 | 
            -
                  @waiting_proc.call(key)
         | 
| 1012 | 
            -
                  if @vi_waiting_operator
         | 
| 1013 | 
            -
                    byte_pointer_diff = @byte_pointer - old_byte_pointer
         | 
| 1014 | 
            -
                    @byte_pointer = old_byte_pointer
         | 
| 1015 | 
            -
                    method_obj = method(@vi_waiting_operator)
         | 
| 1016 | 
            -
                    wrap_method_call(@vi_waiting_operator, method_obj, byte_pointer_diff)
         | 
| 1017 | 
            -
                    cleanup_waiting
         | 
| 1018 | 
            -
                  end
         | 
| 1019 | 
            -
                  @kill_ring.process
         | 
| 1020 | 
            -
                  return
         | 
| 1021 | 
            -
                end
         | 
| 988 | 
            +
              ARGUMENT_DIGIT_METHODS = %i[ed_digit vi_zero ed_argument_digit]
         | 
| 989 | 
            +
              VI_WAITING_ACCEPT_METHODS = %i[vi_change_meta vi_delete_meta vi_yank ed_insert ed_argument_digit]
         | 
| 1022 990 |  | 
| 1023 | 
            -
             | 
| 1024 | 
            -
             | 
| 991 | 
            +
              private def process_key(key, method_symbol)
         | 
| 992 | 
            +
                if @waiting_proc
         | 
| 993 | 
            +
                  cleanup_waiting unless key.size == 1
         | 
| 1025 994 | 
             
                end
         | 
| 1026 | 
            -
                if  | 
| 1027 | 
            -
                   | 
| 1028 | 
            -
                    run_for_operators(key, method_symbol) do |with_operator|
         | 
| 1029 | 
            -
                      wrap_method_call(method_symbol, method_obj, key, with_operator)
         | 
| 1030 | 
            -
                    end
         | 
| 1031 | 
            -
                  else
         | 
| 1032 | 
            -
                    wrap_method_call(method_symbol, method_obj, key) if method_obj
         | 
| 1033 | 
            -
                  end
         | 
| 1034 | 
            -
                  @kill_ring.process
         | 
| 1035 | 
            -
                  if @vi_arg
         | 
| 1036 | 
            -
                    @vi_arg = nil
         | 
| 1037 | 
            -
                  end
         | 
| 1038 | 
            -
                elsif @vi_arg
         | 
| 1039 | 
            -
                  if key.chr =~ /[0-9]/
         | 
| 1040 | 
            -
                    ed_argument_digit(key)
         | 
| 1041 | 
            -
                  else
         | 
| 1042 | 
            -
                    if argumentable?(method_obj)
         | 
| 1043 | 
            -
                      run_for_operators(key, method_symbol) do |with_operator|
         | 
| 1044 | 
            -
                        wrap_method_call(method_symbol, method_obj, key, with_operator)
         | 
| 1045 | 
            -
                      end
         | 
| 1046 | 
            -
                    elsif method_obj
         | 
| 1047 | 
            -
                      wrap_method_call(method_symbol, method_obj, key)
         | 
| 1048 | 
            -
                    else
         | 
| 1049 | 
            -
                      ed_insert(key) unless @config.editing_mode_is?(:vi_command)
         | 
| 1050 | 
            -
                    end
         | 
| 1051 | 
            -
                    @kill_ring.process
         | 
| 1052 | 
            -
                    if @vi_arg
         | 
| 1053 | 
            -
                      @vi_arg = nil
         | 
| 1054 | 
            -
                    end
         | 
| 1055 | 
            -
                  end
         | 
| 1056 | 
            -
                elsif method_obj
         | 
| 1057 | 
            -
                  if method_symbol == :ed_argument_digit
         | 
| 1058 | 
            -
                    wrap_method_call(method_symbol, method_obj, key)
         | 
| 1059 | 
            -
                  else
         | 
| 1060 | 
            -
                    run_for_operators(key, method_symbol) do |with_operator|
         | 
| 1061 | 
            -
                      wrap_method_call(method_symbol, method_obj, key, with_operator)
         | 
| 1062 | 
            -
                    end
         | 
| 1063 | 
            -
                  end
         | 
| 1064 | 
            -
                  @kill_ring.process
         | 
| 1065 | 
            -
                else
         | 
| 1066 | 
            -
                  ed_insert(key) unless @config.editing_mode_is?(:vi_command)
         | 
| 995 | 
            +
                if @vi_waiting_operator
         | 
| 996 | 
            +
                  cleanup_waiting unless VI_WAITING_ACCEPT_METHODS.include?(method_symbol) || VI_MOTIONS.include?(method_symbol)
         | 
| 1067 997 | 
             
                end
         | 
| 1068 | 
            -
              end
         | 
| 1069 998 |  | 
| 1070 | 
            -
             | 
| 1071 | 
            -
             | 
| 1072 | 
            -
                 | 
| 1073 | 
            -
                  if @multibyte_buffer.dup.force_encoding(@encoding).valid_encoding?
         | 
| 1074 | 
            -
                    process_key(@multibyte_buffer.dup.force_encoding(@encoding), nil)
         | 
| 1075 | 
            -
                    @multibyte_buffer.clear
         | 
| 1076 | 
            -
                  else
         | 
| 1077 | 
            -
                    # invalid
         | 
| 1078 | 
            -
                    return
         | 
| 1079 | 
            -
                  end
         | 
| 1080 | 
            -
                else # single byte
         | 
| 1081 | 
            -
                  return if key.char >= 128 # maybe, first byte of multi byte
         | 
| 1082 | 
            -
                  method_symbol = @config.editing_mode.get_method(key.combined_char)
         | 
| 1083 | 
            -
                  process_key(key.combined_char, method_symbol)
         | 
| 1084 | 
            -
                  @multibyte_buffer.clear
         | 
| 1085 | 
            -
                end
         | 
| 1086 | 
            -
                if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
         | 
| 1087 | 
            -
                  byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer)
         | 
| 1088 | 
            -
                  @byte_pointer -= byte_size
         | 
| 1089 | 
            -
                end
         | 
| 999 | 
            +
                process_insert(force: method_symbol != :ed_insert)
         | 
| 1000 | 
            +
             | 
| 1001 | 
            +
                run_for_operators(key, method_symbol)
         | 
| 1090 1002 | 
             
              end
         | 
| 1091 1003 |  | 
| 1092 1004 | 
             
              def update(key)
         | 
| @@ -1100,25 +1012,22 @@ class Reline::LineEditor | |
| 1100 1012 | 
             
              end
         | 
| 1101 1013 |  | 
| 1102 1014 | 
             
              def input_key(key)
         | 
| 1103 | 
            -
                 | 
| 1015 | 
            +
                old_buffer_of_lines = @buffer_of_lines.dup
         | 
| 1104 1016 | 
             
                @config.reset_oneshot_key_bindings
         | 
| 1105 | 
            -
                @dialogs.each do |dialog|
         | 
| 1106 | 
            -
                  if key.char.instance_of?(Symbol) and key.char == dialog.name
         | 
| 1107 | 
            -
                    return
         | 
| 1108 | 
            -
                  end
         | 
| 1109 | 
            -
                end
         | 
| 1110 1017 | 
             
                if key.char.nil?
         | 
| 1111 1018 | 
             
                  process_insert(force: true)
         | 
| 1112 1019 | 
             
                  @eof = buffer_empty?
         | 
| 1113 1020 | 
             
                  finish
         | 
| 1114 1021 | 
             
                  return
         | 
| 1115 1022 | 
             
                end
         | 
| 1023 | 
            +
                return if @dialogs.any? { |dialog| dialog.name == key.method_symbol }
         | 
| 1024 | 
            +
             | 
| 1116 1025 | 
             
                @completion_occurs = false
         | 
| 1117 1026 |  | 
| 1118 | 
            -
                 | 
| 1119 | 
            -
             | 
| 1120 | 
            -
             | 
| 1121 | 
            -
                   | 
| 1027 | 
            +
                process_key(key.char, key.method_symbol)
         | 
| 1028 | 
            +
                if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
         | 
| 1029 | 
            +
                  byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer)
         | 
| 1030 | 
            +
                  @byte_pointer -= byte_size
         | 
| 1122 1031 | 
             
                end
         | 
| 1123 1032 |  | 
| 1124 1033 | 
             
                @prev_action_state, @next_action_state = @next_action_state, NullActionState
         | 
| @@ -1128,15 +1037,16 @@ class Reline::LineEditor | |
| 1128 1037 | 
             
                  @completion_journey_state = nil
         | 
| 1129 1038 | 
             
                end
         | 
| 1130 1039 |  | 
| 1131 | 
            -
                 | 
| 1132 | 
            -
             | 
| 1040 | 
            +
                modified = old_buffer_of_lines != @buffer_of_lines
         | 
| 1041 | 
            +
             | 
| 1042 | 
            +
                push_undo_redo(modified) unless @restoring
         | 
| 1043 | 
            +
                @restoring = false
         | 
| 1133 1044 |  | 
| 1134 1045 | 
             
                if @in_pasting
         | 
| 1135 1046 | 
             
                  clear_dialogs
         | 
| 1136 1047 | 
             
                  return
         | 
| 1137 1048 | 
             
                end
         | 
| 1138 1049 |  | 
| 1139 | 
            -
                modified = @old_buffer_of_lines != @buffer_of_lines
         | 
| 1140 1050 | 
             
                if !@completion_occurs && modified && !@config.disable_completion && @config.autocompletion
         | 
| 1141 1051 | 
             
                  # Auto complete starts only when edited
         | 
| 1142 1052 | 
             
                  process_insert(force: true)
         | 
| @@ -1145,26 +1055,17 @@ class Reline::LineEditor | |
| 1145 1055 | 
             
                modified
         | 
| 1146 1056 | 
             
              end
         | 
| 1147 1057 |  | 
| 1148 | 
            -
               | 
| 1149 | 
            -
             | 
| 1150 | 
            -
             | 
| 1151 | 
            -
             | 
| 1152 | 
            -
             | 
| 1153 | 
            -
             | 
| 1154 | 
            -
             | 
| 1058 | 
            +
              MAX_UNDO_REDO_HISTORY_SIZE = 100
         | 
| 1059 | 
            +
              def push_undo_redo(modified)
         | 
| 1060 | 
            +
                if modified
         | 
| 1061 | 
            +
                  @undo_redo_history = @undo_redo_history[0..@undo_redo_index]
         | 
| 1062 | 
            +
                  @undo_redo_history.push([@buffer_of_lines.dup, @byte_pointer, @line_index])
         | 
| 1063 | 
            +
                  if @undo_redo_history.size > MAX_UNDO_REDO_HISTORY_SIZE
         | 
| 1064 | 
            +
                    @undo_redo_history.shift
         | 
| 1065 | 
            +
                  end
         | 
| 1066 | 
            +
                  @undo_redo_index = @undo_redo_history.size - 1
         | 
| 1155 1067 | 
             
                else
         | 
| 1156 | 
            -
                  @ | 
| 1157 | 
            -
                  @input_lines_position += 1
         | 
| 1158 | 
            -
                  @input_lines.push([@buffer_of_lines.dup, @byte_pointer, @line_index])
         | 
| 1159 | 
            -
                end
         | 
| 1160 | 
            -
                trim_input_lines
         | 
| 1161 | 
            -
              end
         | 
| 1162 | 
            -
             | 
| 1163 | 
            -
              MAX_INPUT_LINES = 100
         | 
| 1164 | 
            -
              def trim_input_lines
         | 
| 1165 | 
            -
                if @input_lines.size > MAX_INPUT_LINES
         | 
| 1166 | 
            -
                  @input_lines.shift
         | 
| 1167 | 
            -
                  @input_lines_position -= 1
         | 
| 1068 | 
            +
                  @undo_redo_history[@undo_redo_index] = [@buffer_of_lines.dup, @byte_pointer, @line_index]
         | 
| 1168 1069 | 
             
                end
         | 
| 1169 1070 | 
             
              end
         | 
| 1170 1071 |  | 
| @@ -1178,9 +1079,8 @@ class Reline::LineEditor | |
| 1178 1079 | 
             
                end
         | 
| 1179 1080 | 
             
              end
         | 
| 1180 1081 |  | 
| 1181 | 
            -
              def call_completion_proc
         | 
| 1182 | 
            -
                 | 
| 1183 | 
            -
                pre, target, post = result
         | 
| 1082 | 
            +
              def call_completion_proc(pre, target, post, quote)
         | 
| 1083 | 
            +
                Reline.core.instance_variable_set(:@completion_quote_character, quote)
         | 
| 1184 1084 | 
             
                result = call_completion_proc_with_checking_args(pre, target, post)
         | 
| 1185 1085 | 
             
                Reline.core.instance_variable_set(:@completion_quote_character, nil)
         | 
| 1186 1086 | 
             
                result
         | 
| @@ -1244,84 +1144,32 @@ class Reline::LineEditor | |
| 1244 1144 | 
             
                process_auto_indent
         | 
| 1245 1145 | 
             
              end
         | 
| 1246 1146 |  | 
| 1247 | 
            -
              def  | 
| 1248 | 
            -
                 | 
| 1249 | 
            -
                 | 
| 1250 | 
            -
                @line_index = line_index
         | 
| 1251 | 
            -
                if byte_pointer
         | 
| 1252 | 
            -
                  @byte_pointer = byte_pointer
         | 
| 1253 | 
            -
                else
         | 
| 1254 | 
            -
                  calculate_nearest_cursor(cursor)
         | 
| 1255 | 
            -
                end
         | 
| 1256 | 
            -
                process_auto_indent
         | 
| 1257 | 
            -
              end
         | 
| 1258 | 
            -
             | 
| 1259 | 
            -
              def retrieve_completion_block(set_completion_quote_character = false)
         | 
| 1260 | 
            -
                if Reline.completer_word_break_characters.empty?
         | 
| 1261 | 
            -
                  word_break_regexp = nil
         | 
| 1262 | 
            -
                else
         | 
| 1263 | 
            -
                  word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
         | 
| 1264 | 
            -
                end
         | 
| 1265 | 
            -
                if Reline.completer_quote_characters.empty?
         | 
| 1266 | 
            -
                  quote_characters_regexp = nil
         | 
| 1267 | 
            -
                else
         | 
| 1268 | 
            -
                  quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
         | 
| 1269 | 
            -
                end
         | 
| 1270 | 
            -
                before = current_line.byteslice(0, @byte_pointer)
         | 
| 1271 | 
            -
                rest = nil
         | 
| 1272 | 
            -
                break_pointer = nil
         | 
| 1147 | 
            +
              def retrieve_completion_block
         | 
| 1148 | 
            +
                quote_characters = Reline.completer_quote_characters
         | 
| 1149 | 
            +
                before = current_line.byteslice(0, @byte_pointer).grapheme_clusters
         | 
| 1273 1150 | 
             
                quote = nil
         | 
| 1274 | 
            -
                 | 
| 1275 | 
            -
                 | 
| 1276 | 
            -
             | 
| 1277 | 
            -
             | 
| 1278 | 
            -
             | 
| 1279 | 
            -
             | 
| 1280 | 
            -
             | 
| 1281 | 
            -
                     | 
| 1282 | 
            -
             | 
| 1283 | 
            -
             | 
| 1284 | 
            -
             | 
| 1285 | 
            -
                     | 
| 1286 | 
            -
             | 
| 1287 | 
            -
                  elsif quote and slice.start_with?(escaped_quote)
         | 
| 1288 | 
            -
                    # skip
         | 
| 1289 | 
            -
                    i += 2
         | 
| 1290 | 
            -
                  elsif quote_characters_regexp and slice =~ quote_characters_regexp # find new "
         | 
| 1291 | 
            -
                    rest = $'
         | 
| 1292 | 
            -
                    quote = $&
         | 
| 1293 | 
            -
                    closing_quote = /(?!\\)#{Regexp.escape(quote)}/
         | 
| 1294 | 
            -
                    escaped_quote = /\\#{Regexp.escape(quote)}/
         | 
| 1295 | 
            -
                    i += 1
         | 
| 1296 | 
            -
                    break_pointer = i - 1
         | 
| 1297 | 
            -
                  elsif word_break_regexp and not quote and slice =~ word_break_regexp
         | 
| 1298 | 
            -
                    rest = $'
         | 
| 1299 | 
            -
                    i += 1
         | 
| 1300 | 
            -
                    before = current_line.byteslice(i, @byte_pointer - i)
         | 
| 1301 | 
            -
                    break_pointer = i
         | 
| 1302 | 
            -
                  else
         | 
| 1303 | 
            -
                    i += 1
         | 
| 1304 | 
            -
                  end
         | 
| 1305 | 
            -
                end
         | 
| 1306 | 
            -
                postposing = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
         | 
| 1307 | 
            -
                if rest
         | 
| 1308 | 
            -
                  preposing = current_line.byteslice(0, break_pointer)
         | 
| 1309 | 
            -
                  target = rest
         | 
| 1310 | 
            -
                  if set_completion_quote_character and quote
         | 
| 1311 | 
            -
                    Reline.core.instance_variable_set(:@completion_quote_character, quote)
         | 
| 1312 | 
            -
                    if postposing !~ /(?!\\)#{Regexp.escape(quote)}/ # closing quote
         | 
| 1313 | 
            -
                      insert_text(quote)
         | 
| 1151 | 
            +
                # Calculate closing quote when cursor is at the end of the line
         | 
| 1152 | 
            +
                if current_line.bytesize == @byte_pointer && !quote_characters.empty?
         | 
| 1153 | 
            +
                  escaped = false
         | 
| 1154 | 
            +
                  before.each do |c|
         | 
| 1155 | 
            +
                    if escaped
         | 
| 1156 | 
            +
                      escaped = false
         | 
| 1157 | 
            +
                      next
         | 
| 1158 | 
            +
                    elsif c == '\\'
         | 
| 1159 | 
            +
                      escaped = true
         | 
| 1160 | 
            +
                    elsif quote
         | 
| 1161 | 
            +
                      quote = nil if c == quote
         | 
| 1162 | 
            +
                    elsif quote_characters.include?(c)
         | 
| 1163 | 
            +
                      quote = c
         | 
| 1314 1164 | 
             
                    end
         | 
| 1315 1165 | 
             
                  end
         | 
| 1316 | 
            -
                else
         | 
| 1317 | 
            -
                  preposing = ''
         | 
| 1318 | 
            -
                  if break_pointer
         | 
| 1319 | 
            -
                    preposing = current_line.byteslice(0, break_pointer)
         | 
| 1320 | 
            -
                  else
         | 
| 1321 | 
            -
                    preposing = ''
         | 
| 1322 | 
            -
                  end
         | 
| 1323 | 
            -
                  target = before
         | 
| 1324 1166 | 
             
                end
         | 
| 1167 | 
            +
             | 
| 1168 | 
            +
                word_break_characters = quote_characters + Reline.completer_word_break_characters
         | 
| 1169 | 
            +
                break_index = before.rindex { |c| word_break_characters.include?(c) || quote_characters.include?(c) } || -1
         | 
| 1170 | 
            +
                preposing = before.take(break_index + 1).join
         | 
| 1171 | 
            +
                target = before.drop(break_index + 1).join
         | 
| 1172 | 
            +
                postposing = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
         | 
| 1325 1173 | 
             
                lines = whole_lines
         | 
| 1326 1174 | 
             
                if @line_index > 0
         | 
| 1327 1175 | 
             
                  preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
         | 
| @@ -1329,7 +1177,7 @@ class Reline::LineEditor | |
| 1329 1177 | 
             
                if (lines.size - 1) > @line_index
         | 
| 1330 1178 | 
             
                  postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
         | 
| 1331 1179 | 
             
                end
         | 
| 1332 | 
            -
                [preposing.encode( | 
| 1180 | 
            +
                [preposing.encode(encoding), target.encode(encoding), postposing.encode(encoding), quote&.encode(encoding)]
         | 
| 1333 1181 | 
             
              end
         | 
| 1334 1182 |  | 
| 1335 1183 | 
             
              def confirm_multiline_termination
         | 
| @@ -1337,16 +1185,14 @@ class Reline::LineEditor | |
| 1337 1185 | 
             
                @confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
         | 
| 1338 1186 | 
             
              end
         | 
| 1339 1187 |  | 
| 1340 | 
            -
              def  | 
| 1341 | 
            -
                save_old_buffer
         | 
| 1188 | 
            +
              def insert_multiline_text(text)
         | 
| 1342 1189 | 
             
                pre = @buffer_of_lines[@line_index].byteslice(0, @byte_pointer)
         | 
| 1343 1190 | 
             
                post = @buffer_of_lines[@line_index].byteslice(@byte_pointer..)
         | 
| 1344 | 
            -
                lines = (pre + text.gsub(/\r\n?/, "\n") + post).split("\n", -1)
         | 
| 1191 | 
            +
                lines = (pre + Reline::Unicode.safe_encode(text, encoding).gsub(/\r\n?/, "\n") + post).split("\n", -1)
         | 
| 1345 1192 | 
             
                lines << '' if lines.empty?
         | 
| 1346 1193 | 
             
                @buffer_of_lines[@line_index, 1] = lines
         | 
| 1347 1194 | 
             
                @line_index += lines.size - 1
         | 
| 1348 1195 | 
             
                @byte_pointer = @buffer_of_lines[@line_index].bytesize - post.bytesize
         | 
| 1349 | 
            -
                push_input_lines
         | 
| 1350 1196 | 
             
              end
         | 
| 1351 1197 |  | 
| 1352 1198 | 
             
              def insert_text(text)
         | 
| @@ -1386,7 +1232,7 @@ class Reline::LineEditor | |
| 1386 1232 | 
             
                  last += current_line.bytesize if last < 0
         | 
| 1387 1233 | 
             
                  first += current_line.bytesize if first < 0
         | 
| 1388 1234 | 
             
                  range = range.exclude_end? ? first...last : first..last
         | 
| 1389 | 
            -
                  line = current_line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding( | 
| 1235 | 
            +
                  line = current_line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(encoding)
         | 
| 1390 1236 | 
             
                  set_current_line(line)
         | 
| 1391 1237 | 
             
                else
         | 
| 1392 1238 | 
             
                  set_current_line(current_line.byteslice(0, start))
         | 
| @@ -1460,10 +1306,11 @@ class Reline::LineEditor | |
| 1460 1306 | 
             
                  @completion_occurs = move_completed_list(:down)
         | 
| 1461 1307 | 
             
                else
         | 
| 1462 1308 | 
             
                  @completion_journey_state = nil
         | 
| 1463 | 
            -
                   | 
| 1309 | 
            +
                  pre, target, post, quote = retrieve_completion_block
         | 
| 1310 | 
            +
                  result = call_completion_proc(pre, target, post, quote)
         | 
| 1464 1311 | 
             
                  if result.is_a?(Array)
         | 
| 1465 1312 | 
             
                    @completion_occurs = true
         | 
| 1466 | 
            -
                    perform_completion( | 
| 1313 | 
            +
                    perform_completion(pre, target, post, quote, result)
         | 
| 1467 1314 | 
             
                  end
         | 
| 1468 1315 | 
             
                end
         | 
| 1469 1316 | 
             
              end
         | 
| @@ -1511,21 +1358,11 @@ class Reline::LineEditor | |
| 1511 1358 | 
             
              #            digit or if the existing argument is already greater than a
         | 
| 1512 1359 | 
             
              #            million.
         | 
| 1513 1360 | 
             
              # GNU Readline:: +self-insert+ (a, b, A, 1, !, …) Insert yourself.
         | 
| 1514 | 
            -
              private def ed_insert( | 
| 1515 | 
            -
                 | 
| 1516 | 
            -
                   | 
| 1517 | 
            -
             | 
| 1518 | 
            -
                   | 
| 1519 | 
            -
                    return
         | 
| 1520 | 
            -
                  end
         | 
| 1521 | 
            -
                  str = key
         | 
| 1522 | 
            -
                else
         | 
| 1523 | 
            -
                  begin
         | 
| 1524 | 
            -
                    key.chr.encode(Encoding::UTF_8)
         | 
| 1525 | 
            -
                  rescue Encoding::UndefinedConversionError
         | 
| 1526 | 
            -
                    return
         | 
| 1527 | 
            -
                  end
         | 
| 1528 | 
            -
                  str = key.chr
         | 
| 1361 | 
            +
              private def ed_insert(str)
         | 
| 1362 | 
            +
                begin
         | 
| 1363 | 
            +
                  str.encode(Encoding::UTF_8)
         | 
| 1364 | 
            +
                rescue Encoding::UndefinedConversionError
         | 
| 1365 | 
            +
                  return
         | 
| 1529 1366 | 
             
                end
         | 
| 1530 1367 | 
             
                if @in_pasting
         | 
| 1531 1368 | 
             
                  @continuous_insertion_buffer << str
         | 
| @@ -1536,24 +1373,26 @@ class Reline::LineEditor | |
| 1536 1373 |  | 
| 1537 1374 | 
             
                insert_text(str)
         | 
| 1538 1375 | 
             
              end
         | 
| 1539 | 
            -
              alias_method :ed_digit, :ed_insert
         | 
| 1540 1376 | 
             
              alias_method :self_insert, :ed_insert
         | 
| 1541 1377 |  | 
| 1542 | 
            -
              private def  | 
| 1543 | 
            -
                @ | 
| 1544 | 
            -
                   | 
| 1545 | 
            -
             | 
| 1546 | 
            -
             | 
| 1547 | 
            -
             | 
| 1548 | 
            -
             | 
| 1549 | 
            -
             | 
| 1550 | 
            -
             | 
| 1551 | 
            -
             | 
| 1378 | 
            +
              private def ed_digit(key)
         | 
| 1379 | 
            +
                if @vi_arg
         | 
| 1380 | 
            +
                  ed_argument_digit(key)
         | 
| 1381 | 
            +
                else
         | 
| 1382 | 
            +
                  ed_insert(key)
         | 
| 1383 | 
            +
                end
         | 
| 1384 | 
            +
              end
         | 
| 1385 | 
            +
             | 
| 1386 | 
            +
              private def insert_raw_char(str, arg: 1)
         | 
| 1387 | 
            +
                arg.times do
         | 
| 1388 | 
            +
                  if str == "\C-j" or str == "\C-m"
         | 
| 1389 | 
            +
                    key_newline(str)
         | 
| 1390 | 
            +
                  elsif str != "\0"
         | 
| 1391 | 
            +
                    # Ignore NUL.
         | 
| 1392 | 
            +
                    ed_insert(str)
         | 
| 1552 1393 | 
             
                  end
         | 
| 1553 | 
            -
             | 
| 1554 | 
            -
                }
         | 
| 1394 | 
            +
                end
         | 
| 1555 1395 | 
             
              end
         | 
| 1556 | 
            -
              alias_method :quoted_insert, :ed_quoted_insert
         | 
| 1557 1396 |  | 
| 1558 1397 | 
             
              private def ed_next_char(key, arg: 1)
         | 
| 1559 1398 | 
             
                byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
         | 
| @@ -1582,14 +1421,21 @@ class Reline::LineEditor | |
| 1582 1421 | 
             
              alias_method :backward_char, :ed_prev_char
         | 
| 1583 1422 |  | 
| 1584 1423 | 
             
              private def vi_first_print(key)
         | 
| 1585 | 
            -
                @byte_pointer | 
| 1424 | 
            +
                @byte_pointer = Reline::Unicode.vi_first_print(current_line)
         | 
| 1586 1425 | 
             
              end
         | 
| 1587 1426 |  | 
| 1588 1427 | 
             
              private def ed_move_to_beg(key)
         | 
| 1589 1428 | 
             
                @byte_pointer = 0
         | 
| 1590 1429 | 
             
              end
         | 
| 1591 1430 | 
             
              alias_method :beginning_of_line, :ed_move_to_beg
         | 
| 1592 | 
            -
             | 
| 1431 | 
            +
             | 
| 1432 | 
            +
              private def vi_zero(key)
         | 
| 1433 | 
            +
                if @vi_arg
         | 
| 1434 | 
            +
                  ed_argument_digit(key)
         | 
| 1435 | 
            +
                else
         | 
| 1436 | 
            +
                  ed_move_to_beg(key)
         | 
| 1437 | 
            +
                end
         | 
| 1438 | 
            +
              end
         | 
| 1593 1439 |  | 
| 1594 1440 | 
             
              private def ed_move_to_end(key)
         | 
| 1595 1441 | 
             
                @byte_pointer = current_line.bytesize
         | 
| @@ -1597,27 +1443,22 @@ class Reline::LineEditor | |
| 1597 1443 | 
             
              alias_method :end_of_line, :ed_move_to_end
         | 
| 1598 1444 |  | 
| 1599 1445 | 
             
              private def generate_searcher(search_key)
         | 
| 1600 | 
            -
                search_word = String.new(encoding:  | 
| 1601 | 
            -
                multibyte_buf = String.new(encoding: 'ASCII-8BIT')
         | 
| 1446 | 
            +
                search_word = String.new(encoding: encoding)
         | 
| 1602 1447 | 
             
                hit_pointer = nil
         | 
| 1603 1448 | 
             
                lambda do |key|
         | 
| 1604 1449 | 
             
                  search_again = false
         | 
| 1605 1450 | 
             
                  case key
         | 
| 1606 | 
            -
                  when "\C-h" | 
| 1451 | 
            +
                  when "\C-h", "\C-?"
         | 
| 1607 1452 | 
             
                    grapheme_clusters = search_word.grapheme_clusters
         | 
| 1608 1453 | 
             
                    if grapheme_clusters.size > 0
         | 
| 1609 1454 | 
             
                      grapheme_clusters.pop
         | 
| 1610 1455 | 
             
                      search_word = grapheme_clusters.join
         | 
| 1611 1456 | 
             
                    end
         | 
| 1612 | 
            -
                  when "\C-r" | 
| 1457 | 
            +
                  when "\C-r", "\C-s"
         | 
| 1613 1458 | 
             
                    search_again = true if search_key == key
         | 
| 1614 1459 | 
             
                    search_key = key
         | 
| 1615 1460 | 
             
                  else
         | 
| 1616 | 
            -
                     | 
| 1617 | 
            -
                    if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
         | 
| 1618 | 
            -
                      search_word << multibyte_buf.dup.force_encoding(@encoding)
         | 
| 1619 | 
            -
                      multibyte_buf.clear
         | 
| 1620 | 
            -
                    end
         | 
| 1461 | 
            +
                    search_word << key
         | 
| 1621 1462 | 
             
                  end
         | 
| 1622 1463 | 
             
                  hit = nil
         | 
| 1623 1464 | 
             
                  if not search_word.empty? and @line_backup_in_history&.include?(search_word)
         | 
| @@ -1630,10 +1471,10 @@ class Reline::LineEditor | |
| 1630 1471 | 
             
                      end
         | 
| 1631 1472 | 
             
                      if @history_pointer
         | 
| 1632 1473 | 
             
                        case search_key
         | 
| 1633 | 
            -
                        when "\C-r" | 
| 1474 | 
            +
                        when "\C-r"
         | 
| 1634 1475 | 
             
                          history_pointer_base = 0
         | 
| 1635 1476 | 
             
                          history = Reline::HISTORY[0..(@history_pointer - 1)]
         | 
| 1636 | 
            -
                        when "\C-s" | 
| 1477 | 
            +
                        when "\C-s"
         | 
| 1637 1478 | 
             
                          history_pointer_base = @history_pointer + 1
         | 
| 1638 1479 | 
             
                          history = Reline::HISTORY[(@history_pointer + 1)..-1]
         | 
| 1639 1480 | 
             
                        end
         | 
| @@ -1643,10 +1484,10 @@ class Reline::LineEditor | |
| 1643 1484 | 
             
                      end
         | 
| 1644 1485 | 
             
                    elsif @history_pointer
         | 
| 1645 1486 | 
             
                      case search_key
         | 
| 1646 | 
            -
                      when "\C-r" | 
| 1487 | 
            +
                      when "\C-r"
         | 
| 1647 1488 | 
             
                        history_pointer_base = 0
         | 
| 1648 1489 | 
             
                        history = Reline::HISTORY[0..@history_pointer]
         | 
| 1649 | 
            -
                      when "\C-s" | 
| 1490 | 
            +
                      when "\C-s"
         | 
| 1650 1491 | 
             
                        history_pointer_base = @history_pointer
         | 
| 1651 1492 | 
             
                        history = Reline::HISTORY[@history_pointer..-1]
         | 
| 1652 1493 | 
             
                      end
         | 
| @@ -1655,11 +1496,11 @@ class Reline::LineEditor | |
| 1655 1496 | 
             
                      history = Reline::HISTORY
         | 
| 1656 1497 | 
             
                    end
         | 
| 1657 1498 | 
             
                    case search_key
         | 
| 1658 | 
            -
                    when "\C-r" | 
| 1499 | 
            +
                    when "\C-r"
         | 
| 1659 1500 | 
             
                      hit_index = history.rindex { |item|
         | 
| 1660 1501 | 
             
                        item.include?(search_word)
         | 
| 1661 1502 | 
             
                      }
         | 
| 1662 | 
            -
                    when "\C-s" | 
| 1503 | 
            +
                    when "\C-s"
         | 
| 1663 1504 | 
             
                      hit_index = history.index { |item|
         | 
| 1664 1505 | 
             
                        item.include?(search_word)
         | 
| 1665 1506 | 
             
                      }
         | 
| @@ -1670,9 +1511,9 @@ class Reline::LineEditor | |
| 1670 1511 | 
             
                    end
         | 
| 1671 1512 | 
             
                  end
         | 
| 1672 1513 | 
             
                  case search_key
         | 
| 1673 | 
            -
                  when "\C-r" | 
| 1514 | 
            +
                  when "\C-r"
         | 
| 1674 1515 | 
             
                    prompt_name = 'reverse-i-search'
         | 
| 1675 | 
            -
                  when "\C-s" | 
| 1516 | 
            +
                  when "\C-s"
         | 
| 1676 1517 | 
             
                    prompt_name = 'i-search'
         | 
| 1677 1518 | 
             
                  end
         | 
| 1678 1519 | 
             
                  prompt_name = "failed #{prompt_name}" unless hit
         | 
| @@ -1681,57 +1522,28 @@ class Reline::LineEditor | |
| 1681 1522 | 
             
              end
         | 
| 1682 1523 |  | 
| 1683 1524 | 
             
              private def incremental_search_history(key)
         | 
| 1684 | 
            -
                 | 
| 1685 | 
            -
                  @line_backup_in_history = whole_buffer
         | 
| 1686 | 
            -
                end
         | 
| 1525 | 
            +
                backup = @buffer_of_lines.dup, @line_index, @byte_pointer, @history_pointer, @line_backup_in_history
         | 
| 1687 1526 | 
             
                searcher = generate_searcher(key)
         | 
| 1688 1527 | 
             
                @searching_prompt = "(reverse-i-search)`': "
         | 
| 1689 | 
            -
                termination_keys = ["\C-j" | 
| 1690 | 
            -
                termination_keys.concat(@config.isearch_terminators | 
| 1528 | 
            +
                termination_keys = ["\C-j"]
         | 
| 1529 | 
            +
                termination_keys.concat(@config.isearch_terminators.chars) if @config.isearch_terminators
         | 
| 1691 1530 | 
             
                @waiting_proc = ->(k) {
         | 
| 1692 | 
            -
                   | 
| 1693 | 
            -
             | 
| 1694 | 
            -
                     | 
| 1695 | 
            -
                      buffer = Reline::HISTORY[@history_pointer]
         | 
| 1696 | 
            -
                    else
         | 
| 1697 | 
            -
                      buffer = @line_backup_in_history
         | 
| 1698 | 
            -
                    end
         | 
| 1699 | 
            -
                    @buffer_of_lines = buffer.split("\n")
         | 
| 1700 | 
            -
                    @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
         | 
| 1701 | 
            -
                    @line_index = @buffer_of_lines.size - 1
         | 
| 1531 | 
            +
                  if k == "\C-g"
         | 
| 1532 | 
            +
                    # cancel search and restore buffer
         | 
| 1533 | 
            +
                    @buffer_of_lines, @line_index, @byte_pointer, @history_pointer, @line_backup_in_history = backup
         | 
| 1702 1534 | 
             
                    @searching_prompt = nil
         | 
| 1703 1535 | 
             
                    @waiting_proc = nil
         | 
| 1704 | 
            -
             | 
| 1705 | 
            -
             | 
| 1706 | 
            -
                     | 
| 1707 | 
            -
                    @ | 
| 1708 | 
            -
                    @ | 
| 1709 | 
            -
                    move_history( | 
| 1536 | 
            +
                  elsif !termination_keys.include?(k) && (k.match?(/[[:print:]]/) || k == "\C-h" || k == "\C-?" || k == "\C-r" || k == "\C-s")
         | 
| 1537 | 
            +
                    search_word, prompt_name, hit_pointer = searcher.call(k)
         | 
| 1538 | 
            +
                    Reline.last_incremental_search = search_word
         | 
| 1539 | 
            +
                    @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
         | 
| 1540 | 
            +
                    @searching_prompt += ': ' unless @is_multiline
         | 
| 1541 | 
            +
                    move_history(hit_pointer, line: :end, cursor: :end) if hit_pointer
         | 
| 1542 | 
            +
                  else
         | 
| 1543 | 
            +
                    # terminaton_keys and other keys will terminalte
         | 
| 1544 | 
            +
                    move_history(@history_pointer, line: :end, cursor: :start)
         | 
| 1710 1545 | 
             
                    @searching_prompt = nil
         | 
| 1711 1546 | 
             
                    @waiting_proc = nil
         | 
| 1712 | 
            -
                    @byte_pointer = 0
         | 
| 1713 | 
            -
                  else
         | 
| 1714 | 
            -
                    chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
         | 
| 1715 | 
            -
                    if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == "\C-?".ord or k == "\C-r".ord or k == "\C-s".ord
         | 
| 1716 | 
            -
                      search_word, prompt_name, hit_pointer = searcher.call(k)
         | 
| 1717 | 
            -
                      Reline.last_incremental_search = search_word
         | 
| 1718 | 
            -
                      @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
         | 
| 1719 | 
            -
                      @searching_prompt += ': ' unless @is_multiline
         | 
| 1720 | 
            -
                      move_history(hit_pointer, line: :end, cursor: :end, save_buffer: false) if hit_pointer
         | 
| 1721 | 
            -
                    else
         | 
| 1722 | 
            -
                      if @history_pointer
         | 
| 1723 | 
            -
                        line = Reline::HISTORY[@history_pointer]
         | 
| 1724 | 
            -
                      else
         | 
| 1725 | 
            -
                        line = @line_backup_in_history
         | 
| 1726 | 
            -
                      end
         | 
| 1727 | 
            -
                      @line_backup_in_history = whole_buffer
         | 
| 1728 | 
            -
                      @buffer_of_lines = line.split("\n")
         | 
| 1729 | 
            -
                      @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
         | 
| 1730 | 
            -
                      @line_index = @buffer_of_lines.size - 1
         | 
| 1731 | 
            -
                      @searching_prompt = nil
         | 
| 1732 | 
            -
                      @waiting_proc = nil
         | 
| 1733 | 
            -
                      @byte_pointer = 0
         | 
| 1734 | 
            -
                    end
         | 
| 1735 1547 | 
             
                  end
         | 
| 1736 1548 | 
             
                }
         | 
| 1737 1549 | 
             
              end
         | 
| @@ -1786,14 +1598,14 @@ class Reline::LineEditor | |
| 1786 1598 | 
             
              end
         | 
| 1787 1599 | 
             
              alias_method :history_search_forward, :ed_search_next_history
         | 
| 1788 1600 |  | 
| 1789 | 
            -
              private def move_history(history_pointer, line:, cursor | 
| 1601 | 
            +
              private def move_history(history_pointer, line:, cursor:)
         | 
| 1790 1602 | 
             
                history_pointer ||= Reline::HISTORY.size
         | 
| 1791 1603 | 
             
                return if history_pointer < 0 || history_pointer > Reline::HISTORY.size
         | 
| 1792 1604 | 
             
                old_history_pointer = @history_pointer || Reline::HISTORY.size
         | 
| 1793 1605 | 
             
                if old_history_pointer == Reline::HISTORY.size
         | 
| 1794 | 
            -
                  @line_backup_in_history =  | 
| 1606 | 
            +
                  @line_backup_in_history = whole_buffer
         | 
| 1795 1607 | 
             
                else
         | 
| 1796 | 
            -
                  Reline::HISTORY[old_history_pointer] = whole_buffer | 
| 1608 | 
            +
                  Reline::HISTORY[old_history_pointer] = whole_buffer
         | 
| 1797 1609 | 
             
                end
         | 
| 1798 1610 | 
             
                if history_pointer == Reline::HISTORY.size
         | 
| 1799 1611 | 
             
                  buf = @line_backup_in_history
         | 
| @@ -1803,7 +1615,7 @@ class Reline::LineEditor | |
| 1803 1615 | 
             
                  @history_pointer = history_pointer
         | 
| 1804 1616 | 
             
                end
         | 
| 1805 1617 | 
             
                @buffer_of_lines = buf.split("\n")
         | 
| 1806 | 
            -
                @buffer_of_lines = [String.new(encoding:  | 
| 1618 | 
            +
                @buffer_of_lines = [String.new(encoding: encoding)] if @buffer_of_lines.empty?
         | 
| 1807 1619 | 
             
                @line_index = line == :start ? 0 : line == :end ? @buffer_of_lines.size - 1 : line
         | 
| 1808 1620 | 
             
                @byte_pointer = cursor == :start ? 0 : cursor == :end ? current_line.bytesize : cursor
         | 
| 1809 1621 | 
             
              end
         | 
| @@ -1853,17 +1665,10 @@ class Reline::LineEditor | |
| 1853 1665 | 
             
                      finish
         | 
| 1854 1666 | 
             
                    end
         | 
| 1855 1667 | 
             
                  else
         | 
| 1856 | 
            -
                    if @line_index ==  | 
| 1857 | 
            -
                      if confirm_multiline_termination
         | 
| 1858 | 
            -
                        finish
         | 
| 1859 | 
            -
                      else
         | 
| 1860 | 
            -
                        key_newline(key)
         | 
| 1861 | 
            -
                      end
         | 
| 1862 | 
            -
                    else
         | 
| 1863 | 
            -
                      # should check confirm_multiline_termination to finish?
         | 
| 1864 | 
            -
                      @line_index = @buffer_of_lines.size - 1
         | 
| 1865 | 
            -
                      @byte_pointer = current_line.bytesize
         | 
| 1668 | 
            +
                    if @line_index == @buffer_of_lines.size - 1 && confirm_multiline_termination
         | 
| 1866 1669 | 
             
                      finish
         | 
| 1670 | 
            +
                    else
         | 
| 1671 | 
            +
                      key_newline(key)
         | 
| 1867 1672 | 
             
                    end
         | 
| 1868 1673 | 
             
                  end
         | 
| 1869 1674 | 
             
                else
         | 
| @@ -1871,6 +1676,11 @@ class Reline::LineEditor | |
| 1871 1676 | 
             
                end
         | 
| 1872 1677 | 
             
              end
         | 
| 1873 1678 |  | 
| 1679 | 
            +
              private def ed_force_submit(_key)
         | 
| 1680 | 
            +
                process_insert(force: true)
         | 
| 1681 | 
            +
                finish
         | 
| 1682 | 
            +
              end
         | 
| 1683 | 
            +
             | 
| 1874 1684 | 
             
              private def em_delete_prev_char(key, arg: 1)
         | 
| 1875 1685 | 
             
                arg.times do
         | 
| 1876 1686 | 
             
                  if @byte_pointer == 0 and @line_index > 0
         | 
| @@ -1937,7 +1747,7 @@ class Reline::LineEditor | |
| 1937 1747 | 
             
              alias_method :kill_whole_line, :em_kill_line
         | 
| 1938 1748 |  | 
| 1939 1749 | 
             
              private def em_delete(key)
         | 
| 1940 | 
            -
                if buffer_empty? and key == "\C-d" | 
| 1750 | 
            +
                if buffer_empty? and key == "\C-d"
         | 
| 1941 1751 | 
             
                  @eof = true
         | 
| 1942 1752 | 
             
                  finish
         | 
| 1943 1753 | 
             
                elsif @byte_pointer < current_line.bytesize
         | 
| @@ -1955,9 +1765,11 @@ class Reline::LineEditor | |
| 1955 1765 | 
             
                if current_line.empty? or @byte_pointer < current_line.bytesize
         | 
| 1956 1766 | 
             
                  em_delete(key)
         | 
| 1957 1767 | 
             
                elsif !@config.autocompletion # show completed list
         | 
| 1958 | 
            -
                   | 
| 1768 | 
            +
                  pre, target, post, quote = retrieve_completion_block
         | 
| 1769 | 
            +
                  result = call_completion_proc(pre, target, post, quote)
         | 
| 1959 1770 | 
             
                  if result.is_a?(Array)
         | 
| 1960 | 
            -
                     | 
| 1771 | 
            +
                    candidates = filter_normalize_candidates(target, result)
         | 
| 1772 | 
            +
                    menu(candidates)
         | 
| 1961 1773 | 
             
                  end
         | 
| 1962 1774 | 
             
                end
         | 
| 1963 1775 | 
             
              end
         | 
| @@ -1982,15 +1794,14 @@ class Reline::LineEditor | |
| 1982 1794 | 
             
              private def ed_clear_screen(key)
         | 
| 1983 1795 | 
             
                Reline::IOGate.clear_screen
         | 
| 1984 1796 | 
             
                @screen_size = Reline::IOGate.get_screen_size
         | 
| 1985 | 
            -
                @rendered_screen.lines = []
         | 
| 1986 1797 | 
             
                @rendered_screen.base_y = 0
         | 
| 1987 | 
            -
                 | 
| 1798 | 
            +
                clear_rendered_screen_cache
         | 
| 1988 1799 | 
             
              end
         | 
| 1989 1800 | 
             
              alias_method :clear_screen, :ed_clear_screen
         | 
| 1990 1801 |  | 
| 1991 1802 | 
             
              private def em_next_word(key)
         | 
| 1992 1803 | 
             
                if current_line.bytesize > @byte_pointer
         | 
| 1993 | 
            -
                  byte_size | 
| 1804 | 
            +
                  byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
         | 
| 1994 1805 | 
             
                  @byte_pointer += byte_size
         | 
| 1995 1806 | 
             
                end
         | 
| 1996 1807 | 
             
              end
         | 
| @@ -1998,7 +1809,7 @@ class Reline::LineEditor | |
| 1998 1809 |  | 
| 1999 1810 | 
             
              private def ed_prev_word(key)
         | 
| 2000 1811 | 
             
                if @byte_pointer > 0
         | 
| 2001 | 
            -
                  byte_size | 
| 1812 | 
            +
                  byte_size = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
         | 
| 2002 1813 | 
             
                  @byte_pointer -= byte_size
         | 
| 2003 1814 | 
             
                end
         | 
| 2004 1815 | 
             
              end
         | 
| @@ -2006,7 +1817,7 @@ class Reline::LineEditor | |
| 2006 1817 |  | 
| 2007 1818 | 
             
              private def em_delete_next_word(key)
         | 
| 2008 1819 | 
             
                if current_line.bytesize > @byte_pointer
         | 
| 2009 | 
            -
                  byte_size | 
| 1820 | 
            +
                  byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
         | 
| 2010 1821 | 
             
                  line, word = byteslice!(current_line, @byte_pointer, byte_size)
         | 
| 2011 1822 | 
             
                  set_current_line(line)
         | 
| 2012 1823 | 
             
                  @kill_ring.append(word)
         | 
| @@ -2016,7 +1827,7 @@ class Reline::LineEditor | |
| 2016 1827 |  | 
| 2017 1828 | 
             
              private def ed_delete_prev_word(key)
         | 
| 2018 1829 | 
             
                if @byte_pointer > 0
         | 
| 2019 | 
            -
                  byte_size | 
| 1830 | 
            +
                  byte_size = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
         | 
| 2020 1831 | 
             
                  line, word = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
         | 
| 2021 1832 | 
             
                  set_current_line(line, @byte_pointer - byte_size)
         | 
| 2022 1833 | 
             
                  @kill_ring.append(word, true)
         | 
| @@ -2056,7 +1867,7 @@ class Reline::LineEditor | |
| 2056 1867 |  | 
| 2057 1868 | 
             
              private def em_capitol_case(key)
         | 
| 2058 1869 | 
             
                if current_line.bytesize > @byte_pointer
         | 
| 2059 | 
            -
                  byte_size,  | 
| 1870 | 
            +
                  byte_size, new_str = Reline::Unicode.em_forward_word_with_capitalization(current_line, @byte_pointer)
         | 
| 2060 1871 | 
             
                  before = current_line.byteslice(0, @byte_pointer)
         | 
| 2061 1872 | 
             
                  after = current_line.byteslice((@byte_pointer + byte_size)..-1)
         | 
| 2062 1873 | 
             
                  set_current_line(before + new_str + after, @byte_pointer + new_str.bytesize)
         | 
| @@ -2066,7 +1877,7 @@ class Reline::LineEditor | |
| 2066 1877 |  | 
| 2067 1878 | 
             
              private def em_lower_case(key)
         | 
| 2068 1879 | 
             
                if current_line.bytesize > @byte_pointer
         | 
| 2069 | 
            -
                  byte_size | 
| 1880 | 
            +
                  byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
         | 
| 2070 1881 | 
             
                  part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
         | 
| 2071 1882 | 
             
                    mbchar =~ /[A-Z]/ ? mbchar.downcase : mbchar
         | 
| 2072 1883 | 
             
                  }.join
         | 
| @@ -2079,7 +1890,7 @@ class Reline::LineEditor | |
| 2079 1890 |  | 
| 2080 1891 | 
             
              private def em_upper_case(key)
         | 
| 2081 1892 | 
             
                if current_line.bytesize > @byte_pointer
         | 
| 2082 | 
            -
                  byte_size | 
| 1893 | 
            +
                  byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
         | 
| 2083 1894 | 
             
                  part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
         | 
| 2084 1895 | 
             
                    mbchar =~ /[a-z]/ ? mbchar.upcase : mbchar
         | 
| 2085 1896 | 
             
                  }.join
         | 
| @@ -2092,7 +1903,7 @@ class Reline::LineEditor | |
| 2092 1903 |  | 
| 2093 1904 | 
             
              private def em_kill_region(key)
         | 
| 2094 1905 | 
             
                if @byte_pointer > 0
         | 
| 2095 | 
            -
                  byte_size | 
| 1906 | 
            +
                  byte_size = Reline::Unicode.em_big_backward_word(current_line, @byte_pointer)
         | 
| 2096 1907 | 
             
                  line, deleted = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
         | 
| 2097 1908 | 
             
                  set_current_line(line, @byte_pointer - byte_size)
         | 
| 2098 1909 | 
             
                  @kill_ring.append(deleted, true)
         | 
| @@ -2123,7 +1934,7 @@ class Reline::LineEditor | |
| 2123 1934 |  | 
| 2124 1935 | 
             
              private def vi_next_word(key, arg: 1)
         | 
| 2125 1936 | 
             
                if current_line.bytesize > @byte_pointer
         | 
| 2126 | 
            -
                  byte_size | 
| 1937 | 
            +
                  byte_size = Reline::Unicode.vi_forward_word(current_line, @byte_pointer, @drop_terminate_spaces)
         | 
| 2127 1938 | 
             
                  @byte_pointer += byte_size
         | 
| 2128 1939 | 
             
                end
         | 
| 2129 1940 | 
             
                arg -= 1
         | 
| @@ -2132,7 +1943,7 @@ class Reline::LineEditor | |
| 2132 1943 |  | 
| 2133 1944 | 
             
              private def vi_prev_word(key, arg: 1)
         | 
| 2134 1945 | 
             
                if @byte_pointer > 0
         | 
| 2135 | 
            -
                  byte_size | 
| 1946 | 
            +
                  byte_size = Reline::Unicode.vi_backward_word(current_line, @byte_pointer)
         | 
| 2136 1947 | 
             
                  @byte_pointer -= byte_size
         | 
| 2137 1948 | 
             
                end
         | 
| 2138 1949 | 
             
                arg -= 1
         | 
| @@ -2141,7 +1952,7 @@ class Reline::LineEditor | |
| 2141 1952 |  | 
| 2142 1953 | 
             
              private def vi_end_word(key, arg: 1, inclusive: false)
         | 
| 2143 1954 | 
             
                if current_line.bytesize > @byte_pointer
         | 
| 2144 | 
            -
                  byte_size | 
| 1955 | 
            +
                  byte_size = Reline::Unicode.vi_forward_end_word(current_line, @byte_pointer)
         | 
| 2145 1956 | 
             
                  @byte_pointer += byte_size
         | 
| 2146 1957 | 
             
                end
         | 
| 2147 1958 | 
             
                arg -= 1
         | 
| @@ -2156,7 +1967,7 @@ class Reline::LineEditor | |
| 2156 1967 |  | 
| 2157 1968 | 
             
              private def vi_next_big_word(key, arg: 1)
         | 
| 2158 1969 | 
             
                if current_line.bytesize > @byte_pointer
         | 
| 2159 | 
            -
                  byte_size | 
| 1970 | 
            +
                  byte_size = Reline::Unicode.vi_big_forward_word(current_line, @byte_pointer)
         | 
| 2160 1971 | 
             
                  @byte_pointer += byte_size
         | 
| 2161 1972 | 
             
                end
         | 
| 2162 1973 | 
             
                arg -= 1
         | 
| @@ -2165,7 +1976,7 @@ class Reline::LineEditor | |
| 2165 1976 |  | 
| 2166 1977 | 
             
              private def vi_prev_big_word(key, arg: 1)
         | 
| 2167 1978 | 
             
                if @byte_pointer > 0
         | 
| 2168 | 
            -
                  byte_size | 
| 1979 | 
            +
                  byte_size = Reline::Unicode.vi_big_backward_word(current_line, @byte_pointer)
         | 
| 2169 1980 | 
             
                  @byte_pointer -= byte_size
         | 
| 2170 1981 | 
             
                end
         | 
| 2171 1982 | 
             
                arg -= 1
         | 
| @@ -2174,7 +1985,7 @@ class Reline::LineEditor | |
| 2174 1985 |  | 
| 2175 1986 | 
             
              private def vi_end_big_word(key, arg: 1, inclusive: false)
         | 
| 2176 1987 | 
             
                if current_line.bytesize > @byte_pointer
         | 
| 2177 | 
            -
                  byte_size | 
| 1988 | 
            +
                  byte_size = Reline::Unicode.vi_big_forward_end_word(current_line, @byte_pointer)
         | 
| 2178 1989 | 
             
                  @byte_pointer += byte_size
         | 
| 2179 1990 | 
             
                end
         | 
| 2180 1991 | 
             
                arg -= 1
         | 
| @@ -2259,9 +2070,11 @@ class Reline::LineEditor | |
| 2259 2070 | 
             
                  line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff)
         | 
| 2260 2071 | 
             
                elsif byte_pointer_diff < 0
         | 
| 2261 2072 | 
             
                  line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
         | 
| 2073 | 
            +
                else
         | 
| 2074 | 
            +
                  return
         | 
| 2262 2075 | 
             
                end
         | 
| 2263 2076 | 
             
                copy_for_vi(cut)
         | 
| 2264 | 
            -
                set_current_line(line | 
| 2077 | 
            +
                set_current_line(line, @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0))
         | 
| 2265 2078 | 
             
              end
         | 
| 2266 2079 |  | 
| 2267 2080 | 
             
              private def vi_yank(key, arg: nil)
         | 
| @@ -2280,6 +2093,8 @@ class Reline::LineEditor | |
| 2280 2093 | 
             
                  cut = current_line.byteslice(@byte_pointer, byte_pointer_diff)
         | 
| 2281 2094 | 
             
                elsif byte_pointer_diff < 0
         | 
| 2282 2095 | 
             
                  cut = current_line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
         | 
| 2096 | 
            +
                else
         | 
| 2097 | 
            +
                  return
         | 
| 2283 2098 | 
             
                end
         | 
| 2284 2099 | 
             
                copy_for_vi(cut)
         | 
| 2285 2100 | 
             
              end
         | 
| @@ -2325,7 +2140,7 @@ class Reline::LineEditor | |
| 2325 2140 | 
             
                }
         | 
| 2326 2141 | 
             
                system("#{ENV['EDITOR']} #{path}")
         | 
| 2327 2142 | 
             
                @buffer_of_lines = File.read(path).split("\n")
         | 
| 2328 | 
            -
                @buffer_of_lines = [String.new(encoding:  | 
| 2143 | 
            +
                @buffer_of_lines = [String.new(encoding: encoding)] if @buffer_of_lines.empty?
         | 
| 2329 2144 | 
             
                @line_index = 0
         | 
| 2330 2145 | 
             
                finish
         | 
| 2331 2146 | 
             
              end
         | 
| @@ -2350,20 +2165,9 @@ class Reline::LineEditor | |
| 2350 2165 | 
             
              end
         | 
| 2351 2166 |  | 
| 2352 2167 | 
             
              private def ed_argument_digit(key)
         | 
| 2353 | 
            -
                 | 
| 2354 | 
            -
             | 
| 2355 | 
            -
             | 
| 2356 | 
            -
                      unescaped_key = key ^ 0b10000000
         | 
| 2357 | 
            -
                      unless unescaped_key.chr.to_i.zero?
         | 
| 2358 | 
            -
                        @vi_arg = unescaped_key.chr.to_i
         | 
| 2359 | 
            -
                      end
         | 
| 2360 | 
            -
                    end
         | 
| 2361 | 
            -
                  else
         | 
| 2362 | 
            -
                    @vi_arg = key.chr.to_i
         | 
| 2363 | 
            -
                  end
         | 
| 2364 | 
            -
                else
         | 
| 2365 | 
            -
                  @vi_arg = @vi_arg * 10 + key.chr.to_i
         | 
| 2366 | 
            -
                end
         | 
| 2168 | 
            +
                # key is expected to be `ESC digit` or `digit`
         | 
| 2169 | 
            +
                num = key[/\d/].to_i
         | 
| 2170 | 
            +
                @vi_arg = (@vi_arg || 0) * 10 + num
         | 
| 2367 2171 | 
             
              end
         | 
| 2368 2172 |  | 
| 2369 2173 | 
             
              private def vi_to_column(key, arg: 0)
         | 
| @@ -2382,7 +2186,7 @@ class Reline::LineEditor | |
| 2382 2186 | 
             
                    before = current_line.byteslice(0, @byte_pointer)
         | 
| 2383 2187 | 
             
                    remaining_point = @byte_pointer + byte_size
         | 
| 2384 2188 | 
             
                    after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
         | 
| 2385 | 
            -
                    set_current_line(before + k | 
| 2189 | 
            +
                    set_current_line(before + k + after)
         | 
| 2386 2190 | 
             
                    @waiting_proc = nil
         | 
| 2387 2191 | 
             
                  elsif arg > 1
         | 
| 2388 2192 | 
             
                    byte_size = 0
         | 
| @@ -2392,7 +2196,7 @@ class Reline::LineEditor | |
| 2392 2196 | 
             
                    before = current_line.byteslice(0, @byte_pointer)
         | 
| 2393 2197 | 
             
                    remaining_point = @byte_pointer + byte_size
         | 
| 2394 2198 | 
             
                    after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
         | 
| 2395 | 
            -
                    replaced = k | 
| 2199 | 
            +
                    replaced = k * arg
         | 
| 2396 2200 | 
             
                    set_current_line(before + replaced + after, @byte_pointer + replaced.bytesize)
         | 
| 2397 2201 | 
             
                    @waiting_proc = nil
         | 
| 2398 2202 | 
             
                  end
         | 
| @@ -2408,11 +2212,6 @@ class Reline::LineEditor | |
| 2408 2212 | 
             
              end
         | 
| 2409 2213 |  | 
| 2410 2214 | 
             
              private def search_next_char(key, arg, need_prev_char: false, inclusive: false)
         | 
| 2411 | 
            -
                if key.instance_of?(String)
         | 
| 2412 | 
            -
                  inputed_char = key
         | 
| 2413 | 
            -
                else
         | 
| 2414 | 
            -
                  inputed_char = key.chr
         | 
| 2415 | 
            -
                end
         | 
| 2416 2215 | 
             
                prev_total = nil
         | 
| 2417 2216 | 
             
                total = nil
         | 
| 2418 2217 | 
             
                found = false
         | 
| @@ -2423,7 +2222,7 @@ class Reline::LineEditor | |
| 2423 2222 | 
             
                    width = Reline::Unicode.get_mbchar_width(mbchar)
         | 
| 2424 2223 | 
             
                    total = [mbchar.bytesize, width]
         | 
| 2425 2224 | 
             
                  else
         | 
| 2426 | 
            -
                    if  | 
| 2225 | 
            +
                    if key == mbchar
         | 
| 2427 2226 | 
             
                      arg -= 1
         | 
| 2428 2227 | 
             
                      if arg.zero?
         | 
| 2429 2228 | 
             
                        found = true
         | 
| @@ -2460,11 +2259,6 @@ class Reline::LineEditor | |
| 2460 2259 | 
             
              end
         | 
| 2461 2260 |  | 
| 2462 2261 | 
             
              private def search_prev_char(key, arg, need_next_char = false)
         | 
| 2463 | 
            -
                if key.instance_of?(String)
         | 
| 2464 | 
            -
                  inputed_char = key
         | 
| 2465 | 
            -
                else
         | 
| 2466 | 
            -
                  inputed_char = key.chr
         | 
| 2467 | 
            -
                end
         | 
| 2468 2262 | 
             
                prev_total = nil
         | 
| 2469 2263 | 
             
                total = nil
         | 
| 2470 2264 | 
             
                found = false
         | 
| @@ -2475,7 +2269,7 @@ class Reline::LineEditor | |
| 2475 2269 | 
             
                    width = Reline::Unicode.get_mbchar_width(mbchar)
         | 
| 2476 2270 | 
             
                    total = [mbchar.bytesize, width]
         | 
| 2477 2271 | 
             
                  else
         | 
| 2478 | 
            -
                    if  | 
| 2272 | 
            +
                    if key == mbchar
         | 
| 2479 2273 | 
             
                      arg -= 1
         | 
| 2480 2274 | 
             
                      if arg.zero?
         | 
| 2481 2275 | 
             
                        found = true
         | 
| @@ -2527,24 +2321,23 @@ class Reline::LineEditor | |
| 2527 2321 | 
             
                @config.editing_mode = :vi_insert
         | 
| 2528 2322 | 
             
              end
         | 
| 2529 2323 |  | 
| 2530 | 
            -
              private def  | 
| 2531 | 
            -
                @ | 
| 2324 | 
            +
              private def move_undo_redo(direction)
         | 
| 2325 | 
            +
                @restoring = true
         | 
| 2326 | 
            +
                return unless (0..@undo_redo_history.size - 1).cover?(@undo_redo_index + direction)
         | 
| 2532 2327 |  | 
| 2533 | 
            -
                 | 
| 2328 | 
            +
                @undo_redo_index += direction
         | 
| 2329 | 
            +
                buffer_of_lines, byte_pointer, line_index = @undo_redo_history[@undo_redo_index]
         | 
| 2330 | 
            +
                @buffer_of_lines = buffer_of_lines.dup
         | 
| 2331 | 
            +
                @line_index = line_index
         | 
| 2332 | 
            +
                @byte_pointer = byte_pointer
         | 
| 2333 | 
            +
              end
         | 
| 2534 2334 |  | 
| 2535 | 
            -
             | 
| 2536 | 
            -
                 | 
| 2537 | 
            -
                set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y)
         | 
| 2335 | 
            +
              private def undo(_key)
         | 
| 2336 | 
            +
                move_undo_redo(-1)
         | 
| 2538 2337 | 
             
              end
         | 
| 2539 2338 |  | 
| 2540 2339 | 
             
              private def redo(_key)
         | 
| 2541 | 
            -
                 | 
| 2542 | 
            -
             | 
| 2543 | 
            -
                return if @input_lines_position >= @input_lines.size - 1
         | 
| 2544 | 
            -
             | 
| 2545 | 
            -
                @input_lines_position += 1
         | 
| 2546 | 
            -
                target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position]
         | 
| 2547 | 
            -
                set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y)
         | 
| 2340 | 
            +
                move_undo_redo(+1)
         | 
| 2548 2341 | 
             
              end
         | 
| 2549 2342 |  | 
| 2550 2343 | 
             
              private def prev_action_state_value(type)
         | 
| @@ -2554,4 +2347,8 @@ class Reline::LineEditor | |
| 2554 2347 | 
             
              private def set_next_action_state(type, value)
         | 
| 2555 2348 | 
             
                @next_action_state = [type, value]
         | 
| 2556 2349 | 
             
              end
         | 
| 2350 | 
            +
             | 
| 2351 | 
            +
              private def re_read_init_file(_key)
         | 
| 2352 | 
            +
                @config.reload
         | 
| 2353 | 
            +
              end
         | 
| 2557 2354 | 
             
            end
         |