diff --git a/lib/reline/key_actor/emacs.rb b/lib/reline/key_actor/emacs.rb index 134f424a2c..ab5c214600 100644 --- a/lib/reline/key_actor/emacs.rb +++ b/lib/reline/key_actor/emacs.rb @@ -39,7 +39,7 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base # 18 ^R :ed_search_prev_history, # 19 ^S - :ed_ignore, + :ed_search_next_history, # 20 ^T :ed_transpose_chars, # 21 ^U diff --git a/lib/reline/key_actor/vi_insert.rb b/lib/reline/key_actor/vi_insert.rb index f5b8a01982..06e94a9c30 100644 --- a/lib/reline/key_actor/vi_insert.rb +++ b/lib/reline/key_actor/vi_insert.rb @@ -39,7 +39,7 @@ class Reline::KeyActor::ViInsert < Reline::KeyActor::Base # 18 ^R :ed_search_prev_history, # 19 ^S - :ed_ignore, + :ed_search_next_history, # 20 ^T :ed_insert, # 21 ^U diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index 20d966f6dc..c74db5286d 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -1181,18 +1181,18 @@ class Reline::LineEditor end alias_method :end_of_line, :ed_move_to_end - private def ed_search_prev_history(key) - unless @history_pointer - if @is_multiline - @line_backup_in_history = whole_buffer - else - @line_backup_in_history = @line - end - end - searcher = Fiber.new do + 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 @@ -1206,8 +1206,9 @@ class Reline::LineEditor grapheme_clusters.pop search_word = grapheme_clusters.join end - when "\C-r".ord - search_again = true + 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? @@ -1224,24 +1225,53 @@ class Reline::LineEditor if search_word.empty? and Reline.last_incremental_search search_word = Reline.last_incremental_search end - if @history_pointer - history = Reline::HISTORY[0..(@history_pointer - 1)] + if @history_pointer # TODO + 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 - history = Reline::HISTORY[0..@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 - hit_index = history.rindex { |item| - item.include?(search_word) - } + 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 = 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") @@ -1249,23 +1279,34 @@ class Reline::LineEditor @line_index = @buffer_of_lines.size - 1 @line = @buffer_of_lines.last @rerender_all = true - @searching_prompt = "(reverse-i-search)`%s'" % [search_word] + @searching_prompt = "(%s)`%s'" % [prompt_name, search_word] else @line = hit - @searching_prompt = "(reverse-i-search)`%s': %s" % [search_word, 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 reverse-i-search)`%s'" % [search_word] + @searching_prompt = "(failed %s)`%s'" % [prompt_name, search_word] else - @searching_prompt = "(failed reverse-i-search)`%s': %s" % [search_word, last_hit] + @searching_prompt = "(failed %s)`%s': %s" % [prompt_name, search_word, last_hit] end end end end - searcher.resume + end + + private def 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)`': " @waiting_proc = ->(k) { case k @@ -1308,7 +1349,7 @@ class Reline::LineEditor @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 + 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 @@ -1337,8 +1378,15 @@ class Reline::LineEditor } end - private def ed_search_next_history(key) + private def ed_search_prev_history(key) + search_history(key) end + alias_method :reverse_search_history, :ed_search_prev_history + + private def ed_search_next_history(key) + search_history(key) + end + alias_method :forward_search_history, :ed_search_next_history private def ed_prev_history(key, arg: 1) if @is_multiline and @line_index > 0 diff --git a/test/reline/test_key_actor_emacs.rb b/test/reline/test_key_actor_emacs.rb index 7ffa28ec87..5e5be0c15c 100644 --- a/test/reline/test_key_actor_emacs.rb +++ b/test/reline/test_key_actor_emacs.rb @@ -6,6 +6,7 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase @prompt = '> ' @config = Reline::Config.new # Emacs mode is default Reline::HISTORY.instance_variable_set(:@config, @config) + Reline::HISTORY.clear @encoding = (RELINE_TEST_ENCODING rescue Encoding.default_external) @line_editor = Reline::LineEditor.new(@config) @line_editor.reset(@prompt, @encoding) @@ -1592,6 +1593,33 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase assert_cursor_max(0) end + def test_search_history_to_front + Reline::HISTORY.concat([ + '1235', # old + '12aa', + '1234' # new + ]) + assert_line('') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + input_keys("\C-s123") + assert_line('1235') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) # doesn't determine yet + input_keys("\C-ha") + assert_line('12aa') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + input_keys("\C-h3") + assert_line('1234') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + end + def test_search_history_to_back_in_the_middle_of_histories Reline::HISTORY.concat([ '1235', # old