From 43fb93bb95b42d1554d20464da627233d7793b14 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 13 Apr 2026 13:19:20 -0400 Subject: [PATCH] fix: avoid Windows LSP cleanup test flake --- packages/opencode/src/lsp/index.ts | 62 ++++++++++++------- .../test/fixture/lsp/fake-lsp-server.js | 7 +++ 2 files changed, 47 insertions(+), 22 deletions(-) diff --git a/packages/opencode/src/lsp/index.ts b/packages/opencode/src/lsp/index.ts index bd07956265..589aa7326b 100644 --- a/packages/opencode/src/lsp/index.ts +++ b/packages/opencode/src/lsp/index.ts @@ -118,6 +118,8 @@ export namespace LSP { SymbolKind.Enum, ] + const key = (id: string, root: string) => `${id}\0${root}` + const filterExperimentalServers = (servers: Record) => { if (Flag.OPENCODE_EXPERIMENTAL_LSP_TY) { if (servers["pyright"]) { @@ -139,7 +141,7 @@ export namespace LSP { broken: Set pruning: Promise | undefined spawning: Map> - subs: Map + subs: Map }> timer: ReturnType | undefined } @@ -224,8 +226,8 @@ export namespace LSP { yield* Effect.addFinalizer(() => Effect.promise(async () => { if (s.timer) clearTimeout(s.timer) - for (const sub of s.subs.values()) { - sub.close() + for (const item of s.subs.values()) { + item.sub.close() } await Promise.all(s.clients.map((client) => client.shutdown())) }), @@ -288,7 +290,8 @@ export namespace LSP { const root = await server.root(file) if (!root) continue - if (s.broken.has(root + server.id)) continue + const id = key(server.id, root) + if (s.broken.has(id)) continue const match = s.clients.find((x) => x.root === root && x.serverID === server.id) if (match) { @@ -296,7 +299,7 @@ export namespace LSP { continue } - const inflight = s.spawning.get(root + server.id) + const inflight = s.spawning.get(id) if (inflight) { const client = await inflight if (!client) continue @@ -304,12 +307,12 @@ export namespace LSP { continue } - const task = schedule(server, root, root + server.id) - s.spawning.set(root + server.id, task) + const task = schedule(server, root, id) + s.spawning.set(id, task) task.finally(() => { - if (s.spawning.get(root + server.id) === task) { - s.spawning.delete(root + server.id) + if (s.spawning.get(id) === task) { + s.spawning.delete(id) } }) @@ -330,34 +333,49 @@ export namespace LSP { }) function sync(s: State) { - const next = new Set(s.clients.map((client) => path.dirname(client.root))) + const next = new Map>() - for (const [dir, sub] of s.subs) { - if (next.has(dir)) continue - s.subs.delete(dir) - sub.close() + for (const client of s.clients) { + const dir = path.dirname(client.root) + const names = next.get(dir) ?? new Set() + names.add(path.basename(client.root)) + next.set(dir, names) } - for (const dir of next) { - if (s.subs.has(dir)) continue + for (const [dir, item] of s.subs) { + if (next.has(dir)) continue + s.subs.delete(dir) + item.sub.close() + } + + for (const [dir, names] of next) { + const existing = s.subs.get(dir) + if (existing) { + existing.names = names + continue + } try { const sub = fswatch( dir, { persistent: false }, - Instance.bind(() => { + Instance.bind((_, file) => { + if (file) { + const name = String(file) + if (!s.subs.get(dir)?.names.has(name)) return + } kick(s) }), ) sub.on( "error", Instance.bind(() => { - if (s.subs.get(dir) !== sub) return + if (s.subs.get(dir)?.sub !== sub) return s.subs.delete(dir) sub.close() kick(s) }), ) - s.subs.set(dir, sub) + s.subs.set(dir, { sub, names }) } catch {} } } @@ -381,8 +399,8 @@ export namespace LSP { ).filter((client): client is LSPClient.Info => Boolean(client)) if (!dead.length) return - const ids = new Set(dead.map((client) => `${client.serverID}:${client.root}`)) - s.clients = s.clients.filter((client) => !ids.has(`${client.serverID}:${client.root}`)) + const ids = new Set(dead.map((client) => key(client.serverID, client.root))) + s.clients = s.clients.filter((client) => !ids.has(key(client.serverID, client.root))) sync(s) await Promise.all(dead.map((client) => client.shutdown().catch(() => undefined))) await Bus.publish(Event.Updated, {}) @@ -432,7 +450,7 @@ export namespace LSP { if (server.extensions.length && !server.extensions.includes(extension)) continue const root = await server.root(file) if (!root) continue - if (s.broken.has(root + server.id)) continue + if (s.broken.has(key(server.id, root))) continue return true } return false diff --git a/packages/opencode/test/fixture/lsp/fake-lsp-server.js b/packages/opencode/test/fixture/lsp/fake-lsp-server.js index 3a0e092e0d..c26e560a66 100644 --- a/packages/opencode/test/fixture/lsp/fake-lsp-server.js +++ b/packages/opencode/test/fixture/lsp/fake-lsp-server.js @@ -3,9 +3,16 @@ const fs = require("fs") const net = require("net") +const path = require("path") const mark = process.argv[2] +if (mark) { + try { + process.chdir(path.dirname(mark)) + } catch {} +} + function writeMark() { if (!mark) return try {