mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-06-01 13:01:21 -03:00
bind: show all modes by default
Ensure `bind` builtin lists binds for all modes if `--mode` is not given. - The `get` function in `src/input.rs` now takes an optional bind mode and returns a list of input mappings (binds). - The `list_one` function in `src/builtins/bind.rs` lists binds in the results returned by `get`. - Creating the output string for a bind has been extracted to its own function: `BuiltinBind::generate_output_string`. - The `bind_mode_given` option has been removed. Fixes #12214 Closes #12285
This commit is contained in:
committed by
Johannes Altmanninger
parent
bbb2f0de8d
commit
8e34dc4cdb
@@ -13,6 +13,7 @@ Interactive improvements
|
||||
- The behavior of ``{,d}{w,W}``, ``{,d}{,g}{e,E}`` bindings in vi-mode is now more compatible with vim, except that the underscore is not a keyword (which can be archived by setting ``set iskeyword-=_`` in vim).
|
||||
- Add commands ``{forward,kill}-{word,bigword}-vi``, ``{forward,backward,kill,backward-kill}-{word,bigword}-end`` and ``kill-{a,inner}-{word,bigword}`` corresponding to above-mentioned bindings.
|
||||
- Vi mode key bindings now support counts for movement and deletion commands (e.g. `d3w` or `3l`), via a new operator mode (:issue:`2192`).
|
||||
- The ``bind`` builtin lists mappings from all modes if ``--mode`` is not provided (:issue:`12214`).
|
||||
|
||||
New or improved bindings
|
||||
------------------------
|
||||
|
||||
@@ -19,8 +19,8 @@ Description
|
||||
``bind`` manages key bindings.
|
||||
|
||||
If both ``KEYS`` and ``COMMAND`` are given, ``bind`` adds (or replaces) a binding in ``MODE``.
|
||||
If only ``KEYS`` is given, any existing binding for those keys in the given ``MODE`` will be printed.
|
||||
If no ``KEYS`` argument is provided, all bindings (in the given ``MODE``) are printed.
|
||||
If only ``KEYS`` is given, ``bind`` lists any existing bindings for those keys in ``MODE`` or in all modes.
|
||||
If no ``KEYS`` argument is provided, ``bind`` lists all bindings in ``MODE`` or in all modes.
|
||||
|
||||
``KEYS`` is a comma-separated list of key names.
|
||||
Modifier keys can be specified by prefixing a key name with a combination of ``ctrl-``, ``alt-``, ``shift-`` and ``super-`` (i.e. the "windows" or "command" key).
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
EscapeFlags, EscapeStringStyle, bytes2wcstring, escape, escape_string, valid_var_name,
|
||||
};
|
||||
use crate::highlight::highlight_and_colorize;
|
||||
use crate::input::{InputMappingSet, KeyNameStyle, input_function_get_names, input_mappings};
|
||||
use crate::input::{
|
||||
InputMapping, InputMappingSet, KeyNameStyle, input_function_get_names, input_mappings,
|
||||
};
|
||||
use crate::key::{
|
||||
self, KEY_NAMES, Key, MAX_FUNCTION_KEY, Modifiers, char_to_symbol, function_key, parse_keys,
|
||||
};
|
||||
@@ -20,7 +22,6 @@
|
||||
|
||||
struct Options {
|
||||
all: bool,
|
||||
bind_mode_given: bool,
|
||||
list_modes: bool,
|
||||
print_help: bool,
|
||||
silent: bool,
|
||||
@@ -29,7 +30,7 @@ struct Options {
|
||||
have_preset: bool,
|
||||
preset: bool,
|
||||
mode: c_int,
|
||||
bind_mode: WString,
|
||||
bind_mode: Option<WString>,
|
||||
sets_bind_mode: Option<WString>,
|
||||
color: ColorEnabled,
|
||||
}
|
||||
@@ -38,7 +39,6 @@ impl Options {
|
||||
fn new() -> Options {
|
||||
Options {
|
||||
all: false,
|
||||
bind_mode_given: false,
|
||||
list_modes: false,
|
||||
print_help: false,
|
||||
silent: false,
|
||||
@@ -47,7 +47,7 @@ fn new() -> Options {
|
||||
have_preset: false,
|
||||
preset: false,
|
||||
mode: BIND_INSERT,
|
||||
bind_mode: DEFAULT_BIND_MODE.to_owned(),
|
||||
bind_mode: None,
|
||||
sets_bind_mode: None,
|
||||
color: ColorEnabled::default(),
|
||||
}
|
||||
@@ -70,30 +70,9 @@ fn new() -> BuiltinBind {
|
||||
}
|
||||
}
|
||||
|
||||
/// List a single key binding.
|
||||
/// Returns false if no binding with that sequence and mode exists.
|
||||
fn list_one(
|
||||
&self,
|
||||
seq: &[Key],
|
||||
bind_mode: &wstr,
|
||||
user: bool,
|
||||
parser: &Parser,
|
||||
streams: &mut IoStreams,
|
||||
) -> bool {
|
||||
let mut ecmds: &[_] = &[];
|
||||
let mut sets_mode = None;
|
||||
let mut key_name_style = KeyNameStyle::Plain;
|
||||
/// Returns a WString for the output line of a bind
|
||||
fn generate_output_string(seq: &[Key], user: bool, bind: &InputMapping) -> WString {
|
||||
let mut out = WString::new();
|
||||
if !self.input_mappings.get(
|
||||
seq,
|
||||
bind_mode,
|
||||
&mut ecmds,
|
||||
user,
|
||||
&mut sets_mode,
|
||||
&mut key_name_style,
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out.push_str("bind");
|
||||
|
||||
@@ -101,20 +80,20 @@ fn list_one(
|
||||
if !user {
|
||||
out.push_str(" --preset");
|
||||
}
|
||||
if bind_mode != DEFAULT_BIND_MODE {
|
||||
if bind.mode != DEFAULT_BIND_MODE {
|
||||
out.push_str(" -M ");
|
||||
out.push_utfstr(&escape(bind_mode));
|
||||
out.push_utfstr(&escape(&bind.mode));
|
||||
}
|
||||
|
||||
if let Some(sets_mode) = sets_mode {
|
||||
if sets_mode != bind_mode {
|
||||
if let Some(sets_mode) = &bind.sets_mode {
|
||||
if *sets_mode != bind.mode {
|
||||
out.push_str(" -m ");
|
||||
out.push_utfstr(&escape(sets_mode));
|
||||
}
|
||||
}
|
||||
|
||||
out.push(' ');
|
||||
match key_name_style {
|
||||
match bind.key_name_style {
|
||||
KeyNameStyle::Plain => {
|
||||
// Append the name.
|
||||
for (i, key) in seq.iter().enumerate() {
|
||||
@@ -148,31 +127,59 @@ fn list_one(
|
||||
}
|
||||
|
||||
// Now show the list of commands.
|
||||
for ecmd in ecmds {
|
||||
for ecmd in &bind.commands {
|
||||
out.push(' ');
|
||||
out.push_utfstr(&escape(ecmd));
|
||||
}
|
||||
out.push('\n');
|
||||
|
||||
if self.opts.color.enabled(streams) {
|
||||
streams.out.append(&bytes2wcstring(&highlight_and_colorize(
|
||||
&out,
|
||||
&parser.context(),
|
||||
parser.vars(),
|
||||
)));
|
||||
} else {
|
||||
streams.out.append(&out);
|
||||
out
|
||||
}
|
||||
|
||||
/// List a single binding of a specific sequence from the specified mode.
|
||||
/// Returns false if no binding with that sequence and mode exists.
|
||||
///
|
||||
/// If bind_mode is None, then binds from all modes are listed.
|
||||
fn list_one(
|
||||
&self,
|
||||
seq: &[Key],
|
||||
bind_mode: Option<&wstr>,
|
||||
user: bool,
|
||||
parser: &Parser,
|
||||
streams: &mut IoStreams,
|
||||
) -> bool {
|
||||
let results = self.input_mappings.get(seq, bind_mode, user);
|
||||
|
||||
// no binds found
|
||||
if results.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
for bind in results {
|
||||
let out = Self::generate_output_string(seq, user, bind);
|
||||
|
||||
if self.opts.color.enabled(streams) {
|
||||
streams.out.append(&bytes2wcstring(&highlight_and_colorize(
|
||||
&out,
|
||||
&parser.context(),
|
||||
parser.vars(),
|
||||
)));
|
||||
} else {
|
||||
streams.out.append(&out);
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
// Overload with both kinds of bindings.
|
||||
// Returns false only if neither exists.
|
||||
/// Overload with both preset and user bindings.
|
||||
/// Returns false only if neither exists.
|
||||
///
|
||||
/// If bind_mode is None, then binds from all modes are listed.
|
||||
fn list_one_user_andor_preset(
|
||||
&self,
|
||||
seq: &[Key],
|
||||
bind_mode: &wstr,
|
||||
bind_mode: Option<&wstr>,
|
||||
user: bool,
|
||||
preset: bool,
|
||||
parser: &Parser,
|
||||
@@ -196,7 +203,7 @@ fn list(&self, bind_mode: Option<&wstr>, user: bool, parser: &Parser, streams: &
|
||||
continue;
|
||||
}
|
||||
|
||||
self.list_one(&binding.seq, &binding.mode, user, parser, streams);
|
||||
self.list_one(&binding.seq, Some(&binding.mode), user, parser, streams);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,24 +274,20 @@ fn compute_seq(&self, streams: &mut IoStreams, seq: &wstr) -> Option<Vec<Key>> {
|
||||
/// if specified, _all_ key bindings will be erased
|
||||
///
|
||||
fn erase(&mut self, seq: &[&wstr], all: bool, user: bool, streams: &mut IoStreams) -> bool {
|
||||
let mode = if self.opts.bind_mode_given {
|
||||
Some(self.opts.bind_mode.as_utfstr())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let bind_mode = self.opts.bind_mode.as_deref();
|
||||
|
||||
if all {
|
||||
self.input_mappings.clear(mode, user);
|
||||
self.input_mappings.clear(bind_mode, user);
|
||||
return false;
|
||||
}
|
||||
|
||||
let mode = mode.unwrap_or(DEFAULT_BIND_MODE);
|
||||
let bind_mode = bind_mode.unwrap_or(DEFAULT_BIND_MODE);
|
||||
|
||||
for s in seq {
|
||||
let Some(s) = self.compute_seq(streams, s) else {
|
||||
return true;
|
||||
};
|
||||
self.input_mappings.erase(&s, mode, user);
|
||||
self.input_mappings.erase(&s, bind_mode, user);
|
||||
}
|
||||
false
|
||||
}
|
||||
@@ -321,11 +324,7 @@ fn insert(
|
||||
if arg_count == 0 {
|
||||
// We don't overload this with user and def because we want them to be grouped.
|
||||
// First the presets, then the users (because of scrolling).
|
||||
let bind_mode = if self.opts.bind_mode_given {
|
||||
Some(self.opts.bind_mode.as_utfstr())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let bind_mode = self.opts.bind_mode.as_deref();
|
||||
if self.opts.preset {
|
||||
self.list(bind_mode, false, parser, streams);
|
||||
}
|
||||
@@ -337,9 +336,11 @@ fn insert(
|
||||
return true;
|
||||
};
|
||||
|
||||
let bind_mode = self.opts.bind_mode.as_deref();
|
||||
|
||||
if !self.list_one_user_andor_preset(
|
||||
&seq,
|
||||
&self.opts.bind_mode,
|
||||
bind_mode,
|
||||
self.opts.user,
|
||||
self.opts.preset,
|
||||
parser,
|
||||
@@ -372,7 +373,10 @@ fn insert(
|
||||
if self.add(
|
||||
seq,
|
||||
&argv[optind + 1..],
|
||||
self.opts.bind_mode.clone(),
|
||||
self.opts
|
||||
.bind_mode
|
||||
.clone()
|
||||
.unwrap_or(DEFAULT_BIND_MODE.to_owned()),
|
||||
self.opts.sets_bind_mode.clone(),
|
||||
self.opts.user,
|
||||
streams,
|
||||
@@ -461,8 +465,7 @@ fn parse_cmd_opts(
|
||||
'M' => {
|
||||
let applicable_mode = w.woptarg.unwrap();
|
||||
check_mode_name(streams, applicable_mode)?;
|
||||
opts.bind_mode = applicable_mode.to_owned();
|
||||
opts.bind_mode_given = true;
|
||||
opts.bind_mode = Some(applicable_mode.to_owned());
|
||||
}
|
||||
'm' => {
|
||||
let new_mode = w.woptarg.unwrap();
|
||||
|
||||
36
src/input.rs
36
src/input.rs
@@ -44,11 +44,11 @@ pub struct InputMapping {
|
||||
/// We wish to preserve the user-specified order. This is just an incrementing value.
|
||||
specification_order: u32,
|
||||
/// Mode in which this command should be evaluated.
|
||||
mode: WString,
|
||||
pub mode: WString,
|
||||
/// New mode that should be switched to after command evaluation, or None to leave the mode unchanged.
|
||||
sets_mode: Option<WString>,
|
||||
pub sets_mode: Option<WString>,
|
||||
/// Perhaps this binding was created using a raw escape sequence.
|
||||
key_name_style: KeyNameStyle,
|
||||
pub key_name_style: KeyNameStyle,
|
||||
}
|
||||
|
||||
impl InputMapping {
|
||||
@@ -938,31 +938,31 @@ pub fn erase(&mut self, sequence: &[Key], mode: &wstr, user: bool) -> bool {
|
||||
result
|
||||
}
|
||||
|
||||
/// Gets the command bound to the specified key sequence in the specified mode. Returns true if
|
||||
/// it exists, false if not.
|
||||
/// Returns the command bound to the specified bind mode.
|
||||
///
|
||||
/// If bind_mode is None, then binds from all modes are returned.
|
||||
pub fn get<'a>(
|
||||
&'a self,
|
||||
sequence: &[Key],
|
||||
mode: &wstr,
|
||||
out_cmds: &mut &'a [WString],
|
||||
bind_mode: Option<&wstr>,
|
||||
user: bool,
|
||||
out_sets_mode: &mut Option<&'a wstr>,
|
||||
out_key_name_style: &mut KeyNameStyle,
|
||||
) -> bool {
|
||||
) -> Vec<&'a InputMapping> {
|
||||
let ml = if user {
|
||||
&self.mapping_list
|
||||
} else {
|
||||
&self.preset_mapping_list
|
||||
};
|
||||
for m in ml {
|
||||
if m.seq == sequence && m.mode == mode {
|
||||
*out_cmds = &m.commands;
|
||||
*out_sets_mode = m.sets_mode.as_deref();
|
||||
*out_key_name_style = m.key_name_style.clone();
|
||||
return true;
|
||||
}
|
||||
|
||||
let ml = ml.iter().filter(|mapping| mapping.seq == sequence);
|
||||
let mut mappings: Vec<_>;
|
||||
if let Some(mode) = bind_mode {
|
||||
mappings = ml.filter(|mapping| mapping.mode == mode).collect();
|
||||
assert!(mappings.len() <= 1);
|
||||
} else {
|
||||
mappings = ml.collect();
|
||||
mappings.sort_unstable_by_key(|mapping| mapping.specification_order);
|
||||
}
|
||||
false
|
||||
mappings
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -178,4 +178,17 @@ bind ctrl-shift-a
|
||||
bind ctrl-shift-ä
|
||||
# CHECKERR: bind: No binding found for key 'ctrl-shift-ä'
|
||||
|
||||
# Verify binds from all modes are returned when querying a sequence
|
||||
fish_vi_key_bindings
|
||||
bind --preset ctrl-q 'echo preset'
|
||||
bind ctrl-q 'echo default'
|
||||
bind ctrl-q --mode insert 'echo insert'
|
||||
bind ctrl-q --mode replace 'echo replace'
|
||||
bind ctrl-q
|
||||
# CHECK: bind --preset ctrl-q 'echo preset'
|
||||
# CHECK: bind ctrl-q 'echo default'
|
||||
# CHECK: bind -M insert ctrl-q 'echo insert'
|
||||
# CHECK: bind -M replace ctrl-q 'echo replace'
|
||||
fish_default_key_bindings
|
||||
|
||||
exit 0
|
||||
|
||||
Reference in New Issue
Block a user