fix(watcher): close deleted opened documents

This commit is contained in:
2026-05-20 06:38:21 -04:00
parent 14749a6449
commit 3f3cb4cdbf
2 changed files with 147 additions and 18 deletions

View File

@@ -5,8 +5,9 @@
import * as fs from "node:fs";
import * as net from "node:net";
import * as path from "node:path";
import type { FileSystemWatcher } from "vscode-languageserver-protocol";
import { LspClient } from "./client.ts";
import { findRoot, findServerById, pathToUri } from "./root.ts";
import { findRoot, findServerById, pathToUri, uriToPath } from "./root.ts";
import type { ServerConfig } from "./types.ts";
import { WorkspaceWatcher, type FileEvent } from "./watcher.ts";
import {
@@ -21,6 +22,7 @@ import {
const DEFAULT_IDLE_TTL_MS = 5 * 60 * 1000;
const WATCHER_READY_TIMEOUT_MS = 5000;
const FILE_CHANGE_DELETED = 3;
const WATCH_KIND_DELETE = 4;
// Client Entry - One LspClient per (server.id, rootDir), plus the bookkeeping
// needed to keep files in sync and evict on idleness.
@@ -110,23 +112,34 @@ async function getOrCreateEntry(
// Attach Watcher - Registration can happen during initialize, before the daemon subscribes.
async function attachWatcher(entry: ClientEntry): Promise<void> {
if (process.env.PI_LSP_DISABLE_WATCHERS) return;
const sync = async () => {
const patterns = entry.client.getFileWatchers();
if (patterns.length === 0 && !entry.watcher) return;
if (!entry.watcher) {
entry.watcher = new WorkspaceWatcher(entry.rootDir, (events) =>
forwardEvents(entry, events),
);
log(`watcher`, entry.server.id, entry.rootDir, `patterns=${patterns.length}`);
}
if (process.env.LSP_DEBUG) {
log(`watcher patterns`, entry.server.id, JSON.stringify(patterns));
}
entry.watcher.setPatterns(patterns);
if (patterns.length > 0) await waitForWatcherReady(entry);
};
entry.unsubscribeWatchers = entry.client.onWatchersChanged(() => void sync());
await sync();
entry.unsubscribeWatchers = entry.client.onWatchersChanged(() => void refreshWatcher(entry));
await refreshWatcher(entry);
}
function watcherPatterns(entry: ClientEntry): FileSystemWatcher[] {
const registered = entry.client.getFileWatchers();
const openedDeletes = [...entry.opened.keys()].map((uri) => ({
globPattern: uriToPath(uri).split(path.sep).join("/"),
kind: WATCH_KIND_DELETE,
}));
return [...registered, ...openedDeletes];
}
async function refreshWatcher(entry: ClientEntry): Promise<void> {
if (process.env.PI_LSP_DISABLE_WATCHERS) return;
const patterns = watcherPatterns(entry);
if (patterns.length === 0 && !entry.watcher) return;
if (!entry.watcher) {
entry.watcher = new WorkspaceWatcher(entry.rootDir, (events) =>
forwardEvents(entry, events),
);
log(`watcher`, entry.server.id, entry.rootDir, `patterns=${patterns.length}`);
}
if (process.env.LSP_DEBUG) {
log(`watcher patterns`, entry.server.id, JSON.stringify(patterns));
}
entry.watcher.setPatterns(patterns);
if (patterns.length > 0) await waitForWatcherReady(entry);
}
async function waitForWatcherReady(entry: ClientEntry): Promise<void> {
@@ -157,6 +170,7 @@ function forwardEvents(entry: ClientEntry, events: FileEvent[]): void {
if (event.type !== FILE_CHANGE_DELETED || !entry.opened.has(event.uri)) continue;
entry.client.closeDocument(event.uri);
entry.opened.delete(event.uri);
void refreshWatcher(entry);
}
if (process.env.LSP_DEBUG) {
log(`watcher fire`, entry.server.id, JSON.stringify(events));
@@ -206,6 +220,7 @@ async function syncFile(
if (prev === undefined) {
entry.client.openDocument(filePath);
entry.opened.set(uri, stat.mtimeMs);
await refreshWatcher(entry);
return { uri, changed: true };
} else if (prev !== stat.mtimeMs) {
entry.client.notifyChange(filePath);