From 29ae571afaeea458d561e37e5e6f2aa929fcd466 Mon Sep 17 00:00:00 2001 From: Peter Ammon Date: Sat, 28 Dec 2024 12:41:22 -0800 Subject: [PATCH] Make scoped_push nicer In C++ it's easy to make an RAII-type object like "increment a counter for the duration of this function." Such an object might accept a pointer or reference, increment the value, and then restore it in its destructor. We do this all the time - for example to mark a region of code as non-interactive, etc. Rust makes this more awkward, because now the reference is tracked by the borrow checker: it "owns" the object for the duration of the function. This leads to approaches like "zelf" where the object that marks the parser as non-interactive itself becomes the new parser, but we can't call it "self" and it's just yucky. In this commit we introduce a notion of the "scoped data" of the Parser, factored out of the library data. This is data which is typically set in a scoped fashion: whether we are a subshell, are interactive, emit fish_trace debugging info, etc. Crucially we set this as Rc: this allow the scope itself to share data with the Parser and we can get rid of lots of "zelf"s. Introduce a new function `Parser::push_scope` which creates a new scope and allows modifying these variables associated with the scope. This ends up as a nice simplification. --- src/builtins/eval.rs | 4 +- src/builtins/function.rs | 5 +- src/builtins/read.rs | 7 +- src/builtins/return.rs | 5 +- src/builtins/status.rs | 2 +- src/complete.rs | 19 ++-- src/event.rs | 22 ++--- src/exec.rs | 21 ++--- src/parse_execution.rs | 8 +- src/parser.rs | 70 ++++++++++----- src/proc.rs | 15 ++-- src/reader.rs | 169 ++++++++++++++--------------------- src/reader_history_search.rs | 16 ++-- src/trace.rs | 3 +- 14 files changed, 165 insertions(+), 201 deletions(-) diff --git a/src/builtins/eval.rs b/src/builtins/eval.rs index 33197fccb..1d9efacde 100644 --- a/src/builtins/eval.rs +++ b/src/builtins/eval.rs @@ -25,7 +25,7 @@ pub fn eval(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Bui // buffer in that case. let mut stdout_fill = None; if streams.out_is_piped { - match IoBufferfill::create_opts(parser.libdata().read_limit, STDOUT_FILENO) { + match IoBufferfill::create_opts(parser.scope().read_limit, STDOUT_FILENO) { Err(_) => { // We were unable to create a pipe, probably fd exhaustion. return Err(STATUS_CMD_ERROR); @@ -40,7 +40,7 @@ pub fn eval(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Bui // Of course the same applies to stderr. let mut stderr_fill = None; if streams.err_is_piped { - match IoBufferfill::create_opts(parser.libdata().read_limit, STDERR_FILENO) { + match IoBufferfill::create_opts(parser.scope().read_limit, STDERR_FILENO) { Err(_) => { // We were unable to create a pipe, probably fd exhaustion. return Err(STATUS_CMD_ERROR); diff --git a/src/builtins/function.rs b/src/builtins/function.rs index c1e52cdab..2f6a36484 100644 --- a/src/builtins/function.rs +++ b/src/builtins/function.rs @@ -134,9 +134,8 @@ fn parse_cmd_opts( let woptarg = w.woptarg.unwrap(); let e: EventDescription; if opt == 'j' && woptarg == "caller" { - let libdata = parser.libdata(); - let caller_id = if libdata.is_subshell { - libdata.caller_id + let caller_id = if parser.scope().is_subshell { + parser.libdata().caller_id } else { 0 }; diff --git a/src/builtins/read.rs b/src/builtins/read.rs index e322f7749..6d48d7f1d 100644 --- a/src/builtins/read.rs +++ b/src/builtins/read.rs @@ -3,7 +3,6 @@ use super::prelude::*; use crate::common::escape; use crate::common::read_blocked; -use crate::common::scoped_push_replacer; use crate::common::str2wcstring; use crate::common::unescape_string; use crate::common::valid_var_name; @@ -238,11 +237,7 @@ fn read_interactive( commandline_set_buffer(Some(commandline.to_owned()), None); let mline = { - let _interactive = scoped_push_replacer( - |new_value| std::mem::replace(&mut parser.libdata_mut().is_interactive, new_value), - true, - ); - + let _interactive = parser.push_scope(|s| s.is_interactive = true); reader_readline(parser, nchars) }; terminal_protocols_disable_ifn(); diff --git a/src/builtins/return.rs b/src/builtins/return.rs index bcba616a7..91fa8ac8c 100644 --- a/src/builtins/return.rs +++ b/src/builtins/return.rs @@ -68,9 +68,8 @@ pub fn r#return(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> // If we're not in a function, exit the current script (but not an interactive shell). if !has_function_block { - let ld = &mut parser.libdata_mut(); - if !ld.is_interactive { - ld.exit_current_script = true; + if !parser.scope().is_interactive { + parser.libdata_mut().exit_current_script = true; } return retval; } diff --git a/src/builtins/status.rs b/src/builtins/status.rs index b26177c0e..b94122d28 100644 --- a/src/builtins/status.rs +++ b/src/builtins/status.rs @@ -516,7 +516,7 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B return Err(STATUS_CMD_ERROR); } STATUS_IS_COMMAND_SUB => { - if parser.libdata().is_subshell { + if parser.scope().is_subshell { return Ok(SUCCESS); } else { return Err(STATUS_CMD_ERROR); diff --git a/src/complete.rs b/src/complete.rs index a972ca640..f86bd7ece 100644 --- a/src/complete.rs +++ b/src/complete.rs @@ -1200,14 +1200,12 @@ fn complete_abbr(&mut self, cmd: WString) { fn complete_from_args(&mut self, s: &wstr, args: &wstr, desc: &wstr, flags: CompleteFlags) { let is_autosuggest = self.flags.autosuggestion; - let saved_state = if let Some(parser) = self.ctx.maybe_parser() { - let saved_interactive = parser.libdata().is_interactive; - parser.libdata_mut().is_interactive = false; - - Some((saved_interactive, parser.get_last_statuses())) - } else { - None - }; + let mut saved_statuses = None; + let mut scope = None; + if let Some(parser) = self.ctx.maybe_parser() { + saved_statuses = Some(parser.get_last_statuses()); + scope = Some(parser.push_scope(|s| s.is_interactive = false)); + } let eflags = if is_autosuggest { ExpandFlags::FAIL_ON_CMDSUBST @@ -1218,10 +1216,9 @@ fn complete_from_args(&mut self, s: &wstr, args: &wstr, desc: &wstr, flags: Comp let possible_comp = Parser::expand_argument_list(args, eflags, self.ctx); if let Some(parser) = self.ctx.maybe_parser() { - let (saved_interactive, status) = saved_state.unwrap(); - parser.libdata_mut().is_interactive = saved_interactive; - parser.set_last_statuses(status); + parser.set_last_statuses(saved_statuses.unwrap()); } + std::mem::drop(scope); // Allow leading dots - see #3707. self.complete_strings( diff --git a/src/event.rs b/src/event.rs index 1fdc7651d..2f0aeacc4 100644 --- a/src/event.rs +++ b/src/event.rs @@ -7,7 +7,7 @@ use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use std::sync::{Arc, Mutex}; -use crate::common::{escape, scoped_push_replacer, ScopeGuard}; +use crate::common::{escape, ScopeGuard}; use crate::flog::FLOG; use crate::io::{IoChain, IoStreams}; use crate::job_group::MaybeJobId; @@ -461,14 +461,10 @@ pub fn get_function_handlers(name: &wstr) -> EventHandlerList { /// allocated/initialized unless needed. fn fire_internal(parser: &Parser, event: &Event) { // Suppress fish_trace during events. - let _set_event = scoped_push_replacer( - |new_value| std::mem::replace(&mut parser.libdata_mut().is_event, new_value), - true, - ); - let _suppress_trace = scoped_push_replacer( - |new_value| std::mem::replace(&mut parser.libdata_mut().suppress_fish_trace, new_value), - true, - ); + let _saved = parser.push_scope(|s| { + s.is_event = true; + s.suppress_fish_trace = true; + }); // Capture the event handlers that match this event. let fire: Vec<_> = EVENT_HANDLERS @@ -497,12 +493,10 @@ fn fire_internal(parser: &Parser, event: &Event) { // Event handlers are not part of the main flow of code, so they are marked as // non-interactive. - let saved_is_interactive = - std::mem::replace(&mut parser.libdata_mut().is_interactive, false); + let _non_interactive = parser.push_scope(|s| s.is_interactive = false); let saved_statuses = parser.get_last_statuses(); let _cleanup = ScopeGuard::new((), |()| { parser.set_last_statuses(saved_statuses); - parser.libdata_mut().is_interactive = saved_is_interactive; }); FLOG!( @@ -530,10 +524,8 @@ fn fire_internal(parser: &Parser, event: &Event) { /// Fire all delayed events attached to the given parser. pub fn fire_delayed(parser: &Parser) { { - let ld = &parser.libdata(); - // Do not invoke new event handlers from within event handlers. - if ld.is_event { + if parser.scope().is_event { return; }; } diff --git a/src/exec.rs b/src/exec.rs index 6afffc16c..bf4f86baa 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -8,8 +8,8 @@ STATUS_READ_TOO_MUCH, }; use crate::common::{ - exit_without_destructors, scoped_push_replacer, str2wcstring, truncate_at_nul, wcs2string, - wcs2zstring, write_loop, ScopeGuard, + exit_without_destructors, str2wcstring, truncate_at_nul, wcs2string, wcs2zstring, write_loop, + ScopeGuard, }; use crate::env::{EnvMode, EnvStack, Environment, Statuses, READ_BYTE_LIMIT}; use crate::env_dispatch::use_posix_spawn; @@ -1450,18 +1450,14 @@ fn exec_subshell_internal( is_subcmd: bool, ) -> Result<(), ErrorCode> { parser.assert_can_execute(); - let _is_subshell = scoped_push_replacer( - |new_value| std::mem::replace(&mut parser.libdata_mut().is_subshell, new_value), - true, - ); - let _read_limit = scoped_push_replacer( - |new_value| std::mem::replace(&mut parser.libdata_mut().read_limit, new_value), - if is_subcmd { + let _scoped = parser.push_scope(|s| { + s.is_subshell = true; + s.read_limit = if is_subcmd { READ_BYTE_LIMIT.load(Ordering::Relaxed) } else { 0 - }, - ); + }; + }); let prev_statuses = parser.get_last_statuses(); let _put_back = ScopeGuard::new((), |()| { @@ -1474,8 +1470,7 @@ fn exec_subshell_internal( // IO buffer creation may fail (e.g. if we have too many open files to make a pipe), so this may // be null. - let Ok(bufferfill) = IoBufferfill::create_opts(parser.libdata().read_limit, STDOUT_FILENO) - else { + let Ok(bufferfill) = IoBufferfill::create_opts(parser.scope().read_limit, STDOUT_FILENO) else { *break_expand = true; return Err(STATUS_CMD_ERROR); }; diff --git a/src/parse_execution.rs b/src/parse_execution.rs index 6add17d13..7ea5ae61f 100644 --- a/src/parse_execution.rs +++ b/src/parse_execution.rs @@ -1623,10 +1623,10 @@ fn run_1_job( props.initial_background = job_is_background; { let parser = ctx.parser(); - let ld = &parser.libdata(); + let sc = parser.scope(); props.skip_notification = - ld.is_subshell || parser.is_block() || ld.is_event || !parser.is_interactive(); - props.from_event_handler = ld.is_event; + sc.is_subshell || parser.is_block() || sc.is_event || !parser.is_interactive(); + props.from_event_handler = sc.is_event; } let mut job = Job::new(props, self.node_source_owned(job_node)); @@ -1887,7 +1887,7 @@ fn setup_group(&self, ctx: &OperationContext<'_>, j: &mut Job) { } else { // This is a "real job" that gets its own pgroup. j.processes_mut()[0].leads_pgrp = true; - let wants_terminal = !ctx.parser().libdata().is_event; + let wants_terminal = !ctx.parser().scope().is_event; j.group = Some(JobGroup::create_with_job_control( j.command().to_owned(), wants_terminal, diff --git a/src/parser.rs b/src/parser.rs index 79999f956..05276012e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4,7 +4,7 @@ use crate::builtins::shared::STATUS_ILLEGAL_CMD; use crate::common::{ escape_string, scoped_push_replacer, CancelChecker, EscapeFlags, EscapeStringStyle, - FilenameRef, ScopeGuarding, PROFILING_ACTIVE, + FilenameRef, ScopeGuarding, ScopedCell, PROFILING_ACTIVE, }; use crate::complete::CompletionList; use crate::env::{EnvMode, EnvStack, EnvStackSetResult, Environment, Statuses}; @@ -217,6 +217,30 @@ pub fn now() -> Microseconds { } } +/// Data which is managed in a scoped fashion: is generally set for the duration of a block +/// of code. Note this is stored in a Cell and so must be Copy. +#[derive(Copy, Clone, Default)] +pub struct ScopedData { + /// Whether we are running a subshell command. + pub is_subshell: bool, + + /// Whether we are running an event handler. + pub is_event: bool, + + /// Whether we are currently interactive. + pub is_interactive: bool, + + /// Whether to suppress fish_trace output. This occurs in the prompt, event handlers, and key + /// bindings. + pub suppress_fish_trace: bool, + + /// The read limit to apply to captured subshell output, or 0 for none. + pub read_limit: usize, + + /// Whether we are currently cleaning processes. + pub is_cleaning_procs: bool, +} + /// Miscellaneous data used to avoid recursion and others. #[derive(Default)] pub struct LibraryData { @@ -232,6 +256,7 @@ pub struct LibraryData { /// This is never null and never invalid. pub cwd_fd: Option>, + /// Variables supporting the "status" builtin. pub status_vars: StatusVars, /// A counter incremented every time a command executes. @@ -259,26 +284,10 @@ pub struct LibraryData { /// Whether we called builtin_complete -C without parameter. pub builtin_complete_current_commandline: bool, - /// Whether we are currently cleaning processes. - pub is_cleaning_procs: bool, - /// The internal job id of the job being populated, or 0 if none. /// This supports the '--on-job-exit caller' feature. pub caller_id: u64, // TODO should be InternalJobId - /// Whether we are running a subshell command. - pub is_subshell: bool, - - /// Whether we are running an event handler. - pub is_event: bool, - - /// Whether we are currently interactive. - pub is_interactive: bool, - - /// Whether to suppress fish_trace output. This occurs in the prompt, event handlers, and key - /// bindings. - pub suppress_fish_trace: bool, - /// Whether we should break or continue the current loop. /// This is set by the 'break' and 'continue' commands. pub loop_status: LoopStatus, @@ -292,9 +301,6 @@ pub struct LibraryData { /// Note this only exits up to the "current script boundary." That is, a call to exit within a /// 'source' or 'read' command will only exit up to that command. pub exit_current_script: bool, - - /// The read limit to apply to captured subshell output, or 0 for none. - pub read_limit: usize, } impl LibraryData { @@ -395,6 +401,9 @@ pub struct Parser { /// Set of variables for the parser. pub variables: Rc, + /// Data managed in a scoped fashion. + scoped_data: ScopedCell, + /// Miscellaneous library data. library_data: RefCell, @@ -422,6 +431,7 @@ pub fn new(variables: Rc, cancel_behavior: CancelBehavior) -> Parser { block_list: RefCell::default(), eval_level: AtomicIsize::new(-1), variables, + scoped_data: ScopedCell::new(ScopedData::default()), library_data: RefCell::new(LibraryData::new()), syncs_uvars: RelaxedAtomicBool::new(false), cancel_behavior, @@ -814,10 +824,28 @@ pub fn vars_ref(&self) -> Rc { Rc::clone(&self.variables) } + /// Get a copy of the scoped data. + #[inline(always)] + pub fn scope(&self) -> ScopedData { + self.scoped_data.get() + } + + /// Modify the scoped values for the duration of the caller's scope (or whenever the ParserScope is dropped). + /// This accepts a closure which modifies the ScopedData, and returns a ParserScope which restores the + /// data when dropped. + pub fn push_scope<'a, F: FnOnce(&mut ScopedData)>( + &'a self, + modifier: F, + ) -> impl ScopeGuarding + 'a { + self.scoped_data.scoped_mod(modifier) + } + /// Get the library data. pub fn libdata(&self) -> Ref<'_, LibraryData> { self.library_data.borrow() } + + /// Get the library data, mutably. pub fn libdata_mut(&self) -> RefMut<'_, LibraryData> { self.library_data.borrow_mut() } @@ -1074,7 +1102,7 @@ pub fn current_filename(&self) -> Option { /// Return if we are interactive, which means we are executing a command that the user typed in /// (and not, say, a prompt). pub fn is_interactive(&self) -> bool { - self.libdata().is_interactive + self.scope().is_interactive } /// Return a string representing the current stack trace. diff --git a/src/proc.rs b/src/proc.rs index e2911d340..5d3f5556f 100644 --- a/src/proc.rs +++ b/src/proc.rs @@ -4,8 +4,8 @@ use crate::ast; use crate::common::{ - charptr2wcstring, escape, is_windows_subsystem_for_linux, redirect_tty_output, - scoped_push_replacer, timef, Timepoint, WSL, + charptr2wcstring, escape, is_windows_subsystem_for_linux, redirect_tty_output, timef, + Timepoint, WSL, }; use crate::env::Statuses; use crate::event::{self, Event}; @@ -1044,7 +1044,7 @@ pub fn continue_job(&self, parser: &Parser) { } else { "UNCOMPLETED" }, - if parser.libdata().is_interactive { + if parser.scope().is_interactive { "INTERACTIVE" } else { "NON-INTERACTIVE" @@ -1357,7 +1357,7 @@ fn handle_child_status(job: &Job, proc: &Process, status: &ProcStatus) { /// Wait for any process finishing, or receipt of a signal. pub fn proc_wait_any(parser: &Parser) { process_mark_finished_children(parser, true /*block_ok*/); - let is_interactive = parser.libdata().is_interactive; + let is_interactive = parser.scope().is_interactive; process_clean_after_marking(parser, is_interactive); } @@ -1779,14 +1779,11 @@ fn process_clean_after_marking(parser: &Parser, allow_interactive: bool) -> bool // This function may fire an event handler, we do not want to call ourselves recursively (to // avoid infinite recursion). - if parser.libdata().is_cleaning_procs { + if parser.scope().is_cleaning_procs { return false; } - let _cleaning = scoped_push_replacer( - |new_value| std::mem::replace(&mut parser.libdata_mut().is_cleaning_procs, new_value), - true, - ); + let _cleaning = parser.push_scope(|s| s.is_cleaning_procs = true); // This may be invoked in an exit handler, after the TERM has been torn down // Don't try to print in that case (#3222) diff --git a/src/reader.rs b/src/reader.rs index 35c26a4e0..788d49643 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -51,9 +51,8 @@ use crate::common::restore_term_foreground_process_group_for_exit; use crate::common::{ escape, escape_string, exit_without_destructors, get_ellipsis_char, get_obfuscation_read_char, - redirect_tty_output, scoped_push_replacer, scoped_push_replacer_ctx, shell_modes, str2wcstring, - wcs2string, write_loop, EscapeFlags, EscapeStringStyle, ScopeGuard, PROGRAM_NAME, - UTF8_BOM_WCHAR, + redirect_tty_output, shell_modes, str2wcstring, wcs2string, write_loop, EscapeFlags, + EscapeStringStyle, ScopeGuard, PROGRAM_NAME, UTF8_BOM_WCHAR, }; use crate::complete::{ complete, complete_load, sort_and_prioritize, CompleteFlags, Completion, CompletionList, @@ -637,10 +636,7 @@ pub fn reader_read(parser: &Parser, fd: RawFd, io: &IoChain) -> Result<(), Error } } - let _interactive_push = scoped_push_replacer( - |new_value| std::mem::replace(&mut parser.libdata_mut().is_interactive, new_value), - interactive, - ); + let _interactive_push = parser.push_scope(|s| s.is_interactive = interactive); signal_set_handlers_once(interactive); let res = if interactive { @@ -2155,40 +2151,31 @@ fn readline(&mut self, nchars: Option) -> Option { // Suppress fish_trace during executing key bindings. // This is simply to reduce noise. - let mut zelf = scoped_push_replacer_ctx( - self, - |zelf, new_value| { - std::mem::replace( - &mut zelf.parser.libdata_mut().suppress_fish_trace, - new_value, - ) - }, - true, - ); + let _restore = self.parser.push_scope(|s| s.suppress_fish_trace = true); // If nchars_or_0 is positive, then that's the maximum number of chars. Otherwise keep it at // SIZE_MAX. - zelf.rls_mut().nchars = nchars; + self.rls_mut().nchars = nchars; // The command line before completion. - zelf.cycle_command_line.clear(); - zelf.cycle_cursor_pos = 0; + self.cycle_command_line.clear(); + self.cycle_cursor_pos = 0; - zelf.history_search.reset(); + self.history_search.reset(); // It may happen that a command we ran when job control was disabled nevertheless stole the tty // from us. In that case when we read from our fd, it will trigger SIGTTIN. So just // unconditionally reclaim the tty. See #9181. - unsafe { libc::tcsetpgrp(zelf.conf.inputfd, libc::getpgrp()) }; + unsafe { libc::tcsetpgrp(self.conf.inputfd, libc::getpgrp()) }; // Get the current terminal modes. These will be restored when the function returns. let mut old_modes: libc::termios = unsafe { std::mem::zeroed() }; - if unsafe { libc::tcgetattr(zelf.conf.inputfd, &mut old_modes) } == -1 && errno().0 == EIO { + if unsafe { libc::tcgetattr(self.conf.inputfd, &mut old_modes) } == -1 && errno().0 == EIO { redirect_tty_output(false); } // Set the new modes. - if unsafe { libc::tcsetattr(zelf.conf.inputfd, TCSANOW, &*shell_modes()) } == -1 { + if unsafe { libc::tcsetattr(self.conf.inputfd, TCSANOW, &*shell_modes()) } == -1 { let err = errno().0; if err == EIO { redirect_tty_output(false); @@ -2202,17 +2189,17 @@ fn readline(&mut self, nchars: Option) -> Option { } } - if *zelf.blocking_wait() == Some(BlockingWait::Startup(Queried::NotYet)) { + if *self.blocking_wait() == Some(BlockingWait::Startup(Queried::NotYet)) { if is_dumb() || IN_MIDNIGHT_COMMANDER.load() || IN_DVTM.load() { - *zelf.blocking_wait() = None; + *self.blocking_wait() = None; } else { - *zelf.blocking_wait() = Some(BlockingWait::Startup(Queried::Once)); + *self.blocking_wait() = Some(BlockingWait::Startup(Queried::Once)); let mut out = Outputter::stdoutput().borrow_mut(); out.begin_buffering(); // Query for kitty keyboard protocol support. let _ = out.write_all(kitty_progressive_enhancements_query()); // Query for cursor position reporting support. - zelf.request_cursor_position(&mut out, None); + self.request_cursor_position(&mut out, None); // Query for synchronized output support. let _ = out.write_all(b"\x1b[?2026$p"); let _ = out.write_all(b"\x1b[>0q"); // XTVERSION @@ -2230,64 +2217,64 @@ fn readline(&mut self, nchars: Option) -> Option { // appear constantly. // // I can't see a good way around this. - if !zelf.first_prompt { - zelf.screen + if !self.first_prompt { + self.screen .reset_abandoning_line(usize::try_from(termsize_last().width).unwrap()); } - zelf.first_prompt = false; + self.first_prompt = false; - if !zelf.conf.event.is_empty() { - event::fire_generic(zelf.parser, zelf.conf.event.to_owned(), vec![]); + if !self.conf.event.is_empty() { + event::fire_generic(self.parser, self.conf.event.to_owned(), vec![]); } - zelf.exec_prompt(); + self.exec_prompt(); // Start out as initially dirty. - zelf.force_exec_prompt_and_repaint = true; + self.force_exec_prompt_and_repaint = true; - while !zelf.rls().finished && !check_exit_loop_maybe_warning(Some(&mut zelf)) { - if zelf.handle_char_event(None).is_break() { + while !self.rls().finished && !check_exit_loop_maybe_warning(Some(self)) { + if self.handle_char_event(None).is_break() { break; } } // Redraw the command line. This is what ensures the autosuggestion is hidden, etc. after the // user presses enter. - if zelf.is_repaint_needed(None) - || zelf.screen.scrolled() - || zelf.conf.inputfd != STDIN_FILENO + if self.is_repaint_needed(None) + || self.screen.scrolled() + || self.conf.inputfd != STDIN_FILENO { - zelf.layout_and_repaint_before_execution(); + self.layout_and_repaint_before_execution(); } // Finish syntax highlighting (but do not wait forever). - if zelf.rls().finished { - zelf.finish_highlighting_before_exec(); + if self.rls().finished { + self.finish_highlighting_before_exec(); } // Emit a newline so that the output is on the line after the command. // But do not emit a newline if the cursor has wrapped onto a new line all its own - see #6826. - if !zelf.screen.cursor_is_wrapped_to_own_line() { + if !self.screen.cursor_is_wrapped_to_own_line() { let _ = write_to_fd(b"\n", STDOUT_FILENO); } // HACK: If stdin isn't the same terminal as stdout, we just moved the cursor. // For now, just reset it to the beginning of the line. - if zelf.conf.inputfd != STDIN_FILENO { + if self.conf.inputfd != STDIN_FILENO { let _ = write_loop(&STDOUT_FILENO, b"\r"); } // Ensure we have no pager contents when we exit. - if !zelf.pager.is_empty() { + if !self.pager.is_empty() { // Clear to end of screen to erase the pager contents. // TODO: this may fail if eos doesn't exist, in which case we should emit newlines. screen_force_clear_to_end(); - zelf.clear_pager(); + self.clear_pager(); } if EXIT_STATE.load(Ordering::Relaxed) != ExitState::FinishedHandlers as _ { // The order of the two conditions below is important. Try to restore the mode // in all cases, but only complain if interactive. - if unsafe { libc::tcsetattr(zelf.conf.inputfd, TCSANOW, &old_modes) } == -1 + if unsafe { libc::tcsetattr(self.conf.inputfd, TCSANOW, &old_modes) } == -1 && is_interactive_session() { if errno().0 == EIO { @@ -2299,11 +2286,11 @@ fn readline(&mut self, nchars: Option) -> Option { .borrow_mut() .set_color(RgbColor::RESET, RgbColor::RESET); } - let result = zelf + let result = self .rls() .finished - .then(|| zelf.command_line.text().to_owned()); - zelf.rls = None; + .then(|| self.command_line.text().to_owned()); + self.rls = None; result } @@ -4513,14 +4500,10 @@ pub fn reader_write_title( parser: &Parser, reset_cursor_position: bool, /* = true */ ) { - let _noninteractive = scoped_push_replacer( - |new_value| std::mem::replace(&mut parser.libdata_mut().is_interactive, new_value), - false, - ); - let _in_title = scoped_push_replacer( - |new_value| std::mem::replace(&mut parser.libdata_mut().suppress_fish_trace, new_value), - true, - ); + let _scoped = parser.push_scope(|s| { + s.is_interactive = false; + s.suppress_fish_trace = true; + }); let mut fish_title_command = DEFAULT_TITLE.to_owned(); if function::exists(L!("fish_title"), parser) { @@ -4586,67 +4569,51 @@ fn exec_prompt(&mut self) { self.right_prompt_buff.clear(); // Suppress fish_trace while in the prompt. - let mut zelf = scoped_push_replacer_ctx( - self, - |zelf, new_value| { - std::mem::replace( - &mut zelf.parser.libdata_mut().suppress_fish_trace, - new_value, - ) - }, - true, - ); + let _suppress_trace = self.parser.push_scope(|s| s.suppress_fish_trace = true); // Update the termsize now. // This allows prompts to react to $COLUMNS. - zelf.update_termsize(); + self.update_termsize(); // If we have any prompts, they must be run non-interactively. - if !zelf.conf.left_prompt_cmd.is_empty() || !zelf.conf.right_prompt_cmd.is_empty() { - let mut zelf = scoped_push_replacer_ctx( - &mut zelf, - |zelf, new_value| { - std::mem::replace(&mut zelf.parser.libdata_mut().is_interactive, new_value) - }, - false, - ); + if !self.conf.left_prompt_cmd.is_empty() || !self.conf.right_prompt_cmd.is_empty() { + let _noninteractive = self.parser.push_scope(|s| s.is_interactive = false); + self.exec_mode_prompt(); - zelf.exec_mode_prompt(); - - if !zelf.conf.left_prompt_cmd.is_empty() { + if !self.conf.left_prompt_cmd.is_empty() { // Status is ignored. let mut prompt_list = vec![]; // Historic compatibility hack. // If the left prompt function is deleted, then use a default prompt instead of // producing an error. - let left_prompt_deleted = zelf.conf.left_prompt_cmd == LEFT_PROMPT_FUNCTION_NAME - && !function::exists(&zelf.conf.left_prompt_cmd, zelf.parser); + let left_prompt_deleted = self.conf.left_prompt_cmd == LEFT_PROMPT_FUNCTION_NAME + && !function::exists(&self.conf.left_prompt_cmd, self.parser); let _ = exec_subshell( if left_prompt_deleted { DEFAULT_PROMPT } else { - &zelf.conf.left_prompt_cmd + &self.conf.left_prompt_cmd }, - zelf.parser, + self.parser, Some(&mut prompt_list), /*apply_exit_status=*/ false, ); - zelf.left_prompt_buff = join_strings(&prompt_list, '\n'); + self.left_prompt_buff = join_strings(&prompt_list, '\n'); } - if !zelf.conf.right_prompt_cmd.is_empty() { - if function::exists(&zelf.conf.right_prompt_cmd, zelf.parser) { + if !self.conf.right_prompt_cmd.is_empty() { + if function::exists(&self.conf.right_prompt_cmd, self.parser) { // Status is ignored. let mut prompt_list = vec![]; let _ = exec_subshell( - &zelf.conf.right_prompt_cmd, - zelf.parser, + &self.conf.right_prompt_cmd, + self.parser, Some(&mut prompt_list), /*apply_exit_status=*/ false, ); // Right prompt does not support multiple lines, so just concatenate all of them. for i in prompt_list { - zelf.right_prompt_buff.push_utfstr(&i); + self.right_prompt_buff.push_utfstr(&i); } } } @@ -4655,17 +4622,17 @@ fn exec_prompt(&mut self) { // Write the screen title. Do not reset the cursor position: exec_prompt is called when there // may still be output on the line from the previous command (#2499) and we need our PROMPT_SP // hack to work. - reader_write_title(L!(""), zelf.parser, false); + reader_write_title(L!(""), self.parser, false); // Reap jobs but do NOT trigger a repaint. // This is to prevent infinite loops in case a job from the prompt triggers a repaint. // See #9796. - job_reap(zelf.parser, true); + job_reap(self.parser, true); // Some prompt may have requested an exit (#8033). - let exit_current_script = zelf.parser.libdata().exit_current_script; - zelf.exit_loop_requested |= exit_current_script; - zelf.parser.libdata_mut().exit_current_script = false; + let exit_current_script = self.parser.libdata().exit_current_script; + self.exit_loop_requested |= exit_current_script; + self.parser.libdata_mut().exit_current_script = false; } } @@ -5354,10 +5321,7 @@ fn expand_replacer( let mut cmd = escape(&repl.replacement); cmd.push(' '); cmd.push_utfstr(&escape(token)); - let _not_interactive = scoped_push_replacer( - |new_value| std::mem::replace(&mut parser.libdata_mut().is_interactive, new_value), - false, - ); + let _not_interactive = parser.push_scope(|s| s.is_interactive = false); let mut outputs = vec![]; if exec_subshell( @@ -5807,10 +5771,7 @@ fn should_add_to_history(&mut self, text: &wstr) -> bool { let mut cmd: WString = L!("fish_should_add_to_history ").into(); cmd.push_utfstr(&escape(text)); - let _not_interactive = scoped_push_replacer( - |new_value| std::mem::replace(&mut parser.libdata_mut().is_interactive, new_value), - false, - ); + let _not_interactive = parser.push_scope(|s| s.is_interactive = false); exec_subshell(&cmd, parser, None, /*apply_exit_status=*/ false).is_ok() } diff --git a/src/reader_history_search.rs b/src/reader_history_search.rs index 8f1a93d2c..63e2a57c7 100644 --- a/src/reader_history_search.rs +++ b/src/reader_history_search.rs @@ -204,12 +204,14 @@ fn add_if_new(&mut self, search_match: SearchMatch) { /// Attempt to append matches from the current history item. /// Return true if something was appended. fn append_matches_from_search(&mut self) -> bool { - fn find(zelf: &ReaderHistorySearch, haystack: &wstr, needle: &wstr) -> Option { - if zelf.search().ignores_case() { - return ifind(haystack, needle, false); + let icase = self.search().ignores_case(); + let find = |haystack: &wstr, needle: &wstr| -> Option { + if icase { + ifind(haystack, needle, false) + } else { + haystack.find(needle) } - haystack.find(needle) - } + }; let before = self.matches.len(); let text = self.search().current_string(); let needle = self.search_string(); @@ -220,7 +222,7 @@ fn find(zelf: &ReaderHistorySearch, haystack: &wstr, needle: &wstr) -> Option Option bool { - let ld = &parser.libdata(); - if ld.suppress_fish_trace { + if parser.scope().suppress_fish_trace { return false; } DO_TRACE.load()