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.
This commit is contained in:
Fabian Boehm 2023-01-10 17:17:18 +01:00
parent 403920e9d6
commit f1e19884fb
4 changed files with 63 additions and 6 deletions

View file

@ -9,7 +9,7 @@ Synopsis
.. synopsis:: .. synopsis::
set 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 [VALUE ...]
set [-Uflg] NAME[[INDEX ...]] [VALUE ...] set [-Uflg] NAME[[INDEX ...]] [VALUE ...]
set (-a | --append) [-flgU] NAME 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. 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 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** **-L** or **--long**
Do not abbreviate long values when printing set variables. Do not abbreviate long values when printing set variables.

View file

@ -43,6 +43,7 @@ struct Options {
append: bool, append: bool,
prepend: bool, prepend: bool,
preserve_failure_exit_status: bool, preserve_failure_exit_status: bool,
no_event: bool,
} }
impl Default for Options { impl Default for Options {
@ -65,6 +66,7 @@ impl Default for Options {
append: false, append: false,
prepend: false, prepend: false,
preserve_failure_exit_status: true, preserve_failure_exit_status: true,
no_event: false,
} }
} }
} }
@ -98,6 +100,7 @@ impl Options {
/// Values used for long-only options. /// Values used for long-only options.
const PATH_ARG: char = 1 as char; const PATH_ARG: char = 1 as char;
const UNPATH_ARG: char = 2 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 "+" // 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 // (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. // 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!("prepend"), NoArgument, 'p'),
wopt(L!("path"), NoArgument, PATH_ARG), wopt(L!("path"), NoArgument, PATH_ARG),
wopt(L!("unpath"), NoArgument, UNPATH_ARG), wopt(L!("unpath"), NoArgument, UNPATH_ARG),
wopt(L!("no-event"), NoArgument, NO_EVENT_ARG),
wopt(L!("help"), NoArgument, 'h'), wopt(L!("help"), NoArgument, 'h'),
]; ];
@ -148,6 +152,7 @@ impl Options {
'u' => opts.unexport = true, 'u' => opts.unexport = true,
PATH_ARG => opts.pathvar = true, PATH_ARG => opts.pathvar = true,
UNPATH_ARG => opts.unpathvar = true, UNPATH_ARG => opts.unpathvar = true,
NO_EVENT_ARG => opts.no_event = true,
'U' => opts.universal = true, 'U' => opts.universal = true,
'L' => opts.shorten_ok = false, 'L' => opts.shorten_ok = false,
'S' => { 'S' => {
@ -341,13 +346,18 @@ fn handle_env_return(retval: EnvStackSetResult, cmd: &wstr, key: &wstr, streams:
/// description of the problem to stderr. /// description of the problem to stderr.
fn env_set_reporting_errors( fn env_set_reporting_errors(
cmd: &wstr, cmd: &wstr,
opts: &Options,
key: &wstr, key: &wstr,
scope: EnvMode, scope: EnvMode,
list: Vec<WString>, list: Vec<WString>,
streams: &mut IoStreams, streams: &mut IoStreams,
parser: &Parser, parser: &Parser,
) -> EnvStackSetResult { ) -> 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. // If this returned OK, the parser already fired the event.
handle_env_return(retval, cmd, key, streams); handle_env_return(retval, cmd, key, streams);
retval retval
@ -776,7 +786,7 @@ fn erase(
if retval != EnvStackSetResult::ENV_NOT_FOUND { if retval != EnvStackSetResult::ENV_NOT_FOUND {
handle_env_return(retval, cmd, split.varname, streams); 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())); event::fire(parser, Event::variable_erase(split.varname.to_owned()));
} }
} else { } else {
@ -785,8 +795,15 @@ fn erase(
return STATUS_CMD_ERROR; return STATUS_CMD_ERROR;
}; };
let result = erased_at_indexes(var.as_list().to_owned(), split.indexes); let result = erased_at_indexes(var.as_list().to_owned(), split.indexes);
retval = retval = env_set_reporting_errors(
env_set_reporting_errors(cmd, split.varname, scope, result, streams, parser); cmd,
opts,
split.varname,
scope,
result,
streams,
parser,
);
} }
// Set $status to the last error value. // 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. // 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 { if retval == EnvStackSetResult::ENV_OK {
warn_if_uvar_shadows_global(cmd, opts, split.varname, streams, parser); warn_if_uvar_shadows_global(cmd, opts, split.varname, streams, parser);

View file

@ -805,6 +805,11 @@ impl Parser {
res res
} }
/// Cover of vars().set(), without firing events
pub fn set_var(&self, key: &wstr, mode: EnvMode, vals: Vec<WString>) -> EnvStackSetResult {
self.vars().set(key, mode, vals)
}
/// Update any universal variables and send event handlers. /// 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 /// 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. /// changes from other fish instances); otherwise only sync if this instance has changed uvars.

View file

@ -965,4 +965,33 @@ set -e undefined[..1]
set -l negative_oob 1 2 3 set -l negative_oob 1 2 3
set -q negative_oob[-10..1] 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 exit 0