Local panels are fs.watched (LocalDirWatcher + setWatchedDirs -> pathRefresh);
remote panels poll every 3s. New refresh({silent}) skips the loading/busy
flags and touches no DOM, snapshotting sessionId+cwd to discard results if the
user navigated mid-flight. Only the active tab is live; switching tabs
refreshes the new pair. A future on/off setting is left as commented
breadcrumbs. Docs updated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
65 lines
1.9 KiB
TypeScript
65 lines
1.9 KiB
TypeScript
import { type FSWatcher, watch } from "node:fs";
|
|
|
|
const DEBOUNCE_MS = 150;
|
|
|
|
/**
|
|
* Watches a set of local directories with node's fs.watch and fires onChange
|
|
* (debounced per dir — fs.watch emits bursts) when their contents change. Used
|
|
* to auto-refresh the active tab's local panels instantly; SFTP has no
|
|
* equivalent, so remote panels are polled UI-side instead.
|
|
*
|
|
* onChange is late-bound (assigned after the RPC exists, like AppDeps.events)
|
|
* so it can push pathRefresh to the main webview.
|
|
*/
|
|
export class LocalDirWatcher {
|
|
onChange: (dir: string) => void = () => {};
|
|
private readonly handles = new Map<string, FSWatcher>();
|
|
private readonly debounces = new Map<string, ReturnType<typeof setTimeout>>();
|
|
|
|
/** Replace the watched set: drop handles no longer wanted, open new ones. */
|
|
setWatched(dirs: string[]): void {
|
|
const wanted = new Set(dirs);
|
|
for (const [dir, handle] of this.handles) {
|
|
if (!wanted.has(dir)) {
|
|
handle.close();
|
|
this.handles.delete(dir);
|
|
}
|
|
}
|
|
for (const dir of wanted) {
|
|
if (this.handles.has(dir)) continue;
|
|
try {
|
|
const handle = watch(dir, () => this.fire(dir));
|
|
// a watched dir that gets deleted/unmounted errors — just drop it
|
|
handle.on("error", () => this.drop(dir));
|
|
this.handles.set(dir, handle);
|
|
} catch {
|
|
// dir vanished between listing and watching — ignore
|
|
}
|
|
}
|
|
}
|
|
|
|
private fire(dir: string): void {
|
|
const pending = this.debounces.get(dir);
|
|
if (pending) clearTimeout(pending);
|
|
this.debounces.set(
|
|
dir,
|
|
setTimeout(() => {
|
|
this.debounces.delete(dir);
|
|
this.onChange(dir);
|
|
}, DEBOUNCE_MS),
|
|
);
|
|
}
|
|
|
|
private drop(dir: string): void {
|
|
this.handles.get(dir)?.close();
|
|
this.handles.delete(dir);
|
|
}
|
|
|
|
dispose(): void {
|
|
for (const handle of this.handles.values()) handle.close();
|
|
this.handles.clear();
|
|
for (const pending of this.debounces.values()) clearTimeout(pending);
|
|
this.debounces.clear();
|
|
}
|
|
}
|