--- id: LSN-0071 ticket: pbs-lsp-workspace-symbols title: PBS workspace symbols are a physical named-declaration search created: 2026-09-21 tags: [studio, lsp, vscode, compiler-pbs, editor, workspace-symbols] --- # PBS workspace symbols are a physical named-declaration search ## Original Problem PBS already had compiler-backed completion, hover, signature help, go-to-definition (`LSN-0068`), find-references (`LSN-0069`), and an AST-backed outline of the open `.pbs` file (`LSN-0070`). VS Code workspace search (`Ctrl+T` / `workspace/symbol`) stayed empty. The risk was treating workspace symbols as a second outline, inventing a `SymbolId` index, waiting for snapshot cache (`AGD-0051`), or returning stdlib/SDK hits whose declarations are still virtual paths (`/virtual/stdlib`). Workspace symbols is a jump. Outline is not. ## Consolidated Decision PBS Workspace Symbols is the LSP projection of a **flat named-declaration search** over **regular filesystem files** of the current analysis, flattening the **same outline set** and using the **same physical-file destination policy** as definition. Durable locks from `DEC-0055` (AGD-0045 Q1 A, Q2 A, Q3 A, Q4 A, Q5 A, Q6 first-wave exclusions): 1. Emit only declarations whose `fileTable` handle is a regular filesystem file. Virtual stdlib/SDK, including `/virtual/stdlib`, is omitted. Do not invent virtual documents, `untitled:` buffers, or synthetic URIs. 2. `workspace/symbol` does not require an open text document. The server obtains the project analysis surface (`astByFile` / `projectSurface`) from `analyze()`. Missing surface → empty list. 3. The set is the flatten, file by file, of the named-declaration tree locked by outline (`LSN-0070`): - `fn` → Function; - `declare struct` → Struct plus fields, `ctor`s, methods; - `declare service` → Service plus methods; - `declare contract` → Contract plus signatures; - `declare host` → Host plus signatures; - `declare builtin type` → Builtin type plus projection fields and signatures; - `declare enum` → Enum plus cases; - `declare error` → Error plus labels; - `declare callback` → Callback; - `declare global` / `const` → Global / Const; - `implements Contract for Owner` → `contractName`, kind CONTRACT, `detail` = owner, plus methods. Locals, parameters, `this`, imports, and comments stay out. Private fields stay in. Overloads stay sibling hits. Unnamed `InvalidDecl` is omitted. Named decls survive later semantic errors. 4. First wave is `.pbs` whose recovered root is `PbsAst.File`. `mod.barrel` (`BarrelFile`) stays empty. Presence, name, kind, `detail`, and spans come from recovered AST. Do not invent nodes from text search. 5. Query is a case-insensitive substring of `name` only. Blank query returns the full first-wave corpus. Do not match `detail`, path, `containerName`, or qualified `Vec.blend`. No fuzzy ranking, no `top N`. After the filter, order is stable by name, then path, then start offset. 6. `containerName` is the parent name for members and empty for top-level declarations. `detail` may repeat the outline AST signature. Attributes such as `[Init]` do not enter the name. 7. Keep the capability on aggregated `FrontendLanguageService`. Add `workspaceSymbols` (default empty) plus a flat DTO with `name`, `kind`, optional `detail`, `containerName`, absolute path, and offsets. Do not reuse pathless `FrontendDocumentSymbol`. PBS flattens in `prometeu-frontend-pbs`. Common LSP must not import `p.studio.compiler.pbs.*`. No Workspace Symbol SPI, `SymbolId`, `RefIndex`, or snapshot cache. 8. Advertise `workspaceSymbolProvider` when the frontend exposes a language service. Payload is flat `SymbolInformation`. Do not emit hierarchical `DocumentSymbol` on this request. Compile-only frontends stay empty. VS Code stays a thin client. Spec 23 §8.3 now also states: workspace symbols are a flat search of named declarations in compiler-known physical source files of the current analysis; recovered syntax is enough; a missing analysis surface is empty; the request must not require an open document; a declaration without a physical file is omitted; this is not outline, folding, a persistent index, or navigation to virtual locations. ## Final Implementation | Layer | What landed | |---|---| | PBS editorial | `PbsAstWorkspaceSymbols` flattens `PbsAstDocumentSymbols` and filters by name. `PbsEditorialSupportService.workspaceSymbols(ast, query)` delegates to that flatten. | | Generic contract | `FrontendWorkspaceRequest` (`projectRoot`, `query`). `FrontendWorkspaceSymbol` carries path. `FrontendLanguageService.workspaceSymbols` defaults to `List.of()`. `prepareEditorialContext(FrontendPhaseContext)` defaults to empty so a workspace request can obtain project surface without a `FileId`. | | PBS mapping | Project-level `prepareEditorialContext` attaches `fileTable` + `astByFile`. Regular `PbsAst.File` entries are flattened; non-regular handles are skipped. Results sort by name, path, offset. | | LSP | `workspaceSymbolProvider`, `workspace/symbol` on `PrometeuWorkspaceService` (no document URI). `CompilerLanguageServiceBridge` analyzes the project with an empty overlay map. Mapper emits flat `SymbolInformation`. Nested enum/error hits with a container project to `EnumMember`. Compile-only frontends stay empty. | | VS Code | Unchanged thin client. | `workspace/symbol` in this wave reads the project on disk. Unsaved editor overlays are not shared into the search. Empty is honest until a later overlay/snapshot decision. `mod.barrel` is still `BarrelFile`, not `PbsAst.File`. Empty barrel hits are the honest first-wave result. Error case labels still have no per-label span. Flattened children reuse the parent `ErrorDecl` span, same as outline. ## Examples Workspace search of a project file is the flatten of that file's outline, not imports or locals: ```pbs import { Log } from @sdk:log; fn helper() -> int { return 42; } fn frame() -> void { let local = 1; helper(local); } ``` Query `frame` → `frame`. Query `helper` → `helper`. Query empty → `helper`, `frame` (plus other physical decls), sorted. Not `Log`, not `local`. Members keep a container; overloads stay siblings: ```pbs declare struct Vec(x: int) { ctor make(x: int) { return; } fn blend(dx: int, dy: int) -> int { return dx; } } fn helper() -> int { return 42; } fn helper(value: int) -> int { return value; } ``` Query `blend` → method `blend` with `containerName` `Vec`. Query `helper` → two hits. Query `Vec.blend` → empty (this wave matches `name` only). Stdlib remains hover-only while the handle is virtual: ```pbs import { Gfx } from @sdk:gfx; Gfx.clear(0); ``` Query `Gfx` is empty. F12 on `Gfx.clear` is already empty (`LSN-0068`). Workspace search does not reopen that jump. Recovered parse keeps the named decl: ```pbs declare ; fn ok() -> int { return 1; } ``` Query empty → `ok`. The unnamed `InvalidDecl` is omitted. `[Init] fn init()` still names the symbol `init`. ## Pitfalls Do not implement workspace symbols as workspace text search in `lsp-v1` or in the VS Code extension. It will lie about overloads, members, and imports. Do not reuse `FrontendDocumentSymbol`. Outline is pathless because the host already has the document URI. Workspace symbols is a jump and must carry a path. Do not require an open editor buffer to answer `workspace/symbol`. Analyze the project and walk `astByFile`. Do not include `/virtual/stdlib` so Ctrl+T on `Gfx` “does something”. Empty is the honest result until those sources are regular files. Do not wait for snapshot cache. References already walks the current analysis request-locally. Snapshots (`AGD-0051`) can make the walk cheaper without changing this contract. Do not add a `SymbolId` index “for rename later”. Outline already named the declarations; definition already named the destination. Do not match `containerName`, path, or `Vec.blend` in this wave. The query is a substring of `name`. Do not emit hierarchical `DocumentSymbol` on `workspace/symbol`. That payload is outline. Do not treat unsaved overlay absence as a missing feature of this contract. First-wave analysis is the project on disk. Do not split a Workspace Symbol SPI “for future languages”. PBS is a real consumer of `FrontendLanguageService` (`LSN-0067`). Do not treat this lesson as closing rename, snapshots, folding, document links, or hierarchy. ## References - Agenda: `AGD-0045` (Q1 A, Q2 A, Q3 A, Q4 A, Q5 A, Q6 first-wave exclusions) - Decision: `DEC-0055` - Plan: `PLN-0134` - Spec 23 §8.3 — optional workspace symbols; physical-file-or-omit; no open document required - `LSN-0070` — outline set this feature flattens - `LSN-0068` — physical-file destination this feature reuses - `LSN-0069` — project `astByFile` walk already exists for references - `LSN-0058` — generic `FrontendLanguageService` for LSP - `LSN-0047` — protocol stays in `lsp-v1`; compiler owns semantics - `LSN-0067` — no SPI without a real in-repo consumer - Still open: `AGD-0046` rename, `AGD-0051` snapshots, `AGD-0053` folding, `AGD-0055` document links, `AGD-0056` hierarchy