prometeu-studio/discussion/lessons/DSC-0042-pbs-lsp-workspace-symbols/LSN-0071-pbs-workspace-symbols-are-a-physical-named-declaration-search.md
bQUARKz 12e2a4edcc
Some checks are pending
Intrepid/Prometeu/Studio/pipeline/pr-master Build started...
JaCoCo Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 63.37% (18783/29640) * Branch Coverage: 53.99% (7369/13648) * Lines of Code: 29640 * Cyclomatic Complexity: 11938 #### Quality Gates Summary Output truncated.
Test / Build skipped: 15, passed: 674
Intrepid/Prometeu/Studio/pipeline/head This commit looks good
implements PLN-0134
Add PBS workspace symbols as a physical flatten of the outline
named-declaration set on FrontendLanguageService. Regular files
only; virtual stdlib, barrels, imports, locals, and compile-only
frontends stay empty. Advertise workspaceSymbolProvider with flat
SymbolInformation. workspace/symbol does not require an open
document.

Housekeep DSC-0042 with LSN-0071.
2026-09-21 17:18:00 +01:00

150 lines
9.0 KiB
Markdown

---
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