Files
opencode/packages/opencode/src/lsp/index.ts
T
2025-06-05 13:51:18 -04:00

133 lines
3.2 KiB
TypeScript

import { App } from "../app/app"
import { Log } from "../util/log"
import { LSPClient } from "./client"
import path from "path"
export namespace LSP {
const log = Log.create({ service: "lsp" })
const state = App.state(
"lsp",
async () => {
log.info("initializing")
const clients = new Map<string, LSPClient.Info>()
return {
clients,
}
},
async (state) => {
for (const client of state.clients.values()) {
await client.shutdown()
}
},
)
export async function file(input: string) {
const extension = path.parse(input).ext
const s = await state()
const matches = AUTO.filter((x) => x.extensions.includes(extension))
for (const match of matches) {
const existing = s.clients.get(match.id)
if (existing) continue
const [binary] = match.command
const bin = Bun.which(binary)
if (!bin) continue
const client = await LSPClient.create({
cmd: match.command,
serverID: match.id,
})
s.clients.set(match.id, client)
}
await run(async (client) => {
const wait = client.waitForDiagnostics({ path: input })
await client.notify.open({ path: input })
return wait
})
}
export async function diagnostics() {
const results: Record<string, LSPClient.Diagnostic[]> = {}
for (const result of await run(async (client) => client.diagnostics)) {
for (const [path, diagnostics] of result.entries()) {
const arr = results[path] || []
arr.push(...diagnostics)
results[path] = arr
}
}
return results
}
export async function hover(input: {
file: string
line: number
character: number
}) {
return run((client) => {
return client.connection.sendRequest("textDocument/hover", {
textDocument: {
uri: `file://${input.file}`,
},
position: {
line: input.line,
character: input.character,
},
})
})
}
async function run<T>(
input: (client: LSPClient.Info) => Promise<T>,
): Promise<T[]> {
const clients = await state().then((x) => [...x.clients.values()])
const tasks = clients.map((x) => input(x))
return Promise.all(tasks)
}
const AUTO: {
id: string
command: string[]
extensions: string[]
install?: () => Promise<void>
}[] = [
{
id: "typescript",
command: ["bun", "x", "typescript-language-server", "--stdio"],
extensions: [
".ts",
".tsx",
".js",
".jsx",
".mjs",
".cjs",
".mts",
".cts",
".mtsx",
".ctsx",
],
},
{
id: "golang",
command: ["gopls"],
extensions: [".go"],
},
]
export namespace Diagnostic {
export function pretty(diagnostic: LSPClient.Diagnostic) {
const severityMap = {
1: "ERROR",
2: "WARN",
3: "INFO",
4: "HINT",
}
const severity = severityMap[diagnostic.severity || 1]
const line = diagnostic.range.start.line + 1
const col = diagnostic.range.start.character + 1
return `${severity} [${line}:${col}] ${diagnostic.message}`
}
}
}