fish-shell/src/input.rs
Johannes Altmanninger 15cd74a3bb Fix fish_escape_delay_ms for terminals that send CSI 27 u
See the parent commit for some context.  Turns out that 8bf8b10f6 (Extended &
human-friendly keys, 2024-03-30) broke this for terminals that speak CSI u.
This is pretty complex, probably not worth it.
2024-04-10 22:39:33 +02:00

1194 lines
44 KiB
Rust

use crate::common::{escape, get_by_sorted_name, shell_modes, str2wcstring, Named};
use crate::curses;
use crate::env::{Environment, CURSES_INITIALIZED};
use crate::event;
use crate::flog::FLOG;
use crate::input_common::{
CharEvent, CharInputStyle, InputEventQueuer, ReadlineCmd, R_END_INPUT_FUNCTIONS,
WAIT_ON_ESCAPE_MS,
};
use crate::key::{self, canonicalize_raw_escapes, ctrl, Key, Modifiers};
use crate::parser::Parser;
use crate::proc::job_reap;
use crate::reader::{
reader_reading_interrupted, reader_reset_interrupted, reader_schedule_prompt_repaint,
};
use crate::signal::signal_clear_cancel;
use crate::threads::assert_is_main_thread;
use crate::wchar::prelude::*;
use once_cell::sync::{Lazy, OnceCell};
use std::cell::RefCell;
use std::collections::VecDeque;
use std::ffi::CString;
use std::os::fd::RawFd;
use std::rc::Rc;
use std::sync::{
atomic::{AtomicU32, Ordering},
Arc, Mutex, MutexGuard,
};
pub const FISH_BIND_MODE_VAR: &wstr = L!("fish_bind_mode");
pub const DEFAULT_BIND_MODE: &wstr = L!("default");
/// A name for our own key mapping for nul.
pub const NUL_MAPPING_NAME: &wstr = L!("nul");
#[derive(Debug, Clone)]
pub struct InputMappingName {
pub seq: Vec<Key>,
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 {
/// Character sequence which generates this event.
seq: Vec<Key>,
/// Commands that should be evaluated by this mapping.
commands: Vec<WString>,
/// 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,
/// New mode that should be switched to after command evaluation, or None to leave the mode unchanged.
sets_mode: Option<WString>,
/// Perhaps this binding was created using a raw escape sequence or terminfo.
key_name_style: KeyNameStyle,
}
impl InputMapping {
/// Create a new mapping.
fn new(
seq: Vec<Key>,
commands: Vec<WString>,
mode: WString,
sets_mode: 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);
assert!(
sets_mode.is_none() || !mode.is_empty(),
"sets_mode set but mode is empty"
);
InputMapping {
seq,
commands,
specification_order,
mode,
sets_mode,
key_name_style,
}
}
/// \return true if this is a generic mapping, i.e. acts as a fallback.
fn is_generic(&self) -> bool {
self.seq.is_empty()
}
}
/// A struct representing the mapping from a terminfo key name to a terminfo character sequence.
#[derive(Debug)]
struct TerminfoMapping {
// name of key
name: &'static wstr,
// character sequence generated on keypress, or none if there was no mapping.
seq: Option<Box<[u8]>>,
}
/// Input function metadata. This list should be kept in sync with the key code list in
/// input_common.rs.
struct InputFunctionMetadata {
name: &'static wstr,
code: ReadlineCmd,
}
impl Named for InputFunctionMetadata {
fn name(&self) -> &'static wstr {
self.name
}
}
/// Helper to create a new InputFunctionMetadata struct.
const fn make_md(name: &'static wstr, code: ReadlineCmd) -> InputFunctionMetadata {
InputFunctionMetadata { name, code }
}
/// A static mapping of all readline commands as strings to their ReadlineCmd equivalent.
/// Keep this list sorted alphabetically!
#[rustfmt::skip]
const INPUT_FUNCTION_METADATA: &[InputFunctionMetadata] = &[
// NULL makes it unusable - this is specially inserted when we detect mouse input
make_md(L!(""), ReadlineCmd::DisableMouseTracking),
make_md(L!(""), ReadlineCmd::FocusIn),
make_md(L!(""), ReadlineCmd::FocusOut),
make_md(L!("accept-autosuggestion"), ReadlineCmd::AcceptAutosuggestion),
make_md(L!("and"), ReadlineCmd::FuncAnd),
make_md(L!("backward-bigword"), ReadlineCmd::BackwardBigword),
make_md(L!("backward-char"), ReadlineCmd::BackwardChar),
make_md(L!("backward-char-passive"), ReadlineCmd::BackwardCharPassive),
make_md(L!("backward-delete-char"), ReadlineCmd::BackwardDeleteChar),
make_md(L!("backward-jump"), ReadlineCmd::BackwardJump),
make_md(L!("backward-jump-till"), ReadlineCmd::BackwardJumpTill),
make_md(L!("backward-kill-bigword"), ReadlineCmd::BackwardKillBigword),
make_md(L!("backward-kill-line"), ReadlineCmd::BackwardKillLine),
make_md(L!("backward-kill-path-component"), ReadlineCmd::BackwardKillPathComponent),
make_md(L!("backward-kill-word"), ReadlineCmd::BackwardKillWord),
make_md(L!("backward-word"), ReadlineCmd::BackwardWord),
make_md(L!("begin-selection"), ReadlineCmd::BeginSelection),
make_md(L!("begin-undo-group"), ReadlineCmd::BeginUndoGroup),
make_md(L!("beginning-of-buffer"), ReadlineCmd::BeginningOfBuffer),
make_md(L!("beginning-of-history"), ReadlineCmd::BeginningOfHistory),
make_md(L!("beginning-of-line"), ReadlineCmd::BeginningOfLine),
make_md(L!("cancel"), ReadlineCmd::Cancel),
make_md(L!("cancel-commandline"), ReadlineCmd::CancelCommandline),
make_md(L!("capitalize-word"), ReadlineCmd::CapitalizeWord),
make_md(L!("clear-screen"), ReadlineCmd::ClearScreenAndRepaint),
make_md(L!("complete"), ReadlineCmd::Complete),
make_md(L!("complete-and-search"), ReadlineCmd::CompleteAndSearch),
make_md(L!("delete-char"), ReadlineCmd::DeleteChar),
make_md(L!("delete-or-exit"), ReadlineCmd::DeleteOrExit),
make_md(L!("down-line"), ReadlineCmd::DownLine),
make_md(L!("downcase-word"), ReadlineCmd::DowncaseWord),
make_md(L!("end-of-buffer"), ReadlineCmd::EndOfBuffer),
make_md(L!("end-of-history"), ReadlineCmd::EndOfHistory),
make_md(L!("end-of-line"), ReadlineCmd::EndOfLine),
make_md(L!("end-selection"), ReadlineCmd::EndSelection),
make_md(L!("end-undo-group"), ReadlineCmd::EndUndoGroup),
make_md(L!("execute"), ReadlineCmd::Execute),
make_md(L!("exit"), ReadlineCmd::Exit),
make_md(L!("expand-abbr"), ReadlineCmd::ExpandAbbr),
make_md(L!("force-repaint"), ReadlineCmd::ForceRepaint),
make_md(L!("forward-bigword"), ReadlineCmd::ForwardBigword),
make_md(L!("forward-char"), ReadlineCmd::ForwardChar),
make_md(L!("forward-char-passive"), ReadlineCmd::ForwardCharPassive),
make_md(L!("forward-jump"), ReadlineCmd::ForwardJump),
make_md(L!("forward-jump-till"), ReadlineCmd::ForwardJumpTill),
make_md(L!("forward-single-char"), ReadlineCmd::ForwardSingleChar),
make_md(L!("forward-word"), ReadlineCmd::ForwardWord),
make_md(L!("history-pager"), ReadlineCmd::HistoryPager),
make_md(L!("history-pager-delete"), ReadlineCmd::HistoryPagerDelete),
make_md(L!("history-prefix-search-backward"), ReadlineCmd::HistoryPrefixSearchBackward),
make_md(L!("history-prefix-search-forward"), ReadlineCmd::HistoryPrefixSearchForward),
make_md(L!("history-search-backward"), ReadlineCmd::HistorySearchBackward),
make_md(L!("history-search-forward"), ReadlineCmd::HistorySearchForward),
make_md(L!("history-token-search-backward"), ReadlineCmd::HistoryTokenSearchBackward),
make_md(L!("history-token-search-forward"), ReadlineCmd::HistoryTokenSearchForward),
make_md(L!("insert-line-over"), ReadlineCmd::InsertLineOver),
make_md(L!("insert-line-under"), ReadlineCmd::InsertLineUnder),
make_md(L!("kill-bigword"), ReadlineCmd::KillBigword),
make_md(L!("kill-inner-line"), ReadlineCmd::KillInnerLine),
make_md(L!("kill-line"), ReadlineCmd::KillLine),
make_md(L!("kill-selection"), ReadlineCmd::KillSelection),
make_md(L!("kill-whole-line"), ReadlineCmd::KillWholeLine),
make_md(L!("kill-word"), ReadlineCmd::KillWord),
make_md(L!("nextd-or-forward-word"), ReadlineCmd::NextdOrForwardWord),
make_md(L!("or"), ReadlineCmd::FuncOr),
make_md(L!("pager-toggle-search"), ReadlineCmd::PagerToggleSearch),
make_md(L!("prevd-or-backward-word"), ReadlineCmd::PrevdOrBackwardWord),
make_md(L!("redo"), ReadlineCmd::Redo),
make_md(L!("repaint"), ReadlineCmd::Repaint),
make_md(L!("repaint-mode"), ReadlineCmd::RepaintMode),
make_md(L!("repeat-jump"), ReadlineCmd::RepeatJump),
make_md(L!("repeat-jump-reverse"), ReadlineCmd::ReverseRepeatJump),
make_md(L!("self-insert"), ReadlineCmd::SelfInsert),
make_md(L!("self-insert-notfirst"), ReadlineCmd::SelfInsertNotFirst),
make_md(L!("suppress-autosuggestion"), ReadlineCmd::SuppressAutosuggestion),
make_md(L!("swap-selection-start-stop"), ReadlineCmd::SwapSelectionStartStop),
make_md(L!("togglecase-char"), ReadlineCmd::TogglecaseChar),
make_md(L!("togglecase-selection"), ReadlineCmd::TogglecaseSelection),
make_md(L!("transpose-chars"), ReadlineCmd::TransposeChars),
make_md(L!("transpose-words"), ReadlineCmd::TransposeWords),
make_md(L!("undo"), ReadlineCmd::Undo),
make_md(L!("up-line"), ReadlineCmd::UpLine),
make_md(L!("upcase-word"), ReadlineCmd::UpcaseWord),
make_md(L!("yank"), ReadlineCmd::Yank),
make_md(L!("yank-pop"), ReadlineCmd::YankPop),
];
assert_sorted_by_name!(INPUT_FUNCTION_METADATA);
const fn _assert_sizes_match() {
let input_function_count = R_END_INPUT_FUNCTIONS;
assert!(
INPUT_FUNCTION_METADATA.len() == input_function_count,
concat!(
"input_function_metadata size mismatch with input_common. ",
"Did you forget to update input_function_metadata?"
)
);
}
const _: () = _assert_sizes_match();
// Keep this function for debug purposes
// See 031b265
#[allow(dead_code)]
pub fn describe_char(c: i32) -> WString {
if c > 0 && (c as usize) < R_END_INPUT_FUNCTIONS {
return sprintf!("%02x (%ls)", c, INPUT_FUNCTION_METADATA[c as usize].name);
}
return sprintf!("%02x", c);
}
/// The input mapping set is the set of mappings from character sequences to commands.
#[derive(Debug, Default)]
pub struct InputMappingSet {
mapping_list: Vec<InputMapping>,
preset_mapping_list: Vec<InputMapping>,
all_mappings_cache: RefCell<Option<Arc<Box<[InputMapping]>>>>,
}
/// Access the singleton input mapping set.
pub fn input_mappings() -> MutexGuard<'static, InputMappingSet> {
static INPUT_MAPPINGS: Lazy<Mutex<InputMappingSet>> =
Lazy::new(|| Mutex::new(InputMappingSet::default()));
INPUT_MAPPINGS.lock().unwrap()
}
/// Terminfo map list.
static TERMINFO_MAPPINGS: OnceCell<Box<[TerminfoMapping]>> = OnceCell::new();
/// Return the current bind mode.
fn input_get_bind_mode(vars: &dyn Environment) -> WString {
if let Some(mode) = vars.get(FISH_BIND_MODE_VAR) {
mode.as_string()
} else {
DEFAULT_BIND_MODE.to_owned()
}
}
/// Returns the arity of a given input function.
fn input_function_arity(function: ReadlineCmd) -> usize {
match function {
ReadlineCmd::ForwardJump
| ReadlineCmd::BackwardJump
| ReadlineCmd::ForwardJumpTill
| ReadlineCmd::BackwardJumpTill => 1,
_ => 0,
}
}
/// Inserts an input mapping at the correct position. We sort them in descending order by length, so
/// that we test longer sequences first.
fn input_mapping_insert_sorted(ml: &mut Vec<InputMapping>, new_mapping: InputMapping) {
let new_mapping_len = new_mapping.seq.len();
let pos = ml
.binary_search_by(|m| m.seq.len().cmp(&new_mapping_len).reverse())
.unwrap_or_else(|e| e);
ml.insert(pos, new_mapping);
}
impl InputMappingSet {
/// Adds an input mapping.
pub fn add(
&mut self,
sequence: Vec<Key>,
key_name_style: KeyNameStyle,
commands: Vec<WString>,
mode: WString,
sets_mode: Option<WString>,
user: bool,
) {
// Clear cached mappings.
self.all_mappings_cache = RefCell::new(None);
// Update any existing mapping with this sequence.
// FIXME: this makes adding multiple bindings quadratic.
let ml = if user {
&mut self.mapping_list
} else {
&mut self.preset_mapping_list
};
for m in ml.iter_mut() {
if m.seq == sequence && m.mode == mode {
m.commands = commands;
m.sets_mode = sets_mode;
return;
}
}
// Add a new mapping, using the next order.
let new_mapping = InputMapping::new(sequence, commands, mode, sets_mode, key_name_style);
input_mapping_insert_sorted(ml, new_mapping);
}
// Like add(), but takes a single command.
pub fn add1(
&mut self,
sequence: Vec<Key>,
key_name_style: KeyNameStyle,
command: WString,
mode: WString,
sets_mode: Option<WString>,
user: bool,
) {
self.add(
sequence,
key_name_style,
vec![command],
mode,
sets_mode,
user,
);
}
}
/// Set up arrays used by readch to detect escape sequences for special keys and perform related
/// initializations for our input subsystem.
pub fn init_input() {
assert_is_main_thread();
if TERMINFO_MAPPINGS.get().is_some() {
return;
}
TERMINFO_MAPPINGS.set(create_input_terminfo()).unwrap();
let mut input_mapping = input_mappings();
// If we have no keybindings, add a few simple defaults.
if input_mapping.preset_mapping_list.is_empty() {
// Helper for adding.
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, KeyNameStyle::Plain, cmd.into(), mode, sets_mode, false);
};
add(vec![], "self-insert");
add(vec![Key::from_raw(key::Enter)], "execute");
add(vec![Key::from_raw(key::Tab)], "complete");
add(vec![ctrl('c')], "cancel-commandline");
add(vec![ctrl('d')], "exit");
add(vec![ctrl('e')], "bind");
add(vec![ctrl('s')], "pager-toggle-search");
add(vec![ctrl('u')], "backward-kill-line");
add(vec![Key::from_raw(key::Backspace)], "backward-delete-char");
// Arrows - can't have functions, so *-or-search isn't available.
add(vec![Key::from_raw(key::Up)], "up-line");
add(vec![Key::from_raw(key::Down)], "down-line");
add(vec![Key::from_raw(key::Right)], "forward-char");
add(vec![Key::from_raw(key::Left)], "backward-char");
// Emacs style
add(vec![ctrl('p')], "up-line");
add(vec![ctrl('n')], "down-line");
add(vec![ctrl('b')], "backward-char");
add(vec![ctrl('f')], "forward-char");
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_raw("\x1B[A", "up-line");
add_raw("\x1B[B", "down-line");
add_raw("\x1B[C", "forward-char");
add_raw("\x1B[D", "backward-char");
}
}
/// CommandHandler is used to run commands. When a character is encountered that
/// would invoke a fish command, it is unread and CharEventType::CheckExit is returned.
/// Note the handler is not stored.
pub type CommandHandler<'a> = dyn FnMut(&[WString]) + 'a;
pub struct Inputter {
in_fd: RawFd,
queue: VecDeque<CharEvent>,
paste_buffer: Option<Vec<u8>>,
// We need a parser to evaluate bindings.
parser: Rc<Parser>,
input_function_args: Vec<char>,
function_status: bool,
// Transient storage to avoid repeated allocations.
event_storage: Vec<CharEvent>,
}
impl InputEventQueuer for Inputter {
fn get_queue(&self) -> &VecDeque<CharEvent> {
&self.queue
}
fn get_queue_mut(&mut self) -> &mut VecDeque<CharEvent> {
&mut self.queue
}
/// Return the fd corresponding to stdin.
fn get_in_fd(&self) -> RawFd {
self.in_fd
}
fn prepare_to_select(&mut self) {
// Fire any pending events and reap stray processes, including printing exit status messages.
event::fire_delayed(&self.parser);
if job_reap(&self.parser, true) {
reader_schedule_prompt_repaint();
}
}
fn select_interrupted(&mut self) {
// Readline commands may be bound to \cc which also sets the cancel flag.
// See #6937, #8125.
signal_clear_cancel();
// Fire any pending events and reap stray processes, including printing exit status messages.
let parser = &self.parser;
event::fire_delayed(parser);
if job_reap(parser, true) {
reader_schedule_prompt_repaint();
}
// Tell the reader an event occurred.
if reader_reading_interrupted() != 0 {
let vintr = shell_modes().c_cc[libc::VINTR];
if vintr != 0 {
self.push_front(CharEvent::from_key(Key::from_single_byte(vintr)));
}
return;
}
self.push_front(CharEvent::from_check_exit());
}
fn uvar_change_notified(&mut self) {
self.parser.sync_uvars_and_fire(true /* always */);
}
fn paste_start_buffering(&mut self) {
self.paste_buffer = Some(vec![]);
self.push_front(CharEvent::from_readline(ReadlineCmd::BeginUndoGroup));
}
fn paste_is_buffering(&self) -> bool {
self.paste_buffer.is_some()
}
fn paste_commit(&mut self) {
self.push_front(CharEvent::from_readline(ReadlineCmd::EndUndoGroup));
let Some(buffer) = self.paste_buffer.take() else {
return;
};
self.push_front(CharEvent::Command(sprintf!(
"__fish_paste %s",
escape(&str2wcstring(&buffer))
)));
}
fn paste_push_char(&mut self, b: u8) {
self.paste_buffer.as_mut().unwrap().push(b)
}
}
impl Inputter {
/// Construct from a parser, and the fd from which to read.
pub fn new(parser: Rc<Parser>, in_fd: RawFd) -> Inputter {
Inputter {
in_fd,
queue: VecDeque::new(),
paste_buffer: None,
parser,
input_function_args: Vec::new(),
function_status: false,
event_storage: Vec::new(),
}
}
fn function_push_arg(&mut self, arg: char) {
self.input_function_args.push(arg);
}
pub fn function_pop_arg(&mut self) -> char {
self.input_function_args
.pop()
.expect("function_pop_arg underflow")
}
fn function_push_args(&mut self, code: ReadlineCmd) {
let arity = input_function_arity(code);
assert!(
self.event_storage.is_empty(),
"event_storage should be empty"
);
let mut skipped = std::mem::take(&mut self.event_storage);
for _ in 0..arity {
// Skip and queue up any function codes. See issue #2357.
let arg: char;
loop {
let evt = self.readch();
if let Some(kevt) = evt.get_key() {
if let Some(c) = kevt.key.codepoint_text() {
// TODO forward the whole key
arg = c;
break;
}
}
skipped.push(evt);
}
self.function_push_arg(arg);
}
// Push the function codes back into the input stream.
self.insert_front(skipped.drain(..));
self.event_storage = skipped;
self.event_storage.clear();
}
/// Perform the action of the specified binding.
fn mapping_execute(&mut self, m: &InputMapping) {
let has_command = m
.commands
.iter()
.any(|cmd| input_function_get_code(cmd).is_none());
if has_command {
self.push_front(CharEvent::from_check_exit());
}
for cmd in m.commands.iter().rev() {
let evt = match input_function_get_code(cmd) {
Some(code) => {
self.function_push_args(code);
// At this point, the sequence is only used for reinserting the keys into
// the event queue for self-insert. Modifiers make no sense here so drop them.
CharEvent::from_readline_seq(
code,
m.seq
.iter()
.filter(|key| key.modifiers.is_none())
.map(|key| key.codepoint)
.collect(),
)
}
None => CharEvent::Command(cmd.clone()),
};
self.push_front(evt);
}
// Missing bind mode indicates to not reset the mode (#2871)
if let Some(mode) = m.sets_mode.as_ref() {
self.push_front(CharEvent::Command(sprintf!(
"set --global %s %s",
FISH_BIND_MODE_VAR,
escape(mode)
)));
}
}
/// Enqueue a char event to the queue of unread characters that input_readch will return before
/// actually reading from fd 0.
pub fn queue_char(&mut self, ch: CharEvent) {
self.queue.push_back(ch);
}
/// Sets the return status of the most recently executed input function.
pub fn function_set_status(&mut self, status: bool) {
self.function_status = status;
}
}
/// A struct which allows accumulating input events, or returns them to the queue.
/// This contains a list of events which have been dequeued, and a current index into that list.
struct EventQueuePeeker<'q> {
/// The list of events which have been dequeued.
peeked: Vec<CharEvent>,
/// If set, then some previous timed event timed out.
had_timeout: bool,
/// The current index. This never exceeds peeked.len().
idx: usize,
/// The current index within a the raw characters within a single key event.
subidx: usize,
/// The queue from which to read more events.
event_queue: &'q mut Inputter,
}
impl EventQueuePeeker<'_> {
fn new(event_queue: &mut Inputter) -> EventQueuePeeker {
EventQueuePeeker {
peeked: Vec::new(),
had_timeout: false,
idx: 0,
subidx: 0,
event_queue,
}
}
/// \return the next event.
fn next(&mut self) -> CharEvent {
assert!(self.subidx == 0);
assert!(
self.idx <= self.peeked.len(),
"Index must not be larger than dequeued event count"
);
if self.idx == self.peeked.len() {
let event = self.event_queue.readch();
self.peeked.push(event);
}
let res = self.peeked[self.idx].clone();
self.idx += 1;
self.subidx = 0;
res
}
/// Check if the next event is the given character. This advances the index on success only.
/// If \p escaped is set, then return false if this (or any other) character had a timeout.
fn next_is_char(&mut self, key: Key, escaped: bool) -> bool {
assert!(
self.idx <= self.peeked.len(),
"Index must not be larger than dequeued event count"
);
// See if we had a timeout already.
if escaped && self.had_timeout {
return false;
}
// Grab a new event if we have exhausted what we have already peeked.
// Use either readch or readch_timed, per our param.
if self.idx == self.peeked.len() {
let Some(newevt) = (if escaped {
self.event_queue.readch_timed_esc()
} else {
self.event_queue.readch_timed_sequence_key()
}) else {
self.had_timeout = true;
return false;
};
self.peeked.push(newevt);
}
// Now we have peeked far enough; check the event.
// If it matches the char, then increment the index.
let evt = &self.peeked[self.idx];
let Some(kevt) = evt.get_key() else {
return false;
};
if WAIT_ON_ESCAPE_MS.load(Ordering::Relaxed) != 0
&& kevt.key == Key::from_raw(key::Escape)
&& key.modifiers == Modifiers::ALT
{
self.idx += 1;
self.subidx = 0;
FLOG!(reader, "matched delayed escape prefix in alt sequence");
return self.next_is_char(Key::from_raw(key.codepoint), true);
}
if self.subidx == 0 && kevt.key == key {
self.idx += 1;
return true;
}
let actual_seq = kevt.seq.as_char_slice();
if !actual_seq.is_empty() {
let seq_char = actual_seq[self.subidx];
if Key::from_single_char(seq_char) == key {
self.subidx += 1;
if self.subidx == actual_seq.len() {
self.idx += 1;
self.subidx = 0;
}
// These FLOGs are extremely chatty because this is run a bunch of times.
// FLOG!(reader, "matched legacy sequence for", key);
return true;
}
if key.modifiers == Modifiers::ALT && seq_char == '\x1b' {
if self.subidx + 1 == actual_seq.len() {
self.idx += 1;
self.subidx = 0;
// FLOG!(reader, "matched escape prefix of legacy alt sequence for", key);
return self.next_is_char(Key::from_raw(key.codepoint), true);
} else if actual_seq
.get(self.subidx + 1)
.cloned()
.map(|c| Key::from_single_char(c).codepoint)
== Some(key.codepoint)
{
self.subidx += 2;
if self.subidx == actual_seq.len() {
self.idx += 1;
self.subidx = 0;
}
// FLOG!(reader, "matched legacy alt sequence for", key);
return true;
}
}
}
false
}
/// Consume all events up to the current index.
/// Remaining events are returned to the queue.
fn consume(mut self) {
// Note this deliberately takes 'self' by value.
self.event_queue.insert_front(self.peeked.drain(self.idx..));
self.peeked.clear();
self.idx = 0;
self.subidx = 0;
}
/// Test if any of our peeked events are readline or check_exit.
fn char_sequence_interrupted(&self) -> bool {
self.peeked
.iter()
.any(|evt| evt.is_readline_or_command() || evt.is_check_exit())
}
/// Reset our index back to 0.
fn restart(&mut self) {
self.idx = 0;
self.subidx = 0;
}
}
impl Drop for EventQueuePeeker<'_> {
fn drop(&mut self) {
assert!(
self.idx == 0 && self.subidx == 0,
"Events left on the queue - missing restart or consume?",
);
self.event_queue.insert_front(self.peeked.drain(self.idx..));
}
}
/// \return true if a given \p peeker matches a given sequence of char events given by \p str.
fn try_peek_sequence(peeker: &mut EventQueuePeeker, seq: &[Key]) -> bool {
assert!(
!seq.is_empty(),
"Empty sequence passed to try_peek_sequence"
);
let mut prev = Key::from_raw(key::Invalid);
for key in seq {
// If we just read an escape, we need to add a timeout for the next char,
// to distinguish between the actual escape key and an "alt"-modifier.
let escaped = prev == Key::from_raw(key::Escape);
if !peeker.next_is_char(*key, escaped) {
return false;
}
prev = *key;
}
if peeker.subidx != 0 {
FLOG!(
reader,
"legacy binding matched prefix of key encoding but did not consume all of it"
);
return false;
}
true
}
/// \return the first mapping that matches, walking first over the user's mapping list, then the
/// preset list.
/// \return none if nothing matches, or if we may have matched a longer sequence but it was
/// interrupted by a readline event.
impl Inputter {
fn find_mapping(vars: &dyn Environment, peeker: &mut EventQueuePeeker) -> Option<InputMapping> {
let mut generic: Option<&InputMapping> = None;
let bind_mode = input_get_bind_mode(vars);
let mut escape: Option<&InputMapping> = None;
let ml = input_mappings().all_mappings();
for m in ml.iter() {
if m.mode != bind_mode {
continue;
}
// Defer generic mappings until the end.
if m.is_generic() {
if generic.is_none() {
generic = Some(m);
}
continue;
}
if try_peek_sequence(peeker, &m.seq) {
// A binding for just escape should also be deferred
// so escape sequences take precedence.
if m.seq == vec![Key::from_raw(key::Escape)] {
if escape.is_none() {
escape = Some(m);
}
} else {
return Some(m.clone());
}
}
peeker.restart();
}
if peeker.char_sequence_interrupted() {
// We might have matched a longer sequence, but we were interrupted, e.g. by a signal.
FLOG!(reader, "torn sequence, rearranging events");
return None;
}
if escape.is_some() {
// We need to reconsume the escape.
peeker.next();
return escape.cloned();
}
if generic.is_some() {
generic.cloned()
} else {
None
}
}
fn mapping_execute_matching_or_generic(&mut self) {
let vars = self.parser.vars_ref();
let mut peeker = EventQueuePeeker::new(self);
// Check for ordinary mappings.
if let Some(mapping) = Self::find_mapping(&*vars, &mut peeker) {
FLOG!(
reader,
format!("Found mapping {:?} from {:?}", &mapping, &peeker.peeked)
);
peeker.consume();
self.mapping_execute(&mapping);
return;
}
peeker.restart();
if peeker.char_sequence_interrupted() {
// This may happen if we received a signal in the middle of an escape sequence or other
// multi-char binding. Move these non-char events to the front of the queue, handle them
// first, and then later we'll return and try the sequence again. See #8628.
peeker.consume();
self.promote_interruptions_to_front();
return;
}
FLOG!(reader, "no generic found, ignoring char...");
let _ = peeker.next();
peeker.consume();
}
/// Helper function. Picks through the queue of incoming characters until we get to one that's not a
/// readline function.
fn read_characters_no_readline(&mut self) -> CharEvent {
assert!(
self.event_storage.is_empty(),
"event_storage should be empty"
);
let mut saved_events = std::mem::take(&mut self.event_storage);
let evt_to_return: CharEvent;
loop {
let evt = self.readch();
if evt.is_readline_or_command() {
saved_events.push(evt);
} else {
evt_to_return = evt;
break;
}
}
// Restore any readline functions
self.insert_front(saved_events.drain(..));
self.event_storage = saved_events;
self.event_storage.clear();
evt_to_return
}
/// Read a key from stdin.
pub fn read_char(&mut self) -> CharEvent {
// Clear the interrupted flag.
reader_reset_interrupted();
// Search for sequence in mapping tables.
loop {
let evt = self.readch();
match evt {
CharEvent::Readline(ref readline_event) => match readline_event.cmd {
ReadlineCmd::SelfInsert | ReadlineCmd::SelfInsertNotFirst => {
// 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);
self.insert_front(seq);
// Issue #1595: ensure we only insert characters, not readline functions. The
// common case is that this will be empty.
let mut res = self.read_characters_no_readline();
// Hackish: mark the input style.
if readline_event.cmd == ReadlineCmd::SelfInsertNotFirst {
if let CharEvent::Key(kevt) = &mut res {
kevt.input_style = CharInputStyle::NotFirst;
}
}
return res;
}
ReadlineCmd::FuncAnd | ReadlineCmd::FuncOr => {
// If previous function has bad status, skip all functions that follow us.
if (!self.function_status && readline_event.cmd == ReadlineCmd::FuncAnd)
|| (self.function_status && readline_event.cmd == ReadlineCmd::FuncOr)
{
self.drop_leading_readline_events();
}
}
_ => {
return evt;
}
},
CharEvent::Command(_) => {
return evt;
}
CharEvent::Eof => {
// If we have EOF, we need to immediately quit.
// There's no need to go through the input functions.
return evt;
}
CharEvent::CheckExit => {
// Allow the reader to check for exit conditions.
return evt;
}
CharEvent::Key(ref kevt) => {
FLOG!(
reader,
"Read char",
kevt.key,
format!(
"-- {:?} -- {:?}",
kevt.key,
kevt.seq.chars().map(u32::from).collect::<Vec<_>>()
)
);
self.push_front(evt);
self.mapping_execute_matching_or_generic();
}
}
}
}
}
impl InputMappingSet {
/// Returns all mapping names and modes.
pub fn get_names(&self, user: bool) -> Vec<InputMappingName> {
// Sort the mappings by the user specification order, so we can return them in the same order
// that the user specified them in.
let mut local_list = if user {
self.mapping_list.clone()
} else {
self.preset_mapping_list.clone()
};
local_list.sort_unstable_by_key(|m| m.specification_order);
local_list
.into_iter()
.map(|m| InputMappingName {
seq: m.seq,
mode: m.mode,
})
.collect()
}
/// Erase all bindings.
pub fn clear(&mut self, mode: Option<&wstr>, user: bool) {
// Clear cached mappings.
self.all_mappings_cache = RefCell::new(None);
let ml = if user {
&mut self.mapping_list
} else {
&mut self.preset_mapping_list
};
let should_erase = |m: &InputMapping| mode.is_none() || mode.unwrap() == m.mode;
ml.retain(|m| !should_erase(m));
}
/// Erase binding for specified key sequence.
pub fn erase(&mut self, sequence: &[Key], mode: &wstr, user: bool) -> bool {
// Clear cached mappings.
self.all_mappings_cache = RefCell::new(None);
let ml = if user {
&mut self.mapping_list
} else {
&mut self.preset_mapping_list
};
let mut result = false;
for (idx, m) in ml.iter().enumerate() {
if m.seq == sequence && m.mode == mode {
ml.remove(idx);
result = true;
break;
}
}
result
}
/// Gets the command bound to the specified key sequence in the specified mode. Returns true if
/// it exists, false if not.
pub fn get<'a>(
&'a self,
sequence: &[Key],
mode: &wstr,
out_cmds: &mut &'a [WString],
user: bool,
out_sets_mode: &mut Option<&'a wstr>,
out_key_name_style: &mut KeyNameStyle,
) -> bool {
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;
}
}
false
}
/// \return a snapshot of the list of input mappings.
fn all_mappings(&self) -> Arc<Box<[InputMapping]>> {
// Populate the cache if needed.
let mut cache = self.all_mappings_cache.borrow_mut();
if cache.is_none() {
let mut all_mappings =
Vec::with_capacity(self.mapping_list.len() + self.preset_mapping_list.len());
all_mappings.extend(self.mapping_list.iter().cloned());
all_mappings.extend(self.preset_mapping_list.iter().cloned());
*cache = Some(Arc::new(all_mappings.into_boxed_slice()));
}
Arc::clone(cache.as_ref().unwrap())
}
}
/// Create a list of terminfo mappings.
fn create_input_terminfo() -> Box<[TerminfoMapping]> {
assert!(CURSES_INITIALIZED.load(Ordering::Relaxed));
let Some(term) = curses::term() else {
// setupterm() failed so we can't reference any key definitions.
return Box::new([]);
};
// Helper to convert an Option<CString> to an Option<Box<[u8]>>.
// The nul-terminator is NOT included.
fn opt_cstr_to_bytes(opt: &Option<CString>) -> Option<Box<[u8]>> {
opt.clone().map(|s| s.into_bytes().into())
}
macro_rules! terminfo_add {
($key:ident) => {
TerminfoMapping {
name: &L!(stringify!($key))[4..],
seq: opt_cstr_to_bytes(&term.$key),
}
};
}
#[rustfmt::skip]
return Box::new([
terminfo_add!(key_a1), terminfo_add!(key_a3), terminfo_add!(key_b2),
terminfo_add!(key_backspace), terminfo_add!(key_beg), terminfo_add!(key_btab),
terminfo_add!(key_c1), terminfo_add!(key_c3), terminfo_add!(key_cancel),
terminfo_add!(key_catab), terminfo_add!(key_clear), terminfo_add!(key_close),
terminfo_add!(key_command), terminfo_add!(key_copy), terminfo_add!(key_create),
terminfo_add!(key_ctab), terminfo_add!(key_dc), terminfo_add!(key_dl), terminfo_add!(key_down),
terminfo_add!(key_eic), terminfo_add!(key_end), terminfo_add!(key_enter),
terminfo_add!(key_eol), terminfo_add!(key_eos), terminfo_add!(key_exit), terminfo_add!(key_f0),
terminfo_add!(key_f1), terminfo_add!(key_f2), terminfo_add!(key_f3), terminfo_add!(key_f4),
terminfo_add!(key_f5), terminfo_add!(key_f6), terminfo_add!(key_f7), terminfo_add!(key_f8),
terminfo_add!(key_f9), terminfo_add!(key_f10), terminfo_add!(key_f11), terminfo_add!(key_f12),
terminfo_add!(key_find), terminfo_add!(key_help), terminfo_add!(key_home),
terminfo_add!(key_ic), terminfo_add!(key_il), terminfo_add!(key_left), terminfo_add!(key_ll),
terminfo_add!(key_mark), terminfo_add!(key_message), terminfo_add!(key_move),
terminfo_add!(key_next), terminfo_add!(key_npage), terminfo_add!(key_open),
terminfo_add!(key_options), terminfo_add!(key_ppage), terminfo_add!(key_previous),
terminfo_add!(key_print), terminfo_add!(key_redo), terminfo_add!(key_reference),
terminfo_add!(key_refresh), terminfo_add!(key_replace), terminfo_add!(key_restart),
terminfo_add!(key_resume), terminfo_add!(key_right), terminfo_add!(key_save),
terminfo_add!(key_sbeg), terminfo_add!(key_scancel), terminfo_add!(key_scommand),
terminfo_add!(key_scopy), terminfo_add!(key_screate), terminfo_add!(key_sdc),
terminfo_add!(key_sdl), terminfo_add!(key_select), terminfo_add!(key_send),
terminfo_add!(key_seol), terminfo_add!(key_sexit), terminfo_add!(key_sf),
terminfo_add!(key_sfind), terminfo_add!(key_shelp), terminfo_add!(key_shome),
terminfo_add!(key_sic), terminfo_add!(key_sleft), terminfo_add!(key_smessage),
terminfo_add!(key_smove), terminfo_add!(key_snext), terminfo_add!(key_soptions),
terminfo_add!(key_sprevious), terminfo_add!(key_sprint), terminfo_add!(key_sr),
terminfo_add!(key_sredo), terminfo_add!(key_sreplace), terminfo_add!(key_sright),
terminfo_add!(key_srsume), terminfo_add!(key_ssave), terminfo_add!(key_ssuspend),
terminfo_add!(key_stab), terminfo_add!(key_sundo), terminfo_add!(key_suspend),
terminfo_add!(key_undo), terminfo_add!(key_up),
// We introduce our own name for the string containing only the nul character - see
// #3189. This can typically be generated via control-space.
TerminfoMapping { name: NUL_MAPPING_NAME, seq: Some(Box::new([0])) },
]);
}
/// Possible errors from from input_terminfo_get_sequence.
pub enum GetSequenceError {
/// The mapping was not found.
NotFound,
/// The terminfo variable does not have a value.
NoSeq,
}
/// Return the sequence for the terminfo variable of the specified name.
///
/// If no terminfo variable of the specified name could be found, return false and set errno to
/// ENOENT. If the terminfo variable does not have a value, return false and set errno to EILSEQ.
pub fn input_terminfo_get_sequence(name: &wstr) -> Result<WString, GetSequenceError> {
let mappings = TERMINFO_MAPPINGS
.get()
.expect("TERMINFO_MAPPINGS not initialized");
for m in mappings.iter() {
if name == m.name {
// Found the mapping.
return if let Some(seq) = &m.seq {
Ok(str2wcstring(seq))
} else {
Err(GetSequenceError::NoSeq)
};
}
}
Err(GetSequenceError::NotFound)
}
/// Return the name of the terminfo variable with the specified sequence.
pub fn input_terminfo_get_name(seq: &wstr) -> Option<WString> {
let mappings = TERMINFO_MAPPINGS
.get()
.expect("TERMINFO_MAPPINGS not initialized");
for m in mappings.iter() {
if m.seq.is_some() && seq == str2wcstring(m.seq.as_ref().unwrap()) {
return Some(m.name.to_owned());
}
}
None
}
/// Return a list of all known terminfo names.
pub fn input_terminfo_get_names(skip_null: bool) -> Vec<WString> {
let mappings = TERMINFO_MAPPINGS
.get()
.expect("TERMINFO_MAPPINGS not initialized");
let mut result = Vec::with_capacity(mappings.len());
for m in mappings.iter() {
if skip_null && m.seq.is_none() {
continue;
}
result.push(m.name.to_owned());
}
result
}
/// Returns a list of all existing input function names.
pub fn input_function_get_names() -> Vec<&'static wstr> {
// Note: the C++ cached this, but we don't to save memory.
INPUT_FUNCTION_METADATA
.iter()
.filter(|&md| !md.name.is_empty())
.map(|md| md.name)
.collect()
}
pub fn input_function_get_code(name: &wstr) -> Option<ReadlineCmd> {
// `input_function_metadata` is required to be kept in asciibetical order, making it OK to do
// a binary search for the matching name.
get_by_sorted_name(name, INPUT_FUNCTION_METADATA).map(|md| md.code)
}