Skip to content

Commit 042e0e1

Browse files
committed
Fix bug #23075: LSP workspaceSymbol is always send with empty query
1 parent c4d8a81 commit 042e0e1

1 file changed

Lines changed: 47 additions & 29 deletions

File tree

  • packages/opencode/src/tool

packages/opencode/src/tool/lsp.ts

Lines changed: 47 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,10 @@ const operations = [
2222

2323
export const Parameters = Schema.Struct({
2424
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)" }),
25+
filePath: Schema.optional(Schema.String).annotate({ description: "The absolute or relative path to the file. Required for all operations except workspaceSymbol." }),
26+
line: Schema.optional(Schema.Number).annotate({ description: "The line number (1-based, as shown in editors). Required for: goToDefinition, findReferences, hover, goToImplementation, prepareCallHierarchy, incomingCalls, outgoingCalls." }),
27+
character: Schema.optional(Schema.Number).annotate({ description: "The character offset (1-based, as shown in editors). Required for: goToDefinition, findReferences, hover, goToImplementation, prepareCallHierarchy, incomingCalls, outgoingCalls." }),
28+
query: Schema.optional(Schema.String).annotate({ description: "Search query. Required for workspaceSymbol operation." }),
3229
})
3330

3431
export const LspTool = Tool.define(
@@ -40,36 +37,39 @@ export const LspTool = Tool.define(
4037
description: DESCRIPTION,
4138
parameters: Parameters,
4239
execute: (
43-
args: { operation: (typeof operations)[number]; filePath: string; line: number; character: number },
40+
args: Schema.Schema.Type<typeof Parameters>,
4441
ctx: Tool.Context,
4542
) =>
4643
Effect.gen(function* () {
44+
if (args.operation === "workspaceSymbol") {
45+
if (!args.query) {
46+
throw new Error(`query is required for operation '${args.operation}'`)
47+
}
48+
const result: unknown[] = yield* lsp.workspaceSymbol(args.query)
49+
return {
50+
title: `workspaceSymbol "${args.query}"`,
51+
metadata: { result },
52+
output: result.length === 0 ? `No workspace symbols found matching query "${args.query}"` : JSON.stringify(result, null, 2),
53+
}
54+
}
55+
56+
if (!args.filePath) {
57+
throw new Error(`filePath is required for operation '${args.operation}'`)
58+
}
59+
4760
const file = path.isAbsolute(args.filePath) ? args.filePath : path.join(Instance.directory, args.filePath)
4861
yield* assertExternalDirectoryEffect(ctx, file)
4962
const meta =
50-
args.operation === "workspaceSymbol"
51-
? { operation: args.operation }
52-
: args.operation === "documentSymbol"
53-
? { operation: args.operation, filePath: file }
54-
: { operation: args.operation, filePath: file, line: args.line, character: args.character }
63+
args.operation === "documentSymbol"
64+
? { operation: args.operation, filePath: file }
65+
: { operation: args.operation, filePath: file, line: args.line, character: args.character }
5566
yield* ctx.ask({
5667
permission: "lsp",
5768
patterns: ["*"],
5869
always: ["*"],
5970
metadata: meta,
6071
})
6172

62-
const uri = pathToFileURL(file).href
63-
const position = { file, line: args.line - 1, character: args.character - 1 }
64-
const relPath = path.relative(Instance.worktree, file)
65-
const detail =
66-
args.operation === "workspaceSymbol"
67-
? ""
68-
: args.operation === "documentSymbol"
69-
? relPath
70-
: `${relPath}:${args.line}:${args.character}`
71-
const title = detail ? `${args.operation} ${detail}` : args.operation
72-
7373
const exists = yield* fs.existsSafe(file)
7474
if (!exists) throw new Error(`File not found: ${file}`)
7575

@@ -78,6 +78,26 @@ export const LspTool = Tool.define(
7878

7979
yield* lsp.touchFile(file, "document")
8080

81+
const uri = pathToFileURL(file).href
82+
const relPath = path.relative(Instance.worktree, file)
83+
84+
if (args.operation === "documentSymbol") {
85+
const result: unknown[] = yield* lsp.documentSymbol(uri)
86+
return {
87+
title: `documentSymbol ${relPath}`,
88+
metadata: { result },
89+
output: result.length === 0 ? `No document symbols found` : JSON.stringify(result, null, 2),
90+
}
91+
}
92+
93+
if (args.line === undefined || args.character === undefined) {
94+
throw new Error(`line and character are required for operation '${args.operation}'`)
95+
}
96+
97+
const position = { file, line: args.line - 1, character: args.character - 1 }
98+
const detail = `${relPath}:${args.line}:${args.character}`
99+
const title = `${args.operation} ${detail}`
100+
81101
const result: unknown[] = yield* (() => {
82102
switch (args.operation) {
83103
case "goToDefinition":
@@ -86,10 +106,6 @@ export const LspTool = Tool.define(
86106
return lsp.references(position)
87107
case "hover":
88108
return lsp.hover(position)
89-
case "documentSymbol":
90-
return lsp.documentSymbol(uri)
91-
case "workspaceSymbol":
92-
return lsp.workspaceSymbol("")
93109
case "goToImplementation":
94110
return lsp.implementation(position)
95111
case "prepareCallHierarchy":
@@ -98,6 +114,8 @@ export const LspTool = Tool.define(
98114
return lsp.incomingCalls(position)
99115
case "outgoingCalls":
100116
return lsp.outgoingCalls(position)
117+
default:
118+
throw new Error(`Unknown operation: ${args.operation}`)
101119
}
102120
})()
103121

@@ -106,7 +124,7 @@ export const LspTool = Tool.define(
106124
metadata: { result },
107125
output: result.length === 0 ? `No results found for ${args.operation}` : JSON.stringify(result, null, 2),
108126
}
109-
}).pipe(Effect.orDie),
127+
}),
110128
}
111129
}),
112130
)

0 commit comments

Comments
 (0)