use crate::{
env::Environment,
flog::flog,
global_safety::RelaxedAtomicBool,
input_common::{
match_key_event_to_key, CharEvent, CharInputStyle, ImplicitEvent, InputEventQueuer,
KeyMatchQuality, ReadlineCmd, R_END_INPUT_FUNCTIONS,
},
key::{self, canonicalize_raw_escapes, ctrl, Key, Modifiers},
prelude::*,
reader::{reader_reset_interrupted, Reader},
threads::assert_is_main_thread,
};
use fish_common::{assert_sorted_by_name, escape, get_by_sorted_name, Named};
use std::{
mem,
sync::{
atomic::{AtomicU32, Ordering},
Mutex, MutexGuard,
},
};
pub const FISH_BIND_MODE_VAR: &wstr = L!("fish_bind_mode");
pub const DEFAULT_BIND_MODE: &wstr = L!("default");
pub const NUL_MAPPING_NAME: &wstr = L!("nul");
#[derive(Debug, Clone)]
pub struct InputMappingName {
pub seq: Vec<Key>,
pub mode: WString,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum KeyNameStyle {
Plain,
RawEscapeSequence,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InputMapping {
seq: Vec<Key>,
pub commands: Vec<WString>,
specification_order: u32,
pub mode: WString,
pub sets_mode: Option<WString>,
pub key_name_style: KeyNameStyle,
}
impl InputMapping {
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,
}
}
fn is_generic(&self) -> bool {
self.seq.is_empty()
}
}
struct InputFunctionMetadata {
name: &'static wstr,
code: ReadlineCmd,
}
impl Named for InputFunctionMetadata {
fn name(&self) -> &'static wstr {
self.name
}
}
const fn make_md(name: &'static wstr, code: ReadlineCmd) -> InputFunctionMetadata {
InputFunctionMetadata { name, code }
}
#[rustfmt::skip]
const INPUT_FUNCTION_METADATA: &[InputFunctionMetadata] = &[
make_md(L!("accept-autosuggestion"), ReadlineCmd::AcceptAutosuggestion),
make_md(L!("and"), ReadlineCmd::FuncAnd),
make_md(L!("backward-bigword"), ReadlineCmd::BackwardBigword),
make_md(L!("backward-bigword-end"), ReadlineCmd::BackwardBigwordEnd),
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-token"), ReadlineCmd::BackwardKillToken),
make_md(L!("backward-kill-word"), ReadlineCmd::BackwardKillWord),
make_md(L!("backward-path-component"), ReadlineCmd::BackwardPathComponent),
make_md(L!("backward-token"), ReadlineCmd::BackwardToken),
make_md(L!("backward-word"), ReadlineCmd::BackwardWord),
make_md(L!("backward-word-end"), ReadlineCmd::BackwardWordEnd),
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-commandline"), ReadlineCmd::ClearCommandline),
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-selection"), ReadlineCmd::DowncaseSelection),
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::ForwardBigwordEmacs),
make_md(L!("forward-bigword-end"), ReadlineCmd::ForwardBigwordEnd),
make_md(L!("forward-bigword-vi"), ReadlineCmd::ForwardBigwordVi),
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-path-component"), ReadlineCmd::ForwardPathComponent),
make_md(L!("forward-single-char"), ReadlineCmd::ForwardSingleChar),
make_md(L!("forward-token"), ReadlineCmd::ForwardToken),
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),
make_md(L!("history-pager"), ReadlineCmd::HistoryPager),
#[allow(deprecated)]
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!("jump-till-matching-bracket"), ReadlineCmd::JumpTillMatchingBracket),
make_md(L!("jump-to-matching-bracket"), ReadlineCmd::JumpToMatchingBracket),
make_md(L!("kill-a-bigword"), ReadlineCmd::KillABigWord),
make_md(L!("kill-a-word"), ReadlineCmd::KillAWord),
make_md(L!("kill-bigword"), ReadlineCmd::KillBigwordEmacs),
make_md(L!("kill-bigword-vi"), ReadlineCmd::KillBigwordVi),
make_md(L!("kill-inner-bigword"), ReadlineCmd::KillInnerBigWord),
make_md(L!("kill-inner-line"), ReadlineCmd::KillInnerLine),
make_md(L!("kill-inner-word"), ReadlineCmd::KillInnerWord),
make_md(L!("kill-line"), ReadlineCmd::KillLine),
make_md(L!("kill-path-component"), ReadlineCmd::KillPathComponent),
make_md(L!("kill-selection"), ReadlineCmd::KillSelection),
make_md(L!("kill-token"), ReadlineCmd::KillToken),
make_md(L!("kill-whole-line"), ReadlineCmd::KillWholeLine),
make_md(L!("kill-word"), ReadlineCmd::KillWordEmacs),
make_md(L!("kill-word-vi"), ReadlineCmd::KillWordVi),
make_md(L!("nextd-or-forward-word"), ReadlineCmd::NextdOrForwardWordEmacs),
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!("scrollback-push"), ReadlineCmd::ScrollbackPush),
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-selection"), ReadlineCmd::UpcaseSelection),
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();
#[derive(Debug, Default)]
pub struct InputMappingSet {
mapping_list: Vec<InputMapping>,
preset_mapping_list: Vec<InputMapping>,
}
impl InputMappingSet {
const fn new() -> Self {
Self {
mapping_list: Vec::new(),
preset_mapping_list: Vec::new(),
}
}
}
pub fn input_mappings() -> MutexGuard<'static, InputMappingSet> {
static INPUT_MAPPINGS: Mutex<InputMappingSet> = Mutex::new(InputMappingSet::new());
INPUT_MAPPINGS.lock().unwrap()
}
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()
}
}
fn input_function_arity(function: ReadlineCmd) -> usize {
match function {
ReadlineCmd::ForwardJump
| ReadlineCmd::BackwardJump
| ReadlineCmd::ForwardJumpTill
| ReadlineCmd::BackwardJumpTill => 1,
_ => 0,
}
}
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 {
pub fn add(
&mut self,
sequence: Vec<Key>,
key_name_style: KeyNameStyle,
commands: Vec<WString>,
mode: WString,
sets_mode: Option<WString>,
user: bool,
) {
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;
}
}
let new_mapping = InputMapping::new(sequence, commands, mode, sets_mode, key_name_style);
input_mapping_insert_sorted(ml, new_mapping);
}
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,
);
}
}
pub fn init_input() {
assert_is_main_thread();
static DONE: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
if DONE.swap(true) {
return;
}
let mut input_mapping = input_mappings();
if input_mapping.preset_mapping_list.is_empty() {
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");
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");
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");
}
}
pub struct EventQueuePeeker<'q, Queuer: InputEventQueuer + ?Sized> {
peeked: Vec<CharEvent>,
had_timeout: bool,
idx: usize,
subidx: usize,
event_queue: &'q mut Queuer,
}
impl<'q, Queuer: InputEventQueuer + ?Sized> EventQueuePeeker<'q, Queuer> {
pub fn new(event_queue: &'q mut Queuer) -> Self {
EventQueuePeeker {
peeked: Vec::new(),
had_timeout: false,
idx: 0,
subidx: 0,
event_queue,
}
}
fn next(&mut self) -> CharEvent {
assert_eq!(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
}
fn next_is_char(
&mut self,
style: &KeyNameStyle,
key: Key,
escaped: bool,
) -> Option<KeyMatchQuality> {
assert!(
self.idx <= self.peeked.len(),
"Index must not be larger than dequeued event count"
);
if escaped && self.had_timeout {
return None;
}
if self.idx == self.peeked.len() {
let newevt = if escaped {
flog!(reader, "reading timed escape");
match self.event_queue.readch_timed_esc() {
Some(evt) => evt,
None => {
self.had_timeout = true;
return None;
}
}
} else {
flog!(reader, "readch timed sequence key");
match self.event_queue.readch_timed_sequence_key() {
Some(evt) => evt,
None => {
self.had_timeout = true;
return None;
}
}
};
flog!(reader, format!("adding peeked {:?}", newevt));
self.peeked.push(newevt);
}
let evt = &self.peeked[self.idx];
let kevt = evt.get_key()?;
if kevt.seq == L!("\x1b") && key.modifiers == Modifiers::ALT {
self.idx += 1;
self.subidx = 0;
flog!(reader, "matched delayed escape prefix in alt sequence");
return self.next_is_char(style, Key::from_raw(key.codepoint), true);
}
if *style == KeyNameStyle::Plain {
let result = match_key_event_to_key(&kevt.key, &key);
if let Some(key_match) = &result {
assert_eq!(self.subidx, 0);
self.idx += 1;
flog!(reader, "matched full key", key, "kind", key_match);
}
return result;
}
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;
}
flog!(
reader,
format!(
"matched char {} with offset {} within raw sequence of length {}",
key,
self.subidx,
actual_seq.len()
)
);
return Some(KeyMatchQuality::Exact);
}
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 in raw escape sequence");
return self.next_is_char(style, Key::from_raw(key.codepoint), true);
} else if actual_seq
.get(self.subidx + 1)
.copied()
.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, format!("matched {key} against raw escape sequence"));
return Some(KeyMatchQuality::Exact);
}
}
}
None
}
fn consume(mut self) {
self.event_queue.insert_front(self.peeked.drain(self.idx..));
self.peeked.clear();
self.idx = 0;
self.subidx = 0;
}
fn char_sequence_interrupted(&self) -> bool {
self.peeked.iter().any(|evt| {
evt.is_readline_or_command()
|| matches!(evt, CharEvent::Implicit(ImplicitEvent::CheckExit))
})
}
pub fn restart(&mut self) {
self.idx = 0;
self.subidx = 0;
}
fn try_peek_sequence(
&mut self,
style: &KeyNameStyle,
seq: &[Key],
quality: &mut Vec<KeyMatchQuality>,
) -> bool {
assert!(
!seq.is_empty(),
"Empty sequence passed to try_peek_sequence"
);
let mut prev = Key::from_raw(key::INVALID);
for key in seq {
let escaped = *style != KeyNameStyle::Plain && prev == Key::from_raw(key::ESCAPE);
let Some(spec) = self.next_is_char(style, *key, escaped) else {
return false;
};
quality.push(spec);
prev = *key;
}
if self.subidx != 0 {
flog!(
reader,
"legacy binding matched prefix of key encoding but did not consume all of it"
);
return false;
}
true
}
pub fn find_mapping<'a>(
&mut self,
vars: &dyn Environment,
ip: &'a InputMappingSet,
) -> Option<InputMapping> {
let bind_mode = input_get_bind_mode(vars);
struct MatchedMapping<'a> {
mapping: &'a InputMapping,
quality: Vec<KeyMatchQuality>,
idx: usize,
subidx: usize,
}
let mut deferred: Option<MatchedMapping<'a>> = None;
let ml = ip.mapping_list.iter().chain(ip.preset_mapping_list.iter());
let mut quality = vec![];
for m in ml {
if m.mode != bind_mode {
continue;
}
if m.is_generic() {
if deferred.is_none() {
deferred = Some(MatchedMapping {
mapping: m,
quality: vec![],
idx: self.idx,
subidx: self.subidx,
});
}
continue;
}
if self.try_peek_sequence(&m.key_name_style, &m.seq, &mut quality) {
let is_escape = m.seq == vec![Key::from_raw(key::ESCAPE)];
let is_perfect_match = quality
.iter()
.all(|key_match| *key_match == KeyMatchQuality::Exact);
if !is_escape && is_perfect_match {
return Some(m.clone());
}
if deferred
.as_ref()
.is_none_or(|matched| !is_escape && quality >= matched.quality)
{
deferred = Some(MatchedMapping {
mapping: m,
quality: mem::take(&mut quality),
idx: self.idx,
subidx: self.subidx,
});
}
}
quality.clear();
self.restart();
}
if self.char_sequence_interrupted() {
flog!(reader, "torn sequence, rearranging events");
return None;
}
deferred
.map(|matched| {
self.idx = matched.idx;
self.subidx = matched.subidx;
matched.mapping
})
.cloned()
}
}
impl<Queue: InputEventQueuer + ?Sized> Drop for EventQueuePeeker<'_, Queue> {
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..));
}
}
impl<'a> Reader<'a> {
pub fn read_char(&mut self) -> CharEvent {
reader_reset_interrupted();
loop {
let evt = self.readch();
match evt {
CharEvent::Readline(ref readline_event) => match readline_event.cmd {
ReadlineCmd::SelfInsert
| ReadlineCmd::SelfInsertNotFirst
| ReadlineCmd::GetKey => {
let seq = readline_event.seq.chars().map(CharEvent::from_char);
self.insert_front(seq);
let mut res = self.read_character_matching(|evt| {
use CharEvent::*;
use ImplicitEvent::*;
match evt {
Key(_) => true,
Implicit(Eof) => true,
Readline(_) | Command(_) | Implicit(_) | QueryResult(_) => false,
}
});
if readline_event.cmd == ReadlineCmd::SelfInsertNotFirst {
if let CharEvent::Key(kevt) = &mut res {
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 => {
let fs = self.get_function_status();
if (!fs && readline_event.cmd == ReadlineCmd::FuncAnd)
|| (fs && readline_event.cmd == ReadlineCmd::FuncOr)
{
self.drop_leading_readline_events();
}
}
_ => {
return evt;
}
},
CharEvent::Command(_) => {
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();
}
CharEvent::Implicit(_) | CharEvent::QueryResult(_) => {
return evt;
}
}
}
}
fn mapping_execute_matching_or_generic(&mut self) {
let vars = self.parser.vars();
let mut peeker = EventQueuePeeker::new(self);
let ip = input_mappings();
if let Some(mapping) = peeker.find_mapping(vars, &ip) {
flog!(
reader,
format!("Found mapping {:?} from {:?}", &mapping, &peeker.peeked)
);
peeker.consume();
self.mapping_execute(&mapping);
return;
}
std::mem::drop(ip);
peeker.restart();
if peeker.char_sequence_interrupted() {
peeker.consume();
self.promote_interruptions_to_front();
return;
}
flog!(reader, "no generic found, ignoring char...");
let _ = peeker.next();
peeker.consume();
}
fn read_character_matching(&mut self, predicate: impl Fn(&CharEvent) -> bool) -> CharEvent {
let mut saved_events = std::mem::take(&mut self.get_input_data_mut().event_storage);
assert!(saved_events.is_empty(), "event_storage should be empty");
let evt_to_return: CharEvent = loop {
let evt = self.readch();
if (predicate)(&evt) {
break evt;
}
saved_events.push(evt);
};
self.insert_front(saved_events.drain(..));
saved_events.clear();
self.get_input_data_mut().event_storage = saved_events;
evt_to_return
}
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);
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);
}
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)
)));
}
}
fn function_push_arg(&mut self, arg: char) {
self.get_input_data_mut().input_function_args.push(arg);
}
pub fn function_pop_arg(&mut self) -> Option<char> {
self.get_input_data_mut().input_function_args.pop()
}
fn function_push_args(&mut self, code: ReadlineCmd) {
let arity = input_function_arity(code);
let mut skipped = std::mem::take(&mut self.get_input_data_mut().event_storage);
assert!(skipped.is_empty(), "event_storage should be empty");
for _ in 0..arity {
let arg: char;
loop {
let evt = self.readch();
if let Some(kevt) = evt.get_key() {
if let Some(c) = kevt.key.codepoint_text() {
arg = c;
break;
}
}
skipped.push(evt);
}
self.function_push_arg(arg);
}
self.insert_front(skipped.drain(..));
skipped.clear();
self.get_input_data_mut().event_storage = skipped;
}
}
impl InputMappingSet {
pub fn get_names(&self, user: bool) -> Vec<InputMappingName> {
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()
}
pub fn clear(&mut self, mode: Option<&wstr>, user: bool) {
let ml = if user {
&mut self.mapping_list
} else {
&mut self.preset_mapping_list
};
let should_erase = |m: &InputMapping| mode.is_none_or(|x| x == m.mode);
ml.retain(|m| !should_erase(m));
}
pub fn erase(&mut self, sequence: &[Key], mode: &wstr, user: bool) -> bool {
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
}
pub fn get<'a>(
&'a self,
sequence: &[Key],
bind_mode: Option<&wstr>,
user: bool,
) -> Vec<&'a InputMapping> {
let ml = if user {
&self.mapping_list
} else {
&self.preset_mapping_list
};
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);
}
mappings
}
}
pub fn input_function_get_names() -> Vec<&'static wstr> {
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> {
get_by_sorted_name(name, INPUT_FUNCTION_METADATA).map(|md| md.code)
}
#[cfg(test)]
mod tests {
use super::{EventQueuePeeker, InputMappingSet, KeyNameStyle, DEFAULT_BIND_MODE};
use crate::env::EnvStack;
use crate::input_common::{CharEvent, InputData, InputEventQueuer, KeyEvent};
use crate::key::Key;
use crate::prelude::*;
struct TestInputEventQueuer {
input_data: InputData,
}
impl InputEventQueuer for TestInputEventQueuer {
fn get_input_data(&self) -> &InputData {
&self.input_data
}
fn get_input_data_mut(&mut self) -> &mut InputData {
&mut self.input_data
}
}
#[test]
fn test_input() {
let vars = EnvStack::new();
let mut input = TestInputEventQueuer {
input_data: InputData::new(i32::MAX, None), };
let prefix_binding: Vec<Key> = "qqqqqqqa".chars().map(Key::from_raw).collect();
let mut desired_binding = prefix_binding.clone();
desired_binding.push(Key::from_raw('a'));
let default_mode = || DEFAULT_BIND_MODE.to_owned();
let mut input_mappings = InputMappingSet::default();
input_mappings.add1(
prefix_binding,
KeyNameStyle::Plain,
L!("up-line").to_owned(),
default_mode(),
None,
true,
);
input_mappings.add1(
desired_binding.clone(),
KeyNameStyle::Plain,
L!("down-line").to_owned(),
default_mode(),
None,
true,
);
for key in desired_binding {
input
.input_data
.queue_char(CharEvent::from_key(KeyEvent::from(key)));
}
let mut peeker = EventQueuePeeker::new(&mut input);
let mapping = peeker.find_mapping(&vars, &input_mappings);
assert!(mapping.is_some());
assert_eq!(mapping.unwrap().commands, ["down-line"]);
peeker.restart();
}
}