From d0cdb142dec8611237c168f4761b0155507ebd57 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Sat, 30 Mar 2024 13:01:57 +0100 Subject: [PATCH] Make CharEvent a native enum --- src/bin/fish_key_reader.rs | 2 +- src/input.rs | 44 ++++++------- src/input_common.rs | 126 ++++++++++++++++++------------------- src/reader.rs | 118 ++++++++++++++++++---------------- src/tests/input_common.rs | 28 ++++----- 5 files changed, 161 insertions(+), 157 deletions(-) diff --git a/src/bin/fish_key_reader.rs b/src/bin/fish_key_reader.rs index af7f1d48b..384e06384 100644 --- a/src/bin/fish_key_reader.rs +++ b/src/bin/fish_key_reader.rs @@ -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 diff --git a/src/input.rs b/src/input.rs index 89cd6c029..9506c2201 100644 --- a/src/input.rs +++ b/src/input.rs @@ -4,7 +4,7 @@ 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 @@ fn function_push_args(&mut self, code: ReadlineCmd) { 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 @@ fn mapping_execute(&mut self, m: &InputMapping) { 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 @@ fn next_is_char(&mut self, c: char, escaped: bool) -> bool { } // 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 @@ pub fn read_char(&mut self) -> CharEvent { // 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 @@ pub fn read_char(&mut self) -> CharEvent { 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(); } diff --git a/src/input_common.rs b/src/input_common.rs index 33ad4b3b3..b2f95c550 100644 --- a/src/input_common.rs +++ b/src/input_common.rs @@ -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 { - 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 { + 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 @@ fn readch(&mut self) -> CharEvent { let rr = readb(self.get_in_fd()); match rr { ReadbResult::Eof => { - return CharEvent::from_eof(); + return CharEvent::Eof; } ReadbResult::Interrupted => { diff --git a/src/reader.rs b/src/reader.rs index e1d3fa0cb..951bd1663 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1842,69 +1842,72 @@ fn readline(&mut self, nchars: Option) -> Option { 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 @@ fn read_normal_chars(&mut self, rls: &mut ReadlineLoopState) -> Option