mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-05-31 20:31:19 -03: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:
@@ -7,9 +7,9 @@
|
||||
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 @@ fn list_one(
|
||||
) -> 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 @@ fn list_one(
|
||||
&mut ecmds,
|
||||
user,
|
||||
&mut sets_mode,
|
||||
&mut terminfo_name,
|
||||
&mut key_name_style,
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@@ -115,21 +115,39 @@ fn list_one(
|
||||
}
|
||||
}
|
||||
|
||||
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(' ');
|
||||
// Append the name.
|
||||
for (i, key) in seq.iter().enumerate() {
|
||||
if i != 0 {
|
||||
out.push(key::KEY_SEPARATOR);
|
||||
out.push(' ');
|
||||
match key_name_style {
|
||||
KeyNameStyle::Plain => {
|
||||
// Append the name.
|
||||
for (i, key) in seq.iter().enumerate() {
|
||||
if i != 0 {
|
||||
out.push(key::KEY_SEPARATOR);
|
||||
}
|
||||
out.push_utfstr(&WString::from(*key));
|
||||
}
|
||||
if seq.is_empty() {
|
||||
out.push_str("''");
|
||||
}
|
||||
out.push_utfstr(&WString::from(*key));
|
||||
}
|
||||
if seq.is_empty() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,22 +254,23 @@ fn add(
|
||||
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 @@ fn insert(
|
||||
&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 @@ fn new(
|
||||
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 @@ fn new(
|
||||
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 @@ pub fn add(
|
||||
}
|
||||
|
||||
// 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 @@ pub fn add(
|
||||
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 @@ pub fn add1(
|
||||
) {
|
||||
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 @@ pub fn get<'a>(
|
||||
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 @@ pub fn get<'a>(
|
||||
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 @@ const fn new() -> Self {
|
||||
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 @@ fn from(key: Key) -> Self {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user