Skip to content

Commit e4cc4e1

Browse files
authored
fix(lsp): preserve instance ref for update events (anomalyco#28016)
1 parent 53e89f9 commit e4cc4e1

2 files changed

Lines changed: 39 additions & 3 deletions

File tree

packages/opencode/src/lsp/lsp.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ import { InstanceState } from "@/effect/instance-state"
1313
import { containsPath } from "@/project/instance-context"
1414
import { NonNegativeInt } from "@opencode-ai/core/schema"
1515
import { RuntimeFlags } from "@/effect/runtime-flags"
16+
import { InstanceRef } from "@/effect/instance-ref"
17+
import { makeRuntime } from "@/effect/run-service"
1618

1719
const log = Log.create({ service: "lsp" })
20+
const busRuntime = makeRuntime(Bus.Service, Bus.layer)
1821

1922
export const Event = {
2023
Updated: BusEvent.define("lsp.updated", Schema.Struct({})),
@@ -291,7 +294,9 @@ export const layer = Layer.effect(
291294
if (!client) continue
292295

293296
result.push(client)
294-
Bus.publish(Event.Updated, {})
297+
void busRuntime.runPromise((bus) =>
298+
bus.publish(Event.Updated, {}).pipe(Effect.provideService(InstanceRef, ctx)),
299+
)
295300
}
296301

297302
return result

packages/opencode/test/lsp/index.test.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { describe, expect, spyOn } from "bun:test"
22
import path from "path"
3-
import { Effect, Layer } from "effect"
3+
import { Deferred, Effect, Layer } from "effect"
4+
import { Bus } from "@/bus"
45
import { Config } from "@/config/config"
56
import { RuntimeFlags } from "@/effect/runtime-flags"
67
import { LSP } from "@/lsp/lsp"
78
import * as LSPServer from "@/lsp/server"
89
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
910
import { provideTmpdirInstance } from "../fixture/fixture"
10-
import { testEffect } from "../lib/effect"
11+
import { awaitWithTimeout, testEffect } from "../lib/effect"
1112

1213
const it = testEffect(Layer.mergeAll(LSP.defaultLayer, CrossSpawnSpawner.defaultLayer))
1314
const experimentalTyIt = testEffect(
@@ -16,6 +17,7 @@ const experimentalTyIt = testEffect(
1617
CrossSpawnSpawner.defaultLayer,
1718
),
1819
)
20+
const fakeServerPath = path.join(__dirname, "../fixture/lsp/fake-lsp-server.js")
1921
const disabledDownloadIt = testEffect(
2022
Layer.mergeAll(
2123
LSP.layer.pipe(Layer.provide(Config.defaultLayer), Layer.provide(RuntimeFlags.layer({ disableLspDownload: true }))),
@@ -92,6 +94,35 @@ describe("lsp.spawn", () => {
9294
),
9395
)
9496

97+
it.live("publishes lsp.updated after custom LSP initialization", () =>
98+
provideTmpdirInstance(
99+
(dir) =>
100+
Effect.gen(function* () {
101+
const lsp = yield* LSP.Service
102+
const updated = yield* Deferred.make<void>()
103+
const unsubscribe = Bus.subscribe(LSP.Event.Updated, () =>
104+
Effect.runSync(Deferred.succeed(updated, undefined)),
105+
)
106+
yield* Effect.addFinalizer(() => Effect.sync(unsubscribe))
107+
108+
const file = path.join(dir, "sample.repro")
109+
yield* Effect.promise(() => Bun.write(file, "sample\n"))
110+
yield* lsp.touchFile(file)
111+
yield* awaitWithTimeout(Deferred.await(updated), "lsp.updated event was not published")
112+
}),
113+
{
114+
config: {
115+
lsp: {
116+
fake: {
117+
command: [process.execPath, fakeServerPath],
118+
extensions: [".repro"],
119+
},
120+
},
121+
},
122+
},
123+
),
124+
)
125+
95126
it.live("would spawn builtin LSP for files inside instance when config object is provided", () =>
96127
provideTmpdirInstance(
97128
(dir) =>

0 commit comments

Comments
 (0)