Skip to content
Open
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
24 changes: 24 additions & 0 deletions packages/opencode/src/lsp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
50 changes: 47 additions & 3 deletions packages/opencode/src/tool/lsp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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")
}
})()

Expand Down
4 changes: 4 additions & 0 deletions packages/opencode/src/tool/lsp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.