From 9d7116c12d6217773160e192c60a2e5fc9a6b4ff Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Mon, 8 Apr 2024 23:57:19 +0200 Subject: [PATCH] Move readline loop state into reader state To be used by the next commit. --- src/reader.rs | 151 +++++++++++++++++++++++++++++++------------------- 1 file changed, 93 insertions(+), 58 deletions(-) diff --git a/src/reader.rs b/src/reader.rs index b780cf623..7d88f55aa 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -373,8 +373,6 @@ enum JumpPrecision { } /// readline_loop_state_t encapsulates the state used in a readline loop. -/// It is always stack allocated transient. This state should not be "publicly visible"; public -/// state should be in reader_data_t. struct ReadlineLoopState { /// The last command that was executed. last_cmd: Option, @@ -550,6 +548,8 @@ pub struct ReaderData { /// If these differs from the text of the command line, then we must kick off a new request. in_flight_highlight_request: WString, in_flight_autosuggest_request: WString, + + rls: Option, } /// Read commands from \c fd until encountering EOF. @@ -1093,6 +1093,7 @@ impl ReaderData { last_jump_precision: JumpPrecision::To, in_flight_highlight_request: Default::default(), in_flight_autosuggest_request: Default::default(), + rls: None, })) } @@ -1143,6 +1144,13 @@ impl ReaderData { &self.parser_ref } + fn rls(&self) -> &ReadlineLoopState { + self.rls.as_ref().unwrap() + } + fn rls_mut(&mut self) -> &mut ReadlineLoopState { + self.rls.as_mut().unwrap() + } + // We repaint our prompt if fstat reports the tty as having changed. // But don't react to tty changes that we initiated, because of commands or // on-variable events (e.g. for fish_bind_mode). See #3481. @@ -1746,7 +1754,7 @@ impl ReaderData { /// Read a command to execute, respecting input bindings. /// \return the command, or none if we were asked to cancel (e.g. SIGHUP). fn readline(&mut self, nchars: Option) -> Option { - let mut rls = ReadlineLoopState::new(); + self.rls = Some(ReadlineLoopState::new()); // Suppress fish_trace during executing key bindings. // This is simply to reduce noise. @@ -1763,7 +1771,7 @@ impl ReaderData { // If nchars_or_0 is positive, then that's the maximum number of chars. Otherwise keep it at // SIZE_MAX. - rls.nchars = nchars; + zelf.rls_mut().nchars = nchars; // The command line before completion. zelf.cycle_command_line.clear(); @@ -1820,8 +1828,8 @@ impl ReaderData { // Start out as initially dirty. zelf.force_exec_prompt_and_repaint = true; - while !rls.finished && !check_exit_loop_maybe_warning(Some(&mut zelf)) { - if zelf.handle_char_event(&mut rls, None).is_break() { + while !zelf.rls().finished && !check_exit_loop_maybe_warning(Some(&mut zelf)) { + if zelf.handle_char_event(None).is_break() { break; } } @@ -1833,7 +1841,7 @@ impl ReaderData { } // Finish syntax highlighting (but do not wait forever). - if rls.finished { + if zelf.rls().finished { zelf.finish_highlighting_before_exec(); } @@ -1872,7 +1880,12 @@ impl ReaderData { .borrow_mut() .set_color(RgbColor::RESET, RgbColor::RESET); } - rls.finished.then(|| zelf.command_line.text().to_owned()) + let result = zelf + .rls() + .finished + .then(|| zelf.command_line.text().to_owned()); + zelf.rls = None; + result } fn eval_bind_cmd(&mut self, cmd: &wstr) { @@ -1907,10 +1920,10 @@ impl ReaderData { /// Read normal characters, inserting them into the command line. /// \return the next unhandled event. - fn read_normal_chars(&mut self, rls: &mut ReadlineLoopState) -> Option { + fn read_normal_chars(&mut self) -> Option { let mut event_needing_handling = None; let limit = std::cmp::min( - rls.nchars.map_or(usize::MAX, |nchars| { + self.rls().nchars.map_or(usize::MAX, |nchars| { usize::from(nchars) - self.command_line.len() }), READAHEAD_MAX, @@ -1954,7 +1967,7 @@ impl ReaderData { } // Since we handled a normal character, we don't have a last command. - rls.last_cmd = None; + self.rls_mut().last_cmd = None; } event_needing_handling @@ -1974,15 +1987,11 @@ impl ReaderData { self.force_exec_prompt_and_repaint = false; } - fn handle_char_event( - &mut self, - rls: &mut ReadlineLoopState, - injected_event: Option, - ) -> ControlFlow<()> { + fn handle_char_event(&mut self, injected_event: Option) -> ControlFlow<()> { if self.reset_loop_state { self.reset_loop_state = false; - rls.last_cmd = None; - rls.complete_did_insert = false; + self.rls_mut().last_cmd = None; + self.rls_mut().complete_did_insert = false; } // Perhaps update the termsize. This is cheap if it has not changed. self.update_termsize(); @@ -1990,21 +1999,23 @@ impl ReaderData { // Repaint as needed. self.color_suggest_repaint_now(); - if rls + if self + .rls() .nchars .is_some_and(|nchars| usize::from(nchars) <= self.command_line.len()) { // We've already hit the specified character limit. - rls.finished = true; + self.rls_mut().finished = true; return ControlFlow::Break(()); } let event_needing_handling = injected_event.or_else(|| loop { - let event_needing_handling = self.read_normal_chars(rls); + let event_needing_handling = self.read_normal_chars(); if event_needing_handling.is_some() { break event_needing_handling; } - if rls + if self + .rls() .nchars .is_some_and(|nchars| usize::from(nchars) <= self.command_line.len()) { @@ -2030,8 +2041,11 @@ impl ReaderData { return ControlFlow::Continue(()); } - if !matches!(rls.last_cmd, Some(ReadlineCmd::Yank | ReadlineCmd::YankPop)) { - rls.yank_len = 0; + if !matches!( + self.rls().last_cmd, + Some(ReadlineCmd::Yank | ReadlineCmd::YankPop) + ) { + self.rls_mut().yank_len = 0; } match event_needing_handling { @@ -2050,7 +2064,7 @@ impl ReaderData { self.clear_pager(); } - self.handle_readline_command(readline_cmd, rls); + self.handle_readline_command(readline_cmd); if self.history_search.active() && command_ends_history_search(readline_cmd) { // "cancel" means to abort the whole thing, other ending commands mean to finish the @@ -2063,7 +2077,7 @@ impl ReaderData { self.command_line_has_transient_edit = false; } - rls.last_cmd = Some(readline_cmd); + self.rls_mut().last_cmd = Some(readline_cmd); } CharEvent::Command(command) => { self.run_input_command_scripts(&command); @@ -2087,7 +2101,7 @@ impl ReaderData { } } } - rls.last_cmd = None; + self.rls_mut().last_cmd = None; } CharEvent::Eof | CharEvent::CheckExit => { panic!("Should have a char, readline or command") @@ -2098,7 +2112,7 @@ impl ReaderData { } impl ReaderData { - fn handle_readline_command(&mut self, c: ReadlineCmd, rls: &mut ReadlineLoopState) { + fn handle_readline_command(&mut self, c: ReadlineCmd) { type rl = ReadlineCmd; match c { rl::BeginningOfLine => { @@ -2163,8 +2177,11 @@ impl ReaderData { // but never complete{,_and_search}) // // Also paging is already cancelled above. - if rls.complete_did_insert - && matches!(rls.last_cmd, Some(rl::Complete | rl::CompleteAndSearch)) + if self.rls().complete_did_insert + && matches!( + self.rls().last_cmd, + Some(rl::Complete | rl::CompleteAndSearch) + ) { let (elt, el) = self.active_edit_line_mut(); el.undo(); @@ -2208,9 +2225,9 @@ impl ReaderData { return; } if self.is_navigating_pager_contents() - || (!rls.comp.is_empty() - && !rls.complete_did_insert - && rls.last_cmd == Some(rl::Complete)) + || (!self.rls().comp.is_empty() + && !self.rls().complete_did_insert + && self.rls().last_cmd == Some(rl::Complete)) { // The user typed complete more than once in a row. If we are not yet fully // disclosed, then become so; otherwise cycle through our available completions. @@ -2228,7 +2245,7 @@ impl ReaderData { } } else { // Either the user hit tab only once, or we had no visible completion list. - self.compute_and_apply_completions(c, rls); + self.compute_and_apply_completions(c); } } rl::PagerToggleSearch => { @@ -2266,7 +2283,12 @@ impl ReaderData { let range = begin..end; if !range.is_empty() { - self.kill(elt, range, Kill::Append, rls.last_cmd != Some(rl::KillLine)); + self.kill( + elt, + range, + Kill::Append, + self.rls().last_cmd != Some(rl::KillLine), + ); } } rl::BackwardKillLine => { @@ -2297,7 +2319,7 @@ impl ReaderData { elt, end - len..end, Kill::Prepend, - rls.last_cmd != Some(rl::BackwardKillLine), + self.rls().last_cmd != Some(rl::BackwardKillLine), ); } rl::KillWholeLine | rl::KillInnerLine => { @@ -2342,20 +2364,25 @@ impl ReaderData { assert!(end >= begin); if end > begin { - self.kill(elt, begin..end, Kill::Append, rls.last_cmd != Some(c)); + self.kill( + elt, + begin..end, + Kill::Append, + self.rls().last_cmd != Some(c), + ); } } rl::Yank => { let yank_str = kill_yank(); self.insert_string(self.active_edit_line_tag(), &yank_str); - rls.yank_len = yank_str.len(); + self.rls_mut().yank_len = yank_str.len(); if self.cursor_end_mode == CursorEndMode::Inclusive { let (_elt, el) = self.active_edit_line(); self.update_buff_pos(self.active_edit_line_tag(), Some(el.position() - 1)); } } rl::YankPop => { - if rls.yank_len != 0 { + if self.rls().yank_len != 0 { let (elt, el) = self.active_edit_line(); let yank_str = kill_yank_rotate(); let new_yank_len = yank_str.len(); @@ -2364,11 +2391,11 @@ impl ReaderData { } else { 0 }; - let begin = el.position() + bias - rls.yank_len; + let begin = el.position() + bias - self.rls().yank_len; let end = el.position() + bias; self.replace_substring(elt, begin..end, yank_str); self.update_buff_pos(elt, None); - rls.yank_len = new_yank_len; + self.rls_mut().yank_len = new_yank_len; self.suppress_autosuggestion = true; } } @@ -2397,7 +2424,7 @@ impl ReaderData { } } rl::Execute => { - if !self.handle_execute(rls) { + if !self.handle_execute() { event::fire_generic( self.parser(), L!("fish_posterror").to_owned(), @@ -2584,7 +2611,7 @@ impl ReaderData { }; // Is this the same killring item as the last kill? let newv = !matches!( - rls.last_cmd, + self.rls().last_cmd, Some( rl::BackwardKillWord | rl::BackwardKillPathComponent @@ -2612,7 +2639,7 @@ impl ReaderData { MoveWordDir::Right, /*erase=*/ true, style, - rls.last_cmd != Some(c), + self.rls().last_cmd != Some(c), ); } rl::BackwardWord | rl::BackwardBigword | rl::PrevdOrBackwardWord => { @@ -2938,7 +2965,7 @@ impl ReaderData { self.update_buff_pos(self.active_edit_line_tag(), Some(tmp)); } rl::KillSelection => { - let newv = rls.last_cmd != Some(rl::KillSelection); + let newv = self.rls().last_cmd != Some(rl::KillSelection); if let Some(selection) = self.get_selection() { self.kill(EditableLineTag::Commandline, selection, Kill::Append, newv); } @@ -3099,7 +3126,7 @@ impl ReaderData { // unfinished. It may also set 'finished' and 'cmd' inside the rls. // \return true on success, false if we got an error, in which case the caller should fire the // error event. - fn handle_execute(&mut self, rls: &mut ReadlineLoopState) -> bool { + fn handle_execute(&mut self) -> bool { // Evaluate. If the current command is unfinished, or if the charater is escaped // using a backslash, insert a newline. // If the user hits return while navigating the pager, it only clears the pager. @@ -3169,7 +3196,7 @@ impl ReaderData { } self.add_to_history(); - rls.finished = true; + self.rls_mut().finished = true; self.update_buff_pos(elt, Some(self.command_line.len())); true } @@ -5176,7 +5203,7 @@ fn get_best_rank(comp: &[Completion]) -> u32 { impl ReaderData { /// Compute completions and update the pager and/or commandline as needed. - fn compute_and_apply_completions(&mut self, c: ReadlineCmd, rls: &mut ReadlineLoopState) { + fn compute_and_apply_completions(&mut self, c: ReadlineCmd) { assert!(matches!( c, ReadlineCmd::Complete | ReadlineCmd::CompleteAndSearch @@ -5236,8 +5263,8 @@ impl ReaderData { return; } ExpandResultCode::ok => { - rls.comp.clear(); - rls.complete_did_insert = false; + self.rls_mut().comp.clear(); + self.rls_mut().complete_did_insert = false; self.push_edit( EditableLineTag::Commandline, Edit::new(token_range, wc_expanded), @@ -5258,31 +5285,38 @@ impl ReaderData { CompletionRequestOptions::normal(), &self.parser().context(), ); - rls.comp = comp; + self.rls_mut().comp = comp; + let el = &self.command_line; // User-supplied completions may have changed the commandline - prevent buffer // overflow. token_range.start = std::cmp::min(token_range.start, el.text().len()); token_range.end = std::cmp::min(token_range.end, el.text().len()); // Munge our completions. - sort_and_prioritize(&mut rls.comp, CompletionRequestOptions::default()); + sort_and_prioritize( + &mut self.rls_mut().comp, + CompletionRequestOptions::default(), + ); + let el = &self.command_line; // Record our cycle_command_line. self.cycle_command_line = el.text().to_owned(); self.cycle_cursor_pos = token_range.end; - rls.complete_did_insert = self.handle_completions(&rls.comp, token_range); + self.rls_mut().complete_did_insert = self.handle_completions(token_range); // Show the search field if requested and if we printed a list of completions. - if c == ReadlineCmd::CompleteAndSearch && !rls.complete_did_insert && !self.pager.is_empty() + if c == ReadlineCmd::CompleteAndSearch + && !self.rls().complete_did_insert + && !self.pager.is_empty() { self.pager.set_search_field_shown(true); self.select_completion_in_direction(SelectionMotion::Next, false); } } - fn try_insert(&mut self, c: &Completion, tok: &wstr, token_range: Range) { + fn try_insert(&mut self, c: Completion, tok: &wstr, token_range: Range) { // If this is a replacement completion, check that we know how to replace it, e.g. that // the token doesn't contain evil operators like {}. if !c.flags.contains(CompleteFlags::REPLACES_TOKEN) || reader_can_replace(tok, c.flags) { @@ -5305,9 +5339,10 @@ impl ReaderData { /// \param token_end the position after the token to complete /// /// Return true if we inserted text into the command line, false if we did not. - fn handle_completions(&mut self, comp: &[Completion], token_range: Range) -> bool { + fn handle_completions(&mut self, token_range: Range) -> bool { let tok = self.command_line.text()[token_range.clone()].to_owned(); + let comp = &self.rls().comp; // Check trivial cases. let len = comp.len(); if len == 0 { @@ -5317,7 +5352,7 @@ impl ReaderData { } else if len == 1 { // Exactly one suitable completion found - insert it. let c = &comp[0]; - self.try_insert(c, &tok, token_range); + self.try_insert(c.clone(), &tok, token_range); return true; } @@ -5367,7 +5402,7 @@ impl ReaderData { // the token is "cma" and the options are "cmake/" and "CMakeLists.txt" // it would be nice if we could figure // out how to use it more. - let c = &surviving_completions[0]; + let c = std::mem::take(&mut surviving_completions[0]); self.try_insert(c, &tok, token_range); return true;