feat(watch): implement --debounce flag with duration (#16187)

- fixes #16178
- `watch --debounce-ms` deprecated

Co-authored-by: Luca Scherzer <luca.scherzer@de.clara.net>
This commit is contained in:
Luca Scherzer
2025-07-29 16:10:40 +02:00
committed by GitHub
parent 2e4900f085
commit 459f3c0c28
4 changed files with 66 additions and 15 deletions

View File

@ -1,6 +1,6 @@
# Security Policy # Security Policy
As a shell and programming language Nushell provides you with great powers and the potential to do dangerous things to your computer and data. Whenever there is a risk that a malicious actor can abuse a bug or a violation of documented behavior/assumptions in Nushell to harm you this is a *security* risk. As a shell and programming language Nushell provides you with great powers and the potential to do dangerous things to your computer and data. Whenever there is a risk that a malicious actor can abuse a bug or a violation of documented behavior/assumptions in Nushell to harm you this is a *security* risk.
We want to fix those issues without exposing our users to unnecessary risk. Thus we want to explain our security policy. We want to fix those issues without exposing our users to unnecessary risk. Thus we want to explain our security policy.
Additional issues may be part of *safety* where the behavior of Nushell as designed and implemented can cause unintended harm or a bug causes damage without the involvement of a third party. Additional issues may be part of *safety* where the behavior of Nushell as designed and implemented can cause unintended harm or a bug causes damage without the involvement of a third party.
@ -11,7 +11,7 @@ Only if you provide a strong reasoning and the necessary resources, will we cons
## Reporting a Vulnerability ## Reporting a Vulnerability
If you suspect that a bug or behavior of Nushell can affect security or may be potentially exploitable, please report the issue to us in private. If you suspect that a bug or behavior of Nushell can affect security or may be potentially exploitable, please report the issue to us in private.
Either reach out to the core team on [our Discord server](https://discord.gg/NtAbbGn) to arrange a private channel or use the [GitHub vulnerability reporting form](https://github.com/nushell/nushell/security/advisories/new). Either reach out to the core team on [our Discord server](https://discord.gg/NtAbbGn) to arrange a private channel or use the [GitHub vulnerability reporting form](https://github.com/nushell/nushell/security/advisories/new).
Please try to answer the following questions: Please try to answer the following questions:
- How can we reach you for further questions? - How can we reach you for further questions?

View File

@ -6,7 +6,11 @@ use notify_debouncer_full::{
}, },
}; };
use nu_engine::{ClosureEval, command_prelude::*}; use nu_engine::{ClosureEval, command_prelude::*};
use nu_protocol::{engine::Closure, report_shell_error, shell_error::io::IoError}; use nu_protocol::{
DeprecationEntry, DeprecationType, ReportMode, engine::Closure, report_shell_error,
shell_error::io::IoError,
};
use std::{ use std::{
path::PathBuf, path::PathBuf,
sync::mpsc::{RecvTimeoutError, channel}, sync::mpsc::{RecvTimeoutError, channel},
@ -33,6 +37,16 @@ impl Command for Watch {
vec!["watcher", "reload", "filesystem"] vec!["watcher", "reload", "filesystem"]
} }
fn deprecation_info(&self) -> Vec<DeprecationEntry> {
vec![DeprecationEntry {
ty: DeprecationType::Flag("--debounce-ms".into()),
report_mode: ReportMode::FirstUse,
since: Some("0.107.0".into()),
expected_removal: Some("0.109.0".into()),
help: Some("`--debounce-ms` will be removed in favour of `--debounce`".into()),
}]
}
fn signature(&self) -> nu_protocol::Signature { fn signature(&self) -> nu_protocol::Signature {
Signature::build("watch") Signature::build("watch")
.input_output_types(vec![(Type::Nothing, Type::table())]) .input_output_types(vec![(Type::Nothing, Type::table())])
@ -43,7 +57,13 @@ impl Command for Watch {
.named( .named(
"debounce-ms", "debounce-ms",
SyntaxShape::Int, SyntaxShape::Int,
"Debounce changes for this many milliseconds (default: 100). Adjust if you find that single writes are reported as multiple events", "Debounce changes for this many milliseconds (default: 100). Adjust if you find that single writes are reported as multiple events (deprecated)",
None,
)
.named(
"debounce",
SyntaxShape::Duration,
"Debounce changes for this duration (default: 100ms). Adjust if you find that single writes are reported as multiple events",
Some('d'), Some('d'),
) )
.named( .named(
@ -95,11 +115,25 @@ impl Command for Watch {
let quiet = call.has_flag(engine_state, stack, "quiet")?; let quiet = call.has_flag(engine_state, stack, "quiet")?;
let debounce_duration_flag: Option<Spanned<i64>> = let debounce_duration_flag_ms: Option<Spanned<i64>> =
call.get_flag(engine_state, stack, "debounce-ms")?; call.get_flag(engine_state, stack, "debounce-ms")?;
let debounce_duration = match debounce_duration_flag {
Some(val) => match u64::try_from(val.item) { let debounce_duration_flag: Option<Spanned<Value>> =
Ok(val) => Duration::from_millis(val), call.get_flag(engine_state, stack, "debounce")?;
let debounce_duration: Duration = match (debounce_duration_flag, debounce_duration_flag_ms)
{
(None, None) => DEFAULT_WATCH_DEBOUNCE_DURATION,
(Some(l), Some(r)) => {
return Err(ShellError::IncompatibleParameters {
left_message: "Here".to_string(),
left_span: l.span,
right_message: "and here".to_string(),
right_span: r.span,
});
}
(None, Some(val)) => match u64::try_from(val.item) {
Ok(v) => Duration::from_millis(v),
Err(_) => { Err(_) => {
return Err(ShellError::TypeMismatch { return Err(ShellError::TypeMismatch {
err_message: "Debounce duration is invalid".to_string(), err_message: "Debounce duration is invalid".to_string(),
@ -107,7 +141,18 @@ impl Command for Watch {
}); });
} }
}, },
None => DEFAULT_WATCH_DEBOUNCE_DURATION, (Some(v), None) => {
let Value::Duration { val, .. } = v.item else {
return Err(ShellError::TypeMismatch {
err_message: "Debounce duration must be a duration".to_string(),
span: v.item.span(),
});
};
Duration::from_nanos(u64::try_from(val).map_err(|_| ShellError::TypeMismatch {
err_message: "Debounce duration is invalid".to_string(),
span: v.item.span(),
})?)
}
}; };
let glob_flag: Option<Spanned<String>> = call.get_flag(engine_state, stack, "glob")?; let glob_flag: Option<Spanned<String>> = call.get_flag(engine_state, stack, "glob")?;
@ -294,6 +339,11 @@ impl Command for Watch {
example: r#"watch /foo/bar { |op, path| $"($op) - ($path)(char nl)" | save --append changes_in_bar.log }"#, example: r#"watch /foo/bar { |op, path| $"($op) - ($path)(char nl)" | save --append changes_in_bar.log }"#,
result: None, result: None,
}, },
Example {
description: "Print file changes with a debounce time of 5 minutes",
example: r#"watch /foo/bar --debounce 5min { |op, path| $"Registered ($op) on ($path)" | print }"#,
result: None,
},
Example { Example {
description: "Note: if you are looking to run a command every N units of time, this can be accomplished with a loop and sleep", description: "Note: if you are looking to run a command every N units of time, this can be accomplished with a loop and sleep",
example: r#"loop { command; sleep duration }"#, example: r#"loop { command; sleep duration }"#,

View File

@ -89,6 +89,7 @@ pub enum Value {
internal_span: Span, internal_span: Span,
}, },
Duration { Duration {
/// The duration in nanoseconds.
val: i64, val: i64,
/// note: spans are being refactored out of Value /// note: spans are being refactored out of Value
/// please use .span() instead of matching this span value /// please use .span() instead of matching this span value

View File

@ -307,13 +307,13 @@ export def "check pr" [
export def run [ export def run [
--experimental-options: oneof<list<string>, string> # enable or disable experimental options --experimental-options: oneof<list<string>, string> # enable or disable experimental options
] { ] {
let experimental_options_arg = $experimental_options let experimental_options_arg = $experimental_options
| default [] | default []
| [$in] | [$in]
| flatten | flatten
| str join "," | str join ","
| $"[($in)]" | $"[($in)]"
^cargo run -- ...[ ^cargo run -- ...[
--experimental-options $experimental_options_arg --experimental-options $experimental_options_arg
-e "$env.PROMPT_COMMAND_RIGHT = $'(ansi magenta_reverse)trying Nushell inside Cargo(ansi reset)'" -e "$env.PROMPT_COMMAND_RIGHT = $'(ansi magenta_reverse)trying Nushell inside Cargo(ansi reset)'"