Make CharEvent a native enum

This commit is contained in:
Johannes Altmanninger 2024-03-30 13:01:57 +01:00
parent aa40c3fb7e
commit d0cdb142de
5 changed files with 161 additions and 157 deletions

View File

@ -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

View File

@ -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();
}

View File

@ -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 => {

View File

@ -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() {

View File

@ -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');
}