refactor: use Effect debounce for LSP cleanup

This commit is contained in:
Kit Langton
2026-04-13 13:57:17 -04:00
parent d5d9260383
commit 06c6babb1b

View File

@@ -12,7 +12,7 @@ import { Instance } from "../project/instance"
import { Flag } from "@/flag/flag"
import { Process } from "../util/process"
import { spawn as lspspawn } from "./launch"
import { Effect, Layer, Context } from "effect"
import { Effect, Layer, Context, PubSub, Stream } from "effect"
import { InstanceState } from "@/effect/instance-state"
import { Filesystem } from "@/util/filesystem"
@@ -139,10 +139,10 @@ export namespace LSP {
clients: LSPClient.Info[]
servers: Record<string, LSPServer.Info>
broken: Set<string>
pulse: PubSub.PubSub<void>
pruning: Promise<void> | undefined
spawning: Map<string, Promise<LSPClient.Info | undefined>>
subs: Map<string, { sub: FSWatcher; names: Set<string> }>
timer: ReturnType<typeof setTimeout> | undefined
}
export interface Interface {
@@ -217,19 +217,25 @@ export namespace LSP {
clients: [],
servers,
broken: new Set(),
pulse: yield* PubSub.unbounded<void>(),
pruning: undefined,
spawning: new Map(),
subs: new Map(),
timer: undefined,
}
yield* Stream.fromPubSub(s.pulse).pipe(
Stream.debounce("50 millis"),
Stream.runForEach(() => Effect.promise(() => scan(s))),
Effect.forkScoped,
)
yield* Effect.addFinalizer(() =>
Effect.promise(async () => {
if (s.timer) clearTimeout(s.timer)
Effect.gen(function* () {
yield* PubSub.shutdown(s.pulse).pipe(Effect.ignore)
for (const item of s.subs.values()) {
item.sub.close()
}
await Promise.all(s.clients.map((client) => client.shutdown()))
yield* Effect.promise(() => Promise.all(s.clients.map((client) => client.shutdown())))
}),
)
@@ -363,7 +369,7 @@ export namespace LSP {
const name = String(file)
if (!s.subs.get(dir)?.names.has(name)) return
}
kick(s)
fire(s)
}),
)
sub.on(
@@ -372,7 +378,7 @@ export namespace LSP {
if (s.subs.get(dir)?.sub !== sub) return
s.subs.delete(dir)
sub.close()
kick(s)
fire(s)
}),
)
s.subs.set(dir, { sub, names })
@@ -380,12 +386,8 @@ export namespace LSP {
}
}
function kick(s: State) {
if (s.timer) clearTimeout(s.timer)
s.timer = setTimeout(() => {
s.timer = undefined
void scan(s)
}, 50)
function fire(s: State) {
Effect.runFork(PubSub.publish(s.pulse, undefined).pipe(Effect.ignore))
}
async function scan(s: State) {