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

151 lines
3.9 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-06-07 03:21:57 +00:00
import { LSPServer } from "./server"
2025-06-30 20:45:13 +00:00
import { Ripgrep } from "../file/ripgrep"
2025-07-02 15:35:25 +00:00
import { z } from "zod"
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-07-02 15:35:25 +00:00
export const Symbol = z
.object({
name: z.string(),
kind: z.number(),
location: z.object({
uri: z.string(),
range: z.object({
start: z.object({
line: z.number(),
character: z.number(),
}),
end: z.object({
line: z.number(),
character: z.number(),
}),
}),
}),
})
.openapi({
ref: "LSP.Symbol",
})
export type Symbol = z.infer<typeof Symbol>
2025-05-26 18:52:38 +00:00
const state = App.state(
"lsp",
2025-07-01 02:46:42 +00:00
async (app) => {
2025-05-31 18:41:00 +00:00
log.info("initializing")
const clients = new Map<string, LSPClient.Info>()
2025-07-01 02:46:42 +00:00
for (const server of Object.values(LSPServer)) {
for (const extension of server.extensions) {
const [file] = await Ripgrep.files({
cwd: app.path.cwd,
glob: "*" + extension,
})
if (!file) continue
const handle = await server.spawn(App.info())
if (!handle) break
const client = await LSPClient.create(server.id, handle).catch(
() => {},
)
if (!client) break
clients.set(server.id, client)
break
}
}
log.info("initialized")
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-06-30 20:45:13 +00:00
export async function init() {
2025-07-01 02:46:42 +00:00
return state()
2025-06-30 20:45:13 +00:00
}
2025-06-06 21:20:01 +00:00
export async function touchFile(input: string, waitForDiagnostics?: boolean) {
2025-05-31 18:41:00 +00:00
const extension = path.parse(input).ext
2025-07-01 02:46:42 +00:00
const matches = Object.values(LSPServer)
.filter((x) => x.extensions.includes(extension))
.map((x) => x.id)
await run(async (client) => {
if (!matches.includes(client.serverID)) return
const wait = waitForDiagnostics
? client.waitForDiagnostics({ path: input })
: Promise.resolve()
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-06-30 20:45:13 +00:00
export async function workspaceSymbol(query: string) {
return run((client) =>
client.connection.sendRequest("workspace/symbol", {
query,
}),
2025-07-02 15:35:25 +00:00
).then((result) => result.flat() as LSP.Symbol[])
2025-06-30 20:45:13 +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
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
}