From 459f3c0c287eb294ba168202ab4deb4752b0d59d Mon Sep 17 00:00:00 2001 From: Luca Scherzer <83914554+lucascherzer@users.noreply.github.com> Date: Tue, 29 Jul 2025 16:10:40 +0200 Subject: [PATCH] feat(watch): implement --debounce flag with duration (#16187) - fixes #16178 - `watch --debounce-ms` deprecated Co-authored-by: Luca Scherzer --- SECURITY.md | 4 +- crates/nu-command/src/filesystem/watch.rs | 64 ++++++++++++++++++++--- crates/nu-protocol/src/value/mod.rs | 1 + toolkit.nu | 12 ++--- 4 files changed, 66 insertions(+), 15 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 3f161a77f9..230c3951c3 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,6 +1,6 @@ # 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. 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 -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). Please try to answer the following questions: - How can we reach you for further questions? diff --git a/crates/nu-command/src/filesystem/watch.rs b/crates/nu-command/src/filesystem/watch.rs index 081054c6c0..cde5f91b8f 100644 --- a/crates/nu-command/src/filesystem/watch.rs +++ b/crates/nu-command/src/filesystem/watch.rs @@ -6,7 +6,11 @@ use notify_debouncer_full::{ }, }; 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::{ path::PathBuf, sync::mpsc::{RecvTimeoutError, channel}, @@ -33,6 +37,16 @@ impl Command for Watch { vec!["watcher", "reload", "filesystem"] } + fn deprecation_info(&self) -> Vec { + 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 { Signature::build("watch") .input_output_types(vec![(Type::Nothing, Type::table())]) @@ -43,7 +57,13 @@ impl Command for Watch { .named( "debounce-ms", 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'), ) .named( @@ -95,11 +115,25 @@ impl Command for Watch { let quiet = call.has_flag(engine_state, stack, "quiet")?; - let debounce_duration_flag: Option> = + let debounce_duration_flag_ms: Option> = call.get_flag(engine_state, stack, "debounce-ms")?; - let debounce_duration = match debounce_duration_flag { - Some(val) => match u64::try_from(val.item) { - Ok(val) => Duration::from_millis(val), + + let debounce_duration_flag: Option> = + 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(_) => { return Err(ShellError::TypeMismatch { 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> = 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 }"#, 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 { 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 }"#, diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 3bc633f302..ae590167f0 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -89,6 +89,7 @@ pub enum Value { internal_span: Span, }, Duration { + /// The duration in nanoseconds. val: i64, /// note: spans are being refactored out of Value /// please use .span() instead of matching this span value diff --git a/toolkit.nu b/toolkit.nu index 9ce6ea3535..446355ccd1 100644 --- a/toolkit.nu +++ b/toolkit.nu @@ -307,13 +307,13 @@ export def "check pr" [ export def run [ --experimental-options: oneof, string> # enable or disable experimental options ] { - let experimental_options_arg = $experimental_options - | default [] - | [$in] - | flatten - | str join "," + let experimental_options_arg = $experimental_options + | default [] + | [$in] + | flatten + | str join "," | $"[($in)]" - + ^cargo run -- ...[ --experimental-options $experimental_options_arg -e "$env.PROMPT_COMMAND_RIGHT = $'(ansi magenta_reverse)trying Nushell inside Cargo(ansi reset)'"