mirror of
https://github.com/nushell/nushell.git
synced 2025-06-30 22:50:14 +02:00
Dependency update: update notify version to v5 (#8114)
# Description Relative: #8060 While investigating, I found we need to update notify, which is a good step to remove some duplicate dependencies. As title, here are some goods and bads after updating: ## Good keep dependency up to date, and remove duplidate dependency(cfg-if, winapi) in Cargo.lock. ## Bad Introduce some breaking changes: After updating to notify v5, I found that we have to remove `Rename` events. But I've testing under notify v4, and it doesn't work good if we running the following command on MacOS: ``` touch a mv a b ``` It fires file create event, but no file rename event. So `rename` event is not really reliable, so I think it's ok for us to remove `Rename` events. The reason to remove `--debounce-ms` flag: It's not provided by defualt file watcher, we can use [PollWatcher](https://docs.rs/notify/latest/notify/poll/struct.PollWatcher.html), but it scans filesystem, which is really expensive. So I just remove the flag. # User-Facing Changes 1. `--debounce-ms` flag is removed 2. no longer watch `Rename` event. # Tests + Formatting Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass # After Submitting If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date.
This commit is contained in:
@ -61,7 +61,7 @@ lscolors = { version = "0.12.0", features = ["crossterm"], default-features = fa
|
||||
md5 = { package = "md-5", version = "0.10.0" }
|
||||
mime = "0.3.16"
|
||||
mime_guess = "2.0.4"
|
||||
notify = "4.0.17"
|
||||
notify = "5.1.0"
|
||||
num = { version = "0.4.0", optional = true }
|
||||
num-traits = "0.2.14"
|
||||
once_cell = "1.17"
|
||||
|
@ -2,7 +2,7 @@ use std::path::PathBuf;
|
||||
use std::sync::mpsc::{channel, RecvTimeoutError};
|
||||
use std::time::Duration;
|
||||
|
||||
use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
|
||||
use notify::{recommended_watcher, EventKind, RecursiveMode, Watcher};
|
||||
use nu_engine::{current_dir, eval_block, CallExt};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Closure, Command, EngineState, Stack, StateWorkingSet};
|
||||
@ -13,7 +13,6 @@ use nu_protocol::{
|
||||
|
||||
// durations chosen mostly arbitrarily
|
||||
const CHECK_CTRL_C_FREQUENCY: Duration = Duration::from_millis(100);
|
||||
const DEFAULT_WATCH_DEBOUNCE_DURATION: Duration = Duration::from_millis(100);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Watch;
|
||||
@ -38,12 +37,6 @@ impl Command for Watch {
|
||||
.required("closure",
|
||||
SyntaxShape::Closure(Some(vec![SyntaxShape::String, SyntaxShape::String, SyntaxShape::String])),
|
||||
"Some Nu code to run whenever a file changes. The closure will be passed `operation`, `path`, and `new_path` (for renames only) arguments in that order")
|
||||
.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",
|
||||
Some('d'),
|
||||
)
|
||||
.named(
|
||||
"glob",
|
||||
SyntaxShape::String, // SyntaxShape::GlobPattern gets interpreted relative to cwd, so use String instead
|
||||
@ -91,22 +84,6 @@ impl Command for Watch {
|
||||
.clone();
|
||||
|
||||
let verbose = call.has_flag("verbose");
|
||||
|
||||
let debounce_duration_flag: Option<Spanned<i64>> =
|
||||
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),
|
||||
Err(_) => {
|
||||
return Err(ShellError::TypeMismatch(
|
||||
"Debounce duration is invalid".to_string(),
|
||||
val.span,
|
||||
))
|
||||
}
|
||||
},
|
||||
None => DEFAULT_WATCH_DEBOUNCE_DURATION,
|
||||
};
|
||||
|
||||
let glob_flag: Option<Spanned<String>> = call.get_flag(engine_state, stack, "glob")?;
|
||||
let glob_pattern = match glob_flag {
|
||||
Some(glob) => {
|
||||
@ -144,7 +121,7 @@ impl Command for Watch {
|
||||
let ctrlc_ref = &engine_state.ctrlc.clone();
|
||||
let (tx, rx) = channel();
|
||||
|
||||
let mut watcher: RecommendedWatcher = match Watcher::new(tx, debounce_duration) {
|
||||
let mut watcher = match recommended_watcher(tx) {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
return Err(ShellError::IOError(format!(
|
||||
@ -152,104 +129,83 @@ impl Command for Watch {
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = watcher.watch(path.clone(), recursive_mode) {
|
||||
if let Err(e) = watcher.watch(&path, recursive_mode) {
|
||||
return Err(ShellError::IOError(format!("Failed to start watcher: {e}")));
|
||||
}
|
||||
|
||||
eprintln!("Now watching files at {path:?}. Press ctrl+c to abort.");
|
||||
|
||||
let event_handler =
|
||||
|operation: &str, path: PathBuf, new_path: Option<PathBuf>| -> Result<(), ShellError> {
|
||||
let glob_pattern = glob_pattern.clone();
|
||||
let matches_glob = match glob_pattern.clone() {
|
||||
Some(glob) => glob.matches_path(&path),
|
||||
None => true,
|
||||
};
|
||||
if verbose && glob_pattern.is_some() {
|
||||
eprintln!("Matches glob: {matches_glob}");
|
||||
}
|
||||
|
||||
if matches_glob {
|
||||
let stack = &mut stack.clone();
|
||||
|
||||
if let Some(position) = block.signature.get_positional(0) {
|
||||
if let Some(position_id) = &position.var_id {
|
||||
stack.add_var(*position_id, Value::string(operation, call.span()));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(position) = block.signature.get_positional(1) {
|
||||
if let Some(position_id) = &position.var_id {
|
||||
stack.add_var(
|
||||
*position_id,
|
||||
Value::string(path.to_string_lossy(), call.span()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(position) = block.signature.get_positional(2) {
|
||||
if let Some(position_id) = &position.var_id {
|
||||
stack.add_var(
|
||||
*position_id,
|
||||
Value::string(
|
||||
new_path.unwrap_or_else(|| "".into()).to_string_lossy(),
|
||||
call.span(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let eval_result = eval_block(
|
||||
engine_state,
|
||||
stack,
|
||||
&block,
|
||||
Value::Nothing { span: call.span() }.into_pipeline_data(),
|
||||
call.redirect_stdout,
|
||||
call.redirect_stderr,
|
||||
);
|
||||
|
||||
match eval_result {
|
||||
Ok(val) => {
|
||||
val.print(engine_state, stack, false, false)?;
|
||||
}
|
||||
Err(err) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
eprintln!("{}", format_error(&working_set, &err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
let event_handler = |operation: &str, path: PathBuf| -> Result<(), ShellError> {
|
||||
let glob_pattern = glob_pattern.clone();
|
||||
let matches_glob = match glob_pattern.clone() {
|
||||
Some(glob) => glob.matches_path(&path),
|
||||
None => true,
|
||||
};
|
||||
if verbose && glob_pattern.is_some() {
|
||||
eprintln!("Matches glob: {matches_glob}");
|
||||
}
|
||||
|
||||
if matches_glob {
|
||||
let stack = &mut stack.clone();
|
||||
|
||||
if let Some(position) = block.signature.get_positional(0) {
|
||||
if let Some(position_id) = &position.var_id {
|
||||
stack.add_var(*position_id, Value::string(operation, call.span()));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(position) = block.signature.get_positional(1) {
|
||||
if let Some(position_id) = &position.var_id {
|
||||
stack.add_var(
|
||||
*position_id,
|
||||
Value::string(path.to_string_lossy(), call.span()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let eval_result = eval_block(
|
||||
engine_state,
|
||||
stack,
|
||||
&block,
|
||||
Value::Nothing { span: call.span() }.into_pipeline_data(),
|
||||
call.redirect_stdout,
|
||||
call.redirect_stderr,
|
||||
);
|
||||
|
||||
match eval_result {
|
||||
Ok(val) => {
|
||||
val.print(engine_state, stack, false, false)?;
|
||||
}
|
||||
Err(err) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
eprintln!("{}", format_error(&working_set, &err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
loop {
|
||||
match rx.recv_timeout(CHECK_CTRL_C_FREQUENCY) {
|
||||
Ok(event) => {
|
||||
Ok(Ok(mut event)) => {
|
||||
if verbose {
|
||||
eprintln!("{event:?}");
|
||||
}
|
||||
let handler_result = match event {
|
||||
DebouncedEvent::Create(path) => event_handler("Create", path, None),
|
||||
DebouncedEvent::Write(path) => event_handler("Write", path, None),
|
||||
DebouncedEvent::Remove(path) => event_handler("Remove", path, None),
|
||||
DebouncedEvent::Rename(path, new_path) => {
|
||||
event_handler("Rename", path, Some(new_path))
|
||||
}
|
||||
DebouncedEvent::Error(err, path) => match path {
|
||||
Some(path) => Err(ShellError::IOError(format!(
|
||||
"Error detected for {path:?}: {err:?}"
|
||||
))),
|
||||
None => Err(ShellError::IOError(format!("Error detected: {err:?}"))),
|
||||
},
|
||||
// These are less likely to be interesting events
|
||||
DebouncedEvent::Chmod(_)
|
||||
| DebouncedEvent::NoticeRemove(_)
|
||||
| DebouncedEvent::NoticeWrite(_)
|
||||
| DebouncedEvent::Rescan => Ok(()),
|
||||
let path = match event.paths.pop() {
|
||||
None => continue,
|
||||
Some(p) => p,
|
||||
};
|
||||
handler_result?;
|
||||
match event.kind {
|
||||
EventKind::Create(_) => event_handler("Create", path),
|
||||
EventKind::Modify(notify::event::ModifyKind::Data(_)) => {
|
||||
event_handler("Write", path)
|
||||
}
|
||||
EventKind::Remove(_) => event_handler("Remove", path),
|
||||
_ => Ok(()),
|
||||
}?
|
||||
}
|
||||
Ok(Err(e)) => return Err(ShellError::IOError(format!("watch error: {e}"))),
|
||||
Err(RecvTimeoutError::Disconnected) => {
|
||||
return Err(ShellError::IOError(
|
||||
"Unexpected disconnect from file watcher".into(),
|
||||
@ -274,7 +230,7 @@ impl Command for Watch {
|
||||
},
|
||||
Example {
|
||||
description: "Watch all changes in the current directory",
|
||||
example: r#"watch . { |op, path, new_path| $"($op) ($path) ($new_path)"}"#,
|
||||
example: r#"watch . { |op, path| $"($op) ($path)"}"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
|
Reference in New Issue
Block a user