diff --git a/share/functions/fish_vi_key_bindings.fish b/share/functions/fish_vi_key_bindings.fish index e4eb1fe78..c701cf6e6 100644 --- a/share/functions/fish_vi_key_bindings.fish +++ b/share/functions/fish_vi_key_bindings.fish @@ -207,8 +207,10 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish' bind f forward-jump bind F backward-jump - bind t forward-jump and backward-char - bind T backward-jump and forward-char + bind t forward-jump-till + bind T backward-jump-till + bind ';' repeat-jump + bind , repeat-jump-reverse # in emacs yank means paste bind p yank @@ -245,9 +247,9 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish' bind $argv visual o swap-selection-start-stop force-repaint bind $argv visual f forward-jump - bind $argv visual t forward-jump backward-char + bind $argv visual t forward-jump-till bind $argv visual F backward-jump - bind $argv visual T backward-jump forward-char + bind $argv visual T backward-jump-till for key in $eol_keys bind $argv visual $key end-of-line diff --git a/src/input.cpp b/src/input.cpp index 0af4ba0d4..e93a267a4 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -120,6 +120,10 @@ static const input_function_metadata_t input_function_metadata[] = { {R_KILL_SELECTION, L"kill-selection"}, {R_FORWARD_JUMP, L"forward-jump"}, {R_BACKWARD_JUMP, L"backward-jump"}, + {R_FORWARD_JUMP_TILL, L"forward-jump-till"}, + {R_BACKWARD_JUMP_TILL, L"backward-jump-till"}, + {R_REPEAT_JUMP, L"repeat-jump"}, + {R_REVERSE_REPEAT_JUMP, L"repeat-jump-reverse"}, {R_AND, L"and"}, {R_CANCEL, L"cancel"}}; @@ -176,7 +180,15 @@ void input_set_bind_mode(const wcstring &bm) { /// Returns the arity of a given input function. static int input_function_arity(int function) { - return (function == R_FORWARD_JUMP || function == R_BACKWARD_JUMP) ? 1 : 0; + switch (function) { + case R_FORWARD_JUMP: + case R_BACKWARD_JUMP: + case R_FORWARD_JUMP_TILL: + case R_BACKWARD_JUMP_TILL: + return 1; + default: + return 0; + } } /// Sets the return status of the most recently executed input function. diff --git a/src/input_common.h b/src/input_common.h index 617ff4157..ef4550cba 100644 --- a/src/input_common.h +++ b/src/input_common.h @@ -67,15 +67,19 @@ enum { R_KILL_SELECTION, R_FORWARD_JUMP, R_BACKWARD_JUMP, + R_FORWARD_JUMP_TILL, + R_BACKWARD_JUMP_TILL, R_AND, R_CANCEL, + R_REPEAT_JUMP, + R_REVERSE_REPEAT_JUMP, R_TIMEOUT, // we didn't get interactive input within wait_on_escape_ms // The range of key codes for inputrc-style keyboard functions that are passed on to the caller // of input_read(). R_BEGIN_INPUT_FUNCTIONS = R_BEGINNING_OF_LINE, - R_END_INPUT_FUNCTIONS = R_CANCEL + 1 + R_END_INPUT_FUNCTIONS = R_REVERSE_REPEAT_JUMP + 1 }; /// Init the library. diff --git a/src/reader.cpp b/src/reader.cpp index 27d13cc42..d7902eac2 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -118,6 +118,9 @@ enum class history_search_direction_t { forward, backward }; +enum class jump_direction_t { forward, backward }; +enum class jump_precision_t { till, to }; + /// Any time the contents of a buffer changes, we update the generation count. This allows for our /// background threads to notice it and skip doing work that they would otherwise have to do. static std::atomic s_generation_count; @@ -370,6 +373,10 @@ class reader_data_t { bool screen_reset_needed{false}; /// Whether the reader should exit on ^C. bool exit_on_interrupt{false}; + /// The target character of the last jump command. + wchar_t last_jump_target{0}; + jump_direction_t last_jump_direction{jump_direction_t::forward}; + jump_precision_t last_jump_precision{jump_precision_t::to}; bool is_navigating_pager_contents() const { return this->pager.is_navigating_contents(); } @@ -429,6 +436,7 @@ static volatile sig_atomic_t interrupted = 0; // Prototypes for a bunch of functions defined later on. static bool is_backslashed(const wcstring &str, size_t pos); static wchar_t unescaped_quote(const wcstring &str, size_t pos); +bool jump(jump_direction_t dir, jump_precision_t precision, editable_line_t *el, wchar_t target); /// Mode on startup, which we restore on exit. static struct termios terminal_mode_on_startup; @@ -3186,33 +3194,80 @@ const wchar_t *reader_readline(int nchars) { case R_FORWARD_JUMP: { editable_line_t *el = data->active_edit_line(); wchar_t target = input_function_pop_arg(); - bool status = false; + bool success = jump(jump_direction_t::forward, jump_precision_t::to, el, target); - for (size_t i = el->position + 1; i < el->size(); i++) { - if (el->at(i) == target) { - update_buff_pos(el, i); - status = true; - break; - } - } - input_function_set_status(status); + input_function_set_status(success); reader_repaint_needed(); break; } case R_BACKWARD_JUMP: { editable_line_t *el = data->active_edit_line(); wchar_t target = input_function_pop_arg(); - bool status = false; + bool success = jump(jump_direction_t::backward, jump_precision_t::to, el, target); - size_t tmp_pos = el->position; - while (tmp_pos--) { - if (el->at(tmp_pos) == target) { - update_buff_pos(el, tmp_pos); - status = true; - break; - } + input_function_set_status(success); + reader_repaint_needed(); + break; + } + case R_FORWARD_JUMP_TILL: { + editable_line_t *el = data->active_edit_line(); + wchar_t target = input_function_pop_arg(); + bool success = jump(jump_direction_t::forward, jump_precision_t::till, el, target); + + input_function_set_status(success); + reader_repaint_needed(); + break; + } + case R_BACKWARD_JUMP_TILL: { + editable_line_t *el = data->active_edit_line(); + wchar_t target = input_function_pop_arg(); + bool success = jump(jump_direction_t::backward, jump_precision_t::till, el, target); + + input_function_set_status(success); + reader_repaint_needed(); + break; + } + case R_REPEAT_JUMP: { + editable_line_t *el = data->active_edit_line(); + bool success = false; + + if (data->last_jump_target) { + success = jump( + data->last_jump_direction, + data->last_jump_precision, + el, + data->last_jump_target + ); } - input_function_set_status(status); + + input_function_set_status(success); + reader_repaint_needed(); + break; + } + case R_REVERSE_REPEAT_JUMP: { + editable_line_t *el = data->active_edit_line(); + bool success = false; + jump_direction_t original_dir, dir; + original_dir = dir = data->last_jump_direction; + + if (data->last_jump_direction == jump_direction_t::forward) { + dir = jump_direction_t::backward; + } else { + dir = jump_direction_t::forward; + } + + if (data->last_jump_target) { + success = jump( + dir, + data->last_jump_precision, + el, + data->last_jump_target + ); + } + + data->last_jump_direction = original_dir; + + input_function_set_status(success); reader_repaint_needed(); break; } @@ -3270,6 +3325,48 @@ const wchar_t *reader_readline(int nchars) { return finished ? data->command_line.text.c_str() : NULL; } +bool jump(jump_direction_t dir, jump_precision_t precision, editable_line_t *el, wchar_t target) { + reader_data_t *data = current_data_or_null(); + bool success = false; + + data->last_jump_target = target; + data->last_jump_direction = dir; + data->last_jump_precision = precision; + + switch (dir) { + case jump_direction_t::backward: { + size_t tmp_pos = el->position; + + while (tmp_pos--) { + if (el->at(tmp_pos) == target) { + if (precision == jump_precision_t::till) { + tmp_pos = std::min(el->size()-1, tmp_pos+1); + } + update_buff_pos(el, tmp_pos); + success = true; + break; + } + } + break; + } + case jump_direction_t::forward: { + for (size_t tmp_pos=el->position+1; tmp_pos < el->size(); tmp_pos++) { + if (el->at(tmp_pos) == target) { + if (precision == jump_precision_t::till && tmp_pos) { + tmp_pos--; + } + update_buff_pos(el, tmp_pos); + success = true; + break; + } + } + break; + } + } + + return success; +} + bool reader_is_in_search_mode() { reader_data_t *data = current_data_or_null(); return data && data->history_search.active(); diff --git a/tests/bind.expect b/tests/bind.expect index 297f99166..98bebc295 100644 --- a/tests/bind.expect +++ b/tests/bind.expect @@ -112,6 +112,54 @@ expect_prompt -re {\r\nMORE\r\n} { puts stderr "vi mode delete char, default timeout: long delay" } +# Test jumping forward til before a character with t +send "echo MORE-TEXT-IS-NICE" +send "\033" +# Delay needed to allow fish to transition to vi "normal" mode. +sleep 0.250 +send "0tTD\r" +expect_prompt -re {\r\nMORE\r\n} { + puts "vi mode forward-jump-till character, default timeout: long delay" +} unmatched { + puts stderr "vi mode forward-jump-till character, default timeout: long delay" +} + +# Test jumping backward til before a character with T +send "echo MORE-TEXT-IS-NICE" +send "\033" +# Delay needed to allow fish to transition to vi "normal" mode. +sleep 0.250 +send "TSD\r" +expect_prompt -re {\r\nMORE-TEXT-IS\r\n} { + puts "vi mode backward-jump-till character, default timeout: long delay" +} unmatched { + puts stderr "vi mode backward-jump-till character, default timeout: long delay" +} + +# Test jumping backward with F and repeating +send "echo MORE-TEXT-IS-NICE" +send "\033" +# Delay needed to allow fish to transition to vi "normal" mode. +sleep 0.250 +send "F-;D\r" +expect_prompt -re {\r\nMORE-TEXT\r\n} { + puts "vi mode backward-jump-to character and repeat, default timeout: long delay" +} unmatched { + puts stderr "vi mode backward-jump-to character and repeat, default timeout: long delay" +} + +# Test jumping backward with F w/reverse jump +send "echo MORE-TEXT-IS-NICE" +send "\033" +# Delay needed to allow fish to transition to vi "normal" mode. +sleep 0.250 +send "F-F-,D\r" +expect_prompt -re {\r\nMORE-TEXT-IS\r\n} { + puts "vi mode backward-jump-to character, and reverse, default timeout: long delay" +} unmatched { + puts stderr "vi mode backward-jump-to character, and reverse, default timeout: long delay" +} + # Verify that changing the escape timeout has an effect. send "set -g fish_escape_delay_ms 200\r" expect_prompt diff --git a/tests/bind.expect.out b/tests/bind.expect.out index d76b605fe..6fd079f7f 100644 --- a/tests/bind.expect.out +++ b/tests/bind.expect.out @@ -7,6 +7,10 @@ vi-mode default timeout set correctly vi replace line, default timeout: long delay vi mode replace char, default timeout: long delay vi mode delete char, default timeout: long delay +vi mode forward-jump-till character, default timeout: long delay +vi mode backward-jump-till character, default timeout: long delay +vi mode backward-jump-to character and repeat, default timeout: long delay +vi mode backward-jump-to character, and reverse, default timeout: long delay vi replace line, 100ms timeout: long delay vi replace line, 100ms timeout: short delay t-binding success