mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-26 12:53:13 +00:00
Add till/repeat/reverse jump bindings
- Add support for: - Jumping to the character before a target. - Repeating the previous jump (same direction, same precision). - Repeating the previous jump in the reverse order. - Enhance vi bindings.
This commit is contained in:
parent
7606bfc2e5
commit
1d68b52cbc
6 changed files with 191 additions and 24 deletions
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
133
src/reader.cpp
133
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<unsigned int> 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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue