mirror of
https://github.com/fish-shell/fish-shell.git
synced 2024-11-25 09:39:52 +08:00
Display raw escape sequences the old way again
If a binding was input starting with "\e", it's usually a raw control sequence. Today we display the canonical version like: bind --preset alt-\[,1,\;,5,C foo even if the input is bind --preset \e\[1\;5C foo Make it look like the input again. This looks more familiar and less surprising (especially since we canonicalize CSI to "alt-["). Except that we use the \x01 representation instead of \ca because the "control" part can be confusing. We're inside an escape sequence so it seems highly unlikely that an ASCII control character actually comes from the user holding the control key. The downside is that this hides the canonical version; it might be surprising that a raw-escape-sequence binding can be erased using the new syntax and vice versa.
This commit is contained in:
parent
d025b245f6
commit
f61ef2c63d
|
@ -7,9 +7,9 @@ use crate::common::{
|
|||
use crate::highlight::{colorize, highlight_shell};
|
||||
use crate::input::{
|
||||
input_function_get_names, input_mappings, input_terminfo_get_names,
|
||||
input_terminfo_get_sequence, GetSequenceError, InputMappingSet,
|
||||
input_terminfo_get_sequence, GetSequenceError, InputMappingSet, KeyNameStyle,
|
||||
};
|
||||
use crate::key::{self, canonicalize_raw_escapes, parse_keys, Key};
|
||||
use crate::key::{self, canonicalize_raw_escapes, char_to_symbol, parse_keys, Key, Modifiers};
|
||||
use crate::nix::isatty;
|
||||
use std::sync::MutexGuard;
|
||||
|
||||
|
@ -84,7 +84,7 @@ impl BuiltinBind {
|
|||
) -> bool {
|
||||
let mut ecmds: &[_] = &[];
|
||||
let mut sets_mode = None;
|
||||
let mut terminfo_name = None;
|
||||
let mut key_name_style = KeyNameStyle::Plain;
|
||||
let mut out = WString::new();
|
||||
if !self.input_mappings.get(
|
||||
seq,
|
||||
|
@ -92,7 +92,7 @@ impl BuiltinBind {
|
|||
&mut ecmds,
|
||||
user,
|
||||
&mut sets_mode,
|
||||
&mut terminfo_name,
|
||||
&mut key_name_style,
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
@ -115,12 +115,9 @@ impl BuiltinBind {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(tname) = terminfo_name {
|
||||
// Note that we show -k here because we have an input key name.
|
||||
out.push_str(" -k ");
|
||||
out.push_utfstr(&tname);
|
||||
} else {
|
||||
out.push(' ');
|
||||
match key_name_style {
|
||||
KeyNameStyle::Plain => {
|
||||
// Append the name.
|
||||
for (i, key) in seq.iter().enumerate() {
|
||||
if i != 0 {
|
||||
|
@ -132,6 +129,27 @@ impl BuiltinBind {
|
|||
out.push_str("''");
|
||||
}
|
||||
}
|
||||
KeyNameStyle::RawEscapeSequence => {
|
||||
for key in seq {
|
||||
if key.modifiers == Modifiers::ALT {
|
||||
out.push_utfstr(&char_to_symbol('\x1b'));
|
||||
out.push_utfstr(&char_to_symbol(if key.codepoint == key::Escape {
|
||||
'\x1b'
|
||||
} else {
|
||||
key.codepoint
|
||||
}));
|
||||
} else {
|
||||
assert!(key.modifiers.is_none());
|
||||
out.push_utfstr(&char_to_symbol(key.codepoint));
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyNameStyle::Terminfo(tname) => {
|
||||
// Note that we show -k here because we have an input key name.
|
||||
out.push_str("-k ");
|
||||
out.push_utfstr(&tname);
|
||||
}
|
||||
}
|
||||
|
||||
// Now show the list of commands.
|
||||
for ecmd in ecmds {
|
||||
|
@ -236,22 +254,23 @@ impl BuiltinBind {
|
|||
cmds: &[&wstr],
|
||||
mode: WString,
|
||||
sets_mode: Option<WString>,
|
||||
is_terminfo_key: bool,
|
||||
user: bool,
|
||||
streams: &mut IoStreams,
|
||||
) -> bool {
|
||||
let cmds = cmds.iter().map(|&s| s.to_owned()).collect();
|
||||
let is_raw_escape_sequence = seq.len() > 2 && seq.char_at(0) == '\x1b';
|
||||
let Some(key_seq) = self.compute_seq(streams, seq) else {
|
||||
return true;
|
||||
};
|
||||
self.input_mappings.add(
|
||||
key_seq,
|
||||
is_terminfo_key.then(|| seq.to_owned()),
|
||||
cmds,
|
||||
mode,
|
||||
sets_mode,
|
||||
user,
|
||||
);
|
||||
let key_name_style = if self.opts.use_terminfo {
|
||||
KeyNameStyle::Terminfo(seq.to_owned())
|
||||
} else if is_raw_escape_sequence {
|
||||
KeyNameStyle::RawEscapeSequence
|
||||
} else {
|
||||
KeyNameStyle::Plain
|
||||
};
|
||||
self.input_mappings
|
||||
.add(key_seq, key_name_style, cmds, mode, sets_mode, user);
|
||||
false
|
||||
}
|
||||
|
||||
|
@ -396,7 +415,6 @@ impl BuiltinBind {
|
|||
&argv[optind + 1..],
|
||||
self.opts.bind_mode.to_owned(),
|
||||
self.opts.sets_bind_mode.to_owned(),
|
||||
self.opts.use_terminfo,
|
||||
self.opts.user,
|
||||
streams,
|
||||
) {
|
||||
|
|
53
src/input.rs
53
src/input.rs
|
@ -38,6 +38,13 @@ pub struct InputMappingName {
|
|||
pub mode: WString,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum KeyNameStyle {
|
||||
Plain,
|
||||
RawEscapeSequence,
|
||||
Terminfo(WString),
|
||||
}
|
||||
|
||||
/// Struct representing a keybinding. Returned by input_get_mappings.
|
||||
#[derive(Debug, Clone)]
|
||||
struct InputMapping {
|
||||
|
@ -51,8 +58,8 @@ struct InputMapping {
|
|||
mode: WString,
|
||||
/// New mode that should be switched to after command evaluation, or None to leave the mode unchanged.
|
||||
sets_mode: Option<WString>,
|
||||
/// Whether this sequence was specified via its terminfo name.
|
||||
terminfo_name: Option<WString>,
|
||||
/// Perhaps this binding was created using a raw escape sequence or terminfo.
|
||||
key_name_style: KeyNameStyle,
|
||||
}
|
||||
|
||||
impl InputMapping {
|
||||
|
@ -62,7 +69,7 @@ impl InputMapping {
|
|||
commands: Vec<WString>,
|
||||
mode: WString,
|
||||
sets_mode: Option<WString>,
|
||||
terminfo_name: Option<WString>,
|
||||
key_name_style: KeyNameStyle,
|
||||
) -> InputMapping {
|
||||
static LAST_INPUT_MAP_SPEC_ORDER: AtomicU32 = AtomicU32::new(0);
|
||||
let specification_order = 1 + LAST_INPUT_MAP_SPEC_ORDER.fetch_add(1, Ordering::Relaxed);
|
||||
|
@ -76,7 +83,7 @@ impl InputMapping {
|
|||
specification_order,
|
||||
mode,
|
||||
sets_mode,
|
||||
terminfo_name,
|
||||
key_name_style,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -282,7 +289,7 @@ impl InputMappingSet {
|
|||
pub fn add(
|
||||
&mut self,
|
||||
sequence: Vec<Key>,
|
||||
terminfo_name: Option<WString>,
|
||||
key_name_style: KeyNameStyle,
|
||||
commands: Vec<WString>,
|
||||
mode: WString,
|
||||
sets_mode: Option<WString>,
|
||||
|
@ -307,7 +314,7 @@ impl InputMappingSet {
|
|||
}
|
||||
|
||||
// Add a new mapping, using the next order.
|
||||
let new_mapping = InputMapping::new(sequence, commands, mode, sets_mode, terminfo_name);
|
||||
let new_mapping = InputMapping::new(sequence, commands, mode, sets_mode, key_name_style);
|
||||
input_mapping_insert_sorted(ml, new_mapping);
|
||||
}
|
||||
|
||||
|
@ -315,7 +322,7 @@ impl InputMappingSet {
|
|||
pub fn add1(
|
||||
&mut self,
|
||||
sequence: Vec<Key>,
|
||||
terminfo_name: Option<WString>,
|
||||
key_name_style: KeyNameStyle,
|
||||
command: WString,
|
||||
mode: WString,
|
||||
sets_mode: Option<WString>,
|
||||
|
@ -323,7 +330,7 @@ impl InputMappingSet {
|
|||
) {
|
||||
self.add(
|
||||
sequence,
|
||||
terminfo_name,
|
||||
key_name_style,
|
||||
vec![command],
|
||||
mode,
|
||||
sets_mode,
|
||||
|
@ -349,7 +356,7 @@ pub fn init_input() {
|
|||
let mut add = |key: Vec<Key>, cmd: &str| {
|
||||
let mode = DEFAULT_BIND_MODE.to_owned();
|
||||
let sets_mode = Some(DEFAULT_BIND_MODE.to_owned());
|
||||
input_mapping.add1(key, None, cmd.into(), mode, sets_mode, false);
|
||||
input_mapping.add1(key, KeyNameStyle::Plain, cmd.into(), mode, sets_mode, false);
|
||||
};
|
||||
|
||||
add(vec![], "self-insert");
|
||||
|
@ -372,18 +379,22 @@ pub fn init_input() {
|
|||
add(vec![ctrl('b')], "backward-char");
|
||||
add(vec![ctrl('f')], "forward-char");
|
||||
|
||||
let mut add_legacy = |escape_sequence: &str, cmd: &str| {
|
||||
add(
|
||||
canonicalize_raw_escapes(
|
||||
escape_sequence.chars().map(Key::from_single_char).collect(),
|
||||
),
|
||||
cmd,
|
||||
let mut add_raw = |escape_sequence: &str, cmd: &str| {
|
||||
let mode = DEFAULT_BIND_MODE.to_owned();
|
||||
let sets_mode = Some(DEFAULT_BIND_MODE.to_owned());
|
||||
input_mapping.add1(
|
||||
canonicalize_raw_escapes(escape_sequence.chars().map(Key::from_raw).collect()),
|
||||
KeyNameStyle::RawEscapeSequence,
|
||||
cmd.into(),
|
||||
mode,
|
||||
sets_mode,
|
||||
false,
|
||||
);
|
||||
};
|
||||
add_legacy("\x1B[A", "up-line");
|
||||
add_legacy("\x1B[B", "down-line");
|
||||
add_legacy("\x1B[C", "forward-char");
|
||||
add_legacy("\x1B[D", "backward-char");
|
||||
add_raw("\x1B[A", "up-line");
|
||||
add_raw("\x1B[B", "down-line");
|
||||
add_raw("\x1B[C", "forward-char");
|
||||
add_raw("\x1B[D", "backward-char");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1008,7 +1019,7 @@ impl InputMappingSet {
|
|||
out_cmds: &mut &'a [WString],
|
||||
user: bool,
|
||||
out_sets_mode: &mut Option<&'a wstr>,
|
||||
out_terminfo_name: &mut Option<WString>,
|
||||
out_key_name_style: &mut KeyNameStyle,
|
||||
) -> bool {
|
||||
let ml = if user {
|
||||
&self.mapping_list
|
||||
|
@ -1019,7 +1030,7 @@ impl InputMappingSet {
|
|||
if m.seq == sequence && m.mode == mode {
|
||||
*out_cmds = &m.commands;
|
||||
*out_sets_mode = m.sets_mode.as_deref();
|
||||
*out_terminfo_name = m.terminfo_name.clone();
|
||||
*out_key_name_style = m.key_name_style.clone();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
38
src/key.rs
38
src/key.rs
|
@ -66,6 +66,11 @@ impl Modifiers {
|
|||
shift: false,
|
||||
}
|
||||
}
|
||||
pub(crate) const ALT: Self = {
|
||||
let mut m = Self::new();
|
||||
m.alt = true;
|
||||
m
|
||||
};
|
||||
pub(crate) fn is_some(&self) -> bool {
|
||||
self.ctrl || self.alt || self.shift
|
||||
}
|
||||
|
@ -396,6 +401,33 @@ impl From<Key> for WString {
|
|||
}
|
||||
}
|
||||
|
||||
fn ctrl_to_symbol(buf: &mut WString, c: char) {
|
||||
// Most ascii control characters like \x01 are canonicalized as ctrl-a, except
|
||||
// 1. if we are explicitly given a codepoint < 32 via CSI u.
|
||||
// 2. key names that are given as raw escape sequence (\e123); those we want to display
|
||||
// similar to how they are given.
|
||||
|
||||
let ctrl_symbolic_names: [&wstr; 28] = {
|
||||
std::array::from_fn(|i| match i {
|
||||
8 => L!("\\b"),
|
||||
9 => L!("\\t"),
|
||||
10 => L!("\\n"),
|
||||
13 => L!("\\r"),
|
||||
27 => L!("\\e"),
|
||||
_ => L!(""),
|
||||
})
|
||||
};
|
||||
|
||||
let c = u8::try_from(c).unwrap();
|
||||
let cu = usize::from(c);
|
||||
|
||||
if !ctrl_symbolic_names[cu].is_empty() {
|
||||
sprintf!(=> buf, "%s", ctrl_symbolic_names[cu]);
|
||||
} else {
|
||||
sprintf!(=> buf, "\\x%02x", c);
|
||||
}
|
||||
}
|
||||
|
||||
/// Return true if the character must be escaped when used in the sequence of chars to be bound in
|
||||
/// a `bind` command.
|
||||
fn must_escape(c: char) -> bool {
|
||||
|
@ -411,13 +443,11 @@ fn ascii_printable_to_symbol(buf: &mut WString, c: char) {
|
|||
}
|
||||
|
||||
/// Convert a wide-char to a symbol that can be used in our output.
|
||||
fn char_to_symbol(c: char) -> WString {
|
||||
pub(crate) fn char_to_symbol(c: char) -> WString {
|
||||
let mut buff = WString::new();
|
||||
let buf = &mut buff;
|
||||
if c <= ' ' {
|
||||
// Most ascii control characters like \x01 are canonicalized like ctrl-a, except if we
|
||||
// are given the control character directly with CSI u.
|
||||
sprintf!(=> buf, "\\x%02x", u8::try_from(c).unwrap());
|
||||
ctrl_to_symbol(buf, c);
|
||||
} else if c < '\u{80}' {
|
||||
// ASCII characters that are not control characters
|
||||
ascii_printable_to_symbol(buf, c);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::input::{input_mappings, Inputter, DEFAULT_BIND_MODE};
|
||||
use crate::input::{input_mappings, Inputter, KeyNameStyle, DEFAULT_BIND_MODE};
|
||||
use crate::input_common::{CharEvent, ReadlineCmd};
|
||||
use crate::key::Key;
|
||||
use crate::parser::Parser;
|
||||
|
@ -26,7 +26,7 @@ fn test_input() {
|
|||
let mut input_mapping = input_mappings();
|
||||
input_mapping.add1(
|
||||
prefix_binding,
|
||||
None,
|
||||
KeyNameStyle::Plain,
|
||||
WString::from_str("up-line"),
|
||||
default_mode(),
|
||||
None,
|
||||
|
@ -34,7 +34,7 @@ fn test_input() {
|
|||
);
|
||||
input_mapping.add1(
|
||||
desired_binding.clone(),
|
||||
None,
|
||||
KeyNameStyle::Plain,
|
||||
WString::from_str("down-line"),
|
||||
default_mode(),
|
||||
None,
|
||||
|
|
|
@ -15,8 +15,8 @@ bind -M bind-mode \cX true
|
|||
bind -M bind_mode \cX true
|
||||
|
||||
# Listing bindings
|
||||
bind | string match -v '*escape,\\[*' # Hide legacy bindings.
|
||||
bind --user --preset | string match -v '*escape,\\[*'
|
||||
bind | string match -v '*\e\\[*' # Hide raw bindings.
|
||||
bind --user --preset | string match -v '*\e\\[*'
|
||||
# CHECK: bind --preset '' self-insert
|
||||
# CHECK: bind --preset enter execute
|
||||
# CHECK: bind --preset tab complete
|
||||
|
@ -55,7 +55,7 @@ bind --user --preset | string match -v '*escape,\\[*'
|
|||
# CHECK: bind -M bind_mode ctrl-x true
|
||||
|
||||
# Preset only
|
||||
bind --preset | string match -v '*escape,\\[*'
|
||||
bind --preset | string match -v '*\e\\[*'
|
||||
# CHECK: bind --preset '' self-insert
|
||||
# CHECK: bind --preset enter execute
|
||||
# CHECK: bind --preset tab complete
|
||||
|
@ -75,12 +75,12 @@ bind --preset | string match -v '*escape,\\[*'
|
|||
# CHECK: bind --preset ctrl-f forward-char
|
||||
|
||||
# User only
|
||||
bind --user | string match -v '*escape,\\[*'
|
||||
bind --user | string match -v '*\e\\[*'
|
||||
# CHECK: bind -M bind_mode ctrl-x true
|
||||
|
||||
# Adding bindings
|
||||
bind tab 'echo banana'
|
||||
bind | string match -v '*escape,\\[*'
|
||||
bind | string match -v '*\e\\[*'
|
||||
# CHECK: bind --preset '' self-insert
|
||||
# CHECK: bind --preset enter execute
|
||||
# CHECK: bind --preset tab complete
|
||||
|
|
Loading…
Reference in New Issue
Block a user