--- id: LSN-0070 ticket: pbs-lsp-document-symbols-outline title: PBS outline is an AST-backed document-symbol tree created: 2026-09-21 tags: [studio, lsp, vscode, compiler-pbs, editor, outline, document-symbols] --- # PBS outline is an AST-backed document-symbol tree ## Original Problem PBS already had compiler-backed completion, hover, signature help, go-to-definition (`LSN-0068`), and find-references (`LSN-0069`). VS Code Outline stayed empty. Long `.pbs` files had to be scanned by hand. The risk was building outline from semantic success (so the tree vanishes while typing), flattening to `SymbolInformation`, inventing a Document Symbol SPI, or stuffing workspace search and folding into the same payload. ## Consolidated Decision PBS Document Symbols is the LSP projection of the **named declaration tree of the open `.pbs` file**, with the **recovered AST as backbone**. Durable locks from `DEC-0054` (AGD-0044 Q1 A, Q2 AST tree, Q3 A, Q4 A, Q5 A, Q6 `.pbs`-only, Q7 existing kinds): 1. Presence, hierarchy, and ranges come from the document AST. Kind and `detail` may use cheap semantic enrichment. Semantics must not drop a named declaration still in the syntax tree, and must not require full analysis. 2. Named `TopDecl` plus syntactic members: - `fn` → Function, no children; - `declare struct` → Struct, children: fields, `ctor`s, methods; - `declare service` → Service, children: methods; - `declare contract` → Contract, children: signatures as methods; - `declare host` → Host, children: signatures; - `declare builtin type` → Builtin type, children: projection fields and signatures; - `declare enum` → Enum, children: cases; - `declare error` → Error, children: labels; - `declare callback` → Callback, no children; - `declare global` / `const` → Global / Const, no children; - `implements Contract for Owner` → top-level Contract named `contractName`, `detail` = owner name, children: methods. Locals, parameters, `this`, imports, and comments stay out. Private fields stay in. Overloads stay siblings. Host and builtin type appear only if the open file declares them. 3. Recovered AST is enough. Unnamed `InvalidDecl` is omitted. Named decls survive later semantic errors. No `PbsAst.File` → empty list. Do not invent nodes from text or token pairs. 4. Keep the capability on aggregated `FrontendLanguageService.documentSymbols` (default empty) plus a hierarchical DTO (`name`, `kind`, optional `detail`, range offsets, selection offsets, `children`). No file path on the DTO. PBS builds the tree in `prometeu-frontend-pbs`. Common LSP must not import `p.studio.compiler.pbs.*`. No Document Symbol SPI. 5. Advertise `documentSymbolProvider`. Payload is hierarchical `DocumentSymbol`, not flat `SymbolInformation`. `range` is the declaration span. `selectionRange` is the name span only if the AST already has one; this wave reuses the declaration span. Do not scan tokens for identifier spans. VS Code stays a thin client. 6. First wave is the current `.pbs` only. Out: imported supplementals, `mod.barrel`, locals, parameters, imports, comments, virtual documents, workspace symbols, folding, rename, snapshot cache. Server order is AST declaration order, not alphabetical. 7. Reuse `FrontendSymbolKind` / `PbsEditorialSymbolKind`. LSP projects to the closest host `SymbolKind`. Name is the identifier. `detail` may carry the AST signature (`(a: int) -> int`). Attributes such as `[Init]` do not enter the name. Outline does not jump to another file. The physical-file destination policy of definition/references is not the outline policy. Spec 23 §8.3 now also states: document symbols are the named declaration tree of the requested document; recovered syntax is enough; a missing syntax tree is empty; this is not a workspace index, folding ranges, or a foreign-file navigation list. ## Final Implementation | Layer | What landed | |---|---| | PBS editorial | `PbsAstDocumentSymbols` walks `PbsAst.File.topDecls` in source order. `PbsEditorialSupportService.documentSymbols` delegates to that walk. | | Generic contract | `FrontendLanguageService.documentSymbols` defaults to `List.of()`. `FrontendDocumentSymbol` is hierarchical and pathless. | | PBS mapping | `PBSFrontendLanguageService` requires `editorialContext.syntaxTree()` to be `PbsAst.File`. Barrels and unknown trees stay empty. Kinds map with `valueOf(kind.name())`. | | LSP | `documentSymbolProvider`, `textDocument/documentSymbol`, hierarchical `DocumentSymbol`. Nested enum/error children project to `EnumMember`. Compile-only frontends stay empty. | | VS Code | Unchanged thin client. | `mod.barrel` is assembled as `BarrelFile`, not `PbsAst.File`, so `astByFile` has no barrel outline. Empty is the honest first-wave result. Error case labels have no per-label span in the AST. Children reuse the parent `ErrorDecl` span rather than scanning tokens. ## Examples Outline of a file is the declarations in that file, not imports or locals: ```pbs import { Log } from @sdk:log; fn helper() -> int { return 42; } fn frame() -> void { let local = 1; helper(local); } ``` Outline: `helper`, `frame`. Not `Log`, not `local`. Struct members and overloads as 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; } ``` Outline: `Vec` (`x`, `make`, `blend`), then two `helper` siblings. Private field `x` is present. `detail` on `blend` is `(dx: int, dy: int) -> int`. Recovered parse keeps the named decl: ```pbs declare ; fn ok() -> int { return 1; } ``` Outline: `ok`. The unnamed `InvalidDecl` is omitted. `[Init] fn init()` still names the symbol `init`. `implements TickLike for Point` is a top-level node named `TickLike` with detail `Point`. ## Pitfalls Do not wait for semantic analysis to succeed before showing outline. The tree must survive broken intermediate edits. Do not omit private fields because they are not in `mod.barrel`. Outline is for the author of this file. Do not collapse overloads. AST identity forbids merging declaration sets. Do not walk `File.imports` or supplemental imported `TopDecl`s. Those symbols are not declared in this document. Do not implement outline for `mod.barrel` in this contract. Barrel AST is a different root. Empty is correct until a later decision. Do not scan tokens to invent a tighter `selectionRange`. This wave uses the declaration span. Do not put a file path on `FrontendDocumentSymbol`. The host already has the document URI. Outline is not go-to-definition. Do not reuse the physical-file jump rule of `LSN-0068` as an outline destination policy. Clicking outline stays inside the buffer. Do not overload document symbols with folding or scope guides (`LSN-0034`, `AGD-0053`). Do not split a Document Symbol SPI “for future languages”. PBS is a real consumer of `FrontendLanguageService` (`LSN-0067`). Do not treat this lesson as closing workspace symbols, folding, rename, document links, or hierarchy. ## References - Agenda: `AGD-0044` (Q1 A, Q2 AST tree, Q3 A, Q4 A, Q5 A, Q6 `.pbs`-only, Q7 existing kinds) - Decision: `DEC-0054` - Plan: `PLN-0133` - Spec 23 §8.3 — optional document symbols; recovered AST tree of the requested document - `LSN-0068` — definition is a jump; outline is not - `LSN-0069` — references invert definition identity - `LSN-0034` — do not overload document symbols with structural anchors - `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-0045` workspace symbols, `AGD-0046` rename, `AGD-0051` snapshots, `AGD-0053` folding, `AGD-0055` document links, `AGD-0056` hierarchy