fix(macos): detect untracked file deletion via Finder as remove event
Finder moves files to Trash (rename) instead of deleting them, which may be reported as `Modify(Name)` rather than `Remove` by the file watcher. This caused untracked files to lose their red color without being removed from the tree. Fix by checking disk existence in the event batch emitter and promoting the kind to "remove" when a changed path no longer exists. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
268b33d2ee
commit
273a8cd7ff
@@ -2466,16 +2466,6 @@ impl WatchEventBatch {
|
||||
!self.overflowed && self.changed_paths.is_empty()
|
||||
}
|
||||
|
||||
fn kind(&self) -> &'static str {
|
||||
if self.has_remove {
|
||||
"remove"
|
||||
} else if self.has_create {
|
||||
"create"
|
||||
} else {
|
||||
"modify"
|
||||
}
|
||||
}
|
||||
|
||||
fn ingest_event(&mut self, root_canonical: &Path, event: notify::Event) {
|
||||
if !should_emit_watch_event(&event.kind) {
|
||||
return;
|
||||
@@ -2521,7 +2511,7 @@ impl WatchEventBatch {
|
||||
}
|
||||
}
|
||||
|
||||
fn emit(&self, emitter: &EventEmitter, root_display: &str) {
|
||||
fn emit(&self, emitter: &EventEmitter, root_display: &str, root_canonical: &Path) {
|
||||
if self.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -2534,6 +2524,24 @@ impl WatchEventBatch {
|
||||
paths
|
||||
};
|
||||
|
||||
// On macOS, Finder trash (move-to-trash) may be reported as a rename
|
||||
// (`Modify(Name)`) instead of `Remove`, so `has_remove` is never set.
|
||||
// Detect this by checking whether any changed path no longer exists on
|
||||
// disk and promote the event kind to "remove" accordingly.
|
||||
let has_missing_path = !self.has_remove
|
||||
&& !self.overflowed
|
||||
&& self
|
||||
.changed_paths
|
||||
.iter()
|
||||
.any(|p| !root_canonical.join(p).exists());
|
||||
let kind = if self.has_remove || has_missing_path {
|
||||
"remove"
|
||||
} else if self.has_create {
|
||||
"create"
|
||||
} else {
|
||||
"modify"
|
||||
};
|
||||
|
||||
let payload = FileTreeChangedEvent {
|
||||
root_path: root_display.to_string(),
|
||||
refresh_git_status: if self.overflowed {
|
||||
@@ -2542,7 +2550,7 @@ impl WatchEventBatch {
|
||||
should_refresh_git_status_for_paths(root_display, &changed_paths)
|
||||
},
|
||||
changed_paths,
|
||||
kind: self.kind().to_string(),
|
||||
kind: kind.to_string(),
|
||||
full_reload: self.overflowed,
|
||||
};
|
||||
|
||||
@@ -2585,21 +2593,21 @@ fn run_file_watch_event_loop(
|
||||
};
|
||||
|
||||
if should_flush {
|
||||
batch.emit(&emitter, &root_display);
|
||||
batch.emit(&emitter, &root_display, &root_canonical);
|
||||
batch.clear();
|
||||
batch_started_at = None;
|
||||
}
|
||||
}
|
||||
Err(mpsc::RecvTimeoutError::Timeout) => {
|
||||
if !batch.is_empty() {
|
||||
batch.emit(&emitter, &root_display);
|
||||
batch.emit(&emitter, &root_display, &root_canonical);
|
||||
batch.clear();
|
||||
batch_started_at = None;
|
||||
}
|
||||
}
|
||||
Err(mpsc::RecvTimeoutError::Disconnected) => {
|
||||
if !batch.is_empty() {
|
||||
batch.emit(&emitter, &root_display);
|
||||
batch.emit(&emitter, &root_display, &root_canonical);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user