Skip to content

Commit 564a03d

Browse files
committed
Fix bug #23075: LSP workspaceSymbol is always send with empty query
1 parent 5cd178b commit 564a03d

1 file changed

Lines changed: 46 additions & 17 deletions

File tree

  • packages/opencode/src/tool

packages/opencode/src/tool/lsp.ts

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,14 @@ const operations = [
2020
"outgoingCalls",
2121
] as const
2222

23+
const oneBasedInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1))
24+
2325
export const Parameters = Schema.Struct({
2426
operation: Schema.Literals(operations).annotate({ description: "The LSP operation to perform" }),
25-
filePath: Schema.String.annotate({ description: "The absolute or relative path to the file" }),
26-
line: Schema.Number.check(Schema.isInt())
27-
.check(Schema.isGreaterThanOrEqualTo(1))
28-
.annotate({ description: "The line number (1-based, as shown in editors)" }),
29-
character: Schema.Number.check(Schema.isInt())
30-
.check(Schema.isGreaterThanOrEqualTo(1))
31-
.annotate({ description: "The character offset (1-based, as shown in editors)" }),
27+
filePath: Schema.optional(Schema.String).annotate({ description: "The absolute or relative path to the file. Required for all operations except workspaceSymbol." }),
28+
line: Schema.optional(oneBasedInt).annotate({ description: "The line number (1-based, as shown in editors). Required for: goToDefinition, findReferences, hover, goToImplementation, prepareCallHierarchy, incomingCalls, outgoingCalls." }),
29+
character: Schema.optional(oneBasedInt).annotate({ description: "The character offset (1-based, as shown in editors). Required for: goToDefinition, findReferences, hover, goToImplementation, prepareCallHierarchy, incomingCalls, outgoingCalls." }),
30+
query: Schema.optional(Schema.String).annotate({ description: "Search query. Required for workspaceSymbol operation." }),
3231
})
3332

3433
export const LspTool = Tool.define(
@@ -41,19 +40,30 @@ export const LspTool = Tool.define(
4140
description: DESCRIPTION,
4241
parameters: Parameters,
4342
execute: (
44-
args: { operation: (typeof operations)[number]; filePath: string; line: number; character: number },
43+
args: Schema.Schema.Type<typeof Parameters>,
4544
ctx: Tool.Context,
4645
) =>
4746
Effect.gen(function* () {
47+
if (args.operation === "workspaceSymbol") {
48+
if (!args.query) {
49+
throw new Error(`query is required for operation '${args.operation}'`)
50+
}
51+
const result: unknown[] = yield* lsp.workspaceSymbol(args.query)
52+
return {
53+
title: `workspaceSymbol "${args.query}"`,
54+
metadata: { result },
55+
output: result.length === 0 ? `No workspace symbols found matching query "${args.query}"` : JSON.stringify(result, null, 2),
56+
}
57+
}
58+
59+
if (!args.filePath) {
60+
throw new Error(`filePath is required for operation '${args.operation}'`)
61+
}
62+
4863
const file = path.isAbsolute(args.filePath) ? args.filePath : path.join(Instance.directory, args.filePath)
4964
yield* assertExternalDirectoryEffect(ctx, file)
5065
yield* ctx.ask({ permission: "lsp", patterns: ["*"], always: ["*"], metadata: {} })
5166

52-
const uri = pathToFileURL(file).href
53-
const position = { file, line: args.line - 1, character: args.character - 1 }
54-
const relPath = path.relative(Instance.worktree, file)
55-
const title = `${args.operation} ${relPath}:${args.line}:${args.character}`
56-
5767
const exists = yield* fs.existsSafe(file)
5868
if (!exists) throw new Error(`File not found: ${file}`)
5969

@@ -62,6 +72,27 @@ export const LspTool = Tool.define(
6272

6373
yield* lsp.touchFile(file, "document")
6474

75+
if (args.operation === "documentSymbol") {
76+
const uri = pathToFileURL(file).href
77+
const relPath = path.relative(Instance.worktree, file)
78+
79+
const result: unknown[] = yield* lsp.documentSymbol(uri)
80+
return {
81+
title: `documentSymbol ${relPath}`,
82+
metadata: { result },
83+
output: result.length === 0 ? `No document symbols found` : JSON.stringify(result, null, 2),
84+
}
85+
}
86+
87+
if (args.line === undefined || args.character === undefined) {
88+
throw new Error(`line and character are required for operation '${args.operation}'`)
89+
}
90+
91+
const uri = pathToFileURL(file).href
92+
const relPath = path.relative(Instance.worktree, file)
93+
const position = { file, line: args.line - 1, character: args.character - 1 }
94+
const title = `${args.operation} ${relPath}:${args.line}:${args.character}`
95+
6596
const result: unknown[] = yield* (() => {
6697
switch (args.operation) {
6798
case "goToDefinition":
@@ -70,10 +101,6 @@ export const LspTool = Tool.define(
70101
return lsp.references(position)
71102
case "hover":
72103
return lsp.hover(position)
73-
case "documentSymbol":
74-
return lsp.documentSymbol(uri)
75-
case "workspaceSymbol":
76-
return lsp.workspaceSymbol("")
77104
case "goToImplementation":
78105
return lsp.implementation(position)
79106
case "prepareCallHierarchy":
@@ -82,6 +109,8 @@ export const LspTool = Tool.define(
82109
return lsp.incomingCalls(position)
83110
case "outgoingCalls":
84111
return lsp.outgoingCalls(position)
112+
default:
113+
throw new Error(`Unknown operation: ${args.operation}`)
85114
}
86115
})()
87116

0 commit comments

Comments
 (0)