Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 19 additions & 20 deletions packages/opencode/src/lsp/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import z from "zod"
import type * as LSPServer from "./server"
import { NamedError } from "@opencode-ai/shared/util/error"
import { withTimeout } from "../util/timeout"
import { Instance } from "../project/instance"
import { Filesystem } from "../util"

const DIAGNOSTICS_DEBOUNCE_MS = 150
Expand Down Expand Up @@ -39,7 +38,7 @@ export const Event = {
),
}

export async function create(input: { serverID: string; server: LSPServer.Handle; root: string }) {
export async function create(input: { serverID: string; server: LSPServer.Handle; root: string; directory: string }) {
const l = log.clone().tag("serverID", input.serverID)
l.info("starting client")

Expand Down Expand Up @@ -145,70 +144,70 @@ export async function create(input: { serverID: string; server: LSPServer.Handle
return connection
},
notify: {
async open(input: { path: string }) {
input.path = path.isAbsolute(input.path) ? input.path : path.resolve(Instance.directory, input.path)
const text = await Filesystem.readText(input.path)
const extension = path.extname(input.path)
async open(request: { path: string }) {
request.path = path.isAbsolute(request.path) ? request.path : path.resolve(input.directory, request.path)
const text = await Filesystem.readText(request.path)
const extension = path.extname(request.path)
const languageId = LANGUAGE_EXTENSIONS[extension] ?? "plaintext"

const version = files[input.path]
const version = files[request.path]
if (version !== undefined) {
log.info("workspace/didChangeWatchedFiles", input)
log.info("workspace/didChangeWatchedFiles", request)
await connection.sendNotification("workspace/didChangeWatchedFiles", {
changes: [
{
uri: pathToFileURL(input.path).href,
uri: pathToFileURL(request.path).href,
type: 2, // Changed
},
],
})

const next = version + 1
files[input.path] = next
files[request.path] = next
log.info("textDocument/didChange", {
path: input.path,
path: request.path,
version: next,
})
await connection.sendNotification("textDocument/didChange", {
textDocument: {
uri: pathToFileURL(input.path).href,
uri: pathToFileURL(request.path).href,
version: next,
},
contentChanges: [{ text }],
})
return
}

log.info("workspace/didChangeWatchedFiles", input)
log.info("workspace/didChangeWatchedFiles", request)
await connection.sendNotification("workspace/didChangeWatchedFiles", {
changes: [
{
uri: pathToFileURL(input.path).href,
uri: pathToFileURL(request.path).href,
type: 1, // Created
},
],
})

log.info("textDocument/didOpen", input)
diagnostics.delete(input.path)
log.info("textDocument/didOpen", request)
diagnostics.delete(request.path)
await connection.sendNotification("textDocument/didOpen", {
textDocument: {
uri: pathToFileURL(input.path).href,
uri: pathToFileURL(request.path).href,
languageId,
version: 0,
text,
},
})
files[input.path] = 0
files[request.path] = 0
return
},
},
get diagnostics() {
return diagnostics
},
async waitForDiagnostics(input: { path: string }) {
async waitForDiagnostics(request: { path: string }) {
const normalizedPath = Filesystem.normalizePath(
path.isAbsolute(input.path) ? input.path : path.resolve(Instance.directory, input.path),
path.isAbsolute(request.path) ? request.path : path.resolve(input.directory, request.path),
)
log.info("waiting for diagnostics", { path: normalizedPath })
let unsub: () => void
Expand Down
22 changes: 14 additions & 8 deletions packages/opencode/src/lsp/lsp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import { pathToFileURL, fileURLToPath } from "url"
import * as LSPServer from "./server"
import z from "zod"
import { Config } from "../config"
import { Instance } from "../project/instance"
import { Flag } from "@/flag/flag"
import { Process } from "../util"
import { spawn as lspspawn } from "./launch"
import { Effect, Layer, Context } from "effect"
import { InstanceState } from "@/effect"
import { AppFileSystem } from "@opencode-ai/shared/filesystem"

const log = Log.create({ service: "lsp" })

Expand Down Expand Up @@ -162,7 +162,7 @@ export const layer = Layer.effect(
const config = yield* Config.Service

const state = yield* InstanceState.make<State>(
Effect.fn("LSP.state")(function* () {
Effect.fn("LSP.state")(function* (ctx) {
const cfg = yield* config.get()

const servers: Record<string, LSPServer.Info> = {}
Expand All @@ -187,7 +187,7 @@ export const layer = Layer.effect(
servers[name] = {
...existing,
id: name,
root: existing?.root ?? (async () => Instance.directory),
root: existing?.root ?? (async (_file, ctx) => ctx.directory),
extensions: item.extensions ?? existing?.extensions ?? [],
spawn: async (root) => ({
process: lspspawn(item.command[0], item.command.slice(1), {
Expand Down Expand Up @@ -225,15 +225,18 @@ export const layer = Layer.effect(
)

const getClients = Effect.fnUntraced(function* (file: string) {
if (!Instance.containsPath(file)) return [] as LSPClient.Info[]
const ctx = yield* InstanceState.context
if (!AppFileSystem.contains(ctx.directory, file) && (ctx.worktree === "/" || !AppFileSystem.contains(ctx.worktree, file))) {
return [] as LSPClient.Info[]
}
const s = yield* InstanceState.get(state)
return yield* Effect.promise(async () => {
const extension = path.parse(file).ext || file
const result: LSPClient.Info[] = []

async function schedule(server: LSPServer.Info, root: string, key: string) {
const handle = await server
.spawn(root)
.spawn(root, ctx)
.then((value) => {
if (!value) s.broken.add(key)
return value
Expand All @@ -251,6 +254,7 @@ export const layer = Layer.effect(
serverID: server.id,
server: handle,
root,
directory: ctx.directory,
}).catch(async (err) => {
s.broken.add(key)
await Process.stop(handle.process)
Expand All @@ -273,7 +277,7 @@ export const layer = Layer.effect(
for (const server of Object.values(s.servers)) {
if (server.extensions.length && !server.extensions.includes(extension)) continue

const root = await server.root(file)
const root = await server.root(file, ctx)
if (!root) continue
if (s.broken.has(root + server.id)) continue

Expand Down Expand Up @@ -326,26 +330,28 @@ export const layer = Layer.effect(
})

const status = Effect.fn("LSP.status")(function* () {
const ctx = yield* InstanceState.context
const s = yield* InstanceState.get(state)
const result: Status[] = []
for (const client of s.clients) {
result.push({
id: client.serverID,
name: s.servers[client.serverID].id,
root: path.relative(Instance.directory, client.root),
root: path.relative(ctx.directory, client.root),
status: "connected",
})
}
return result
})

const hasClients = Effect.fn("LSP.hasClients")(function* (file: string) {
const ctx = yield* InstanceState.context
const s = yield* InstanceState.get(state)
return yield* Effect.promise(async () => {
const extension = path.parse(file).ext || file
for (const server of Object.values(s.servers)) {
if (server.extensions.length && !server.extensions.includes(extension)) continue
const root = await server.root(file)
const root = await server.root(file, ctx)
if (!root) continue
if (s.broken.has(root + server.id)) continue
return true
Expand Down
Loading
Loading