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(); private readonly debounces = new Map>(); /** 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(); } }