mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-05-21 03:21:16 -03:00
Vi mode: hack in support for df and friends
Commit 38e633d49b (fish_vi_key_bindings: add support for count,
2025-12-16) introduced an operator mode which kind of makes a lot of
sense for today's fish. If we end up needing more flexibility and
tighter integration, we might want to move some of this into core.
Unfortunately the change is at odds with our cursed forward-jump
implementation. The forward-jump special input function works by
magically reading the next key from stdin, which causes problems when
we are executing a script:
commandline -f begin-selection
commandline -f forward-jump
commandline -f end-selection
here end-selection will be executed immediately
and forward-jump fails to wait for a keystroke.
We should get rid of forward-jump implementation.
For now, replace only the broken thing with a dedicated bind mode
for each of f/F/t/T.
Fixes #12417
This commit is contained in:
@@ -401,6 +401,13 @@ The following special input functions are available:
|
||||
``self-insert-notfirst``
|
||||
inserts the matching sequence into the command line, unless the cursor is at the beginning
|
||||
|
||||
``get-key``
|
||||
sets :envvar:`fish_key` to the key that was pressed to trigger this binding. Example use::
|
||||
|
||||
for i in (seq 0 9)
|
||||
bind $i get-key 'commandline -i "#$fish_key"' 'set -eg fish_key'
|
||||
end
|
||||
|
||||
``suppress-autosuggestion``
|
||||
remove the current autosuggestion. Returns true if there was a suggestion to remove.
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ Example
|
||||
case visual
|
||||
set_color --bold brmagenta
|
||||
echo 'V'
|
||||
case operator
|
||||
case operator f F t T
|
||||
set_color --bold cyan
|
||||
echo 'N'
|
||||
case '*'
|
||||
|
||||
@@ -18,7 +18,7 @@ function fish_default_mode_prompt --description "Display vi prompt mode"
|
||||
case visual
|
||||
set_color --bold magenta
|
||||
echo '[V]'
|
||||
case operator
|
||||
case operator f F t T
|
||||
set_color --bold cyan
|
||||
echo '[N]'
|
||||
end
|
||||
|
||||
@@ -53,7 +53,7 @@ function fish_vi_yank_selection
|
||||
end
|
||||
|
||||
function fish_vi_exec_motion
|
||||
argparse linewise -- $argv
|
||||
argparse --stop-nonopt linewise -- $argv
|
||||
or return
|
||||
|
||||
set -l motion $argv
|
||||
@@ -83,7 +83,7 @@ function fish_vi_exec_motion
|
||||
else
|
||||
set -l use_selection true
|
||||
set -l swap_case_hack
|
||||
switch $motion
|
||||
switch $motion[1]
|
||||
case forward-word-vi forward-bigword-vi
|
||||
if test $__fish_vi_operator = swap-case
|
||||
set swap_case_hack (string replace -r -- '^forward-((?:big)?word)-vi$' '$1' $motion)
|
||||
@@ -93,50 +93,62 @@ function fish_vi_exec_motion
|
||||
set motion (string replace -- forward kill $motion)
|
||||
end
|
||||
end
|
||||
switch $motion[1]
|
||||
case commandline
|
||||
case '*'
|
||||
set motion commandline -f $motion
|
||||
end
|
||||
if $use_selection
|
||||
commandline -f begin-selection
|
||||
else
|
||||
commandline -f begin-undo-group
|
||||
end
|
||||
set -l ok true
|
||||
switch $__fish_vi_operator
|
||||
case delete
|
||||
for i in (seq $total)
|
||||
commandline -f $motion
|
||||
$motion || { set ok false; break }
|
||||
end
|
||||
if $use_selection
|
||||
if $ok && $use_selection
|
||||
commandline -f kill-selection
|
||||
end
|
||||
case change
|
||||
for i in (seq $total)
|
||||
commandline -f $motion
|
||||
$motion || { set ok false; break }
|
||||
end
|
||||
if $use_selection
|
||||
commandline -f kill-selection
|
||||
if $ok
|
||||
if $use_selection
|
||||
commandline -f kill-selection
|
||||
end
|
||||
set fish_bind_mode insert
|
||||
end
|
||||
set fish_bind_mode insert
|
||||
case yank
|
||||
for i in (seq $total)
|
||||
commandline -f $motion
|
||||
$motion || { set ok false; break }
|
||||
end
|
||||
if $use_selection
|
||||
fish_vi_yank_selection
|
||||
else
|
||||
commandline -f yank
|
||||
if $ok
|
||||
if $use_selection
|
||||
fish_vi_yank_selection
|
||||
else
|
||||
commandline -f yank
|
||||
end
|
||||
end
|
||||
case swap-case
|
||||
for i in (seq $total)
|
||||
commandline -f $motion
|
||||
$motion || { set ok false; break }
|
||||
end
|
||||
if set -q swap_case_hack[1]
|
||||
set -l word $swap_case_hack
|
||||
commandline -f \
|
||||
backward-$word \
|
||||
forward-$word-end \
|
||||
togglecase-selection \
|
||||
backward-$word \
|
||||
forward-$word-vi
|
||||
else
|
||||
commandline -f togglecase-selection
|
||||
if $ok
|
||||
if set -q swap_case_hack[1]
|
||||
set -l word $swap_case_hack
|
||||
commandline -f \
|
||||
backward-$word \
|
||||
forward-$word-end \
|
||||
togglecase-selection \
|
||||
backward-$word \
|
||||
forward-$word-vi
|
||||
else
|
||||
commandline -f togglecase-selection
|
||||
end
|
||||
end
|
||||
end
|
||||
if $use_selection
|
||||
@@ -400,10 +412,16 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish'
|
||||
bind --preset -M operator \^ 'fish_vi_exec_motion beginning-of-line'
|
||||
bind --preset -M operator \$ 'fish_vi_exec_motion end-of-line'
|
||||
|
||||
bind --preset -M operator f 'fish_vi_exec_motion forward-jump'
|
||||
bind --preset -M operator F 'fish_vi_exec_motion backward-jump'
|
||||
bind --preset -M operator t 'fish_vi_exec_motion forward-jump-till'
|
||||
bind --preset -M operator T 'fish_vi_exec_motion backward-jump-till'
|
||||
bind --preset -M operator f --sets-mode f ''
|
||||
bind --preset -M operator F --sets-mode F ''
|
||||
bind --preset -M operator t --sets-mode t ''
|
||||
bind --preset -M operator T --sets-mode T ''
|
||||
|
||||
bind --preset -M f '' get-key 'fish_vi_exec_motion commandline --forward-jump=$fish_key' 'set -eg fish_key'
|
||||
bind --preset -M F '' get-key 'fish_vi_exec_motion commandline --backward-jump=$fish_key' 'set -eg fish_key'
|
||||
bind --preset -M t '' get-key 'fish_vi_exec_motion commandline --forward-jump-till=$fish_key' 'set -eg fish_key'
|
||||
bind --preset -M T '' get-key 'fish_vi_exec_motion commandline --backward-jump-till=$fish_key' 'set -eg fish_key'
|
||||
|
||||
bind --preset -M operator ';' 'fish_vi_exec_motion repeat-jump'
|
||||
bind --preset -M operator , 'fish_vi_exec_motion repeat-jump-reverse'
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ function fish_prompt
|
||||
switch $fish_bind_mode
|
||||
case default
|
||||
set mode (set_color --bold red)N
|
||||
case operator
|
||||
case operator f F t T
|
||||
set mode (set_color --bold cyan)N
|
||||
case insert
|
||||
set mode (set_color --bold green)I
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
use crate::prelude::*;
|
||||
use crate::proc::is_interactive_session;
|
||||
use crate::reader::{
|
||||
commandline_get_state, commandline_set_buffer, commandline_set_search_field,
|
||||
reader_execute_readline_cmd, reader_showing_suggestion,
|
||||
JumpDirection, JumpPrecision, commandline_get_state, commandline_set_buffer,
|
||||
commandline_set_search_field, reader_execute_readline_cmd, reader_jump,
|
||||
reader_showing_suggestion,
|
||||
};
|
||||
use crate::tokenizer::{TOK_ACCEPT_UNFINISHED, TokenType, Tokenizer};
|
||||
use fish_wcstringutil::join_strings;
|
||||
@@ -262,6 +263,11 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|
||||
let mut showing_suggestion = false;
|
||||
|
||||
let mut override_buffer = None;
|
||||
let mut forward_jump = false;
|
||||
let mut backward_jump = false;
|
||||
let mut forward_jump_till = false;
|
||||
let mut backward_jump_till = false;
|
||||
let mut jump_target = None;
|
||||
|
||||
let short_options = L!("abijpctfxorhI:CBELSsP");
|
||||
let long_options: &[WOption] = &[
|
||||
@@ -292,6 +298,10 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|
||||
wopt(L!("search-field"), ArgType::NoArgument, '\x03'),
|
||||
wopt(L!("is-valid"), ArgType::NoArgument, '\x01'),
|
||||
wopt(L!("showing-suggestion"), ArgType::NoArgument, '\x04'),
|
||||
wopt(L!("forward-jump"), ArgType::RequiredArgument, '\x07'),
|
||||
wopt(L!("backward-jump"), ArgType::RequiredArgument, '\x08'),
|
||||
wopt(L!("forward-jump-till"), ArgType::RequiredArgument, '\x09'),
|
||||
wopt(L!("backward-jump-till"), ArgType::RequiredArgument, '\x0a'),
|
||||
];
|
||||
|
||||
let mut w = WGetopter::new(short_options, long_options, args);
|
||||
@@ -341,6 +351,22 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|
||||
'\x03' => search_field_mode = true,
|
||||
'\x01' => is_valid = true,
|
||||
'\x04' => showing_suggestion = true,
|
||||
'\x07' => {
|
||||
forward_jump = true;
|
||||
jump_target = Some(w.woptarg.unwrap().to_owned());
|
||||
}
|
||||
'\x08' => {
|
||||
backward_jump = true;
|
||||
jump_target = Some(w.woptarg.unwrap().to_owned());
|
||||
}
|
||||
'\x09' => {
|
||||
forward_jump_till = true;
|
||||
jump_target = Some(w.woptarg.unwrap().to_owned());
|
||||
}
|
||||
'\x0a' => {
|
||||
backward_jump_till = true;
|
||||
jump_target = Some(w.woptarg.unwrap().to_owned());
|
||||
}
|
||||
'h' => {
|
||||
builtin_print_help(parser, streams, cmd);
|
||||
return Ok(SUCCESS);
|
||||
@@ -360,6 +386,27 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
if forward_jump || forward_jump_till || backward_jump || backward_jump_till {
|
||||
let direction = if forward_jump || forward_jump_till {
|
||||
JumpDirection::Forward
|
||||
} else {
|
||||
JumpDirection::Backward
|
||||
};
|
||||
let precision = if forward_jump || backward_jump {
|
||||
JumpPrecision::To
|
||||
} else {
|
||||
JumpPrecision::Till
|
||||
};
|
||||
let target = jump_target.unwrap();
|
||||
let Some(target) = target.chars().next() else {
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
};
|
||||
return if reader_jump(direction, precision, target) {
|
||||
Ok(SUCCESS)
|
||||
} else {
|
||||
Err(STATUS_CMD_ERROR)
|
||||
};
|
||||
}
|
||||
|
||||
let positional_args = w.argv.len() - w.wopt_index;
|
||||
|
||||
|
||||
19
src/input.rs
19
src/input.rs
@@ -162,6 +162,7 @@ const fn make_md(name: &'static wstr, code: ReadlineCmd) -> InputFunctionMetadat
|
||||
make_md(L!("forward-word"), ReadlineCmd::ForwardWordEmacs),
|
||||
make_md(L!("forward-word-end"), ReadlineCmd::ForwardWordEnd),
|
||||
make_md(L!("forward-word-vi"), ReadlineCmd::ForwardWordVi),
|
||||
make_md(L!("get-key"), ReadlineCmd::GetKey),
|
||||
make_md(L!("history-delete"), ReadlineCmd::HistoryDelete),
|
||||
make_md(L!("history-last-token-search-backward"), ReadlineCmd::HistoryLastTokenSearchBackward),
|
||||
make_md(L!("history-last-token-search-forward"), ReadlineCmd::HistoryLastTokenSearchForward),
|
||||
@@ -698,7 +699,9 @@ pub fn read_char(&mut self) -> CharEvent {
|
||||
let evt = self.readch();
|
||||
match evt {
|
||||
CharEvent::Readline(ref readline_event) => match readline_event.cmd {
|
||||
ReadlineCmd::SelfInsert | ReadlineCmd::SelfInsertNotFirst => {
|
||||
ReadlineCmd::SelfInsert
|
||||
| ReadlineCmd::SelfInsertNotFirst
|
||||
| ReadlineCmd::GetKey => {
|
||||
// Typically self-insert is generated by the generic (empty) binding.
|
||||
// However if it is generated by a real sequence, then insert that sequence.
|
||||
let seq = readline_event.seq.chars().map(CharEvent::from_char);
|
||||
@@ -721,6 +724,20 @@ pub fn read_char(&mut self) -> CharEvent {
|
||||
kevt.input_style = CharInputStyle::NotFirst;
|
||||
}
|
||||
}
|
||||
if readline_event.cmd == ReadlineCmd::GetKey {
|
||||
if let CharEvent::Key(kevt) = res {
|
||||
return CharEvent::Command(sprintf!(
|
||||
"set -g fish_key %s",
|
||||
escape(
|
||||
&kevt
|
||||
.key
|
||||
.codepoint_text()
|
||||
.map(|c| WString::from_chars(vec![c]))
|
||||
.unwrap_or_default()
|
||||
)
|
||||
));
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
ReadlineCmd::FuncAnd | ReadlineCmd::FuncOr => {
|
||||
|
||||
@@ -107,6 +107,7 @@ pub enum ReadlineCmd {
|
||||
HistoryLastTokenSearchForward,
|
||||
SelfInsert,
|
||||
SelfInsertNotFirst,
|
||||
GetKey,
|
||||
TransposeChars,
|
||||
TransposeWords,
|
||||
UpcaseWord,
|
||||
|
||||
@@ -546,13 +546,13 @@ enum Kill {
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
enum JumpDirection {
|
||||
pub enum JumpDirection {
|
||||
Forward,
|
||||
Backward,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
enum JumpPrecision {
|
||||
pub enum JumpPrecision {
|
||||
Till,
|
||||
To,
|
||||
}
|
||||
@@ -1175,6 +1175,15 @@ pub fn reader_execute_readline_cmd(parser: &Parser, ch: CharEvent) {
|
||||
let _ = data.handle_char_event(Some(ch));
|
||||
}
|
||||
|
||||
pub fn reader_jump(direction: JumpDirection, precision: JumpPrecision, target: char) -> bool {
|
||||
let Some(data) = current_data() else {
|
||||
return false;
|
||||
};
|
||||
data.save_screen_state();
|
||||
let elt = data.active_edit_line_tag();
|
||||
data.jump_and_remember_last_jump(direction, precision, elt, target, false)
|
||||
}
|
||||
|
||||
pub fn reader_showing_suggestion(parser: &Parser) -> bool {
|
||||
if !is_interactive_session() {
|
||||
return false;
|
||||
@@ -4346,7 +4355,7 @@ fn handle_readline_command(&mut self, c: ReadlineCmd) {
|
||||
rl::ScrollbackPush => {
|
||||
self.screen.push_to_scrollback();
|
||||
}
|
||||
rl::SelfInsert | rl::SelfInsertNotFirst | rl::FuncAnd | rl::FuncOr => {
|
||||
rl::SelfInsert | rl::SelfInsertNotFirst | rl::GetKey | rl::FuncAnd | rl::FuncOr => {
|
||||
// This can be reached via `commandline -f and` etc
|
||||
// panic!("should have been handled by inputter_t::readch");
|
||||
}
|
||||
@@ -6220,6 +6229,7 @@ fn command_ends_paging(c: ReadlineCmd, focused_on_search_field: bool) -> bool {
|
||||
| rl::BackwardKillBigword
|
||||
| rl::BackwardKillToken
|
||||
| rl::SelfInsert
|
||||
| rl::GetKey
|
||||
| rl::SelfInsertNotFirst
|
||||
| rl::TransposeChars
|
||||
| rl::TransposeWords
|
||||
|
||||
@@ -321,7 +321,7 @@ expect_prompt("echo two three")
|
||||
# Now test that exactly the expected bind modes are defined
|
||||
sendline("bind --list-modes")
|
||||
expect_prompt(
|
||||
"default\r\ninsert\r\noperator\r\nreplace\r\nreplace_one\r\nvisual\r\n",
|
||||
"F\r\nT\r\ndefault\r\nf\r\ninsert\r\noperator\r\nreplace\r\nreplace_one\r\nt\r\nvisual\r\n",
|
||||
unmatched="Unexpected vi bind modes",
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user