summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortomoya ishida <tomoyapenguin@gmail.com>2024-04-16 23:06:18 +0900
committergit <svn-admin@ruby-lang.org>2024-04-16 14:06:22 +0000
commit79df0f135e9179b64f6fa29d5131f586511a088b (patch)
tree60ef683a1a82e5ee5c4dff031b7c784af26fda59
parent982dfa07936001013aafedd8aa656c69e268ebc3 (diff)
[ruby/reline] Refactor history move and history search
(https://github.com/ruby/reline/pull/651) https://github.com/ruby/reline/commit/90e43e01d4
-rw-r--r--lib/reline/line_editor.rb399
1 files changed, 134 insertions, 265 deletions
diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb
index 36bd6d435f..c84a78a06c 100644
--- a/lib/reline/line_editor.rb
+++ b/lib/reline/line_editor.rb
@@ -1543,118 +1543,89 @@ class Reline::LineEditor
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'
+ private def generate_searcher(search_key)
+ search_word = String.new(encoding: @encoding)
+ multibyte_buf = String.new(encoding: 'ASCII-8BIT')
+ last_hit = nil
+ hit_pointer = nil
+ lambda do |key|
+ search_again = false
+ case key
+ 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 search_key == key
+ 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
- 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
+ hit = nil
+ if not search_word.empty? and @line_backup_in_history&.include?(search_word)
+ hit_pointer = Reline::HISTORY.size
+ 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
- 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
+ if @history_pointer
+ case search_key
when "\C-r".ord
history_pointer_base = 0
- history = Reline::HISTORY[0..@history_pointer]
+ history = Reline::HISTORY[0..(@history_pointer - 1)]
when "\C-s".ord
- history_pointer_base = @history_pointer
- history = Reline::HISTORY[@history_pointer..-1]
+ history_pointer_base = @history_pointer + 1
+ history = Reline::HISTORY[(@history_pointer + 1)..-1]
end
else
history_pointer_base = 0
history = Reline::HISTORY
end
- case prev_search_key
+ elsif @history_pointer
+ case search_key
when "\C-r".ord
- hit_index = history.rindex { |item|
- item.include?(search_word)
- }
+ history_pointer_base = 0
+ history = Reline::HISTORY[0..@history_pointer]
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]
+ 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
+ case search_key
when "\C-r".ord
- prompt_name = 'reverse-i-search'
+ hit_index = history.rindex { |item|
+ item.include?(search_word)
+ }
when "\C-s".ord
- prompt_name = 'i-search'
+ hit_index = history.index { |item|
+ item.include?(search_word)
+ }
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
- @byte_pointer = current_line.bytesize
- @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
- else
- @buffer_of_lines = [hit]
- @byte_pointer = hit.bytesize
- @searching_prompt = "(%s)`%s': %s" % [prompt_name, search_word, hit]
- end
- last_hit = hit
- else
- if @is_multiline
- @searching_prompt = "(failed %s)`%s'" % [prompt_name, search_word]
- else
- @searching_prompt = "(failed %s)`%s': %s" % [prompt_name, search_word, last_hit]
- end
+ if hit_index
+ hit_pointer = history_pointer_base + hit_index
+ hit = Reline::HISTORY[hit_pointer]
end
end
+ case search_key
+ when "\C-r".ord
+ prompt_name = 'reverse-i-search'
+ when "\C-s".ord
+ prompt_name = 'i-search'
+ end
+ prompt_name = "failed #{prompt_name}" unless hit
+ last_hit = hit if hit_pointer
+ [search_word, prompt_name, hit_pointer, hit, last_hit]
end
end
@@ -1666,8 +1637,7 @@ class Reline::LineEditor
@line_backup_in_history = current_line
end
end
- searcher = generate_searcher
- searcher.resume(key)
+ searcher = generate_searcher(key)
@searching_prompt = "(reverse-i-search)`': "
termination_keys = ["\C-j".ord]
termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators
@@ -1689,7 +1659,6 @@ class Reline::LineEditor
@searching_prompt = nil
@waiting_proc = nil
@byte_pointer = 0
- searcher.resume(-1)
when "\C-g".ord
if @is_multiline
@buffer_of_lines = @line_backup_in_history.split("\n")
@@ -1698,15 +1667,21 @@ class Reline::LineEditor
else
@buffer_of_lines = [@line_backup_in_history]
end
- @history_pointer = nil
+ move_history(nil, line: :end, cursor: :end, save_buffer: false)
@searching_prompt = nil
@waiting_proc = nil
- @line_backup_in_history = nil
@byte_pointer = 0
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)
+ search_word, prompt_name, hit_pointer, hit, last_hit = searcher.call(k)
+ Reline.last_incremental_search = search_word
+ if @is_multiline
+ @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
+ else
+ @searching_prompt = "(%s)`%s': %s" % [prompt_name, search_word, hit || last_hit]
+ end
+ move_history(hit_pointer, line: :end, cursor: :end, save_buffer: false) if hit_pointer
else
if @history_pointer
line = Reline::HISTORY[@history_pointer]
@@ -1725,7 +1700,6 @@ class Reline::LineEditor
@searching_prompt = nil
@waiting_proc = nil
@byte_pointer = 0
- searcher.resume(-1)
end
end
}
@@ -1741,104 +1715,70 @@ class Reline::LineEditor
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 = current_line.slice(0, @byte_pointer)
- if @history_pointer.nil?
- return if not current_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
- cursor = current_byte_pointer_cursor
- 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
- calculate_nearest_cursor(cursor)
- else
- @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
- calculate_nearest_cursor(cursor)
+ private def search_history(prefix, pointer_range)
+ pointer_range.each do |pointer|
+ lines = Reline::HISTORY[pointer].split("\n")
+ lines.each_with_index do |line, index|
+ return [pointer, index] if line.start_with?(prefix)
+ end
end
+ nil
+ end
+
+ private def ed_search_prev_history(key, arg: 1)
+ substr = current_line.byteslice(0, @byte_pointer)
+ return if @history_pointer == 0
+ return if @history_pointer.nil? && substr.empty? && !current_line.empty?
+
+ history_range = 0...(@history_pointer || Reline::HISTORY.size)
+ h_pointer, line_index = search_history(substr, history_range.reverse_each)
+ return unless h_pointer
+ move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer)
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 = current_line.slice(0, @byte_pointer)
- if @history_pointer.nil?
- return
- elsif @history_pointer == (Reline::HISTORY.size - 1) and not substr.empty?
- return
- end
+ substr = current_line.byteslice(0, @byte_pointer)
+ return if @history_pointer.nil?
+
+ history_range = @history_pointer + 1...Reline::HISTORY.size
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?
- }
+ h_pointer, line_index = search_history(substr, history_range)
+ return if h_pointer.nil? and not substr.empty?
+
+ move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer)
+ arg -= 1
+ ed_search_next_history(key, arg: arg) if arg > 0
+ end
+ alias_method :history_search_forward, :ed_search_next_history
+
+ private def move_history(history_pointer, line:, cursor:, save_buffer: true)
+ history_pointer ||= Reline::HISTORY.size
+ return if history_pointer < 0 || history_pointer > Reline::HISTORY.size
+ old_history_pointer = @history_pointer || Reline::HISTORY.size
+ if old_history_pointer == Reline::HISTORY.size
+ @line_backup_in_history = save_buffer ? whole_buffer : ''
else
- h_pointer = history.index { |l|
- l.start_with?(substr)
- }
+ Reline::HISTORY[old_history_pointer] = whole_buffer if save_buffer
+ end
+ if history_pointer == Reline::HISTORY.size
+ buf = @line_backup_in_history
+ @history_pointer = @line_backup_in_history = nil
+ else
+ buf = Reline::HISTORY[history_pointer]
+ @history_pointer = history_pointer
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
- @byte_pointer = 0
- else
- cursor = current_byte_pointer_cursor
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
- @line_index = line_no
- calculate_nearest_cursor(cursor)
- end
+ @buffer_of_lines = buf.split("\n")
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
+ @line_index = line == :start ? 0 : line == :end ? @buffer_of_lines.size - 1 : line
else
- if @history_pointer.nil? and substr.empty?
- set_current_line('', 0)
- else
- set_current_line(Reline::HISTORY[@history_pointer])
- end
+ @buffer_of_lines = [buf]
end
- arg -= 1
- ed_search_next_history(key, arg: arg) if arg > 0
+ @byte_pointer = cursor == :start ? 0 : cursor == :end ? current_line.bytesize : cursor
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
@@ -1847,43 +1787,11 @@ class Reline::LineEditor
calculate_nearest_cursor(cursor)
return
end
- if Reline::HISTORY.empty?
- return
- end
- if @history_pointer.nil?
- @history_pointer = Reline::HISTORY.size - 1
- cursor = current_byte_pointer_cursor
- 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
- calculate_nearest_cursor(cursor)
- else
- @line_backup_in_history = whole_buffer
- @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
- calculate_nearest_cursor(cursor)
- 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
- else
- Reline::HISTORY[@history_pointer] = whole_buffer
- @history_pointer -= 1
- @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
- end
- end
- if @config.editing_mode_is?(:emacs, :vi_insert)
- @byte_pointer = current_line.bytesize
- elsif @config.editing_mode_is?(:vi_command)
- @byte_pointer = 0
- end
+ move_history(
+ (@history_pointer || Reline::HISTORY.size) - 1,
+ line: :end,
+ cursor: @config.editing_mode_is?(:vi_command) ? :start : :end,
+ )
arg -= 1
ed_prev_history(key, arg: arg) if arg > 0
end
@@ -1896,36 +1804,11 @@ class Reline::LineEditor
calculate_nearest_cursor(cursor)
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
- else
- @history_pointer = nil
- @buffer_of_lines = [@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
- else
- Reline::HISTORY[@history_pointer] = whole_buffer
- @history_pointer += 1
- @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
- end
- end
- if @config.editing_mode_is?(:emacs, :vi_insert)
- @byte_pointer = current_line.bytesize
- elsif @config.editing_mode_is?(:vi_command)
- @byte_pointer = 0
- end
+ move_history(
+ (@history_pointer || Reline::HISTORY.size) + 1,
+ line: :start,
+ cursor: @config.editing_mode_is?(:vi_command) ? :start : :end,
+ )
arg -= 1
ed_next_history(key, arg: arg) if arg > 0
end
@@ -1956,10 +1839,6 @@ class Reline::LineEditor
end
end
else
- if @history_pointer
- Reline::HISTORY[@history_pointer] = whole_buffer
- @history_pointer = nil
- end
finish
end
end
@@ -2409,17 +2288,7 @@ class Reline::LineEditor
if Reline::HISTORY.empty?
return
end
- if @history_pointer.nil?
- @history_pointer = 0
- @line_backup_in_history = current_line
- set_current_line(Reline::HISTORY[@history_pointer], 0)
- elsif @history_pointer.zero?
- return
- else
- Reline::HISTORY[@history_pointer] = current_line
- @history_pointer = 0
- set_current_line(Reline::HISTORY[@history_pointer], 0)
- end
+ move_history(0, line: :start, cursor: :start)
end
private def vi_histedit(key)