From 8718b98ee1e7c114fa2d2055295d39d01b70b6f4 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Mon, 27 Apr 2026 15:39:36 +1000 Subject: [PATCH] fix: pass workspace symbol query to experimental LSP tool (#24576) --- packages/opencode/src/tool/lsp.ts | 7 +++-- packages/opencode/src/tool/lsp.txt | 7 ++++- .../__snapshots__/parameters.test.ts.snap | 4 +++ packages/opencode/test/tool/lsp.test.ts | 26 ++++++++++++++++++- 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/tool/lsp.ts b/packages/opencode/src/tool/lsp.ts index bb3b503441..ac13cfb435 100644 --- a/packages/opencode/src/tool/lsp.ts +++ b/packages/opencode/src/tool/lsp.ts @@ -29,6 +29,9 @@ export const Parameters = Schema.Struct({ character: Schema.Number.check(Schema.isInt()) .check(Schema.isGreaterThanOrEqualTo(1)) .annotate({ description: "The character offset (1-based, as shown in editors)" }), + query: Schema.optional(Schema.String).annotate({ + description: "Search query for workspaceSymbol. Empty string requests all symbols.", + }), }) export const LspTool = Tool.define( @@ -40,7 +43,7 @@ export const LspTool = Tool.define( description: DESCRIPTION, parameters: Parameters, execute: ( - args: { operation: (typeof operations)[number]; filePath: string; line: number; character: number }, + args: Schema.Schema.Type, ctx: Tool.Context, ) => Effect.gen(function* () { @@ -89,7 +92,7 @@ export const LspTool = Tool.define( case "documentSymbol": return lsp.documentSymbol(uri) case "workspaceSymbol": - return lsp.workspaceSymbol("") + return lsp.workspaceSymbol(args.query ?? "") case "goToImplementation": return lsp.implementation(position) case "prepareCallHierarchy": diff --git a/packages/opencode/src/tool/lsp.txt b/packages/opencode/src/tool/lsp.txt index 5a50a571b0..85db65c173 100644 --- a/packages/opencode/src/tool/lsp.txt +++ b/packages/opencode/src/tool/lsp.txt @@ -5,7 +5,7 @@ Supported operations: - findReferences: Find all references to a symbol - hover: Get hover information (documentation, type info) for a symbol - documentSymbol: Get all symbols (functions, classes, variables) in a document -- workspaceSymbol: Search for symbols across the entire workspace +- workspaceSymbol: List project-wide symbols matching a query string - goToImplementation: Find implementations of an interface or abstract method - prepareCallHierarchy: Get call hierarchy item at a position (functions/methods) - incomingCalls: Find all functions/methods that call the function at a position @@ -16,4 +16,9 @@ All operations require: - line: The line number (1-based, as shown in editors) - character: The character offset (1-based, as shown in editors) +workspaceSymbol also accepts: +- query: A query string to filter symbols by. Empty string requests all symbols. + +For workspaceSymbol, filePath is not sent in the LSP workspace/symbol request. It is used by opencode to select and start the matching LSP server. + Note: LSP servers must be configured for the file type. If no server is available, an error will be returned. diff --git a/packages/opencode/test/tool/__snapshots__/parameters.test.ts.snap b/packages/opencode/test/tool/__snapshots__/parameters.test.ts.snap index eb3fe6cce4..b20665b34d 100644 --- a/packages/opencode/test/tool/__snapshots__/parameters.test.ts.snap +++ b/packages/opencode/test/tool/__snapshots__/parameters.test.ts.snap @@ -209,6 +209,10 @@ exports[`tool parameters JSON Schema (wire shape) lsp 1`] = ` ], "type": "string", }, + "query": { + "description": "Search query for workspaceSymbol. Empty string requests all symbols.", + "type": "string", + }, }, "required": [ "operation", diff --git a/packages/opencode/test/tool/lsp.test.ts b/packages/opencode/test/tool/lsp.test.ts index b7a52da19c..502ea30a02 100644 --- a/packages/opencode/test/tool/lsp.test.ts +++ b/packages/opencode/test/tool/lsp.test.ts @@ -28,6 +28,8 @@ const ctx = { ask: () => Effect.void, } +const workspaceSymbolQueries: string[] = [] + const lsp = Layer.succeed( LSP.Service, LSP.Service.of({ @@ -41,7 +43,11 @@ const lsp = Layer.succeed( references: () => Effect.succeed([]), implementation: () => Effect.succeed([]), documentSymbol: () => Effect.succeed([]), - workspaceSymbol: () => Effect.succeed([]), + workspaceSymbol: (query) => + Effect.sync(() => { + workspaceSymbolQueries.push(query) + return [] + }), prepareCallHierarchy: () => Effect.succeed([]), incomingCalls: () => Effect.succeed([]), outgoingCalls: () => Effect.succeed([]), @@ -142,6 +148,7 @@ describe("tool.lsp", () => { provideTmpdirInstance( (dir) => Effect.gen(function* () { + workspaceSymbolQueries.length = 0 const file = path.join(dir, "test.ts") yield* put(file) @@ -158,5 +165,22 @@ describe("tool.lsp", () => { { git: true }, ), ) + + it.live("passes workspaceSymbol query to LSP", () => + provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + workspaceSymbolQueries.length = 0 + const file = path.join(dir, "test.ts") + yield* put(file) + + yield* run({ operation: "workspaceSymbol", filePath: file, line: 3, character: 7, query: "TestSymbol" }) + yield* run({ operation: "workspaceSymbol", filePath: file, line: 3, character: 7 }) + + expect(workspaceSymbolQueries).toEqual(["TestSymbol", ""]) + }), + { git: true }, + ), + ) }) })