diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 5cb9589e8..6efc40ea6 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -17,6 +17,7 @@ fd::{AsRawFd, BorrowedFd, RawFd}, unix::ffi::OsStrExt as _, }, + rc::Rc, sync::{ Arc, LazyLock, atomic::{AtomicI32, AtomicU32, Ordering}, @@ -1184,10 +1185,10 @@ pub fn restore_term_foreground_process_group_for_exit() { } } -/// A wrapper around Cell which supports modifying the contents, scoped to a region of code. -/// This provides a somewhat nicer API than ScopedRefCell because you can directly modify the value, -/// instead of requiring an accessor function which returns a mutable reference to a field. -pub struct ScopedCell(Cell); +/// A wrapper around `Rc` which supports modifying the contents, scoped to a region of code. +/// This provides a somewhat nicer API than ScopedRefCell because you can directly modify the +/// value, instead of requiring an accessor function which returns a mutable reference to a field. +pub struct ScopedCell(Rc>); impl Deref for ScopedCell { type Target = Cell; @@ -1197,15 +1198,9 @@ fn deref(&self) -> &Self::Target { } } -impl DerefMut for ScopedCell { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - impl ScopedCell { pub fn new(value: T) -> Self { - Self(Cell::new(value)) + Self(Rc::new(Cell::new(value))) } /// Temporarily modify a value in the ScopedCell, restoring it when the returned object is dropped. @@ -1229,19 +1224,22 @@ pub fn new(value: T) -> Self { /// // Restored after scope /// assert_eq!(cell.get(), 5); /// ``` - pub fn scoped_mod<'a, Modifier: FnOnce(&mut T)>( - &'a self, + pub fn scoped_mod( + &self, modifier: Modifier, - ) -> impl DerefMut + 'a { + ) -> impl DerefMut + use { let mut val = self.get(); modifier(&mut val); let saved = self.replace(val); - ScopeGuard::new(self, move |cell| cell.set(saved)) + let inner = Rc::clone(&self.0); + ScopeGuard::new((), move |()| inner.set(saved)) } } -/// A wrapper around RefCell which supports modifying the contents, scoped to a region of code. -pub struct ScopedRefCell(RefCell); +/// A wrapper around `Rc` which supports modifying the contents, scoped to a region +/// of code. +#[derive(Default)] +pub struct ScopedRefCell(Rc>); impl Deref for ScopedRefCell { type Target = RefCell; @@ -1251,15 +1249,9 @@ fn deref(&self) -> &Self::Target { } } -impl DerefMut for ScopedRefCell { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - impl ScopedRefCell { pub fn new(value: T) -> Self { - Self(RefCell::new(value)) + Self(Rc::new(RefCell::new(value))) } /// Temporarily modify a field in the ScopedRefCell, restoring it when the returned guard is dropped. @@ -1286,19 +1278,20 @@ pub fn new(value: T) -> Self { /// // Restored after scope /// assert_eq!(cell.borrow().flag, false); /// ``` - pub fn scoped_set<'a, Accessor, Value: 'a>( - &'a self, + pub fn scoped_set( + &self, value: Value, accessor: Accessor, - ) -> impl DerefMut + 'a + ) -> impl DerefMut + use where - Accessor: Fn(&mut T) -> &mut Value + 'a, + Accessor: Fn(&mut T) -> &mut Value, { let mut data = self.borrow_mut(); let mut saved = std::mem::replace(accessor(&mut data), value); - ScopeGuard::new(self, move |cell| { - let mut data = cell.borrow_mut(); - std::mem::swap((accessor)(&mut data), &mut saved); + let inner = Rc::clone(&self.0); + ScopeGuard::new((), move |()| { + let mut inner = inner.borrow_mut(); + std::mem::swap((accessor)(&mut inner), &mut saved); }) } @@ -1320,7 +1313,7 @@ pub fn scoped_set<'a, Accessor, Value: 'a>( /// /// assert_eq!(*cell.borrow(), 10); /// ``` - pub fn scoped_replace<'a>(&'a self, value: T) -> impl DerefMut + 'a { + pub fn scoped_replace(&self, value: T) -> impl DerefMut + use { self.scoped_set(value, |s| s) } } diff --git a/src/abbrs.rs b/src/abbrs.rs index ab4a6f5b0..56606529b 100644 --- a/src/abbrs.rs +++ b/src/abbrs.rs @@ -294,7 +294,7 @@ mod tests { #[serial] fn test_abbreviations() { test_init(); - let parser = TestParser::new(); + let parser = &mut TestParser::new(); { let mut abbrs = abbrs_get_set(); abbrs.add(Abbreviation::new( @@ -352,12 +352,12 @@ macro_rules! abbr_expand_1 { abbr_expand_1!("gc", cmd, "git checkout"); abbr_expand_1!("foo", cmd, "bar"); - let expand_abbreviation_in_command = + let mut expand_abbreviation_in_command = |cmdline: &wstr, cursor_pos: Option| -> Option { let replacement = reader_expand_abbreviation_at_cursor( cmdline, cursor_pos.unwrap_or(cmdline.len()), - &parser, + parser, )?; let mut cmdline_expanded = cmdline.to_owned(); let mut colors = vec![HighlightSpec::new(); cmdline.len()]; diff --git a/src/autoload.rs b/src/autoload.rs index 8c214d526..cfb5f824f 100644 --- a/src/autoload.rs +++ b/src/autoload.rs @@ -122,12 +122,12 @@ pub fn resolve_command(&mut self, cmd: &wstr, env: &dyn Environment) -> Autoload /// Helper to actually perform an autoload. /// This is a static function because it executes fish script, and so must be called without /// holding any particular locks. - pub fn perform_autoload(path: &AutoloadPath, parser: &Parser) { + pub fn perform_autoload(path: &AutoloadPath, parser: &mut Parser) { // We do the useful part of what exec_subshell does ourselves // - we source the file. // We don't create a buffer or check ifs or create a read_limit let prev_statuses = parser.last_statuses(); - let _put_back = ScopeGuard::new((), |()| parser.set_last_statuses(prev_statuses)); + let mut parser = ScopeGuard::new(parser, |parser| parser.set_last_statuses(prev_statuses)); match path { AutoloadPath::OnDisk(p) => { let script_source = L!("source ").to_owned() + &escape(p)[..]; diff --git a/src/bin/fish.rs b/src/bin/fish.rs index 5cd1e0810..18db56e7e 100644 --- a/src/bin/fish.rs +++ b/src/bin/fish.rs @@ -139,7 +139,7 @@ fn print_rusage_self() { // Source the file config.fish in the given directory. // Returns true if successful, false if not. -fn source_config_in_directory(parser: &Parser, dir: &wstr) -> bool { +fn source_config_in_directory(parser: &mut Parser, dir: &wstr) -> bool { // If the config.fish file doesn't exist or isn't readable silently return. Fish versions up // thru 2.2.0 would instead try to source the file with stderr redirected to /dev/null to deal // with that possibility. @@ -168,7 +168,7 @@ fn source_config_in_directory(parser: &Parser, dir: &wstr) -> bool { } /// Parse init files. exec_path is the path of fish executable as determined by argv[0]. -fn read_init(parser: &Parser, paths: &ConfigPaths) { +fn read_init(parser: &mut Parser, paths: &ConfigPaths) { use fish::autoload::Asset; let emfile = Asset::get("config.fish").expect("Embedded file not found"); let src = bytes2wcstring(&emfile.data); @@ -190,7 +190,7 @@ fn read_init(parser: &Parser, paths: &ConfigPaths) { } } -fn run_command_list(parser: &Parser, cmds: &[OsString]) -> Result<(), libc::c_int> { +fn run_command_list(parser: &mut Parser, cmds: &[OsString]) -> Result<(), libc::c_int> { let mut retval = Ok(()); for cmd in cmds { let cmd_wcs = osstr2wcstring(cmd); @@ -490,7 +490,7 @@ fn throwing_main() -> i32 { // Construct the root parser! let env = EnvStack::globals().create_child(true /* dispatches_var_changes */); - let parser = &Parser::new(env, CancelBehavior::Clear); + let parser = &mut Parser::new(env, CancelBehavior::Clear); parser.set_syncs_uvars(!opts.no_config); if !opts.no_exec && !opts.no_config { diff --git a/src/builtins/abbr.rs b/src/builtins/abbr.rs index 5234324a9..49d5d0579 100644 --- a/src/builtins/abbr.rs +++ b/src/builtins/abbr.rs @@ -120,7 +120,7 @@ fn join(list: &[&wstr], sep: &wstr) -> WString { } // Print abbreviations in a fish-script friendly way. -fn abbr_show(opts: &Options, streams: &mut IoStreams, parser: &Parser) -> BuiltinResult { +fn abbr_show(opts: &Options, streams: &mut IoStreams, parser: &mut Parser) -> BuiltinResult { let style = EscapeStringStyle::Script(Default::default()); abbrs::with_abbrs(|abbrs| { @@ -172,7 +172,7 @@ fn abbr_show(opts: &Options, streams: &mut IoStreams, parser: &Parser) -> Builti if opts.color.enabled(streams) { streams.out.append(&bytes2wcstring(&highlight_and_colorize( &result, - &parser.context(), + &mut parser.context(), ))); } else { streams.out.append(&result); @@ -423,7 +423,7 @@ fn abbr_add(opts: &Options, streams: &mut IoStreams) -> BuiltinResult { } // Erase the named abbreviations. -fn abbr_erase(opts: &Options, parser: &Parser) -> BuiltinResult { +fn abbr_erase(opts: &Options, parser: &mut Parser) -> BuiltinResult { if opts.args.is_empty() { // This has historically been a silent failure. return Err(STATUS_CMD_ERROR); @@ -454,7 +454,7 @@ fn abbr_erase(opts: &Options, parser: &Parser) -> BuiltinResult { }) } -pub fn abbr(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { +pub fn abbr(parser: &mut Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { let mut argv_read = Vec::with_capacity(argv.len()); argv_read.extend_from_slice(argv); diff --git a/src/builtins/argparse.rs b/src/builtins/argparse.rs index 6af01c3aa..c4709fd53 100644 --- a/src/builtins/argparse.rs +++ b/src/builtins/argparse.rs @@ -663,7 +663,7 @@ fn populate_option_strings<'args>( } fn validate_arg<'opts>( - parser: &Parser, + parser: &mut Parser, opts_name: &wstr, opt_spec: &mut OptionSpec<'opts>, is_long_flag: bool, @@ -732,7 +732,7 @@ fn is_implicit_int(opts: &ArgParseCmdOpts, val: &wstr) -> bool { // Store this value under the implicit int option. fn validate_and_store_implicit_int<'args>( - parser: &Parser, + parser: &mut Parser, opts: &mut ArgParseCmdOpts<'args>, val: &'args wstr, w: &mut WGetopter, @@ -823,7 +823,7 @@ fn delete_flag<'args>(w: &mut WGetopter<'_, 'args, '_>, is_long_flag: bool) -> C } fn handle_flag<'args>( - parser: &Parser, + parser: &mut Parser, opts: &mut ArgParseCmdOpts<'args>, opt: char, is_long_flag: bool, @@ -874,7 +874,7 @@ fn handle_flag<'args>( } fn argparse_parse_flags<'args>( - parser: &Parser, + parser: &mut Parser, opts: &mut ArgParseCmdOpts<'args>, argc: usize, args: &mut [&'args wstr], @@ -1067,7 +1067,7 @@ fn argparse_parse_args<'args>( opts: &mut ArgParseCmdOpts<'args>, args: &mut [&'args wstr], argc: usize, - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, ) -> BuiltinResult { if argc <= 1 { @@ -1146,7 +1146,7 @@ fn set_argparse_result_vars(vars: &EnvStack, local_mode: EnvSetMode, opts: ArgPa /// an external command also means its output has to be in a form that can be eval'd. Because our /// version is a builtin it can directly set variables local to the current scope (e.g., a /// function). It doesn't need to write anything to stdout that then needs to be eval'd. -pub fn argparse(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +pub fn argparse(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { let Some(&cmd) = args.first() else { return Err(STATUS_INVALID_ARGS); }; diff --git a/src/builtins/bg.rs b/src/builtins/bg.rs index 6e9b22240..db7152e44 100644 --- a/src/builtins/bg.rs +++ b/src/builtins/bg.rs @@ -8,7 +8,7 @@ /// Helper function for builtin_bg(). fn send_to_bg( - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, cmd: &wstr, job_pos: usize, @@ -46,7 +46,7 @@ fn send_to_bg( } /// Builtin for putting a job in the background. -pub fn bg(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +pub fn bg(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { let opts = HelpOnlyCmdOpts::parse(args, parser, streams)?; let Some(&cmd) = args.first() else { diff --git a/src/builtins/bind.rs b/src/builtins/bind.rs index 921cd06f6..427c3f255 100644 --- a/src/builtins/bind.rs +++ b/src/builtins/bind.rs @@ -151,7 +151,7 @@ fn list_one( seq: &[Key], bind_mode: Option<&wstr>, user: bool, - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, ) -> bool { let results = self.input_mappings.get(seq, bind_mode, user); @@ -167,7 +167,7 @@ fn list_one( if self.opts.color.enabled(streams) { streams.out.append(&bytes2wcstring(&highlight_and_colorize( &out, - &parser.context(), + &mut parser.context(), ))); } else { streams.out.append(&out); @@ -187,7 +187,7 @@ fn list_one_user_andor_preset( bind_mode: Option<&wstr>, user: bool, preset: bool, - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, ) -> bool { let mut retval = false; @@ -201,7 +201,13 @@ fn list_one_user_andor_preset( } /// List all current key bindings. - fn list(&self, bind_mode: Option<&wstr>, user: bool, parser: &Parser, streams: &mut IoStreams) { + fn list( + &self, + bind_mode: Option<&wstr>, + user: bool, + parser: &mut Parser, + streams: &mut IoStreams, + ) { let lst = self.input_mappings.get_names(user); for binding in lst { if bind_mode.is_some_and(|m| m != binding.mode) { @@ -301,7 +307,7 @@ fn insert( &mut self, optind: usize, argv: &[&wstr], - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, ) -> bool { let argc = argv.len(); @@ -407,7 +413,7 @@ fn parse_cmd_opts( opts: &mut Options, optind: &mut usize, argv: &mut [&wstr], - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, ) -> BuiltinResult { let cmd = argv[0]; @@ -509,7 +515,7 @@ impl BuiltinBind { /// The bind builtin, used for setting character sequences. pub fn bind( &mut self, - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, argv: &mut [&wstr], ) -> BuiltinResult { @@ -568,6 +574,6 @@ pub fn bind( } } -pub fn bind(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +pub fn bind(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { BuiltinBind::new().bind(parser, streams, args) } diff --git a/src/builtins/block.rs b/src/builtins/block.rs index 86cc4b861..fcb76552a 100644 --- a/src/builtins/block.rs +++ b/src/builtins/block.rs @@ -1,5 +1,3 @@ -use std::sync::atomic::Ordering; - use crate::err_str; // Implementation of the block builtin. @@ -74,7 +72,7 @@ fn parse_options( } /// The block builtin, used for temporarily blocking events. -pub fn block(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +pub fn block(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { let cmd = args[0]; let (opts, _) = parse_options(args, parser, streams)?; @@ -92,11 +90,11 @@ pub fn block(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Bu return Err(STATUS_INVALID_ARGS); } - if parser.global_event_blocks.load(Ordering::Relaxed) == 0 { + if parser.global_event_blocks == 0 { err_str!("No blocks defined").cmd(cmd).finish(streams); return Err(STATUS_CMD_ERROR); } - parser.global_event_blocks.fetch_sub(1, Ordering::Relaxed); + parser.global_event_blocks -= 1; return Ok(SUCCESS); } @@ -135,7 +133,7 @@ pub fn block(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Bu if have_block { parser.block_at_index_mut(block_idx).unwrap().event_blocks |= true; } else { - parser.global_event_blocks.fetch_add(1, Ordering::Relaxed); + parser.global_event_blocks += 1; } Ok(SUCCESS) diff --git a/src/builtins/break.rs b/src/builtins/break.rs index 67bf721c2..3be81e629 100644 --- a/src/builtins/break.rs +++ b/src/builtins/break.rs @@ -1,5 +1,5 @@ use super::prelude::*; -pub fn r#break(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { +pub fn r#break(parser: &mut Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { builtin_break_continue(parser, streams, argv) } diff --git a/src/builtins/breakpoint.rs b/src/builtins/breakpoint.rs index 4bd0bd14c..0a4720639 100644 --- a/src/builtins/breakpoint.rs +++ b/src/builtins/breakpoint.rs @@ -6,7 +6,11 @@ use libc::STDIN_FILENO; /// Implementation of the builtin breakpoint command, used to launch the interactive debugger. -pub fn breakpoint(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { +pub fn breakpoint( + parser: &mut Parser, + streams: &mut IoStreams, + argv: &mut [&wstr], +) -> BuiltinResult { let cmd = argv[0]; if argv.len() != 1 { err_fmt!(Error::UNEXP_ARG_COUNT, 0, argv.len() - 1) diff --git a/src/builtins/builtin.rs b/src/builtins/builtin.rs index 7b8bdbd02..3d4f6ebd3 100644 --- a/src/builtins/builtin.rs +++ b/src/builtins/builtin.rs @@ -8,7 +8,11 @@ struct builtin_cmd_opts_t { list_names: bool, } -pub fn r#builtin(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { +pub fn r#builtin( + parser: &mut Parser, + streams: &mut IoStreams, + argv: &mut [&wstr], +) -> BuiltinResult { let cmd = argv[0]; let argc = argv.len(); let print_hints = false; diff --git a/src/builtins/cd.rs b/src/builtins/cd.rs index d0ceeaf90..739909611 100644 --- a/src/builtins/cd.rs +++ b/src/builtins/cd.rs @@ -15,7 +15,7 @@ // The cd builtin. Changes the current directory to the one specified or to $HOME if none is // specified. The directory can be relative to any directory in the CDPATH variable. -pub fn cd(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +pub fn cd(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { localizable_consts! { DIR_DOES_NOT_EXIST "The directory '%s' does not exist" diff --git a/src/builtins/command.rs b/src/builtins/command.rs index 5840d3a74..9d622773c 100644 --- a/src/builtins/command.rs +++ b/src/builtins/command.rs @@ -8,7 +8,11 @@ struct command_cmd_opts_t { find_path: bool, } -pub fn r#command(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { +pub fn r#command( + parser: &mut Parser, + streams: &mut IoStreams, + argv: &mut [&wstr], +) -> BuiltinResult { let cmd = argv[0]; let argc = argv.len(); let print_hints = false; diff --git a/src/builtins/commandline.rs b/src/builtins/commandline.rs index 7e03e55c6..2511b912a 100644 --- a/src/builtins/commandline.rs +++ b/src/builtins/commandline.rs @@ -148,7 +148,7 @@ fn strip_dollar_prefixes(insert_mode: AppendMode, prefix: &wstr, insert: &wstr) /// \param cursor_pos the position of the cursor in the command line #[allow(clippy::too_many_arguments)] fn write_part( - parser: &Parser, + parser: &mut Parser, range: Range, range_is_single_token: bool, cut_at_cursor: bool, @@ -179,7 +179,7 @@ fn write_part( token_text.to_owned(), &mut args, ExpandFlags::SKIP_CMDSUBST, - &OperationContext::foreground( + &mut OperationContext::foreground( parser, Box::new(no_cancel), COMMANDLINE_TOKENS_MAX_EXPANSION, @@ -242,7 +242,11 @@ fn write_part( } /// The commandline builtin. It is used for specifying a new value for the commandline. -pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +pub fn commandline( + parser: &mut Parser, + streams: &mut IoStreams, + args: &mut [&wstr], +) -> BuiltinResult { let rstate = commandline_get_state(true); let mut buffer_part = None; @@ -684,7 +688,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) } else if let Some(override_buffer) = &override_buffer { current_buffer = override_buffer; current_cursor_pos = current_buffer.len(); - } else if parser.libdata().transient_commandline.is_some() { + } else if parser.libdata().transient_commandline.borrow().is_some() { if cursor_mode && positional_args != 0 { err_str!("setting cursor while evaluating 'complete --arguments' is not yet supported") .cmd(cmd) @@ -695,12 +699,13 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) transient = parser .libdata() .transient_commandline + .borrow() .as_ref() .unwrap() .clone(); current_buffer = &transient; current_cursor_pos = transient.len(); - } else if parser.interactive_initialized.load() || is_interactive_session() { + } else if parser.interactive_initialized || is_interactive_session() { current_buffer = &rstate.text; current_cursor_pos = rstate.cursor_pos; } else { diff --git a/src/builtins/complete.rs b/src/builtins/complete.rs index 9b4f76507..32fcd1fd3 100644 --- a/src/builtins/complete.rs +++ b/src/builtins/complete.rs @@ -14,7 +14,7 @@ proc::is_interactive_session, reader::{commandline_get_state, completion_apply_to_command_line}, }; -use fish_common::{ScopeGuard, UnescapeFlags, UnescapeStringStyle, unescape_string}; +use fish_common::{UnescapeFlags, UnescapeStringStyle, unescape_string}; use fish_wcstringutil::string_suffixes_string; use fish_widestring::bytes2wcstring; @@ -222,7 +222,7 @@ fn builtin_complete_remove( fn builtin_complete_print( cmd: &wstr, streams: &mut IoStreams, - parser: &Parser, + parser: &mut Parser, color: ColorEnabled, ) { let repr = complete_print(cmd); @@ -230,7 +230,7 @@ fn builtin_complete_print( if color.enabled(streams) { streams.out.append(&bytes2wcstring(&highlight_and_colorize( &repr, - &parser.context(), + &mut parser.context(), ))); } else { streams.out.append(&repr); @@ -242,7 +242,7 @@ fn builtin_complete_print( /// The complete builtin. Used for specifying programmable tab-completions. Calls the functions in /// complete.rs for any heavy lifting. -pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { +pub fn complete(parser: &mut Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { localizable_consts! { OPTION_REQUIRES_NON_EMPTY_STRING "%s requires a non-empty string" @@ -486,7 +486,7 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> None => { // No argument given, try to use the current commandline. let commandline_state = commandline_get_state(true); - if !parser.interactive_initialized.load() && !is_interactive_session() { + if !parser.interactive_initialized && !is_interactive_session() { err_str!("Can not get commandline in non-interactive mode") .cmd(cmd) .finish(streams); @@ -501,13 +501,10 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> // Create a scoped transient command line, so that builtin_commandline will see our // argument, not the reader buffer. - let saved_transient = parser - .libdata_mut() + let _remove_transient = parser + .libdata() .transient_commandline - .replace(do_complete_param.clone()); - let _remove_transient = ScopeGuard::new((), |()| { - parser.libdata_mut().transient_commandline = saved_transient; - }); + .scoped_replace(Some(do_complete_param.clone())); // Prevent accidental recursion (see #6171). if !parser.libdata().builtin_complete_current_commandline { @@ -518,7 +515,7 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> let (mut comp, _needs_load) = crate::complete::complete( &do_complete_param, CompletionRequestOptions::normal(), - &parser.context(), + &mut parser.context(), ); // Apply the same sort and deduplication treatment as pager completions @@ -529,7 +526,7 @@ pub fn complete(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> let faux_cmdline = &do_complete_param[token.clone()]; let mut tmp_cursor = faux_cmdline.len(); let mut faux_cmdline_with_completion = completion_apply_to_command_line( - &OperationContext::background_interruptible(parser.vars()), + &mut OperationContext::background_interruptible(parser.vars()), &next.completion, next.flags, faux_cmdline, diff --git a/src/builtins/contains.rs b/src/builtins/contains.rs index 2469a0e33..bf072419a 100644 --- a/src/builtins/contains.rs +++ b/src/builtins/contains.rs @@ -52,7 +52,7 @@ fn parse_options( /// Implementation of the builtin contains command, used to check if a specified string is part of /// a list. -pub fn contains(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +pub fn contains(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { let cmd = args[0]; let (opts, optind) = parse_options(args, parser, streams)?; diff --git a/src/builtins/continue.rs b/src/builtins/continue.rs index d318fb1e3..b656b56f2 100644 --- a/src/builtins/continue.rs +++ b/src/builtins/continue.rs @@ -1,5 +1,9 @@ use super::prelude::*; -pub fn r#continue(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { +pub fn r#continue( + parser: &mut Parser, + streams: &mut IoStreams, + argv: &mut [&wstr], +) -> BuiltinResult { builtin_break_continue(parser, streams, argv) } diff --git a/src/builtins/count.rs b/src/builtins/count.rs index f604c294f..6c6880b37 100644 --- a/src/builtins/count.rs +++ b/src/builtins/count.rs @@ -5,7 +5,7 @@ const COUNT_CHUNK_SIZE: usize = 512 * 256; /// Implementation of the builtin count command, used to count the number of arguments sent to it. -pub fn count(_parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { +pub fn count(_parser: &mut Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { // Always add the size of argv (minus 0, which is "count"). // That means if you call `something | count a b c`, you'll get the count of something _plus 3_. let mut numargs = argv.len() - 1; diff --git a/src/builtins/disown.rs b/src/builtins/disown.rs index 32b21e907..1f5523f68 100644 --- a/src/builtins/disown.rs +++ b/src/builtins/disown.rs @@ -40,7 +40,7 @@ fn disown_job(cmd: &wstr, streams: &mut IoStreams, j: &Job) { } /// Builtin for removing jobs from the job list. -pub fn disown(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +pub fn disown(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { let opts = HelpOnlyCmdOpts::parse(args, parser, streams)?; let cmd = args[0]; diff --git a/src/builtins/echo.rs b/src/builtins/echo.rs index 108cbf7f8..2b620f98d 100644 --- a/src/builtins/echo.rs +++ b/src/builtins/echo.rs @@ -22,7 +22,7 @@ fn default() -> Self { fn parse_options( args: &mut [&wstr], - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, ) -> Result<(Options, usize), ErrorCode> { let Some(&cmd) = args.first() else { @@ -140,7 +140,7 @@ fn parse_numeric_sequence(chars: I) -> Option<(usize, u8)> /// /// Bash only respects `-n` if it's the first argument. We'll do the same. We also support a new, /// fish specific, option `-s` to mean "no spaces". -pub fn echo(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +pub fn echo(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { let (opts, optind) = parse_options(args, parser, streams)?; // The special character \c can be used to indicate no more output. diff --git a/src/builtins/emit.rs b/src/builtins/emit.rs index efc6e3f72..26922eeb4 100644 --- a/src/builtins/emit.rs +++ b/src/builtins/emit.rs @@ -1,7 +1,7 @@ use super::prelude::*; use crate::{err_str, event}; -pub fn emit(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { +pub fn emit(parser: &mut Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { let Some(&cmd) = argv.first() else { return Err(STATUS_INVALID_ARGS); }; diff --git a/src/builtins/eval.rs b/src/builtins/eval.rs index 42fddc170..991c59fd5 100644 --- a/src/builtins/eval.rs +++ b/src/builtins/eval.rs @@ -6,7 +6,7 @@ use fish_wcstringutil::join_strings; use libc::{STDERR_FILENO, STDOUT_FILENO}; -pub fn eval(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +pub fn eval(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { let argc = args.len(); if argc <= 1 { return Ok(SUCCESS); diff --git a/src/builtins/exit.rs b/src/builtins/exit.rs index 91406bf5a..900cac45a 100644 --- a/src/builtins/exit.rs +++ b/src/builtins/exit.rs @@ -4,7 +4,7 @@ use super::r#return::parse_return_value; /// Function for handling the exit builtin. -pub fn exit(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +pub fn exit(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { let retval = match parse_return_value(args, parser, streams) { ControlFlow::Continue(r) => r, ControlFlow::Break(result) => return result, diff --git a/src/builtins/false.rs b/src/builtins/false.rs index f6ed68014..ec5509ad1 100644 --- a/src/builtins/false.rs +++ b/src/builtins/false.rs @@ -1,5 +1,9 @@ use super::prelude::*; -pub fn r#false(_parser: &Parser, _streams: &mut IoStreams, _argv: &mut [&wstr]) -> BuiltinResult { +pub fn r#false( + _parser: &mut Parser, + _streams: &mut IoStreams, + _argv: &mut [&wstr], +) -> BuiltinResult { Err(STATUS_CMD_ERROR) } diff --git a/src/builtins/fg.rs b/src/builtins/fg.rs index 06eb4770c..0832a1e0b 100644 --- a/src/builtins/fg.rs +++ b/src/builtins/fg.rs @@ -15,7 +15,7 @@ use super::prelude::*; /// Builtin for putting a job in the foreground. -pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { +pub fn fg(parser: &mut Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { let opts = HelpOnlyCmdOpts::parse(argv, parser, streams)?; let Some(&cmd) = argv.first() else { diff --git a/src/builtins/fish_indent.rs b/src/builtins/fish_indent.rs index 773db45f2..90f05bdb7 100644 --- a/src/builtins/fish_indent.rs +++ b/src/builtins/fish_indent.rs @@ -950,13 +950,17 @@ fn throwing_main() -> i32 { do_indent(None, &mut streams, args).builtin_status_code() } -pub fn fish_indent(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +pub fn fish_indent( + parser: &mut Parser, + streams: &mut IoStreams, + args: &mut [&wstr], +) -> BuiltinResult { let args = args.iter_mut().map(|x| x.to_owned()).collect(); do_indent(Some(parser), streams, args) } fn do_indent( - parser: Option<&Parser>, + parser: Option<&mut Parser>, streams: &mut IoStreams, args: Vec, ) -> BuiltinResult { @@ -1139,7 +1143,7 @@ enum OutputType { highlight_shell( &output_wtext, &mut colors, - &OperationContext::globals(), + &mut OperationContext::globals(), false, None, ); @@ -1214,7 +1218,13 @@ fn read_file(mut f: impl Read) -> Result { // 3,7,command fn make_pygments_csv(src: &wstr) -> Vec { let mut colors = vec![]; - highlight_shell(src, &mut colors, &OperationContext::globals(), false, None); + highlight_shell( + src, + &mut colors, + &mut OperationContext::globals(), + false, + None, + ); assert_eq!( colors.len(), src.len(), diff --git a/src/builtins/fish_key_reader.rs b/src/builtins/fish_key_reader.rs index 4b8d00a79..89cde52e3 100644 --- a/src/builtins/fish_key_reader.rs +++ b/src/builtins/fish_key_reader.rs @@ -174,7 +174,7 @@ fn setup_and_process_keys( } fn parse_flags( - parser: Option<&Parser>, + parser: Option<&mut Parser>, streams: &mut IoStreams, args: Vec, continuous_mode: &mut bool, @@ -239,7 +239,7 @@ fn parse_flags( } pub fn fish_key_reader( - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr], ) -> BuiltinResult { diff --git a/src/builtins/function.rs b/src/builtins/function.rs index 62f27a4be..085b638e8 100644 --- a/src/builtins/function.rs +++ b/src/builtins/function.rs @@ -290,7 +290,7 @@ fn validate_function_name( /// function. Note this isn't strictly a "builtin": it is called directly from parse_execution. /// That is why its signature is different from the other builtins. pub fn function( - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, c_args: &mut [&wstr], func_node: NodeRef, diff --git a/src/builtins/functions.rs b/src/builtins/functions.rs index bb55b2a72..df1e7c31d 100644 --- a/src/builtins/functions.rs +++ b/src/builtins/functions.rs @@ -56,7 +56,7 @@ fn parse_cmd_opts<'args>( opts: &mut FunctionsCmdOpts<'args>, optind: &mut usize, argv: &mut [&'args wstr], - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, ) -> BuiltinResult { let cmd = L!("functions"); @@ -119,7 +119,11 @@ fn parse_cmd_opts<'args>( Ok(SUCCESS) } -pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +pub fn functions( + parser: &mut Parser, + streams: &mut IoStreams, + args: &mut [&wstr], +) -> BuiltinResult { let Some(&cmd) = args.first() else { return Err(STATUS_INVALID_ARGS); }; @@ -435,7 +439,7 @@ pub fn functions(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) - if opts.color.enabled(streams) { streams.out.append(&bytes2wcstring(&highlight_and_colorize( &def, - &parser.context(), + &mut parser.context(), ))); } else { streams.out.append(&def); diff --git a/src/builtins/gettext.rs b/src/builtins/gettext.rs index 71ab7dce2..936de270f 100644 --- a/src/builtins/gettext.rs +++ b/src/builtins/gettext.rs @@ -4,7 +4,7 @@ /// For scripts in `share/`, the corresponding strings are extracted from the scripts using /// `cargo xtask gettext update`. /// Strings not present in our repo would require a custom MO file for translation to be possible. -pub fn gettext(_parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { +pub fn gettext(_parser: &mut Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { for arg in &argv[1..] { streams.out.append( crate::localization::LocalizableString::from_external_source((*arg).to_owned()) diff --git a/src/builtins/history.rs b/src/builtins/history.rs index 7802c885a..6973b4d34 100644 --- a/src/builtins/history.rs +++ b/src/builtins/history.rs @@ -237,7 +237,7 @@ fn parse_cmd_opts( } /// Manipulate history of interactive commands executed by the user. -pub fn history(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +pub fn history(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { let mut opts = HistoryCmdOpts::default(); let mut optind = 0; diff --git a/src/builtins/jobs.rs b/src/builtins/jobs.rs index ffe346400..43d5a3f2d 100644 --- a/src/builtins/jobs.rs +++ b/src/builtins/jobs.rs @@ -139,7 +139,7 @@ fn builtin_jobs_print(j: &Job, mode: JobsPrintMode, header: bool, streams: &mut ]; /// The jobs builtin. Used for printing running jobs. Defined in builtin_jobs.c. -pub fn jobs(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { +pub fn jobs(parser: &mut Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { let cmd = match argv.first() { Some(cmd) => *cmd, None => return Err(STATUS_INVALID_ARGS), diff --git a/src/builtins/math.rs b/src/builtins/math.rs index 83f86df45..2f08a9852 100644 --- a/src/builtins/math.rs +++ b/src/builtins/math.rs @@ -30,7 +30,7 @@ struct Options { fn parse_cmd_opts( args: &mut [&wstr], - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, ) -> Result<(Options, usize), ErrorCode> { let cmd = L!("math"); @@ -275,7 +275,7 @@ fn evaluate_expression( const MATH_CHUNK_SIZE: usize = 1024; /// The math builtin evaluates math expressions. -pub fn math(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { +pub fn math(parser: &mut Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { let cmd = argv[0]; let (opts, mut optind) = parse_cmd_opts(argv, parser, streams)?; diff --git a/src/builtins/path.rs b/src/builtins/path.rs index d194fcdde..93a9e34c3 100644 --- a/src/builtins/path.rs +++ b/src/builtins/path.rs @@ -215,7 +215,7 @@ fn parse_opts<'args>( optind: &mut usize, n_req_args: usize, args: &mut [&'args wstr], - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, ) -> BuiltinResult { let cmd = L!("path"); @@ -391,7 +391,7 @@ fn parse_opts<'args>( } fn path_transform( - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr], func: impl Fn(&wstr) -> WString, @@ -437,7 +437,11 @@ fn path_transform( } } -fn path_basename(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +fn path_basename( + parser: &mut Parser, + streams: &mut IoStreams, + args: &mut [&wstr], +) -> BuiltinResult { path_transform( parser, streams, @@ -449,7 +453,7 @@ fn path_basename(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) - ) } -fn path_dirname(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +fn path_dirname(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { path_transform(parser, streams, args, |s| wdirname(s).to_owned(), |_| {}) } @@ -461,11 +465,15 @@ fn normalize_help(path: &wstr) -> WString { np } -fn path_normalize(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +fn path_normalize( + parser: &mut Parser, + streams: &mut IoStreams, + args: &mut [&wstr], +) -> BuiltinResult { path_transform(parser, streams, args, normalize_help, |_| {}) } -fn path_mtime(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +fn path_mtime(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { let mut opts = Options { relative_valid: true, ..Default::default() @@ -534,7 +542,11 @@ fn find_extension(path: &wstr) -> Option { } } -fn path_extension(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +fn path_extension( + parser: &mut Parser, + streams: &mut IoStreams, + args: &mut [&wstr], +) -> BuiltinResult { let mut opts = Options::default(); let mut optind = 0; @@ -570,7 +582,7 @@ fn path_extension(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) } fn path_change_extension( - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr], ) -> BuiltinResult { @@ -616,7 +628,7 @@ fn path_change_extension( } } -fn path_resolve(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +fn path_resolve(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { let mut opts = Options::default(); let mut optind = 0; @@ -679,7 +691,7 @@ fn path_resolve(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> } } -fn path_sort(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +fn path_sort(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { let mut opts = Options { reverse_valid: true, unique_valid: true, @@ -846,7 +858,7 @@ fn filter_path(opts: &Options, path: &wstr, uid: Option, gid: Option) } fn path_filter_maybe_is( - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr], is_is: bool, @@ -937,16 +949,16 @@ fn path_filter_maybe_is( } } -fn path_filter(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +fn path_filter(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { path_filter_maybe_is(parser, streams, args, false) } -fn path_is(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +fn path_is(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { path_filter_maybe_is(parser, streams, args, true) } /// The path builtin, for handling paths. -pub fn path(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +pub fn path(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { let Some(&cmd) = args.first() else { return Err(STATUS_INVALID_ARGS); }; diff --git a/src/builtins/printf.rs b/src/builtins/printf.rs index 4ae754711..247522ef4 100644 --- a/src/builtins/printf.rs +++ b/src/builtins/printf.rs @@ -746,7 +746,7 @@ fn append_output(&mut self, c: char) { } /// The printf builtin. -pub fn printf(_parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { +pub fn printf(_parser: &mut Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { let mut argc = argv.len(); // Rebind argv as immutable slice (can't rearrange its elements), skipping the command name. diff --git a/src/builtins/pwd.rs b/src/builtins/pwd.rs index 83b2c07fb..62d370b15 100644 --- a/src/builtins/pwd.rs +++ b/src/builtins/pwd.rs @@ -12,7 +12,7 @@ wopt(L!("physical"), NoArgument, 'P'), ]; -pub fn pwd(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { +pub fn pwd(parser: &mut Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { let cmd = argv[0]; let argc = argv.len(); let mut resolve_symlinks = false; diff --git a/src/builtins/random.rs b/src/builtins/random.rs index ec4db2d01..37fd503f8 100644 --- a/src/builtins/random.rs +++ b/src/builtins/random.rs @@ -10,7 +10,7 @@ static RNG: LazyLock> = LazyLock::new(|| Mutex::new(get_seeded_rng(rand::rng().next_u64()))); -pub fn random(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { +pub fn random(parser: &mut Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { let cmd = argv[0]; let argc = argv.len(); let print_hints = false; diff --git a/src/builtins/read.rs b/src/builtins/read.rs index 53af17ba6..d1fdba181 100644 --- a/src/builtins/read.rs +++ b/src/builtins/read.rs @@ -223,7 +223,7 @@ fn parse_cmd_opts( /// we weren't asked to split on null characters. #[allow(clippy::too_many_arguments)] fn read_interactive( - parser: &Parser, + parser: &mut Parser, buff: &mut WString, nchars: Option, shell: bool, @@ -543,7 +543,7 @@ fn tokenize_flag(token_mode: TokenOutputMode) -> &'static wstr { } /// The read builtin. Reads from stdin and stores the values in environment variables. -pub fn read(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { +pub fn read(parser: &mut Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { let mut buff = WString::new(); let mut exit_res: BuiltinResult; @@ -575,7 +575,7 @@ pub fn read(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui let mut var_ptr = 0; let vars_left = |var_ptr: usize| argc - var_ptr; - let clear_remaining_vars = |var_ptr: &mut usize| { + let clear_remaining_vars = |parser: &mut Parser, var_ptr: &mut usize| { while vars_left(*var_ptr) != 0 { parser.set_empty(argv[*var_ptr], opts.place); *var_ptr += 1; @@ -635,7 +635,7 @@ pub fn read(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui } if exit_res.is_err() { - clear_remaining_vars(&mut var_ptr); + clear_remaining_vars(parser, &mut var_ptr); return exit_res; } @@ -800,7 +800,7 @@ pub fn read(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Bui if !opts.array { // In case there were more args than splits - clear_remaining_vars(&mut var_ptr); + clear_remaining_vars(parser, &mut var_ptr); } exit_res diff --git a/src/builtins/realpath.rs b/src/builtins/realpath.rs index a7f3f9fbf..2753685ec 100644 --- a/src/builtins/realpath.rs +++ b/src/builtins/realpath.rs @@ -59,7 +59,7 @@ fn parse_options( /// An implementation of the external realpath command. Doesn't support any options. /// In general scripts shouldn't invoke this directly. They should just use `realpath` which /// will fallback to this builtin if an external command cannot be found. -pub fn realpath(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +pub fn realpath(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { let cmd = args[0]; let (opts, optind) = parse_options(args, parser, streams)?; diff --git a/src/builtins/return.rs b/src/builtins/return.rs index 6e127af51..bbf1ed374 100644 --- a/src/builtins/return.rs +++ b/src/builtins/return.rs @@ -52,7 +52,7 @@ fn parse_options( } /// Function for handling the return builtin. -pub fn r#return(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +pub fn r#return(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { let mut retval = match parse_return_value(args, parser, streams) { ControlFlow::Continue(r) => r, ControlFlow::Break(result) => return result, @@ -88,7 +88,7 @@ pub fn r#return(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> pub fn parse_return_value( args: &mut [&wstr], - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, ) -> ControlFlow { let cmd = args[0]; diff --git a/src/builtins/set.rs b/src/builtins/set.rs index 59ee37312..782ecd0a6 100644 --- a/src/builtins/set.rs +++ b/src/builtins/set.rs @@ -84,7 +84,7 @@ fn env_mode(&self) -> EnvMode { fn parse( cmd: &wstr, args: &mut [&wstr], - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, ) -> Result, ErrorCode> { /// Values used for long-only options. @@ -376,7 +376,7 @@ fn env_set_reporting_errors( mode: EnvMode, list: Vec, streams: &mut IoStreams, - parser: &Parser, + parser: &mut Parser, ) -> EnvStackSetResult { let mode = ParserEnvSetMode::user(mode); let retval = if opts.no_event { @@ -779,7 +779,7 @@ fn show(cmd: &wstr, parser: &Parser, streams: &mut IoStreams, args: &[&wstr]) -> fn erase( cmd: &wstr, opts: &Options, - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, args: &[&wstr], ) -> BuiltinResult { @@ -949,7 +949,7 @@ fn new_var_values_by_index(split: &SplitVar, argv: &[&wstr]) -> Vec { fn set_internal( cmd: &wstr, opts: &Options, - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, argv: &[&wstr], ) -> BuiltinResult { @@ -1042,7 +1042,7 @@ fn set_internal( } /// The set builtin creates, updates, and erases (removes, deletes) variables. -pub fn set(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +pub fn set(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { let cmd = args[0]; let (opts, optind) = match Options::parse(cmd, args, parser, streams)? { Some((opts, optind)) => (opts, optind), diff --git a/src/builtins/set_color.rs b/src/builtins/set_color.rs index 3d9086ab8..e679d8fb7 100644 --- a/src/builtins/set_color.rs +++ b/src/builtins/set_color.rs @@ -53,7 +53,11 @@ fn print_colors( } /// set_color builtin. -pub fn set_color(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { +pub fn set_color( + parser: &mut Parser, + streams: &mut IoStreams, + argv: &mut [&wstr], +) -> BuiltinResult { // Variables used for parsing the argument list. let argc = argv.len(); diff --git a/src/builtins/shared/misc.rs b/src/builtins/shared/misc.rs index 6ba0a92c2..329ea007f 100644 --- a/src/builtins/shared/misc.rs +++ b/src/builtins/shared/misc.rs @@ -14,7 +14,7 @@ use fish_widestring::{L, bytes2wcstring, str2wcstring}; use std::io::{BufRead as _, BufReader, Read as _}; -pub type BuiltinCmd = fn(&Parser, &mut IoStreams, &mut [&wstr]) -> BuiltinResult; +pub type BuiltinCmd = fn(&mut Parser, &mut IoStreams, &mut [&wstr]) -> BuiltinResult; /// The default prompt for the read command. pub const DEFAULT_READ_PROMPT: &wstr = @@ -393,7 +393,7 @@ fn cmd_needs_help(cmd: &wstr) -> bool { } /// Execute a builtin command -pub fn builtin_run(parser: &Parser, argv: &mut [&wstr], streams: &mut IoStreams) -> ProcStatus { +pub fn builtin_run(parser: &mut Parser, argv: &mut [&wstr], streams: &mut IoStreams) -> ProcStatus { if argv.is_empty() { return ProcStatus::from_exit_code(STATUS_INVALID_ARGS); } @@ -546,7 +546,7 @@ pub fn builtin_get_desc(name: &wstr) -> Option<&'static wstr> { /// builtin or function name to get up help for /// /// Process and print help for the specified builtin or function. -pub fn builtin_print_help(parser: &Parser, streams: &mut IoStreams, cmd: &wstr) { +pub fn builtin_print_help(parser: &mut Parser, streams: &mut IoStreams, cmd: &wstr) { // This won't ever work if no_exec is set. if no_exec() { return; @@ -650,7 +650,7 @@ pub struct HelpOnlyCmdOpts { impl HelpOnlyCmdOpts { pub fn parse( args: &mut [&wstr], - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, ) -> Result { let cmd = args[0]; @@ -903,7 +903,11 @@ fn parsed_pid( /// A generic builtin that only supports showing a help message. This is only a placeholder that /// prints the help message. Useful for commands that live in the parser. -fn builtin_generic(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { +fn builtin_generic( + parser: &mut Parser, + streams: &mut IoStreams, + argv: &mut [&wstr], +) -> BuiltinResult { let argc = argv.len(); let opts = HelpOnlyCmdOpts::parse(argv, parser, streams)?; @@ -925,7 +929,7 @@ fn builtin_generic(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) /// This function handles both the 'continue' and the 'break' builtins that are used for loop /// control. pub fn builtin_break_continue( - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, argv: &mut [&wstr], ) -> BuiltinResult { diff --git a/src/builtins/source.rs b/src/builtins/source.rs index a887dd64f..a3dde5f33 100644 --- a/src/builtins/source.rs +++ b/src/builtins/source.rs @@ -9,7 +9,7 @@ /// The source builtin, sometimes called `.`. Evaluates the contents of a file in the current /// context. -pub fn source(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +pub fn source(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { let argc = args.len(); let opts = HelpOnlyCmdOpts::parse(args, parser, streams)?; diff --git a/src/builtins/status.rs b/src/builtins/status.rs index b626dd6e1..acab63376 100644 --- a/src/builtins/status.rs +++ b/src/builtins/status.rs @@ -340,7 +340,7 @@ fn iter() -> impl Iterator> { } ); -pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +pub fn status(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { let cmd = args[0]; let argc = args.len(); diff --git a/src/builtins/string.rs b/src/builtins/string.rs index d804b0371..9dc64636a 100644 --- a/src/builtins/string.rs +++ b/src/builtins/string.rs @@ -31,7 +31,7 @@ trait StringSubCommand<'args> { fn parse_opts( &mut self, args: &mut [&'args wstr], - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, ) -> Result { let cmd = L!("string"); @@ -98,7 +98,7 @@ fn take_args( /// Perform the business logic of the command. fn handle( &mut self, - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, optind: &mut usize, args: &[&'args wstr], @@ -106,7 +106,7 @@ fn handle( fn run( &mut self, - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, args: &mut [&'args wstr], ) -> BuiltinResult { @@ -118,7 +118,7 @@ fn run( fn run_impl( &mut self, - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, args: &mut [&'args wstr], ) -> Result<(), ErrorCode> { @@ -280,7 +280,7 @@ fn arguments<'iter, 'args>( } /// The string builtin, for manipulating strings. -pub fn string(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +pub fn string(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { let cmd = args[0]; let argc = args.len(); diff --git a/src/builtins/string/collect.rs b/src/builtins/string/collect.rs index 5617ed365..e1930d338 100644 --- a/src/builtins/string/collect.rs +++ b/src/builtins/string/collect.rs @@ -24,7 +24,7 @@ fn parse_opt(&mut self, c: char, _arg: Option<&wstr>) -> Result<(), StringError< fn handle( &mut self, - _parser: &Parser, + _parser: &mut Parser, streams: &mut IoStreams, optind: &mut usize, args: &[&wstr], diff --git a/src/builtins/string/escape.rs b/src/builtins/string/escape.rs index e13811547..d4c730b89 100644 --- a/src/builtins/string/escape.rs +++ b/src/builtins/string/escape.rs @@ -30,7 +30,7 @@ fn parse_opt(&mut self, c: char, arg: Option<&wstr>) -> Result<(), StringError<' fn handle( &mut self, - _parser: &Parser, + _parser: &mut Parser, streams: &mut IoStreams, optind: &mut usize, args: &[&wstr], diff --git a/src/builtins/string/join.rs b/src/builtins/string/join.rs index 320c0bc07..1243da2cc 100644 --- a/src/builtins/string/join.rs +++ b/src/builtins/string/join.rs @@ -61,7 +61,7 @@ fn take_args( fn handle( &mut self, - _parser: &Parser, + _parser: &mut Parser, streams: &mut IoStreams, optind: &mut usize, args: &[&wstr], diff --git a/src/builtins/string/length.rs b/src/builtins/string/length.rs index 6a7f661ce..1607fab36 100644 --- a/src/builtins/string/length.rs +++ b/src/builtins/string/length.rs @@ -24,7 +24,7 @@ fn parse_opt(&mut self, c: char, _arg: Option<&wstr>) -> Result<(), StringError< fn handle( &mut self, - _parser: &Parser, + _parser: &mut Parser, streams: &mut IoStreams, optind: &mut usize, args: &[&wstr], diff --git a/src/builtins/string/match.rs b/src/builtins/string/match.rs index e04826ed8..9eab3aaef 100644 --- a/src/builtins/string/match.rs +++ b/src/builtins/string/match.rs @@ -88,7 +88,7 @@ fn take_args( fn handle( &mut self, - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, optind: &mut usize, args: &[&wstr], diff --git a/src/builtins/string/pad.rs b/src/builtins/string/pad.rs index bbcf069cb..b43128690 100644 --- a/src/builtins/string/pad.rs +++ b/src/builtins/string/pad.rs @@ -64,7 +64,7 @@ fn parse_opt(&mut self, c: char, arg: Option<&wstr>) -> Result<(), StringError<' fn handle<'args>( &mut self, - _parser: &Parser, + _parser: &mut Parser, streams: &mut IoStreams, optind: &mut usize, args: &[&'args wstr], diff --git a/src/builtins/string/repeat.rs b/src/builtins/string/repeat.rs index f57b5e9b6..00da3346e 100644 --- a/src/builtins/string/repeat.rs +++ b/src/builtins/string/repeat.rs @@ -76,7 +76,7 @@ fn take_args( fn handle( &mut self, - _parser: &Parser, + _parser: &mut Parser, streams: &mut IoStreams, optind: &mut usize, args: &[&wstr], diff --git a/src/builtins/string/replace.rs b/src/builtins/string/replace.rs index 2a79d2774..603108afa 100644 --- a/src/builtins/string/replace.rs +++ b/src/builtins/string/replace.rs @@ -82,7 +82,7 @@ fn take_args( fn handle( &mut self, - _parser: &Parser, + _parser: &mut Parser, streams: &mut IoStreams, optind: &mut usize, args: &[&wstr], diff --git a/src/builtins/string/shorten.rs b/src/builtins/string/shorten.rs index 1634a97ce..b94b81e51 100644 --- a/src/builtins/string/shorten.rs +++ b/src/builtins/string/shorten.rs @@ -61,7 +61,7 @@ fn parse_opt(&mut self, c: char, arg: Option<&'args wstr>) -> Result<(), StringE fn handle( &mut self, - _parser: &Parser, + _parser: &mut Parser, streams: &mut IoStreams, optind: &mut usize, args: &[&wstr], diff --git a/src/builtins/string/split.rs b/src/builtins/string/split.rs index bfab84c10..f0516800b 100644 --- a/src/builtins/string/split.rs +++ b/src/builtins/string/split.rs @@ -166,7 +166,7 @@ fn take_args( fn handle( &mut self, - _parser: &Parser, + _parser: &mut Parser, streams: &mut IoStreams, optind: &mut usize, args: &[&'args wstr], diff --git a/src/builtins/string/sub.rs b/src/builtins/string/sub.rs index 2926dbb36..3461db349 100644 --- a/src/builtins/string/sub.rs +++ b/src/builtins/string/sub.rs @@ -53,7 +53,7 @@ fn parse_opt(&mut self, c: char, arg: Option<&wstr>) -> Result<(), StringError<' fn handle( &mut self, - _parser: &Parser, + _parser: &mut Parser, streams: &mut IoStreams, optind: &mut usize, args: &[&wstr], diff --git a/src/builtins/string/test_helpers.rs b/src/builtins/string/test_helpers.rs index 149b09377..2815a6782 100644 --- a/src/builtins/string/test_helpers.rs +++ b/src/builtins/string/test_helpers.rs @@ -21,14 +21,14 @@ macro_rules! validate { } pub fn string_test(mut args: Vec<&wstr>) -> (WString, libc::c_int) { - let parser = TestParser::new(); + let parser = &mut TestParser::new(); let mut outs = OutputStream::String(StringOutputStream::new()); let mut errs = OutputStream::Null; let io_chain = IoChain::new(); let mut streams = IoStreams::new(&mut outs, &mut errs, &io_chain); streams.stdin_is_directly_redirected = false; // read from argv instead of stdin - let rc = string(&parser, &mut streams, args.as_mut_slice()); + let rc = string(parser, &mut streams, args.as_mut_slice()); (outs.contents().to_owned(), rc.builtin_status_code()) } diff --git a/src/builtins/string/transform.rs b/src/builtins/string/transform.rs index c92bb799a..b3a507691 100644 --- a/src/builtins/string/transform.rs +++ b/src/builtins/string/transform.rs @@ -18,7 +18,7 @@ fn parse_opt(&mut self, c: char, _arg: Option<&wstr>) -> Result<(), StringError< fn handle( &mut self, - _parser: &Parser, + _parser: &mut Parser, streams: &mut IoStreams, optind: &mut usize, args: &[&wstr], diff --git a/src/builtins/string/trim.rs b/src/builtins/string/trim.rs index 1ff9875ba..181b74a16 100644 --- a/src/builtins/string/trim.rs +++ b/src/builtins/string/trim.rs @@ -41,7 +41,7 @@ fn parse_opt(&mut self, c: char, arg: Option<&'args wstr>) -> Result<(), StringE fn handle( &mut self, - _parser: &Parser, + _parser: &mut Parser, streams: &mut IoStreams, optind: &mut usize, args: &[&wstr], diff --git a/src/builtins/string/unescape.rs b/src/builtins/string/unescape.rs index 6093476bd..0dcaded50 100644 --- a/src/builtins/string/unescape.rs +++ b/src/builtins/string/unescape.rs @@ -32,7 +32,7 @@ fn parse_opt(&mut self, c: char, arg: Option<&wstr>) -> Result<(), StringError<' fn handle( &mut self, - _parser: &Parser, + _parser: &mut Parser, streams: &mut IoStreams, optind: &mut usize, args: &[&wstr], diff --git a/src/builtins/test.rs b/src/builtins/test.rs index 276082bb9..86dd6226e 100644 --- a/src/builtins/test.rs +++ b/src/builtins/test.rs @@ -984,7 +984,7 @@ fn unary_primary_evaluate( /// Evaluate a conditional expression given the arguments. For POSIX conformance this /// supports a more limited range of functionality. /// Return status is the final shell status, i.e. 0 for true, 1 for false and 2 for error. -pub fn test(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { +pub fn test(parser: &mut Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { // The first argument should be the name of the command ('test'). if argv.is_empty() { return Err(STATUS_INVALID_ARGS); @@ -1092,7 +1092,7 @@ mod tests { use fish_widestring::str2wcstring; fn run_one_test_test_mbracket(expected: i32, lst: &[&str], bracket: bool) -> bool { - let parser = TestParser::new(); + let parser = &mut TestParser::new(); let mut argv = Vec::new(); if bracket { argv.push(L!("[").to_owned()); @@ -1113,7 +1113,7 @@ fn run_one_test_test_mbracket(expected: i32, lst: &[&str], bracket: bool) -> boo let io_chain = IoChain::new(); let mut streams = IoStreams::new(&mut out, &mut err, &io_chain); - let result = builtin_test(&parser, &mut streams, &mut argv).builtin_status_code(); + let result = builtin_test(parser, &mut streams, &mut argv).builtin_status_code(); if result != expected { eprintf!( @@ -1134,7 +1134,7 @@ fn run_test_test(expected: i32, lst: &[&str]) -> bool { fn test_test_brackets() { // Ensure [ knows it needs a ]. - let parser = TestParser::new(); + let parser = &mut TestParser::new(); let mut out = OutputStream::Null; let mut err = OutputStream::Null; @@ -1143,16 +1143,16 @@ fn test_test_brackets() { let args1 = &mut [L!("["), L!("foo")]; assert_eq!( - builtin_test(&parser, &mut streams, args1), + builtin_test(parser, &mut streams, args1), Err(STATUS_INVALID_ARGS) ); let args2 = &mut [L!("["), L!("foo"), L!("]")]; - assert_eq!(builtin_test(&parser, &mut streams, args2), Ok(SUCCESS)); + assert_eq!(builtin_test(parser, &mut streams, args2), Ok(SUCCESS)); let args3 = &mut [L!("["), L!("foo"), L!("]"), L!("bar")]; assert_eq!( - builtin_test(&parser, &mut streams, args3), + builtin_test(parser, &mut streams, args3), Err(STATUS_INVALID_ARGS) ); } diff --git a/src/builtins/true.rs b/src/builtins/true.rs index fbaa6dfd0..7805d8981 100644 --- a/src/builtins/true.rs +++ b/src/builtins/true.rs @@ -1,5 +1,9 @@ use super::prelude::*; -pub fn r#true(_parser: &Parser, _streams: &mut IoStreams, _argv: &mut [&wstr]) -> BuiltinResult { +pub fn r#true( + _parser: &mut Parser, + _streams: &mut IoStreams, + _argv: &mut [&wstr], +) -> BuiltinResult { Ok(SUCCESS) } diff --git a/src/builtins/type.rs b/src/builtins/type.rs index 1bf09bd4e..ab6a32e5e 100644 --- a/src/builtins/type.rs +++ b/src/builtins/type.rs @@ -20,7 +20,7 @@ struct type_cmd_opts_t { color: ColorEnabled, } -pub fn r#type(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { +pub fn r#type(parser: &mut Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { let cmd = argv[0]; let argc = argv.len(); let print_hints = false; @@ -168,7 +168,7 @@ pub fn r#type(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> B if opts.color.enabled(streams) { streams.out.append(&bytes2wcstring(&highlight_and_colorize( &def, - &parser.context(), + &mut parser.context(), ))); } else { streams.out.append(&def); diff --git a/src/builtins/ulimit.rs b/src/builtins/ulimit.rs index 068f40dce..83fa463a5 100644 --- a/src/builtins/ulimit.rs +++ b/src/builtins/ulimit.rs @@ -250,7 +250,7 @@ fn default() -> Self { } } -pub fn ulimit(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { +pub fn ulimit(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult { let cmd = args[0]; const SHORT_OPTS: &wstr = L!("HSabcdefilmnqrstuvwyKPTh"); diff --git a/src/builtins/wait.rs b/src/builtins/wait.rs index ee763de6b..55ee86196 100644 --- a/src/builtins/wait.rs +++ b/src/builtins/wait.rs @@ -33,12 +33,12 @@ enum WaitHandleQuery<'a> { /// Return true if we found a matching job (even if not waitable), false if not. fn find_wait_handles( query: WaitHandleQuery<'_>, - parser: &Parser, + parser: &mut Parser, handles: &mut Vec, ) -> bool { // Has a job already completed? let mut matched = false; - let wait_handles: &mut WaitHandleStore = &mut parser.mut_wait_handles(); + let wait_handles: &mut WaitHandleStore = parser.mut_wait_handles(); match query { WaitHandleQuery::Pid(pid) => { if let Some(wh) = wait_handles.get_by_pid(pid) { @@ -57,7 +57,7 @@ fn find_wait_handles( } // Is there a running job match? - for j in &*parser.jobs() { + for j in parser.jobs() { // We want to set 'matched' to true if we could have matched, even if the job was stopped. let provide_handle = can_wait_on_job(j); let internal_job_id = j.internal_job_id; @@ -81,7 +81,7 @@ fn get_all_wait_handles(parser: &Parser) -> Vec { let mut result = parser.wait_handles().get_list(); // Get wait handles for running jobs. - for j in &*parser.jobs() { + for j in parser.jobs() { if !can_wait_on_job(j) { continue; } @@ -102,7 +102,11 @@ fn is_completed(wh: &WaitHandleRef) -> bool { /// Wait for the given wait handles to be marked as completed. /// If `any_flag` is set, wait for the first one; otherwise wait for all. /// Return a status code. -fn wait_for_completion(parser: &Parser, whs: &[WaitHandleRef], any_flag: bool) -> BuiltinResult { +fn wait_for_completion( + parser: &mut Parser, + whs: &[WaitHandleRef], + any_flag: bool, +) -> BuiltinResult { if whs.is_empty() { return Ok(SUCCESS); } @@ -134,7 +138,7 @@ fn wait_for_completion(parser: &Parser, whs: &[WaitHandleRef], any_flag: bool) - } } -pub fn wait(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { +pub fn wait(parser: &mut Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> BuiltinResult { let cmd = argv[0]; let argc = argv.len(); let mut any_flag = false; // flag for -n option diff --git a/src/complete.rs b/src/complete.rs index d8c26e322..47110e039 100644 --- a/src/complete.rs +++ b/src/complete.rs @@ -17,7 +17,7 @@ operation_context::OperationContext, parse_constants::SourceRange, parse_util::{get_cmdsubst_extent, get_process_extent, unescape_wildcards}, - parser::{Block, Parser, ParserEnvSetMode}, + parser::{Block, BlockId, Parser, ParserEnvSetMode}, parser_keywords::parser_keywords_is_subcommand, path::{path_get_path, path_try_get_path}, prelude::*, @@ -585,9 +585,9 @@ pub fn new(var_assignments: &'a mut Vec) -> Self { } /// Class representing an attempt to compute completions. -struct Completer<'ctx> { +struct Completer<'ctx, 'parser> { /// The operation context for this completion. - ctx: &'ctx OperationContext<'ctx>, + ctx: &'ctx mut OperationContext<'parser>, /// Flags associated with the completion request. flags: CompletionRequestOptions, /// The output completions. @@ -602,12 +602,13 @@ struct Completer<'ctx> { static COMPLETION_AUTOLOADER: LazyLock> = LazyLock::new(|| Mutex::new(Autoload::new(L!("fish_complete_path")))); -impl<'ctx> Completer<'ctx> { - pub fn new(ctx: &'ctx OperationContext<'ctx>, flags: CompletionRequestOptions) -> Self { +impl<'ctx, 'parser> Completer<'ctx, 'parser> { + pub fn new(ctx: &'ctx mut OperationContext<'parser>, flags: CompletionRequestOptions) -> Self { + let expansion_limit = ctx.expansion_limit; Self { ctx, flags, - completions: CompletionReceiver::new(ctx.expansion_limit), + completions: CompletionReceiver::new(expansion_limit), needs_load: vec![], condition_cache: HashMap::new(), } @@ -831,21 +832,22 @@ fn perform_for_commandline_impl(&mut self, cmdline: WString) { } // Maybe apply variable assignments. - let _restore_vars = self.apply_var_assignments(&var_assignments); - if self.ctx.check_cancel() { - return; + let block = self.apply_var_assignments(&var_assignments); + if !self.ctx.check_cancel() { + // This function wants the unescaped string. + self.complete_param_expand( + current_argument, + do_file, + handle_as_special_cd, + cur_tok.is_unterminated_brace, + ); + + // Lastly mark any completions that appear to already be present in arguments. + self.mark_completions_duplicating_arguments(&cmdline, current_token, tokens); + } + if let Some(block) = block { + self.ctx.parser().pop_block(block); } - - // This function wants the unescaped string. - self.complete_param_expand( - current_argument, - do_file, - handle_as_special_cd, - cur_tok.is_unterminated_brace, - ); - - // Lastly mark any completions that appear to already be present in arguments. - self.mark_completions_duplicating_arguments(&cmdline, current_token, tokens); } pub fn acquire_completions(&mut self) -> Vec { @@ -1853,21 +1855,10 @@ fn getpwent_name() -> Option { /// If we have variable assignments, attempt to apply them in our parser. As soon as the return /// value goes out of scope, the variables will be removed from the parser. - fn apply_var_assignments>( - &mut self, - var_assignments: &[T], - ) -> Option>> { + fn apply_var_assignments>(&mut self, var_assignments: &[T]) -> Option { if !self.ctx.has_parser() || var_assignments.is_empty() { return None; } - let parser = self.ctx.parser(); - - let vars = parser.vars(); - assert_eq!( - std::ptr::from_ref(self.ctx.vars()).cast::<()>(), - std::ptr::from_ref(vars).cast::<()>(), - "Don't know how to tab complete with a parser but a different variable set" - ); // clone of parse_execution_context_t::apply_variable_assignments. // Crucially do NOT expand subcommands: @@ -1875,7 +1866,10 @@ fn apply_var_assignments>( // should not launch missiles. // Note we also do NOT send --on-variable events. let expand_flags = ExpandFlags::FAIL_ON_CMDSUBST; - let block = parser.push_block(Block::variable_assignment_block()); + let block = self + .ctx + .parser() + .push_block(Block::variable_assignment_block()); for var_assign in var_assignments { let var_assign: &wstr = var_assign.as_ref(); let equals_pos = variable_assignment_equals_pos(var_assign) @@ -1901,7 +1895,7 @@ fn apply_var_assignments>( } else { Vec::new() }; - parser.set_var( + self.ctx.parser().set_var( variable_name, ParserEnvSetMode::new(EnvMode::LOCAL | EnvMode::EXPORT), vals, @@ -1911,8 +1905,7 @@ fn apply_var_assignments>( } } - let parser = self.ctx.parser(); - Some(ScopeGuard::new((), move |_| parser.pop_block(block))) + Some(block) } /// Complete a command by invoking user-specified completions. @@ -1926,30 +1919,28 @@ fn complete_custom(&mut self, cmd: &wstr, cmdline: &wstr, ad: &mut CustomArgData // builtin_commandline will refer to the wrapped command. But not if // we're doing autosuggestions. let _remove_transient = (!is_autosuggest).then(|| { - let parser = self.ctx.parser(); - let saved_transient = parser - .libdata_mut() + self.ctx + .parser() + .libdata() .transient_commandline - .replace(cmdline.to_owned()); - ScopeGuard::new((), move |_| { - parser.libdata_mut().transient_commandline = saved_transient; - }) + .scoped_replace(Some(cmdline.to_owned())) }); // Maybe apply variable assignments. - let _restore_vars = self.apply_var_assignments(ad.var_assignments); - if self.ctx.check_cancel() { - return; + let block = self.apply_var_assignments(ad.var_assignments); + if !self.ctx.check_cancel() { + // Invoke any custom completions for this command. + self.complete_param_for_command( + cmd, + &ad.previous_argument, + &ad.current_argument, + !ad.had_ddash, + &mut ad.do_file, + ); + } + if let Some(block) = block { + self.ctx.parser().pop_block(block); } - - // Invoke any custom completions for this command. - self.complete_param_for_command( - cmd, - &ad.previous_argument, - &ad.current_argument, - !ad.had_ddash, - &mut ad.do_file, - ); } // Invoke command-specific completions given by `arg_data`. @@ -2235,7 +2226,7 @@ fn short_option_pos(arg: &wstr, options: &[CompleteEntryOpt]) -> Option { Some(arg.len() - 1) } -fn expand_command_token(ctx: &OperationContext<'_>, cmd_tok: &mut WString) -> bool { +fn expand_command_token(ctx: &mut OperationContext<'_>, cmd_tok: &mut WString) -> bool { // TODO: we give up if the first token expands to more than one argument. We could handle // that case by propagating arguments. // Also we could expand wildcards. @@ -2353,7 +2344,7 @@ pub fn complete_remove_all(cmd: WString, cmd_is_path: bool, explicit: bool) { pub fn complete( cmd_with_subcmds: &wstr, flags: CompletionRequestOptions, - ctx: &OperationContext, + ctx: &mut OperationContext<'_>, ) -> (Vec, Vec) { // Determine the innermost subcommand. let cmdsubst = get_cmdsubst_extent(cmd_with_subcmds, cmd_with_subcmds.len()); @@ -2453,7 +2444,7 @@ fn strip_partial_executable_suffix(cmd: &wstr) -> Option<(&wstr, &wstr)> { /// Load command-specific completions for the specified command. /// Returns `true` if something new was loaded, `false` if not. -pub fn complete_load(cmd: &wstr, parser: &Parser) -> bool { +pub fn complete_load(cmd: &wstr, parser: &mut Parser) -> bool { if COMPLETION_TOMBSTONES.lock().unwrap().contains(cmd) { return false; } @@ -2623,6 +2614,7 @@ mod tests { }; use crate::{ abbrs::{self, Abbreviation, with_abbrs_mut}, + complete::Completion, env::{EnvMode, EnvSetMode, Environment as _}, io::IoChain, operation_context::{ @@ -2666,13 +2658,18 @@ fn test_complete() { }, }; - let parser = TestParser::new(); - let ctx = OperationContext::test_only_foreground(&parser, &vars, Box::new(no_cancel)); + let TestParser { + ref mut parser, + ref mut pushed_dirs, + } = TestParser::new(); + let ctx = &mut OperationContext::test_only_foreground(parser, &vars, Box::new(no_cancel)); - let do_complete = - |cmd: &wstr, flags: CompletionRequestOptions| complete(cmd, flags, &ctx).0; + let do_complete = |ctx: &mut OperationContext<'_>, + cmd: &wstr, + flags: CompletionRequestOptions| + -> Vec { complete(cmd, flags, ctx).0 }; - let mut completions = do_complete(L!("$"), CompletionRequestOptions::default()); + let mut completions = do_complete(ctx, L!("$"), CompletionRequestOptions::default()); sort_and_prioritize(&mut completions, CompletionRequestOptions::default()); assert_eq!( completions @@ -2689,21 +2686,21 @@ fn test_complete() { ); // Smartcase test. Lowercase inputs match both lowercase and uppercase. - let mut completions = do_complete(L!("$a"), CompletionRequestOptions::default()); + let mut completions = do_complete(ctx, L!("$a"), CompletionRequestOptions::default()); sort_and_prioritize(&mut completions, CompletionRequestOptions::default()); assert_eq!(completions.len(), 2); assert_eq!(completions[0].completion, L!("$ALPHA!")); assert_eq!(completions[1].completion, L!("lpha")); - let mut completions = do_complete(L!("$F"), CompletionRequestOptions::default()); + let mut completions = do_complete(ctx, L!("$F"), CompletionRequestOptions::default()); sort_and_prioritize(&mut completions, CompletionRequestOptions::default()); assert_eq!(completions.len(), 3); assert_eq!(completions[0].completion, L!("oo1")); assert_eq!(completions[1].completion, L!("oo2")); assert_eq!(completions[2].completion, L!("oo3")); - completions = do_complete(L!("$1"), CompletionRequestOptions::default()); + completions = do_complete(ctx, L!("$1"), CompletionRequestOptions::default()); sort_and_prioritize(&mut completions, CompletionRequestOptions::default()); assert_eq!(completions, vec![]); @@ -2711,7 +2708,7 @@ fn test_complete() { fuzzy_match: true, ..Default::default() }; - let mut completions = do_complete(L!("$1"), fuzzy_options); + let mut completions = do_complete(ctx, L!("$1"), fuzzy_options); sort_and_prioritize(&mut completions, fuzzy_options); assert_eq!(completions.len(), 3); assert_eq!(completions[0].completion, L!("$Bar1")); @@ -2741,6 +2738,7 @@ fn test_complete() { std::fs::create_dir_all("test/complete_test/foo3").unwrap(); completions = do_complete( + ctx, L!("echo (test/complete_test/testfil"), CompletionRequestOptions::default(), ); @@ -2748,6 +2746,7 @@ fn test_complete() { assert_eq!(completions[0].completion, L!("e")); completions = do_complete( + ctx, L!("echo (ls test/complete_test/testfil"), CompletionRequestOptions::default(), ); @@ -2755,6 +2754,7 @@ fn test_complete() { assert_eq!(completions[0].completion, L!("e")); completions = do_complete( + ctx, L!("echo (command ls test/complete_test/testfil"), CompletionRequestOptions::default(), ); @@ -2763,6 +2763,7 @@ fn test_complete() { // Completing after spaces - see #2447 completions = do_complete( + ctx, L!("echo (ls test/complete_test/has\\ "), CompletionRequestOptions::default(), ); @@ -2779,7 +2780,7 @@ macro_rules! whole_token_completion_dominates { $completion_from_token_start:literal, $completion_from_separator:literal, ) => { - completions = do_complete(L!($cmd), $options); + completions = do_complete(ctx, L!($cmd), $options); let actual: Vec<_> = completions .iter() .map(|c| (c.completion.as_utfstr(), c.r#match.from_separator)) @@ -2805,7 +2806,8 @@ macro_rules! whole_token_completion_dominates { }; } - parser.pushd("test/complete_test/cwd-for-colon"); + ctx.parser() + .pushd(pushed_dirs, "test/complete_test/cwd-for-colon"); whole_token_completion_dominates!( ": ../colon:", CompletionRequestOptions::default(), @@ -2826,13 +2828,13 @@ macro_rules! whole_token_completion_dominates { "../colon:TTestWithColon", "../colon:test-file-in-cwd", ); - parser.popd(); + ctx.parser().popd(pushed_dirs); } macro_rules! unique_completion_applies_as { ( $cmdline:expr, $completion_result:expr, $applied:expr $(,)? ) => { let cmdline = L!($cmdline); - let completions = do_complete(cmdline, CompletionRequestOptions::default()); + let completions = do_complete(ctx, cmdline, CompletionRequestOptions::default()); assert_eq!(completions.len(), 1); assert_eq!( completions[0].completion, @@ -2841,7 +2843,7 @@ macro_rules! unique_completion_applies_as { ); let mut cursor = cmdline.len(); let newcmdline = completion_apply_to_command_line( - &ctx, + ctx, &completions[0].completion, completions[0].flags, cmdline, @@ -2888,7 +2890,8 @@ macro_rules! unique_completion_applies_as { #[cfg(not(cygwin))] // Colons are not legal filename characters on WIN32/CYGWIN { - parser.pushd("test/complete_test/cwd-for-colon"); + ctx.parser() + .pushd(pushed_dirs, "test/complete_test/cwd-for-colon"); unique_completion_applies_as!( r"touch ../colon", r":TTestWithColon", @@ -2900,7 +2903,7 @@ macro_rules! unique_completion_applies_as { r"TTestWithColon", r#"touch "../colon:TTestWithColon" "#, ); - parser.popd(); + ctx.parser().popd(pushed_dirs); } unique_completion_applies_as!("echo $SOMEV", r"AR", "echo $SOMEVAR "); @@ -2910,7 +2913,7 @@ macro_rules! unique_completion_applies_as { // #8820 let mut cursor_pos = 11; let newcmdline = completion_apply_to_command_line( - &ctx, + ctx, L!("Debug/"), CompleteFlags::REPLACES_TOKEN | CompleteFlags::NO_SPACE, L!("mv debug debug"), @@ -2921,15 +2924,21 @@ macro_rules! unique_completion_applies_as { assert_eq!(newcmdline, L!("mv debug Debug/")); // Add a function and test completing it in various ways. - parser.eval(L!("function scuttlebutt; end"), &IoChain::new()); + ctx.parser() + .eval(L!("function scuttlebutt; end"), &IoChain::new()); // Complete a function name. - completions = do_complete(L!("echo (scuttlebut"), CompletionRequestOptions::default()); + completions = do_complete( + ctx, + L!("echo (scuttlebut"), + CompletionRequestOptions::default(), + ); assert_eq!(completions.len(), 1); assert_eq!(completions[0].completion, L!("t")); // But not with the command prefix. completions = do_complete( + ctx, L!("echo (command scuttlebut"), CompletionRequestOptions::default(), ); @@ -2937,6 +2946,7 @@ macro_rules! unique_completion_applies_as { // Not with the builtin prefix. let completions = do_complete( + ctx, L!("echo (builtin scuttlebut"), CompletionRequestOptions::default(), ); @@ -2944,6 +2954,7 @@ macro_rules! unique_completion_applies_as { // Not after a redirection. let completions = do_complete( + ctx, L!("echo hi > scuttlebut"), CompletionRequestOptions::default(), ); @@ -2965,38 +2976,41 @@ macro_rules! unique_completion_applies_as { WString::new(), CompleteFlags::AUTO_SPACE, ); - let completions = do_complete(L!("foobarbaz "), CompletionRequestOptions::default()); + let completions = do_complete(ctx, L!("foobarbaz "), CompletionRequestOptions::default()); assert_eq!(completions.len(), 1); assert_eq!(completions[0].completion, L!("qux")); // Don't complete variable names in single quotes (#1023). - let completions = do_complete(L!("echo '$Foo"), CompletionRequestOptions::default()); + let completions = do_complete(ctx, L!("echo '$Foo"), CompletionRequestOptions::default()); assert_eq!(completions, vec![]); - let completions = do_complete(L!("echo \\$Foo"), CompletionRequestOptions::default()); + let completions = do_complete(ctx, L!("echo \\$Foo"), CompletionRequestOptions::default()); assert_eq!(completions, vec![]); // File completions. let completions = do_complete( + ctx, L!("cat test/complete_test/te"), CompletionRequestOptions::default(), ); assert_eq!(completions.len(), 1); assert_eq!(completions[0].completion, L!("stfile")); let completions = do_complete( + ctx, L!("echo sup > test/complete_test/te"), CompletionRequestOptions::default(), ); assert_eq!(completions.len(), 1); assert_eq!(completions[0].completion, L!("stfile")); let completions = do_complete( + ctx, L!("echo sup > test/complete_test/te"), CompletionRequestOptions::default(), ); assert_eq!(completions.len(), 1); assert_eq!(completions[0].completion, L!("stfile")); - parser.pushd("test/complete_test"); - let completions = do_complete(L!("cat te"), CompletionRequestOptions::default()); + ctx.parser().pushd(pushed_dirs, "test/complete_test"); + let completions = do_complete(ctx, L!("cat te"), CompletionRequestOptions::default()); assert_eq!(completions.len(), 1); assert_eq!(completions[0].completion, L!("stfile")); assert!(!completions[0].replaces_token()); @@ -3005,7 +3019,11 @@ macro_rules! unique_completion_applies_as { .flags .contains(CompleteFlags::DUPLICATES_ARGUMENT)) ); - let completions = do_complete(L!("cat testfile te"), CompletionRequestOptions::default()); + let completions = do_complete( + ctx, + L!("cat testfile te"), + CompletionRequestOptions::default(), + ); assert_eq!(completions.len(), 1); assert_eq!(completions[0].completion, L!("stfile")); assert!( @@ -3013,7 +3031,11 @@ macro_rules! unique_completion_applies_as { .flags .contains(CompleteFlags::DUPLICATES_ARGUMENT) ); - let completions = do_complete(L!("cat testfile TE"), CompletionRequestOptions::default()); + let completions = do_complete( + ctx, + L!("cat testfile TE"), + CompletionRequestOptions::default(), + ); assert_eq!(completions.len(), 1); assert_eq!(completions[0].completion, L!("testfile")); assert!(completions[0].replaces_token()); @@ -3023,41 +3045,56 @@ macro_rules! unique_completion_applies_as { .contains(CompleteFlags::DUPLICATES_ARGUMENT) ); let completions = do_complete( + ctx, L!("something --abc=te"), CompletionRequestOptions::default(), ); assert_eq!(completions.len(), 1); assert_eq!(completions[0].completion, L!("stfile")); - let completions = do_complete(L!("something -abc=te"), CompletionRequestOptions::default()); - assert_eq!(completions.len(), 1); - assert_eq!(completions[0].completion, L!("stfile")); - let completions = do_complete(L!("something abc=te"), CompletionRequestOptions::default()); + let completions = do_complete( + ctx, + L!("something -abc=te"), + CompletionRequestOptions::default(), + ); assert_eq!(completions.len(), 1); assert_eq!(completions[0].completion, L!("stfile")); let completions = do_complete( + ctx, + L!("something abc=te"), + CompletionRequestOptions::default(), + ); + assert_eq!(completions.len(), 1); + assert_eq!(completions[0].completion, L!("stfile")); + let completions = do_complete( + ctx, L!("something abc=stfile"), CompletionRequestOptions::default(), ); assert_eq!(&completions, &[]); - let completions = do_complete(L!("something abc=stfile"), fuzzy_options); + let completions = do_complete(ctx, L!("something abc=stfile"), fuzzy_options); assert_eq!(completions.len(), 1); assert_eq!(completions[0].completion, L!("abc=testfile")); // Zero escapes can cause problems. See issue #1631. - let completions = do_complete(L!("cat foo\\0"), CompletionRequestOptions::default()); + let completions = do_complete(ctx, L!("cat foo\\0"), CompletionRequestOptions::default()); assert_eq!(&completions, &[]); - let completions = do_complete(L!("cat foo\\0bar"), CompletionRequestOptions::default()); + let completions = do_complete( + ctx, + L!("cat foo\\0bar"), + CompletionRequestOptions::default(), + ); assert_eq!(&completions, &[]); - let completions = do_complete(L!("cat \\0"), CompletionRequestOptions::default()); + let completions = do_complete(ctx, L!("cat \\0"), CompletionRequestOptions::default()); assert_eq!(&completions, &[]); - let mut completions = do_complete(L!("cat te\\0"), CompletionRequestOptions::default()); + let mut completions = + do_complete(ctx, L!("cat te\\0"), CompletionRequestOptions::default()); assert_eq!(&completions, &[]); - parser.popd(); + ctx.parser().popd(pushed_dirs); completions.clear(); // Test abbreviations. - parser.eval( + ctx.parser().eval( L!("function testabbrsonetwothreefour; end"), &IoChain::new(), ); @@ -3074,7 +3111,7 @@ macro_rules! unique_completion_applies_as { let completions = complete( L!("testabbrsonetwothree"), CompletionRequestOptions::default(), - &parser.context(), + ctx, ) .0; assert_eq!(completions.len(), 2); @@ -3128,18 +3165,20 @@ macro_rules! unique_completion_applies_as { ); // Test cd wrapping chain - parser.pushd("test/complete_test"); + ctx.parser().pushd(pushed_dirs, "test/complete_test"); complete_add_wrapper(L!("cdwrap1").into(), L!("cd").into()); complete_add_wrapper(L!("cdwrap2").into(), L!("cdwrap1").into()); - let mut cd_compl = do_complete(L!("cd "), CompletionRequestOptions::default()); + let mut cd_compl = do_complete(ctx, L!("cd "), CompletionRequestOptions::default()); sort_and_prioritize(&mut cd_compl, CompletionRequestOptions::default()); - let mut cdwrap1_compl = do_complete(L!("cdwrap1 "), CompletionRequestOptions::default()); + let mut cdwrap1_compl = + do_complete(ctx, L!("cdwrap1 "), CompletionRequestOptions::default()); sort_and_prioritize(&mut cdwrap1_compl, CompletionRequestOptions::default()); - let mut cdwrap2_compl = do_complete(L!("cdwrap2 "), CompletionRequestOptions::default()); + let mut cdwrap2_compl = + do_complete(ctx, L!("cdwrap2 "), CompletionRequestOptions::default()); sort_and_prioritize(&mut cdwrap2_compl, CompletionRequestOptions::default()); let min_compl_size = cd_compl @@ -3156,7 +3195,7 @@ macro_rules! unique_completion_applies_as { complete_remove_wrapper(L!("cdwrap1").into(), L!("cd")); complete_remove_wrapper(L!("cdwrap2").into(), L!("cdwrap1")); - parser.popd(); + parser.popd(pushed_dirs); } // Testing test_autosuggest_suggest_special, in particular for properly handling quotes and @@ -3165,13 +3204,16 @@ macro_rules! unique_completion_applies_as { #[serial] fn test_autosuggest_suggest_special() { test_init(); - let parser = TestParser::new(); + let TestParser { + ref mut parser, + ref mut pushed_dirs, + } = TestParser::new(); macro_rules! perform_one_autosuggestion_cd_test { ($command:literal, $expected:literal, $vars:expr) => { let mut comps = complete( L!($command), CompletionRequestOptions::autosuggest(), - &OperationContext::background($vars, EXPANSION_LIMIT_BACKGROUND), + &mut OperationContext::background($vars, EXPANSION_LIMIT_BACKGROUND), ) .0; @@ -3191,8 +3233,8 @@ macro_rules! perform_one_completion_cd_test { let mut comps = complete( L!($command), CompletionRequestOptions::default(), - &OperationContext::foreground( - &parser, + &mut OperationContext::foreground( + parser, Box::new(no_cancel), EXPANSION_LIMIT_DEFAULT, ), @@ -3297,7 +3339,7 @@ macro_rules! perform_one_completion_cd_test { &vars ); - parser.pushd(wd); + parser.pushd(pushed_dirs, wd); perform_one_autosuggestion_cd_test!("cd 0", "foobar/", &vars); perform_one_autosuggestion_cd_test!("cd \"0", "foobar/", &vars); perform_one_autosuggestion_cd_test!("cd '0", "foobar/", &vars); @@ -3339,7 +3381,7 @@ macro_rules! perform_one_completion_cd_test { L!("HOME"), EnvSetMode::new(EnvMode::LOCAL | EnvMode::EXPORT, false), ); - parser.popd(); + parser.popd(pushed_dirs); } #[test] @@ -3352,7 +3394,7 @@ macro_rules! perform_one_autosuggestion_should_ignore_test { let comps = complete( L!($command), CompletionRequestOptions::autosuggest(), - &OperationContext::empty(), + &mut OperationContext::empty(), ) .0; assert_eq!(&comps, &[]); diff --git a/src/env/environment.rs b/src/env/environment.rs index ce70b9b44..c8eecf1d7 100644 --- a/src/env/environment.rs +++ b/src/env/environment.rs @@ -814,9 +814,12 @@ mod tests { fn test_env_snapshot() { test_init(); std::fs::create_dir_all("test/fish_env_snapshot_test/").unwrap(); - let parser = TestParser::new(); + let TestParser { + ref mut parser, + ref mut pushed_dirs, + } = TestParser::new(); + parser.pushd(pushed_dirs, "test/fish_env_snapshot_test/"); let vars = parser.vars(); - parser.pushd("test/fish_env_snapshot_test/"); vars.push(true); let before_pwd = vars.get(L!("PWD")).unwrap().as_string(); vars.set_one( @@ -878,7 +881,7 @@ fn test_env_snapshot() { ); vars.pop(false); - parser.popd(); + parser.popd(pushed_dirs); } // Can't push/pop from globals. diff --git a/src/event.rs b/src/event.rs index 380fbf530..59641045c 100644 --- a/src/event.rs +++ b/src/event.rs @@ -274,7 +274,7 @@ fn is_blocked(&self, parser: &Parser) -> bool { } } - parser.global_event_blocks.load(Ordering::Relaxed) != 0 + parser.global_event_blocks != 0 } } @@ -463,7 +463,7 @@ pub fn get_function_handlers(name: &wstr) -> EventHandlerList { /// Perform the specified event. Since almost all event firings will not be matched by even a single /// event handler, we make sure to optimize the 'no matches' path. This means that nothing is /// allocated/initialized unless needed. -fn fire_internal(parser: &Parser, event: &Event) { +fn fire_internal(parser: &mut Parser, event: &Event) { // Suppress fish_trace during events. let _saved = parser.push_scope(|s| { s.is_event = true; @@ -499,7 +499,7 @@ fn fire_internal(parser: &Parser, event: &Event) { // non-interactive. let _non_interactive = parser.push_scope(|s| s.is_interactive = false); let saved_statuses = parser.last_statuses(); - let _cleanup = ScopeGuard::new((), |()| { + let parser = &mut **ScopeGuard::new(&mut *parser, |parser| { parser.set_last_statuses(saved_statuses); }); @@ -526,7 +526,7 @@ fn fire_internal(parser: &Parser, event: &Event) { } /// Fire all delayed events attached to the given parser. -pub fn fire_delayed(parser: &Parser) { +pub fn fire_delayed(parser: &mut Parser) { // Do not invoke new event handlers from within event handlers. if parser.scope().is_event { return; @@ -585,7 +585,7 @@ pub fn enqueue_signal(signal: libc::c_int) { } /// Fire the specified event event, executing it on `parser`. -pub fn fire(parser: &Parser, event: Event) { +pub fn fire(parser: &mut Parser, event: Event) { // Fire events triggered by signals. fire_delayed(parser); @@ -661,7 +661,7 @@ pub fn print(streams: &mut IoStreams, type_filter: &wstr) { } /// Fire a generic event with the specified name. -pub fn fire_generic(parser: &Parser, name: WString, arguments: Vec) { +pub fn fire_generic(parser: &mut Parser, name: WString, arguments: Vec) { fire( parser, Event { diff --git a/src/exec.rs b/src/exec.rs index fc9b870f9..a78978bad 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -83,7 +83,7 @@ fn exec_thread_pool() -> &'static Arc { /// On a true return, the job was successfully launched and the parser will take responsibility for /// cleaning it up. On a false return, the job could not be launched and the caller must clean it /// up. -pub fn exec_job(parser: &Parser, job: &Job, block_io: IoChain) -> bool { +pub fn exec_job(parser: &mut Parser, job: &Job, block_io: IoChain) -> bool { // If fish was invoked with -n or --no-execute, then no_exec will be set and we do nothing. if no_exec() { return true; @@ -269,7 +269,7 @@ pub fn exec_job(parser: &Parser, job: &Job, block_io: IoChain) -> bool { /// Return a value appropriate for populating $status. pub fn exec_subshell( cmd: &wstr, - parser: &Parser, + parser: &mut Parser, outputs: Option<&mut Vec>, apply_exit_status: bool, ) -> Result<(), ErrorCode> { @@ -291,7 +291,7 @@ pub fn exec_subshell( /// pgroup. pub fn exec_subshell_for_expand( cmd: &wstr, - parser: &Parser, + parser: &mut Parser, job_group: Option<&JobGroupRef>, outputs: &mut Vec, ) -> Result<(), ErrorCode> { @@ -652,7 +652,7 @@ fn skip_err(&self) -> bool { /// If `outdata` or `errdata` are both empty, then mark the process as completed immediately. /// Otherwise, run an internal process. fn run_internal_process_or_short_circuit( - parser: &Parser, + parser: &mut Parser, j: &Job, p: &Process, outdata: Vec, @@ -838,7 +838,7 @@ fn create_output_stream_for_builtin( /// Handle output from a builtin, by printing the contents of builtin_io_streams to the redirections /// given in io_chain. fn handle_builtin_output( - parser: &Parser, + parser: &mut Parser, j: &Job, p: &Process, io_chain: &IoChain, @@ -867,7 +867,7 @@ fn handle_builtin_output( /// An error return here indicates that the process failed to launch, and the rest of /// the pipeline should be cancelled. fn exec_external_command( - parser: &Parser, + parser: &mut Parser, j: &Job, p: &Process, proc_io_chain: &IoChain, @@ -949,7 +949,7 @@ fn exec_external_command( // Given that we are about to execute a function, push a function block and set up the // variable environment. fn function_prepare_environment( - parser: &Parser, + parser: &mut Parser, mut argv: Vec, props: &FunctionProperties, ) -> BlockId { @@ -1000,7 +1000,7 @@ fn function_prepare_environment( } // Given that we are done executing a function, restore the environment. -fn function_restore_environment(parser: &Parser, block: BlockId) { +fn function_restore_environment(parser: &mut Parser, block: BlockId) { parser.pop_block(block); // If we returned due to a return statement, then stop returning now. @@ -1011,7 +1011,7 @@ fn function_restore_environment(parser: &Parser, block: BlockId) { // This accepts a place to execute as `parser` and then executes the result, returning a status. // This is factored out in this funny way in preparation for concurrent execution. type ProcPerformer = - dyn FnOnce(&Parser, Option<&mut OutputStream>, Option<&mut OutputStream>) -> ProcStatus; + dyn FnOnce(&mut Parser, Option<&mut OutputStream>, Option<&mut OutputStream>) -> ProcStatus; // Return a function which may be to run the given block node process 'p'. fn get_performer_for_block_node(p: &Process, job: &Job, io_chain: &IoChain) -> Box { @@ -1023,7 +1023,7 @@ fn get_performer_for_block_node(p: &Process, job: &Job, io_chain: &IoChain) -> B let job_group = job.group.clone(); let io_chain = io_chain.clone(); let node = node.clone(); - Box::new(move |parser: &Parser, _out, _err| { + Box::new(move |parser: &mut Parser, _out, _err| { parser .eval_node(&node, &io_chain, job_group.as_ref(), BlockType::Top, false) .status @@ -1057,7 +1057,7 @@ fn get_performer_for_function( return Err(()); }; let argv = p.argv().clone(); - Ok(Box::new(move |parser: &Parser, _out, _err| { + Ok(Box::new(move |parser: &mut Parser, _out, _err| { // Pull out the job list from the function. let fb = function_prepare_environment(parser, argv, &props); let body_node = props.func_node.child_ref(|n| &n.jobs); @@ -1081,7 +1081,7 @@ fn get_performer_for_function( /// Execute a block node or function "process". /// `piped_output_needs_buffering` if true, buffer the output. fn exec_block_or_func_process( - parser: &Parser, + parser: &mut Parser, j: &Job, p: &Process, mut io_chain: IoChain, @@ -1159,7 +1159,7 @@ fn get_performer_for_builtin(p: &Process, j: &Job, io_chain: &IoChain) -> Box, errput_stream: Option<&mut OutputStream>| { let output_stream = output_stream.unwrap(); @@ -1207,7 +1207,7 @@ fn get_performer_for_builtin(p: &Process, j: &Job, io_chain: &IoChain) -> Box bool { +fn allow_exec_with_background_jobs(parser: &mut Parser) -> bool { // If we're not interactive, we cannot warn. if !parser.is_interactive() { return true; @@ -1439,7 +1439,7 @@ fn allow_exec_with_background_jobs(parser: &Parser) -> bool { *last_exec_run_count = current_run_count; false } else { - hup_jobs(&parser.jobs()); + hup_jobs(parser.jobs()); true } } @@ -1492,14 +1492,14 @@ fn populate_subshell_output(lst: &mut Vec, buffer: &SeparatedBuffer, sp /// of $status. fn exec_subshell_internal( cmd: &wstr, - parser: &Parser, + parser: &mut Parser, job_group: Option<&JobGroupRef>, lst: Option<&mut Vec>, break_expand: &mut bool, apply_exit_status: bool, is_subcmd: bool, ) -> Result<(), ErrorCode> { - let _scoped = parser.push_scope(|s| { + let _scoped = parser.push_scope(move |s| { s.is_subshell = true; s.read_limit = if is_subcmd { READ_BYTE_LIMIT.load(Ordering::Relaxed) @@ -1509,7 +1509,7 @@ fn exec_subshell_internal( }); let prev_statuses = parser.last_statuses(); - let _put_back = ScopeGuard::new((), |()| { + let parser = &mut **ScopeGuard::new(parser, |parser| { if !apply_exit_status { parser.set_last_statuses(prev_statuses); } diff --git a/src/expand.rs b/src/expand.rs index c117a4f25..14bca7e41 100644 --- a/src/expand.rs +++ b/src/expand.rs @@ -125,7 +125,7 @@ pub fn expand_string( input: WString, out_completions: &mut CompletionList, flags: ExpandFlags, - ctx: &OperationContext, + ctx: &mut OperationContext, errors: Option<&mut ParseErrorList>, ) -> ExpandResult { let completions = std::mem::take(out_completions); @@ -140,7 +140,7 @@ pub fn expand_to_receiver( input: WString, out_completions: &mut CompletionReceiver, flags: ExpandFlags, - ctx: &OperationContext, + ctx: &mut OperationContext, errors: Option<&mut ParseErrorList>, ) -> ExpandResult { Expander::expand_string(input, out_completions, flags, ctx, errors) @@ -158,7 +158,7 @@ pub fn expand_to_receiver( pub fn expand_one( s: &mut WString, flags: ExpandFlags, - ctx: &OperationContext, + ctx: &mut OperationContext, errors: Option<&mut ParseErrorList>, ) -> bool { if !flags.contains(ExpandFlags::FOR_COMPLETIONS) && expand_is_clean(s) { @@ -187,7 +187,7 @@ pub fn expand_one( /// Return an expand error. pub fn expand_to_command_and_args( instr: &wstr, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, out_cmd: &mut WString, mut out_args: Option<&mut Vec>, errors: Option<&mut ParseErrorList>, @@ -894,7 +894,7 @@ fn expand_braces( /// `out_list`, or any errors into `errors`. Return an expand result. pub fn expand_cmdsubst( input: WString, - ctx: &OperationContext, + ctx: &mut OperationContext, out: &mut CompletionReceiver, errors: &mut Option<&mut ParseErrorList>, ) -> ExpandResult { @@ -1173,7 +1173,7 @@ fn remove_internal_separator(s: &mut WString, conv: bool) { /// A type that knows how to perform expansions. struct Expander<'a, 'b, 'c> { /// Operation context for this expansion. - ctx: &'c OperationContext<'b>, + ctx: &'c mut OperationContext<'b>, /// Flags to use during expansion. flags: ExpandFlags, @@ -1184,7 +1184,7 @@ struct Expander<'a, 'b, 'c> { impl<'a, 'b, 'c> Expander<'a, 'b, 'c> { fn new( - ctx: &'c OperationContext<'b>, + ctx: &'c mut OperationContext<'b>, flags: ExpandFlags, errors: &'c mut Option<&'a mut ParseErrorList>, ) -> Self { @@ -1195,7 +1195,7 @@ fn expand_string( input: WString, out_completions: &'a mut CompletionReceiver, flags: ExpandFlags, - ctx: &'a OperationContext<'b>, + ctx: &'a mut OperationContext<'b>, mut errors: Option<&'a mut ParseErrorList>, ) -> ExpandResult { assert!( @@ -1567,19 +1567,14 @@ fn expand_test_impl( expected: Vec, error_message: Option<&str>, ) { - let parser = TestParser::new(); + let parser = &mut TestParser::new(); let mut output = CompletionList::new(); let mut errors = ParseErrorList::new(); let pwd = PwdEnvironment::default(); - let ctx = OperationContext::test_only_foreground(&parser, &pwd, Box::new(no_cancel)); + let ctx = &mut OperationContext::test_only_foreground(parser, &pwd, Box::new(no_cancel)); - if expand_string( - input.to_owned(), - &mut output, - flags, - &ctx, - Some(&mut errors), - ) == ExpandResultCode::error + if expand_string(input.to_owned(), &mut output, flags, ctx, Some(&mut errors)) + == ExpandResultCode::error { assert_ne!( errors, @@ -1607,7 +1602,10 @@ fn expand_test_impl( #[serial] fn test_expand() { test_init(); - let parser = TestParser::new(); + let TestParser { + ref mut parser, + ref mut pushed_dirs, + } = TestParser::new(); /// Perform parameter expansion and test if the output equals the zero-terminated parameter list /// supplied. /// /// \param in the string to expand @@ -1882,7 +1880,7 @@ macro_rules! expand_test { "" ); - parser.pushd("test/fish_expand_test"); + parser.pushd(pushed_dirs, "test/fish_expand_test"); expand_test!( "b/xx", @@ -1894,7 +1892,7 @@ macro_rules! expand_test { // multiple slashes with fuzzy matching - #3185 expand_test!("l///n", fuzzy_comp, "lol///nub/", "Wrong fuzzy matching 6"); - parser.popd(); + parser.popd(pushed_dirs); } #[test] @@ -1909,14 +1907,14 @@ fn test_expand_overflow() { let vals: Vec = (1..=64).map(|i| i.to_wstring()).collect(); let expansion = str2wcstring(str::repeat("$bigvar", 64)); - let parser = TestParser::new(); + let parser = &mut TestParser::new(); parser.vars().push(true); let set = parser.set_var(L!("bigvar"), ParserEnvSetMode::new(EnvMode::LOCAL), vals); assert_eq!(set, EnvStackSetResult::Ok); let mut errors = ParseErrorList::new(); let ctx = - OperationContext::foreground(&parser, Box::new(no_cancel), EXPANSION_LIMIT_DEFAULT); + &mut OperationContext::foreground(parser, Box::new(no_cancel), EXPANSION_LIMIT_DEFAULT); // We accept only 1024 completions. let mut output = CompletionReceiver::new(1024); @@ -1925,13 +1923,13 @@ fn test_expand_overflow() { expansion, &mut output, ExpandFlags::default(), - &ctx, + ctx, Some(&mut errors), ); assert_ne!(errors, vec![]); assert_eq!(res, ExpandResultCode::error); - parser.vars().pop(false); + ctx.parser().vars().pop(false); } #[test] diff --git a/src/function.rs b/src/function.rs index 064ca84ec..1dacf22e9 100644 --- a/src/function.rs +++ b/src/function.rs @@ -116,7 +116,7 @@ fn allow_autoload(&self, name: &wstr) -> bool { /// Make sure that if the specified function is a dynamically loaded function, it has been fully /// loaded. Note this executes fish script code. -pub fn load(name: &wstr, parser: &Parser) -> bool { +pub fn load(name: &wstr, parser: &mut Parser) -> bool { let mut path_to_autoload: Option<_> = None; // Note we can't autoload while holding the funcset lock. // Lock around a local region. @@ -213,7 +213,7 @@ pub fn get_props(name: &wstr) -> Option> { } /// Return the properties for a function, or None, perhaps triggering autoloading. -pub fn get_props_autoload(name: &wstr, parser: &Parser) -> Option> { +pub fn get_props_autoload(name: &wstr, parser: &mut Parser) -> Option> { if parser_keywords_is_reserved(name) { return None; } @@ -223,7 +223,7 @@ pub fn get_props_autoload(name: &wstr, parser: &Parser) -> Option bool { +pub fn exists(cmd: &wstr, parser: &mut Parser) -> bool { if !valid_func_name(cmd) { return false; } @@ -289,7 +289,7 @@ fn get_function_body_source(props: &FunctionProperties) -> &wstr { /// Sets the description of the function with the name \c name. /// This triggers autoloading. -pub(crate) fn set_desc(name: &wstr, desc: WString, parser: &Parser) { +pub(crate) fn set_desc(name: &wstr, desc: WString, parser: &mut Parser) { load(name, parser); let mut funcset = FUNCTION_SET.lock().unwrap(); if let Some(props) = funcset.funcs.get(name) { diff --git a/src/highlight/file_tester.rs b/src/highlight/file_tester.rs index 3e2fe8a4f..59d3f5c0c 100644 --- a/src/highlight/file_tester.rs +++ b/src/highlight/file_tester.rs @@ -48,15 +48,15 @@ pub struct PathFlags { /// The result of a file test. pub type FileTestResult = Result; -pub struct FileTester<'s> { +pub struct FileTester<'src, 'opctx> { // The working directory, for resolving paths against. working_directory: WString, // The operation context. - ctx: &'s OperationContext<'s>, + pub(super) ctx: &'opctx mut OperationContext<'src>, } -impl<'s> FileTester<'s> { - pub fn new(working_directory: WString, ctx: &'s OperationContext<'s>) -> Self { +impl<'src, 'opctx> FileTester<'src, 'opctx> { + pub fn new(working_directory: WString, ctx: &'opctx mut OperationContext<'src>) -> Self { Self { working_directory, ctx, @@ -102,7 +102,7 @@ pub fn test_path(&self, token: &wstr, prefix: bool) -> bool { // Test if the string is a prefix of a valid path we could cd into, or is some other token // we recognize (primarily --help). // If is_prefix is true, we test if the string is a prefix of a valid path we could cd into. - pub fn test_cd_path(&self, token: &wstr, is_prefix: bool) -> FileTestResult { + pub fn test_cd_path(&mut self, token: &wstr, is_prefix: bool) -> FileTestResult { let mut param = token.to_owned(); if !expand_one(&mut param, ExpandFlags::FAIL_ON_CMDSUBST, self.ctx, None) { // Failed expansion (e.g. may contain a command substitution). Ignore it. @@ -133,7 +133,11 @@ pub fn test_cd_path(&self, token: &wstr, is_prefix: bool) -> FileTestResult { // Test if a the given string is a valid redirection target, and if so, whether // it is a path to an existing file. - pub fn test_redirection_target(&self, target: &wstr, mode: RedirectionMode) -> FileTestResult { + pub fn test_redirection_target( + &mut self, + target: &wstr, + mode: RedirectionMode, + ) -> FileTestResult { // Skip targets exceeding PATH_MAX. See #7837. if target.len() > (PATH_MAX as usize) { return Err(IsErr); @@ -435,6 +439,7 @@ mod tests { redirection::RedirectionMode, tests::prelude::*, }; + use fish_tempfile::TempDir; use fish_widestring::osstr2wcstring; use std::{ fs::{self, File, Permissions, create_dir_all}, @@ -442,34 +447,31 @@ mod tests { path::PathBuf, }; - struct TempDirWithCtx { - tempdir: fish_tempfile::TempDir, - ctx: OperationContext<'static>, + fn temp_dir() -> TempDir { + fish_tempfile::new_dir().unwrap() } - impl TempDirWithCtx { - fn new() -> TempDirWithCtx { - TempDirWithCtx { - tempdir: fish_tempfile::new_dir().unwrap(), - ctx: OperationContext::empty(), - } - } + fn filepath(tempdir: &TempDir, name: &str) -> PathBuf { + tempdir.path().join(name) + } - fn filepath(&self, name: &str) -> PathBuf { - self.tempdir.path().join(name) - } + fn op_context() -> OperationContext<'static> { + OperationContext::empty() + } - fn file_tester(&self) -> FileTester<'_> { - FileTester::new(osstr2wcstring(self.tempdir.path()), &self.ctx) - } + fn file_tester<'a>( + ctx: &'a mut OperationContext<'static>, + tempdir: &TempDir, + ) -> FileTester<'static, 'a> { + FileTester::new(osstr2wcstring(tempdir.path()), ctx) } #[test] fn test_ispath() { - let temp = TempDirWithCtx::new(); - let tester = temp.file_tester(); + let (tempdir, ctx) = (temp_dir(), &mut op_context()); + let tester = file_tester(ctx, &tempdir); - let file_path = temp.filepath("file.txt"); + let file_path = filepath(&tempdir, "file.txt"); File::create(file_path).unwrap(); let result = tester.test_path(L!("file.txt"), false); @@ -497,7 +499,7 @@ fn test_ispath() { assert!(!result); // Directories are also files. - let dir_path = temp.filepath("somedir"); + let dir_path = filepath(&tempdir, "somedir"); create_dir_all(dir_path).unwrap(); let result = tester.test_path(L!("somedir"), false); @@ -515,13 +517,13 @@ fn test_ispath() { #[test] fn test_iscdpath() { - let temp = TempDirWithCtx::new(); - let tester = temp.file_tester(); + let (tempdir, ctx) = (temp_dir(), &mut op_context()); + let mut tester = file_tester(ctx, &tempdir); // Note cd (unlike file paths) should report IsErr for invalid cd paths, // rather than IsFile(false). - let dir_path = temp.filepath("somedir"); + let dir_path = filepath(&tempdir, "somedir"); create_dir_all(dir_path).unwrap(); let result = tester.test_cd_path(L!("somedir"), false); @@ -546,12 +548,12 @@ fn test_iscdpath() { #[test] fn test_redirections() { // Note we use is_ok and is_err since we don't care about the IsFile part. - let temp = TempDirWithCtx::new(); - let tester = temp.file_tester(); - let file_path = temp.filepath("file.txt"); + let (tempdir, ctx) = (temp_dir(), &mut op_context()); + let mut tester = file_tester(ctx, &tempdir); + let file_path = filepath(&tempdir, "file.txt"); File::create(&file_path).unwrap(); - let dir_path = temp.filepath("somedir"); + let dir_path = filepath(&tempdir, "somedir"); create_dir_all(&dir_path).unwrap(); // Normal redirection. diff --git a/src/highlight/highlight.rs b/src/highlight/highlight.rs index edaad1843..755c358dd 100644 --- a/src/highlight/highlight.rs +++ b/src/highlight/highlight.rs @@ -95,10 +95,10 @@ pub fn colorize(text: &wstr, colors: &[HighlightSpec], vars: &dyn Environment) - /// \param io_ok If set, allow IO which may block. This means that e.g. invalid commands may be /// detected. /// \param cursor The position of the cursor in the commandline. -pub fn highlight_shell( - buff: &wstr, +pub fn highlight_shell<'src, 'ctx>( + buff: &'src wstr, color: &mut Vec, - ctx: &OperationContext<'_>, + ctx: &'ctx mut OperationContext<'src>, io_ok: bool, /* = false */ cursor: Option, ) { @@ -107,7 +107,10 @@ pub fn highlight_shell( *color = highlighter.highlight(); } -pub fn highlight_and_colorize(text: &wstr, ctx: &OperationContext<'_>) -> Vec { +pub fn highlight_and_colorize<'src, 'ctx>( + text: &'src wstr, + ctx: &'ctx mut OperationContext<'src>, +) -> Vec { let mut colors = Vec::new(); highlight_shell( text, @@ -301,7 +304,7 @@ fn has_expand_reserved(s: &wstr) -> bool { // command (as a string), if any. This is used to validate autosuggestions. fn autosuggest_parse_command( buff: &wstr, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, ) -> Option<(WString, WString)> { let flags = ParseTreeFlags { continue_after_error: true, @@ -342,7 +345,7 @@ pub fn autosuggest_validate_from_history( suggested_range: std::ops::Range, required_paths: &[WString], working_directory: &wstr, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, ) -> bool { assert_is_background_thread(); @@ -683,32 +686,30 @@ enum Mode { pub type ColorArray = Vec; /// Syntax highlighter helper. -struct Highlighter<'s> { +struct Highlighter<'src, 'ctx> { // The string we're highlighting. Note this is a reference member variable (to avoid copying)! - buff: &'s wstr, + buff: &'src wstr, // The position of the cursor within the string. cursor: Option, - // The operation context. - ctx: &'s OperationContext<'s>, // Whether it's OK to do I/O. io_ok: bool, // Working directory. working_directory: WString, // Our component for testing strings for being potential file paths. - file_tester: FileTester<'s>, + file_tester: FileTester<'src, 'ctx>, // The resulting colors. color_array: ColorArray, // A stack of variables that the current commandline probably defines. We mark redirections // as valid if they use one of these variables, to avoid marking valid targets as error. - pending_variables: Vec<&'s wstr>, + pending_variables: Vec<&'src wstr>, done: bool, } -impl<'s> Highlighter<'s> { +impl<'src, 'ctx> Highlighter<'src, 'ctx> { pub fn new( - buff: &'s wstr, + buff: &'src wstr, cursor: Option, - ctx: &'s OperationContext<'s>, + ctx: &'ctx mut OperationContext<'src>, working_directory: WString, can_do_io: bool, ) -> Self { @@ -716,7 +717,6 @@ pub fn new( Self { buff, cursor, - ctx, io_ok: can_do_io, working_directory, file_tester, @@ -726,6 +726,10 @@ pub fn new( } } + fn ctx(&self) -> &OperationContext<'src> { + self.file_tester.ctx + } + pub fn highlight(&mut self) -> ColorArray { assert!(!self.done); self.done = true; @@ -750,7 +754,7 @@ pub fn highlight(&mut self) -> ColorArray { let ast = ast::parse(self.buff, ast_flags, None); self.visit_children(ast.top()); - if self.ctx.check_cancel() { + if self.file_tester.ctx.check_cancel() { return std::mem::take(&mut self.color_array); } @@ -777,14 +781,14 @@ pub fn highlight(&mut self) -> ColorArray { } /// Return a substring of our buffer. - pub fn get_source(&self, r: SourceRange) -> &'s wstr { + pub fn get_source(&self, r: SourceRange) -> &'src wstr { assert!(r.end() >= r.start(), "Overflow"); assert!(r.end() <= self.buff.len(), "Out of range"); &self.buff[r.start()..r.end()] } fn io_still_ok(&self) -> bool { - self.io_ok && !self.ctx.check_cancel() + self.io_ok && !self.ctx().check_cancel() } // Color a command. @@ -847,7 +851,7 @@ fn color_as_argument(&mut self, node: &dyn ast::Node, options_allowed: bool /* = let mut cmdsub_highlighter = Highlighter::new( cmdsub_contents, arg_cursor, - self.ctx, + self.file_tester.ctx, self.working_directory.clone(), self.io_still_ok(), ); @@ -1041,14 +1045,16 @@ fn visit_decorated_statement(&mut self, stmt: &DecoratedStatement) { } else { // Check to see if the command is valid. // Try expanding it. If we cannot, it's an error. - if let Some(expanded) = statement_get_expanded_command(self.buff, stmt, self.ctx) { + if let Some(expanded) = + statement_get_expanded_command(self.buff, stmt, self.file_tester.ctx) + { expanded_cmd = expanded; if !has_expand_reserved(&expanded_cmd) { is_valid_cmd = command_is_valid( &expanded_cmd, stmt.decoration(), &self.working_directory, - self.ctx.vars(), + self.file_tester.ctx.vars(), ); } } @@ -1143,7 +1149,7 @@ fn contains_pending_variable(pending_variables: &[&wstr], haystack: &wstr) -> bo false } -impl<'s, 'a> NodeVisitor<'a> for Highlighter<'s> { +impl<'src, 'ctx, 'a> NodeVisitor<'a> for Highlighter<'src, 'ctx> { fn visit(&mut self, node: &'a dyn Node) { if let Some(keyword) = node.as_keyword() { return self.visit_keyword(keyword); @@ -1174,7 +1180,7 @@ fn visit(&mut self, node: &'a dyn Node) { fn statement_get_expanded_command( src: &wstr, stmt: &ast::DecoratedStatement, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, ) -> Option { // Get the command. Try expanding it. If we cannot, it's an error. let cmd = stmt.command.try_source(src)?; @@ -1329,10 +1335,13 @@ fn get_overlong_path() -> String { #[serial] fn test_highlighting() { test_init(); - let parser = TestParser::new(); + let TestParser { + ref mut parser, + ref mut pushed_dirs, + } = TestParser::new(); // Testing syntax highlighting - parser.pushd("test/fish_highlight_test/"); - let _popd = ScopeGuard::new((), |_| parser.popd()); + parser.pushd(pushed_dirs, "test/fish_highlight_test/"); + let parser = &mut **ScopeGuard::new(parser, |parser| parser.popd(pushed_dirs)); std::fs::create_dir_all("dir").unwrap(); std::fs::create_dir_all("cdpath-entry/dir-in-cdpath").unwrap(); std::fs::write("foo", []).unwrap(); @@ -1388,7 +1397,7 @@ macro_rules! validate { highlight_shell( &text, &mut colors, - &OperationContext::background(vars, EXPANSION_LIMIT_BACKGROUND), + &mut OperationContext::background(vars, EXPANSION_LIMIT_BACKGROUND), true, /* io_ok */ Some(text.len()), ); @@ -1814,7 +1823,7 @@ macro_rules! validate { #[allow(clippy::needless_range_loop)] fn test_trailing_spaces_after_command() { test_init(); - let parser = TestParser::new(); + let parser = &mut TestParser::new(); let vars = parser.vars(); // First, set up fish_color_command to include underline @@ -1830,7 +1839,7 @@ fn test_trailing_spaces_after_command() { highlight_shell( &text, &mut colors, - &OperationContext::background(vars, EXPANSION_LIMIT_BACKGROUND), + &mut OperationContext::background(vars, EXPANSION_LIMIT_BACKGROUND), true, /* io_ok */ Some(text.len()), ); @@ -1868,7 +1877,7 @@ fn test_trailing_spaces_after_command() { #[serial] fn test_resolve_role() { test_init(); - let parser = TestParser::new(); + let parser = &mut TestParser::new(); let vars = parser.vars(); let set = |var: &wstr, value: Vec| { diff --git a/src/history/history.rs b/src/history/history.rs index b935bd84c..31549a0ca 100644 --- a/src/history/history.rs +++ b/src/history/history.rs @@ -16,7 +16,7 @@ use crate::{ ast::{self, Kind, Node as _}, - common::{CancelChecker, valid_var_name}, + common::valid_var_name, env::{EnvMode, EnvSetMode, EnvStack, EnvVar, Environment}, expand::{ExpandFlags, expand_one}, fds::wopen_cloexec, @@ -1094,12 +1094,12 @@ fn string_could_be_path(potential_path: &wstr) -> bool { /// Perform a search of `hist` for `search_string`. Invoke a function `func` for each match. If /// `func` returns [`ControlFlow::Break`], stop the search. fn do_1_history_search( + parser: &mut Parser, hist: Arc, search_type: SearchType, search_string: WString, case_sensitive: bool, - mut func: impl FnMut(&HistoryItem) -> ControlFlow<(), ()>, - cancel_check: &CancelChecker, + mut func: impl FnMut(&mut Parser, &HistoryItem) -> ControlFlow<(), ()>, ) { let mut searcher = HistorySearch::new_with( hist, @@ -1112,8 +1112,11 @@ fn do_1_history_search( }, 0, ); - while !cancel_check() && searcher.go_to_next_match(SearchDirection::Backward) { - if let ControlFlow::Break(()) = func(searcher.current_item()) { + + while !(parser.context().cancel_checker)() + && searcher.go_to_next_match(SearchDirection::Backward) + { + if let ControlFlow::Break(()) = func(parser, searcher.current_item()) { break; } } @@ -1124,7 +1127,7 @@ fn format_history_record( item: &HistoryItem, show_time_format: Option<&str>, null_terminate: bool, - parser: &Parser, + parser: &mut Parser, color_enabled: bool, ) -> WString { let mut result = WString::new(); @@ -1156,7 +1159,7 @@ fn format_history_record( let mut command = item.str().to_owned(); if color_enabled { - command = bytes2wcstring(&highlight_and_colorize(&command, &parser.context())); + command = bytes2wcstring(&highlight_and_colorize(&command, &mut parser.context())); } result.push_utfstr(&command); @@ -1377,7 +1380,7 @@ pub fn save(&self) { #[allow(clippy::too_many_arguments)] pub fn search( self: &Arc, - parser: &Parser, + parser: &mut Parser, streams: &mut IoStreams, search_type: SearchType, search_args: &[&wstr], @@ -1393,7 +1396,7 @@ pub fn search( let mut output_error = false; // The function we use to act on each item. - let mut func = |item: &HistoryItem| { + let mut func = |parser: &mut Parser, item: &HistoryItem| { if remaining == 0 { return ControlFlow::Break(()); } @@ -1420,17 +1423,15 @@ pub fn search( ControlFlow::Continue(()) }; - let cancel_check = &parser.context().cancel_checker; - if search_args.is_empty() { // The user had no search terms; just append everything. do_1_history_search( + parser, Arc::clone(self), SearchType::Contains, WString::new(), true, &mut func, - cancel_check, ); } else { #[allow(clippy::unnecessary_to_owned)] @@ -1442,12 +1443,12 @@ pub fn search( return false; } do_1_history_search( + parser, Arc::clone(self), search_type, search_string.to_owned(), case_sensitive, &mut func, - cancel_check, ); } } @@ -1750,7 +1751,7 @@ pub fn expand_and_detect_paths>( ) -> Vec { assert_is_background_thread(); let working_directory = vars.get_pwd_slash(); - let ctx = OperationContext::background(vars, EXPANSION_LIMIT_BACKGROUND); + let ctx = &mut OperationContext::background(vars, EXPANSION_LIMIT_BACKGROUND); let mut result = Vec::new(); for path in paths { // Suppress cmdsubs since we are on a background thread and don't want to execute fish @@ -1762,7 +1763,7 @@ pub fn expand_and_detect_paths>( if expand_one( &mut expanded_path, ExpandFlags::FAIL_ON_CMDSUBST | ExpandFlags::SKIP_WILDCARDS, - &ctx, + ctx, None, ) && path_is_valid(&expanded_path, &working_directory) { @@ -1777,7 +1778,7 @@ pub fn expand_and_detect_paths>( /// Given a list of proposed paths and a context, expand each one and see if it refers to a file. /// Wildcard expansions are suppressed. /// Returns `true` if `paths` is empty or every path is valid. -pub fn all_paths_are_valid(paths: &[WString], ctx: &OperationContext<'_>) -> bool { +pub fn all_paths_are_valid(paths: &[WString], ctx: &mut OperationContext<'_>) -> bool { assert_is_background_thread(); let working_directory = ctx.vars().get_pwd_slash(); let mut path = WString::new(); diff --git a/src/input.rs b/src/input.rs index 4664d9785..1dd01e421 100644 --- a/src/input.rs +++ b/src/input.rs @@ -258,7 +258,7 @@ pub fn input_mappings() -> MutexGuard<'static, InputMappingSet> { } /// Return the current bind mode. -fn input_get_bind_mode(vars: &dyn Environment) -> WString { +pub fn input_get_bind_mode(vars: &dyn Environment) -> WString { if let Some(mode) = vars.get(FISH_BIND_MODE_VAR) { mode.as_string() } else { @@ -610,12 +610,8 @@ fn try_peek_sequence( /// user's mapping list, then the preset list. /// Return none if nothing matches, or if we may have matched a longer sequence but it was /// interrupted by a readline event. - pub fn find_mapping<'a>( - &mut self, - vars: &dyn Environment, - ip: &'a InputMappingSet, - ) -> Option { - let bind_mode = input_get_bind_mode(vars); + pub fn find_mapping<'a>(&mut self, ip: &'a InputMappingSet) -> Option { + let bind_mode = self.event_queue.get_bind_mode(); struct MatchedMapping<'a> { mapping: &'a InputMapping, @@ -789,11 +785,10 @@ pub fn read_char(&mut self) -> CharEvent { } fn mapping_execute_matching_or_generic(&mut self) { - let vars = self.parser.vars(); let mut peeker = EventQueuePeeker::new(self); // Check for ordinary mappings. let ip = input_mappings(); - if let Some(mapping) = peeker.find_mapping(vars, &ip) { + if let Some(mapping) = peeker.find_mapping(&ip) { flog!( reader, format!("Found mapping {:?} from {:?}", &mapping, &peeker.peeked) @@ -1009,8 +1004,7 @@ pub fn input_function_get_code(name: &wstr) -> Option { #[cfg(test)] mod tests { - use super::{DEFAULT_BIND_MODE, EventQueuePeeker, InputMappingSet, KeyNameStyle}; - use crate::env::EnvStack; + use super::{EventQueuePeeker, InputMappingSet, KeyNameStyle}; use crate::input_common::{CharEvent, InputData, InputEventQueuer, KeyEvent}; use crate::key::Key; use crate::prelude::*; @@ -1030,7 +1024,6 @@ fn get_input_data_mut(&mut self) -> &mut InputData { #[test] fn test_input() { - let vars = EnvStack::new(); let mut input = TestInputEventQueuer { input_data: InputData::new(i32::MAX, None), // value doesn't matter since we don't read from it }; @@ -1041,14 +1034,14 @@ fn test_input() { let mut desired_binding = prefix_binding.clone(); desired_binding.push(Key::from_raw('a')); - let default_mode = || DEFAULT_BIND_MODE.to_owned(); + let bind_mode = || input.get_bind_mode(); let mut input_mappings = InputMappingSet::default(); input_mappings.add1( prefix_binding, KeyNameStyle::Plain, L!("up-line").to_owned(), - default_mode(), + bind_mode(), None, true, ); @@ -1056,7 +1049,7 @@ fn test_input() { desired_binding.clone(), KeyNameStyle::Plain, L!("down-line").to_owned(), - default_mode(), + bind_mode(), None, true, ); @@ -1069,7 +1062,7 @@ fn test_input() { } let mut peeker = EventQueuePeeker::new(&mut input); - let mapping = peeker.find_mapping(&vars, &input_mappings); + let mapping = peeker.find_mapping(&input_mappings); assert!(mapping.is_some()); assert_eq!(mapping.unwrap().commands, ["down-line"]); peeker.restart(); diff --git a/src/input_common.rs b/src/input_common.rs index 1f304676b..c3a621fa1 100644 --- a/src/input_common.rs +++ b/src/input_common.rs @@ -21,7 +21,6 @@ use fish_widestring::{bytes2wcstring, encode_byte_to_char, fish_reserved_codepoint}; use nix::sys::{select::FdSet, signal::SigSet, time::TimeSpec}; use std::{ - cell::{RefCell, RefMut}, collections::VecDeque, os::fd::{BorrowedFd, RawFd}, sync::atomic::{AtomicUsize, Ordering}, @@ -671,7 +670,7 @@ pub struct InputData { pub blocking_query_timeout: Option, // If set, events will be buffered until the query finishes. - pub blocking_query: RefCell>, + pub blocking_query: Option, } impl InputData { @@ -685,7 +684,7 @@ pub fn new(in_fd: RawFd, blocking_query_timeout: Option) -> Self { function_status: false, event_storage: Vec::new(), blocking_query_timeout, - blocking_query: RefCell::new(None), + blocking_query: None, } } @@ -888,7 +887,7 @@ fn readch(&mut self) -> CharEvent { reader, "Received interrupt key, giving up waiting for response from terminal" ); - let ok = stop_query(self.blocking_query()); + let ok = stop_query(self.blocking_query_mut()); assert!(ok); self.get_input_data_mut().queue.clear(); self.push_front(CharEvent::QueryResult(QueryResultEvent::Interrupted)); @@ -1603,8 +1602,11 @@ fn drop_leading_readline_events(&mut self) { } } - fn blocking_query(&self) -> RefMut<'_, Option> { - self.get_input_data().blocking_query.borrow_mut() + fn blocking_query(&self) -> &Option { + &self.get_input_data().blocking_query + } + fn blocking_query_mut(&mut self) -> &mut Option { + &mut self.get_input_data_mut().blocking_query } fn is_blocked_querying(&self) -> bool { self.blocking_query().is_some() @@ -1621,7 +1623,7 @@ fn enqueue_interrupt_key(&mut self) { let vintr = shell_modes().control_chars[libc::VINTR]; if vintr != 0 { let interrupt_evt = CharEvent::from_key(KeyEvent::from_single_byte(vintr)); - if stop_query(self.blocking_query()) { + if stop_query(self.blocking_query_mut()) { flog!( reader, "Received interrupt, giving up on waiting for terminal response" @@ -1651,6 +1653,14 @@ fn function_status(&self) -> bool { fn has_lookahead(&self) -> bool { !self.get_input_data().queue.is_empty() } + + fn get_bind_mode(&self) -> WString { + #[allow(clippy::assertions_on_constants)] + { + assert!(cfg!(test)); + } + WString::from("test-bind-mode") + } } pub(crate) enum DecodeState { @@ -1711,7 +1721,7 @@ pub(crate) fn decode_utf8( } } -pub(crate) fn stop_query(mut query: RefMut<'_, Option>) -> bool { +pub(crate) fn stop_query(query: &mut Option) -> bool { query.take().is_some() } diff --git a/src/operation_context.rs b/src/operation_context.rs index d2e6b36bc..894fee4c8 100644 --- a/src/operation_context.rs +++ b/src/operation_context.rs @@ -22,11 +22,11 @@ pub fn no_cancel() -> bool { enum Vars<'a> { // The parser, if this is a foreground operation. If this is a background operation, this may be // nullptr. - Parser(&'a Parser), + Parser(&'a mut Parser), // A set of variables. Vars(&'a dyn Environment), - TestOnly(&'a Parser, &'a dyn Environment), + TestOnly(&'a mut Parser, &'a dyn Environment), } /// A operation_context_t is a simple property bag which wraps up data needed for highlighting, @@ -70,7 +70,7 @@ pub fn globals() -> OperationContext<'static> { /// Construct from a full set of properties. pub fn foreground( - parser: &'a Parser, + parser: &'a mut Parser, cancel_checker: CancelChecker, expansion_limit: usize, ) -> OperationContext<'a> { @@ -83,7 +83,7 @@ pub fn foreground( } pub fn test_only_foreground( - parser: &'a Parser, + parser: &'a mut Parser, vars: &'a dyn Environment, cancel_checker: CancelChecker, ) -> OperationContext<'a> { @@ -129,15 +129,15 @@ pub fn background_interruptible(env: &dyn Environment) -> OperationContext<'_> { pub fn has_parser(&self) -> bool { matches!(self.vars, Vars::Parser(_) | Vars::TestOnly(_, _)) } - pub fn maybe_parser(&self) -> Option<&Parser> { - match &self.vars { + pub fn maybe_parser(&mut self) -> Option<&mut Parser> { + match &mut self.vars { Vars::Parser(parser) => Some(parser), Vars::Vars(_) => None, Vars::TestOnly(parser, _) => Some(parser), } } - pub fn parser(&self) -> &Parser { - match &self.vars { + pub fn parser(&mut self) -> &mut Parser { + match &mut self.vars { Vars::Parser(parser) => parser, Vars::Vars(_) => panic!(), Vars::TestOnly(parser, _) => parser, diff --git a/src/pager.rs b/src/pager.rs index 12884bbf2..d04d81ab9 100644 --- a/src/pager.rs +++ b/src/pager.rs @@ -1291,7 +1291,7 @@ fn process_completions_into_infos(lst: &[Completion]) -> Vec { highlight_shell( &comp.completion, &mut comp_info.colors, - &OperationContext::empty(), + &mut OperationContext::empty(), false, None, ); diff --git a/src/parse_execution.rs b/src/parse_execution.rs index 1e249ce14..a7f48b21c 100644 --- a/src/parse_execution.rs +++ b/src/parse_execution.rs @@ -146,7 +146,7 @@ pub fn pstree(&self) -> &ParsedSourceRef { pub fn eval_node( &mut self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, node: &dyn Node, associated_block: Option, ) -> EndExecutionReason { @@ -161,7 +161,7 @@ pub fn eval_node( /// error. fn eval_statement( &mut self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, statement: &ast::Statement, associated_block: Option, ) -> EndExecutionReason { @@ -179,7 +179,7 @@ fn eval_statement( fn eval_job_list( &mut self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, job_list: &ast::JobList, associated_block: BlockId, ) -> EndExecutionReason { @@ -219,7 +219,7 @@ fn eval_job_list( // Check to see if we should end execution. // Return the eval result to end with, or none() to continue on. // This will never return end_execution_reason_t::ok. - fn check_end_execution(&self, ctx: &OperationContext<'_>) -> Option { + fn check_end_execution(&self, ctx: &mut OperationContext<'_>) -> Option { // If one of our jobs ended with SIGINT, we stop execution. // Likewise if fish itself got a SIGINT, or if something ran exit, etc. if self.cancel_signal.is_some() || ctx.check_cancel() || fish_is_unwinding_for_exit() { @@ -241,7 +241,7 @@ fn check_end_execution(&self, ctx: &OperationContext<'_>) -> Option, + ctx: &mut OperationContext<'_>, status: c_int, error_list: &ParseErrorList, ) -> EndExecutionReason { @@ -267,7 +267,7 @@ fn report_errors( /// Command not found support. fn handle_command_not_found( &mut self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, cmd: &wstr, statement: &ast::DecoratedStatement, err: std::io::Error, @@ -386,27 +386,23 @@ fn node_source_owned(&self, node: &dyn ast::Node) -> WString { fn infinite_recursive_statement_in_job_list<'a>( &self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, jobs: &'a ast::JobList, out_func_name: &mut WString, ) -> Option<&'a ast::DecoratedStatement> { // This is a bit fragile. It is a test to see if we are inside of function call, but // not inside a block in that function call. If, in the future, the rules for what // block scopes are pushed on function invocation changes, then this check will break. - let parser = ctx.parser(); - let parent; - let parent_fn_name = { + fn parent_fn_name<'a, 'ctx>(ctx: &'ctx mut OperationContext<'a>) -> Option<&'ctx wstr> { + let parser = ctx.parser(); match (parser.block_at_index(0), parser.block_at_index(1)) { - (Some(current), Some(p)) if current.typ() == BlockType::Top => { - parent = p; - match parent.data() { - Some(BlockData::Function { name, .. }) => name, - _ => return None, - } - } - _ => return None, // Not within function call. + (Some(current), Some(p)) if current.typ() == BlockType::Top => match p.data() { + Some(BlockData::Function { name, .. }) => Some(name), + _ => None, + }, + _ => None, // Not within function call. } - }; + } // Get the function name of the immediate block. let forbidden_function_name = parent_fn_name; @@ -416,16 +412,18 @@ fn infinite_recursive_statement_in_job_list<'a>( let job = &jc.job; // Helper to return if a statement is infinitely recursive in this function. - let statement_recurses = |stat: &'a ast::Statement| -> Option<&'a ast::DecoratedStatement> { + let statement_recurses = |ctx: &mut OperationContext<'_>, + stat: &'a ast::Statement| + -> Option> { // Ignore non-decorated statements like `if`, etc. let Statement::Decorated(dc) = &stat else { - return None; + return Some(None); }; // Ignore statements with decorations like 'builtin' or 'command', since those // are not infinite recursion. In particular that is what enables 'wrapper functions'. if dc.decoration() != StatementDecoration::None { - return None; + return Some(None); } // Check the command. @@ -437,16 +435,20 @@ fn infinite_recursive_statement_in_job_list<'a>( ctx, None, ) - && &cmd == forbidden_function_name; - if forbidden { Some(dc) } else { None } + && cmd == forbidden_function_name(ctx)?; + if forbidden { + Some(Some(dc)) + } else { + Some(None) + } }; // Check main statement. - let infinite_recursive_statement = statement_recurses(&jc.job.statement) + let infinite_recursive_statement = statement_recurses(ctx, &jc.job.statement)? // Check piped remainder. .or_else(|| { for c in &job.continuation { - let s = statement_recurses(&c.statement); + let s = statement_recurses(ctx, &c.statement)?; if s.is_some() { return s; } @@ -455,7 +457,7 @@ fn infinite_recursive_statement_in_job_list<'a>( }); if infinite_recursive_statement.is_some() { - forbidden_function_name.clone_into(out_func_name); + forbidden_function_name(ctx)?.clone_into(out_func_name); } // may be none @@ -464,7 +466,7 @@ fn infinite_recursive_statement_in_job_list<'a>( fn report_wildcard_error( &self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, node: &dyn ast::Node, ) -> EndExecutionReason { report_error!( @@ -482,7 +484,7 @@ fn report_wildcard_error( // arguments. Prints an error message on error. fn expand_command( &mut self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, statement: &ast::DecoratedStatement, out_cmd: &mut WString, out_args: &mut Vec, @@ -580,7 +582,7 @@ fn job_is_simple_block(&self, job: &ast::JobPipeline) -> bool { fn process_type_for_command( &self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, statement: &ast::DecoratedStatement, cmd: &wstr, ) -> ProcessType { @@ -604,7 +606,7 @@ fn process_type_for_command( fn apply_variable_assignments( &mut self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, mut proc: Option<&mut Process>, variable_assignment_list: &ast::VariableAssignmentList, block: &mut Option, @@ -664,7 +666,7 @@ fn apply_variable_assignments( // These create process_t structures from statements. fn populate_job_process( &mut self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, job: &mut Job, proc: &mut Process, statement: &ast::Statement, @@ -673,7 +675,7 @@ fn populate_job_process( let mut block = None; let result = self.apply_variable_assignments(ctx, Some(proc), variable_assignments, &mut block); - let _scope = ScopeGuard::new((), |()| { + let ctx = &mut **ScopeGuard::new(ctx, |ctx| { if let Some(block) = block { ctx.parser().pop_block(block); } @@ -697,7 +699,7 @@ fn populate_job_process( fn populate_not_process( &mut self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, job: &mut Job, proc: &mut Process, not_statement: &ast::NotStatement, @@ -718,7 +720,7 @@ fn populate_not_process( /// Creates a 'normal' (non-block) process. fn populate_plain_process( &mut self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, proc: &mut Process, statement: &ast::DecoratedStatement, ) -> EndExecutionReason { @@ -837,7 +839,7 @@ fn populate_plain_process( fn populate_block_process( &mut self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, proc: &mut Process, statement: &ast::Statement, ) -> EndExecutionReason { @@ -865,7 +867,7 @@ fn populate_block_process( // These encapsulate the actual logic of various (block) statements. fn run_block_statement( &mut self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, statement: &ast::BlockStatement, associated_block: Option, ) -> EndExecutionReason { @@ -883,7 +885,7 @@ fn run_block_statement( fn run_for_statement( &mut self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, header: &ast::ForHeader, block_contents: &ast::JobList, ) -> EndExecutionReason { @@ -992,7 +994,7 @@ fn run_for_statement( fn run_if_statement( &mut self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, statement: &ast::IfStatement, associated_block: Option, ) -> EndExecutionReason { @@ -1082,7 +1084,7 @@ fn run_if_statement( fn run_switch_statement( &mut self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, statement: &ast::SwitchStatement, ) -> EndExecutionReason { // Get the switch variable. @@ -1192,7 +1194,7 @@ fn run_switch_statement( fn run_while_statement( &mut self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, header: &ast::WhileHeader, contents: &ast::JobList, associated_block: Option, @@ -1277,7 +1279,7 @@ fn run_while_statement( // Define a function. fn run_function_statement( &mut self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, statement: &ast::BlockStatement, header: &ast::FunctionHeader, ) -> EndExecutionReason { @@ -1326,7 +1328,7 @@ fn run_function_statement( fn run_begin_statement( &mut self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, contents: &ast::JobList, ) -> EndExecutionReason { // Basic begin/end block. Push a scope block, run jobs, pop it @@ -1360,7 +1362,7 @@ fn get_argument_nodes_no_redirs(args: &ast::ArgumentOrRedirectionList) -> AstArg fn expand_arguments_from_nodes( &mut self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, argument_nodes: &AstArgsList<'_>, out_arguments: &mut Vec, glob_behavior: WildcardNoMatchBehavior, @@ -1425,7 +1427,7 @@ fn expand_arguments_from_nodes( // Determines the list of redirections for a node. fn determine_redirections( &self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, list: &ast::ArgumentOrRedirectionList, out_redirections: &mut RedirectionSpecList, ) -> EndExecutionReason { @@ -1527,7 +1529,7 @@ fn determine_redirections( fn run_1_job( &mut self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, job_node: &ast::JobPipeline, associated_block: Option, ) -> EndExecutionReason { @@ -1558,15 +1560,15 @@ fn run_1_job( } else { 0 }; - move |ctx: &OperationContext<'_>, cmd: WString, skipped: bool| { + move |ctx: &mut OperationContext<'_>, cmd: WString, skipped: bool| { let Some(profile_item_id) = profile_item_id else { return; }; let parser = ctx.parser(); - let mut profile_items = parser.profile_items_mut(); - let profile_item = &mut profile_items[profile_item_id]; + let eval_level = parser.scope().eval_level; + let profile_item = &mut parser.profile_items_mut()[profile_item_id]; profile_item.duration = ProfileItem::now() - start_time; - profile_item.level = ctx.parser().scope().eval_level; + profile_item.level = eval_level; profile_item.cmd = cmd; profile_item.skipped = skipped; } @@ -1596,7 +1598,7 @@ fn run_1_job( let mut block = None; let mut result = self.apply_variable_assignments(ctx, None, &job_node.variables, &mut block); - let _scope = ScopeGuard::new((), |()| { + let ctx = &mut **ScopeGuard::new(ctx, |ctx| { if let Some(block) = block { ctx.parser().pop_block(block); } @@ -1706,7 +1708,7 @@ fn run_1_job( fn test_and_run_1_job_conjunction( &mut self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, jc: &ast::JobConjunction, associated_block: Option, ) -> EndExecutionReason { @@ -1741,7 +1743,7 @@ fn test_and_run_1_job_conjunction( fn run_job_conjunction( &mut self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, job_expr: &ast::JobConjunction, associated_block: Option, ) -> EndExecutionReason { @@ -1775,7 +1777,7 @@ fn run_job_conjunction( fn run_job_list( &mut self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, job_list_node: &ast::JobList, associated_block: Option, ) -> EndExecutionReason { @@ -1789,7 +1791,7 @@ fn run_job_list( fn run_andor_job_list( &mut self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, job_list_node: &ast::AndorJobList, associated_block: Option, ) -> EndExecutionReason { @@ -1803,7 +1805,7 @@ fn run_andor_job_list( fn populate_job_from_job_node( &mut self, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, j: &mut Job, job_node: &ast::JobPipeline, _associated_block: Option, @@ -1876,7 +1878,7 @@ fn populate_job_from_job_node( } // Assign a job group to the given job. - fn setup_group(&self, ctx: &OperationContext<'_>, j: &mut Job) { + fn setup_group(&self, ctx: &mut OperationContext<'_>, j: &mut Job) { // We can use the parent group if it's compatible and we're not backgrounded. if ctx .job_group @@ -1905,7 +1907,7 @@ fn setup_group(&self, ctx: &OperationContext<'_>, j: &mut Job) { } // Return whether we should apply job control to our processes. - fn use_job_control(&self, ctx: &OperationContext<'_>) -> bool { + fn use_job_control(&self, ctx: &mut OperationContext<'_>) -> bool { if ctx.parser().is_command_substitution() { return false; } @@ -2012,8 +2014,8 @@ fn is_timed_not_statement(mut stat: &ast::Statement) -> bool { false } -fn remove_job(parser: &Parser, job: &JobRef) -> bool { - let mut jobs = parser.jobs_mut(); +fn remove_job(parser: &mut Parser, job: &JobRef) -> bool { + let jobs = parser.jobs_mut(); let num_jobs = jobs.len(); for i in 0..num_jobs { if Rc::ptr_eq(&jobs[i], job) { diff --git a/src/parse_util.rs b/src/parse_util.rs index 3ef73230c..c4dbd2283 100644 --- a/src/parse_util.rs +++ b/src/parse_util.rs @@ -1653,7 +1653,7 @@ fn detect_errors_in_decorated_statement( if matches!( expand_to_command_and_args( unexp_command, - &OperationContext::empty(), + &mut OperationContext::empty(), &mut command, None, Some(&mut new_errors), @@ -1729,7 +1729,7 @@ fn detect_errors_in_decorated_statement( if expand_one( &mut command, ExpandFlags::FAIL_ON_CMDSUBST, - &OperationContext::empty(), + &mut OperationContext::empty(), match parse_errors { Some(pe) => Some(pe), None => None, diff --git a/src/parser.rs b/src/parser.rs index 0f0fe8dce..4ebb687a8 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -12,7 +12,6 @@ event::{self, Event}, expand::{ExpandFlags, ExpandResultCode, expand_string, replace_home_directory_with_tilde}, flog, flogf, function, - global_safety::RelaxedAtomicBool, io::IoChain, job_group::MaybeJobId, operation_context::{EXPANSION_LIMIT_DEFAULT, OperationContext}, @@ -22,7 +21,6 @@ }, parse_execution::{EndExecutionReason, ExecutionContext}, parse_tree::{NodeRef, ParsedSourceRef, SourceLineCache, parse_source}, - portable_atomic::AtomicU64, prelude::*, proc::{InternalJobId, JobGroupRef, JobList, JobRef, Pid, ProcStatus, job_reap}, signal::{Signal, signal_check_cancel, signal_clear_cancel}, @@ -35,7 +33,6 @@ use fish_util::get_time; use fish_widestring::{WExt as _, wcs2bytes}; use libc::c_int; -use std::cell::{Ref, RefCell, RefMut}; use std::ffi::OsStr; use std::fs::File; use std::io::Write as _; @@ -266,13 +263,13 @@ fn default() -> Self { } } -/// Miscellaneous data used to avoid recursion and others. +/// Miscellaneous data. #[derive(Default)] pub struct LibraryData { /// A fake value to be returned by builtin_commandline. This is used by the completion /// machinery when wrapping: e.g. if `tig` wraps `git` then git completions need to see git on /// the command line. - pub transient_commandline: Option, + pub transient_commandline: ScopedRefCell>, /// Variables supporting the "status" builtin. pub status_vars: StatusVars, @@ -378,7 +375,7 @@ pub enum CancelBehavior { } pub struct Parser { - pub interactive_initialized: RelaxedAtomicBool, + pub interactive_initialized: bool, /// The current filename we are evaluating, either from builtin source or on the command line. pub current_filename: ScopedRefCell>, @@ -387,16 +384,16 @@ pub struct Parser { current_node: ScopedRefCell>>, /// The jobs associated with this parser. - job_list: RefCell, + job_list: JobList, /// Our store of recorded wait-handles. These are jobs that finished in the background, /// and have been reaped, but may still be wait'ed on. - wait_handles: RefCell, + wait_handles: WaitHandleStore, /// The list of blocks. /// This is a stack; the topmost block is at the end. This is to avoid invalidating block /// indexes during recursive evaluation. - block_list: RefCell>, + block_list: Vec, /// Set of variables for the parser. pub variables: EnvStack, @@ -405,23 +402,23 @@ pub struct Parser { scoped_data: ScopedCell, /// Miscellaneous library data. - pub library_data: ScopedRefCell, + pub library_data: LibraryData, /// If set, we synchronize universal variables after external commands, /// including sending on-variable change events. - syncs_uvars: RelaxedAtomicBool, + syncs_uvars: bool, /// The behavior when fish itself receives a signal and there are no blocks on the stack. cancel_behavior: CancelBehavior, /// List of profile items. - profile_items: RefCell>, + profile_items: Vec, /// Global event blocks. - pub global_event_blocks: AtomicU64, + pub global_event_blocks: u64, // Timeout for blocking terminal queries. - pub blocking_query_timeout: RefCell>, + pub blocking_query_timeout: Option, } #[derive(Copy, Clone, Default)] @@ -443,27 +440,27 @@ impl Parser { /// Create a parser. pub fn new(variables: EnvStack, cancel_behavior: CancelBehavior) -> Parser { let result = Self { - interactive_initialized: RelaxedAtomicBool::new(false), + interactive_initialized: false, current_node: ScopedRefCell::new(None), current_filename: ScopedRefCell::new(None), - job_list: RefCell::default(), - wait_handles: RefCell::default(), - block_list: RefCell::default(), + job_list: Default::default(), + wait_handles: Default::default(), + block_list: Default::default(), variables, scoped_data: ScopedCell::new(ScopedData::default()), - library_data: ScopedRefCell::new(LibraryData::new()), - syncs_uvars: RelaxedAtomicBool::new(false), + library_data: LibraryData::new(), + syncs_uvars: false, cancel_behavior, - profile_items: RefCell::default(), - global_event_blocks: AtomicU64::new(0), - blocking_query_timeout: RefCell::new(None), + profile_items: Default::default(), + global_event_blocks: 0, + blocking_query_timeout: None, }; result } /// Adds a job to the beginning of the job list. - pub fn job_add(&self, job: JobRef) { + pub fn job_add(&mut self, job: JobRef) { assert!(!job.processes().is_empty()); self.jobs_mut().insert(0, job); } @@ -484,7 +481,7 @@ pub fn is_command_substitution(&self) -> bool { .any(|b| b.typ() == BlockType::Subst) } - pub fn eval(&self, cmd: &wstr, io: &IoChain) -> EvalRes { + pub fn eval(&mut self, cmd: &wstr, io: &IoChain) -> EvalRes { self.eval_with(cmd, io, None, BlockType::Top, false) } @@ -497,7 +494,7 @@ pub fn eval(&self, cmd: &wstr, io: &IoChain) -> EvalRes { /// or 'subst'. /// Return the result of evaluation. pub fn eval_with( - &self, + &mut self, cmd: &wstr, io: &IoChain, job_group: Option<&JobGroupRef>, @@ -541,7 +538,7 @@ pub fn eval_with( /// Evaluate the parsed source ps. /// Because the source has been parsed, a syntax error is impossible. pub fn eval_parsed_source( - &self, + &mut self, ps: &ParsedSourceRef, io: &IoChain, job_group: Option<&JobGroupRef>, @@ -571,7 +568,7 @@ pub fn eval_parsed_source( } pub fn eval_wstr( - &self, + &mut self, src: WString, io: &IoChain, job_group: Option<&JobGroupRef>, @@ -597,7 +594,7 @@ pub fn eval_wstr( } pub fn eval_file_wstr( - &self, + &mut self, src: WString, filename: Arc, io: &IoChain, @@ -617,7 +614,7 @@ pub fn eval_file_wstr( /// Evaluates a node. /// The node type must be ast::Statement or ast::JobList. pub fn eval_node( - &self, + &mut self, node: &NodeRef, block_io: &IoChain, job_group: Option<&JobGroupRef>, @@ -637,8 +634,7 @@ pub fn eval_node( // cause fish to exit. let sig = signal_check_cancel(); if sig != 0 { - if self.cancel_behavior == CancelBehavior::Clear && self.block_list.borrow().is_empty() - { + if self.cancel_behavior == CancelBehavior::Clear && self.block_list.is_empty() { signal_clear_cancel(); } else { return EvalRes::new(ProcStatus::from_signal(Signal::new(sig))); @@ -667,19 +663,18 @@ pub fn eval_node( job_reap(self, false, Some(block_io)); // not sure why we reap jobs here // Start it up - let mut op_ctx = self.context(); let scope_block = self.push_block(Block::scope_block(block_type)); - // Propagate our job group. - op_ctx.job_group = job_group.cloned(); - - // Replace the context's cancel checker with one that checks the job group's signal. - let cancel_checker: CancelChecker = Box::new(move || check_cancel_signal().is_some()); - op_ctx.cancel_checker = cancel_checker; - // Restore the current pipeline node. let restore_current_node = self.current_node.scoped_replace(None); + let op_ctx = &mut self.context(); + // Propagate our job group. + op_ctx.job_group = job_group.cloned(); + // Replace the context's cancel checker with one that checks the job group's signal. + let cancel_checker: CancelChecker = Box::new(move || check_cancel_signal().is_some()); + op_ctx.cancel_checker = cancel_checker; + // Create a new execution context. let mut execution_context = ExecutionContext::new( node.parsed_source_ref(), @@ -688,13 +683,13 @@ pub fn eval_node( ); // Check the exec count so we know if anything got executed. - let exec_counts = || { - let ld = op_ctx.parser().libdata(); + let exec_counts = |ctx: &mut OperationContext<'_>| { + let ld = ctx.parser().libdata(); (ld.exec_count, ld.status_count) }; - let (prev_exec_count, prev_status_count) = exec_counts(); - let reason = execution_context.eval_node(&op_ctx, &**node, Some(scope_block)); - let (new_exec_count, new_status_count) = exec_counts(); + let (prev_exec_count, prev_status_count) = exec_counts(op_ctx); + let reason = execution_context.eval_node(op_ctx, &**node, Some(scope_block)); + let (new_exec_count, new_status_count) = exec_counts(op_ctx); drop(restore_current_node); self.pop_block(scope_block); @@ -722,7 +717,7 @@ pub fn eval_node( pub fn expand_argument_list( arg_list_src: &wstr, flags: ExpandFlags, - ctx: &OperationContext<'_>, + ctx: &mut OperationContext<'_>, ) -> CompletionList { // Parse the string as an argument list. let ast = ast::parse_argument_list(arg_list_src, ParseTreeFlags::default(), None); @@ -750,13 +745,6 @@ pub fn expand_argument_list( /// /// init.fish (line 127): ls|grep pancake pub fn current_line(&self) -> WString { - let Some(node_ref) = self.current_node.borrow().clone() else { - return WString::new(); - }; - let Some(source_offset) = node_ref.source_offset() else { - return WString::new(); - }; - let lineno = self.lineno_for_display(); let file = self.current_filename(); @@ -780,17 +768,26 @@ pub fn current_line(&self) -> WString { let skip_caret = self.is_interactive() && !self.is_function(); // Use an error with empty text. - let empty_error = ParseError { - source_start: source_offset, - ..Default::default() - }; + let mut line_info = { + let node_ref = self.current_node.borrow(); + let Some(node_ref) = node_ref.as_ref() else { + return WString::new(); + }; + let Some(source_offset) = node_ref.source_offset() else { + return WString::new(); + }; + let empty_error = ParseError { + source_start: source_offset, + ..Default::default() + }; - let mut line_info = empty_error.describe_with_prefix( - node_ref.source_str(), - &prefix, - self.is_interactive(), - skip_caret, - ); + empty_error.describe_with_prefix( + node_ref.source_str(), + &prefix, + self.is_interactive(), + skip_caret, + ) + }; if !line_info.is_empty() { line_info.push('\n'); } @@ -809,14 +806,10 @@ pub fn lineno_for_display(&self) -> u32 { self.lineno().map_or(0, |n| n.get()) } - pub fn current_node(&self) -> &ScopedRefCell>> { - &self.current_node - } - /// Returns a NodeRef to the current node being executed, if any. /// This can be used for lazy line number computation. - pub fn current_node_ref(&self) -> Option> { - self.current_node.borrow().clone() + pub fn current_node(&mut self) -> &ScopedRefCell>> { + &self.current_node } /// Return whether we are currently evaluating a "block" such as an if statement. @@ -841,55 +834,47 @@ pub fn is_breakpoint(&self) -> bool { // Return an iterator over the blocks, in reverse order. // That is, the first block is the innermost block. - pub fn blocks_iter_rev<'a>(&'a self) -> impl Iterator> { - let blocks = self.block_list.borrow(); - let mut indices = (0..blocks.len()).rev(); - std::iter::from_fn(move || { - let last = indices.next()?; - // note this clone is cheap - Some(Ref::map(Ref::clone(&blocks), |bl| &bl[last])) - }) + pub fn blocks_iter_rev<'a>(&'a self) -> impl Iterator { + self.block_list.iter().rev() } // Return the block at a given index, where 0 is the innermost block. - pub fn block_at_index(&self, index: usize) -> Option> { - let block_list = self.block_list.borrow(); + pub fn block_at_index(&self, index: usize) -> Option<&Block> { + let block_list = &self.block_list; let block_count = block_list.len(); if index >= block_count { None } else { - Some(Ref::map(block_list, |bl| &bl[block_count - 1 - index])) + Some(&block_list[block_count - 1 - index]) } } // Return the block at a given index, where 0 is the innermost block. - pub fn block_at_index_mut(&self, index: usize) -> Option> { - let block_list = self.block_list.borrow_mut(); + pub fn block_at_index_mut(&mut self, index: usize) -> Option<&mut Block> { + let block_list = &mut self.block_list; let block_count = block_list.len(); if index >= block_count { None } else { - Some(RefMut::map(block_list, |bl| { - &mut bl[block_count - 1 - index] - })) + Some(&mut block_list[block_count - 1 - index]) } } // Return the block with the given id, asserting it exists. Note ids are recycled. - pub fn block_with_id(&self, id: BlockId) -> Ref<'_, Block> { - Ref::map(self.block_list.borrow(), |bl| &bl[id.0]) + pub fn block_with_id(&self, id: BlockId) -> &Block { + &self.block_list[id.0] } pub fn blocks_size(&self) -> usize { - self.block_list.borrow().len() + self.block_list.len() } /// Get the list of jobs. - pub fn jobs(&self) -> Ref<'_, JobList> { - self.job_list.borrow() + pub fn jobs(&self) -> &JobList { + &self.job_list } - pub fn jobs_mut(&self) -> RefMut<'_, JobList> { - self.job_list.borrow_mut() + pub fn jobs_mut(&mut self) -> &mut JobList { + &mut self.job_list } /// Get the variables. @@ -906,26 +891,26 @@ pub fn scope(&self) -> ScopedData { /// 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 DerefMut + 'a { + pub fn push_scope(&self, modifier: F) -> impl DerefMut + use { self.scoped_data.scoped_mod(modifier) } /// Get the library data. - pub fn libdata(&self) -> Ref<'_, LibraryData> { - self.library_data.borrow() + pub fn libdata(&self) -> &LibraryData { + &self.library_data } /// Get the library data, mutably. - pub fn libdata_mut(&self) -> RefMut<'_, LibraryData> { - self.library_data.borrow_mut() + pub fn libdata_mut(&mut self) -> &mut LibraryData { + &mut self.library_data } /// Get our wait handle store. - pub fn wait_handles(&self) -> Ref<'_, WaitHandleStore> { - self.wait_handles.borrow() + pub fn wait_handles(&self) -> &WaitHandleStore { + &self.wait_handles } - pub fn mut_wait_handles(&self) -> RefMut<'_, WaitHandleStore> { - self.wait_handles.borrow_mut() + pub fn mut_wait_handles(&mut self) -> &mut WaitHandleStore { + &mut self.wait_handles } /// Get and set the last proc statuses. @@ -941,7 +926,7 @@ pub fn set_last_statuses(&self, s: Statuses) { /// Cover of vars().set(), which also fires any returned event handlers. pub fn set_var_and_fire( - &self, + &mut self, key: &wstr, mode: ParserEnvSetMode, vals: Vec, @@ -963,7 +948,7 @@ pub fn convert_env_set_mode(&self, mode: ParserEnvSetMode) -> EnvSetMode { /// Cover of vars().set(), without firing events pub fn set_var( - &self, + &mut self, key: &wstr, mode: ParserEnvSetMode, vals: Vec, @@ -973,19 +958,24 @@ pub fn set_var( } /// Cover of vars().set_one(), without firing events - pub fn set_one(&self, key: &wstr, mode: ParserEnvSetMode, val: WString) -> EnvStackSetResult { + pub fn set_one( + &mut self, + key: &wstr, + mode: ParserEnvSetMode, + val: WString, + ) -> EnvStackSetResult { let mode = self.convert_env_set_mode(mode); self.vars().set_one(key, mode, val) } /// Cover of vars().set_empty(), without firing events - pub fn set_empty(&self, key: &wstr, mode: ParserEnvSetMode) -> EnvStackSetResult { + pub fn set_empty(&mut self, key: &wstr, mode: ParserEnvSetMode) -> EnvStackSetResult { let mode = self.convert_env_set_mode(mode); self.vars().set_empty(key, mode) } /// Cover of vars().remove(), without firing events - pub fn remove_var(&self, key: &wstr, mode: ParserEnvSetMode) -> EnvStackSetResult { + pub fn remove_var(&mut self, key: &wstr, mode: ParserEnvSetMode) -> EnvStackSetResult { let mode = self.convert_env_set_mode(mode); self.vars().remove(key, mode) } @@ -993,8 +983,8 @@ pub fn remove_var(&self, key: &wstr, mode: ParserEnvSetMode) -> EnvStackSetResul /// Update any universal variables and send event handlers. /// If `always` is set, then do it even if we have no pending changes (that is, look for /// changes from other fish instances); otherwise only sync if this instance has changed uvars. - pub fn sync_uvars_and_fire(&self, always: bool) { - if self.syncs_uvars.load() { + pub fn sync_uvars_and_fire(&mut self, always: bool) { + if self.syncs_uvars { let evts = self.vars().universal_sync(always, self.is_repainting()); for evt in evts { event::fire(self, evt); @@ -1003,7 +993,7 @@ pub fn sync_uvars_and_fire(&self, always: bool) { } /// Pushes a new block. Returns an id (index) of the block, which is stored in the parser. - pub fn push_block(&self, mut block: Block) -> BlockId { + pub fn push_block(&mut self, mut block: Block) -> BlockId { block.src_filename = self.current_filename(); block.src_node.clone_from(&self.current_node.borrow()); if block.typ() != BlockType::Top { @@ -1011,15 +1001,15 @@ pub fn push_block(&self, mut block: Block) -> BlockId { self.vars().push(new_scope); } - let mut block_list = self.block_list.borrow_mut(); + let block_list = &mut self.block_list; block_list.push(block); BlockId(block_list.len() - 1) } /// Remove the outermost block, asserting it's the given one. - pub fn pop_block(&self, expected: BlockId) { + pub fn pop_block(&mut self, expected: BlockId) { let block = { - let mut block_list = self.block_list.borrow_mut(); + let block_list = &mut self.block_list; assert_eq!(expected.0, block_list.len() - 1); block_list.pop().unwrap() }; @@ -1067,14 +1057,14 @@ pub fn get_function_name(&self, level: i32) -> Option { } /// Promotes a job to the front of the list. - pub fn job_promote_at(&self, job_pos: usize) { + pub fn job_promote_at(&mut self, job_pos: usize) { // Move the job to the beginning. self.jobs_mut().rotate_left(job_pos); } /// Return the job with the specified job ID. If id is 0 or less, return the last job used. pub fn job_with_id(&self, job_id: MaybeJobId) -> Option { - for job in self.jobs().iter() { + for job in self.jobs() { if job_id.is_none() || job_id == job.job_id() { return Some(job.clone()); } @@ -1103,21 +1093,21 @@ pub fn job_get_with_index_from_pid(&self, pid: Pid) -> Option<(usize, JobRef)> { /// Returns a new profile item if profiling is active. The caller should fill it in. /// The Parser will deallocate it. /// If profiling is not active, this returns None. - pub fn create_profile_item(&self) -> Option { + pub fn create_profile_item(&mut self) -> Option { if PROFILING_ACTIVE.load() { - let mut profile_items = self.profile_items.borrow_mut(); + let profile_items = &mut self.profile_items; profile_items.push(ProfileItem::new()); return Some(profile_items.len() - 1); } None } - pub fn profile_items_mut(&self) -> RefMut<'_, Vec> { - self.profile_items.borrow_mut() + pub fn profile_items_mut(&mut self) -> &mut Vec { + &mut self.profile_items } /// Flush profiling data to the given filename. - pub fn flush_profiling(&self, path: &OsStr) { + pub fn flush_profiling(&mut self, path: &OsStr) { // Save profiling information. OK to not use CLO_EXEC here because this is called while fish is // exiting (and hence will not fork). let mut f = match std::fs::File::create(path) { @@ -1134,8 +1124,8 @@ pub fn flush_profiling(&self, path: &OsStr) { return; } }; - let mut profile_items = self.profile_items.borrow_mut(); - print_profile(&profile_items, &mut f); + let profile_items = &mut self.profile_items; + print_profile(&*profile_items, &mut f); profile_items.clear(); } @@ -1214,7 +1204,7 @@ pub fn stack_trace(&self) -> WString { // detect that. .take_while(|b| b.typ() != BlockType::Event) .fold(WString::new(), |mut trace, b| { - append_block_description_to_stack_trace(self, &b, &mut trace, &mut line_cache); + append_block_description_to_stack_trace(self, b, &mut trace, &mut line_cache); trace }) } @@ -1236,12 +1226,12 @@ pub fn function_stack_is_overflowing(&self) -> bool { } /// Mark whether we should sync universal variables. - pub fn set_syncs_uvars(&self, flag: bool) { - self.syncs_uvars.store(flag); + pub fn set_syncs_uvars(&mut self, flag: bool) { + self.syncs_uvars = flag; } /// Return the operation context for this parser. - pub fn context(&self) -> OperationContext<'_> { + pub fn context(&mut self) -> OperationContext<'_> { OperationContext::foreground( self, Box::new(|| signal_check_cancel() != 0), @@ -1254,7 +1244,7 @@ pub fn is_eval_depth_exceeded(&self) -> bool { self.scope().eval_level >= FISH_MAX_EVAL_DEPTH } - pub fn set_color_theme(&self, background_color: Option<&xterm_color::Color>) { + pub fn set_color_theme(&mut self, background_color: Option<&xterm_color::Color>) { let color_theme = match background_color.map(|c| c.perceived_lightness()) { Some(x) if x < 0.5 => L!("dark"), Some(_) => L!("light"), @@ -2112,7 +2102,7 @@ macro_rules! validate { fn test_eval_recursion_detection() { test_init(); // Ensure that we don't crash on infinite self recursion and mutual recursion. - let parser = TestParser::new(); + let parser = &mut TestParser::new(); parser.eval( L!("function recursive ; recursive ; end ; recursive; "), &IoChain::new(), @@ -2131,7 +2121,10 @@ fn test_eval_recursion_detection() { #[serial] fn test_eval_illegal_exit_code() { test_init(); - let parser = TestParser::new(); + let TestParser { + ref mut parser, + ref mut pushed_dirs, + } = TestParser::new(); macro_rules! validate { ($cmd:expr, $result:expr) => { parser.eval($cmd, &IoChain::new()); @@ -2143,21 +2136,21 @@ macro_rules! validate { // We need to be in an empty directory so that none of the wildcards match a file that might be // in the fish source tree. In particular we need to ensure that "?" doesn't match a file // named by a single character. See issue #3852. - parser.pushd("test/temp"); + parser.pushd(pushed_dirs, "test/temp"); validate!(L!("echo -n"), STATUS_CMD_OK.unwrap()); validate!(L!("pwd"), STATUS_CMD_OK.unwrap()); validate!(L!("UNMATCHABLE_WILDCARD*"), STATUS_UNMATCHED_WILDCARD); validate!(L!("UNMATCHABLE_WILDCARD**"), STATUS_UNMATCHED_WILDCARD); validate!(L!("?"), STATUS_UNMATCHED_WILDCARD); validate!(L!("abc?def"), STATUS_UNMATCHED_WILDCARD); - parser.popd(); + parser.popd(pushed_dirs); } #[test] #[serial] fn test_eval_empty_function_name() { test_init(); - let parser = TestParser::new(); + let parser = &mut TestParser::new(); parser.eval( L!("function '' ; echo fail; exit 42 ; end ; ''"), &IoChain::new(), @@ -2168,11 +2161,11 @@ fn test_eval_empty_function_name() { #[serial] fn test_expand_argument_list() { test_init(); - let parser = TestParser::new(); + let parser = &mut TestParser::new(); let comps: Vec = Parser::expand_argument_list( L!("alpha 'beta gamma' delta"), ExpandFlags::default(), - &parser.context(), + &mut parser.context(), ) .into_iter() .map(|c| c.completion) @@ -2180,7 +2173,7 @@ fn test_expand_argument_list() { assert_eq!(comps, &[L!("alpha"), L!("beta gamma"), L!("delta"),]); } - fn test_1_cancellation(parser: &Parser, src: &wstr) { + fn test_1_cancellation(parser: &mut Parser, src: &wstr) { let filler = IoBufferfill::create().unwrap(); let delay = Duration::from_millis(100); #[allow(clippy::unnecessary_cast)] @@ -2210,8 +2203,9 @@ fn test_1_cancellation(parser: &Parser, src: &wstr) { #[serial] fn test_cancellation() { test_init(); - let parser = Parser::new(EnvStack::new(), CancelBehavior::Clear); - let _pop = fake_scoped_reader(&parser); + let parser = &mut Parser::new(EnvStack::new(), CancelBehavior::Clear); + let mut reader = fake_scoped_reader(parser); + let parser = &mut *reader.parser; printf!("Testing Ctrl-C cancellation. If this hangs, that's a bug!\n"); @@ -2223,16 +2217,16 @@ fn test_cancellation() { // Here the command substitution is an infinite loop. echo never even gets its argument, so when // we cancel we expect no output. - test_1_cancellation(&parser, L!("echo (while true ; echo blah ; end)")); + test_1_cancellation(parser, L!("echo (while true ; echo blah ; end)")); // Nasty infinite loop that doesn't actually execute anything. test_1_cancellation( - &parser, + parser, L!("echo (while true ; end) (while true ; end) (while true ; end)"), ); - test_1_cancellation(&parser, L!("while true ; end")); - test_1_cancellation(&parser, L!("while true ; echo nothing > /dev/null; end")); - test_1_cancellation(&parser, L!("for i in (while true ; end) ; end")); + test_1_cancellation(parser, L!("while true ; end")); + test_1_cancellation(parser, L!("while true ; echo nothing > /dev/null; end")); + test_1_cancellation(parser, L!("for i in (while true ; end) ; end")); signal_reset_handlers(); diff --git a/src/proc.rs b/src/proc.rs index e85325223..24ce46a5e 100644 --- a/src/proc.rs +++ b/src/proc.rs @@ -820,7 +820,7 @@ pub fn posts_job_exit_events(&self) -> bool { } /// Run ourselves. Returning once we complete or stop. - pub fn continue_job(&self, parser: &Parser, block_io: Option<&IoChain>) { + pub fn continue_job(&self, parser: &mut Parser, block_io: Option<&IoChain>) { flogf!( proc_job_run, "Run job %d (%s), %s, %s", @@ -989,7 +989,7 @@ pub fn set_job_control_mode(mode: JobControl) { /// Notify the user about stopped or terminated jobs, and delete completed jobs from the job list. /// If `interactive` is set, allow removing interactive jobs; otherwise skip them. /// Return whether text was printed to stdout. -pub fn job_reap(parser: &Parser, interactive: bool, block_io: Option<&IoChain>) -> bool { +pub fn job_reap(parser: &mut Parser, interactive: bool, block_io: Option<&IoChain>) -> bool { // Early out for the common case that there are no jobs. if parser.jobs().is_empty() { return false; @@ -1003,7 +1003,7 @@ pub fn job_reap(parser: &Parser, interactive: bool, block_io: Option<&IoChain>) /// exit. An empty result (common) means no such jobs. pub fn jobs_requiring_warning_on_exit(parser: &Parser) -> JobList { let mut result = vec![]; - for job in parser.jobs().iter() { + for job in parser.jobs() { if !job.is_foreground() && job.is_constructed() && !job.is_completed() { result.push(job.clone()); } @@ -1067,8 +1067,8 @@ pub fn proc_get_jiffies(inpid: Pid) -> ClockTicks { /// Update process time usage for all processes by calling the proc_get_jiffies function for every /// process of every job. -pub fn proc_update_jiffies(parser: &Parser) { - for job in parser.jobs().iter() { +pub fn proc_update_jiffies(parser: &mut Parser) { + for job in parser.jobs() { for p in job.external_procs() { p.last_times.replace(ProcTimes { time: timef(), @@ -1121,7 +1121,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) { +pub fn proc_wait_any(parser: &mut Parser) { process_mark_finished_children(parser, /*block_ok=*/ true, /*block_io=*/ None); let is_interactive = parser.scope().is_interactive; process_clean_after_marking(parser, is_interactive); @@ -1199,13 +1199,13 @@ fn reap_disowned_pids() { /// See if any reapable processes have exited, and mark them accordingly. /// \param block_ok if no reapable processes have exited, block until one is (or until we receive a /// signal). -fn process_mark_finished_children(parser: &Parser, block_ok: bool, block_io: Option<&IoChain>) { +fn process_mark_finished_children(parser: &mut Parser, block_ok: bool, block_io: Option<&IoChain>) { // Get the exit and signal generations of all reapable processes. // The exit generation tells us if we have an exit; the signal generation allows for detecting // SIGHUP and SIGINT. // Go through each process and figure out if and how it wants to be reaped. let mut reapgens = GenerationsList::invalid(); - for j in parser.jobs().iter() { + for j in parser.jobs() { for proc in j.processes().iter() { if !j.can_reap(proc) { continue; @@ -1234,7 +1234,7 @@ fn process_mark_finished_children(parser: &Parser, block_ok: bool, block_io: Opt // Update the hup/int generations and reap any reapable processes. // We structure this as two loops for some simplicity. // First reap all pids. - for j in parser.jobs().iter() { + for j in parser.jobs() { for proc in j.external_procs() { // It's an external proc so it has a pid, but is it reapable? if !j.can_reap(proc) { @@ -1303,7 +1303,7 @@ fn process_mark_finished_children(parser: &Parser, block_ok: bool, block_io: Opt // We are done reaping pids. // Reap internal processes. - for j in parser.jobs().iter() { + for j in parser.jobs() { for proc in j.processes.iter() { // Does this proc have an internal process that is reapable? if proc.internal_proc.borrow().is_none() || !j.can_reap(proc) { @@ -1438,7 +1438,7 @@ fn job_or_proc_wants_summary(j: &Job) -> bool { } /// Invoke the fish_job_summary function by executing the given command. -fn call_job_summary(parser: &Parser, cmd: &wstr) { +fn call_job_summary(parser: &mut Parser, cmd: &wstr) { let event = Event::generic(L!("fish_job_summary").to_owned()); let b = parser.push_block(Block::event_block(event)); let saved_status = parser.last_statuses(); @@ -1500,7 +1500,7 @@ fn summary_command(j: &Job, p: Option<&Process>) -> WString { // Summarize a list of jobs, by emitting calls to fish_job_summary. // Note the given list must NOT be the parser's own job list, since the call to fish_job_summary // could modify it. -fn summarize_jobs(parser: &Parser, jobs: &[JobRef]) -> bool { +fn summarize_jobs(parser: &mut Parser, jobs: &[JobRef]) -> bool { if jobs.is_empty() { return false; } @@ -1554,7 +1554,7 @@ fn save_wait_handle_for_completed_job(job: &Job, store: &mut WaitHandleStore) { /// Remove completed jobs from the job list, printing status messages as appropriate. /// Return whether something was printed. -fn process_clean_after_marking(parser: &Parser, interactive: bool) -> bool { +fn process_clean_after_marking(parser: &mut Parser, interactive: bool) -> bool { // This function may fire an event handler, we do not want to call ourselves recursively (to // avoid infinite recursion). if parser.scope().is_cleaning_procs { @@ -1564,7 +1564,7 @@ fn process_clean_after_marking(parser: &Parser, interactive: bool) -> bool { let _cleaning = parser.push_scope(|s| s.is_cleaning_procs = true); // Remove all disowned jobs. - remove_disowned_jobs(&mut parser.jobs_mut()); + remove_disowned_jobs(parser.jobs_mut()); // Accumulate exit events into a new list, which we fire after the list manipulation is // complete. @@ -1584,7 +1584,7 @@ fn process_clean_after_marking(parser: &Parser, interactive: bool) -> bool { let mut jobs_to_summarize = vec![]; // Handle stopped jobs. These stay in our list. - for j in parser.jobs().iter() { + for j in parser.jobs() { if j.is_stopped() && !j.flags().notified_of_stop && should_process_job(j) @@ -1596,7 +1596,7 @@ fn process_clean_after_marking(parser: &Parser, interactive: bool) -> bool { } // Generate process_exit events for finished processes. - for j in parser.jobs().iter() { + for j in parser.jobs() { generate_process_exit_events(j, &mut exit_events); } @@ -1617,7 +1617,7 @@ fn process_clean_after_marking(parser: &Parser, interactive: bool) -> bool { false }); for j in completed_jobs { - save_wait_handle_for_completed_job(&j, &mut parser.mut_wait_handles()); + save_wait_handle_for_completed_job(&j, parser.mut_wait_handles()); } // Emit calls to fish_job_summary. diff --git a/src/reader/input.rs b/src/reader/input.rs index aa20ab557..92ae44ff1 100644 --- a/src/reader/input.rs +++ b/src/reader/input.rs @@ -2,12 +2,13 @@ use super::{Reader, reader_reading_interrupted, reader_schedule_prompt_repaint}; use crate::{ event, + input::input_get_bind_mode, input_common::{CharEvent, InputData, InputEventQueuer, ReadlineCmd}, proc::job_reap, signal::signal_clear_cancel, }; use fish_common::escape; -use fish_widestring::bytes2wcstring; +use fish_widestring::{WString, bytes2wcstring}; use std::os::fd::RawFd; impl<'a> InputEventQueuer for Reader<'a> { @@ -37,7 +38,7 @@ fn select_interrupted(&mut self) { signal_clear_cancel(); // Fire any pending events and reap stray processes, including printing exit status messages. - let parser = self.parser; + let parser = &mut *self.parser; event::fire_delayed(parser); if job_reap(parser, true, None) { reader_schedule_prompt_repaint(); @@ -76,4 +77,8 @@ fn paste_commit(&mut self) { escape(&bytes2wcstring(&buffer)) ))); } + + fn get_bind_mode(&self) -> WString { + input_get_bind_mode(self.parser.vars()) + } } diff --git a/src/reader/reader.rs b/src/reader/reader.rs index 92e8ecaf9..fb200e25f 100644 --- a/src/reader/reader.rs +++ b/src/reader/reader.rs @@ -277,7 +277,9 @@ pub fn terminal_init(vars: &dyn Environment, inputfd: RawFd) -> TerminalInitResu query_capabilities_via_dcs(&mut out, vars); out.write_command(QueryPrimaryDeviceAttribute); } - input_queue.blocking_query().replace(TerminalQuery::Initial); + input_queue + .blocking_query_mut() + .replace(TerminalQuery::Initial); while !check_exit_loop_maybe_warning(None) { use CharEvent::{Command, Implicit, Key, Readline}; @@ -322,7 +324,7 @@ pub fn terminal_init(vars: &dyn Environment, inputfd: RawFd) -> TerminalInitResu } } - stop_query(input_queue.blocking_query()); + stop_query(input_queue.blocking_query_mut()); let input_data = input_queue.get_input_data(); // We blocked execution of code and mappings so input function args must be empty. @@ -370,13 +372,14 @@ pub fn current_data() -> Option<&'static mut ReaderData> { /// Add a new reader to the reader stack. pub fn reader_push<'a>( - parser: &'a Parser, + parser: &'a mut Parser, history_id: HistoryId, conf: ReaderConfig, ) -> Reader<'a> { assert_is_main_thread(); let inputfd = conf.inputfd; - let input_data = if !parser.interactive_initialized.swap(true) { + let input_data = if !parser.interactive_initialized { + parser.interactive_initialized = true; let TerminalInitResult { mut input_queue, background_color, @@ -392,14 +395,12 @@ pub fn reader_push<'a>( ParserEnvSetMode::new(EnvMode::GLOBAL), L!("fish").to_owned(), ); - let old = parser - .blocking_query_timeout - .replace(input_data.blocking_query_timeout); - assert!(old.is_none()); + assert!(parser.blocking_query_timeout.is_none()); + parser.blocking_query_timeout = input_data.blocking_query_timeout; parser.set_color_theme(background_color.as_ref()); std::mem::take(input_data) } else { - InputData::new(inputfd, *parser.blocking_query_timeout.borrow()) + InputData::new(inputfd, parser.blocking_query_timeout) }; let hist = History::new(history_id); hist.resolve_pending(); @@ -424,7 +425,7 @@ pub fn reader_pop() { } } -pub fn fake_scoped_reader<'a>(parser: &'a Parser) -> impl DerefMut> + 'a { +pub fn fake_scoped_reader<'a>(parser: &'a mut Parser) -> impl DerefMut> + 'a { let inputfd = -1; let conf = ReaderConfig { inputfd, @@ -749,7 +750,7 @@ pub struct ReaderData { /// It also provides access to I/O threads. pub struct Reader<'a> { pub data: &'a mut ReaderData, - pub parser: &'a Parser, + pub parser: &'a mut Parser, } /// Reader dereferences to its referenced ReaderData. @@ -789,13 +790,13 @@ pub(super) fn service_debounced_results(&mut self) { /// Read commands from \c fd until encountering EOF. /// The fd is not closed. -pub fn reader_read(parser: &Parser, fd: RawFd, io: &IoChain) -> Result<(), ErrorCode> { +pub fn reader_read(parser: &mut Parser, fd: RawFd, io: &IoChain) -> Result<(), ErrorCode> { // If reader_read is called recursively through the '.' builtin, we need to preserve // is_interactive. This, and signal handler setup is handled by // proc_push_interactive/proc_pop_interactive. let interactive = (fd == STDIN_FILENO) && isatty(STDIN_FILENO); - let _interactive_push = parser.push_scope(|s| s.is_interactive = interactive); + let _interactive_push = parser.push_scope(move |s| s.is_interactive = interactive); signal_set_handlers_once(interactive); let res = if interactive { @@ -812,7 +813,7 @@ pub fn reader_read(parser: &Parser, fd: RawFd, io: &IoChain) -> Result<(), Error } /// Read interactively. Read input from stdin while providing editing facilities. -fn read_i(parser: &Parser) { +fn read_i(parser: &mut Parser) { assert_is_main_thread(); let mut conf = ReaderConfig { event: L!("fish_prompt"), @@ -833,18 +834,18 @@ fn read_i(parser: &Parser) { conf.right_prompt_cmd = RIGHT_PROMPT_FUNCTION_NAME.to_owned(); } - let mut data = reader_push(parser, history_id(parser.vars()), conf); - data.import_history_if_necessary(); + let mut reader = reader_push(parser, history_id(parser.vars()), conf); + reader.import_history_if_necessary(); // Set up tty protocols. These should be enabled while we're reading interactively, // and disabled before we run fish script, wildcards, or completions. This is scoped. // Note this may be disabled within the loop, e.g. when running fish script bound to keys. let mut tty = TtyHandoff::new(reader_save_screen_state); - while !check_exit_loop_maybe_warning(Some(&mut data)) { + while !check_exit_loop_maybe_warning(Some(&mut reader)) { RUN_COUNT.fetch_add(1, Ordering::Relaxed); - let Some(command) = data.readline(set_shell_modes_temporarily(data.conf.inputfd), None) + let Some(command) = reader.readline(set_shell_modes_temporarily(reader.conf.inputfd), None) else { continue; }; @@ -855,38 +856,42 @@ fn read_i(parser: &Parser) { // Got a command. Disable tty protocols while we execute it. tty.disable_tty_protocols(); - data.clear(EditableLineTag::Commandline); - data.update_buff_pos(EditableLineTag::Commandline, None); + reader.clear(EditableLineTag::Commandline); + reader.update_buff_pos(EditableLineTag::Commandline, None); BufferedOutputter::new(Outputter::stdoutput()).write_command(Osc133CommandStart(&command)); - event::fire_generic(parser, L!("fish_preexec").to_owned(), vec![command.clone()]); - let eval_res = reader_run_command(parser, &command); + event::fire_generic( + reader.parser, + L!("fish_preexec").to_owned(), + vec![command.clone()], + ); + let eval_res = reader_run_command(reader.parser, &command); signal_clear_cancel(); if !eval_res.no_status { STATUS_COUNT.fetch_add(1, Ordering::Relaxed); } // If the command requested an exit, then process it now and clear it. - data.exit_loop_requested |= parser.libdata().exit_current_script; - parser.libdata_mut().exit_current_script = false; + reader.data.exit_loop_requested |= reader.parser.libdata().exit_current_script; + reader.parser.libdata_mut().exit_current_script = false; BufferedOutputter::new(Outputter::stdoutput()).write_command(Osc133CommandFinished { - exit_status: parser.last_status(), + exit_status: reader.parser.last_status(), }); - event::fire_generic(parser, L!("fish_postexec").to_owned(), vec![command]); + event::fire_generic(reader.parser, L!("fish_postexec").to_owned(), vec![command]); // Allow any pending history items to be returned in the history array. - data.history.resolve_pending(); + reader.history.resolve_pending(); // Make cursor visible. Every even vaguely used terminal agrees on this sequence. - data.screen.write_command(DecsetShowCursor); + reader.screen.write_command(DecsetShowCursor); - let already_warned = data.did_warn_for_bg_jobs; - if check_exit_loop_maybe_warning(Some(&mut data)) { + let already_warned = reader.did_warn_for_bg_jobs; + if check_exit_loop_maybe_warning(Some(&mut reader)) { break; } if already_warned { // We had previously warned the user and they ran another command. // Reset the warning. - data.did_warn_for_bg_jobs = false; + reader.did_warn_for_bg_jobs = false; } } reader_pop(); @@ -901,16 +906,16 @@ fn read_i(parser: &Parser) { if reader_data_stack().is_empty() { // Send the exit event and then commit to not executing any more fish script. EXIT_STATE.store(ExitState::RunningHandlers as u8, Ordering::Relaxed); - event::fire_generic(parser, L!("fish_exit").to_owned(), vec![]); + event::fire_generic(reader.parser, L!("fish_exit").to_owned(), vec![]); EXIT_STATE.store(ExitState::FinishedHandlers as u8, Ordering::Relaxed); - hup_jobs(&parser.jobs()); + hup_jobs(reader.parser.jobs()); } } /// Read non-interactively. Read input from stdin without displaying the prompt, using syntax /// highlighting. This is used for reading scripts and init files. /// The file is not closed. -fn read_ni(parser: &Parser, fd: RawFd, io: &IoChain) -> Result<(), ErrorCode> { +fn read_ni(parser: &mut Parser, fd: RawFd, io: &IoChain) -> Result<(), ErrorCode> { let md = match fstat(fd) { Ok(md) => md, Err(err) => { @@ -1130,7 +1135,7 @@ pub fn reader_schedule_prompt_repaint() { data.schedule_prompt_repaint(); } -pub fn reader_update_termsize(parser: &Parser) { +pub fn reader_update_termsize(parser: &mut Parser) { let last = termsize_last(); let new = termsize_update(parser); if new.height() == last.height() { @@ -1143,7 +1148,7 @@ pub fn reader_update_termsize(parser: &Parser) { data.push_front(CharEvent::Implicit(ImplicitEvent::NewWindowHeight)); } -pub fn reader_execute_readline_cmd(parser: &Parser, ch: CharEvent) { +pub fn reader_execute_readline_cmd(parser: &mut Parser, ch: CharEvent) { if parser.scope().readonly_commandline { return; } @@ -1183,7 +1188,7 @@ pub fn reader_jump(direction: JumpDirection, precision: JumpPrecision, target: c data.jump_and_remember_last_jump(direction, precision, elt, target, false) } -pub fn reader_showing_suggestion(parser: &Parser) -> bool { +pub fn reader_showing_suggestion(parser: &mut Parser) -> bool { if !is_interactive_session() { return false; } @@ -1220,7 +1225,7 @@ pub fn reader_reading_interrupted(data: &mut ReaderData) -> i32 { /// than nchars if a single keypress resulted in multiple characters being inserted into the /// commandline. pub fn reader_readline( - parser: &Parser, + parser: &mut Parser, old_modes: Option, nchars: Option, ) -> Option { @@ -1713,7 +1718,7 @@ fn query(&mut self, query_state: RecurrentQuery) { if !querying_allowed(self.vars()) { return; } - let mut query = self.blocking_query(); + let query = self.blocking_query_mut(); assert!(query.is_none()); { let mut out = Outputter::stdoutput().borrow_mut(); @@ -1728,7 +1733,6 @@ fn query(&mut self, query_state: RecurrentQuery) { out.end_buffering(); } *query = Some(TerminalQuery::Recurrent(query_state)); - drop(query); self.save_screen_state(); } @@ -2913,11 +2917,10 @@ fn handle_char_event(&mut self, injected_event: Option) -> ControlFlo } } CharEvent::QueryResult(query_result) => { - let mut maybe_query = self.blocking_query(); - let query = &mut maybe_query; + let query = self.blocking_query_mut(); use QueryResponse::*; use QueryResultEvent::*; - let query = match (&mut **query, query_result) { + let query = match (query, query_result) { (Some(TerminalQuery::Initial), _) => panic!(), ( Some(TerminalQuery::Recurrent(RecurrentQuery { @@ -2944,7 +2947,6 @@ fn handle_char_event(&mut self, injected_event: Option) -> ControlFlo Response(PrimaryDeviceAttribute) | Timeout | Interrupted, ) => { let query = query_state.clone(); - drop(maybe_query); if let Some(cursor_pos_query) = query.cursor_position { let cursor_pos = cursor_pos_query.result; use CursorPositionQueryReason::*; @@ -2968,7 +2970,7 @@ fn handle_char_event(&mut self, injected_event: Option) -> ControlFlo self.parser.set_color_theme(Some(background_color)); } } - self.blocking_query() + self.blocking_query_mut() } // Rogue reply (_, _) => return ControlFlow::Continue(()), @@ -4677,7 +4679,7 @@ fn pager_selection_changed(&mut self) { if let Some(completion) = self.pager.selected_completion(&self.current_page_rendering) { let new_cmd_line = completion_apply_to_command_line( - &OperationContext::background_interruptible(EnvStack::globals()), // To-do: include locals. + &mut OperationContext::background_interruptible(EnvStack::globals()), // To-do: include locals. &completion.completion, completion.flags, &self.cycle_command_line, @@ -5042,11 +5044,11 @@ pub fn fish_is_unwinding_for_exit() -> bool { /// \param reset_cursor_position If set, issue a \r so the line driver knows where we are pub fn reader_write_title( cmd: &wstr, - parser: &Parser, + parser: &mut Parser, reset_cursor_position: bool, /* = true */ ) { fn write_title( - parser: &Parser, + parser: &mut Parser, out: &mut BufferedOutputter, cmd: &wstr, osc: fn(&[WString]) -> TerminalCommand<'_>, @@ -5116,7 +5118,7 @@ fn write_title( } } -fn exec_prompt_cmd(parser: &Parser, prompt_cmd: &wstr, final_prompt: bool) -> Vec { +fn exec_prompt_cmd(parser: &mut Parser, prompt_cmd: &wstr, final_prompt: bool) -> Vec { let mut output = vec![]; let prompt_cmd = if final_prompt && function::exists(prompt_cmd, parser) { Cow::Owned(prompt_cmd.to_owned() + L!(" --final-rendering")) @@ -5163,9 +5165,9 @@ fn exec_prompt(&mut self, full_prompt: bool, final_prompt: bool) { // If the left prompt function is deleted, then use a default prompt instead of // producing an error. let prompt_cmd = if self.conf.left_prompt_cmd != LEFT_PROMPT_FUNCTION_NAME - || function::exists(&self.conf.left_prompt_cmd, self.parser) + || function::exists(&self.data.conf.left_prompt_cmd, self.parser) { - &self.conf.left_prompt_cmd + &self.data.conf.left_prompt_cmd } else { DEFAULT_PROMPT }; @@ -5190,12 +5192,12 @@ fn exec_prompt(&mut self, full_prompt: bool, final_prompt: bool) { // Don't execute the right prompt if it is undefined fish_right_prompt if !self.conf.right_prompt_cmd.is_empty() && (self.conf.right_prompt_cmd != RIGHT_PROMPT_FUNCTION_NAME - || function::exists(&self.conf.right_prompt_cmd, self.parser)) + || function::exists(&self.data.conf.right_prompt_cmd, self.parser)) { // Right prompt does not support multiple lines, so just concatenate all of them. self.right_prompt_buff = WString::from_iter(exec_prompt_cmd( self.parser, - &self.conf.right_prompt_cmd, + &self.data.conf.right_prompt_cmd, final_prompt, )); } @@ -5307,7 +5309,7 @@ fn get_autosuggestion_performer( move || { assert_is_background_thread(); let nothing = AutosuggestionResult::default(); - let ctx = get_bg_context(&vars, generation_count); + let ctx = &mut get_bg_context(&vars, generation_count); if ctx.check_cancel() { return nothing; } @@ -5406,7 +5408,7 @@ fn get_autosuggestion_performer( suggested_range.clone(), item.get_required_paths(), &working_directory, - &ctx, + ctx, ) { // The command autosuggestion was handled specially, so we're done. let is_whole = suggested_range.len() == item.str().len(); @@ -5454,7 +5456,7 @@ fn get_autosuggestion_performer( let complete_flags = CompletionRequestOptions::autosuggest(); let mut would_be_cursor = line_range.end; let (mut completions, needs_load) = - complete(&command_line[..would_be_cursor], complete_flags, &ctx); + complete(&command_line[..would_be_cursor], complete_flags, ctx); let suggestion = if completions.is_empty() { // If there are no completions to suggest, fall back to icase history. @@ -5474,7 +5476,7 @@ fn get_autosuggestion_performer( } let full_line = completion_apply_to_command_line( - &OperationContext::background_interruptible(&vars), + &mut OperationContext::background_interruptible(&vars), &comp.completion, comp.flags, &command_line, @@ -5736,9 +5738,9 @@ fn get_highlight_performer( if text.is_empty() { return HighlightResult::default(); } - let ctx = get_bg_context(&vars, generation_count); + let ctx = &mut get_bg_context(&vars, generation_count); let mut colors = vec![]; - highlight_shell(&text, &mut colors, &ctx, io_ok, Some(position)); + highlight_shell(&text, &mut colors, ctx, io_ok, Some(position)); HighlightResult { colors, text } } } @@ -5992,7 +5994,7 @@ fn expand_replacer( range: SourceRange, token: &wstr, repl: &abbrs::Replacer, - parser: &Parser, + parser: &mut Parser, ) -> Option { if !repl.is_function { // Literal replacement cannot fail. @@ -6117,7 +6119,7 @@ fn extract_tokens(s: &wstr) -> Vec { pub fn reader_expand_abbreviation_at_cursor( cmdline: &wstr, cursor_pos: usize, - parser: &Parser, + parser: &mut Parser, ) -> Option { // Find the token containing the cursor. Usually users edit from the end, so walk backwards. let tokens = extract_tokens(cmdline); @@ -6166,7 +6168,7 @@ impl<'a> Reader<'a> { /// may change the command line but does NOT repaint it. This is to allow the caller to coalesce /// repaints. fn expand_abbreviation_at_cursor(&mut self, cursor_backtrack: usize) -> bool { - let (elt, el) = self.active_edit_line(); + let (elt, el) = self.data.active_edit_line(); if self.conf.expand_abbrev_ok && elt == EditableLineTag::Commandline { // Try expanding abbreviations. let cursor_pos = el.position().saturating_sub(cursor_backtrack); @@ -6363,7 +6365,7 @@ fn check_for_orphaned_process(loop_count: usize, shell_pgid: libc::pid_t) -> boo /// Run the specified command with the correct terminal modes, and while taking care to perform job /// notification, set the title, etc. -fn reader_run_command(parser: &Parser, cmd: &wstr) -> EvalRes { +fn reader_run_command(parser: &mut Parser, cmd: &wstr) -> EvalRes { assert!( !get_tty_protocols_active(), "TTY protocols should not be active" @@ -6464,7 +6466,7 @@ fn import_history_if_necessary(&mut self) { } fn should_add_to_history(&mut self, text: &wstr) -> bool { - let parser = self.parser; + let parser = &mut *self.parser; if !function::exists(L!("fish_should_add_to_history"), parser) { // Historical behavior, if the command starts with a space we don't save it. return text.as_char_slice()[0] != ' '; @@ -6591,7 +6593,7 @@ fn try_expand_wildcard( /// If expansion would exceed this many results, beep and do nothing. const TAB_COMPLETE_WILDCARD_MAX_EXPANSION: usize = 256; - let ctx = OperationContext::background_with_cancel_checker( + let ctx = &mut OperationContext::background_with_cancel_checker( &parser.variables, Box::new(|| signal_check_cancel() != 0), TAB_COMPLETE_WILDCARD_MAX_EXPANSION, @@ -6603,7 +6605,7 @@ fn try_expand_wildcard( | ExpandFlags::SKIP_VARIABLES | ExpandFlags::PRESERVE_HOME_TILDES; let mut expanded = CompletionList::new(); - let ret = expand_string(wc, &mut expanded, flags, &ctx, None); + let ret = expand_string(wc, &mut expanded, flags, ctx, None); if ret.result != ExpandResultCode::ok { return ret.result; } @@ -6724,7 +6726,7 @@ pub(crate) fn get_quote(cmd_str: &wstr, len: usize) -> Option { /// /// Return The completed string pub fn completion_apply_to_command_line( - ctx: &OperationContext, + ctx: &mut OperationContext, val_str: &wstr, flags: CompleteFlags, command_line: &wstr, @@ -6764,7 +6766,7 @@ pub fn completion_apply_to_command_line( escape_flags.insert(EscapeFlags::NO_TILDE); } - let maybe_add_slash = |trailer: &mut char, token: &wstr| { + let mut maybe_add_slash = |trailer: &mut char, token: &wstr| { let mut expanded = token.to_owned(); if expand_one(&mut expanded, ExpandFlags::FAIL_ON_CMDSUBST, ctx, None) && wstat(&expanded).is_ok_and(|md| md.is_dir()) @@ -6940,7 +6942,7 @@ fn compute_and_apply_completions(&mut self, c: ReadlineCmd) { let mut wc_expanded = WString::new(); match try_expand_wildcard( self.parser, - el.text()[token_range.clone()].to_owned(), + self.command_line.text()[token_range.clone()].to_owned(), position_in_token, &mut wc_expanded, ) { @@ -6970,11 +6972,11 @@ fn compute_and_apply_completions(&mut self, c: ReadlineCmd) { // up to the end of the token we're completing. let (mut comp, _needs_load) = { - let cmdsub = &el.text()[cmdsub_range.start..token_range.end]; + let cmdsub = &self.data.command_line.text()[cmdsub_range.start..token_range.end]; complete( cmdsub, CompletionRequestOptions::normal(), - &self.parser.context(), + &mut self.parser.context(), ) }; @@ -7216,7 +7218,7 @@ fn completion_insert( let (_elt, el) = self.active_edit_line(); let mut cursor = el.position(); let new_command_line = completion_apply_to_command_line( - &OperationContext::background_interruptible(self.parser.vars()), + &mut OperationContext::background_interruptible(self.parser.vars()), val, flags, el.text(), @@ -7281,7 +7283,7 @@ fn test_autosuggestion_combining() { #[test] fn test_completion_insertions() { - let parser = TestParser::new(); + let parser = &mut TestParser::new(); macro_rules! validate { ( @@ -7302,8 +7304,8 @@ macro_rules! validate { let mut cursor_pos = in_cursor_pos; let result = completion_apply_to_command_line( - &OperationContext::foreground( - &parser, + &mut OperationContext::foreground( + parser, Box::new(no_cancel), crate::operation_context::EXPANSION_LIMIT_DEFAULT, ), diff --git a/src/termsize.rs b/src/termsize.rs index 9c313d353..f0c681f27 100644 --- a/src/termsize.rs +++ b/src/termsize.rs @@ -167,7 +167,7 @@ pub fn initialize(&self, vars: &dyn Environment) -> Termsize { /// registered for COLUMNS and LINES. /// This requires a shared reference so it can work from a static. /// Return the updated termsize. - fn updating(&self, parser: &Parser) -> Termsize { + fn updating(&self, parser: &mut Parser) -> Termsize { let new_size; let prev_size; @@ -196,7 +196,7 @@ fn updating(&self, parser: &Parser) -> Termsize { new_size } - fn set_columns_lines_vars(&self, val: Termsize, parser: &Parser) { + fn set_columns_lines_vars(&self, val: Termsize, parser: &mut Parser) { let saved = self.setting_env_vars.swap(true, Ordering::Relaxed); parser.set_var_and_fire( L!("COLUMNS"), @@ -251,7 +251,7 @@ pub fn handle_columns_lines_var_change(vars: &dyn Environment) { SHARED_CONTAINER.handle_columns_lines_var_change(vars); } -pub fn termsize_update(parser: &Parser) -> Termsize { +pub fn termsize_update(parser: &mut Parser) -> Termsize { SHARED_CONTAINER.updating(parser) } @@ -273,8 +273,7 @@ mod tests { fn test_termsize() { test_init(); let env_global = EnvSetMode::new(EnvMode::GLOBAL, false); - let parser = TestParser::new(); - let vars = parser.vars(); + let parser = &mut TestParser::new(); // Use a static variable so we can pretend we're the kernel exposing a terminal size. static STUBBY_TERMSIZE: Mutex> = Mutex::new(None); @@ -310,16 +309,18 @@ fn stubby_termsize() -> Option { }; // Ok now we tell it to update. - ts.updating(&parser); + ts.updating(parser); assert_eq!(ts.last(), new_test_termsize(42, 84)); + let vars = parser.vars(); assert_eq!(vars.get(L!("COLUMNS")).unwrap().as_string(), "42"); assert_eq!(vars.get(L!("LINES")).unwrap().as_string(), "84"); // Wow someone set COLUMNS and LINES to a weird value. // Now the tty's termsize doesn't matter. + let vars = parser.vars(); vars.set_one(L!("COLUMNS"), env_global, L!("75").to_owned()); vars.set_one(L!("LINES"), env_global, L!("150").to_owned()); - ts.handle_columns_lines_var_change(parser.vars()); + ts.handle_columns_lines_var_change(vars); assert_eq!(ts.last(), new_test_termsize(75, 150)); assert_eq!(vars.get(L!("COLUMNS")).unwrap().as_string(), "75"); assert_eq!(vars.get(L!("LINES")).unwrap().as_string(), "150"); @@ -331,7 +332,8 @@ fn stubby_termsize() -> Option { // Oh it got SIGWINCH, now the tty matters again. handle_winch(); assert_eq!(ts.last(), new_test_termsize(33, 150)); - assert_eq!(ts.updating(&parser), stubby_termsize().unwrap()); + assert_eq!(ts.updating(parser), stubby_termsize().unwrap()); + let vars = parser.vars(); assert_eq!(vars.get(L!("COLUMNS")).unwrap().as_string(), "42"); assert_eq!(vars.get(L!("LINES")).unwrap().as_string(), "84"); @@ -348,9 +350,9 @@ fn stubby_termsize() -> Option { tty_size_reader: stubby_termsize, }; ts.initialize(parser.vars()); - ts2.updating(&parser); + ts2.updating(parser); assert_eq!(ts.last(), new_test_termsize(83, 38)); handle_winch(); - assert_eq!(ts2.updating(&parser), stubby_termsize().unwrap()); + assert_eq!(ts2.updating(parser), stubby_termsize().unwrap()); } } diff --git a/src/tests/prelude.rs b/src/tests/prelude.rs index 6abd778a7..05c5ba86a 100644 --- a/src/tests/prelude.rs +++ b/src/tests/prelude.rs @@ -8,7 +8,6 @@ use crate::topic_monitor::topic_monitor_init; use crate::wutil::wgetcwd; use crate::{env::EnvStack, proc::proc_init}; -use std::cell::RefCell; use std::collections::HashMap; use std::env::set_current_dir; use std::path::PathBuf; @@ -93,35 +92,42 @@ fn get_names(&self, flags: EnvMode) -> Vec { /// A wrapper around a Parser with some test helpers. pub struct TestParser { - parser: Parser, - pushed_dirs: RefCell>, + pub parser: Parser, + pub pushed_dirs: Vec, } impl TestParser { pub fn new() -> TestParser { TestParser { parser: Parser::new(EnvStack::new(), CancelBehavior::default()), - pushed_dirs: RefCell::new(Vec::new()), + pushed_dirs: Vec::new(), } } +} +pub trait ParserExt { /// Helper to chdir and then update $PWD. - pub fn pushd(&self, path: &str) { + fn pushd(&self, pushed_dirs: &mut Vec, path: &str); + fn popd(&self, pushed_dirs: &mut Vec); +} + +impl ParserExt for Parser { + fn pushd(&self, pushed_dirs: &mut Vec, path: &str) { let cwd = wgetcwd(); - self.pushed_dirs.borrow_mut().push(cwd.to_string()); + pushed_dirs.push(cwd.to_string()); // We might need to create the directory. We don't care if this fails due to the directory // already being present. std::fs::create_dir_all(path).unwrap(); std::env::set_current_dir(path).unwrap(); - self.parser.vars().set_pwd_from_getcwd(); + self.vars().set_pwd_from_getcwd(); } - pub fn popd(&self) { - let old_cwd = self.pushed_dirs.borrow_mut().pop().unwrap(); + fn popd(&self, pushed_dirs: &mut Vec) { + let old_cwd = pushed_dirs.pop().unwrap(); std::env::set_current_dir(old_cwd).unwrap(); - self.parser.vars().set_pwd_from_getcwd(); + self.vars().set_pwd_from_getcwd(); } } @@ -131,3 +137,9 @@ fn deref(&self) -> &Self::Target { &self.parser } } + +impl std::ops::DerefMut for TestParser { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.parser + } +}