From f1e19884fb4137468a870cb15d751c9359c75002 Mon Sep 17 00:00:00 2001 From: Fabian Boehm Date: Tue, 10 Jan 2023 17:17:18 +0100 Subject: [PATCH] Add set --no-event This allows running `set` without triggering any event handlers. That is useful, for example, if you want to set a variable in an event handler for that variable - we could do it, for example, in the fish_user_path or fish_key_bindings handlers. This is something the `block` builtin was supposed to be for, but it never really worked because it only allows suppressing the event for the duration, they would fire later. See #9030. Because it is possible to abuse this, we only have a long-option so that people see what is up. --- doc_src/cmds/set.rst | 7 ++++++- src/builtins/set.rs | 28 +++++++++++++++++++++++----- src/parser.rs | 5 +++++ tests/checks/set.fish | 29 +++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 6 deletions(-) diff --git a/doc_src/cmds/set.rst b/doc_src/cmds/set.rst index 463815557..643b72d4d 100644 --- a/doc_src/cmds/set.rst +++ b/doc_src/cmds/set.rst @@ -9,7 +9,7 @@ Synopsis .. synopsis:: set - set (-f | --function) (-l | --local) (-g | --global) (-U | --universal) + set (-f | --function) (-l | --local) (-g | --global) (-U | --universal) [--no-event] set [-Uflg] NAME [VALUE ...] set [-Uflg] NAME[[INDEX ...]] [VALUE ...] set (-a | --append) [-flgU] NAME VALUE ... @@ -102,6 +102,11 @@ Further options: It shows the scopes the given variables are set in, along with the values in each and whether or not it is exported. No other flags can be used with this option. +**--no-event** + Don't generate a variable change event when setting or erasing a variable. + We recommend using this carefully because the event handlers are usually set up for a reason. + Possible uses include modifying the variable inside a variable handler. + **-L** or **--long** Do not abbreviate long values when printing set variables. diff --git a/src/builtins/set.rs b/src/builtins/set.rs index 621206a77..54574e19c 100644 --- a/src/builtins/set.rs +++ b/src/builtins/set.rs @@ -43,6 +43,7 @@ struct Options { append: bool, prepend: bool, preserve_failure_exit_status: bool, + no_event: bool, } impl Default for Options { @@ -65,6 +66,7 @@ impl Default for Options { append: false, prepend: false, preserve_failure_exit_status: true, + no_event: false, } } } @@ -98,6 +100,7 @@ impl Options { /// Values used for long-only options. const PATH_ARG: char = 1 as char; const UNPATH_ARG: char = 2 as char; + const NO_EVENT_ARG: char = 3 as char; // Variables used for parsing the argument list. This command is atypical in using the "+" // (REQUIRE_ORDER) option for flag parsing. This is not typical of most fish commands. It means // we stop scanning for flags when the first non-flag argument is seen. @@ -118,6 +121,7 @@ impl Options { wopt(L!("prepend"), NoArgument, 'p'), wopt(L!("path"), NoArgument, PATH_ARG), wopt(L!("unpath"), NoArgument, UNPATH_ARG), + wopt(L!("no-event"), NoArgument, NO_EVENT_ARG), wopt(L!("help"), NoArgument, 'h'), ]; @@ -148,6 +152,7 @@ impl Options { 'u' => opts.unexport = true, PATH_ARG => opts.pathvar = true, UNPATH_ARG => opts.unpathvar = true, + NO_EVENT_ARG => opts.no_event = true, 'U' => opts.universal = true, 'L' => opts.shorten_ok = false, 'S' => { @@ -341,13 +346,18 @@ fn handle_env_return(retval: EnvStackSetResult, cmd: &wstr, key: &wstr, streams: /// description of the problem to stderr. fn env_set_reporting_errors( cmd: &wstr, + opts: &Options, key: &wstr, scope: EnvMode, list: Vec, streams: &mut IoStreams, parser: &Parser, ) -> EnvStackSetResult { - let retval = parser.set_var_and_fire(key, scope | EnvMode::USER, list); + let retval = if opts.no_event { + parser.set_var(key, scope | EnvMode::USER, list) + } else { + parser.set_var_and_fire(key, scope | EnvMode::USER, list) + }; // If this returned OK, the parser already fired the event. handle_env_return(retval, cmd, key, streams); retval @@ -776,7 +786,7 @@ fn erase( if retval != EnvStackSetResult::ENV_NOT_FOUND { handle_env_return(retval, cmd, split.varname, streams); } - if retval == EnvStackSetResult::ENV_OK { + if retval == EnvStackSetResult::ENV_OK && !opts.no_event { event::fire(parser, Event::variable_erase(split.varname.to_owned())); } } else { @@ -785,8 +795,15 @@ fn erase( return STATUS_CMD_ERROR; }; let result = erased_at_indexes(var.as_list().to_owned(), split.indexes); - retval = - env_set_reporting_errors(cmd, split.varname, scope, result, streams, parser); + retval = env_set_reporting_errors( + cmd, + opts, + split.varname, + scope, + result, + streams, + parser, + ); } // Set $status to the last error value. @@ -951,7 +968,8 @@ fn set_internal( }; // Set the value back in the variable stack and fire any events. - let retval = env_set_reporting_errors(cmd, split.varname, scope, new_values, streams, parser); + let retval = + env_set_reporting_errors(cmd, opts, split.varname, scope, new_values, streams, parser); if retval == EnvStackSetResult::ENV_OK { warn_if_uvar_shadows_global(cmd, opts, split.varname, streams, parser); diff --git a/src/parser.rs b/src/parser.rs index c5bc1b97e..b61eb4dcc 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -805,6 +805,11 @@ impl Parser { res } + /// Cover of vars().set(), without firing events + pub fn set_var(&self, key: &wstr, mode: EnvMode, vals: Vec) -> EnvStackSetResult { + self.vars().set(key, mode, vals) + } + /// 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. diff --git a/tests/checks/set.fish b/tests/checks/set.fish index 2236b221b..a3c9c5daa 100644 --- a/tests/checks/set.fish +++ b/tests/checks/set.fish @@ -965,4 +965,33 @@ set -e undefined[..1] set -l negative_oob 1 2 3 set -q negative_oob[-10..1] +# --no-event + +function onevent --on-variable nonevent + echo ONEVENT $argv $nonevent +end + +set -g nonevent bar +set -e nonevent + +# CHECK: ONEVENT VARIABLE SET nonevent bar +# CHECK: ONEVENT VARIABLE ERASE nonevent + +set -g --no-event nonevent 2 +set -e --no-event nonevent +set -S nonevent + +set -g --no-event nonevent 3 +set -e nonevent +# CHECK: ONEVENT VARIABLE ERASE nonevent + +set -g nonevent 4 +# CHECK: ONEVENT VARIABLE SET nonevent 4 +set -e --no-event nonevent + +set -l nonevent 4 +set -e nonevent +# CHECK: ONEVENT VARIABLE SET nonevent +# CHECK: ONEVENT VARIABLE ERASE nonevent + exit 0