diff --git a/packages/opencode/src/lsp/index.ts b/packages/opencode/src/lsp/index.ts index 0fd3b69dfcd..82491581f3e 100644 --- a/packages/opencode/src/lsp/index.ts +++ b/packages/opencode/src/lsp/index.ts @@ -466,6 +466,30 @@ export namespace LSP { return Promise.all(tasks) } + export async function restartByExtension(extension: string) { + const s = await state() + const restarted: string[] = [] + + for (const server of Object.values(s.servers)) { + if (server.extensions.length && !server.extensions.includes(extension)) continue + + for (const client of s.clients.filter((c) => c.serverID === server.id)) { + log.info("restarting LSP server", { serverID: client.serverID, root: client.root }) + await client.shutdown() + const idx = s.clients.indexOf(client) + if (idx >= 0) s.clients.splice(idx, 1) + s.broken.delete(client.root + client.serverID) + restarted.push(server.id) + } + } + + if (restarted.length > 0) { + Bus.publish(Event.Updated, {}) + } + + return restarted + } + export namespace Diagnostic { export function pretty(diagnostic: LSPClient.Diagnostic) { const severityMap = { diff --git a/packages/opencode/src/tool/lsp.ts b/packages/opencode/src/tool/lsp.ts index 2a15ed7e33b..9a27241575c 100644 --- a/packages/opencode/src/tool/lsp.ts +++ b/packages/opencode/src/tool/lsp.ts @@ -16,17 +16,59 @@ const operations = [ "prepareCallHierarchy", "incomingCalls", "outgoingCalls", + "restartServer", ] as const export const LspTool = Tool.define("lsp", { description: DESCRIPTION, parameters: z.object({ operation: z.enum(operations).describe("The LSP operation to perform"), - filePath: z.string().describe("The absolute or relative path to the file"), - line: z.number().int().min(1).describe("The line number (1-based, as shown in editors)"), - character: z.number().int().min(1).describe("The character offset (1-based, as shown in editors)"), + filePath: z + .string() + .optional() + .describe("The absolute or relative path to the file (required for most operations)"), + line: z + .number() + .int() + .min(1) + .optional() + .describe("The line number (1-based, required for position-based operations)"), + character: z + .number() + .int() + .min(1) + .optional() + .describe("The character offset (1-based, required for position-based operations)"), + extension: z.string().optional().describe("File extension for restartServer operation (e.g., '.py', '.ts')"), }), execute: async (args) => { + if (args.operation === "restartServer") { + if (!args.extension) { + throw new Error("extension is required for restartServer operation") + } + const ext = args.extension.startsWith(".") ? args.extension : `.${args.extension}` + const restarted = await LSP.restartByExtension(ext) + const output = + restarted.length === 0 + ? `No LSP servers found for extension ${ext}` + : `Restarted LSP server(s): ${restarted.join(", ")}` + return { + title: `restartServer ${ext}`, + metadata: { result: restarted }, + output, + } + } + + if (!args.filePath) { + throw new Error("filePath is required for this operation") + } + if (args.line === undefined) { + throw new Error("line is required for this operation") + } + if (args.character === undefined) { + throw new Error("character is required for this operation") + } + const file = path.isAbsolute(args.filePath) ? args.filePath : path.join(Instance.directory, args.filePath) const uri = pathToFileURL(file).href const position = { @@ -70,6 +112,8 @@ export const LspTool = Tool.define("lsp", { return LSP.incomingCalls(position) case "outgoingCalls": return LSP.outgoingCalls(position) + case "restartServer": + throw new Error("Unreachable") } })() diff --git a/packages/opencode/src/tool/lsp.txt b/packages/opencode/src/tool/lsp.txt index 5a50a571b08..7cd6dd9c964 100644 --- a/packages/opencode/src/tool/lsp.txt +++ b/packages/opencode/src/tool/lsp.txt @@ -10,10 +10,14 @@ Supported operations: - prepareCallHierarchy: Get call hierarchy item at a position (functions/methods) - incomingCalls: Find all functions/methods that call the function at a position - outgoingCalls: Find all functions/methods called by the function at a position +- restartServer: Restart LSP server(s) for a file extension (useful after installing dependencies) All operations require: - filePath: The file to operate on - line: The line number (1-based, as shown in editors) - character: The character offset (1-based, as shown in editors) +The restartServer operation requires: +- extension: The file extension (e.g., ".py", ".ts") + Note: LSP servers must be configured for the file type. If no server is available, an error will be returned.