Pass Parser by exclusive reference

As described in
https://github.com/fish-shell/fish-shell/pull/9990#discussion_r1382494440,
prior to 77aeb6a2a8 (Port execution, 2023-10-08), "Parser" was
passed by mutable reference ("parser_t&"), even though operation
context was passed as "const operation_context_t &".  This worked
because C++ doesn't propagate const to pointers by default (see
https://en.cppreference.com/cpp/experimental/propagate_const).

	class operation_context_t {
		std::shared_ptr<parser_t> parser;
		...
	};

So "*ctx->parser" was a "parser_t&", not "const parser_t&".

Rust has stricter const propagation rules which means that const
operation context can't simply hand out a non-const reference to parser.

To be able to port code without changing its structure,
77aeb6a2a8 passed "Parser" by shared reference, using interior
mutability (RefCell) to modify parser fields. This is a bit ugly
(c.f. https://doc.rust-lang.org/std/cell/index.html "interior mutability
is something of a last resort") and means that some borrowing conflicts
are not found at compile time but runtime.

Pass both parser and operation context by exclusive reference, and
remove the interior mutability wrappers from parser's fields.
Since "libdata" is no longer inside a "RefCell", add a "ScopedRefCell"
around "transient_commandline".

The downside is that "ScopeGuard" use can become more intrusive
when we pass "Parser" or "OperationContext" as context (especially
when we use "zelf" since we can't shadow "self"), see
* 2930466d53 (Introduce ScopedCell and ScopedRefCell, 2025-03-15)
* 29ae571afa (Make scoped_push nicer, 2024-12-28)
Avoid this in some cases, specifically when using "ScopedCell" or
"ScopedRefCell". Since "&mut Parser" prevents the "ScopeCell"'s
"ScopeGuard" from holding a shared reference, use an "Rc" to capture
a dynamically-checked reference to the Cell. We could also use raw
pointers instead.

Change "Completer::apply_var_assignments" to return  a block ID, to
avoid the need to return a "zelf" "ScopeGuard".  In future, we could
probably untangle completer and get away with returning a "ScopeGuard"
called "ctx".

Closes #12694
This commit is contained in:
Johannes Altmanninger
2026-04-29 14:37:43 +08:00
parent 638777a4de
commit ef8e62727c
88 changed files with 925 additions and 789 deletions

View File

@@ -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<usize>| -> Option<WString> {
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()];

View File

@@ -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)[..];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<usize>,
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 {

View File

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

View File

@@ -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)?;

View File

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

View File

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

View File

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

View File

@@ -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<I>(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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<WString>,
) -> 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<WString, ()> {
// 3,7,command
fn make_pygments_csv(src: &wstr) -> Vec<u8> {
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(),

View File

@@ -174,7 +174,7 @@ fn setup_and_process_keys(
}
fn parse_flags(
parser: Option<&Parser>,
parser: Option<&mut Parser>,
streams: &mut IoStreams,
args: Vec<WString>,
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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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)?;

View File

@@ -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<usize> {
}
}
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<Uid>, gid: Option<Gid>)
}
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);
};

View File

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

View File

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

View File

@@ -10,7 +10,7 @@
static RNG: LazyLock<Mutex<SmallRng>> =
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;

View File

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

View File

@@ -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)?;

View File

