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
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.
150 lines
9.0 KiB
Markdown
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
|