summaryrefslogtreecommitdiff
path: root/lib/reline/line_editor.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/reline/line_editor.rb')
-rw-r--r--lib/reline/line_editor.rb1357
1 files changed, 0 insertions, 1357 deletions
diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb
deleted file mode 100644
index 7d1030ea0e..0000000000
--- a/lib/reline/line_editor.rb
+++ /dev/null
@@ -1,1357 +0,0 @@
-require 'reline/kill_ring'
-require 'reline/unicode'
-
-require 'tempfile'
-require 'pathname'
-
-class Reline::LineEditor
- # TODO: undo
- attr_reader :line
- attr_accessor :confirm_multiline_termination_proc
- attr_accessor :completion_proc
- attr_accessor :dig_perfect_match_proc
- attr_writer :retrieve_completion_block
-
- ARGUMENTABLE = %i{
- ed_delete_next_char
- ed_delete_prev_char
- ed_delete_prev_word
- ed_next_char
- ed_next_history
- ed_next_line#
- ed_prev_char
- ed_prev_history
- ed_prev_line#
- ed_prev_word
- ed_quoted_insert
- vi_to_column
- vi_next_word
- vi_prev_word
- vi_end_word
- vi_next_big_word
- vi_prev_big_word
- vi_end_big_word
- vi_next_char
- vi_delete_meta
- vi_paste_prev
- vi_paste_next
- vi_replace_char
- }
-
- VI_OPERATORS = %i{
- vi_change_meta
- vi_delete_meta
- vi_yank
- }
-
- 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
- PERFECT_MATCH = :perfect_match
- end
-
- CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer)
- MenuInfo = Struct.new('MenuInfo', :target, :list)
-
- def initialize(config, prompt, encoding = Encoding.default_external)
- @config = config
- @prompt = prompt
- @prompt_width = calculate_width(@prompt)
- @cursor = 0
- @cursor_max = 0
- @byte_pointer = 0
- @encoding = encoding
- @buffer_of_lines = [String.new(encoding: @encoding)]
- @line_index = 0
- @previous_line_index = nil
- @line = @buffer_of_lines[0]
- @is_multiline = false
- @finished = false
- @cleared = false
- @rerender_all = false
- @is_confirm_multiline_termination = false
- @history_pointer = nil
- @line_backup_in_history = nil
- @kill_ring = Reline::KillRing.new
- @vi_clipboard = ''
- @vi_arg = nil
- @multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
- @meta_prefix = false
- @waiting_proc = nil
- @waiting_operator_proc = nil
- @completion_journey_data = nil
- @completion_state = CompletionState::NORMAL
- @perfect_matched = nil
- @screen_size = Reline.get_screen_size
- @first_line_started_from = 0
- @move_up = 0
- @started_from = 0
- @highest_in_this = 1
- @highest_in_all = 1
- @menu_info = nil
- end
-
- def multiline_on
- @is_multiline = true
- end
-
- def multiline_off
- @is_multiline = false
- 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
- end
-
- private def calculate_height_by_width(width)
- return 1 if width.zero?
- height = 1
- max_width = @screen_size.last
- while width > max_width * height
- height += 1
- end
- height += 1 if (width % max_width).zero?
- height
- end
-
- private def split_by_width(str, max_width)
- lines = [String.new(encoding: @encoding)]
- width = 0
- str.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
- mbchar_width = Reline::Unicode.get_mbchar_width(gc)
- width += mbchar_width
- if width > max_width
- width = mbchar_width
- lines << String.new(encoding: @encoding)
- end
- lines.last << gc
- end
- # The cursor moves to next line in first
- lines << String.new(encoding: @encoding) if width == max_width
- lines
- end
-
- private def scroll_down(val)
- if val <= @rest_height
- Reline.move_cursor_down(val)
- @rest_height -= val
- else
- Reline.move_cursor_down(@rest_height)
- Reline.scroll_down(val - @rest_height)
- @rest_height = 0
- end
- end
-
- private def move_cursor_up(val)
- if val > 0
- Reline.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.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
- @cursor_max = calculate_width(line)
- new_cursor = 0
- new_byte_pointer = 0
- height = 1
- max_width = @screen_size.last
- @line.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
- mbchar_width = Reline::Unicode.get_mbchar_width(gc)
- now = new_cursor + mbchar_width
- if now > @cursor_max or now > @cursor
- break
- end
- new_cursor += mbchar_width
- if new_cursor > max_width * height
- height += 1
- end
- new_byte_pointer += gc.bytesize
- end
- @started_from = height - 1
- @cursor = new_cursor
- @byte_pointer = new_byte_pointer
- end
-
- def rerender # TODO: support physical and logical lines
- @rest_height ||= (Reline.get_screen_size.first - 1) - Reline.cursor_pos.y
- if @menu_info
- puts
- @menu_info.list.each do |item|
- puts item
- end
- @menu_info = nil
- end
- return if @line.nil?
- if @vi_arg
- prompt = "(arg: #{@vi_arg}) "
- prompt_width = calculate_width(prompt)
- else
- prompt = @prompt
- prompt_width = @prompt_width
- end
- if @cleared
- Reline.clear_screen
- @cleared = false
- back = 0
- @buffer_of_lines.each_with_index do |line, index|
- line = @line if index == @line_index
- height = render_partial(prompt, prompt_width, line, false)
- 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.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
- return
- end
- # FIXME: end of logical line sometimes breaks
- if @previous_line_index
- previous_line = @line
- all_height = @buffer_of_lines.inject(0) { |result, line|
- result + calculate_height_by_width(@prompt_width + calculate_width(line))
- }
- diff = all_height - @highest_in_all
- if diff > 0
- @highest_in_all = all_height
- scroll_down(diff)
- move_cursor_up(@first_line_started_from + @started_from + diff)
- back = 0
- @buffer_of_lines.each_with_index do |line, index|
- line = @line if index == @previous_line_index
- height = render_partial(prompt, prompt_width, line, false)
- if index < (@buffer_of_lines.size - 1)
- move_cursor_down(height)
- back += height
- end
- end
- move_cursor_up(back)
- else
- render_partial(prompt, prompt_width, previous_line)
- move_cursor_up(@first_line_started_from + @started_from)
- end
- @buffer_of_lines[@previous_line_index] = @line
- @line = @buffer_of_lines[@line_index]
- @first_line_started_from =
- if @line_index.zero?
- 0
- else
- @buffer_of_lines[0..(@line_index - 1)].inject(0) { |result, line|
- result + calculate_height_by_width(@prompt_width + calculate_width(line))
- }
- end
- move_cursor_down(@first_line_started_from)
- calculate_nearest_cursor
- @highest_in_this = calculate_height_by_width(@prompt_width + @cursor_max)
- @previous_line_index = nil
- elsif @rerender_all
- move_cursor_up(@first_line_started_from + @started_from)
- Reline.move_cursor_column(0)
- back = 0
- @buffer_of_lines.each do |line|
- width = prompt_width + calculate_width(line)
- height = calculate_height_by_width(width)
- back += height
- end
- if back > @highest_in_all
- scroll_down(back)
- move_cursor_up(back)
- elsif back < @highest_in_all
- scroll_down(back)
- Reline.erase_after_cursor
- (@highest_in_all - back).times do
- scroll_down(1)
- Reline.erase_after_cursor
- end
- move_cursor_up(@highest_in_all)
- end
- @buffer_of_lines.each_with_index do |line, index|
- height = render_partial(prompt, prompt_width, line, false)
- if index < (@buffer_of_lines.size - 1)
- move_cursor_down(1)
- end
- end
- move_cursor_up(back - 1)
- @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
- @buffer_of_lines[0..(@line_index - 1)].inject(0) { |result, line|
- result + calculate_height_by_width(@prompt_width + calculate_width(line))
- }
- end
- move_cursor_down(@first_line_started_from)
- @rerender_all = false
- end
- render_partial(prompt, prompt_width, @line) if !@is_multiline or !finished?
- if @is_multiline and finished?
- scroll_down(1) unless @buffer_of_lines.last.empty?
- Reline.move_cursor_column(0)
- Reline.erase_after_cursor
- end
- end
-
- private def render_partial(prompt, prompt_width, line_to_render, with_control = true)
- whole_line = prompt + (line_to_render.nil? ? '' : line_to_render)
- visual_lines = split_by_width(whole_line, @screen_size.last)
- if with_control
- if visual_lines.size > @highest_in_this
- diff = visual_lines.size - @highest_in_this
- scroll_down(diff)
- @highest_in_all += diff
- @highest_in_this = visual_lines.size
- move_cursor_up(1)
- end
- move_cursor_up(@started_from)
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
- end
- visual_lines.each_with_index do |line, index|
- Reline.move_cursor_column(0)
- escaped_print line
- Reline.erase_after_cursor
- move_cursor_down(1) if index < (visual_lines.size - 1)
- end
- if with_control
- if finished?
- puts
- else
- move_cursor_up((visual_lines.size - 1) - @started_from)
- Reline.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
- end
- end
- visual_lines.size
- end
-
- def editing_mode
- @config.editing_mode
- end
-
- private def escaped_print(str)
- print str.chars.map { |gr|
- escaped = Reline::Unicode::EscapedPairs[gr.ord]
- if escaped
- escaped
- else
- gr
- end
- }.join
- 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.(@line, @byte_pointer)
- list = list.select { |i| i&.start_with?(target) }
- if is_menu
- menu(target, list)
- return nil
- end
- completed = list.inject { |memo, item|
- memo_mbchars = memo.unicode_normalize.grapheme_clusters
- item_mbchars = item.unicode_normalize.grapheme_clusters
- size = [memo_mbchars.size, item_mbchars.size].min
- result = ''
- size.times do |i|
- if memo_mbchars[i] == item_mbchars[i]
- result << memo_mbchars[i]
- else
- break
- end
- end
- result
- }
- [target, preposing, completed, postposing]
- end
-
- private def complete(list)
- case @completion_state
- when CompletionState::NORMAL, CompletionState::JOURNEY
- @completion_state = CompletionState::COMPLETION
- when CompletionState::PERFECT_MATCH
- @dig_perfect_match_proc&.(@perfect_matched)
- end
- is_menu = (@completion_state == CompletionState::MENU)
- result = complete_internal_proc(list, is_menu)
- return if result.nil?
- target, preposing, completed, postposing = result
- return if completed.nil?
- if target <= completed and (@completion_state == CompletionState::COMPLETION or @completion_state == CompletionState::PERFECT_MATCH)
- @completion_state = CompletionState::MENU
- if list.include?(completed)
- @completion_state = CompletionState::PERFECT_MATCH
- @perfect_matched = completed
- end
- if target < completed
- @line = preposing + completed + postposing
- line_to_pointer = preposing + completed
- @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
- @completion_state = CompletionState::JOURNEY
- result = @retrieve_completion_block.(@line, @byte_pointer)
- 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
- block.()
- 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
- @waiting_proc = proc { |key|
- old_cursor, old_byte_pointer = @cursor, @byte_pointer
- old_waiting_proc.(key)
- 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)
- }
- end
- else
- # Ignores operator when not motion is given.
- block.()
- end
- @waiting_operator_proc = nil
- else
- block.()
- end
- end
-
- private def process_key(key, method_symbol, method_obj)
- if @vi_arg
- if key.chr =~ /[0-9]/
- ed_argument_digit(key)
- else
- if ARGUMENTABLE.include?(method_symbol) and method_obj
- run_for_operators(key, method_symbol) do
- method_obj.(key, arg: @vi_arg)
- end
- elsif @waiting_proc
- @waiting_proc.(key)
- elsif method_obj
- method_obj.(key)
- else
- ed_insert(key)
- 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
- method_obj.(key)
- else
- run_for_operators(key, method_symbol) do
- method_obj.(key)
- end
- end
- @kill_ring.process
- else
- ed_insert(key)
- end
- end
-
- private def normal_char(key)
- method_symbol = method_obj = nil
- @multibyte_buffer << key
- if @multibyte_buffer.size > 1
- if @multibyte_buffer.dup.force_encoding(@encoding).valid_encoding?
- key = @multibyte_buffer.dup.force_encoding(@encoding)
- @multibyte_buffer.clear
- else
- # invalid
- return
- end
- else # single byte
- return if key >= 128 # maybe, first byte of multi byte
- if @meta_prefix
- key |= 0b10000000 if key.nobits?(0b10000000)
- @meta_prefix = false
- end
- method_symbol = @config.editing_mode.get_method(key)
- if key.allbits?(0b10000000) and method_symbol == :ed_unassigned
- return # This is unknown input
- end
- if method_symbol and respond_to?(method_symbol, true)
- method_obj = method(method_symbol)
- end
- @multibyte_buffer.clear
- end
- process_key(key, method_symbol, method_obj)
- 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)
- completion_occurs = false
- if @config.editing_mode_is?(:emacs, :vi_insert) and key == "\C-i".ord
- result = @completion_proc&.(@line)
- if result.is_a?(Array)
- completion_occurs = true
- complete(result)
- end
- elsif @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key)
- result = @completion_proc&.(@line)
- if result.is_a?(Array)
- completion_occurs = true
- move_completed_list(result, "\C-p".ord == key ? :up : :down)
- end
- elsif @config.editing_mode_is?(:emacs) and key == "\e".ord # meta key
- if @meta_prefix
- # escape twice
- @meta_prefix = false
- @kill_ring.process
- else
- @meta_prefix = true
- end
- elsif @config.editing_mode_is?(:vi_command) and key == "\e".ord
- # suppress ^[ when command_mode
- elsif Symbol === key and respond_to?(key, true)
- process_key(key, key, method(key))
- else
- normal_char(key)
- end
- unless completion_occurs
- @completion_state = CompletionState::NORMAL
- end
- if @is_confirm_multiline_termination and @confirm_multiline_termination_proc
- @is_confirm_multiline_termination = false
- temp_buffer = @buffer_of_lines.dup
- if @previous_line_index and @line_index == (@buffer_of_lines.size - 1)
- temp_buffer[@previous_line_index] = @line
- end
- finish if @confirm_multiline_termination_proc.(temp_buffer.join("\n"))
- end
- end
-
- def whole_buffer
- temp_lines = @buffer_of_lines.dup
- temp_lines[@line_index] = @line
- if @buffer_of_lines.size == 1 and @line.nil?
- nil
- else
- temp_lines.join("\n")
- end
- end
-
- def finished?
- @finished
- end
-
- def finish
- @finished = 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)
- str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |width, gc|
- width + Reline::Unicode.get_mbchar_width(gc)
- }
- end
-
- private def ed_insert(key)
- if key.instance_of?(String)
- width = Reline::Unicode.get_mbchar_width(key)
- if @cursor == @cursor_max
- @line += key
- else
- @line = byteinsert(@line, @byte_pointer, key)
- end
- @byte_pointer += key.bytesize
- @cursor += width
- @cursor_max += width
- else
- if @cursor == @cursor_max
- @line += key.chr
- else
- @line = byteinsert(@line, @byte_pointer, key.chr)
- end
- width = Reline::Unicode.get_mbchar_width(key.chr)
- @byte_pointer += 1
- @cursor += width
- @cursor_max += width
- end
- end
- alias_method :ed_digit, :ed_insert
-
- private def ed_quoted_insert(str, arg: 1)
- @waiting_proc = proc { |key|
- arg.times do
- ed_insert(key)
- end
- @waiting_proc = nil
- }
- end
-
- 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
- end
- arg -= 1
- ed_next_char(key, arg: arg) if arg > 0
- end
-
- 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
- end
- arg -= 1
- ed_prev_char(key, arg: arg) if arg > 0
- end
-
- private def ed_move_to_beg(key)
- @byte_pointer, @cursor = Reline::Unicode.ed_move_to_begin(@line)
- end
-
- 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
-
- 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")
- @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")
- @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)
- @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")
- @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)
- @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)
- if @is_multiline
- if @config.editing_mode_is?(:vi_command)
- if @line_index < (@buffer_of_lines.size - 1)
- ed_next_history(key)
- else
- @is_confirm_multiline_termination = true
- end
- else
- next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
- cursor_line = @line.byteslice(0, @byte_pointer)
- insert_new_line(cursor_line, next_line)
- if @line_index == (@buffer_of_lines.size - 1)
- @is_confirm_multiline_termination = true
- end
- end
- return
- end
- if @history_pointer
- Reline::HISTORY[@history_pointer] = @line
- @history_pointer = nil
- end
- finish
- 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
-
- 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)
- 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
-
- private def em_delete_or_list(key)
- if @line.empty?
- @line = nil
- 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)
- end
- end
-
- 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
-
- 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
-
- private def ed_clear_screen(key)
- @cleared = true
- end
-
- 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
-
- 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
-
- 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
-
- 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
-
- 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
-
- 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
-
- 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)
- end
- end
-
- 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
-
- private def vi_next_word(key, arg: 1)
- if @line.bytesize > @byte_pointer
- byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer)
- @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)
- 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
- 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)
- 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
- 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 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)
- end
-
- private def vi_delete_meta(key)
- @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
- }
- end
-
- private def vi_yank(key)
- end
-
- private def vi_end_of_transmission(key)
- if @line.empty?
- @line = nil
- finish
- end
- end
-
- private def vi_list_or_eof(key)
- if @line.empty?
- @line = nil
- finish
- else
- # TODO: list
- end
- end
-
- private def ed_delete_next_char(key, arg: 1)
- unless @line.empty?
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
- copy_for_vi(mbchar)
- width = Reline::Unicode.get_mbchar_width(mbchar)
- @cursor_max -= width # FIXME
- if @cursor > 0 and @cursor >= @cursor_max
- @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|
- fp.write @line
- fp.path
- }
- system("#{ENV['EDITOR']} #{path}")
- @line = Pathname.new(path).read
- 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 = ->(key) {
- 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.size - remaining_point)
- @line = before + key.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.size - remaining_point)
- replaced = key.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)
- @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg) }
- end
-
- private def search_next_char(key, arg)
- if key.instance_of?(String)
- inputed_char = key
- else
- inputed_char = key.chr
- end
- 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)
- total = [total.first + mbchar.bytesize, total.last + width]
- end
- end
- if found and total
- byte_size, width = total
- @byte_pointer += byte_size
- @cursor += width
- end
- @waiting_proc = nil
- end
-end