mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00

* [ruby/irb] Stub a screen size for tests6663057083
* [ruby/irb] Support GitHub Actions8e9e6c4037
* [ruby/irb] Stub a screen size for test_context http://ci.rvm.jp/logfiles/brlog.trunk-random1.20210119-074232ea87592d4a
* [ruby/irb] Use a real screen size for pp by default9b9300dec2
* [ruby/irb] Rescue Errno::EINVAL on IRB pp20210119
T070008Z.log.html.gz is caused by: /export/home/chkbuild/chkbuild-gcc/tmp/build/20210119T150010Z/ruby/lib/reline/ansi.rb:157:in `winsize': Invalid argument - <STDIN> (Errno::EINVAL) from /export/home/chkbuild/chkbuild-gcc/tmp/build/20210119T150010Z/ruby/lib/reline/ansi.rb:157:in `get_screen_size' from /export/home/chkbuild/chkbuild-gcc/tmp/build/20210119T150010Z/ruby/lib/reline.rb:168:in `get_screen_size' from /export/home/chkbuild/chkbuild-gcc/tmp/build/20210119T150010Z/ruby/lib/forwardable.rb:238:in `get_screen_size' from /export/home/chkbuild/chkbuild-gcc/tmp/build/20210119T150010Z/ruby/lib/irb/color_printer.rb:7:in `pp' from -e:1:in `<main>'1719514598
* [ruby/irb] Split test files for IRB::Color and IRB::ColorPrinterd95e8daab3
* [ruby/irb] Undefine unused constantseea9c16804
* [ruby/irb] Remove pp-specific stub from TestColor because it was for TestColorPrinter7569206fd4
* [ruby/irb] Delete a doodle-level memo comment...fc3e1d9e0c
* [ruby/irb] Indent correctly with keyword "for" and "in"47c83ea724
* [ruby/irb] Indent correctly with method calling with receivere7c68e74a0
* [ruby/irb] add `IRB::FileInputMethod.open` to ensure closing associated File * tweak some methods not to raise exception after `#close` * use it in `IRB::IrbLoader#{source_file,load_file}ec2947acbd
* [ruby/irb] use `RubyLex::TerminateLineInput` appropriately [Bug #17564] * using the appropriciate exception instead of `break` so that the session can be continue after the `irb_source` and `irb_load` commands * suppress extra new line due to one more `#prompt` callbdefaa7cfd
* [ruby/irb] specify the `VERBOSE` to `false` and fix tests to fit502c590925
* In test, need to pass a context to IRB::WorkSpace.new explicitly * Fix absolute path predicate on Windows A path starts with '/' is not an absolute path on Windows, because of drive letter or UNC. * [ruby/irb] follow up the actual line number7aed8fe3b1
* [ruby/irb] Add info.rb to gemspecadbba19adf
* [ruby/irb] Allow "measure" command to take block20f1ca23e9
* [ruby/irb] Enable to reassign a new block with "measure" commandb444573aa2
* [ruby/reline] Cache pasting state in processing a key Because it's too slow. The rendering time in IRB has been reduced as follows: start = Time.now def each_top_level_statement initialize_input catch(:TERM_INPUT) do loop do begin prompt unless l = lex throw :TERM_INPUT if @line == '' else @line_no += l.count("\n") next if l == "\n" @line.concat l if @code_block_open or @ltype or @continue or @indent > 0 next end end if @line != "\n" @line.force_encoding(@io.encoding) yield @line, @exp_line_no end break if @io.eof? @line = '' @exp_line_no = @line_no @indent = 0 rescue TerminateLineInput initialize_input prompt end end end end puts "Duration: #{Time.now - start} seconds" 0.22sec -> 0.14secb8b3dd52c0
* [ruby/reline] Initialize uninitialized variables in tests25af4bb64b
* [ruby/reline] Remove an unused variable123ea51166
* [ruby/reline] Scroll down when ^C is pressed6877a7e3f5
* [ruby/reline] Show all lines higher than the screen when finished On Unix-like OSes, logs prior to the screen are not editable. When the code is higher than the screen, the code is only shown on the screen until input is finished, but when it is finished, all lines are outputted.8cd9132a39
* [ruby/reline] Handle past logs correctly when the code is higher than the screenf197139b4a
* [ruby/reline] Update cursor info by inserting newline even if not in pasting92d314f514
* [ruby/reline] Move cursor just after the last line when finishedba06e4c480
* [ruby/reline] The vi_histedit supports multiline This closes ruby/reline#253.f131f86d71
* [ruby/reline] Autowrap correctly when inserting chars in the middle of a lineebaf37255f
* [ruby/reline] Terminate correctly in the middle of lines higher than the screene1d9240ada
* [ruby/irb] Version 1.3.34c87035b7c
* [ruby/reline] Version 0.2.3b26c7d60c8
Co-authored-by: Takashi Kokubun <takashikkbn@gmail.com> Co-authored-by: Nobuhiro IMAI <nov@yo.rim.or.jp> Co-authored-by: Nobuyoshi Nakada <nobu@ruby-lang.org> Co-authored-by: ima1zumi <mariimaizumi5@gmail.com>
2710 lines
88 KiB
Ruby
2710 lines
88 KiB
Ruby
require 'reline/kill_ring'
|
|
require 'reline/unicode'
|
|
|
|
require 'tempfile'
|
|
|
|
class Reline::LineEditor
|
|
# TODO: undo
|
|
attr_reader :line
|
|
attr_reader :byte_pointer
|
|
attr_accessor :confirm_multiline_termination_proc
|
|
attr_accessor :completion_proc
|
|
attr_accessor :completion_append_character
|
|
attr_accessor :output_modifier_proc
|
|
attr_accessor :prompt_proc
|
|
attr_accessor :auto_indent_proc
|
|
attr_accessor :pre_input_hook
|
|
attr_accessor :dig_perfect_match_proc
|
|
attr_writer :output
|
|
|
|
VI_MOTIONS = %i{
|
|
ed_prev_char
|
|
ed_next_char
|
|
vi_zero
|
|
ed_move_to_beg
|
|
ed_move_to_end
|
|
vi_to_column
|
|
vi_next_char
|
|
vi_prev_char
|
|
vi_next_word
|
|
vi_prev_word
|
|
vi_to_next_char
|
|
vi_to_prev_char
|
|
vi_end_word
|
|
vi_next_big_word
|
|
vi_prev_big_word
|
|
vi_end_big_word
|
|
vi_repeat_next_char
|
|
vi_repeat_prev_char
|
|
}
|
|
|
|
module CompletionState
|
|
NORMAL = :normal
|
|
COMPLETION = :completion
|
|
MENU = :menu
|
|
JOURNEY = :journey
|
|
MENU_WITH_PERFECT_MATCH = :menu_with_perfect_match
|
|
PERFECT_MATCH = :perfect_match
|
|
end
|
|
|
|
CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer)
|
|
MenuInfo = Struct.new('MenuInfo', :target, :list)
|
|
|
|
PROMPT_LIST_CACHE_TIMEOUT = 0.5
|
|
|
|
def initialize(config, encoding)
|
|
@config = config
|
|
@completion_append_character = ''
|
|
reset_variables(encoding: encoding)
|
|
end
|
|
|
|
def set_pasting_state(in_pasting)
|
|
@in_pasting = in_pasting
|
|
end
|
|
|
|
def simplified_rendering?
|
|
if finished?
|
|
false
|
|
elsif @just_cursor_moving and not @rerender_all
|
|
true
|
|
else
|
|
not @rerender_all and not finished? and @in_pasting
|
|
end
|
|
end
|
|
|
|
private def check_mode_string
|
|
mode_string = nil
|
|
if @config.show_mode_in_prompt
|
|
if @config.editing_mode_is?(:vi_command)
|
|
mode_string = @config.vi_cmd_mode_string
|
|
elsif @config.editing_mode_is?(:vi_insert)
|
|
mode_string = @config.vi_ins_mode_string
|
|
elsif @config.editing_mode_is?(:emacs)
|
|
mode_string = @config.emacs_mode_string
|
|
else
|
|
mode_string = '?'
|
|
end
|
|
end
|
|
if mode_string != @prev_mode_string
|
|
@rerender_all = true
|
|
end
|
|
@prev_mode_string = mode_string
|
|
mode_string
|
|
end
|
|
|
|
private def check_multiline_prompt(buffer, prompt)
|
|
if @vi_arg
|
|
prompt = "(arg: #{@vi_arg}) "
|
|
@rerender_all = true
|
|
elsif @searching_prompt
|
|
prompt = @searching_prompt
|
|
@rerender_all = true
|
|
else
|
|
prompt = @prompt
|
|
end
|
|
if simplified_rendering?
|
|
mode_string = check_mode_string
|
|
prompt = mode_string + prompt if mode_string
|
|
return [prompt, calculate_width(prompt, true), [prompt] * buffer.size]
|
|
end
|
|
if @prompt_proc
|
|
use_cached_prompt_list = false
|
|
if @cached_prompt_list
|
|
if @just_cursor_moving
|
|
use_cached_prompt_list = true
|
|
elsif Time.now.to_f < (@prompt_cache_time + PROMPT_LIST_CACHE_TIMEOUT) and buffer.size == @cached_prompt_list.size
|
|
use_cached_prompt_list = true
|
|
end
|
|
end
|
|
use_cached_prompt_list = false if @rerender_all
|
|
if use_cached_prompt_list
|
|
prompt_list = @cached_prompt_list
|
|
else
|
|
prompt_list = @cached_prompt_list = @prompt_proc.(buffer)
|
|
@prompt_cache_time = Time.now.to_f
|
|
end
|
|
prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
|
|
mode_string = check_mode_string
|
|
prompt_list = prompt_list.map{ |pr| mode_string + pr } if mode_string
|
|
prompt = prompt_list[@line_index]
|
|
prompt = prompt_list[0] if prompt.nil?
|
|
prompt = prompt_list.last if prompt.nil?
|
|
if buffer.size > prompt_list.size
|
|
(buffer.size - prompt_list.size).times do
|
|
prompt_list << prompt_list.last
|
|
end
|
|
end
|
|
prompt_width = calculate_width(prompt, true)
|
|
[prompt, prompt_width, prompt_list]
|
|
else
|
|
mode_string = check_mode_string
|
|
prompt = mode_string + prompt if mode_string
|
|
prompt_width = calculate_width(prompt, true)
|
|
[prompt, prompt_width, nil]
|
|
end
|
|
end
|
|
|
|
def reset(prompt = '', encoding:)
|
|
@rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
|
|
@screen_size = Reline::IOGate.get_screen_size
|
|
@screen_height = @screen_size.first
|
|
reset_variables(prompt, encoding: encoding)
|
|
@old_trap = Signal.trap('SIGINT') {
|
|
if @scroll_partial_screen
|
|
move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
|
|
else
|
|
move_cursor_down(@highest_in_all - @line_index - 1)
|
|
end
|
|
Reline::IOGate.move_cursor_column(0)
|
|
scroll_down(1)
|
|
@old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
|
|
raise Interrupt
|
|
}
|
|
Reline::IOGate.set_winch_handler do
|
|
@rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
|
|
old_screen_size = @screen_size
|
|
@screen_size = Reline::IOGate.get_screen_size
|
|
@screen_height = @screen_size.first
|
|
if old_screen_size.last < @screen_size.last # columns increase
|
|
@rerender_all = true
|
|
rerender
|
|
else
|
|
back = 0
|
|
new_buffer = whole_lines
|
|
prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
|
|
new_buffer.each_with_index do |line, index|
|
|
prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
|
|
width = prompt_width + calculate_width(line)
|
|
height = calculate_height_by_width(width)
|
|
back += height
|
|
end
|
|
@highest_in_all = back
|
|
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
|
|
@first_line_started_from =
|
|
if @line_index.zero?
|
|
0
|
|
else
|
|
calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
|
|
end
|
|
if @prompt_proc
|
|
prompt = prompt_list[@line_index]
|
|
prompt_width = calculate_width(prompt, true)
|
|
end
|
|
calculate_nearest_cursor
|
|
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
|
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
|
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
|
|
@rerender_all = true
|
|
end
|
|
end
|
|
end
|
|
|
|
def finalize
|
|
Signal.trap('SIGINT', @old_trap)
|
|
end
|
|
|
|
def eof?
|
|
@eof
|
|
end
|
|
|
|
def reset_variables(prompt = '', encoding:)
|
|
@prompt = prompt
|
|
@mark_pointer = nil
|
|
@encoding = encoding
|
|
@is_multiline = false
|
|
@finished = false
|
|
@cleared = false
|
|
@rerender_all = false
|
|
@history_pointer = nil
|
|
@kill_ring ||= Reline::KillRing.new
|
|
@vi_clipboard = ''
|
|
@vi_arg = nil
|
|
@waiting_proc = nil
|
|
@waiting_operator_proc = nil
|
|
@waiting_operator_vi_arg = nil
|
|
@completion_journey_data = nil
|
|
@completion_state = CompletionState::NORMAL
|
|
@perfect_matched = nil
|
|
@menu_info = nil
|
|
@first_prompt = true
|
|
@searching_prompt = nil
|
|
@first_char = true
|
|
@add_newline_to_end_of_buffer = false
|
|
@just_cursor_moving = nil
|
|
@cached_prompt_list = nil
|
|
@prompt_cache_time = nil
|
|
@eof = false
|
|
@continuous_insertion_buffer = String.new(encoding: @encoding)
|
|
@scroll_partial_screen = nil
|
|
@prev_mode_string = nil
|
|
@drop_terminate_spaces = false
|
|
@in_pasting = false
|
|
@auto_indent_proc = nil
|
|
reset_line
|
|
end
|
|
|
|
def reset_line
|
|
@cursor = 0
|
|
@cursor_max = 0
|
|
@byte_pointer = 0
|
|
@buffer_of_lines = [String.new(encoding: @encoding)]
|
|
@line_index = 0
|
|
@previous_line_index = nil
|
|
@line = @buffer_of_lines[0]
|
|
@first_line_started_from = 0
|
|
@move_up = 0
|
|
@started_from = 0
|
|
@highest_in_this = 1
|
|
@highest_in_all = 1
|
|
@line_backup_in_history = nil
|
|
@multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
|
|
@check_new_auto_indent = false
|
|
end
|
|
|
|
def multiline_on
|
|
@is_multiline = true
|
|
end
|
|
|
|
def multiline_off
|
|
@is_multiline = false
|
|
end
|
|
|
|
private def calculate_height_by_lines(lines, prompt)
|
|
result = 0
|
|
prompt_list = prompt.is_a?(Array) ? prompt : nil
|
|
lines.each_with_index { |line, i|
|
|
prompt = prompt_list[i] if prompt_list and prompt_list[i]
|
|
result += calculate_height_by_width(calculate_width(prompt, true) + calculate_width(line))
|
|
}
|
|
result
|
|
end
|
|
|
|
private def insert_new_line(cursor_line, next_line)
|
|
@line = cursor_line
|
|
@buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
|
|
@previous_line_index = @line_index
|
|
@line_index += 1
|
|
@just_cursor_moving = false
|
|
end
|
|
|
|
private def calculate_height_by_width(width)
|
|
width.div(@screen_size.last) + 1
|
|
end
|
|
|
|
private def split_by_width(str, max_width)
|
|
Reline::Unicode.split_by_width(str, max_width, @encoding)
|
|
end
|
|
|
|
private def scroll_down(val)
|
|
if val <= @rest_height
|
|
Reline::IOGate.move_cursor_down(val)
|
|
@rest_height -= val
|
|
else
|
|
Reline::IOGate.move_cursor_down(@rest_height)
|
|
Reline::IOGate.scroll_down(val - @rest_height)
|
|
@rest_height = 0
|
|
end
|
|
end
|
|
|
|
private def move_cursor_up(val)
|
|
if val > 0
|
|
Reline::IOGate.move_cursor_up(val)
|
|
@rest_height += val
|
|
elsif val < 0
|
|
move_cursor_down(-val)
|
|
end
|
|
end
|
|
|
|
private def move_cursor_down(val)
|
|
if val > 0
|
|
Reline::IOGate.move_cursor_down(val)
|
|
@rest_height -= val
|
|
@rest_height = 0 if @rest_height < 0
|
|
elsif val < 0
|
|
move_cursor_up(-val)
|
|
end
|
|
end
|
|
|
|
private def calculate_nearest_cursor(line_to_calc = @line, cursor = @cursor, started_from = @started_from, byte_pointer = @byte_pointer, update = true)
|
|
new_cursor_max = calculate_width(line_to_calc)
|
|
new_cursor = 0
|
|
new_byte_pointer = 0
|
|
height = 1
|
|
max_width = @screen_size.last
|
|
if @config.editing_mode_is?(:vi_command)
|
|
last_byte_size = Reline::Unicode.get_prev_mbchar_size(line_to_calc, line_to_calc.bytesize)
|
|
if last_byte_size > 0
|
|
last_mbchar = line_to_calc.byteslice(line_to_calc.bytesize - last_byte_size, last_byte_size)
|
|
last_width = Reline::Unicode.get_mbchar_width(last_mbchar)
|
|
end_of_line_cursor = new_cursor_max - last_width
|
|
else
|
|
end_of_line_cursor = new_cursor_max
|
|
end
|
|
else
|
|
end_of_line_cursor = new_cursor_max
|
|
end
|
|
line_to_calc.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
|
|
mbchar_width = Reline::Unicode.get_mbchar_width(gc)
|
|
now = new_cursor + mbchar_width
|
|
if now > end_of_line_cursor or now > cursor
|
|
break
|
|
end
|
|
new_cursor += mbchar_width
|
|
if new_cursor > max_width * height
|
|
height += 1
|
|
end
|
|
new_byte_pointer += gc.bytesize
|
|
end
|
|
new_started_from = height - 1
|
|
if update
|
|
@cursor = new_cursor
|
|
@cursor_max = new_cursor_max
|
|
@started_from = new_started_from
|
|
@byte_pointer = new_byte_pointer
|
|
else
|
|
[new_cursor, new_cursor_max, new_started_from, new_byte_pointer]
|
|
end
|
|
end
|
|
|
|
def rerender_all
|
|
@rerender_all = true
|
|
process_insert(force: true)
|
|
rerender
|
|
end
|
|
|
|
def rerender
|
|
return if @line.nil?
|
|
if @menu_info
|
|
scroll_down(@highest_in_all - @first_line_started_from)
|
|
@rerender_all = true
|
|
end
|
|
if @menu_info
|
|
show_menu
|
|
@menu_info = nil
|
|
end
|
|
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
|
|
if @cleared
|
|
clear_screen_buffer(prompt, prompt_list, prompt_width)
|
|
@cleared = false
|
|
return
|
|
end
|
|
if @is_multiline and finished? and @scroll_partial_screen
|
|
# Re-output all code higher than the screen when finished.
|
|
Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
|
|
Reline::IOGate.move_cursor_column(0)
|
|
@scroll_partial_screen = nil
|
|
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
|
|
if @previous_line_index
|
|
new_lines = whole_lines(index: @previous_line_index, line: @line)
|
|
else
|
|
new_lines = whole_lines
|
|
end
|
|
modify_lines(new_lines).each_with_index do |line, index|
|
|
@output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\n"
|
|
Reline::IOGate.erase_after_cursor
|
|
end
|
|
@output.flush
|
|
return
|
|
end
|
|
new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
|
|
# FIXME: end of logical line sometimes breaks
|
|
rendered = false
|
|
if @add_newline_to_end_of_buffer
|
|
rerender_added_newline(prompt, prompt_width)
|
|
@add_newline_to_end_of_buffer = false
|
|
else
|
|
if @just_cursor_moving and not @rerender_all
|
|
rendered = just_move_cursor
|
|
@just_cursor_moving = false
|
|
return
|
|
elsif @previous_line_index or new_highest_in_this != @highest_in_this
|
|
rerender_changed_current_line
|
|
@previous_line_index = nil
|
|
rendered = true
|
|
elsif @rerender_all
|
|
rerender_all_lines
|
|
@rerender_all = false
|
|
rendered = true
|
|
else
|
|
end
|
|
end
|
|
if @is_multiline
|
|
if finished?
|
|
# Always rerender on finish because output_modifier_proc may return a different output.
|
|
if @previous_line_index
|
|
new_lines = whole_lines(index: @previous_line_index, line: @line)
|
|
else
|
|
new_lines = whole_lines
|
|
end
|
|
line = modify_lines(new_lines)[@line_index]
|
|
prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
|
|
render_partial(prompt, prompt_width, line, @first_line_started_from)
|
|
move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
|
|
scroll_down(1)
|
|
Reline::IOGate.move_cursor_column(0)
|
|
Reline::IOGate.erase_after_cursor
|
|
elsif not rendered
|
|
unless @in_pasting
|
|
line = modify_lines(whole_lines)[@line_index]
|
|
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
|
|
render_partial(prompt, prompt_width, line, @first_line_started_from)
|
|
end
|
|
end
|
|
@buffer_of_lines[@line_index] = @line
|
|
@rest_height = 0 if @scroll_partial_screen
|
|
else
|
|
line = modify_lines(whole_lines)[@line_index]
|
|
render_partial(prompt, prompt_width, line, 0)
|
|
if finished?
|
|
scroll_down(1)
|
|
Reline::IOGate.move_cursor_column(0)
|
|
Reline::IOGate.erase_after_cursor
|
|
end
|
|
end
|
|
end
|
|
|
|
private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
|
|
if @screen_height < highest_in_all
|
|
old_scroll_partial_screen = @scroll_partial_screen
|
|
if cursor_y == 0
|
|
@scroll_partial_screen = 0
|
|
elsif cursor_y == (highest_in_all - 1)
|
|
@scroll_partial_screen = highest_in_all - @screen_height
|
|
else
|
|
if @scroll_partial_screen
|
|
if cursor_y <= @scroll_partial_screen
|
|
@scroll_partial_screen = cursor_y
|
|
elsif (@scroll_partial_screen + @screen_height - 1) < cursor_y
|
|
@scroll_partial_screen = cursor_y - (@screen_height - 1)
|
|
end
|
|
else
|
|
if cursor_y > (@screen_height - 1)
|
|
@scroll_partial_screen = cursor_y - (@screen_height - 1)
|
|
else
|
|
@scroll_partial_screen = 0
|
|
end
|
|
end
|
|
end
|
|
if @scroll_partial_screen != old_scroll_partial_screen
|
|
@rerender_all = true
|
|
end
|
|
else
|
|
if @scroll_partial_screen
|
|
@rerender_all = true
|
|
end
|
|
@scroll_partial_screen = nil
|
|
end
|
|
end
|
|
|
|
private def rerender_added_newline(prompt, prompt_width)
|
|
scroll_down(1)
|
|
@buffer_of_lines[@previous_line_index] = @line
|
|
@line = @buffer_of_lines[@line_index]
|
|
unless @in_pasting
|
|
render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
|
|
end
|
|
@cursor = @cursor_max = calculate_width(@line)
|
|
@byte_pointer = @line.bytesize
|
|
@highest_in_all += @highest_in_this
|
|
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
|
|
@first_line_started_from += @started_from + 1
|
|
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
|
@previous_line_index = nil
|
|
end
|
|
|
|
def just_move_cursor
|
|
prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines, prompt)
|
|
move_cursor_up(@started_from)
|
|
new_first_line_started_from =
|
|
if @line_index.zero?
|
|
0
|
|
else
|
|
calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
|
|
end
|
|
first_line_diff = new_first_line_started_from - @first_line_started_from
|
|
new_cursor, new_cursor_max, new_started_from, new_byte_pointer = calculate_nearest_cursor(@buffer_of_lines[@line_index], @cursor, @started_from, @byte_pointer, false)
|
|
new_started_from = calculate_height_by_width(prompt_width + new_cursor) - 1
|
|
calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from)
|
|
@previous_line_index = nil
|
|
if @rerender_all
|
|
@line = @buffer_of_lines[@line_index]
|
|
rerender_all_lines
|
|
@rerender_all = false
|
|
true
|
|
else
|
|
@line = @buffer_of_lines[@line_index]
|
|
@first_line_started_from = new_first_line_started_from
|
|
@started_from = new_started_from
|
|
@cursor = new_cursor
|
|
@cursor_max = new_cursor_max
|
|
@byte_pointer = new_byte_pointer
|
|
move_cursor_down(first_line_diff + @started_from)
|
|
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
|
false
|
|
end
|
|
end
|
|
|
|
private def rerender_changed_current_line
|
|
if @previous_line_index
|
|
new_lines = whole_lines(index: @previous_line_index, line: @line)
|
|
else
|
|
new_lines = whole_lines
|
|
end
|
|
prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
|
|
all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
|
|
diff = all_height - @highest_in_all
|
|
move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
|
|
if diff > 0
|
|
scroll_down(diff)
|
|
move_cursor_up(all_height - 1)
|
|
elsif diff < 0
|
|
(-diff).times do
|
|
Reline::IOGate.move_cursor_column(0)
|
|
Reline::IOGate.erase_after_cursor
|
|
move_cursor_up(1)
|
|
end
|
|
move_cursor_up(all_height - 1)
|
|
else
|
|
move_cursor_up(all_height - 1)
|
|
end
|
|
@highest_in_all = all_height
|
|
back = render_whole_lines(new_lines, prompt_list || prompt, prompt_width)
|
|
move_cursor_up(back)
|
|
if @previous_line_index
|
|
@buffer_of_lines[@previous_line_index] = @line
|
|
@line = @buffer_of_lines[@line_index]
|
|
end
|
|
@first_line_started_from =
|
|
if @line_index.zero?
|
|
0
|
|
else
|
|
calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
|
|
end
|
|
if @prompt_proc
|
|
prompt = prompt_list[@line_index]
|
|
prompt_width = calculate_width(prompt, true)
|
|
end
|
|
move_cursor_down(@first_line_started_from)
|
|
calculate_nearest_cursor
|
|
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
|
move_cursor_down(@started_from)
|
|
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
|
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
|
|
end
|
|
|
|
private def rerender_all_lines
|
|
move_cursor_up(@first_line_started_from + @started_from)
|
|
Reline::IOGate.move_cursor_column(0)
|
|
back = 0
|
|
new_buffer = whole_lines
|
|
prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
|
|
new_buffer.each_with_index do |line, index|
|
|
prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
|
|
width = prompt_width + calculate_width(line)
|
|
height = calculate_height_by_width(width)
|
|
back += height
|
|
end
|
|
old_highest_in_all = @highest_in_all
|
|
if @line_index.zero?
|
|
new_first_line_started_from = 0
|
|
else
|
|
new_first_line_started_from = calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
|
|
end
|
|
new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
|
calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
|
|
if @scroll_partial_screen
|
|
move_cursor_up(@first_line_started_from + @started_from)
|
|
scroll_down(@screen_height - 1)
|
|
move_cursor_up(@screen_height)
|
|
Reline::IOGate.move_cursor_column(0)
|
|
elsif back > old_highest_in_all
|
|
scroll_down(back - 1)
|
|
move_cursor_up(back - 1)
|
|
elsif back < old_highest_in_all
|
|
scroll_down(back)
|
|
Reline::IOGate.erase_after_cursor
|
|
(old_highest_in_all - back - 1).times do
|
|
scroll_down(1)
|
|
Reline::IOGate.erase_after_cursor
|
|
end
|
|
move_cursor_up(old_highest_in_all - 1)
|
|
end
|
|
render_whole_lines(new_buffer, prompt_list || prompt, prompt_width)
|
|
if @prompt_proc
|
|
prompt = prompt_list[@line_index]
|
|
prompt_width = calculate_width(prompt, true)
|
|
end
|
|
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
|
|
@highest_in_all = back
|
|
@first_line_started_from = new_first_line_started_from
|
|
@started_from = new_started_from
|
|
if @scroll_partial_screen
|
|
Reline::IOGate.move_cursor_up(@screen_height - (@first_line_started_from + @started_from - @scroll_partial_screen) - 1)
|
|
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
|
else
|
|
move_cursor_down(@first_line_started_from + @started_from - back + 1)
|
|
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
|
end
|
|
end
|
|
|
|
private def render_whole_lines(lines, prompt, prompt_width)
|
|
rendered_height = 0
|
|
modify_lines(lines).each_with_index do |line, index|
|
|
if prompt.is_a?(Array)
|
|
line_prompt = prompt[index]
|
|
prompt_width = calculate_width(line_prompt, true)
|
|
else
|
|
line_prompt = prompt
|
|
end
|
|
height = render_partial(line_prompt, prompt_width, line, rendered_height, with_control: false)
|
|
if index < (lines.size - 1)
|
|
if @scroll_partial_screen
|
|
if (@scroll_partial_screen - height) < rendered_height and (@scroll_partial_screen + @screen_height - 1) >= (rendered_height + height)
|
|
move_cursor_down(1)
|
|
end
|
|
else
|
|
scroll_down(1)
|
|
end
|
|
rendered_height += height
|
|
else
|
|
rendered_height += height - 1
|
|
end
|
|
end
|
|
rendered_height
|
|
end
|
|
|
|
private def render_partial(prompt, prompt_width, line_to_render, this_started_from, with_control: true)
|
|
visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
|
|
cursor_up_from_last_line = 0
|
|
# TODO: This logic would be sometimes buggy if this logical line isn't the current @line_index.
|
|
if @scroll_partial_screen
|
|
last_visual_line = this_started_from + (height - 1)
|
|
last_screen_line = @scroll_partial_screen + (@screen_height - 1)
|
|
if (@scroll_partial_screen - this_started_from) >= height
|
|
# Render nothing because this line is before the screen.
|
|
visual_lines = []
|
|
elsif this_started_from > last_screen_line
|
|
# Render nothing because this line is after the screen.
|
|
visual_lines = []
|
|
else
|
|
deleted_lines_before_screen = []
|
|
if @scroll_partial_screen > this_started_from and last_visual_line >= @scroll_partial_screen
|
|
# A part of visual lines are before the screen.
|
|
deleted_lines_before_screen = visual_lines.shift((@scroll_partial_screen - this_started_from) * 2)
|
|
deleted_lines_before_screen.compact!
|
|
end
|
|
if this_started_from <= last_screen_line and last_screen_line < last_visual_line
|
|
# A part of visual lines are after the screen.
|
|
visual_lines.pop((last_visual_line - last_screen_line) * 2)
|
|
end
|
|
move_cursor_up(deleted_lines_before_screen.size - @started_from)
|
|
cursor_up_from_last_line = @started_from - deleted_lines_before_screen.size
|
|
end
|
|
end
|
|
if with_control
|
|
if height > @highest_in_this
|
|
diff = height - @highest_in_this
|
|
scroll_down(diff)
|
|
@highest_in_all += diff
|
|
@highest_in_this = height
|
|
move_cursor_up(diff)
|
|
elsif height < @highest_in_this
|
|
diff = @highest_in_this - height
|
|
@highest_in_all -= diff
|
|
@highest_in_this = height
|
|
end
|
|
move_cursor_up(@started_from)
|
|
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
|
cursor_up_from_last_line = height - 1 - @started_from
|
|
end
|
|
if Reline::Unicode::CSI_REGEXP.match?(prompt + line_to_render)
|
|
@output.write "\e[0m" # clear character decorations
|
|
end
|
|
visual_lines.each_with_index do |line, index|
|
|
Reline::IOGate.move_cursor_column(0)
|
|
if line.nil?
|
|
if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
|
|
# Reaches the end of line.
|
|
#
|
|
# When the cursor is at the end of the line and erases characters
|
|
# after the cursor, some terminals delete the character at the
|
|
# cursor position.
|
|
move_cursor_down(1)
|
|
Reline::IOGate.move_cursor_column(0)
|
|
else
|
|
Reline::IOGate.erase_after_cursor
|
|
move_cursor_down(1)
|
|
Reline::IOGate.move_cursor_column(0)
|
|
end
|
|
next
|
|
end
|
|
@output.write line
|
|
@output.flush
|
|
if @first_prompt
|
|
@first_prompt = false
|
|
@pre_input_hook&.call
|
|
end
|
|
end
|
|
unless visual_lines.empty?
|
|
Reline::IOGate.erase_after_cursor
|
|
Reline::IOGate.move_cursor_column(0)
|
|
end
|
|
if with_control
|
|
# Just after rendring, so the cursor is on the last line.
|
|
if finished?
|
|
Reline::IOGate.move_cursor_column(0)
|
|
else
|
|
# Moves up from bottom of lines to the cursor position.
|
|
move_cursor_up(cursor_up_from_last_line)
|
|
# This logic is buggy if a fullwidth char is wrapped because there is only one halfwidth at end of a line.
|
|
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
|
end
|
|
end
|
|
height
|
|
end
|
|
|
|
private def modify_lines(before)
|
|
return before if before.nil? || before.empty? || simplified_rendering?
|
|
|
|
if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
|
|
after.lines("\n").map { |l| l.chomp('') }
|
|
else
|
|
before
|
|
end
|
|
end
|
|
|
|
private def show_menu
|
|
scroll_down(@highest_in_all - @first_line_started_from)
|
|
@rerender_all = true
|
|
@menu_info.list.sort!.each do |item|
|
|
Reline::IOGate.move_cursor_column(0)
|
|
@output.write item
|
|
@output.flush
|
|
scroll_down(1)
|
|
end
|
|
scroll_down(@highest_in_all - 1)
|
|
move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
|
|
end
|
|
|
|
private def clear_screen_buffer(prompt, prompt_list, prompt_width)
|
|
Reline::IOGate.clear_screen
|
|
back = 0
|
|
modify_lines(whole_lines).each_with_index do |line, index|
|
|
if @prompt_proc
|
|
pr = prompt_list[index]
|
|
height = render_partial(pr, calculate_width(pr), line, back, with_control: false)
|
|
else
|
|
height = render_partial(prompt, prompt_width, line, back, with_control: false)
|
|
end
|
|
if index < (@buffer_of_lines.size - 1)
|
|
move_cursor_down(height)
|
|
back += height
|
|
end
|
|
end
|
|
move_cursor_up(back)
|
|
move_cursor_down(@first_line_started_from + @started_from)
|
|
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
|
end
|
|
|
|
def editing_mode
|
|
@config.editing_mode
|
|
end
|
|
|
|
private def menu(target, list)
|
|
@menu_info = MenuInfo.new(target, list)
|
|
end
|
|
|
|
private def complete_internal_proc(list, is_menu)
|
|
preposing, target, postposing = retrieve_completion_block
|
|
list = list.select { |i|
|
|
if i and not Encoding.compatible?(target.encoding, i.encoding)
|
|
raise Encoding::CompatibilityError, "#{target.encoding.name} is not compatible with #{i.encoding.name}"
|
|
end
|
|
if @config.completion_ignore_case
|
|
i&.downcase&.start_with?(target.downcase)
|
|
else
|
|
i&.start_with?(target)
|
|
end
|
|
}.uniq
|
|
if is_menu
|
|
menu(target, list)
|
|
return nil
|
|
end
|
|
completed = list.inject { |memo, item|
|
|
begin
|
|
memo_mbchars = memo.unicode_normalize.grapheme_clusters
|
|
item_mbchars = item.unicode_normalize.grapheme_clusters
|
|
rescue Encoding::CompatibilityError
|
|
memo_mbchars = memo.grapheme_clusters
|
|
item_mbchars = item.grapheme_clusters
|
|
end
|
|
size = [memo_mbchars.size, item_mbchars.size].min
|
|
result = ''
|
|
size.times do |i|
|
|
if @config.completion_ignore_case
|
|
if memo_mbchars[i].casecmp?(item_mbchars[i])
|
|
result << memo_mbchars[i]
|
|
else
|
|
break
|
|
end
|
|
else
|
|
if memo_mbchars[i] == item_mbchars[i]
|
|
result << memo_mbchars[i]
|
|
else
|
|
break
|
|
end
|
|
end
|
|
end
|
|
result
|
|
}
|
|
[target, preposing, completed, postposing]
|
|
end
|
|
|
|
private def complete(list, just_show_list = false)
|
|
case @completion_state
|
|
when CompletionState::NORMAL, CompletionState::JOURNEY
|
|
@completion_state = CompletionState::COMPLETION
|
|
when CompletionState::PERFECT_MATCH
|
|
@dig_perfect_match_proc&.(@perfect_matched)
|
|
end
|
|
if just_show_list
|
|
is_menu = true
|
|
elsif @completion_state == CompletionState::MENU
|
|
is_menu = true
|
|
elsif @completion_state == CompletionState::MENU_WITH_PERFECT_MATCH
|
|
is_menu = true
|
|
else
|
|
is_menu = false
|
|
end
|
|
result = complete_internal_proc(list, is_menu)
|
|
if @completion_state == CompletionState::MENU_WITH_PERFECT_MATCH
|
|
@completion_state = CompletionState::PERFECT_MATCH
|
|
end
|
|
return if result.nil?
|
|
target, preposing, completed, postposing = result
|
|
return if completed.nil?
|
|
if target <= completed and (@completion_state == CompletionState::COMPLETION)
|
|
if list.include?(completed)
|
|
if list.one?
|
|
@completion_state = CompletionState::PERFECT_MATCH
|
|
else
|
|
@completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
|
|
end
|
|
@perfect_matched = completed
|
|
else
|
|
@completion_state = CompletionState::MENU
|
|
end
|
|
if not just_show_list and target < completed
|
|
@line = preposing + completed + completion_append_character.to_s + postposing
|
|
line_to_pointer = preposing + completed + completion_append_character.to_s
|
|
@cursor_max = calculate_width(@line)
|
|
@cursor = calculate_width(line_to_pointer)
|
|
@byte_pointer = line_to_pointer.bytesize
|
|
end
|
|
end
|
|
end
|
|
|
|
private def move_completed_list(list, direction)
|
|
case @completion_state
|
|
when CompletionState::NORMAL, CompletionState::COMPLETION,
|
|
CompletionState::MENU, CompletionState::MENU_WITH_PERFECT_MATCH
|
|
@completion_state = CompletionState::JOURNEY
|
|
result = retrieve_completion_block
|
|
return if result.nil?
|
|
preposing, target, postposing = result
|
|
@completion_journey_data = CompletionJourneyData.new(
|
|
preposing, postposing,
|
|
[target] + list.select{ |item| item.start_with?(target) }, 0)
|
|
@completion_state = CompletionState::JOURNEY
|
|
else
|
|
case direction
|
|
when :up
|
|
@completion_journey_data.pointer -= 1
|
|
if @completion_journey_data.pointer < 0
|
|
@completion_journey_data.pointer = @completion_journey_data.list.size - 1
|
|
end
|
|
when :down
|
|
@completion_journey_data.pointer += 1
|
|
if @completion_journey_data.pointer >= @completion_journey_data.list.size
|
|
@completion_journey_data.pointer = 0
|
|
end
|
|
end
|
|
completed = @completion_journey_data.list[@completion_journey_data.pointer]
|
|
@line = @completion_journey_data.preposing + completed + @completion_journey_data.postposing
|
|
line_to_pointer = @completion_journey_data.preposing + completed
|
|
@cursor_max = calculate_width(@line)
|
|
@cursor = calculate_width(line_to_pointer)
|
|
@byte_pointer = line_to_pointer.bytesize
|
|
end
|
|
end
|
|
|
|
private def run_for_operators(key, method_symbol, &block)
|
|
if @waiting_operator_proc
|
|
if VI_MOTIONS.include?(method_symbol)
|
|
old_cursor, old_byte_pointer = @cursor, @byte_pointer
|
|
@vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg > 1
|
|
block.(true)
|
|
unless @waiting_proc
|
|
cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
|
|
@cursor, @byte_pointer = old_cursor, old_byte_pointer
|
|
@waiting_operator_proc.(cursor_diff, byte_pointer_diff)
|
|
else
|
|
old_waiting_proc = @waiting_proc
|
|
old_waiting_operator_proc = @waiting_operator_proc
|
|
current_waiting_operator_proc = @waiting_operator_proc
|
|
@waiting_proc = proc { |k|
|
|
old_cursor, old_byte_pointer = @cursor, @byte_pointer
|
|
old_waiting_proc.(k)
|
|
cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
|
|
@cursor, @byte_pointer = old_cursor, old_byte_pointer
|
|
current_waiting_operator_proc.(cursor_diff, byte_pointer_diff)
|
|
@waiting_operator_proc = old_waiting_operator_proc
|
|
}
|
|
end
|
|
else
|
|
# Ignores operator when not motion is given.
|
|
block.(false)
|
|
end
|
|
@waiting_operator_proc = nil
|
|
@waiting_operator_vi_arg = nil
|
|
@vi_arg = nil
|
|
else
|
|
block.(false)
|
|
end
|
|
end
|
|
|
|
private def argumentable?(method_obj)
|
|
method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :arg }
|
|
end
|
|
|
|
private def inclusive?(method_obj)
|
|
# If a motion method with the keyword argument "inclusive" follows the
|
|
# operator, it must contain the character at the cursor position.
|
|
method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :inclusive }
|
|
end
|
|
|
|
def wrap_method_call(method_symbol, method_obj, key, with_operator = false)
|
|
if @config.editing_mode_is?(:emacs, :vi_insert) and @waiting_proc.nil? and @waiting_operator_proc.nil?
|
|
not_insertion = method_symbol != :ed_insert
|
|
process_insert(force: not_insertion)
|
|
end
|
|
if @vi_arg and argumentable?(method_obj)
|
|
if with_operator and inclusive?(method_obj)
|
|
method_obj.(key, arg: @vi_arg, inclusive: true)
|
|
else
|
|
method_obj.(key, arg: @vi_arg)
|
|
end
|
|
else
|
|
if with_operator and inclusive?(method_obj)
|
|
method_obj.(key, inclusive: true)
|
|
else
|
|
method_obj.(key)
|
|
end
|
|
end
|
|
end
|
|
|
|
private def process_key(key, method_symbol)
|
|
if method_symbol and respond_to?(method_symbol, true)
|
|
method_obj = method(method_symbol)
|
|
else
|
|
method_obj = nil
|
|
end
|
|
if method_symbol and key.is_a?(Symbol)
|
|
if @vi_arg and argumentable?(method_obj)
|
|
run_for_operators(key, method_symbol) do |with_operator|
|
|
wrap_method_call(method_symbol, method_obj, key, with_operator)
|
|
end
|
|
else
|
|
wrap_method_call(method_symbol, method_obj, key) if method_obj
|
|
end
|
|
@kill_ring.process
|
|
@vi_arg = nil
|
|
elsif @vi_arg
|
|
if key.chr =~ /[0-9]/
|
|
ed_argument_digit(key)
|
|
else
|
|
if argumentable?(method_obj)
|
|
run_for_operators(key, method_symbol) do |with_operator|
|
|
wrap_method_call(method_symbol, method_obj, key, with_operator)
|
|
end
|
|
elsif @waiting_proc
|
|
@waiting_proc.(key)
|
|
elsif method_obj
|
|
wrap_method_call(method_symbol, method_obj, key)
|
|
else
|
|
ed_insert(key) unless @config.editing_mode_is?(:vi_command)
|
|
end
|
|
@kill_ring.process
|
|
@vi_arg = nil
|
|
end
|
|
elsif @waiting_proc
|
|
@waiting_proc.(key)
|
|
@kill_ring.process
|
|
elsif method_obj
|
|
if method_symbol == :ed_argument_digit
|
|
wrap_method_call(method_symbol, method_obj, key)
|
|
else
|
|
run_for_operators(key, method_symbol) do |with_operator|
|
|
wrap_method_call(method_symbol, method_obj, key, with_operator)
|
|
end
|
|
end
|
|
@kill_ring.process
|
|
else
|
|
ed_insert(key) unless @config.editing_mode_is?(:vi_command)
|
|
end
|
|
end
|
|
|
|
private def normal_char(key)
|
|
method_symbol = method_obj = nil
|
|
if key.combined_char.is_a?(Symbol)
|
|
process_key(key.combined_char, key.combined_char)
|
|
return
|
|
end
|
|
@multibyte_buffer << key.combined_char
|
|
if @multibyte_buffer.size > 1
|
|
if @multibyte_buffer.dup.force_encoding(@encoding).valid_encoding?
|
|
process_key(@multibyte_buffer.dup.force_encoding(@encoding), nil)
|
|
@multibyte_buffer.clear
|
|
else
|
|
# invalid
|
|
return
|
|
end
|
|
else # single byte
|
|
return if key.char >= 128 # maybe, first byte of multi byte
|
|
method_symbol = @config.editing_mode.get_method(key.combined_char)
|
|
if key.with_meta and method_symbol == :ed_unassigned
|
|
# split ESC + key
|
|
method_symbol = @config.editing_mode.get_method("\e".ord)
|
|
process_key("\e".ord, method_symbol)
|
|
method_symbol = @config.editing_mode.get_method(key.char)
|
|
process_key(key.char, method_symbol)
|
|
else
|
|
process_key(key.combined_char, method_symbol)
|
|
end
|
|
@multibyte_buffer.clear
|
|
end
|
|
if @config.editing_mode_is?(:vi_command) and @cursor > 0 and @cursor == @cursor_max
|
|
byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
|
|
@byte_pointer -= byte_size
|
|
mbchar = @line.byteslice(@byte_pointer, byte_size)
|
|
width = Reline::Unicode.get_mbchar_width(mbchar)
|
|
@cursor -= width
|
|
end
|
|
end
|
|
|
|
def input_key(key)
|
|
@just_cursor_moving = nil
|
|
if key.char.nil?
|
|
if @first_char
|
|
@line = nil
|
|
end
|
|
finish
|
|
return
|
|
end
|
|
old_line = @line.dup
|
|
@first_char = false
|
|
completion_occurs = false
|
|
if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
|
|
unless @config.disable_completion
|
|
result = call_completion_proc
|
|
if result.is_a?(Array)
|
|
completion_occurs = true
|
|
process_insert
|
|
complete(result)
|
|
end
|
|
end
|
|
elsif not @config.disable_completion and @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
|
|
unless @config.disable_completion
|
|
result = call_completion_proc
|
|
if result.is_a?(Array)
|
|
completion_occurs = true
|
|
process_insert
|
|
move_completed_list(result, "\C-p".ord == key.char ? :up : :down)
|
|
end
|
|
end
|
|
elsif Symbol === key.char and respond_to?(key.char, true)
|
|
process_key(key.char, key.char)
|
|
else
|
|
normal_char(key)
|
|
end
|
|
unless completion_occurs
|
|
@completion_state = CompletionState::NORMAL
|
|
end
|
|
if not @in_pasting and @just_cursor_moving.nil?
|
|
if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
|
|
@just_cursor_moving = true
|
|
elsif @previous_line_index.nil? and @buffer_of_lines[@line_index] == @line and old_line == @line
|
|
@just_cursor_moving = true
|
|
else
|
|
@just_cursor_moving = false
|
|
end
|
|
else
|
|
@just_cursor_moving = false
|
|
end
|
|
if @is_multiline and @auto_indent_proc and not simplified_rendering?
|
|
process_auto_indent
|
|
end
|
|
end
|
|
|
|
def call_completion_proc
|
|
result = retrieve_completion_block(true)
|
|
slice = result[1]
|
|
result = @completion_proc.(slice) if @completion_proc and slice
|
|
Reline.core.instance_variable_set(:@completion_quote_character, nil)
|
|
result
|
|
end
|
|
|
|
private def process_auto_indent
|
|
return if not @check_new_auto_indent and @previous_line_index # move cursor up or down
|
|
if @check_new_auto_indent and @previous_line_index and @previous_line_index > 0 and @line_index > @previous_line_index
|
|
# Fix indent of a line when a newline is inserted to the next
|
|
new_lines = whole_lines(index: @previous_line_index, line: @line)
|
|
new_indent = @auto_indent_proc.(new_lines[0..-3].push(''), @line_index - 1, 0, true)
|
|
md = @line.match(/\A */)
|
|
prev_indent = md[0].count(' ')
|
|
@line = ' ' * new_indent + @line.lstrip
|
|
|
|
new_indent = nil
|
|
result = @auto_indent_proc.(new_lines[0..-2], @line_index - 1, (new_lines[-2].size + 1), false)
|
|
if result
|
|
new_indent = result
|
|
end
|
|
if new_indent&.>= 0
|
|
@line = ' ' * new_indent + @line.lstrip
|
|
end
|
|
end
|
|
if @previous_line_index
|
|
new_lines = whole_lines(index: @previous_line_index, line: @line)
|
|
else
|
|
new_lines = whole_lines
|
|
end
|
|
new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
|
|
new_indent = @cursor_max if new_indent&.> @cursor_max
|
|
if new_indent&.>= 0
|
|
md = new_lines[@line_index].match(/\A */)
|
|
prev_indent = md[0].count(' ')
|
|
if @check_new_auto_indent
|
|
@buffer_of_lines[@line_index] = ' ' * new_indent + @buffer_of_lines[@line_index].lstrip
|
|
@cursor = new_indent
|
|
@byte_pointer = new_indent
|
|
else
|
|
@line = ' ' * new_indent + @line.lstrip
|
|
@cursor += new_indent - prev_indent
|
|
@byte_pointer += new_indent - prev_indent
|
|
end
|
|
end
|
|
@check_new_auto_indent = false
|
|
end
|
|
|
|
def retrieve_completion_block(set_completion_quote_character = false)
|
|
word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
|
|
quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
|
|
before = @line.byteslice(0, @byte_pointer)
|
|
rest = nil
|
|
break_pointer = nil
|
|
quote = nil
|
|
closing_quote = nil
|
|
escaped_quote = nil
|
|
i = 0
|
|
while i < @byte_pointer do
|
|
slice = @line.byteslice(i, @byte_pointer - i)
|
|
unless slice.valid_encoding?
|
|
i += 1
|
|
next
|
|
end
|
|
if quote and slice.start_with?(closing_quote)
|
|
quote = nil
|
|
i += 1
|
|
rest = nil
|
|
elsif quote and slice.start_with?(escaped_quote)
|
|
# skip
|
|
i += 2
|
|
elsif slice =~ quote_characters_regexp # find new "
|
|
rest = $'
|
|
quote = $&
|
|
closing_quote = /(?!\\)#{Regexp.escape(quote)}/
|
|
escaped_quote = /\\#{Regexp.escape(quote)}/
|
|
i += 1
|
|
break_pointer = i - 1
|
|
elsif not quote and slice =~ word_break_regexp
|
|
rest = $'
|
|
i += 1
|
|
before = @line.byteslice(i, @byte_pointer - i)
|
|
break_pointer = i
|
|
else
|
|
i += 1
|
|
end
|
|
end
|
|
postposing = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
|
|
if rest
|
|
preposing = @line.byteslice(0, break_pointer)
|
|
target = rest
|
|
if set_completion_quote_character and quote
|
|
Reline.core.instance_variable_set(:@completion_quote_character, quote)
|
|
if postposing !~ /(?!\\)#{Regexp.escape(quote)}/ # closing quote
|
|
insert_text(quote)
|
|
end
|
|
end
|
|
else
|
|
preposing = ''
|
|
if break_pointer
|
|
preposing = @line.byteslice(0, break_pointer)
|
|
else
|
|
preposing = ''
|
|
end
|
|
target = before
|
|
end
|
|
[preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
|
|
end
|
|
|
|
def confirm_multiline_termination
|
|
temp_buffer = @buffer_of_lines.dup
|
|
if @previous_line_index and @line_index == (@buffer_of_lines.size - 1)
|
|
temp_buffer[@previous_line_index] = @line
|
|
else
|
|
temp_buffer[@line_index] = @line
|
|
end
|
|
@confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
|
|
end
|
|
|
|
def insert_text(text)
|
|
width = calculate_width(text)
|
|
if @cursor == @cursor_max
|
|
@line += text
|
|
else
|
|
@line = byteinsert(@line, @byte_pointer, text)
|
|
end
|
|
@byte_pointer += text.bytesize
|
|
@cursor += width
|
|
@cursor_max += width
|
|
end
|
|
|
|
def delete_text(start = nil, length = nil)
|
|
if start.nil? and length.nil?
|
|
@line&.clear
|
|
@byte_pointer = 0
|
|
@cursor = 0
|
|
@cursor_max = 0
|
|
elsif not start.nil? and not length.nil?
|
|
if @line
|
|
before = @line.byteslice(0, start)
|
|
after = @line.byteslice(start + length, @line.bytesize)
|
|
@line = before + after
|
|
@byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
|
|
str = @line.byteslice(0, @byte_pointer)
|
|
@cursor = calculate_width(str)
|
|
@cursor_max = calculate_width(@line)
|
|
end
|
|
elsif start.is_a?(Range)
|
|
range = start
|
|
first = range.first
|
|
last = range.last
|
|
last = @line.bytesize - 1 if last > @line.bytesize
|
|
last += @line.bytesize if last < 0
|
|
first += @line.bytesize if first < 0
|
|
range = range.exclude_end? ? first...last : first..last
|
|
@line = @line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(@encoding)
|
|
@byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
|
|
str = @line.byteslice(0, @byte_pointer)
|
|
@cursor = calculate_width(str)
|
|
@cursor_max = calculate_width(@line)
|
|
else
|
|
@line = @line.byteslice(0, start)
|
|
@byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
|
|
str = @line.byteslice(0, @byte_pointer)
|
|
@cursor = calculate_width(str)
|
|
@cursor_max = calculate_width(@line)
|
|
end
|
|
end
|
|
|
|
def byte_pointer=(val)
|
|
@byte_pointer = val
|
|
str = @line.byteslice(0, @byte_pointer)
|
|
@cursor = calculate_width(str)
|
|
@cursor_max = calculate_width(@line)
|
|
end
|
|
|
|
def whole_lines(index: @line_index, line: @line)
|
|
temp_lines = @buffer_of_lines.dup
|
|
temp_lines[index] = line
|
|
temp_lines
|
|
end
|
|
|
|
def whole_buffer
|
|
if @buffer_of_lines.size == 1 and @line.nil?
|
|
nil
|
|
else
|
|
if @previous_line_index
|
|
whole_lines(index: @previous_line_index, line: @line).join("\n")
|
|
else
|
|
whole_lines.join("\n")
|
|
end
|
|
end
|
|
end
|
|
|
|
def finished?
|
|
@finished
|
|
end
|
|
|
|
def finish
|
|
@finished = true
|
|
@rerender_all = true
|
|
@config.reset
|
|
end
|
|
|
|
private def byteslice!(str, byte_pointer, size)
|
|
new_str = str.byteslice(0, byte_pointer)
|
|
new_str << str.byteslice(byte_pointer + size, str.bytesize)
|
|
[new_str, str.byteslice(byte_pointer, size)]
|
|
end
|
|
|
|
private def byteinsert(str, byte_pointer, other)
|
|
new_str = str.byteslice(0, byte_pointer)
|
|
new_str << other
|
|
new_str << str.byteslice(byte_pointer, str.bytesize)
|
|
new_str
|
|
end
|
|
|
|
private def calculate_width(str, allow_escape_code = false)
|
|
Reline::Unicode.calculate_width(str, allow_escape_code)
|
|
end
|
|
|
|
private def key_delete(key)
|
|
if @config.editing_mode_is?(:vi_insert, :emacs)
|
|
ed_delete_next_char(key)
|
|
end
|
|
end
|
|
|
|
private def key_newline(key)
|
|
if @is_multiline
|
|
if (@buffer_of_lines.size - 1) == @line_index and @line.bytesize == @byte_pointer
|
|
@add_newline_to_end_of_buffer = true
|
|
end
|
|
next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
|
|
cursor_line = @line.byteslice(0, @byte_pointer)
|
|
insert_new_line(cursor_line, next_line)
|
|
@cursor = 0
|
|
@check_new_auto_indent = true unless @in_pasting
|
|
end
|
|
end
|
|
|
|
private def ed_unassigned(key) end # do nothing
|
|
|
|
private def process_insert(force: false)
|
|
return if @continuous_insertion_buffer.empty? or (@in_pasting and not force)
|
|
width = Reline::Unicode.calculate_width(@continuous_insertion_buffer)
|
|
bytesize = @continuous_insertion_buffer.bytesize
|
|
if @cursor == @cursor_max
|
|
@line += @continuous_insertion_buffer
|
|
else
|
|
@line = byteinsert(@line, @byte_pointer, @continuous_insertion_buffer)
|
|
end
|
|
@byte_pointer += bytesize
|
|
@cursor += width
|
|
@cursor_max += width
|
|
@continuous_insertion_buffer.clear
|
|
end
|
|
|
|
private def ed_insert(key)
|
|
str = nil
|
|
width = nil
|
|
bytesize = nil
|
|
if key.instance_of?(String)
|
|
begin
|
|
key.encode(Encoding::UTF_8)
|
|
rescue Encoding::UndefinedConversionError
|
|
return
|
|
end
|
|
str = key
|
|
bytesize = key.bytesize
|
|
else
|
|
begin
|
|
key.chr.encode(Encoding::UTF_8)
|
|
rescue Encoding::UndefinedConversionError
|
|
return
|
|
end
|
|
str = key.chr
|
|
bytesize = 1
|
|
end
|
|
if @in_pasting
|
|
@continuous_insertion_buffer << str
|
|
return
|
|
elsif not @continuous_insertion_buffer.empty?
|
|
process_insert
|
|
end
|
|
width = Reline::Unicode.get_mbchar_width(str)
|
|
if @cursor == @cursor_max
|
|
@line += str
|
|
else
|
|
@line = byteinsert(@line, @byte_pointer, str)
|
|
end
|
|
last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
|
|
@byte_pointer += bytesize
|
|
last_mbchar = @line.byteslice((@byte_pointer - bytesize - last_byte_size), last_byte_size)
|
|
if last_byte_size != 0 and (last_mbchar + str).grapheme_clusters.size == 1
|
|
width = 0
|
|
end
|
|
@cursor += width
|
|
@cursor_max += width
|
|
end
|
|
alias_method :ed_digit, :ed_insert
|
|
alias_method :self_insert, :ed_insert
|
|
|
|
private def ed_quoted_insert(str, arg: 1)
|
|
@waiting_proc = proc { |key|
|
|
arg.times do
|
|
if key == "\C-j".ord or key == "\C-m".ord
|
|
key_newline(key)
|
|
else
|
|
ed_insert(key)
|
|
end
|
|
end
|
|
@waiting_proc = nil
|
|
}
|
|
end
|
|
alias_method :quoted_insert, :ed_quoted_insert
|
|
|
|
private def ed_next_char(key, arg: 1)
|
|
byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
|
|
if (@byte_pointer < @line.bytesize)
|
|
mbchar = @line.byteslice(@byte_pointer, byte_size)
|
|
width = Reline::Unicode.get_mbchar_width(mbchar)
|
|
@cursor += width if width
|
|
@byte_pointer += byte_size
|
|
elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == @line.bytesize and @line_index < @buffer_of_lines.size - 1
|
|
next_line = @buffer_of_lines[@line_index + 1]
|
|
@cursor = 0
|
|
@byte_pointer = 0
|
|
@cursor_max = calculate_width(next_line)
|
|
@previous_line_index = @line_index
|
|
@line_index += 1
|
|
end
|
|
arg -= 1
|
|
ed_next_char(key, arg: arg) if arg > 0
|
|
end
|
|
alias_method :forward_char, :ed_next_char
|
|
|
|
private def ed_prev_char(key, arg: 1)
|
|
if @cursor > 0
|
|
byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
|
|
@byte_pointer -= byte_size
|
|
mbchar = @line.byteslice(@byte_pointer, byte_size)
|
|
width = Reline::Unicode.get_mbchar_width(mbchar)
|
|
@cursor -= width
|
|
elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0
|
|
prev_line = @buffer_of_lines[@line_index - 1]
|
|
@cursor = calculate_width(prev_line)
|
|
@byte_pointer = prev_line.bytesize
|
|
@cursor_max = calculate_width(prev_line)
|
|
@previous_line_index = @line_index
|
|
@line_index -= 1
|
|
end
|
|
arg -= 1
|
|
ed_prev_char(key, arg: arg) if arg > 0
|
|
end
|
|
alias_method :backward_char, :ed_prev_char
|
|
|
|
private def vi_first_print(key)
|
|
@byte_pointer, @cursor = Reline::Unicode.vi_first_print(@line)
|
|
end
|
|
|
|
private def ed_move_to_beg(key)
|
|
@byte_pointer = @cursor = 0
|
|
end
|
|
alias_method :beginning_of_line, :ed_move_to_beg
|
|
|
|
private def ed_move_to_end(key)
|
|
@byte_pointer = 0
|
|
@cursor = 0
|
|
byte_size = 0
|
|
while @byte_pointer < @line.bytesize
|
|
byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
|
|
if byte_size > 0
|
|
mbchar = @line.byteslice(@byte_pointer, byte_size)
|
|
@cursor += Reline::Unicode.get_mbchar_width(mbchar)
|
|
end
|
|
@byte_pointer += byte_size
|
|
end
|
|
end
|
|
alias_method :end_of_line, :ed_move_to_end
|
|
|
|
private def generate_searcher
|
|
Fiber.new do |first_key|
|
|
prev_search_key = first_key
|
|
search_word = String.new(encoding: @encoding)
|
|
multibyte_buf = String.new(encoding: 'ASCII-8BIT')
|
|
last_hit = nil
|
|
case first_key
|
|
when "\C-r".ord
|
|
prompt_name = 'reverse-i-search'
|
|
when "\C-s".ord
|
|
prompt_name = 'i-search'
|
|
end
|
|
loop do
|
|
key = Fiber.yield(search_word)
|
|
search_again = false
|
|
case key
|
|
when -1 # determined
|
|
Reline.last_incremental_search = search_word
|
|
break
|
|
when "\C-h".ord, "\C-?".ord
|
|
grapheme_clusters = search_word.grapheme_clusters
|
|
if grapheme_clusters.size > 0
|
|
grapheme_clusters.pop
|
|
search_word = grapheme_clusters.join
|
|
end
|
|
when "\C-r".ord, "\C-s".ord
|
|
search_again = true if prev_search_key == key
|
|
prev_search_key = key
|
|
else
|
|
multibyte_buf << key
|
|
if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
|
|
search_word << multibyte_buf.dup.force_encoding(@encoding)
|
|
multibyte_buf.clear
|
|
end
|
|
end
|
|
hit = nil
|
|
if not search_word.empty? and @line_backup_in_history&.include?(search_word)
|
|
@history_pointer = nil
|
|
hit = @line_backup_in_history
|
|
else
|
|
if search_again
|
|
if search_word.empty? and Reline.last_incremental_search
|
|
search_word = Reline.last_incremental_search
|
|
end
|
|
if @history_pointer
|
|
case prev_search_key
|
|
when "\C-r".ord
|
|
history_pointer_base = 0
|
|
history = Reline::HISTORY[0..(@history_pointer - 1)]
|
|
when "\C-s".ord
|
|
history_pointer_base = @history_pointer + 1
|
|
history = Reline::HISTORY[(@history_pointer + 1)..-1]
|
|
end
|
|
else
|
|
history_pointer_base = 0
|
|
history = Reline::HISTORY
|
|
end
|
|
elsif @history_pointer
|
|
case prev_search_key
|
|
when "\C-r".ord
|
|
history_pointer_base = 0
|
|
history = Reline::HISTORY[0..@history_pointer]
|
|
when "\C-s".ord
|
|
history_pointer_base = @history_pointer
|
|
history = Reline::HISTORY[@history_pointer..-1]
|
|
end
|
|
else
|
|
history_pointer_base = 0
|
|
history = Reline::HISTORY
|
|
end
|
|
case prev_search_key
|
|
when "\C-r".ord
|
|
hit_index = history.rindex { |item|
|
|
item.include?(search_word)
|
|
}
|
|
when "\C-s".ord
|
|
hit_index = history.index { |item|
|
|
item.include?(search_word)
|
|
}
|
|
end
|
|
if hit_index
|
|
@history_pointer = history_pointer_base + hit_index
|
|
hit = Reline::HISTORY[@history_pointer]
|
|
end
|
|
end
|
|
case prev_search_key
|
|
when "\C-r".ord
|
|
prompt_name = 'reverse-i-search'
|
|
when "\C-s".ord
|
|
prompt_name = 'i-search'
|
|
end
|
|
if hit
|
|
if @is_multiline
|
|
@buffer_of_lines = hit.split("\n")
|
|
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
|
@line_index = @buffer_of_lines.size - 1
|
|
@line = @buffer_of_lines.last
|
|
@rerender_all = true
|
|
@searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
|
|
else
|
|
@line = hit
|
|
@searching_prompt = "(%s)`%s': %s" % [prompt_name, search_word, hit]
|
|
end
|
|
last_hit = hit
|
|
else
|
|
if @is_multiline
|
|
@rerender_all = true
|
|
@searching_prompt = "(failed %s)`%s'" % [prompt_name, search_word]
|
|
else
|
|
@searching_prompt = "(failed %s)`%s': %s" % [prompt_name, search_word, last_hit]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
private def incremental_search_history(key)
|
|
unless @history_pointer
|
|
if @is_multiline
|
|
@line_backup_in_history = whole_buffer
|
|
else
|
|
@line_backup_in_history = @line
|
|
end
|
|
end
|
|
searcher = generate_searcher
|
|
searcher.resume(key)
|
|
@searching_prompt = "(reverse-i-search)`': "
|
|
termination_keys = ["\C-j".ord]
|
|
termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators
|
|
@waiting_proc = ->(k) {
|
|
case k
|
|
when *termination_keys
|
|
if @history_pointer
|
|
buffer = Reline::HISTORY[@history_pointer]
|
|
else
|
|
buffer = @line_backup_in_history
|
|
end
|
|
if @is_multiline
|
|
@buffer_of_lines = buffer.split("\n")
|
|
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
|
@line_index = @buffer_of_lines.size - 1
|
|
@line = @buffer_of_lines.last
|
|
@rerender_all = true
|
|
else
|
|
@line = buffer
|
|
end
|
|
@searching_prompt = nil
|
|
@waiting_proc = nil
|
|
@cursor_max = calculate_width(@line)
|
|
@cursor = @byte_pointer = 0
|
|
@rerender_all = true
|
|
@cached_prompt_list = nil
|
|
searcher.resume(-1)
|
|
when "\C-g".ord
|
|
if @is_multiline
|
|
@buffer_of_lines = @line_backup_in_history.split("\n")
|
|
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
|
@line_index = @buffer_of_lines.size - 1
|
|
@line = @buffer_of_lines.last
|
|
@rerender_all = true
|
|
else
|
|
@line = @line_backup_in_history
|
|
end
|
|
@history_pointer = nil
|
|
@searching_prompt = nil
|
|
@waiting_proc = nil
|
|
@line_backup_in_history = nil
|
|
@cursor_max = calculate_width(@line)
|
|
@cursor = @byte_pointer = 0
|
|
@rerender_all = true
|
|
else
|
|
chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
|
|
if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == "\C-?".ord or k == "\C-r".ord or k == "\C-s".ord
|
|
searcher.resume(k)
|
|
else
|
|
if @history_pointer
|
|
line = Reline::HISTORY[@history_pointer]
|
|
else
|
|
line = @line_backup_in_history
|
|
end
|
|
if @is_multiline
|
|
@line_backup_in_history = whole_buffer
|
|
@buffer_of_lines = line.split("\n")
|
|
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
|
@line_index = @buffer_of_lines.size - 1
|
|
@line = @buffer_of_lines.last
|
|
@rerender_all = true
|
|
else
|
|
@line_backup_in_history = @line
|
|
@line = line
|
|
end
|
|
@searching_prompt = nil
|
|
@waiting_proc = nil
|
|
@cursor_max = calculate_width(@line)
|
|
@cursor = @byte_pointer = 0
|
|
@rerender_all = true
|
|
@cached_prompt_list = nil
|
|
searcher.resume(-1)
|
|
end
|
|
end
|
|
}
|
|
end
|
|
|
|
private def vi_search_prev(key)
|
|
incremental_search_history(key)
|
|
end
|
|
alias_method :reverse_search_history, :vi_search_prev
|
|
|
|
private def vi_search_next(key)
|
|
incremental_search_history(key)
|
|
end
|
|
alias_method :forward_search_history, :vi_search_next
|
|
|
|
private def ed_search_prev_history(key, arg: 1)
|
|
history = nil
|
|
h_pointer = nil
|
|
line_no = nil
|
|
substr = @line.slice(0, @byte_pointer)
|
|
if @history_pointer.nil?
|
|
return if not @line.empty? and substr.empty?
|
|
history = Reline::HISTORY
|
|
elsif @history_pointer.zero?
|
|
history = nil
|
|
h_pointer = nil
|
|
else
|
|
history = Reline::HISTORY.slice(0, @history_pointer)
|
|
end
|
|
return if history.nil?
|
|
if @is_multiline
|
|
h_pointer = history.rindex { |h|
|
|
h.split("\n").each_with_index { |l, i|
|
|
if l.start_with?(substr)
|
|
line_no = i
|
|
break
|
|
end
|
|
}
|
|
not line_no.nil?
|
|
}
|
|
else
|
|
h_pointer = history.rindex { |l|
|
|
l.start_with?(substr)
|
|
}
|
|
end
|
|
return if h_pointer.nil?
|
|
@history_pointer = h_pointer
|
|
if @is_multiline
|
|
@buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
|
|
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
|
@line_index = line_no
|
|
@line = @buffer_of_lines[@line_index]
|
|
@rerender_all = true
|
|
else
|
|
@line = Reline::HISTORY[@history_pointer]
|
|
end
|
|
@cursor_max = calculate_width(@line)
|
|
arg -= 1
|
|
ed_search_prev_history(key, arg: arg) if arg > 0
|
|
end
|
|
alias_method :history_search_backward, :ed_search_prev_history
|
|
|
|
private def ed_search_next_history(key, arg: 1)
|
|
substr = @line.slice(0, @byte_pointer)
|
|
if @history_pointer.nil?
|
|
return
|
|
elsif @history_pointer == (Reline::HISTORY.size - 1) and not substr.empty?
|
|
return
|
|
end
|
|
history = Reline::HISTORY.slice((@history_pointer + 1)..-1)
|
|
h_pointer = nil
|
|
line_no = nil
|
|
if @is_multiline
|
|
h_pointer = history.index { |h|
|
|
h.split("\n").each_with_index { |l, i|
|
|
if l.start_with?(substr)
|
|
line_no = i
|
|
break
|
|
end
|
|
}
|
|
not line_no.nil?
|
|
}
|
|
else
|
|
h_pointer = history.index { |l|
|
|
l.start_with?(substr)
|
|
}
|
|
end
|
|
h_pointer += @history_pointer + 1 if h_pointer and @history_pointer
|
|
return if h_pointer.nil? and not substr.empty?
|
|
@history_pointer = h_pointer
|
|
if @is_multiline
|
|
if @history_pointer.nil? and substr.empty?
|
|
@buffer_of_lines = []
|
|
@line_index = 0
|
|
else
|
|
@buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
|
|
@line_index = line_no
|
|
end
|
|
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
|
@line = @buffer_of_lines[@line_index]
|
|
@rerender_all = true
|
|
else
|
|
if @history_pointer.nil? and substr.empty?
|
|
@line = ''
|
|
else
|
|
@line = Reline::HISTORY[@history_pointer]
|
|
end
|
|
end
|
|
@cursor_max = calculate_width(@line)
|
|
arg -= 1
|
|
ed_search_next_history(key, arg: arg) if arg > 0
|
|
end
|
|
alias_method :history_search_forward, :ed_search_next_history
|
|
|
|
private def ed_prev_history(key, arg: 1)
|
|
if @is_multiline and @line_index > 0
|
|
@previous_line_index = @line_index
|
|
@line_index -= 1
|
|
return
|
|
end
|
|
if Reline::HISTORY.empty?
|
|
return
|
|
end
|
|
if @history_pointer.nil?
|
|
@history_pointer = Reline::HISTORY.size - 1
|
|
if @is_multiline
|
|
@line_backup_in_history = whole_buffer
|
|
@buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
|
|
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
|
@line_index = @buffer_of_lines.size - 1
|
|
@line = @buffer_of_lines.last
|
|
@rerender_all = true
|
|
else
|
|
@line_backup_in_history = @line
|
|
@line = Reline::HISTORY[@history_pointer]
|
|
end
|
|
elsif @history_pointer.zero?
|
|
return
|
|
else
|
|
if @is_multiline
|
|
Reline::HISTORY[@history_pointer] = whole_buffer
|
|
@history_pointer -= 1
|
|
@buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
|
|
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
|
@line_index = @buffer_of_lines.size - 1
|
|
@line = @buffer_of_lines.last
|
|
@rerender_all = true
|
|
else
|
|
Reline::HISTORY[@history_pointer] = @line
|
|
@history_pointer -= 1
|
|
@line = Reline::HISTORY[@history_pointer]
|
|
end
|
|
end
|
|
if @config.editing_mode_is?(:emacs, :vi_insert)
|
|
@cursor_max = @cursor = calculate_width(@line)
|
|
@byte_pointer = @line.bytesize
|
|
elsif @config.editing_mode_is?(:vi_command)
|
|
@byte_pointer = @cursor = 0
|
|
@cursor_max = calculate_width(@line)
|
|
end
|
|
arg -= 1
|
|
ed_prev_history(key, arg: arg) if arg > 0
|
|
end
|
|
|
|
private def ed_next_history(key, arg: 1)
|
|
if @is_multiline and @line_index < (@buffer_of_lines.size - 1)
|
|
@previous_line_index = @line_index
|
|
@line_index += 1
|
|
return
|
|
end
|
|
if @history_pointer.nil?
|
|
return
|
|
elsif @history_pointer == (Reline::HISTORY.size - 1)
|
|
if @is_multiline
|
|
@history_pointer = nil
|
|
@buffer_of_lines = @line_backup_in_history.split("\n")
|
|
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
|
@line_index = 0
|
|
@line = @buffer_of_lines.first
|
|
@rerender_all = true
|
|
else
|
|
@history_pointer = nil
|
|
@line = @line_backup_in_history
|
|
end
|
|
else
|
|
if @is_multiline
|
|
Reline::HISTORY[@history_pointer] = whole_buffer
|
|
@history_pointer += 1
|
|
@buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
|
|
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
|
@line_index = 0
|
|
@line = @buffer_of_lines.first
|
|
@rerender_all = true
|
|
else
|
|
Reline::HISTORY[@history_pointer] = @line
|
|
@history_pointer += 1
|
|
@line = Reline::HISTORY[@history_pointer]
|
|
end
|
|
end
|
|
@line = '' unless @line
|
|
if @config.editing_mode_is?(:emacs, :vi_insert)
|
|
@cursor_max = @cursor = calculate_width(@line)
|
|
@byte_pointer = @line.bytesize
|
|
elsif @config.editing_mode_is?(:vi_command)
|
|
@byte_pointer = @cursor = 0
|
|
@cursor_max = calculate_width(@line)
|
|
end
|
|
arg -= 1
|
|
ed_next_history(key, arg: arg) if arg > 0
|
|
end
|
|
|
|
private def ed_newline(key)
|
|
process_insert(force: true)
|
|
if @is_multiline
|
|
if @config.editing_mode_is?(:vi_command)
|
|
if @line_index < (@buffer_of_lines.size - 1)
|
|
ed_next_history(key) # means cursor down
|
|
else
|
|
# should check confirm_multiline_termination to finish?
|
|
finish
|
|
end
|
|
else
|
|
if @line_index == (@buffer_of_lines.size - 1)
|
|
if confirm_multiline_termination
|
|
finish
|
|
else
|
|
key_newline(key)
|
|
end
|
|
else
|
|
# should check confirm_multiline_termination to finish?
|
|
@previous_line_index = @line_index
|
|
@line_index = @buffer_of_lines.size - 1
|
|
finish
|
|
end
|
|
end
|
|
else
|
|
if @history_pointer
|
|
Reline::HISTORY[@history_pointer] = @line
|
|
@history_pointer = nil
|
|
end
|
|
finish
|
|
end
|
|
end
|
|
|
|
private def em_delete_prev_char(key)
|
|
if @is_multiline and @cursor == 0 and @line_index > 0
|
|
@buffer_of_lines[@line_index] = @line
|
|
@cursor = calculate_width(@buffer_of_lines[@line_index - 1])
|
|
@byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
|
|
@buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
|
|
@line_index -= 1
|
|
@line = @buffer_of_lines[@line_index]
|
|
@cursor_max = calculate_width(@line)
|
|
@rerender_all = true
|
|
elsif @cursor > 0
|
|
byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
|
|
@byte_pointer -= byte_size
|
|
@line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
|
|
width = Reline::Unicode.get_mbchar_width(mbchar)
|
|
@cursor -= width
|
|
@cursor_max -= width
|
|
end
|
|
end
|
|
alias_method :backward_delete_char, :em_delete_prev_char
|
|
|
|
private def ed_kill_line(key)
|
|
if @line.bytesize > @byte_pointer
|
|
@line, deleted = byteslice!(@line, @byte_pointer, @line.bytesize - @byte_pointer)
|
|
@byte_pointer = @line.bytesize
|
|
@cursor = @cursor_max = calculate_width(@line)
|
|
@kill_ring.append(deleted)
|
|
elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1
|
|
@cursor = calculate_width(@line)
|
|
@byte_pointer = @line.bytesize
|
|
@line += @buffer_of_lines.delete_at(@line_index + 1)
|
|
@cursor_max = calculate_width(@line)
|
|
@buffer_of_lines[@line_index] = @line
|
|
@rerender_all = true
|
|
@rest_height += 1
|
|
end
|
|
end
|
|
|
|
private def em_kill_line(key)
|
|
if @byte_pointer > 0
|
|
@line, deleted = byteslice!(@line, 0, @byte_pointer)
|
|
@byte_pointer = 0
|
|
@kill_ring.append(deleted, true)
|
|
@cursor_max = calculate_width(@line)
|
|
@cursor = 0
|
|
end
|
|
end
|
|
alias_method :kill_line, :em_kill_line
|
|
|
|
private def em_delete(key)
|
|
if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
|
|
@line = nil
|
|
if @buffer_of_lines.size > 1
|
|
scroll_down(@highest_in_all - @first_line_started_from)
|
|
end
|
|
Reline::IOGate.move_cursor_column(0)
|
|
@eof = true
|
|
finish
|
|
elsif @byte_pointer < @line.bytesize
|
|
splitted_last = @line.byteslice(@byte_pointer, @line.bytesize)
|
|
mbchar = splitted_last.grapheme_clusters.first
|
|
width = Reline::Unicode.get_mbchar_width(mbchar)
|
|
@cursor_max -= width
|
|
@line, = byteslice!(@line, @byte_pointer, mbchar.bytesize)
|
|
elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1
|
|
@cursor = calculate_width(@line)
|
|
@byte_pointer = @line.bytesize
|
|
@line += @buffer_of_lines.delete_at(@line_index + 1)
|
|
@cursor_max = calculate_width(@line)
|
|
@buffer_of_lines[@line_index] = @line
|
|
@rerender_all = true
|
|
@rest_height += 1
|
|
end
|
|
end
|
|
alias_method :delete_char, :em_delete
|
|
|
|
private def em_delete_or_list(key)
|
|
if @line.empty? or @byte_pointer < @line.bytesize
|
|
em_delete(key)
|
|
else # show completed list
|
|
result = call_completion_proc
|
|
if result.is_a?(Array)
|
|
complete(result, true)
|
|
end
|
|
end
|
|
end
|
|
alias_method :delete_char_or_list, :em_delete_or_list
|
|
|
|
private def em_yank(key)
|
|
yanked = @kill_ring.yank
|
|
if yanked
|
|
@line = byteinsert(@line, @byte_pointer, yanked)
|
|
yanked_width = calculate_width(yanked)
|
|
@cursor += yanked_width
|
|
@cursor_max += yanked_width
|
|
@byte_pointer += yanked.bytesize
|
|
end
|
|
end
|
|
alias_method :yank, :em_yank
|
|
|
|
private def em_yank_pop(key)
|
|
yanked, prev_yank = @kill_ring.yank_pop
|
|
if yanked
|
|
prev_yank_width = calculate_width(prev_yank)
|
|
@cursor -= prev_yank_width
|
|
@cursor_max -= prev_yank_width
|
|
@byte_pointer -= prev_yank.bytesize
|
|
@line, = byteslice!(@line, @byte_pointer, prev_yank.bytesize)
|
|
@line = byteinsert(@line, @byte_pointer, yanked)
|
|
yanked_width = calculate_width(yanked)
|
|
@cursor += yanked_width
|
|
@cursor_max += yanked_width
|
|
@byte_pointer += yanked.bytesize
|
|
end
|
|
end
|
|
alias_method :yank_pop, :em_yank_pop
|
|
|
|
private def ed_clear_screen(key)
|
|
@cleared = true
|
|
end
|
|
alias_method :clear_screen, :ed_clear_screen
|
|
|
|
private def em_next_word(key)
|
|
if @line.bytesize > @byte_pointer
|
|
byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
|
|
@byte_pointer += byte_size
|
|
@cursor += width
|
|
end
|
|
end
|
|
alias_method :forward_word, :em_next_word
|
|
|
|
private def ed_prev_word(key)
|
|
if @byte_pointer > 0
|
|
byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
|
|
@byte_pointer -= byte_size
|
|
@cursor -= width
|
|
end
|
|
end
|
|
alias_method :backward_word, :ed_prev_word
|
|
|
|
private def em_delete_next_word(key)
|
|
if @line.bytesize > @byte_pointer
|
|
byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
|
|
@line, word = byteslice!(@line, @byte_pointer, byte_size)
|
|
@kill_ring.append(word)
|
|
@cursor_max -= width
|
|
end
|
|
end
|
|
|
|
private def ed_delete_prev_word(key)
|
|
if @byte_pointer > 0
|
|
byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
|
|
@line, word = byteslice!(@line, @byte_pointer - byte_size, byte_size)
|
|
@kill_ring.append(word, true)
|
|
@byte_pointer -= byte_size
|
|
@cursor -= width
|
|
@cursor_max -= width
|
|
end
|
|
end
|
|
|
|
private def ed_transpose_chars(key)
|
|
if @byte_pointer > 0
|
|
if @cursor_max > @cursor
|
|
byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
|
|
mbchar = @line.byteslice(@byte_pointer, byte_size)
|
|
width = Reline::Unicode.get_mbchar_width(mbchar)
|
|
@cursor += width
|
|
@byte_pointer += byte_size
|
|
end
|
|
back1_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
|
|
if (@byte_pointer - back1_byte_size) > 0
|
|
back2_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer - back1_byte_size)
|
|
back2_pointer = @byte_pointer - back1_byte_size - back2_byte_size
|
|
@line, back2_mbchar = byteslice!(@line, back2_pointer, back2_byte_size)
|
|
@line = byteinsert(@line, @byte_pointer - back2_byte_size, back2_mbchar)
|
|
end
|
|
end
|
|
end
|
|
alias_method :transpose_chars, :ed_transpose_chars
|
|
|
|
private def ed_transpose_words(key)
|
|
left_word_start, middle_start, right_word_start, after_start = Reline::Unicode.ed_transpose_words(@line, @byte_pointer)
|
|
before = @line.byteslice(0, left_word_start)
|
|
left_word = @line.byteslice(left_word_start, middle_start - left_word_start)
|
|
middle = @line.byteslice(middle_start, right_word_start - middle_start)
|
|
right_word = @line.byteslice(right_word_start, after_start - right_word_start)
|
|
after = @line.byteslice(after_start, @line.bytesize - after_start)
|
|
return if left_word.empty? or right_word.empty?
|
|
@line = before + right_word + middle + left_word + after
|
|
from_head_to_left_word = before + right_word + middle + left_word
|
|
@byte_pointer = from_head_to_left_word.bytesize
|
|
@cursor = calculate_width(from_head_to_left_word)
|
|
end
|
|
alias_method :transpose_words, :ed_transpose_words
|
|
|
|
private def em_capitol_case(key)
|
|
if @line.bytesize > @byte_pointer
|
|
byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(@line, @byte_pointer)
|
|
before = @line.byteslice(0, @byte_pointer)
|
|
after = @line.byteslice((@byte_pointer + byte_size)..-1)
|
|
@line = before + new_str + after
|
|
@byte_pointer += new_str.bytesize
|
|
@cursor += calculate_width(new_str)
|
|
end
|
|
end
|
|
alias_method :capitalize_word, :em_capitol_case
|
|
|
|
private def em_lower_case(key)
|
|
if @line.bytesize > @byte_pointer
|
|
byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
|
|
part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
|
|
mbchar =~ /[A-Z]/ ? mbchar.downcase : mbchar
|
|
}.join
|
|
rest = @line.byteslice((@byte_pointer + byte_size)..-1)
|
|
@line = @line.byteslice(0, @byte_pointer) + part
|
|
@byte_pointer = @line.bytesize
|
|
@cursor = calculate_width(@line)
|
|
@cursor_max = @cursor + calculate_width(rest)
|
|
@line += rest
|
|
end
|
|
end
|
|
alias_method :downcase_word, :em_lower_case
|
|
|
|
private def em_upper_case(key)
|
|
if @line.bytesize > @byte_pointer
|
|
byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
|
|
part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
|
|
mbchar =~ /[a-z]/ ? mbchar.upcase : mbchar
|
|
}.join
|
|
rest = @line.byteslice((@byte_pointer + byte_size)..-1)
|
|
@line = @line.byteslice(0, @byte_pointer) + part
|
|
@byte_pointer = @line.bytesize
|
|
@cursor = calculate_width(@line)
|
|
@cursor_max = @cursor + calculate_width(rest)
|
|
@line += rest
|
|
end
|
|
end
|
|
alias_method :upcase_word, :em_upper_case
|
|
|
|
private def em_kill_region(key)
|
|
if @byte_pointer > 0
|
|
byte_size, width = Reline::Unicode.em_big_backward_word(@line, @byte_pointer)
|
|
@line, deleted = byteslice!(@line, @byte_pointer - byte_size, byte_size)
|
|
@byte_pointer -= byte_size
|
|
@cursor -= width
|
|
@cursor_max -= width
|
|
@kill_ring.append(deleted, true)
|
|
end
|
|
end
|
|
alias_method :unix_word_rubout, :em_kill_region
|
|
|
|
private def copy_for_vi(text)
|
|
if @config.editing_mode_is?(:vi_insert) or @config.editing_mode_is?(:vi_command)
|
|
@vi_clipboard = text
|
|
end
|
|
end
|
|
|
|
private def vi_insert(key)
|
|
@config.editing_mode = :vi_insert
|
|
end
|
|
|
|
private def vi_add(key)
|
|
@config.editing_mode = :vi_insert
|
|
ed_next_char(key)
|
|
end
|
|
|
|
private def vi_command_mode(key)
|
|
ed_prev_char(key)
|
|
@config.editing_mode = :vi_command
|
|
end
|
|
alias_method :vi_movement_mode, :vi_command_mode
|
|
|
|
private def vi_next_word(key, arg: 1)
|
|
if @line.bytesize > @byte_pointer
|
|
byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer, @drop_terminate_spaces)
|
|
@byte_pointer += byte_size
|
|
@cursor += width
|
|
end
|
|
arg -= 1
|
|
vi_next_word(key, arg: arg) if arg > 0
|
|
end
|
|
|
|
private def vi_prev_word(key, arg: 1)
|
|
if @byte_pointer > 0
|
|
byte_size, width = Reline::Unicode.vi_backward_word(@line, @byte_pointer)
|
|
@byte_pointer -= byte_size
|
|
@cursor -= width
|
|
end
|
|
arg -= 1
|
|
vi_prev_word(key, arg: arg) if arg > 0
|
|
end
|
|
|
|
private def vi_end_word(key, arg: 1, inclusive: false)
|
|
if @line.bytesize > @byte_pointer
|
|
byte_size, width = Reline::Unicode.vi_forward_end_word(@line, @byte_pointer)
|
|
@byte_pointer += byte_size
|
|
@cursor += width
|
|
end
|
|
arg -= 1
|
|
if inclusive and arg.zero?
|
|
byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
|
|
if byte_size > 0
|
|
c = @line.byteslice(@byte_pointer, byte_size)
|
|
width = Reline::Unicode.get_mbchar_width(c)
|
|
@byte_pointer += byte_size
|
|
@cursor += width
|
|
end
|
|
end
|
|
vi_end_word(key, arg: arg) if arg > 0
|
|
end
|
|
|
|
private def vi_next_big_word(key, arg: 1)
|
|
if @line.bytesize > @byte_pointer
|
|
byte_size, width = Reline::Unicode.vi_big_forward_word(@line, @byte_pointer)
|
|
@byte_pointer += byte_size
|
|
@cursor += width
|
|
end
|
|
arg -= 1
|
|
vi_next_big_word(key, arg: arg) if arg > 0
|
|
end
|
|
|
|
private def vi_prev_big_word(key, arg: 1)
|
|
if @byte_pointer > 0
|
|
byte_size, width = Reline::Unicode.vi_big_backward_word(@line, @byte_pointer)
|
|
@byte_pointer -= byte_size
|
|
@cursor -= width
|
|
end
|
|
arg -= 1
|
|
vi_prev_big_word(key, arg: arg) if arg > 0
|
|
end
|
|
|
|
private def vi_end_big_word(key, arg: 1, inclusive: false)
|
|
if @line.bytesize > @byte_pointer
|
|
byte_size, width = Reline::Unicode.vi_big_forward_end_word(@line, @byte_pointer)
|
|
@byte_pointer += byte_size
|
|
@cursor += width
|
|
end
|
|
arg -= 1
|
|
if inclusive and arg.zero?
|
|
byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
|
|
if byte_size > 0
|
|
c = @line.byteslice(@byte_pointer, byte_size)
|
|
width = Reline::Unicode.get_mbchar_width(c)
|
|
@byte_pointer += byte_size
|
|
@cursor += width
|
|
end
|
|
end
|
|
vi_end_big_word(key, arg: arg) if arg > 0
|
|
end
|
|
|
|
private def vi_delete_prev_char(key)
|
|
if @is_multiline and @cursor == 0 and @line_index > 0
|
|
@buffer_of_lines[@line_index] = @line
|
|
@cursor = calculate_width(@buffer_of_lines[@line_index - 1])
|
|
@byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
|
|
@buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
|
|
@line_index -= 1
|
|
@line = @buffer_of_lines[@line_index]
|
|
@cursor_max = calculate_width(@line)
|
|
@rerender_all = true
|
|
elsif @cursor > 0
|
|
byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
|
|
@byte_pointer -= byte_size
|
|
@line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
|
|
width = Reline::Unicode.get_mbchar_width(mbchar)
|
|
@cursor -= width
|
|
@cursor_max -= width
|
|
end
|
|
end
|
|
|
|
private def vi_insert_at_bol(key)
|
|
ed_move_to_beg(key)
|
|
@config.editing_mode = :vi_insert
|
|
end
|
|
|
|
private def vi_add_at_eol(key)
|
|
ed_move_to_end(key)
|
|
@config.editing_mode = :vi_insert
|
|
end
|
|
|
|
private def ed_delete_prev_char(key, arg: 1)
|
|
deleted = ''
|
|
arg.times do
|
|
if @cursor > 0
|
|
byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
|
|
@byte_pointer -= byte_size
|
|
@line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
|
|
deleted.prepend(mbchar)
|
|
width = Reline::Unicode.get_mbchar_width(mbchar)
|
|
@cursor -= width
|
|
@cursor_max -= width
|
|
end
|
|
end
|
|
copy_for_vi(deleted)
|
|
end
|
|
|
|
private def vi_zero(key)
|
|
@byte_pointer = 0
|
|
@cursor = 0
|
|
end
|
|
|
|
private def vi_change_meta(key, arg: 1)
|
|
@drop_terminate_spaces = true
|
|
@waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
|
|
if byte_pointer_diff > 0
|
|
@line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
|
|
elsif byte_pointer_diff < 0
|
|
@line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
|
|
end
|
|
copy_for_vi(cut)
|
|
@cursor += cursor_diff if cursor_diff < 0
|
|
@cursor_max -= cursor_diff.abs
|
|
@byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
|
|
@config.editing_mode = :vi_insert
|
|
@drop_terminate_spaces = false
|
|
}
|
|
@waiting_operator_vi_arg = arg
|
|
end
|
|
|
|
private def vi_delete_meta(key, arg: 1)
|
|
@waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
|
|
if byte_pointer_diff > 0
|
|
@line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
|
|
elsif byte_pointer_diff < 0
|
|
@line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
|
|
end
|
|
copy_for_vi(cut)
|
|
@cursor += cursor_diff if cursor_diff < 0
|
|
@cursor_max -= cursor_diff.abs
|
|
@byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
|
|
}
|
|
@waiting_operator_vi_arg = arg
|
|
end
|
|
|
|
private def vi_yank(key, arg: 1)
|
|
@waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
|
|
if byte_pointer_diff > 0
|
|
cut = @line.byteslice(@byte_pointer, byte_pointer_diff)
|
|
elsif byte_pointer_diff < 0
|
|
cut = @line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
|
|
end
|
|
copy_for_vi(cut)
|
|
}
|
|
@waiting_operator_vi_arg = arg
|
|
end
|
|
|
|
private def vi_list_or_eof(key)
|
|
if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
|
|
@line = nil
|
|
if @buffer_of_lines.size > 1
|
|
scroll_down(@highest_in_all - @first_line_started_from)
|
|
end
|
|
Reline::IOGate.move_cursor_column(0)
|
|
@eof = true
|
|
finish
|
|
else
|
|
ed_newline(key)
|
|
end
|
|
end
|
|
alias_method :vi_end_of_transmission, :vi_list_or_eof
|
|
alias_method :vi_eof_maybe, :vi_list_or_eof
|
|
|
|
private def ed_delete_next_char(key, arg: 1)
|
|
byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
|
|
unless @line.empty? || byte_size == 0
|
|
@line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
|
|
copy_for_vi(mbchar)
|
|
width = Reline::Unicode.get_mbchar_width(mbchar)
|
|
@cursor_max -= width
|
|
if @cursor > 0 and @cursor >= @cursor_max
|
|
byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
|
|
mbchar = @line.byteslice(@byte_pointer - byte_size, byte_size)
|
|
width = Reline::Unicode.get_mbchar_width(mbchar)
|
|
@byte_pointer -= byte_size
|
|
@cursor -= width
|
|
end
|
|
end
|
|
arg -= 1
|
|
ed_delete_next_char(key, arg: arg) if arg > 0
|
|
end
|
|
|
|
private def vi_to_history_line(key)
|
|
if Reline::HISTORY.empty?
|
|
return
|
|
end
|
|
if @history_pointer.nil?
|
|
@history_pointer = 0
|
|
@line_backup_in_history = @line
|
|
@line = Reline::HISTORY[@history_pointer]
|
|
@cursor_max = calculate_width(@line)
|
|
@cursor = 0
|
|
@byte_pointer = 0
|
|
elsif @history_pointer.zero?
|
|
return
|
|
else
|
|
Reline::HISTORY[@history_pointer] = @line
|
|
@history_pointer = 0
|
|
@line = Reline::HISTORY[@history_pointer]
|
|
@cursor_max = calculate_width(@line)
|
|
@cursor = 0
|
|
@byte_pointer = 0
|
|
end
|
|
end
|
|
|
|
private def vi_histedit(key)
|
|
path = Tempfile.open { |fp|
|
|
if @is_multiline
|
|
fp.write whole_lines.join("\n")
|
|
else
|
|
fp.write @line
|
|
end
|
|
fp.path
|
|
}
|
|
system("#{ENV['EDITOR']} #{path}")
|
|
if @is_multiline
|
|
@buffer_of_lines = File.read(path).split("\n")
|
|
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
|
@line_index = 0
|
|
@line = @buffer_of_lines[@line_index]
|
|
@rerender_all = true
|
|
else
|
|
@line = File.read(path)
|
|
end
|
|
finish
|
|
end
|
|
|
|
private def vi_paste_prev(key, arg: 1)
|
|
if @vi_clipboard.size > 0
|
|
@line = byteinsert(@line, @byte_pointer, @vi_clipboard)
|
|
@cursor_max += calculate_width(@vi_clipboard)
|
|
cursor_point = @vi_clipboard.grapheme_clusters[0..-2].join
|
|
@cursor += calculate_width(cursor_point)
|
|
@byte_pointer += cursor_point.bytesize
|
|
end
|
|
arg -= 1
|
|
vi_paste_prev(key, arg: arg) if arg > 0
|
|
end
|
|
|
|
private def vi_paste_next(key, arg: 1)
|
|
if @vi_clipboard.size > 0
|
|
byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
|
|
@line = byteinsert(@line, @byte_pointer + byte_size, @vi_clipboard)
|
|
@cursor_max += calculate_width(@vi_clipboard)
|
|
@cursor += calculate_width(@vi_clipboard)
|
|
@byte_pointer += @vi_clipboard.bytesize
|
|
end
|
|
arg -= 1
|
|
vi_paste_next(key, arg: arg) if arg > 0
|
|
end
|
|
|
|
private def ed_argument_digit(key)
|
|
if @vi_arg.nil?
|
|
unless key.chr.to_i.zero?
|
|
@vi_arg = key.chr.to_i
|
|
end
|
|
else
|
|
@vi_arg = @vi_arg * 10 + key.chr.to_i
|
|
end
|
|
end
|
|
|
|
private def vi_to_column(key, arg: 0)
|
|
@byte_pointer, @cursor = @line.grapheme_clusters.inject([0, 0]) { |total, gc|
|
|
# total has [byte_size, cursor]
|
|
mbchar_width = Reline::Unicode.get_mbchar_width(gc)
|
|
if (total.last + mbchar_width) >= arg
|
|
break total
|
|
elsif (total.last + mbchar_width) >= @cursor_max
|
|
break total
|
|
else
|
|
total = [total.first + gc.bytesize, total.last + mbchar_width]
|
|
total
|
|
end
|
|
}
|
|
end
|
|
|
|
private def vi_replace_char(key, arg: 1)
|
|
@waiting_proc = ->(k) {
|
|
if arg == 1
|
|
byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
|
|
before = @line.byteslice(0, @byte_pointer)
|
|
remaining_point = @byte_pointer + byte_size
|
|
after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
|
|
@line = before + k.chr + after
|
|
@cursor_max = calculate_width(@line)
|
|
@waiting_proc = nil
|
|
elsif arg > 1
|
|
byte_size = 0
|
|
arg.times do
|
|
byte_size += Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer + byte_size)
|
|
end
|
|
before = @line.byteslice(0, @byte_pointer)
|
|
remaining_point = @byte_pointer + byte_size
|
|
after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
|
|
replaced = k.chr * arg
|
|
@line = before + replaced + after
|
|
@byte_pointer += replaced.bytesize
|
|
@cursor += calculate_width(replaced)
|
|
@cursor_max = calculate_width(@line)
|
|
@waiting_proc = nil
|
|
end
|
|
}
|
|
end
|
|
|
|
private def vi_next_char(key, arg: 1, inclusive: false)
|
|
@waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, inclusive: inclusive) }
|
|
end
|
|
|
|
private def vi_to_next_char(key, arg: 1, inclusive: false)
|
|
@waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, need_prev_char: true, inclusive: inclusive) }
|
|
end
|
|
|
|
private def search_next_char(key, arg, need_prev_char: false, inclusive: false)
|
|
if key.instance_of?(String)
|
|
inputed_char = key
|
|
else
|
|
inputed_char = key.chr
|
|
end
|
|
prev_total = nil
|
|
total = nil
|
|
found = false
|
|
@line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
|
|
# total has [byte_size, cursor]
|
|
unless total
|
|
# skip cursor point
|
|
width = Reline::Unicode.get_mbchar_width(mbchar)
|
|
total = [mbchar.bytesize, width]
|
|
else
|
|
if inputed_char == mbchar
|
|
arg -= 1
|
|
if arg.zero?
|
|
found = true
|
|
break
|
|
end
|
|
end
|
|
width = Reline::Unicode.get_mbchar_width(mbchar)
|
|
prev_total = total
|
|
total = [total.first + mbchar.bytesize, total.last + width]
|
|
end
|
|
end
|
|
if not need_prev_char and found and total
|
|
byte_size, width = total
|
|
@byte_pointer += byte_size
|
|
@cursor += width
|
|
elsif need_prev_char and found and prev_total
|
|
byte_size, width = prev_total
|
|
@byte_pointer += byte_size
|
|
@cursor += width
|
|
end
|
|
if inclusive
|
|
byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
|
|
if byte_size > 0
|
|
c = @line.byteslice(@byte_pointer, byte_size)
|
|
width = Reline::Unicode.get_mbchar_width(c)
|
|
@byte_pointer += byte_size
|
|
@cursor += width
|
|
end
|
|
end
|
|
@waiting_proc = nil
|
|
end
|
|
|
|
private def vi_prev_char(key, arg: 1)
|
|
@waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg) }
|
|
end
|
|
|
|
private def vi_to_prev_char(key, arg: 1)
|
|
@waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg, true) }
|
|
end
|
|
|
|
private def search_prev_char(key, arg, need_next_char = false)
|
|
if key.instance_of?(String)
|
|
inputed_char = key
|
|
else
|
|
inputed_char = key.chr
|
|
end
|
|
prev_total = nil
|
|
total = nil
|
|
found = false
|
|
@line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar|
|
|
# total has [byte_size, cursor]
|
|
unless total
|
|
# skip cursor point
|
|
width = Reline::Unicode.get_mbchar_width(mbchar)
|
|
total = [mbchar.bytesize, width]
|
|
else
|
|
if inputed_char == mbchar
|
|
arg -= 1
|
|
if arg.zero?
|
|
found = true
|
|
break
|
|
end
|
|
end
|
|
width = Reline::Unicode.get_mbchar_width(mbchar)
|
|
prev_total = total
|
|
total = [total.first + mbchar.bytesize, total.last + width]
|
|
end
|
|
end
|
|
if not need_next_char and found and total
|
|
byte_size, width = total
|
|
@byte_pointer -= byte_size
|
|
@cursor -= width
|
|
elsif need_next_char and found and prev_total
|
|
byte_size, width = prev_total
|
|
@byte_pointer -= byte_size
|
|
@cursor -= width
|
|
end
|
|
@waiting_proc = nil
|
|
end
|
|
|
|
private def vi_join_lines(key, arg: 1)
|
|
if @is_multiline and @buffer_of_lines.size > @line_index + 1
|
|
@cursor = calculate_width(@line)
|
|
@byte_pointer = @line.bytesize
|
|
@line += ' ' + @buffer_of_lines.delete_at(@line_index + 1).lstrip
|
|
@cursor_max = calculate_width(@line)
|
|
@buffer_of_lines[@line_index] = @line
|
|
@rerender_all = true
|
|
@rest_height += 1
|
|
end
|
|
arg -= 1
|
|
vi_join_lines(key, arg: arg) if arg > 0
|
|
end
|
|
|
|
private def em_set_mark(key)
|
|
@mark_pointer = [@byte_pointer, @line_index]
|
|
end
|
|
alias_method :set_mark, :em_set_mark
|
|
|
|
private def em_exchange_mark(key)
|
|
return unless @mark_pointer
|
|
new_pointer = [@byte_pointer, @line_index]
|
|
@previous_line_index = @line_index
|
|
@byte_pointer, @line_index = @mark_pointer
|
|
@cursor = calculate_width(@line.byteslice(0, @byte_pointer))
|
|
@cursor_max = calculate_width(@line)
|
|
@mark_pointer = new_pointer
|
|
end
|
|
alias_method :exchange_point_and_mark, :em_exchange_mark
|
|
end
|