fix(lsp): preserve instance ref for update events (#28016)

This commit is contained in:
Shoubhit Dash
2026-05-17 16:37:00 +05:30
committed by GitHub
parent 53e89f9d52
commit e4cc4e1682
2 changed files with 39 additions and 3 deletions

View File

@@ -13,8 +13,11 @@ import { InstanceState } from "@/effect/instance-state"
import { containsPath } from "@/project/instance-context"
import { NonNegativeInt } from "@opencode-ai/core/schema"
import { RuntimeFlags } from "@/effect/runtime-flags"
import { InstanceRef } from "@/effect/instance-ref"
import { makeRuntime } from "@/effect/run-service"
const log = Log.create({ service: "lsp" })
const busRuntime = makeRuntime(Bus.Service, Bus.layer)
export const Event = {
Updated: BusEvent.define("lsp.updated", Schema.Struct({})),
@@ -291,7 +294,9 @@ export const layer = Layer.effect(
if (!client) continue
result.push(client)
Bus.publish(Event.Updated, {})
void busRuntime.runPromise((bus) =>
bus.publish(Event.Updated, {}).pipe(Effect.provideService(InstanceRef, ctx)),
)
}
return result

View File

@@ -1,13 +1,14 @@
import { describe, expect, spyOn } from "bun:test"
import path from "path"
import { Effect, Layer } from "effect"
import { Deferred, Effect, Layer } from "effect"
import { Bus } from "@/bus"
import { Config } from "@/config/config"
import { RuntimeFlags } from "@/effect/runtime-flags"
import { LSP } from "@/lsp/lsp"
import * as LSPServer from "@/lsp/server"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { provideTmpdirInstance } from "../fixture/fixture"
import { testEffect } from "../lib/effect"
import { awaitWithTimeout, testEffect } from "../lib/effect"
const it = testEffect(Layer.mergeAll(LSP.defaultLayer, CrossSpawnSpawner.defaultLayer))
const experimentalTyIt = testEffect(
@@ -16,6 +17,7 @@ const experimentalTyIt = testEffect(
CrossSpawnSpawner.defaultLayer,
),
)
const fakeServerPath = path.join(__dirname, "../fixture/lsp/fake-lsp-server.js")
const disabledDownloadIt = testEffect(
Layer.mergeAll(
LSP.layer.pipe(Layer.provide(Config.defaultLayer), Layer.provide(RuntimeFlags.layer({ disableLspDownload: true }))),
@@ -92,6 +94,35 @@ describe("lsp.spawn", () => {
),
)
it.live("publishes lsp.updated after custom LSP initialization", () =>
provideTmpdirInstance(
(dir) =>
Effect.gen(function* () {
const lsp = yield* LSP.Service
const updated = yield* Deferred.make<void>()
const unsubscribe = Bus.subscribe(LSP.Event.Updated, () =>
Effect.runSync(Deferred.succeed(updated, undefined)),
)
yield* Effect.addFinalizer(() => Effect.sync(unsubscribe))
const file = path.join(dir, "sample.repro")
yield* Effect.promise(() => Bun.write(file, "sample\n"))
yield* lsp.touchFile(file)
yield* awaitWithTimeout(Deferred.await(updated), "lsp.updated event was not published")
}),
{
config: {
lsp: {
fake: {
command: [process.execPath, fakeServerPath],
extensions: [".repro"],
},
},
},
},
),
)
it.live("would spawn builtin LSP for files inside instance when config object is provided", () =>
provideTmpdirInstance(
(dir) =>