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:
Chris 2018-08-11 03:05:49 -04:00 committed by ridiculousfish
parent 7606bfc2e5
commit 1d68b52cbc
6 changed files with 191 additions and 24 deletions

View file

@ -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

View file

@ -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.

View file

@ -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.

View file

@ -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;
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;
}
input_function_set_status(status);
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(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();

View file

@ -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

View file

@ -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