@@ -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<BuiltinResult, i32> {
let cmd = args[0];

View File

@@ -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<Option<(Options, usize)>, ErrorCode> {
/// Values used for long-only options.
@@ -376,7 +376,7 @@ fn env_set_reporting_errors(
mode: EnvMode,
list: Vec<WString>,
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<WString> {
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),

View File

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

View File

@@ -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<Self, ErrorCode> {
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 {

View File

@@ -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)?;

View File

@@ -340,7 +340,7 @@ fn iter() -> impl Iterator<Item = std::borrow::Cow<'static, str>> {
}
);
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();

View File

@@ -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<usize, ErrorCode> {
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();

View File

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

View File

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

View File

@@ -61,7 +61,7 @@ fn take_args(
fn handle(
&mut self,
_parser: &Parser,
_parser: &mut Parser,
streams: &mut IoStreams,
optind: &mut usize,
args: &[&wstr],

View File

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

View File

@@ -88,7 +88,7 @@ fn take_args(
fn handle(
&mut self,
parser: &Parser,
parser: &mut Parser,
streams: &mut IoStreams,
optind: &mut usize,
args: &[&wstr],

View File

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

View File

@@ -76,7 +76,7 @@ fn take_args(
fn handle(
&mut self,
_parser: &Parser,
_parser: &mut Parser,
streams: &mut IoStreams,
optind: &mut usize,
args: &[&wstr],

View File

@@ -82,7 +82,7 @@ fn take_args(
fn handle(
&mut self,
_parser: &Parser,
_parser: &mut Parser,
streams: &mut IoStreams,
optind: &mut usize,
args: &[&wstr],

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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");

View File

@@ -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<WaitHandleRef>,
) -> 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<WaitHandleRef> {
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

View File

@@ -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<WString>) -> 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<Mutex<Autoload>> =
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<Completion> {
@@ -1853,21 +1855,10 @@ fn getpwent_name() -> Option<WString> {
/// 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<T: AsRef<wstr>>(
&mut self,
var_assignments: &[T],
) -> Option<ScopeGuard<(), impl FnOnce(()) + 'ctx + use<'ctx, T>>> {
fn apply_var_assignments<T: AsRef<wstr>>(&mut self, var_assignments: &[T]) -> Option<BlockId> {
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<T: AsRef<wstr>>(
// 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<T: AsRef<wstr>>(
} 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<T: AsRef<wstr>>(
}
}
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<usize> {
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<Completion>, Vec<WString>) {
// 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<Completion> { 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, &[]);

View File

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

View File

@@ -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<WString>) {
pub fn fire_generic(parser: &mut Parser, name: WString, arguments: Vec<WString>) {
fire(
parser,
Event {

View File

@@ -83,7 +83,7 @@ fn exec_thread_pool() -> &'static Arc<ThreadPool> {
/// 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<WString>>,
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<WString>,
) -> 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<u8>,
@@ -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<WString>,
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<ProcPerformer> {
@@ -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<Pr
// thread.
let argv = p.argv().clone();
Box::new(
move |parser: &Parser,
move |parser: &mut Parser,
output_stream: Option<&mut OutputStream>,
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<Pr
/// Executes a builtin "process".
fn exec_builtin_process(
parser: &Parser,
parser: &mut Parser,
j: &Job,
p: &Process,
io_chain: &IoChain,
@@ -1242,7 +1242,7 @@ struct PartialPipes {
/// An error return here indicates that the process failed to launch, and the rest of
/// the pipeline should be cancelled.
fn exec_process_in_job(
parser: &Parser,
parser: &mut Parser,
p: &Process,
j: &Job,
block_io: IoChain,
@@ -1324,7 +1324,7 @@ fn exec_process_in_job(
if !p.variable_assignments.is_empty() {
block_id = Some(parser.push_block(Block::variable_assignment_block()));
}
let _pop_block = ScopeGuard::new((), |()| {
let parser = &mut **ScopeGuard::new(parser, |parser| {
if let Some(block_id) = block_id {
parser.pop_block(block_id);
}
@@ -1419,7 +1419,7 @@ fn abort_pipeline_from(job: &Job, offset: usize) {
// Given that we are about to execute an exec() call, check if the parser is interactive and there
// are extant background jobs. If so, warn the user and do not exec().
// Return true if we should allow exec, false to disallow it.
fn allow_exec_with_background_jobs(parser: &Parser) -> 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<WString>, buffer: &SeparatedBuffer, sp
/// of $status.
fn exec_subshell_internal(
cmd: &wstr,
parser: &Parser,
parser: &mut Parser,
job_group: Option<&JobGroupRef>,
lst: Option<&mut Vec<WString>>,
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);
}

View File

@@ -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<WString>>,
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<WString>,
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<WString> = (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]

View File

@@ -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<Arc<FunctionProperties>> {
}
/// Return the properties for a function, or None, perhaps triggering autoloading.
pub fn get_props_autoload(name: &wstr, parser: &Parser) -> Option<Arc<FunctionProperties>> {
pub fn get_props_autoload(name: &wstr, parser: &mut Parser) -> Option<Arc<FunctionProperties>> {
if parser_keywords_is_reserved(name) {
return None;
}
@@ -223,7 +223,7 @@ pub fn get_props_autoload(name: &wstr, parser: &Parser) -> Option<Arc<FunctionPr
/// Returns true if the function named `cmd` exists.
/// This may autoload.
pub fn exists(cmd: &wstr, parser: &Parser) -> 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) {

View File

@@ -48,15 +48,15 @@ pub struct PathFlags {
/// The result of a file test.
pub type FileTestResult = Result<IsFile, IsErr>;
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.

View File

@@ -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<HighlightSpec>,
ctx: &OperationContext<'_>,
ctx: &'ctx mut OperationContext<'src>,
io_ok: bool, /* = false */
cursor: Option<usize>,
) {
@@ -107,7 +107,10 @@ pub fn highlight_shell(
*color = highlighter.highlight();
}
pub fn highlight_and_colorize(text: &wstr, ctx: &OperationContext<'_>) -> Vec<u8> {
pub fn highlight_and_colorize<'src, 'ctx>(
text: &'src wstr,
ctx: &'ctx mut OperationContext<'src>,
) -> Vec<u8> {
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<usize>,
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<HighlightSpec>;
/// 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<usize>,
// 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<usize>,
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<WString> {
// 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<WString>| {

View File

@@ -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<History>,
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<Self>,
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<P: IntoIterator<Item = WString>>(
) -> Vec<WString> {
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<P: IntoIterator<Item = WString>>(
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<P: IntoIterator<Item = WString>>(
/// 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();

View File

@@ -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<InputMapping> {
let bind_mode = input_get_bind_mode(vars);
pub fn find_mapping<'a>(&mut self, ip: &'a InputMappingSet) -> Option<InputMapping> {
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<ReadlineCmd> {
#[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();

View File

@@ -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<Duration>,
// If set, events will be buffered until the query finishes.
pub blocking_query: RefCell<Option<TerminalQuery>>,
pub blocking_query: Option<TerminalQuery>,
}
impl InputData {
@@ -685,7 +684,7 @@ pub fn new(in_fd: RawFd, blocking_query_timeout: Option<Duration>) -> 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<TerminalQuery>> {
self.get_input_data().blocking_query.borrow_mut()
fn blocking_query(&self) -> &Option<TerminalQuery> {
&self.get_input_data().blocking_query
}
fn blocking_query_mut(&mut self) -> &mut Option<TerminalQuery> {
&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<TerminalQuery>>) -> bool {
pub(crate) fn stop_query(query: &mut Option<TerminalQuery>) -> bool {
query.take().is_some()
}

View File

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

View File

@@ -1291,7 +1291,7 @@ fn process_completions_into_infos(lst: &[Completion]) -> Vec<PagerComp> {
highlight_shell(
&comp.completion,
&mut comp_info.colors,
&OperationContext::empty(),
&mut OperationContext::empty(),
false,
None,
);

View File

@@ -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<BlockId>,
) -> 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<BlockId>,
) -> 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<EndExecutionReason> {
fn check_end_execution(&self, ctx: &mut OperationContext<'_>) -> Option<EndExecutionReason> {
// 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<EndExecution
fn report_errors(
&self,
ctx: &OperationContext<'_>,
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<Option<&'a ast::DecoratedStatement>> {
// 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<WString>,
@@ -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<BlockId>,
@@ -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<BlockId>,
) -> 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<BlockId>,
) -> 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<BlockId>,
@@ -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<WString>,
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<BlockId>,
) -> 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<BlockId>,
) -> 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<BlockId>,
) -> 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<BlockId>,
) -> 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<BlockId>,
) -> 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<BlockId>,
@@ -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) {

View File

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

View File

@@ -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<WString>,
pub transient_commandline: ScopedRefCell<Option<WString>>,
/// 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<Option<FilenameRef>>,
@@ -387,16 +384,16 @@ pub struct Parser {
current_node: ScopedRefCell<Option<NodeRef<ast::JobPipeline>>>,
/// The jobs associated with this parser.
job_list: RefCell<JobList>,
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<WaitHandleStore>,
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<Vec<Block>>,
block_list: Vec<Block>,
/// Set of variables for the parser.
pub variables: EnvStack,
@@ -405,23 +402,23 @@ pub struct Parser {
scoped_data: ScopedCell<ScopedData>,
/// Miscellaneous library data.
pub library_data: ScopedRefCell<LibraryData>,
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<Vec<ProfileItem>>,
profile_items: Vec<ProfileItem>,
/// Global event blocks.
pub global_event_blocks: AtomicU64,
pub global_event_blocks: u64,
// Timeout for blocking terminal queries.
pub blocking_query_timeout: RefCell<Option<Duration>>,
pub blocking_query_timeout: Option<Duration>,
}
#[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<WString>,
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<T: Node>(
&self,
&mut self,
node: &NodeRef<T>,
block_io: &IoChain,
job_group: Option<&JobGroupRef>,
@@ -637,8 +634,7 @@ pub fn eval_node<T: 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<T: 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<T: 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<T: 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<Option<NodeRef<ast::JobPipeline>>> {
&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<NodeRef<ast::JobPipeline>> {
self.current_node.borrow().clone()
pub fn current_node(&mut self) -> &ScopedRefCell<Option<NodeRef<ast::JobPipeline>>> {
&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<Item = Ref<'a, Block>> {
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<Item = &'a Block> {
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<Ref<'_, Block>> {
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<RefMut<'_, Block>> {
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<F: FnOnce(&mut ScopedData)>(&self, modifier: F) -> impl DerefMut + use<F> {
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<WString>,
@@ -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<WString>,
@@ -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<WString> {
}
/// 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<JobRef> {
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<usize> {
pub fn create_profile_item(&mut self) -> Option<usize> {
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<ProfileItem>> {
self.profile_items.borrow_mut()
pub fn profile_items_mut(&mut self) -> &mut Vec<ProfileItem> {
&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<WString> = 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();

View File

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

View File

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

View File

@@ -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<Target = Reader<'a>> + 'a {
pub fn fake_scoped_reader<'a>(parser: &'a mut Parser) -> impl DerefMut<Target = Reader<'a>> + '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<Termios>,
nchars: Option<NonZeroUsize>,
) -> Option<WString> {
@@ -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<CharEvent>) -> 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<CharEvent>) -> 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<CharEvent>) -> 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<WString> {
fn exec_prompt_cmd(parser: &mut Parser, prompt_cmd: &wstr, final_prompt: bool) -> Vec<WString> {
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<abbrs::Replacement> {
if !repl.is_function {
// Literal replacement cannot fail.
@@ -6117,7 +6119,7 @@ fn extract_tokens(s: &wstr) -> Vec<PositionedToken> {
pub fn reader_expand_abbreviation_at_cursor(
cmdline: &wstr,
cursor_pos: usize,
parser: &Parser,
parser: &mut Parser,
) -> Option<abbrs::Replacement> {
// 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<char> {
///
/// 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,
),

View File

@@ -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<Option<Termsize>> = Mutex::new(None);
@@ -310,16 +309,18 @@ fn stubby_termsize() -> Option<Termsize> {
};
// 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<Termsize> {
// 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<Termsize> {
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());
}
}

View File

@@ -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<WString> {
/// A wrapper around a Parser with some test helpers.
pub struct TestParser {
parser: Parser,
pushed_dirs: RefCell<Vec<String>>,
pub parser: Parser,
pub pushed_dirs: Vec<String>,
}
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<String>, path: &str);
fn popd(&self, pushed_dirs: &mut Vec<String>);
}
impl ParserExt for Parser {
fn pushd(&self, pushed_dirs: &mut Vec<String>, 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<String>) {
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
}
}