mirror of
https://github.com/fish-shell/fish-shell.git
synced 2024-11-23 03:32:27 +08:00
Make CharEvent a native enum
This commit is contained in:
parent
aa40c3fb7e
commit
d0cdb142de
|
@ -248,7 +248,7 @@ fn process_input(continuous_mode: bool, verbose: bool) -> i32 {
|
|||
}
|
||||
let evt = evt.unwrap();
|
||||
|
||||
let c = evt.get_char();
|
||||
let c = evt.get_char().unwrap();
|
||||
prev_timestamp = output_elapsed_time(prev_timestamp, first_char_seen, verbose);
|
||||
// Hack for #3189. Do not suggest \c@ as the binding for nul, because a string containing
|
||||
// nul cannot be passed to builtin_bind since it uses C strings. We'll output the name of
|
||||
|
|
44
src/input.rs
44
src/input.rs
|
@ -4,7 +4,7 @@ use crate::env::{Environment, CURSES_INITIALIZED};
|
|||
use crate::event;
|
||||
use crate::flog::FLOG;
|
||||
use crate::input_common::{
|
||||
CharEvent, CharEventType, CharInputStyle, InputEventQueuer, ReadlineCmd, R_END_INPUT_FUNCTIONS,
|
||||
CharEvent, CharInputStyle, InputEventQueuer, ReadlineCmd, R_END_INPUT_FUNCTIONS,
|
||||
};
|
||||
use crate::parser::Parser;
|
||||
use crate::proc::job_reap;
|
||||
|
@ -464,8 +464,8 @@ impl Inputter {
|
|||
let arg: char;
|
||||
loop {
|
||||
let evt = self.readch();
|
||||
if evt.is_char() {
|
||||
arg = evt.get_char();
|
||||
if let Some(c) = evt.get_char() {
|
||||
arg = c;
|
||||
break;
|
||||
}
|
||||
skipped.push(evt);
|
||||
|
@ -494,13 +494,13 @@ impl Inputter {
|
|||
self.function_push_args(code);
|
||||
CharEvent::from_readline_seq(code, m.seq.clone())
|
||||
}
|
||||
None => CharEvent::from_command(cmd.clone()),
|
||||
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::from_command(sprintf!(
|
||||
self.push_front(CharEvent::Command(sprintf!(
|
||||
"set --global %s %s",
|
||||
FISH_BIND_MODE_VAR,
|
||||
escape(mode)
|
||||
|
@ -601,7 +601,7 @@ impl EventQueuePeeker<'_> {
|
|||
}
|
||||
// Now we have peeked far enough; check the event.
|
||||
// If it matches the char, then increment the index.
|
||||
if self.peeked[self.idx].maybe_char() == Some(c) {
|
||||
if self.peeked[self.idx].get_char() == Some(c) {
|
||||
self.idx += 1;
|
||||
return true;
|
||||
}
|
||||
|
@ -656,7 +656,7 @@ fn have_mouse_tracking_csi(peeker: &mut EventQueuePeeker) -> bool {
|
|||
return false;
|
||||
}
|
||||
|
||||
let mut next = peeker.next().maybe_char();
|
||||
let mut next = peeker.next().get_char();
|
||||
let length;
|
||||
if next == Some('M') {
|
||||
// Generic X10 or modified VT200 sequence. It doesn't matter which, they're both 6 chars
|
||||
|
@ -667,7 +667,7 @@ fn have_mouse_tracking_csi(peeker: &mut EventQueuePeeker) -> bool {
|
|||
// Extended (SGR/1006) mouse reporting mode, with semicolon-separated parameters for button
|
||||
// code, Px, and Py, ending with 'M' for button press or 'm' for button release.
|
||||
loop {
|
||||
next = peeker.next().maybe_char();
|
||||
next = peeker.next().get_char();
|
||||
if next == Some('M') || next == Some('m') {
|
||||
// However much we've read, we've consumed the CSI in its entirety.
|
||||
length = peeker.len();
|
||||
|
@ -854,29 +854,29 @@ impl Inputter {
|
|||
// Search for sequence in mapping tables.
|
||||
loop {
|
||||
let evt = self.readch();
|
||||
match evt.evt {
|
||||
CharEventType::Readline(cmd) => match cmd {
|
||||
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 = evt.seq.chars().map(CharEvent::from_char);
|
||||
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.
|
||||
res.input_style = if cmd == ReadlineCmd::SelfInsertNotFirst {
|
||||
CharInputStyle::NotFirst
|
||||
} else {
|
||||
CharInputStyle::Normal
|
||||
};
|
||||
if readline_event.cmd == ReadlineCmd::SelfInsertNotFirst {
|
||||
if let CharEvent::Char(cevt) = &mut res {
|
||||
cevt.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 && cmd == ReadlineCmd::FuncAnd)
|
||||
|| (self.function_status && cmd == ReadlineCmd::FuncOr)
|
||||
if (!self.function_status && readline_event.cmd == ReadlineCmd::FuncAnd)
|
||||
|| (self.function_status && readline_event.cmd == ReadlineCmd::FuncOr)
|
||||
{
|
||||
self.drop_leading_readline_events();
|
||||
}
|
||||
|
@ -885,19 +885,19 @@ impl Inputter {
|
|||
return evt;
|
||||
}
|
||||
},
|
||||
CharEventType::Command(_) => {
|
||||
CharEvent::Command(_) => {
|
||||
return evt;
|
||||
}
|
||||
CharEventType::Eof => {
|
||||
CharEvent::Eof => {
|
||||
// If we have EOF, we need to immediately quit.
|
||||
// There's no need to go through the input functions.
|
||||
return evt;
|
||||
}
|
||||
CharEventType::CheckExit => {
|
||||
CharEvent::CheckExit => {
|
||||
// Allow the reader to check for exit conditions.
|
||||
return evt;
|
||||
}
|
||||
CharEventType::Char(_) => {
|
||||
CharEvent::Char(ref _cevt) => {
|
||||
self.push_front(evt);
|
||||
self.mapping_execute_matching_or_generic();
|
||||
}
|
||||
|
|
|
@ -138,113 +138,109 @@ pub enum CharEventType {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CharEvent {
|
||||
pub evt: CharEventType,
|
||||
|
||||
// The style to use when inserting characters into the command line.
|
||||
// todo!("This is only needed if the type is Readline")
|
||||
pub input_style: CharInputStyle,
|
||||
|
||||
pub struct ReadlineCmdEvent {
|
||||
pub cmd: ReadlineCmd,
|
||||
/// The sequence of characters in the input mapping which generated this event.
|
||||
/// Note that the generic self-insert case does not have any characters, so this would be empty.
|
||||
/// This is also empty for invalid Unicode code points, which produce multiple characters.
|
||||
pub seq: WString,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PlainCharEvent {
|
||||
// The key.
|
||||
pub char: char,
|
||||
// The style to use when inserting characters into the command line.
|
||||
pub input_style: CharInputStyle,
|
||||
/// The sequence of characters in the input mapping which generated this event.
|
||||
/// Note that the generic self-insert case does not have any characters, so this would be empty.
|
||||
/// This is also empty for invalid Unicode code points, which produce multiple characters.
|
||||
pub seq: WString,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CharEvent {
|
||||
/// A character was entered.
|
||||
Char(PlainCharEvent),
|
||||
|
||||
/// A readline event.
|
||||
Readline(ReadlineCmdEvent),
|
||||
|
||||
/// A shell command.
|
||||
Command(WString),
|
||||
|
||||
/// end-of-file was reached.
|
||||
Eof,
|
||||
|
||||
/// An event was handled internally, or an interrupt was received. Check to see if the reader
|
||||
/// loop should exit.
|
||||
CheckExit,
|
||||
}
|
||||
|
||||
impl CharEvent {
|
||||
pub fn is_char(&self) -> bool {
|
||||
matches!(self.evt, CharEventType::Char(_))
|
||||
matches!(self, CharEvent::Char(_))
|
||||
}
|
||||
|
||||
pub fn is_eof(&self) -> bool {
|
||||
matches!(self.evt, CharEventType::Eof)
|
||||
matches!(self, CharEvent::Eof)
|
||||
}
|
||||
|
||||
pub fn is_check_exit(&self) -> bool {
|
||||
matches!(self.evt, CharEventType::CheckExit)
|
||||
matches!(self, CharEvent::CheckExit)
|
||||
}
|
||||
|
||||
pub fn is_readline(&self) -> bool {
|
||||
matches!(self.evt, CharEventType::Readline(_))
|
||||
matches!(self, CharEvent::Readline(_))
|
||||
}
|
||||
|
||||
pub fn is_readline_or_command(&self) -> bool {
|
||||
matches!(
|
||||
self.evt,
|
||||
CharEventType::Readline(_) | CharEventType::Command(_)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_char(&self) -> char {
|
||||
let CharEventType::Char(c) = self.evt else {
|
||||
panic!("Not a char type");
|
||||
};
|
||||
c
|
||||
}
|
||||
|
||||
pub fn maybe_char(&self) -> Option<char> {
|
||||
if let CharEventType::Char(c) = self.evt {
|
||||
Some(c)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
matches!(self, CharEvent::Readline(_) | CharEvent::Command(_))
|
||||
}
|
||||
|
||||
pub fn get_readline(&self) -> ReadlineCmd {
|
||||
let CharEventType::Readline(c) = self.evt else {
|
||||
let CharEvent::Readline(c) = self else {
|
||||
panic!("Not a readline type");
|
||||
};
|
||||
c
|
||||
c.cmd
|
||||
}
|
||||
|
||||
pub fn get_command(&self) -> Option<&wstr> {
|
||||
match &self.evt {
|
||||
CharEventType::Command(c) => Some(c),
|
||||
match self {
|
||||
CharEvent::Command(c) => Some(c),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_char(c: char) -> CharEvent {
|
||||
CharEvent {
|
||||
evt: CharEventType::Char(c),
|
||||
input_style: CharInputStyle::Normal,
|
||||
seq: WString::new(),
|
||||
Self::from_char_seq(c, WString::new())
|
||||
}
|
||||
|
||||
pub fn get_char(&self) -> Option<char> {
|
||||
match self {
|
||||
CharEvent::Char(cevt) => Some(cevt.char),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_char_seq(c: char, seq: WString) -> CharEvent {
|
||||
CharEvent::Char(PlainCharEvent {
|
||||
char: c,
|
||||
input_style: CharInputStyle::Normal,
|
||||
seq,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_readline(cmd: ReadlineCmd) -> CharEvent {
|
||||
Self::from_readline_seq(cmd, WString::new())
|
||||
}
|
||||
|
||||
pub fn from_readline_seq(cmd: ReadlineCmd, seq: WString) -> CharEvent {
|
||||
CharEvent {
|
||||
evt: CharEventType::Readline(cmd),
|
||||
input_style: CharInputStyle::Normal,
|
||||
seq,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_command(cmd: WString) -> CharEvent {
|
||||
CharEvent {
|
||||
evt: CharEventType::Command(cmd),
|
||||
input_style: CharInputStyle::Normal,
|
||||
seq: WString::new(),
|
||||
}
|
||||
CharEvent::Readline(ReadlineCmdEvent { cmd, seq })
|
||||
}
|
||||
|
||||
pub fn from_check_exit() -> CharEvent {
|
||||
CharEvent {
|
||||
evt: CharEventType::CheckExit,
|
||||
input_style: CharInputStyle::Normal,
|
||||
seq: WString::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_eof() -> CharEvent {
|
||||
CharEvent {
|
||||
evt: CharEventType::Eof,
|
||||
input_style: CharInputStyle::Normal,
|
||||
seq: WString::new(),
|
||||
}
|
||||
CharEvent::CheckExit
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -420,7 +416,7 @@ pub trait InputEventQueuer {
|
|||
let rr = readb(self.get_in_fd());
|
||||
match rr {
|
||||
ReadbResult::Eof => {
|
||||
return CharEvent::from_eof();
|
||||
return CharEvent::Eof;
|
||||
}
|
||||
|
||||
ReadbResult::Interrupted => {
|
||||
|
|
118
src/reader.rs
118
src/reader.rs
|
@ -1842,69 +1842,72 @@ impl ReaderData {
|
|||
reader_sighup();
|
||||
continue;
|
||||
}
|
||||
assert!(
|
||||
event_needing_handling.is_char() || event_needing_handling.is_readline_or_command(),
|
||||
"Should have a char, readline or command"
|
||||
);
|
||||
|
||||
if !matches!(rls.last_cmd, Some(rl::Yank | rl::YankPop)) {
|
||||
rls.yank_len = 0;
|
||||
}
|
||||
|
||||
if event_needing_handling.is_readline() {
|
||||
let readline_cmd = event_needing_handling.get_readline();
|
||||
if readline_cmd == rl::Cancel && zelf.is_navigating_pager_contents() {
|
||||
zelf.clear_transient_edit();
|
||||
}
|
||||
|
||||
// Clear the pager if necessary.
|
||||
let focused_on_search_field =
|
||||
zelf.active_edit_line_tag() == EditableLineTag::SearchField;
|
||||
if !zelf.history_search.active()
|
||||
&& command_ends_paging(readline_cmd, focused_on_search_field)
|
||||
{
|
||||
zelf.clear_pager();
|
||||
}
|
||||
|
||||
zelf.handle_readline_command(readline_cmd, &mut rls);
|
||||
|
||||
if zelf.history_search.active() && command_ends_history_search(readline_cmd) {
|
||||
// "cancel" means to abort the whole thing, other ending commands mean to finish the
|
||||
// search.
|
||||
if readline_cmd == rl::Cancel {
|
||||
// Go back to the search string by simply undoing the history-search edit.
|
||||
match event_needing_handling {
|
||||
CharEvent::Readline(readline_cmd_evt) => {
|
||||
let readline_cmd = readline_cmd_evt.cmd;
|
||||
if readline_cmd == rl::Cancel && zelf.is_navigating_pager_contents() {
|
||||
zelf.clear_transient_edit();
|
||||
}
|
||||
zelf.history_search.reset();
|
||||
zelf.command_line_has_transient_edit = false;
|
||||
}
|
||||
|
||||
rls.last_cmd = Some(readline_cmd);
|
||||
} else if let Some(command) = event_needing_handling.get_command() {
|
||||
zelf.run_input_command_scripts(command);
|
||||
} else {
|
||||
// Ordinary char.
|
||||
let c = event_needing_handling.get_char();
|
||||
if event_needing_handling.input_style == CharInputStyle::NotFirst
|
||||
&& zelf.active_edit_line().1.position() == 0
|
||||
{
|
||||
// This character is skipped.
|
||||
} else if c.is_control() {
|
||||
// This can happen if the user presses a control char we don't recognize. No
|
||||
// reason to report this to the user unless they've enabled debugging output.
|
||||
FLOG!(reader, wgettext_fmt!("Unknown key binding 0x%X", c));
|
||||
} else {
|
||||
// Regular character.
|
||||
let (elt, _el) = zelf.active_edit_line();
|
||||
zelf.insert_char(elt, c);
|
||||
|
||||
if elt == EditableLineTag::Commandline {
|
||||
// Clear the pager if necessary.
|
||||
let focused_on_search_field =
|
||||
zelf.active_edit_line_tag() == EditableLineTag::SearchField;
|
||||
if !zelf.history_search.active()
|
||||
&& command_ends_paging(readline_cmd, focused_on_search_field)
|
||||
{
|
||||
zelf.clear_pager();
|
||||
// We end history search. We could instead update the search string.
|
||||
zelf.history_search.reset();
|
||||
}
|
||||
|
||||
zelf.handle_readline_command(readline_cmd, &mut rls);
|
||||
|
||||
if zelf.history_search.active() && command_ends_history_search(readline_cmd) {
|
||||
// "cancel" means to abort the whole thing, other ending commands mean to finish the
|
||||
// search.
|
||||
if readline_cmd == rl::Cancel {
|
||||
// Go back to the search string by simply undoing the history-search edit.
|
||||
zelf.clear_transient_edit();
|
||||
}
|
||||
zelf.history_search.reset();
|
||||
zelf.command_line_has_transient_edit = false;
|
||||
}
|
||||
|
||||
rls.last_cmd = Some(readline_cmd);
|
||||
}
|
||||
CharEvent::Command(command) => {
|
||||
zelf.run_input_command_scripts(&command);
|
||||
}
|
||||
CharEvent::Char(cevt) => {
|
||||
// Ordinary char.
|
||||
let c = cevt.char;
|
||||
if cevt.input_style == CharInputStyle::NotFirst
|
||||
&& zelf.active_edit_line().1.position() == 0
|
||||
{
|
||||
// This character is skipped.
|
||||
} else if c.is_control() {
|
||||
// This can happen if the user presses a control char we don't recognize. No
|
||||
// reason to report this to the user unless they've enabled debugging output.
|
||||
FLOG!(reader, wgettext_fmt!("Unknown key binding 0x%X", c));
|
||||
} else {
|
||||
// Regular character.
|
||||
let (elt, _el) = zelf.active_edit_line();
|
||||
zelf.insert_char(elt, c);
|
||||
|
||||
if elt == EditableLineTag::Commandline {
|
||||
zelf.clear_pager();
|
||||
// We end history search. We could instead update the search string.
|
||||
zelf.history_search.reset();
|
||||
}
|
||||
}
|
||||
rls.last_cmd = None;
|
||||
}
|
||||
CharEvent::Eof | CharEvent::CheckExit => {
|
||||
panic!("Should have a char, readline or command")
|
||||
}
|
||||
rls.last_cmd = None;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2002,17 +2005,22 @@ impl ReaderData {
|
|||
|
||||
while accumulated_chars.len() < limit {
|
||||
let evt = self.inputter.read_char();
|
||||
if !evt.is_char() || !poll_fd_readable(self.conf.inputfd) {
|
||||
let CharEvent::Char(cevt) = &evt else {
|
||||
event_needing_handling = Some(evt);
|
||||
break;
|
||||
} else if evt.input_style == CharInputStyle::NotFirst
|
||||
};
|
||||
if !poll_fd_readable(self.conf.inputfd) {
|
||||
event_needing_handling = Some(evt);
|
||||
break;
|
||||
}
|
||||
if cevt.input_style == CharInputStyle::NotFirst
|
||||
&& accumulated_chars.is_empty()
|
||||
&& self.active_edit_line().1.position() == 0
|
||||
{
|
||||
// The cursor is at the beginning and nothing is accumulated, so skip this character.
|
||||
continue;
|
||||
} else {
|
||||
accumulated_chars.push(evt.get_char());
|
||||
accumulated_chars.push(cevt.char);
|
||||
}
|
||||
|
||||
if last_exec_count != self.exec_count() {
|
||||
|
|
|
@ -7,10 +7,10 @@ fn test_push_front_back() {
|
|||
queue.push_front(CharEvent::from_char('b'));
|
||||
queue.push_back(CharEvent::from_char('c'));
|
||||
queue.push_back(CharEvent::from_char('d'));
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'b');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'a');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'c');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'd');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'b');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'a');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'c');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'd');
|
||||
assert!(queue.try_pop().is_none());
|
||||
}
|
||||
|
||||
|
@ -27,15 +27,15 @@ fn test_promote_interruptions_to_front() {
|
|||
|
||||
assert_eq!(queue.try_pop().unwrap().get_readline(), ReadlineCmd::Undo);
|
||||
assert_eq!(queue.try_pop().unwrap().get_readline(), ReadlineCmd::Redo);
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'a');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'b');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'c');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'd');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'a');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'b');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'c');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'd');
|
||||
assert!(!queue.has_lookahead());
|
||||
|
||||
queue.push_back(CharEvent::from_char('e'));
|
||||
queue.promote_interruptions_to_front();
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'e');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'e');
|
||||
assert!(!queue.has_lookahead());
|
||||
}
|
||||
|
||||
|
@ -51,9 +51,9 @@ fn test_insert_front() {
|
|||
CharEvent::from_char('C'),
|
||||
];
|
||||
queue.insert_front(events);
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'A');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'B');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'C');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'a');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'b');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'A');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'B');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'C');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'a');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char().unwrap(), 'b');
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user