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

9.0 KiB

id ticket title created tags
LSN-0071 pbs-lsp-workspace-symbols PBS workspace symbols are a physical named-declaration search 2026-09-21
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, ctors, 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 OwnercontractName, 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:

import { Log } from @sdk:log;

fn helper() -> int { return 42; }

fn frame() -> void {
  let local = 1;
  helper(local);
}

Query frameframe. Query helperhelper. Query empty → helper, frame (plus other physical decls), sorted. Not Log, not local.

Members keep a container; overloads stay siblings:

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:

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:

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