cloudaxe-opencode/packages/opencode/src/lsp/index.ts

140 lines
3.4 KiB
TypeScript
Raw Normal View History

2025-05-31 18:41:00 +00:00
import { App } from "../app/app"
import { Log } from "../util/log"
import { LSPClient } from "./client"
import path from "path"
2025-05-21 02:00:00 +00:00
export namespace LSP {
2025-05-31 18:41:00 +00:00
const log = Log.create({ service: "lsp" })
2025-05-21 02:00:00 +00:00
2025-05-26 18:52:38 +00:00
const state = App.state(
"lsp",
async () => {
2025-05-31 18:41:00 +00:00
log.info("initializing")
const clients = new Map<string, LSPClient.Info>()
2025-05-21 02:00:00 +00:00
2025-05-26 18:52:38 +00:00
return {
clients,
2025-05-31 18:41:00 +00:00
}
2025-05-26 18:52:38 +00:00
},
async (state) => {
for (const client of state.clients.values()) {
2025-05-31 18:41:00 +00:00
await client.shutdown()
2025-05-26 18:52:38 +00:00
}
},
2025-05-31 18:41:00 +00:00
)
2025-05-21 02:00:00 +00:00
2025-05-26 21:15:41 +00:00
export async function file(input: string) {
2025-05-31 18:41:00 +00:00
const extension = path.parse(input).ext
const s = await state()
const matches = AUTO.filter((x) => x.extensions.includes(extension))
2025-05-26 21:15:41 +00:00
for (const match of matches) {
2025-05-31 18:41:00 +00:00
const existing = s.clients.get(match.id)
if (existing) continue
2025-06-05 17:49:59 +00:00
const [binary] = match.command
const bin = Bun.which(binary)
if (!bin) continue
2025-05-26 21:15:41 +00:00
const client = await LSPClient.create({
cmd: match.command,
serverID: match.id,
2025-06-06 03:42:04 +00:00
initialization: match.initialization,
2025-05-31 18:41:00 +00:00
})
s.clients.set(match.id, client)
2025-05-26 21:15:41 +00:00
}
await run(async (client) => {
2025-05-31 18:41:00 +00:00
const wait = client.waitForDiagnostics({ path: input })
await client.notify.open({ path: input })
return wait
})
2025-05-26 21:15:41 +00:00
}
export async function diagnostics() {
2025-05-31 18:41:00 +00:00
const results: Record<string, LSPClient.Diagnostic[]> = {}
2025-05-26 21:15:41 +00:00
for (const result of await run(async (client) => client.diagnostics)) {
for (const [path, diagnostics] of result.entries()) {
2025-05-31 18:41:00 +00:00
const arr = results[path] || []
arr.push(...diagnostics)
results[path] = arr
2025-05-26 21:15:41 +00:00
}
}
2025-05-31 18:41:00 +00:00
return results
2025-05-26 21:15:41 +00:00
}
export async function hover(input: {
2025-05-31 18:41:00 +00:00
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,
},
2025-05-31 18:41:00 +00:00
})
})
}
2025-05-26 21:15:41 +00:00
async function run<T>(
2025-05-21 02:00:00 +00:00
input: (client: LSPClient.Info) => Promise<T>,
): Promise<T[]> {
2025-05-31 18:41:00 +00:00
const clients = await state().then((x) => [...x.clients.values()])
const tasks = clients.map((x) => input(x))
return Promise.all(tasks)
2025-05-21 02:00:00 +00:00
}
2025-05-26 17:39:16 +00:00
const AUTO: {
2025-05-31 18:41:00 +00:00
id: string
command: string[]
2025-06-06 03:42:04 +00:00
initialization?: any
2025-05-31 18:41:00 +00:00
extensions: string[]
install?: () => Promise<void>
2025-05-26 17:39:16 +00:00
}[] = [
{
2025-05-26 21:15:41 +00:00
id: "typescript",
2025-05-26 17:39:16 +00:00
command: ["bun", "x", "typescript-language-server", "--stdio"],
extensions: [
2025-05-26 21:15:41 +00:00
".ts",
".tsx",
".js",
".jsx",
".mjs",
".cjs",
".mts",
".cts",
".mtsx",
".ctsx",
2025-05-26 17:39:16 +00:00
],
2025-06-06 03:42:04 +00:00
initialization: {
tsserver: {
path: require.resolve("typescript/lib/tsserver.js"),
},
},
2025-05-26 17:39:16 +00:00
},
{
2025-05-26 21:15:41 +00:00
id: "golang",
2025-06-06 03:42:04 +00:00
command: ["gopls" /*"-logfile", "gopls.log", "-rpc.trace", "-vv"*/],
2025-05-26 21:15:41 +00:00
extensions: [".go"],
2025-05-26 17:39:16 +00:00
},
2025-05-31 18:41:00 +00:00
]
2025-05-26 21:15:41 +00:00
export namespace Diagnostic {
export function pretty(diagnostic: LSPClient.Diagnostic) {
const severityMap = {
1: "ERROR",
2: "WARN",
3: "INFO",
4: "HINT",
2025-05-31 18:41:00 +00:00
}
2025-05-26 21:15:41 +00:00
2025-05-31 18:41:00 +00:00
const severity = severityMap[diagnostic.severity || 1]
const line = diagnostic.range.start.line + 1
const col = diagnostic.range.start.character + 1
2025-05-26 21:15:41 +00:00
2025-05-31 18:41:00 +00:00
return `${severity} [${line}:${col}] ${diagnostic.message}`
2025-05-26 21:15:41 +00:00
}
}
2025-05-21 02:00:00 +00:00
}