Compare commits
No commits in common. "master" and "dev/multi-frontend-architectural-tests" have entirely different histories.
master
...
dev/multi-
@ -1,5 +1,5 @@
|
|||||||
{"type":"meta","next_id":{"DSC":66,"AGD":69,"DEC":55,"PLN":134,"LSN":71,"CLSN":1}}
|
{"type":"meta","next_id":{"DSC":66,"AGD":69,"DEC":51,"PLN":130,"LSN":67,"CLSN":1}}
|
||||||
{"type":"discussion","id":"DSC-0065","status":"done","ticket":"multi-frontend-avoid-premature-abstractions","title":"Evitar abstracoes prematuras na preparacao multi-frontend","created_at":"2026-07-15","updated_at":"2026-09-19","tags":["compiler","compiler-general","studio","frontend","architecture","multi-frontend","simplicity"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0067","file":"discussion/lessons/DSC-0065-multi-frontend-avoid-premature-abstractions/LSN-0067-multi-frontend-phase-rejects-premature-infrastructure.md","status":"done","created_at":"2026-09-19","updated_at":"2026-09-19"}]}
|
{"type":"discussion","id":"DSC-0065","status":"open","ticket":"multi-frontend-avoid-premature-abstractions","title":"Evitar abstracoes prematuras na preparacao multi-frontend","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","studio","frontend","architecture","multi-frontend","simplicity"],"agendas":[{"id":"AGD-0068","file":"AGD-0068-multi-frontend-avoid-premature-abstractions.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0064","status":"done","ticket":"multi-frontend-architectural-tests","title":"Testes arquiteturais para fronteiras multi-frontend","created_at":"2026-07-15","updated_at":"2026-09-19","tags":["compiler","compiler-general","studio","frontend","architecture","tests","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0066","file":"discussion/lessons/DSC-0064-multi-frontend-architectural-tests/LSN-0066-archunit-guards-java-multi-frontend-boundaries.md","status":"done","created_at":"2026-09-19","updated_at":"2026-09-19"}]}
|
{"type":"discussion","id":"DSC-0064","status":"done","ticket":"multi-frontend-architectural-tests","title":"Testes arquiteturais para fronteiras multi-frontend","created_at":"2026-07-15","updated_at":"2026-09-19","tags":["compiler","compiler-general","studio","frontend","architecture","tests","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0066","file":"discussion/lessons/DSC-0064-multi-frontend-architectural-tests/LSN-0066-archunit-guards-java-multi-frontend-boundaries.md","status":"done","created_at":"2026-09-19","updated_at":"2026-09-19"}]}
|
||||||
{"type":"discussion","id":"DSC-0063","status":"done","ticket":"multi-frontend-synthetic-test-frontend","title":"Frontend sintetico de teste para provar neutralidade do pipeline","created_at":"2026-07-15","updated_at":"2026-09-19","tags":["compiler","compiler-general","frontend","tests","backend","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0065","file":"discussion/lessons/DSC-0063-multi-frontend-synthetic-test-frontend/LSN-0065-synthetic-test-frontend-proves-pipeline-neutrality.md","status":"done","created_at":"2026-09-19","updated_at":"2026-09-19"}]}
|
{"type":"discussion","id":"DSC-0063","status":"done","ticket":"multi-frontend-synthetic-test-frontend","title":"Frontend sintetico de teste para provar neutralidade do pipeline","created_at":"2026-07-15","updated_at":"2026-09-19","tags":["compiler","compiler-general","frontend","tests","backend","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0065","file":"discussion/lessons/DSC-0063-multi-frontend-synthetic-test-frontend/LSN-0065-synthetic-test-frontend-proves-pipeline-neutrality.md","status":"done","created_at":"2026-09-19","updated_at":"2026-09-19"}]}
|
||||||
{"type":"discussion","id":"DSC-0062","status":"done","ticket":"multi-frontend-pvm-neutrality","title":"Neutralidade da PVM e identificacao PBX independente de PBS","created_at":"2026-07-15","updated_at":"2026-09-18","tags":["vm-arch","runtime","pvm","pbx","compiler","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0061","file":"discussion/lessons/DSC-0062-multi-frontend-pvm-neutrality/LSN-0061-pbx-runtime-boundary.md","status":"done","created_at":"2026-09-18","updated_at":"2026-09-18"}]}
|
{"type":"discussion","id":"DSC-0062","status":"done","ticket":"multi-frontend-pvm-neutrality","title":"Neutralidade da PVM e identificacao PBX independente de PBS","created_at":"2026-07-15","updated_at":"2026-09-18","tags":["vm-arch","runtime","pvm","pbx","compiler","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0061","file":"discussion/lessons/DSC-0062-multi-frontend-pvm-neutrality/LSN-0061-pbx-runtime-boundary.md","status":"done","created_at":"2026-09-18","updated_at":"2026-09-18"}]}
|
||||||
@ -23,9 +23,9 @@
|
|||||||
{"type":"discussion","id":"DSC-0044","status":"open","ticket":"pbs-lsp-code-actions","title":"PBS LSP Code Actions and Quick Fixes","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["studio","lsp","vscode","compiler-pbs","editor","code-actions","quick-fix"],"agendas":[{"id":"AGD-0047","file":"AGD-0047-pbs-lsp-code-actions-and-quick-fixes.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0044","status":"open","ticket":"pbs-lsp-code-actions","title":"PBS LSP Code Actions and Quick Fixes","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["studio","lsp","vscode","compiler-pbs","editor","code-actions","quick-fix"],"agendas":[{"id":"AGD-0047","file":"AGD-0047-pbs-lsp-code-actions-and-quick-fixes.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0043","status":"open","ticket":"pbs-lsp-rename-symbol","title":"PBS LSP Rename Symbol","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["studio","lsp","vscode","compiler-pbs","editor","rename"],"agendas":[{"id":"AGD-0046","file":"AGD-0046-pbs-lsp-rename-symbol.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0043","status":"open","ticket":"pbs-lsp-rename-symbol","title":"PBS LSP Rename Symbol","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["studio","lsp","vscode","compiler-pbs","editor","rename"],"agendas":[{"id":"AGD-0046","file":"AGD-0046-pbs-lsp-rename-symbol.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0042","status":"open","ticket":"pbs-lsp-workspace-symbols","title":"PBS LSP Workspace Symbols","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["studio","lsp","vscode","compiler-pbs","editor","workspace-symbols"],"agendas":[{"id":"AGD-0045","file":"AGD-0045-pbs-lsp-workspace-symbols.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0042","status":"open","ticket":"pbs-lsp-workspace-symbols","title":"PBS LSP Workspace Symbols","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["studio","lsp","vscode","compiler-pbs","editor","workspace-symbols"],"agendas":[{"id":"AGD-0045","file":"AGD-0045-pbs-lsp-workspace-symbols.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0041","status":"done","ticket":"pbs-lsp-document-symbols-outline","title":"PBS LSP Document Symbols and Outline","created_at":"2026-07-15","updated_at":"2026-09-21","tags":["studio","lsp","vscode","compiler-pbs","editor","outline","document-symbols"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0070","file":"discussion/lessons/DSC-0041-pbs-lsp-document-symbols-outline/LSN-0070-pbs-outline-is-an-ast-backed-document-symbol-tree.md","status":"done","created_at":"2026-09-21","updated_at":"2026-09-21"}]}
|
{"type":"discussion","id":"DSC-0041","status":"open","ticket":"pbs-lsp-document-symbols-outline","title":"PBS LSP Document Symbols and Outline","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["studio","lsp","vscode","compiler-pbs","editor","outline","document-symbols"],"agendas":[{"id":"AGD-0044","file":"AGD-0044-pbs-lsp-document-symbols-and-outline.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0040","status":"done","ticket":"pbs-lsp-find-references","title":"PBS LSP Find References","created_at":"2026-07-15","updated_at":"2026-09-21","tags":["studio","lsp","vscode","compiler-pbs","editor","references"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0069","file":"discussion/lessons/DSC-0040-pbs-lsp-find-references/LSN-0069-pbs-find-references-is-the-physical-inverse-of-definition.md","status":"done","created_at":"2026-09-21","updated_at":"2026-09-21"}]}
|
{"type":"discussion","id":"DSC-0040","status":"open","ticket":"pbs-lsp-find-references","title":"PBS LSP Find References","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["studio","lsp","vscode","compiler-pbs","editor","references"],"agendas":[{"id":"AGD-0043","file":"AGD-0043-pbs-lsp-find-references.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0039","status":"done","ticket":"pbs-lsp-go-to-definition","title":"PBS LSP Go to Definition","created_at":"2026-07-15","updated_at":"2026-09-21","tags":["studio","lsp","vscode","compiler-pbs","editor","definition"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0068","file":"discussion/lessons/DSC-0039-pbs-lsp-go-to-definition/LSN-0068-pbs-go-to-definition-uses-editorial-lookup-and-physical-files.md","status":"done","created_at":"2026-09-21","updated_at":"2026-09-21"}]}
|
{"type":"discussion","id":"DSC-0039","status":"open","ticket":"pbs-lsp-go-to-definition","title":"PBS LSP Go to Definition","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["studio","lsp","vscode","compiler-pbs","editor","definition"],"agendas":[{"id":"AGD-0042","file":"AGD-0042-pbs-lsp-go-to-definition.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0038","status":"done","ticket":"studio-packer-rgba8888-asset-pipeline","title":"Studio and Packer RGBA8888 Asset Pipeline Alignment","created_at":"2026-05-23","updated_at":"2026-07-14","tags":["studio","packer","assets","glyph-bank","palette","rgba8888","runtime-alignment"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0053","file":"discussion/lessons/DSC-0038-studio-packer-rgba8888-asset-pipeline/LSN-0053-rgba8888-is-the-canonical-studio-packer-palette-contract.md","status":"done","created_at":"2026-07-14","updated_at":"2026-07-14"}]}
|
{"type":"discussion","id":"DSC-0038","status":"done","ticket":"studio-packer-rgba8888-asset-pipeline","title":"Studio and Packer RGBA8888 Asset Pipeline Alignment","created_at":"2026-05-23","updated_at":"2026-07-14","tags":["studio","packer","assets","glyph-bank","palette","rgba8888","runtime-alignment"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0053","file":"discussion/lessons/DSC-0038-studio-packer-rgba8888-asset-pipeline/LSN-0053-rgba8888-is-the-canonical-studio-packer-palette-contract.md","status":"done","created_at":"2026-07-14","updated_at":"2026-07-14"}]}
|
||||||
{"type":"discussion","id":"DSC-0037","status":"done","ticket":"pbs-autocomplete-parameter-names","title":"PBS autocomplete parameter names for stdlib and method calls","created_at":"2026-05-08","updated_at":"2026-05-14","tags":["compiler-pbs","studio","lsp","autocomplete","signature-help","stdlib"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0052","file":"discussion/lessons/DSC-0037-pbs-autocomplete-parameter-names/LSN-0052-canonical-callable-parameter-names-through-pbs-editor-assistance.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14"}]}
|
{"type":"discussion","id":"DSC-0037","status":"done","ticket":"pbs-autocomplete-parameter-names","title":"PBS autocomplete parameter names for stdlib and method calls","created_at":"2026-05-08","updated_at":"2026-05-14","tags":["compiler-pbs","studio","lsp","autocomplete","signature-help","stdlib"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0052","file":"discussion/lessons/DSC-0037-pbs-autocomplete-parameter-names/LSN-0052-canonical-callable-parameter-names-through-pbs-editor-assistance.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14"}]}
|
||||||
{"type":"discussion","id":"DSC-0036","status":"done","ticket":"pbs-symbol-documentation-and-hover-markdown","title":"Modelo de documentacao de simbolos em PBS e consumo markdown no hover","created_at":"2026-05-08","updated_at":"2026-07-15","tags":["compiler","compiler-pbs","studio","lsp","vscode","editor","hover","documentation","markdown"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0057","file":"discussion/lessons/DSC-0036-pbs-symbol-documentation-and-hover-markdown/LSN-0057-compiler-owned-pbs-doc-markdown-for-editor-assistance.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15"}]}
|
{"type":"discussion","id":"DSC-0036","status":"done","ticket":"pbs-symbol-documentation-and-hover-markdown","title":"Modelo de documentacao de simbolos em PBS e consumo markdown no hover","created_at":"2026-05-08","updated_at":"2026-07-15","tags":["compiler","compiler-pbs","studio","lsp","vscode","editor","hover","documentation","markdown"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0057","file":"discussion/lessons/DSC-0036-pbs-symbol-documentation-and-hover-markdown/LSN-0057-compiler-owned-pbs-doc-markdown-for-editor-assistance.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15"}]}
|
||||||
|
|||||||
@ -1,98 +0,0 @@
|
|||||||
---
|
|
||||||
id: LSN-0068
|
|
||||||
ticket: pbs-lsp-go-to-definition
|
|
||||||
title: PBS go-to-definition uses editorial lookup and physical files
|
|
||||||
created: 2026-09-21
|
|
||||||
tags: [studio, lsp, vscode, compiler-pbs, editor, definition]
|
|
||||||
---
|
|
||||||
|
|
||||||
# PBS go-to-definition uses editorial lookup and physical files
|
|
||||||
|
|
||||||
## Original Problem
|
|
||||||
|
|
||||||
PBS editor assistance already had compiler-backed completion, hover, and signature help (`LSN-0051`). Users still could not jump from a use to a declaration. Wave 1 explicitly deferred `go to definition`.
|
|
||||||
|
|
||||||
The remaining LSP navigation family (references, rename, workspace symbols, document links, hierarchy) needed a destination policy before those features could be honest. The risk was shipping a text search, a new `SymbolId` index, or virtual documents for stdlib before a real physical target existed.
|
|
||||||
|
|
||||||
## Consolidated Decision
|
|
||||||
|
|
||||||
PBS Go to Definition is the LSP projection of **physical declaration locations** on top of the **same editorial lookup hover already uses**.
|
|
||||||
|
|
||||||
Durable locks from `DEC-0052`:
|
|
||||||
|
|
||||||
1. Resolve the identifier under the cursor through the hover path (locals, parameters, members, imports/supplementals, current-module top-level). Do not use text search or a new usage index.
|
|
||||||
2. Keep the capability on the existing aggregated `FrontendLanguageService`. Add a default-empty `definition` method and a generic location DTO. Do not add a Definition SPI.
|
|
||||||
3. Map PBS editorial spans inside `prometeu-frontend-pbs`. Common LSP must not import `p.studio.compiler.pbs.*`.
|
|
||||||
4. Jump only when `Span.fileId` maps to a **regular filesystem file**. Otherwise return an empty list. Do not create virtual documents, `untitled:` buffers, or synthetic URIs.
|
|
||||||
5. The destination is the canonical declaration, not the import-alias line. Overloads return every matching location.
|
|
||||||
6. Advertise `definitionProvider`. Partial coverage is valid. Keywords, source-less builtins, unresolved names, and virtual stdlib paths stay empty; hover is unchanged.
|
|
||||||
7. Analysis stays request-local, like hover. This discussion does not build references, rename, or snapshot cache.
|
|
||||||
|
|
||||||
Spec 23 §8.3 already listed definition as an optional editor capability. It now also states: a definition location must identify a compiler-known physical source file, or the response must be empty.
|
|
||||||
|
|
||||||
## Final Implementation
|
|
||||||
|
|
||||||
| Layer | What landed |
|
|
||||||
|---|---|
|
|
||||||
| PBS editorial | `PbsEditorialSupportService.definition` reuses hover dispatch and attaches declaration `Span`s (`PbsEditorialLocation`). |
|
|
||||||
| Generic contract | `FrontendDefinitionLocation` (absolute path + offsets) and `FrontendLanguageService.definition` defaulting to `List.of()`. |
|
|
||||||
| PBS mapping | `PBSFrontendLanguageService` maps `FileId` through `fileTable` and keeps the location only if `Files.isRegularFile(path)`. |
|
|
||||||
| LSP | `LanguageServiceBridge.definition`, `definitionProvider`, `textDocument/definition`. Compile-only frontends return empty. |
|
|
||||||
| VS Code | Unchanged thin client. |
|
|
||||||
|
|
||||||
Stdlib and SDK modules are still registered under virtual paths such as `/virtual/stdlib/...`. Those handles are not regular files, so F12 on `Gfx.clear` is empty even though hover works. That is the physical-file rule, not a missing lookup.
|
|
||||||
|
|
||||||
The location payload (path + span of an already resolved symbol) is reusable later by references (`AGD-0043`) and rename (`AGD-0046`). Those indexes were not built here. Snapshots (`AGD-0051`) remain a later performance discussion, not a prerequisite for this request-local feature.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
Same-file jump:
|
|
||||||
|
|
||||||
```pbs
|
|
||||||
fn helper() -> int { return 42; }
|
|
||||||
|
|
||||||
fn frame() -> void {
|
|
||||||
helper(); // definition -> helper's declaration in this file
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Aliased import jumps to the original declaration span, not the `import` line:
|
|
||||||
|
|
||||||
```pbs
|
|
||||||
import { Log as Logger } from @sdk:log;
|
|
||||||
Logger.info("hello");
|
|
||||||
```
|
|
||||||
|
|
||||||
If `@sdk:log` is only a virtual stdlib handle, definition is empty. If a project file owns the declaration and that file exists on disk, definition uses that path.
|
|
||||||
|
|
||||||
Builtins stay hover-only:
|
|
||||||
|
|
||||||
```pbs
|
|
||||||
let n: int = 1; // hover describes the builtin; definition is empty
|
|
||||||
```
|
|
||||||
|
|
||||||
## Pitfalls
|
|
||||||
|
|
||||||
Do not implement definition as a second name search in `lsp-v1` or in the VS Code extension. It will break on imports, overloads, and members, then be thrown away when references arrive.
|
|
||||||
|
|
||||||
Do not open `/virtual/stdlib/...` or invent an `untitled:` document so stdlib F12 “does something”. Empty is the honest result until those sources are real files.
|
|
||||||
|
|
||||||
Do not pick the first overload location in the server. Return the full `Location[]`.
|
|
||||||
|
|
||||||
Do not wait for snapshot cache before definition. Hover is already request-local; definition follows that shape.
|
|
||||||
|
|
||||||
Do not split a new top-level definition service “for future languages”. PBS is a real consumer of the existing language-service surface (`LSN-0067`).
|
|
||||||
|
|
||||||
Do not treat this lesson as closing references, rename, outline, workspace symbols, or document links. Those agendas stay open. This lesson only locks the destination policy they can reuse.
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- Agenda: `AGD-0042` (Q1–Q6 = A)
|
|
||||||
- Decision: `DEC-0052`
|
|
||||||
- Plan: `PLN-0131`
|
|
||||||
- Spec 23 §8.3 — optional definition capability; physical-file-or-empty
|
|
||||||
- `LSN-0051` — wave 1 deferred go-to-definition
|
|
||||||
- `LSN-0058` — generic `FrontendLanguageService` for LSP
|
|
||||||
- `LSN-0047` — protocol stays in `lsp-v1`; compiler owns semantics
|
|
||||||
- `LSN-0067` — no SPI or virtual-document host without a real consumer
|
|
||||||
- Still open: `AGD-0043` references, `AGD-0045` workspace symbols, `AGD-0046` rename, `AGD-0051` snapshots, `AGD-0055` document links, `AGD-0056` hierarchy
|
|
||||||
@ -1,111 +0,0 @@
|
|||||||
---
|
|
||||||
id: LSN-0069
|
|
||||||
ticket: pbs-lsp-find-references
|
|
||||||
title: PBS find-references is the physical inverse of definition
|
|
||||||
created: 2026-09-21
|
|
||||||
tags: [studio, lsp, vscode, compiler-pbs, editor, references]
|
|
||||||
---
|
|
||||||
|
|
||||||
# PBS find-references is the physical inverse of definition
|
|
||||||
|
|
||||||
## Original Problem
|
|
||||||
|
|
||||||
PBS already had compiler-backed completion, hover, signature help, and go-to-definition (`LSN-0051`, `LSN-0068`). Users still could not ask where a resolved symbol is used. Text search confuses overloads, shadowing, members, comments, and coincidental names. Rename (`AGD-0046`) and hierarchy (`AGD-0056`) need an honest usage list before they exist.
|
|
||||||
|
|
||||||
The risk was inventing a `SymbolId` / `RefIndex`, waiting for snapshot cache (`AGD-0051`), or listing uses of stdlib APIs whose declarations are still virtual paths.
|
|
||||||
|
|
||||||
## Consolidated Decision
|
|
||||||
|
|
||||||
PBS Find References is the LSP projection of **physical usage sites** of the **same identity definition already returns**.
|
|
||||||
|
|
||||||
Durable locks from `DEC-0053` (AGD-0043 Q1–Q6 = A):
|
|
||||||
|
|
||||||
1. Resolve the cursor through the definition path (locals, parameters, members, imports/supplementals, current-module top-level). Identity is that physical declaration-location set. Do not text-search. Do not add `SymbolId` or a persistent `RefIndex`.
|
|
||||||
2. A site is a usage when its definition locations intersect the cursor identity. Overloads follow that intersection.
|
|
||||||
3. If definition at the same offset is empty, references is empty.
|
|
||||||
4. Walk regular `fileTable` files from the current `analyze()` surface. Do not scan `/virtual/stdlib` or other non-regular handles.
|
|
||||||
5. Keep the capability on the existing aggregated `FrontendLanguageService`. Reuse `FrontendDefinitionLocation`. Map PBS spans in `prometeu-frontend-pbs`. Common LSP must not import `p.studio.compiler.pbs.*`. No References SPI.
|
|
||||||
6. Honor LSP `includeDeclaration` in the server: union usage sites with definition locations when the host asks and the declaration is a regular file. No read/write classification.
|
|
||||||
7. Advertise `referencesProvider`. Partial coverage is valid. Analysis stays request-local. This discussion does not build rename, hierarchy, workspace symbols, virtual documents, or snapshot cache.
|
|
||||||
|
|
||||||
Spec 23 §8.3 now also states: a references location must identify a compiler-known physical source file; empty definition implies empty references; `includeDeclaration` must not invent a virtual location.
|
|
||||||
|
|
||||||
## Final Implementation
|
|
||||||
|
|
||||||
| Layer | What landed |
|
|
||||||
|---|---|
|
|
||||||
| PBS editorial | `PbsEditorialSupportService.references` walks `IDENTIFIER` tokens and keeps sites whose `definition` locations intersect the cursor identity (`FileId` + start + end). |
|
|
||||||
| Generic contract | `FrontendLanguageService.references` defaults to `List.of()`. `FrontendEditorialContext.projectSurface` carries the PBS semantic read surface so the walk can see `astByFile`. |
|
|
||||||
| PBS mapping | `PBSFrontendLanguageService` requires a non-empty physical definition set, skips non-regular files, and maps surviving spans through the same `Files.isRegularFile` helper as definition. |
|
|
||||||
| LSP | `referencesProvider`, `textDocument/references`. The bridge applies `includeDeclaration` by unioning `references` with `definition`. Compile-only frontends stay empty. |
|
|
||||||
| VS Code | Unchanged thin client. |
|
|
||||||
|
|
||||||
Stdlib and SDK modules remain virtual (`/virtual/stdlib/...`). Shift+F12 on `Gfx.clear` is empty, matching F12. Hover is unchanged.
|
|
||||||
|
|
||||||
Local binding names are not usage sites: definition at `let total` is empty because the name is not yet in scope. The use `return total` matches. `includeDeclaration` adds the declaration span from definition, not a second name search.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
Same-file function uses, including the declaring identifier (top-level names resolve to themselves):
|
|
||||||
|
|
||||||
```pbs
|
|
||||||
fn helper() -> int { return 42; }
|
|
||||||
|
|
||||||
fn frame() -> void {
|
|
||||||
helper();
|
|
||||||
helper();
|
|
||||||
// helper // not a reference
|
|
||||||
let text = "helper"; // not a reference
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Imported project (or otherwise physical) symbols collect uses in the importing file. Identity is the foreign declaration span.
|
|
||||||
|
|
||||||
```pbs
|
|
||||||
import { Log as Logger } from @sdk:log;
|
|
||||||
Logger.info("a");
|
|
||||||
Logger.info("b");
|
|
||||||
```
|
|
||||||
|
|
||||||
If `@sdk:log` is only a virtual handle, definition is empty, so references is empty.
|
|
||||||
|
|
||||||
Builtins stay hover-only:
|
|
||||||
|
|
||||||
```pbs
|
|
||||||
let n: int = 1; // hover works; references is empty
|
|
||||||
```
|
|
||||||
|
|
||||||
`includeDeclaration=true` adds the canonical declaration location on top of identifier usage sites. `false` returns usages only.
|
|
||||||
|
|
||||||
## Pitfalls
|
|
||||||
|
|
||||||
Do not implement find-references as workspace text search in `lsp-v1` or in the VS Code extension. It will lie about overloads, members, and imports, then poison rename.
|
|
||||||
|
|
||||||
Do not add a `SymbolId` index “for rename later”. Definition already gave the key: physical declaration locations. Rename can consume this list once it has its own decision.
|
|
||||||
|
|
||||||
Do not wait for snapshot cache. The LSP already reanalyzes on each editorial request. Snapshots (`AGD-0051`) can make the walk cheaper without changing the contract.
|
|
||||||
|
|
||||||
Do not treat empty stdlib references as a missing feature. Virtual paths are not regular files. Empty is the honest result until those sources live on disk.
|
|
||||||
|
|
||||||
Do not expect `let total` to appear as a usage of `total`. Inverse-of-definition only keeps identifier sites that themselves resolve. The declaration span arrives through `includeDeclaration`.
|
|
||||||
|
|
||||||
Do not put `includeDeclaration` into PBS types. It is a host flag applied in the LSP bridge.
|
|
||||||
|
|
||||||
Do not scan `/virtual/stdlib` to “find more uses”. That reopens virtual documents.
|
|
||||||
|
|
||||||
Do not split a References SPI “for future languages”. PBS is a real consumer of `FrontendLanguageService` (`LSN-0067`).
|
|
||||||
|
|
||||||
Do not treat this lesson as closing rename, snapshots, workspace symbols, document links, or hierarchy.
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- Agenda: `AGD-0043` (Q1–Q6 = A)
|
|
||||||
- Decision: `DEC-0053`
|
|
||||||
- Plan: `PLN-0132`
|
|
||||||
- Spec 23 §8.3 — optional navigation; physical-file-or-empty; empty definition implies empty references
|
|
||||||
- `LSN-0068` — definition identity this feature inverts
|
|
||||||
- `LSN-0051` — wave 1 deferred navigation
|
|
||||||
- `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-0045` workspace symbols, `AGD-0051` snapshots, `AGD-0055` document links, `AGD-0056` hierarchy
|
|
||||||
@ -1,141 +0,0 @@
|
|||||||
---
|
|
||||||
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
|
|
||||||
@ -1,167 +0,0 @@
|
|||||||
---
|
|
||||||
id: LSN-0067
|
|
||||||
ticket: multi-frontend-avoid-premature-abstractions
|
|
||||||
title: Multi-frontend phase rejects premature infrastructure
|
|
||||||
created: 2026-09-19
|
|
||||||
tags: [compiler, compiler-general, studio, frontend, architecture, multi-frontend, simplicity]
|
|
||||||
---
|
|
||||||
|
|
||||||
# Multi-frontend phase rejects premature infrastructure
|
|
||||||
|
|
||||||
## Original Problem
|
|
||||||
|
|
||||||
The multi-frontend preparation needed a standing simplicity rule. The original
|
|
||||||
agenda asked for that rule *before* the other multi-frontend discussions.
|
|
||||||
Those discussions already closed (DSC-0054 through DSC-0064). The remaining
|
|
||||||
risk was not a missing plugin platform.
|
|
||||||
|
|
||||||
The remaining risk was twofold:
|
|
||||||
|
|
||||||
1. using "keep it simple" to put PBS construction back into common compiler,
|
|
||||||
Studio, LSP, or app code;
|
|
||||||
2. inventing marketplace, JAR loading, RPC, validator-core, or a generic SPI
|
|
||||||
"for a future language" that does not exist in this repository.
|
|
||||||
|
|
||||||
`FrontendRegistryService` is a static singleton. The original agenda listed
|
|
||||||
a global service locator as out of scope. Without an explicit exception, a
|
|
||||||
later change could inject a `Registry` into the pipeline for purity, which
|
|
||||||
DSC-0063 already refused.
|
|
||||||
|
|
||||||
## Consolidated Decision
|
|
||||||
|
|
||||||
This is standing policy for *future* work in the current phase: PBS as the
|
|
||||||
only product frontend, `synth` as a test-only provider, frontends in-repo.
|
|
||||||
It is not an audit of closed discussions and must not rewrite them.
|
|
||||||
|
|
||||||
Durable locks from DEC-0051:
|
|
||||||
|
|
||||||
- Implementation of this discussion is the decision plus this lesson. There
|
|
||||||
is no new spec, no new ArchUnit rule, and no production refactor.
|
|
||||||
- Spec 23 §2 and §8.3 remain the discovery MUST NOT surface. Do not copy
|
|
||||||
those sentences into a new spec.
|
|
||||||
- Architectural tests stay owned by LSN-0066.
|
|
||||||
|
|
||||||
### Static registry is allowed
|
|
||||||
|
|
||||||
`FrontendRegistryService` as a static singleton, with public `register` and
|
|
||||||
`unregister` and `bootstrapDefaults()` registering only PBS, is the
|
|
||||||
composition mechanism of this phase. Treat that as an explicit exception,
|
|
||||||
not as a banned service locator.
|
|
||||||
|
|
||||||
Discovery must not use `ServiceLoader`, classpath scanning, reflection-based
|
|
||||||
discovery, an external JAR, or runtime plugin installation.
|
|
||||||
|
|
||||||
Do not inject a `Registry` into the pipeline, LSP, or `AppContainer`.
|
|
||||||
`AppContainer` is not a PBS composition root. It may call
|
|
||||||
`FrontendRegistryService.bootstrapDefaults()`. It must not instantiate PBS
|
|
||||||
types.
|
|
||||||
|
|
||||||
### Real in-repo consumer
|
|
||||||
|
|
||||||
A new interface, module, SPI, generator, or framework is allowed only when
|
|
||||||
a real in-repo consumer exists. In this phase that consumer is one of:
|
|
||||||
|
|
||||||
- the PBS product frontend;
|
|
||||||
- the `synth` test frontend;
|
|
||||||
- a second product frontend accepted by its own decision.
|
|
||||||
|
|
||||||
"For a future language", "for the marketplace", or "when we have plugins"
|
|
||||||
is not a consumer.
|
|
||||||
|
|
||||||
`FrontendProvider`, `IRBackend`, common lifecycle assembly, and test-only
|
|
||||||
ArchUnit are not premature. They already have consumers.
|
|
||||||
|
|
||||||
### Forbidden in this phase
|
|
||||||
|
|
||||||
Until a later decision authorizes it with a real consumer, do not introduce:
|
|
||||||
|
|
||||||
1. dynamic frontend discovery (plugin, `ServiceLoader`, scan, external JAR,
|
|
||||||
runtime install);
|
|
||||||
2. RPC, sockets, a process supervisor, or version negotiation between
|
|
||||||
compiler and frontend;
|
|
||||||
3. a marketplace, hot reload, or frontend sandbox;
|
|
||||||
4. a validator-core or generic validation framework;
|
|
||||||
5. an SDK schema/generator or `HOSTCALLS.csv` as canonical ABI (LSN-0064);
|
|
||||||
6. a product module `frontends/synth`;
|
|
||||||
7. an injected `Registry`, or `AppContainer` as the PBS composition root.
|
|
||||||
|
|
||||||
This list is not a monorepo-wide simplicity charter. It does not govern
|
|
||||||
JavaFX, packer, or LSP protocol design. Owner is `compiler/general`, with
|
|
||||||
impact on `studio` on the multi-frontend axis.
|
|
||||||
|
|
||||||
### Neighbors stay closed
|
|
||||||
|
|
||||||
Do not reinterpret, relax, "simplify", or reimplement:
|
|
||||||
|
|
||||||
- provider/registry (LSN-0055 / spec 23 §8.3);
|
|
||||||
- common lifecycle;
|
|
||||||
- console ABI / PBS delivery;
|
|
||||||
- `synth`;
|
|
||||||
- ArchUnit / PBS module identity.
|
|
||||||
|
|
||||||
Using this policy to keep `new PBSFrontendPhaseService()` (or equivalent)
|
|
||||||
in common code is refused. That is PBS recoupling, not simplicity.
|
|
||||||
|
|
||||||
A second product frontend needs its own decision. This lesson neither
|
|
||||||
replaces that decision nor blocks it.
|
|
||||||
|
|
||||||
## Final Implementation
|
|
||||||
|
|
||||||
No production Java, Gradle, spec, or ArchUnit file changed.
|
|
||||||
|
|
||||||
The map above is the implementation. Future compiler/studio plans on this
|
|
||||||
axis should cite it when they propose a new module, SPI, or discovery
|
|
||||||
mechanism.
|
|
||||||
|
|
||||||
Code that already matches the phase:
|
|
||||||
|
|
||||||
- `FrontendProvider` exposes `specification()`, `compiler()`, and optional
|
|
||||||
`languageService()`;
|
|
||||||
- `FrontendRegistryService.bootstrapDefaults()` registers PBS only;
|
|
||||||
- `AppContainer` only calls that bootstrap;
|
|
||||||
- `synth` lives in build-pipeline test sources;
|
|
||||||
- ArchUnit lives in `:prometeu-architecture-tests`.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
Good:
|
|
||||||
|
|
||||||
- add a method to `FrontendProvider` because PBS or `synth` cannot compile
|
|
||||||
or register without it;
|
|
||||||
- keep `unregister` on the static registry so `synth` tests can clean up;
|
|
||||||
- reject a proposal to load language JARs from a plugin directory.
|
|
||||||
|
|
||||||
Bad:
|
|
||||||
|
|
||||||
- inject `Registry` into `BuilderPipelineService` because static state looks
|
|
||||||
like a service locator;
|
|
||||||
- create `frontends/synth` as a product module;
|
|
||||||
- add validator-core "so the next language has somewhere to put checks";
|
|
||||||
- keep `new PBSFrontendPhaseService()` in pipeline code and call that
|
|
||||||
simpler than going through the provider.
|
|
||||||
|
|
||||||
## Pitfalls
|
|
||||||
|
|
||||||
- The original agenda order ("before the other agendas") is historical.
|
|
||||||
Do not reopen DSC-0054..DSC-0064 to satisfy it.
|
|
||||||
- Static registry plus explicit `register`/`unregister` is visible. Hidden
|
|
||||||
`ServiceLoader` discovery is not the same thing.
|
|
||||||
- A second product frontend *is* a real consumer. Do not use this lesson to
|
|
||||||
refuse that frontend; use it to refuse infrastructure that frontend does
|
|
||||||
not yet need.
|
|
||||||
- Spec 23 already forbids plugin discovery. Do not duplicate it. Do not
|
|
||||||
treat silence in spec 23 as permission for RPC or validator-core.
|
|
||||||
- Simplicity does not mean PBS may leak into common modules. LSN-0066 still
|
|
||||||
owns that boundary.
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- Decision: `DEC-0051`
|
|
||||||
- Plan: `PLN-0130`
|
|
||||||
- Related lessons: `LSN-0055`, `LSN-0060`, `LSN-0063`, `LSN-0064`,
|
|
||||||
`LSN-0065`, `LSN-0066`
|
|
||||||
- `docs/specs/compiler/23. Compiler Pipeline Entry Points Specification.md`
|
|
||||||
(§2, §8.3)
|
|
||||||
- `prometeu-compiler/prometeu-frontend-registry/src/main/java/p/studio/compiler/FrontendRegistryService.java`
|
|
||||||
- `prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/services/FrontendProvider.java`
|
|
||||||
- `prometeu-app/src/main/java/p/studio/AppContainer.java`
|
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
---
|
||||||
|
id: AGD-0042
|
||||||
|
ticket: pbs-lsp-go-to-definition
|
||||||
|
title: PBS LSP Go to Definition
|
||||||
|
status: open
|
||||||
|
created: 2026-07-15
|
||||||
|
resolved:
|
||||||
|
decision:
|
||||||
|
tags: [studio, lsp, vscode, compiler-pbs, editor, definition]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pain
|
||||||
|
|
||||||
|
Domain owner: `studio/lsp`
|
||||||
|
|
||||||
|
PBS users cannot jump from a symbol use to its declaration, which makes navigation across source files, stdlib imports, services, and generated/editorial surfaces slow and error-prone.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The current LSP exposes diagnostics, hover, completion, signature help, and semantic tokens. The compiler already builds semantic read surfaces and editorial symbol resolution for hover/completion, but there is no LSP definition capability or stable location mapping exposed to VS Code.
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
- [ ] Which symbol categories must be supported in the first definition wave?
|
||||||
|
- [ ] Should stdlib and generated/supplemental declarations navigate to source files, virtual documents, or remain hover-only?
|
||||||
|
- [ ] What identity model should connect an editor token to the canonical declaration without relying on text search?
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
### Option A - Syntactic location lookup
|
||||||
|
|
||||||
|
- **Approach:** Use parser spans and local AST traversal to map the token under cursor to a declaration with matching text.
|
||||||
|
- **Pro:** Fast to implement and useful for same-file declarations.
|
||||||
|
- **Con:** Breaks on overloads, imports, stdlib, aliases, and same-name symbols.
|
||||||
|
- **Maintainability:** Weak; it creates a second navigation model separate from semantic resolution.
|
||||||
|
|
||||||
|
### Option B - Semantic identity lookup
|
||||||
|
|
||||||
|
- **Approach:** Extend the compiler/editorial surface with resolved declaration locations keyed by symbol identity, then expose `textDocument/definition`.
|
||||||
|
- **Pro:** Correct for imports, overloads, methods, stdlib-backed declarations, and future references/rename.
|
||||||
|
- **Con:** Requires explicit location metadata in semantic/editorial surfaces.
|
||||||
|
- **Maintainability:** Strong; it becomes shared infrastructure for references, rename, hierarchy, and code actions.
|
||||||
|
|
||||||
|
## Tradeoffs
|
||||||
|
|
||||||
|
The syntactic option is tempting for speed, but it would likely be thrown away once references and rename arrive. The main tradeoff is whether stdlib/supplemental declarations must navigate to physical files immediately or can initially return no location while still supporting project symbols.
|
||||||
|
|
||||||
|
## Recommendation
|
||||||
|
|
||||||
|
Prefer Option B. Build a reusable semantic location index and implement the first wave for project source declarations, then define a policy for stdlib/supplemental targets.
|
||||||
|
|
||||||
|
## Discussion
|
||||||
|
|
||||||
|
This should probably be the first navigation agenda to resolve because references, rename, workspace symbols, and hierarchy depend on the same symbol identity/location model.
|
||||||
|
|
||||||
|
## Resolution
|
||||||
|
|
||||||
|
Ainda em aberto.
|
||||||
|
|
||||||
|
## Next Step
|
||||||
|
|
||||||
|
Decide the first-wave symbol categories and whether stdlib/supplemental declarations require virtual document support.
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
---
|
||||||
|
id: AGD-0043
|
||||||
|
ticket: pbs-lsp-find-references
|
||||||
|
title: PBS LSP Find References
|
||||||
|
status: open
|
||||||
|
created: 2026-07-15
|
||||||
|
resolved:
|
||||||
|
decision:
|
||||||
|
tags: [studio, lsp, vscode, compiler-pbs, editor, references]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pain
|
||||||
|
|
||||||
|
Domain owner: `studio/lsp`
|
||||||
|
|
||||||
|
PBS users cannot ask where a symbol is used, so changes to functions, services, structs, constants, and stdlib-facing APIs require manual search that confuses overloads and unrelated text matches.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Completion and hover already resolve symbols in local editor context, but the LSP does not expose references and the compiler/editorial layer does not yet provide project-wide usage locations keyed by semantic identity.
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
- [ ] Which declarations need reference tracking first: local functions, methods, types, constants, imports, stdlib symbols, or all of them?
|
||||||
|
- [ ] How should overloads and same-name symbols in different scopes be disambiguated?
|
||||||
|
- [ ] Should references include declarations, write/read classification, or only usage sites in the first wave?
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
### Option A - Text search with filters
|
||||||
|
|
||||||
|
- **Approach:** Search workspace text for the selected identifier and filter obvious false positives by token kind.
|
||||||
|
- **Pro:** Simple and fast for early demos.
|
||||||
|
- **Con:** Incorrect for overloads, scope, shadowing, fields, methods, imports, and generated/stdlib surfaces.
|
||||||
|
- **Maintainability:** Poor; later semantic references would duplicate and replace it.
|
||||||
|
|
||||||
|
### Option B - Semantic usage index
|
||||||
|
|
||||||
|
- **Approach:** During semantic analysis, emit definition and usage sites tied to stable symbol identities and expose `textDocument/references`.
|
||||||
|
- **Pro:** Correct foundation for rename, call hierarchy, diagnostics UX, and code actions.
|
||||||
|
- **Con:** Requires compiler-facing usage modeling and careful invalidation.
|
||||||
|
- **Maintainability:** Strong; one source of truth for all symbol usage features.
|
||||||
|
|
||||||
|
## Tradeoffs
|
||||||
|
|
||||||
|
Reference accuracy matters more than early breadth. False positives are worse than missing unsupported categories because users will trust rename and references for safe edits.
|
||||||
|
|
||||||
|
## Recommendation
|
||||||
|
|
||||||
|
Prefer Option B, but scope the first wave to project-owned declarations whose identity is already resolved reliably.
|
||||||
|
|
||||||
|
## Discussion
|
||||||
|
|
||||||
|
References should follow go-to-definition because both need the same identity and location model.
|
||||||
|
|
||||||
|
## Resolution
|
||||||
|
|
||||||
|
Ainda em aberto.
|
||||||
|
|
||||||
|
## Next Step
|
||||||
|
|
||||||
|
Define first-wave symbol categories and whether declarations are included in reference results.
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
---
|
||||||
|
id: AGD-0044
|
||||||
|
ticket: pbs-lsp-document-symbols-outline
|
||||||
|
title: PBS LSP Document Symbols and Outline
|
||||||
|
status: open
|
||||||
|
created: 2026-07-15
|
||||||
|
resolved:
|
||||||
|
decision:
|
||||||
|
tags: [studio, lsp, vscode, compiler-pbs, editor, outline, document-symbols]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pain
|
||||||
|
|
||||||
|
Domain owner: `studio/lsp`
|
||||||
|
|
||||||
|
PBS files do not populate a reliable VS Code outline, forcing users to navigate long source files manually instead of scanning declarations and members structurally.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The parser and semantic surfaces know top-level declarations and many member declarations. The LSP currently does not announce documentSymbolProvider and does not map PBS declarations to DocumentSymbol or SymbolInformation payloads.
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
- [ ] Should the first outline be syntactic, semantic, or hybrid?
|
||||||
|
- [ ] Which hierarchy should be shown for structs, services, hosts, contracts, builtin types, enums, and methods?
|
||||||
|
- [ ] How should invalid or partially parsed files contribute to outline during editing?
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
### Option A - AST-only outline
|
||||||
|
|
||||||
|
- **Approach:** Map parsed declarations and member spans directly to LSP `DocumentSymbol`.
|
||||||
|
- **Pro:** Works even when semantic analysis is incomplete and gives immediate editor value.
|
||||||
|
- **Con:** Cannot classify every symbol with semantic precision.
|
||||||
|
- **Maintainability:** Good if kept as structural outline, not overloaded with semantic behavior.
|
||||||
|
|
||||||
|
### Option B - Semantic outline
|
||||||
|
|
||||||
|
- **Approach:** Build outline from semantic symbols after analysis.
|
||||||
|
- **Pro:** More accurate kinds and can hide invalid/unresolved surfaces.
|
||||||
|
- **Con:** More fragile during active editing and depends on full semantic success.
|
||||||
|
- **Maintainability:** Good for final precision, but heavier than needed for outline.
|
||||||
|
|
||||||
|
## Tradeoffs
|
||||||
|
|
||||||
|
Outline should remain available during broken intermediate edits. Semantic precision is useful, but not at the cost of disappearing structure while typing.
|
||||||
|
|
||||||
|
## Recommendation
|
||||||
|
|
||||||
|
Prefer Option A with semantic enrichment where available. Use AST spans as the stable backbone.
|
||||||
|
|
||||||
|
## Discussion
|
||||||
|
|
||||||
|
Document symbols are relatively independent and can be implemented before definition/references.
|
||||||
|
|
||||||
|
## Resolution
|
||||||
|
|
||||||
|
Ainda em aberto.
|
||||||
|
|
||||||
|
## Next Step
|
||||||
|
|
||||||
|
Decide hierarchy and symbol kinds for PBS declarations and members.
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
---
|
||||||
|
id: AGD-0068
|
||||||
|
ticket: multi-frontend-avoid-premature-abstractions
|
||||||
|
title: Evitar abstracoes prematuras na preparacao multi-frontend
|
||||||
|
status: open
|
||||||
|
created: 2026-07-15
|
||||||
|
resolved:
|
||||||
|
decision:
|
||||||
|
tags: [compiler, compiler-general, studio, frontend, architecture, multi-frontend, simplicity]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Agenda - Evitar abstracoes prematuras
|
||||||
|
|
||||||
|
## Objetivo
|
||||||
|
|
||||||
|
Domain owner: `compiler/general`, com impacto em `studio`.
|
||||||
|
|
||||||
|
Definir limites para a preparacao multi-frontend: remover acoplamento com baixo custo sem construir marketplace, hot reload, RPC, sandbox ou infraestrutura generica antes da necessidade real.
|
||||||
|
|
||||||
|
## Contexto atual
|
||||||
|
|
||||||
|
O PBS continuara sendo o unico frontend implementado no curto prazo. O objetivo e preparar contratos, fronteiras e testes de neutralidade, nao criar uma plataforma completa de plugins.
|
||||||
|
|
||||||
|
## Escopo
|
||||||
|
|
||||||
|
- Estabelecer principios para as agendas derivadas.
|
||||||
|
- Definir o que e explicitamente proibido nesta fase.
|
||||||
|
- Criar criterio para rejeitar abstracoes sem uso real.
|
||||||
|
|
||||||
|
## Fora de escopo
|
||||||
|
|
||||||
|
- Plugin marketplace.
|
||||||
|
- Carregamento dinamico de JARs.
|
||||||
|
- RPC, sockets, process supervisor ou version negotiation complexa.
|
||||||
|
- Service locator global.
|
||||||
|
|
||||||
|
## Arquivos e componentes a inspecionar
|
||||||
|
|
||||||
|
- `prometeu-compiler/prometeu-frontend-registry/...`
|
||||||
|
- `prometeu-app/src/main/java/p/studio/AppContainer.java`
|
||||||
|
- `prometeu-studio/src/main/java/p/studio/...`
|
||||||
|
- planos futuros derivados das discussions multi-frontend
|
||||||
|
|
||||||
|
## Alteracoes propostas
|
||||||
|
|
||||||
|
Opcao A: registrar esta decisao como guardrail transversal antes das demais.
|
||||||
|
|
||||||
|
Opcao B: deixar cada agenda repetir suas restricoes locais.
|
||||||
|
|
||||||
|
Recomendacao inicial: criar uma decisao curta e transversal apos discussao, para guiar provider, language services, synthetic frontend e testes arquiteturais.
|
||||||
|
|
||||||
|
## Estrategia de implementacao
|
||||||
|
|
||||||
|
Transformar o alinhamento em criterios de review: interface pequena, registro explicito, DI/composicao, records imutaveis e testes; rejeitar infra que nao seja exigida pelo PBS ou pelo frontend sintetico de teste.
|
||||||
|
|
||||||
|
## Testes necessarios
|
||||||
|
|
||||||
|
- Nao ha teste direto para simplicidade, mas plans devem incluir criterio de aceite contra reflection/scanning/RPC quando aplicavel.
|
||||||
|
- Testes arquiteturais podem proteger contra service locator global ou acoplamentos indevidos.
|
||||||
|
|
||||||
|
## Criterios de aceitacao
|
||||||
|
|
||||||
|
- Plans derivados nao introduzem infraestrutura de plugin antes da necessidade.
|
||||||
|
- Registro manual do PBS e suficiente.
|
||||||
|
- O frontend sintetico prova neutralidade sem virar plataforma externa.
|
||||||
|
|
||||||
|
## Riscos
|
||||||
|
|
||||||
|
- Usar "evitar abstracao" como desculpa para manter acoplamento PBS.
|
||||||
|
- Criar contratos tao pequenos que precisem ser quebrados imediatamente.
|
||||||
|
|
||||||
|
## Decisoes que devem ser registradas
|
||||||
|
|
||||||
|
- Guardrails obrigatorios para esta fase.
|
||||||
|
- Lista de mecanismos proibidos.
|
||||||
|
- Criterio para aceitar uma nova abstracao.
|
||||||
|
|
||||||
@ -272,12 +272,6 @@ Absent editor capabilities MUST have deterministic fallback behavior at tooling
|
|||||||
3. host protocols that can represent unsupported operations SHOULD do so explicitly;
|
3. host protocols that can represent unsupported operations SHOULD do so explicitly;
|
||||||
4. and absent editor capability MUST NOT be reported as a compiler failure.
|
4. and absent editor capability MUST NOT be reported as a compiler failure.
|
||||||
|
|
||||||
When a frontend exposes definition or navigation, each returned location MUST identify a compiler-known physical source file. A resolved symbol without a physical file MUST produce an empty definition response. Tooling MUST NOT require virtual documents, untitled buffers, or synthetic URIs for this capability.
|
|
||||||
|
|
||||||
When a frontend exposes find-references, each returned usage or declaration location MUST identify a compiler-known physical source file. A cursor offset whose definition response is empty MUST produce an empty references response. Host `includeDeclaration` MAY add the canonical declaration location only when that declaration is a compiler-known physical source file; it MUST NOT invent a virtual, untitled, or synthetic location.
|
|
||||||
|
|
||||||
When a frontend exposes document symbols or outline, the result MUST be the named declaration tree of the requested document. Presence, hierarchy, and ranges MUST come from that document's recovered syntax tree. Semantic success MUST NOT be required. Semantic enrichment MUST NOT drop a named declaration that the syntax tree still exposes. A missing syntax tree MUST produce an empty list. Document symbols MUST NOT be a workspace symbol index, folding ranges, or a navigation list of foreign files.
|
|
||||||
|
|
||||||
Compiler diagnostics and editor diagnostics are distinct ownership surfaces. Diagnostics returned by `analyze`, `compile`, and `build` are compiler contract output. Editor diagnostics MAY reuse compiler analysis results, live overlays, caches, or cancellation-aware tooling state, but a frontend MUST NOT be required to provide editor diagnostics in order to compile.
|
Compiler diagnostics and editor diagnostics are distinct ownership surfaces. Diagnostics returned by `analyze`, `compile`, and `build` are compiler contract output. Editor diagnostics MAY reuse compiler analysis results, live overlays, caches, or cancellation-aware tooling state, but a frontend MUST NOT be required to provide editor diagnostics in order to compile.
|
||||||
|
|
||||||
`FrontendSpec` remains the source of static frontend-owned presentation metadata such as semantic vocabularies, host projections, and visual themes. Producing semantic tokens for a live document is an optional editor-facing capability; the existence of static presentation metadata MUST NOT imply that every frontend can provide live semantic-token results.
|
`FrontendSpec` remains the source of static frontend-owned presentation metadata such as semantic vocabularies, host projections, and visual themes. Producing semantic tokens for a live document is an optional editor-facing capability; the existence of static presentation metadata MUST NOT imply that every frontend can provide live semantic-token results.
|
||||||
|
|||||||
@ -6,18 +6,12 @@ import p.studio.compiler.messages.FrontendPhaseContext;
|
|||||||
import p.studio.compiler.models.FrontendSemanticToken;
|
import p.studio.compiler.models.FrontendSemanticToken;
|
||||||
import p.studio.compiler.pbs.ast.PbsAst;
|
import p.studio.compiler.pbs.ast.PbsAst;
|
||||||
import p.studio.compiler.pbs.semantics.PbsEditorialCompletionCandidate;
|
import p.studio.compiler.pbs.semantics.PbsEditorialCompletionCandidate;
|
||||||
import p.studio.compiler.pbs.semantics.PbsEditorialDocumentSymbol;
|
|
||||||
import p.studio.compiler.pbs.semantics.PbsEditorialLocation;
|
|
||||||
import p.studio.compiler.pbs.semantics.PbsEditorialResolvedSymbol;
|
import p.studio.compiler.pbs.semantics.PbsEditorialResolvedSymbol;
|
||||||
import p.studio.compiler.pbs.semantics.PbsEditorialSignatureHelp;
|
import p.studio.compiler.pbs.semantics.PbsEditorialSignatureHelp;
|
||||||
import p.studio.compiler.pbs.semantics.PbsEditorialSymbolKind;
|
import p.studio.compiler.pbs.semantics.PbsEditorialSymbolKind;
|
||||||
import p.studio.compiler.pbs.semantics.PbsEditorialSupportService;
|
import p.studio.compiler.pbs.semantics.PbsEditorialSupportService;
|
||||||
import p.studio.compiler.pbs.semantics.PbsEditorialSupportService.EditorialDocument;
|
|
||||||
import p.studio.compiler.services.PBSFrontendPhaseService.PbsSemanticReadSurface;
|
|
||||||
import p.studio.compiler.services.FrontendCompletionCandidate;
|
import p.studio.compiler.services.FrontendCompletionCandidate;
|
||||||
import p.studio.compiler.services.FrontendDefinitionLocation;
|
|
||||||
import p.studio.compiler.services.FrontendDocumentRequest;
|
import p.studio.compiler.services.FrontendDocumentRequest;
|
||||||
import p.studio.compiler.services.FrontendDocumentSymbol;
|
|
||||||
import p.studio.compiler.services.FrontendDocumentation;
|
import p.studio.compiler.services.FrontendDocumentation;
|
||||||
import p.studio.compiler.services.FrontendEditorialContext;
|
import p.studio.compiler.services.FrontendEditorialContext;
|
||||||
import p.studio.compiler.services.FrontendHover;
|
import p.studio.compiler.services.FrontendHover;
|
||||||
@ -26,16 +20,10 @@ import p.studio.compiler.services.FrontendSignature;
|
|||||||
import p.studio.compiler.services.FrontendSignatureHelp;
|
import p.studio.compiler.services.FrontendSignatureHelp;
|
||||||
import p.studio.compiler.services.FrontendSymbolKind;
|
import p.studio.compiler.services.FrontendSymbolKind;
|
||||||
import p.studio.compiler.services.PBSFrontendPhaseService;
|
import p.studio.compiler.services.PBSFrontendPhaseService;
|
||||||
import p.studio.compiler.source.Span;
|
|
||||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||||
import p.studio.compiler.source.identifiers.FileId;
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
import p.studio.compiler.source.tables.FileTableReader;
|
|
||||||
import p.studio.utilities.structures.ReadOnlyList;
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@ -73,9 +61,7 @@ public final class PBSFrontendLanguageService implements FrontendLanguageService
|
|||||||
return Optional.of(new FrontendEditorialContext(
|
return Optional.of(new FrontendEditorialContext(
|
||||||
ast,
|
ast,
|
||||||
semanticReadSurface.supplementalTopDeclsByFile().getOrDefault(fileId, ReadOnlyList.empty()),
|
semanticReadSurface.supplementalTopDeclsByFile().getOrDefault(fileId, ReadOnlyList.empty()),
|
||||||
phaseContext.feSurfaceContext(),
|
phaseContext.feSurfaceContext()));
|
||||||
phaseContext.fileTable,
|
|
||||||
semanticReadSurface));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PbsEditorialCompletionCandidate> completion(
|
public List<PbsEditorialCompletionCandidate> completion(
|
||||||
@ -181,106 +167,6 @@ public final class PBSFrontendLanguageService implements FrontendLanguageService
|
|||||||
.orElseGet(FrontendSignatureHelp::empty);
|
.orElseGet(FrontendSignatureHelp::empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PbsEditorialLocation> definition(
|
|
||||||
final String sourceText,
|
|
||||||
final PbsAst.File ast,
|
|
||||||
final ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
|
|
||||||
final FESurfaceContext feSurfaceContext,
|
|
||||||
final int offset) {
|
|
||||||
return editorialSupportService.definition(sourceText, ast, supplementalTopDecls, feSurfaceContext, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<FrontendDefinitionLocation> definition(
|
|
||||||
final FrontendDocumentRequest request,
|
|
||||||
final FrontendEditorialContext editorialContext,
|
|
||||||
final int offset) {
|
|
||||||
if (request == null
|
|
||||||
|| editorialContext == null
|
|
||||||
|| !(editorialContext.syntaxTree() instanceof PbsAst.File ast)
|
|
||||||
|| !(editorialContext.supplementalDeclarations() instanceof ReadOnlyList<?> supplementalTopDecls)
|
|
||||||
|| !(editorialContext.semanticContext() instanceof FESurfaceContext feSurfaceContext)
|
|
||||||
|| !(editorialContext.sourceFiles() instanceof FileTableReader fileTable)) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
final ReadOnlyList<PbsAst.TopDecl> typedSupplementalTopDecls =
|
|
||||||
(ReadOnlyList<PbsAst.TopDecl>) supplementalTopDecls;
|
|
||||||
final ArrayList<FrontendDefinitionLocation> locations = new ArrayList<>();
|
|
||||||
for (final PbsEditorialLocation location : definition(
|
|
||||||
request.documentText(),
|
|
||||||
ast,
|
|
||||||
typedSupplementalTopDecls,
|
|
||||||
feSurfaceContext,
|
|
||||||
offset)) {
|
|
||||||
toFrontendLocation(location, fileTable).ifPresent(locations::add);
|
|
||||||
}
|
|
||||||
return List.copyOf(locations);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<FrontendDefinitionLocation> references(
|
|
||||||
final FrontendDocumentRequest request,
|
|
||||||
final FrontendEditorialContext editorialContext,
|
|
||||||
final int offset) {
|
|
||||||
if (request == null
|
|
||||||
|| editorialContext == null
|
|
||||||
|| !(editorialContext.syntaxTree() instanceof PbsAst.File ast)
|
|
||||||
|| !(editorialContext.supplementalDeclarations() instanceof ReadOnlyList<?> supplementalTopDecls)
|
|
||||||
|| !(editorialContext.semanticContext() instanceof FESurfaceContext feSurfaceContext)
|
|
||||||
|| !(editorialContext.sourceFiles() instanceof FileTableReader fileTable)) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
final ReadOnlyList<PbsAst.TopDecl> typedSupplementalTopDecls =
|
|
||||||
(ReadOnlyList<PbsAst.TopDecl>) supplementalTopDecls;
|
|
||||||
final List<PbsEditorialLocation> editorialIdentity = definition(
|
|
||||||
request.documentText(),
|
|
||||||
ast,
|
|
||||||
typedSupplementalTopDecls,
|
|
||||||
feSurfaceContext,
|
|
||||||
offset);
|
|
||||||
final ArrayList<PbsEditorialLocation> physicalIdentity = new ArrayList<>();
|
|
||||||
for (final PbsEditorialLocation location : editorialIdentity) {
|
|
||||||
if (toFrontendLocation(location, fileTable).isPresent()) {
|
|
||||||
physicalIdentity.add(location);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (physicalIdentity.isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
final EditorialDocument cursorDocument = new EditorialDocument(
|
|
||||||
request.documentText(),
|
|
||||||
ast,
|
|
||||||
typedSupplementalTopDecls,
|
|
||||||
feSurfaceContext);
|
|
||||||
final List<PbsEditorialLocation> usages = editorialSupportService.references(
|
|
||||||
cursorDocument,
|
|
||||||
offset,
|
|
||||||
projectDocuments(request, editorialContext, cursorDocument, fileTable));
|
|
||||||
final LinkedHashMap<String, FrontendDefinitionLocation> mapped = new LinkedHashMap<>();
|
|
||||||
for (final PbsEditorialLocation usage : usages) {
|
|
||||||
toFrontendLocation(usage, fileTable).ifPresent(location ->
|
|
||||||
mapped.putIfAbsent(frontendLocationKey(location), location));
|
|
||||||
}
|
|
||||||
return List.copyOf(mapped.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<FrontendDocumentSymbol> documentSymbols(
|
|
||||||
final FrontendDocumentRequest request,
|
|
||||||
final FrontendEditorialContext editorialContext) {
|
|
||||||
if (editorialContext == null || !(editorialContext.syntaxTree() instanceof PbsAst.File ast)) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
return editorialSupportService.documentSymbols(ast).stream()
|
|
||||||
.map(this::toFrontendDocumentSymbol)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<FrontendSemanticToken> semanticTokens(final FrontendDocumentRequest request) {
|
public List<FrontendSemanticToken> semanticTokens(final FrontendDocumentRequest request) {
|
||||||
return semanticTokens(request == null ? "" : request.documentText());
|
return semanticTokens(request == null ? "" : request.documentText());
|
||||||
@ -327,117 +213,4 @@ public final class PBSFrontendLanguageService implements FrontendLanguageService
|
|||||||
private FrontendSymbolKind toFrontendSymbolKind(final PbsEditorialSymbolKind kind) {
|
private FrontendSymbolKind toFrontendSymbolKind(final PbsEditorialSymbolKind kind) {
|
||||||
return FrontendSymbolKind.valueOf(kind.name());
|
return FrontendSymbolKind.valueOf(kind.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
private FrontendDocumentSymbol toFrontendDocumentSymbol(final PbsEditorialDocumentSymbol symbol) {
|
|
||||||
final int start = toOffset(symbol.span().getStart());
|
|
||||||
final int end = toOffset(symbol.span().getEnd());
|
|
||||||
return new FrontendDocumentSymbol(
|
|
||||||
symbol.name(),
|
|
||||||
toFrontendSymbolKind(symbol.kind()),
|
|
||||||
symbol.detail(),
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
symbol.children().stream().map(this::toFrontendDocumentSymbol).toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<EditorialDocument> projectDocuments(
|
|
||||||
final FrontendDocumentRequest request,
|
|
||||||
final FrontendEditorialContext editorialContext,
|
|
||||||
final EditorialDocument cursorDocument,
|
|
||||||
final FileTableReader fileTable) {
|
|
||||||
if (!(editorialContext.projectSurface() instanceof PbsSemanticReadSurface surface)
|
|
||||||
|| surface.astByFile() == null
|
|
||||||
|| surface.astByFile().isEmpty()) {
|
|
||||||
return List.of(cursorDocument);
|
|
||||||
}
|
|
||||||
final ArrayList<EditorialDocument> documents = new ArrayList<>();
|
|
||||||
for (final var entry : surface.astByFile().entrySet()) {
|
|
||||||
if (!isRegularSource(entry.getKey(), fileTable)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final PbsAst.File fileAst = entry.getValue();
|
|
||||||
if (fileAst == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final ReadOnlyList<PbsAst.TopDecl> fileSupplementals = fileAst == cursorDocument.ast()
|
|
||||||
? cursorDocument.supplementalTopDecls()
|
|
||||||
: surface.supplementalTopDeclsByFile().getOrDefault(entry.getKey(), ReadOnlyList.empty());
|
|
||||||
documents.add(new EditorialDocument(
|
|
||||||
textForFile(entry.getKey(), fileTable, request),
|
|
||||||
fileAst,
|
|
||||||
fileSupplementals,
|
|
||||||
cursorDocument.feSurfaceContext()));
|
|
||||||
}
|
|
||||||
if (documents.isEmpty()) {
|
|
||||||
return List.of(cursorDocument);
|
|
||||||
}
|
|
||||||
return List.copyOf(documents);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isRegularSource(
|
|
||||||
final FileId fileId,
|
|
||||||
final FileTableReader fileTable) {
|
|
||||||
if (fileId == null || fileId.isNone() || fileTable == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final var sourceHandle = fileTable.get(fileId);
|
|
||||||
if (sourceHandle == null || sourceHandle.getCanonPath() == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return Files.isRegularFile(sourceHandle.getCanonPath().toAbsolutePath().normalize());
|
|
||||||
}
|
|
||||||
|
|
||||||
private String textForFile(
|
|
||||||
final FileId fileId,
|
|
||||||
final FileTableReader fileTable,
|
|
||||||
final FrontendDocumentRequest request) {
|
|
||||||
final var sourceHandle = fileTable.get(fileId);
|
|
||||||
if (sourceHandle == null || sourceHandle.getCanonPath() == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
final Path path = sourceHandle.getCanonPath().toAbsolutePath().normalize();
|
|
||||||
if (request != null
|
|
||||||
&& request.documentPath() != null
|
|
||||||
&& path.equals(request.documentPath().toAbsolutePath().normalize())) {
|
|
||||||
return request.documentText() == null ? "" : request.documentText();
|
|
||||||
}
|
|
||||||
return sourceHandle.readUtf8().orElse("");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String frontendLocationKey(final FrontendDefinitionLocation location) {
|
|
||||||
return location.path() + ":" + location.startOffset() + ":" + location.endOffset();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<FrontendDefinitionLocation> toFrontendLocation(
|
|
||||||
final PbsEditorialLocation location,
|
|
||||||
final FileTableReader fileTable) {
|
|
||||||
if (location == null || fileTable == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
final Span span = location.span();
|
|
||||||
if (span == null || span.isNone() || span.getFileId() == null || span.getFileId().isNone()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
final var sourceHandle = fileTable.get(span.getFileId());
|
|
||||||
if (sourceHandle == null || sourceHandle.getCanonPath() == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
final Path path = sourceHandle.getCanonPath().toAbsolutePath().normalize();
|
|
||||||
if (!Files.isRegularFile(path)) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
return Optional.of(new FrontendDefinitionLocation(
|
|
||||||
path,
|
|
||||||
toOffset(span.getStart()),
|
|
||||||
toOffset(span.getEnd())));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int toOffset(final long value) {
|
|
||||||
if (value <= 0L) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return (int) Math.min(Integer.MAX_VALUE, value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,321 +0,0 @@
|
|||||||
package p.studio.compiler.pbs.semantics;
|
|
||||||
|
|
||||||
import p.studio.compiler.pbs.ast.PbsAst;
|
|
||||||
import p.studio.compiler.source.Span;
|
|
||||||
import p.studio.utilities.structures.ReadOnlyList;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
final class PbsAstDocumentSymbols {
|
|
||||||
private PbsAstDocumentSymbols() {
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<PbsEditorialDocumentSymbol> list(final PbsAst.File ast) {
|
|
||||||
if (ast == null || ast.topDecls() == null) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
final ArrayList<PbsEditorialDocumentSymbol> symbols = new ArrayList<>();
|
|
||||||
for (final PbsAst.TopDecl topDecl : ast.topDecls()) {
|
|
||||||
symbolFor(topDecl).ifPresent(symbols::add);
|
|
||||||
}
|
|
||||||
return List.copyOf(symbols);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Optional<PbsEditorialDocumentSymbol> symbolFor(final PbsAst.TopDecl topDecl) {
|
|
||||||
if (topDecl instanceof PbsAst.FunctionDecl functionDecl) {
|
|
||||||
return callableSymbol(
|
|
||||||
functionDecl.name(),
|
|
||||||
PbsEditorialSymbolKind.FUNCTION,
|
|
||||||
functionDecl.parameters(),
|
|
||||||
functionDecl.returnKind(),
|
|
||||||
functionDecl.returnType(),
|
|
||||||
functionDecl.resultErrorType(),
|
|
||||||
functionDecl.span());
|
|
||||||
}
|
|
||||||
if (topDecl instanceof PbsAst.StructDecl structDecl) {
|
|
||||||
return structSymbol(structDecl);
|
|
||||||
}
|
|
||||||
if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
|
|
||||||
return namedSymbol(
|
|
||||||
serviceDecl.name(),
|
|
||||||
PbsEditorialSymbolKind.SERVICE,
|
|
||||||
"",
|
|
||||||
serviceDecl.span(),
|
|
||||||
methodSymbols(serviceDecl.methods()));
|
|
||||||
}
|
|
||||||
if (topDecl instanceof PbsAst.ContractDecl contractDecl) {
|
|
||||||
return namedSymbol(
|
|
||||||
contractDecl.name(),
|
|
||||||
PbsEditorialSymbolKind.CONTRACT,
|
|
||||||
"",
|
|
||||||
contractDecl.span(),
|
|
||||||
signatureSymbols(contractDecl.signatures()));
|
|
||||||
}
|
|
||||||
if (topDecl instanceof PbsAst.HostDecl hostDecl) {
|
|
||||||
return namedSymbol(
|
|
||||||
hostDecl.name(),
|
|
||||||
PbsEditorialSymbolKind.HOST,
|
|
||||||
"",
|
|
||||||
hostDecl.span(),
|
|
||||||
signatureSymbols(hostDecl.signatures()));
|
|
||||||
}
|
|
||||||
if (topDecl instanceof PbsAst.BuiltinTypeDecl builtinTypeDecl) {
|
|
||||||
return builtinTypeSymbol(builtinTypeDecl);
|
|
||||||
}
|
|
||||||
if (topDecl instanceof PbsAst.EnumDecl enumDecl) {
|
|
||||||
return enumSymbol(enumDecl);
|
|
||||||
}
|
|
||||||
if (topDecl instanceof PbsAst.ErrorDecl errorDecl) {
|
|
||||||
return errorSymbol(errorDecl);
|
|
||||||
}
|
|
||||||
if (topDecl instanceof PbsAst.CallbackDecl callbackDecl) {
|
|
||||||
return callableSymbol(
|
|
||||||
callbackDecl.name(),
|
|
||||||
PbsEditorialSymbolKind.CALLBACK,
|
|
||||||
callbackDecl.parameters(),
|
|
||||||
callbackDecl.returnKind(),
|
|
||||||
callbackDecl.returnType(),
|
|
||||||
callbackDecl.resultErrorType(),
|
|
||||||
callbackDecl.span());
|
|
||||||
}
|
|
||||||
if (topDecl instanceof PbsAst.GlobalDecl globalDecl) {
|
|
||||||
return namedSymbol(
|
|
||||||
globalDecl.name(),
|
|
||||||
PbsEditorialSymbolKind.GLOBAL,
|
|
||||||
formatTypeRef(globalDecl.explicitType()),
|
|
||||||
globalDecl.span(),
|
|
||||||
List.of());
|
|
||||||
}
|
|
||||||
if (topDecl instanceof PbsAst.ConstDecl constDecl) {
|
|
||||||
return namedSymbol(
|
|
||||||
constDecl.name(),
|
|
||||||
PbsEditorialSymbolKind.CONST,
|
|
||||||
formatTypeRef(constDecl.explicitType()),
|
|
||||||
constDecl.span(),
|
|
||||||
List.of());
|
|
||||||
}
|
|
||||||
if (topDecl instanceof PbsAst.ImplementsDecl implementsDecl) {
|
|
||||||
return namedSymbol(
|
|
||||||
implementsDecl.contractName(),
|
|
||||||
PbsEditorialSymbolKind.CONTRACT,
|
|
||||||
implementsDecl.ownerName() == null ? "" : implementsDecl.ownerName(),
|
|
||||||
implementsDecl.span(),
|
|
||||||
methodSymbols(implementsDecl.methods()));
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Optional<PbsEditorialDocumentSymbol> structSymbol(final PbsAst.StructDecl structDecl) {
|
|
||||||
final ArrayList<PbsEditorialDocumentSymbol> children = new ArrayList<>();
|
|
||||||
for (final PbsAst.StructField field : structDecl.fields()) {
|
|
||||||
namedSymbol(
|
|
||||||
field.name(),
|
|
||||||
PbsEditorialSymbolKind.FIELD,
|
|
||||||
formatTypeRef(field.typeRef()),
|
|
||||||
field.span(),
|
|
||||||
List.of()).ifPresent(children::add);
|
|
||||||
}
|
|
||||||
for (final PbsAst.CtorDecl ctorDecl : structDecl.ctors()) {
|
|
||||||
namedSymbol(
|
|
||||||
ctorDecl.name(),
|
|
||||||
PbsEditorialSymbolKind.CONSTRUCTOR,
|
|
||||||
ctorDetail(ctorDecl.parameters()),
|
|
||||||
ctorDecl.span(),
|
|
||||||
List.of()).ifPresent(children::add);
|
|
||||||
}
|
|
||||||
children.addAll(methodSymbols(structDecl.methods()));
|
|
||||||
return namedSymbol(
|
|
||||||
structDecl.name(),
|
|
||||||
PbsEditorialSymbolKind.STRUCT,
|
|
||||||
"",
|
|
||||||
structDecl.span(),
|
|
||||||
children);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Optional<PbsEditorialDocumentSymbol> builtinTypeSymbol(final PbsAst.BuiltinTypeDecl builtinTypeDecl) {
|
|
||||||
final ArrayList<PbsEditorialDocumentSymbol> children = new ArrayList<>();
|
|
||||||
for (final PbsAst.BuiltinFieldDecl field : builtinTypeDecl.fields()) {
|
|
||||||
namedSymbol(
|
|
||||||
field.name(),
|
|
||||||
PbsEditorialSymbolKind.FIELD,
|
|
||||||
formatTypeRef(field.typeRef()),
|
|
||||||
field.span(),
|
|
||||||
List.of()).ifPresent(children::add);
|
|
||||||
}
|
|
||||||
children.addAll(signatureSymbols(builtinTypeDecl.signatures()));
|
|
||||||
return namedSymbol(
|
|
||||||
builtinTypeDecl.name(),
|
|
||||||
PbsEditorialSymbolKind.BUILTIN_TYPE,
|
|
||||||
"",
|
|
||||||
builtinTypeDecl.span(),
|
|
||||||
children);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Optional<PbsEditorialDocumentSymbol> enumSymbol(final PbsAst.EnumDecl enumDecl) {
|
|
||||||
final ArrayList<PbsEditorialDocumentSymbol> children = new ArrayList<>();
|
|
||||||
for (final PbsAst.EnumCase enumCase : enumDecl.cases()) {
|
|
||||||
final String detail = enumCase.explicitValue() == null ? "" : String.valueOf(enumCase.explicitValue());
|
|
||||||
namedSymbol(
|
|
||||||
enumCase.name(),
|
|
||||||
PbsEditorialSymbolKind.ENUM,
|
|
||||||
detail,
|
|
||||||
enumCase.span(),
|
|
||||||
List.of()).ifPresent(children::add);
|
|
||||||
}
|
|
||||||
return namedSymbol(
|
|
||||||
enumDecl.name(),
|
|
||||||
PbsEditorialSymbolKind.ENUM,
|
|
||||||
"",
|
|
||||||
enumDecl.span(),
|
|
||||||
children);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Optional<PbsEditorialDocumentSymbol> errorSymbol(final PbsAst.ErrorDecl errorDecl) {
|
|
||||||
final ArrayList<PbsEditorialDocumentSymbol> children = new ArrayList<>();
|
|
||||||
for (final String label : errorDecl.cases()) {
|
|
||||||
namedSymbol(
|
|
||||||
label,
|
|
||||||
PbsEditorialSymbolKind.ERROR,
|
|
||||||
"",
|
|
||||||
errorDecl.span(),
|
|
||||||
List.of()).ifPresent(children::add);
|
|
||||||
}
|
|
||||||
return namedSymbol(
|
|
||||||
errorDecl.name(),
|
|
||||||
PbsEditorialSymbolKind.ERROR,
|
|
||||||
"",
|
|
||||||
errorDecl.span(),
|
|
||||||
children);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<PbsEditorialDocumentSymbol> methodSymbols(final ReadOnlyList<PbsAst.FunctionDecl> methods) {
|
|
||||||
if (methods == null || methods.isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
final ArrayList<PbsEditorialDocumentSymbol> symbols = new ArrayList<>();
|
|
||||||
for (final PbsAst.FunctionDecl method : methods) {
|
|
||||||
callableSymbol(
|
|
||||||
method.name(),
|
|
||||||
PbsEditorialSymbolKind.METHOD,
|
|
||||||
method.parameters(),
|
|
||||||
method.returnKind(),
|
|
||||||
method.returnType(),
|
|
||||||
method.resultErrorType(),
|
|
||||||
method.span()).ifPresent(symbols::add);
|
|
||||||
}
|
|
||||||
return symbols;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<PbsEditorialDocumentSymbol> signatureSymbols(
|
|
||||||
final ReadOnlyList<PbsAst.FunctionSignature> signatures) {
|
|
||||||
if (signatures == null || signatures.isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
final ArrayList<PbsEditorialDocumentSymbol> symbols = new ArrayList<>();
|
|
||||||
for (final PbsAst.FunctionSignature signature : signatures) {
|
|
||||||
callableSymbol(
|
|
||||||
signature.name(),
|
|
||||||
PbsEditorialSymbolKind.METHOD,
|
|
||||||
signature.parameters(),
|
|
||||||
signature.returnKind(),
|
|
||||||
signature.returnType(),
|
|
||||||
signature.resultErrorType(),
|
|
||||||
signature.span()).ifPresent(symbols::add);
|
|
||||||
}
|
|
||||||
return symbols;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Optional<PbsEditorialDocumentSymbol> callableSymbol(
|
|
||||||
final String name,
|
|
||||||
final PbsEditorialSymbolKind kind,
|
|
||||||
final ReadOnlyList<PbsAst.Parameter> parameters,
|
|
||||||
final PbsAst.ReturnKind returnKind,
|
|
||||||
final PbsAst.TypeRef returnType,
|
|
||||||
final PbsAst.TypeRef resultErrorType,
|
|
||||||
final Span span) {
|
|
||||||
return namedSymbol(
|
|
||||||
name,
|
|
||||||
kind,
|
|
||||||
callableDetail(parameters, returnKind, returnType, resultErrorType),
|
|
||||||
span,
|
|
||||||
List.of());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Optional<PbsEditorialDocumentSymbol> namedSymbol(
|
|
||||||
final String name,
|
|
||||||
final PbsEditorialSymbolKind kind,
|
|
||||||
final String detail,
|
|
||||||
final Span span,
|
|
||||||
final List<PbsEditorialDocumentSymbol> children) {
|
|
||||||
if (name == null || name.isBlank() || span == null || span.isNone()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
return Optional.of(new PbsEditorialDocumentSymbol(name, kind, detail, span, children));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String callableDetail(
|
|
||||||
final ReadOnlyList<PbsAst.Parameter> parameters,
|
|
||||||
final PbsAst.ReturnKind returnKind,
|
|
||||||
final PbsAst.TypeRef returnType,
|
|
||||||
final PbsAst.TypeRef resultErrorType) {
|
|
||||||
return "(" + formatParameters(parameters) + ") -> " + formatReturn(returnKind, returnType, resultErrorType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String ctorDetail(final ReadOnlyList<PbsAst.Parameter> parameters) {
|
|
||||||
return "(" + formatParameters(parameters) + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String formatParameters(final ReadOnlyList<PbsAst.Parameter> parameters) {
|
|
||||||
if (parameters == null || parameters.isEmpty()) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
final ArrayList<String> parts = new ArrayList<>(parameters.size());
|
|
||||||
for (final PbsAst.Parameter parameter : parameters) {
|
|
||||||
parts.add(parameter.name() + ": " + formatTypeRef(parameter.typeRef()));
|
|
||||||
}
|
|
||||||
return String.join(", ", parts);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String formatReturn(
|
|
||||||
final PbsAst.ReturnKind returnKind,
|
|
||||||
final PbsAst.TypeRef returnType,
|
|
||||||
final PbsAst.TypeRef resultErrorType) {
|
|
||||||
if (returnKind == null) {
|
|
||||||
return formatTypeRef(returnType);
|
|
||||||
}
|
|
||||||
return switch (returnKind) {
|
|
||||||
case INFERRED_UNIT, EXPLICIT_UNIT -> "void";
|
|
||||||
case PLAIN -> formatTypeRef(returnType);
|
|
||||||
case RESULT -> {
|
|
||||||
final String errorType = formatTypeRef(resultErrorType);
|
|
||||||
if (returnType == null || returnType.kind() == PbsAst.TypeRefKind.UNIT) {
|
|
||||||
yield "result<" + errorType + ">";
|
|
||||||
}
|
|
||||||
yield "result<" + errorType + "> " + formatTypeRef(returnType);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String formatTypeRef(final PbsAst.TypeRef typeRef) {
|
|
||||||
if (typeRef == null) {
|
|
||||||
return "void";
|
|
||||||
}
|
|
||||||
return switch (typeRef.kind()) {
|
|
||||||
case UNIT -> typeRef.name() == null || typeRef.name().isBlank() ? "void" : typeRef.name();
|
|
||||||
case SELF -> "Self";
|
|
||||||
case SIMPLE -> typeRef.name() == null ? "void" : typeRef.name();
|
|
||||||
case OPTIONAL -> "optional " + formatTypeRef(typeRef.inner());
|
|
||||||
case GROUP -> "(" + formatTypeRef(typeRef.inner()) + ")";
|
|
||||||
case NAMED_TUPLE -> {
|
|
||||||
final ArrayList<String> fields = new ArrayList<>(typeRef.fields().size());
|
|
||||||
for (final var field : typeRef.fields()) {
|
|
||||||
fields.add(field.label() + ": " + formatTypeRef(field.typeRef()));
|
|
||||||
}
|
|
||||||
yield "(" + String.join(", ", fields) + ")";
|
|
||||||
}
|
|
||||||
case ERROR -> "unknown";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
package p.studio.compiler.pbs.semantics;
|
|
||||||
|
|
||||||
import p.studio.compiler.source.Span;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record PbsEditorialDocumentSymbol(
|
|
||||||
String name,
|
|
||||||
PbsEditorialSymbolKind kind,
|
|
||||||
String detail,
|
|
||||||
Span span,
|
|
||||||
List<PbsEditorialDocumentSymbol> children) {
|
|
||||||
|
|
||||||
public PbsEditorialDocumentSymbol {
|
|
||||||
name = Objects.requireNonNull(name, "name").trim();
|
|
||||||
kind = Objects.requireNonNull(kind, "kind");
|
|
||||||
detail = detail == null ? "" : detail;
|
|
||||||
span = Objects.requireNonNull(span, "span");
|
|
||||||
children = children == null ? List.of() : List.copyOf(children);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
package p.studio.compiler.pbs.semantics;
|
|
||||||
|
|
||||||
import p.studio.compiler.source.Span;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record PbsEditorialLocation(Span span) {
|
|
||||||
public PbsEditorialLocation {
|
|
||||||
Objects.requireNonNull(span, "span");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -74,94 +74,6 @@ public final class PbsEditorialSupportService {
|
|||||||
return context.resolveSymbolHover(token.lexeme(), tokenIndex, scopeState);
|
return context.resolveSymbolHover(token.lexeme(), tokenIndex, scopeState);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PbsEditorialDocumentSymbol> documentSymbols(final PbsAst.File ast) {
|
|
||||||
return PbsAstDocumentSymbols.list(ast);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<PbsEditorialLocation> definition(
|
|
||||||
final String sourceText,
|
|
||||||
final PbsAst.File ast,
|
|
||||||
final ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
|
|
||||||
final int offset) {
|
|
||||||
return definition(sourceText, ast, supplementalTopDecls, FESurfaceContext.empty(), offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<PbsEditorialLocation> definition(
|
|
||||||
final String sourceText,
|
|
||||||
final PbsAst.File ast,
|
|
||||||
final ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
|
|
||||||
final FESurfaceContext feSurfaceContext,
|
|
||||||
final int offset) {
|
|
||||||
return buildContext(sourceText, ast, supplementalTopDecls, feSurfaceContext).definitionAt(offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<PbsEditorialLocation> references(
|
|
||||||
final String sourceText,
|
|
||||||
final PbsAst.File ast,
|
|
||||||
final ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
|
|
||||||
final int offset) {
|
|
||||||
return references(sourceText, ast, supplementalTopDecls, FESurfaceContext.empty(), offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<PbsEditorialLocation> references(
|
|
||||||
final String sourceText,
|
|
||||||
final PbsAst.File ast,
|
|
||||||
final ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
|
|
||||||
final FESurfaceContext feSurfaceContext,
|
|
||||||
final int offset) {
|
|
||||||
final EditorialDocument cursor = new EditorialDocument(sourceText, ast, supplementalTopDecls, feSurfaceContext);
|
|
||||||
return references(cursor, offset, List.of(cursor));
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<PbsEditorialLocation> references(
|
|
||||||
final EditorialDocument cursorDocument,
|
|
||||||
final int offset,
|
|
||||||
final List<EditorialDocument> projectDocuments) {
|
|
||||||
if (cursorDocument == null) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
final List<PbsEditorialLocation> identity = definition(
|
|
||||||
cursorDocument.sourceText(),
|
|
||||||
cursorDocument.ast(),
|
|
||||||
cursorDocument.supplementalTopDecls(),
|
|
||||||
cursorDocument.feSurfaceContext(),
|
|
||||||
offset);
|
|
||||||
if (identity.isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
final Set<String> identityKeys = locationKeys(identity);
|
|
||||||
final LinkedHashMap<String, PbsEditorialLocation> usages = new LinkedHashMap<>();
|
|
||||||
final List<EditorialDocument> documents = projectDocuments == null || projectDocuments.isEmpty()
|
|
||||||
? List.of(cursorDocument)
|
|
||||||
: projectDocuments;
|
|
||||||
for (final EditorialDocument document : documents) {
|
|
||||||
if (document == null || document.ast() == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (final PbsEditorialLocation usage : buildContext(
|
|
||||||
document.sourceText(),
|
|
||||||
document.ast(),
|
|
||||||
document.supplementalTopDecls(),
|
|
||||||
document.feSurfaceContext()).usagesMatching(identityKeys)) {
|
|
||||||
usages.putIfAbsent(locationKey(usage.span()), usage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return List.copyOf(usages.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
public record EditorialDocument(
|
|
||||||
String sourceText,
|
|
||||||
PbsAst.File ast,
|
|
||||||
ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
|
|
||||||
FESurfaceContext feSurfaceContext) {
|
|
||||||
public EditorialDocument {
|
|
||||||
sourceText = sourceText == null ? "" : sourceText;
|
|
||||||
Objects.requireNonNull(ast, "ast");
|
|
||||||
supplementalTopDecls = supplementalTopDecls == null ? ReadOnlyList.empty() : supplementalTopDecls;
|
|
||||||
feSurfaceContext = feSurfaceContext == null ? FESurfaceContext.empty() : feSurfaceContext;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<PbsEditorialSignatureHelp> signatureHelp(
|
public Optional<PbsEditorialSignatureHelp> signatureHelp(
|
||||||
final String sourceText,
|
final String sourceText,
|
||||||
final PbsAst.File ast,
|
final PbsAst.File ast,
|
||||||
@ -204,25 +116,6 @@ public final class PbsEditorialSupportService {
|
|||||||
return new DocumentContext(sourceText, ast, supplementalTopDecls, feSurfaceContext);
|
return new DocumentContext(sourceText, ast, supplementalTopDecls, feSurfaceContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<String> locationKeys(final List<PbsEditorialLocation> locations) {
|
|
||||||
final LinkedHashSet<String> keys = new LinkedHashSet<>();
|
|
||||||
if (locations == null) {
|
|
||||||
return Set.of();
|
|
||||||
}
|
|
||||||
for (final PbsEditorialLocation location : locations) {
|
|
||||||
if (location != null && location.span() != null) {
|
|
||||||
keys.add(locationKey(location.span()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Set.copyOf(keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String locationKey(final Span span) {
|
|
||||||
final FileId fileId = span.getFileId();
|
|
||||||
final int fileNumber = fileId == null ? -1 : fileId.getId();
|
|
||||||
return fileNumber + ":" + span.getStart() + ":" + span.getEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class DocumentContext {
|
private static final class DocumentContext {
|
||||||
private final String sourceText;
|
private final String sourceText;
|
||||||
private final PbsAst.File ast;
|
private final PbsAst.File ast;
|
||||||
@ -717,264 +610,6 @@ public final class PbsEditorialSupportService {
|
|||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<PbsEditorialLocation> definitionAt(final int offset) {
|
|
||||||
final Integer tokenIndex = identifierTokenIndexAt(offset);
|
|
||||||
if (tokenIndex == null) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
final var scopeState = scopeStateAt(offset);
|
|
||||||
final var token = token(tokenIndex);
|
|
||||||
final Integer previousTokenIndex = previousSignificantTokenIndex(tokenIndex);
|
|
||||||
if (previousTokenIndex != null && token(previousTokenIndex).kind() == PbsTokenKind.DOT) {
|
|
||||||
final TypeView receiverType = resolveReceiverTypeBeforeDot(previousTokenIndex, scopeState);
|
|
||||||
return resolveMemberDefinition(receiverType, token.lexeme());
|
|
||||||
}
|
|
||||||
return resolveSymbolDefinition(token.lexeme(), tokenIndex, scopeState, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<PbsEditorialLocation> usagesMatching(final Set<String> identityKeys) {
|
|
||||||
if (identityKeys == null || identityKeys.isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
final FileId fileId = ast.span() == null ? FileId.none() : ast.span().getFileId();
|
|
||||||
final LinkedHashMap<String, PbsEditorialLocation> usages = new LinkedHashMap<>();
|
|
||||||
for (final PbsToken token : tokens) {
|
|
||||||
if (token.kind() != PbsTokenKind.IDENTIFIER) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!intersects(definitionAt(token.start()), identityKeys)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final Span usageSpan = new Span(fileId, token.start(), token.end());
|
|
||||||
if (!hasPhysicalSpan(usageSpan)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
usages.putIfAbsent(locationKey(usageSpan), new PbsEditorialLocation(usageSpan));
|
|
||||||
}
|
|
||||||
return List.copyOf(usages.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean intersects(
|
|
||||||
final List<PbsEditorialLocation> locations,
|
|
||||||
final Set<String> identityKeys) {
|
|
||||||
if (locations == null || locations.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (final PbsEditorialLocation location : locations) {
|
|
||||||
if (location != null && location.span() != null && identityKeys.contains(locationKey(location.span()))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<PbsEditorialLocation> resolveSymbolDefinition(
|
|
||||||
final String symbolName,
|
|
||||||
final int tokenIndex,
|
|
||||||
final ScopeState scopeState,
|
|
||||||
final int offset) {
|
|
||||||
if (scopeState.parameterNames().contains(symbolName)) {
|
|
||||||
return parameterLocations(symbolName, offset);
|
|
||||||
}
|
|
||||||
final Scope.LocalSymbol localSymbol = scopeState.scope().entries().get(symbolName);
|
|
||||||
if (localSymbol != null) {
|
|
||||||
return localLocations(symbolName, offset);
|
|
||||||
}
|
|
||||||
final Integer previousTokenIndex = previousSignificantTokenIndex(tokenIndex);
|
|
||||||
if (previousTokenIndex != null && token(previousTokenIndex).kind() == PbsTokenKind.NEW) {
|
|
||||||
return constructorLocations(resolveTopDeclSymbol(symbolName));
|
|
||||||
}
|
|
||||||
final TopDeclSymbol importedSymbol = importedSymbols.get(symbolName);
|
|
||||||
if (importedSymbol != null) {
|
|
||||||
return topDeclLocations(importedSymbol);
|
|
||||||
}
|
|
||||||
final TopDeclSymbol supplementalSymbol = supplementalSymbols.get(symbolName);
|
|
||||||
if (supplementalSymbol != null) {
|
|
||||||
return topDeclLocations(supplementalSymbol);
|
|
||||||
}
|
|
||||||
final TopDeclSymbol topLevelSymbol = topLevelSymbols.get(symbolName);
|
|
||||||
if (topLevelSymbol != null) {
|
|
||||||
return topDeclLocations(topLevelSymbol);
|
|
||||||
}
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<PbsEditorialLocation> resolveMemberDefinition(
|
|
||||||
final TypeView receiverType,
|
|
||||||
final String memberName) {
|
|
||||||
if (receiverType == null) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
if (receiverType.kind() == Kind.STRUCT) {
|
|
||||||
final Span fieldSpan = structFieldSpan(receiverType.name(), memberName);
|
|
||||||
if (fieldSpan != null) {
|
|
||||||
return locations(fieldSpan);
|
|
||||||
}
|
|
||||||
final StructInfo structInfo = model.structs.get(receiverType.name());
|
|
||||||
if (structInfo == null) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
return callableLocations(structInfo.methods().get(memberName));
|
|
||||||
}
|
|
||||||
if (receiverType.kind() == Kind.SERVICE || receiverType.kind() == Kind.CONTRACT) {
|
|
||||||
final Map<String, List<CallableSymbol>> methods = receiverType.kind() == Kind.SERVICE
|
|
||||||
? Optional.ofNullable(model.services.get(receiverType.name())).map(ServiceInfo::methods).orElse(Map.of())
|
|
||||||
: Optional.ofNullable(model.contracts.get(receiverType.name())).map(ContractInfo::methods).orElse(Map.of());
|
|
||||||
return callableLocations(methods.get(memberName));
|
|
||||||
}
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<PbsEditorialLocation> parameterLocations(
|
|
||||||
final String symbolName,
|
|
||||||
final int offset) {
|
|
||||||
final CallableFrame callableFrame = callableFrameAt(offset);
|
|
||||||
if (callableFrame == null) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
for (final var parameter : callableFrame.parameters()) {
|
|
||||||
if (symbolName.equals(parameter.name())) {
|
|
||||||
return locations(parameter.span());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<PbsEditorialLocation> localLocations(
|
|
||||||
final String symbolName,
|
|
||||||
final int offset) {
|
|
||||||
final CallableFrame callableFrame = callableFrameAt(offset);
|
|
||||||
if (callableFrame == null) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
return locations(bindingSpan(callableFrame.body(), symbolName, offset, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Span bindingSpan(
|
|
||||||
final PbsAst.Block block,
|
|
||||||
final String symbolName,
|
|
||||||
final int offset,
|
|
||||||
final Span current) {
|
|
||||||
Span found = current;
|
|
||||||
for (final var statement : block.statements()) {
|
|
||||||
if (statement.span().getStart() >= offset) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (statement instanceof PbsAst.LetStatement letStatement && symbolName.equals(letStatement.name())) {
|
|
||||||
found = letStatement.nameSpan() == null ? letStatement.span() : letStatement.nameSpan();
|
|
||||||
}
|
|
||||||
if (statement.span().contains(offset)) {
|
|
||||||
if (statement instanceof PbsAst.IfStatement ifStatement) {
|
|
||||||
if (ifStatement.thenBlock().span().contains(offset)) {
|
|
||||||
return bindingSpan(ifStatement.thenBlock(), symbolName, offset, found);
|
|
||||||
}
|
|
||||||
if (ifStatement.elseBlock() != null && ifStatement.elseBlock().span().contains(offset)) {
|
|
||||||
return bindingSpan(ifStatement.elseBlock(), symbolName, offset, found);
|
|
||||||
}
|
|
||||||
if (ifStatement.elseIf() != null && ifStatement.elseIf().span().contains(offset)) {
|
|
||||||
final var nestedBlock = new PbsAst.Block(
|
|
||||||
ReadOnlyList.wrap(List.of(ifStatement.elseIf())),
|
|
||||||
null,
|
|
||||||
ifStatement.elseIf().span());
|
|
||||||
return bindingSpan(nestedBlock, symbolName, offset, found);
|
|
||||||
}
|
|
||||||
} else if (statement instanceof PbsAst.ForStatement forStatement) {
|
|
||||||
Span loopFound = found;
|
|
||||||
if (symbolName.equals(forStatement.iteratorName())) {
|
|
||||||
loopFound = forStatement.span();
|
|
||||||
}
|
|
||||||
if (forStatement.body().span().contains(offset)) {
|
|
||||||
return bindingSpan(forStatement.body(), symbolName, offset, loopFound);
|
|
||||||
}
|
|
||||||
} else if (statement instanceof PbsAst.WhileStatement whileStatement
|
|
||||||
&& whileStatement.body().span().contains(offset)) {
|
|
||||||
return bindingSpan(whileStatement.body(), symbolName, offset, found);
|
|
||||||
}
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<PbsEditorialLocation> constructorLocations(final TopDeclSymbol topDeclSymbol) {
|
|
||||||
if (topDeclSymbol == null) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
if (topDeclSymbol.decl() instanceof PbsAst.StructDecl structDecl) {
|
|
||||||
if (structDecl.ctors().isEmpty()) {
|
|
||||||
return locations(structDecl.span());
|
|
||||||
}
|
|
||||||
final ArrayList<Span> ctorSpans = new ArrayList<>();
|
|
||||||
for (final var ctor : structDecl.ctors()) {
|
|
||||||
ctorSpans.add(ctor.span());
|
|
||||||
}
|
|
||||||
return locations(ctorSpans.toArray(Span[]::new));
|
|
||||||
}
|
|
||||||
return topDeclLocations(topDeclSymbol);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<PbsEditorialLocation> topDeclLocations(final TopDeclSymbol topDeclSymbol) {
|
|
||||||
if (topDeclSymbol == null) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
return locations(topDeclSymbol.decl().span());
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<PbsEditorialLocation> callableLocations(final List<CallableSymbol> callables) {
|
|
||||||
if (callables == null || callables.isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
final ArrayList<Span> spans = new ArrayList<>();
|
|
||||||
for (final var callable : callables) {
|
|
||||||
spans.add(callable.span());
|
|
||||||
}
|
|
||||||
return locations(spans.toArray(Span[]::new));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Span structFieldSpan(
|
|
||||||
final String typeName,
|
|
||||||
final String fieldName) {
|
|
||||||
final TopDeclSymbol typeSymbol = resolveTopDeclSymbol(typeName);
|
|
||||||
if (typeSymbol == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (typeSymbol.decl() instanceof PbsAst.StructDecl structDecl) {
|
|
||||||
for (final var field : structDecl.fields()) {
|
|
||||||
if (fieldName.equals(field.name())) {
|
|
||||||
return field.span();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (typeSymbol.decl() instanceof PbsAst.BuiltinTypeDecl builtinTypeDecl) {
|
|
||||||
for (final var field : builtinTypeDecl.fields()) {
|
|
||||||
if (fieldName.equals(field.name())) {
|
|
||||||
return field.span();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<PbsEditorialLocation> locations(final Span... spans) {
|
|
||||||
final ArrayList<PbsEditorialLocation> locations = new ArrayList<>();
|
|
||||||
if (spans == null) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
for (final Span span : spans) {
|
|
||||||
if (hasPhysicalSpan(span)) {
|
|
||||||
locations.add(new PbsEditorialLocation(span));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return List.copyOf(locations);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasPhysicalSpan(final Span span) {
|
|
||||||
return span != null
|
|
||||||
&& !span.isNone()
|
|
||||||
&& span.getFileId() != null
|
|
||||||
&& !span.getFileId().isNone();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<PbsEditorialSignature> resolveCallableSignatures(
|
private List<PbsEditorialSignature> resolveCallableSignatures(
|
||||||
final String callableName,
|
final String callableName,
|
||||||
final ScopeState scopeState) {
|
final ScopeState scopeState) {
|
||||||
|
|||||||
@ -265,300 +265,6 @@ final class PbsEditorialSupportServiceTest {
|
|||||||
&& candidate.detail().equals("draw_pixel(x: int, y: int, color: int) -> void")));
|
&& candidate.detail().equals("draw_pixel(x: int, y: int, color: int) -> void")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void shouldResolveDefinitionForLocalsParametersMembersAndCanonicalImports() {
|
|
||||||
final var source = """
|
|
||||||
import { Log as Logger } from @sdk:log;
|
|
||||||
|
|
||||||
fn helper(value: int) -> int {
|
|
||||||
let total = value;
|
|
||||||
Logger.info("hello");
|
|
||||||
missingName;
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
final var ast = parseOrdinary(source);
|
|
||||||
final var supplementalTopDecls = supplementalTopDecls("""
|
|
||||||
declare service Log {
|
|
||||||
fn info(message: str) -> void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""");
|
|
||||||
|
|
||||||
final var parameter = requireDefinition(source, ast, supplementalTopDecls, "value;");
|
|
||||||
assertEquals(1, parameter.size());
|
|
||||||
assertEquals(0, parameter.getFirst().span().getFileId().getId());
|
|
||||||
assertTrue(parameter.getFirst().span().contains(source.indexOf("value: int")));
|
|
||||||
|
|
||||||
final var local = requireDefinition(source, ast, supplementalTopDecls, "total;");
|
|
||||||
assertEquals(1, local.size());
|
|
||||||
assertEquals(0, local.getFirst().span().getFileId().getId());
|
|
||||||
assertTrue(local.getFirst().span().contains(source.indexOf("total = value")));
|
|
||||||
|
|
||||||
final var function = requireDefinition(source, ast, supplementalTopDecls, "helper(value");
|
|
||||||
assertEquals(1, function.size());
|
|
||||||
assertEquals(0, function.getFirst().span().getFileId().getId());
|
|
||||||
assertTrue(function.getFirst().span().getStart() <= source.indexOf("fn helper"));
|
|
||||||
|
|
||||||
final var imported = requireDefinition(source, ast, supplementalTopDecls, "Logger.info");
|
|
||||||
assertEquals(1, imported.size());
|
|
||||||
assertEquals(100, imported.getFirst().span().getFileId().getId());
|
|
||||||
assertNotEquals(ast.span().getFileId().getId(), imported.getFirst().span().getFileId().getId());
|
|
||||||
|
|
||||||
final var member = requireDefinition(source, ast, supplementalTopDecls, "info(");
|
|
||||||
assertEquals(1, member.size());
|
|
||||||
assertEquals(100, member.getFirst().span().getFileId().getId());
|
|
||||||
|
|
||||||
assertTrue(service.definition(source, ast, supplementalTopDecls, source.indexOf("fn helper")).isEmpty());
|
|
||||||
assertTrue(service.definition(source, ast, supplementalTopDecls, source.indexOf("int")).isEmpty());
|
|
||||||
assertTrue(service.definition(source, ast, supplementalTopDecls, source.indexOf("missingName")).isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void shouldReturnEveryMatchingDeclarationLocationForOverloadedMembers() {
|
|
||||||
final var source = """
|
|
||||||
declare struct Vec() {
|
|
||||||
fn blend(dx: int) -> int { return dx; }
|
|
||||||
fn blend(dx: int, dy: int) -> int { return dx; }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main(vec: Vec) -> void {
|
|
||||||
vec.blend(1, 2);
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
final var ast = parseOrdinary(source);
|
|
||||||
final var locations = requireDefinition(source, ast, ReadOnlyList.empty(), "blend(1");
|
|
||||||
assertEquals(2, locations.size());
|
|
||||||
assertTrue(locations.getFirst().span().contains(source.indexOf("fn blend(dx: int)")));
|
|
||||||
assertTrue(locations.get(1).span().contains(source.indexOf("fn blend(dx: int, dy: int)")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void shouldCollectSameFileReferencesForPhysicalSymbolsAndSkipTrivia() {
|
|
||||||
final var source = """
|
|
||||||
fn helper(value: int) -> int {
|
|
||||||
let total = value;
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn frame() -> void {
|
|
||||||
helper(1);
|
|
||||||
helper(2);
|
|
||||||
// helper
|
|
||||||
let text = "helper";
|
|
||||||
missingName;
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
final var ast = parseOrdinary(source);
|
|
||||||
|
|
||||||
final var helperRefs = service.references(source, ast, ReadOnlyList.empty(), source.indexOf("helper(1)"));
|
|
||||||
assertEquals(3, helperRefs.size(), helperRefs.toString());
|
|
||||||
assertTrue(helperRefs.stream().anyMatch(location -> location.span().contains(source.indexOf("helper(value"))));
|
|
||||||
assertTrue(helperRefs.stream().anyMatch(location -> location.span().contains(source.indexOf("helper(1)"))));
|
|
||||||
assertTrue(helperRefs.stream().anyMatch(location -> location.span().contains(source.indexOf("helper(2)"))));
|
|
||||||
assertTrue(helperRefs.stream().noneMatch(location -> location.span().contains(source.indexOf("// helper"))));
|
|
||||||
assertTrue(helperRefs.stream().noneMatch(location -> location.span().contains(source.indexOf("\"helper\""))));
|
|
||||||
|
|
||||||
final var parameterRefs = service.references(source, ast, ReadOnlyList.empty(), source.indexOf("value;"));
|
|
||||||
assertEquals(2, parameterRefs.size());
|
|
||||||
assertTrue(parameterRefs.stream().anyMatch(location -> location.span().contains(source.indexOf("value: int"))));
|
|
||||||
assertTrue(parameterRefs.stream().anyMatch(location -> location.span().contains(source.indexOf("value;"))));
|
|
||||||
|
|
||||||
final var localRefs = service.references(source, ast, ReadOnlyList.empty(), source.indexOf("total;"));
|
|
||||||
assertEquals(1, localRefs.size());
|
|
||||||
assertTrue(localRefs.getFirst().span().contains(source.indexOf("total;")));
|
|
||||||
assertTrue(service.definition(source, ast, ReadOnlyList.empty(), source.indexOf("total = value")).isEmpty());
|
|
||||||
|
|
||||||
assertTrue(service.references(source, ast, ReadOnlyList.empty(), source.indexOf("fn helper")).isEmpty());
|
|
||||||
assertTrue(service.references(source, ast, ReadOnlyList.empty(), source.indexOf("int")).isEmpty());
|
|
||||||
assertTrue(service.references(source, ast, ReadOnlyList.empty(), source.indexOf("missingName")).isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void shouldCollectImportedAndOverloadedReferencesByDefinitionIntersection() {
|
|
||||||
final var importedSource = """
|
|
||||||
declare service Log {
|
|
||||||
fn info(message: str) -> void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
final var importedAst = parseInterface(importedSource, new FileId(100));
|
|
||||||
final var source = """
|
|
||||||
import { Log as Logger } from @sdk:log;
|
|
||||||
|
|
||||||
declare struct Vec() {
|
|
||||||
fn blend(dx: int) -> int { return dx; }
|
|
||||||
fn blend(dx: int, dy: int) -> int { return dx; }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn frame(vec: Vec) -> void {
|
|
||||||
Logger.info("a");
|
|
||||||
Logger.info("b");
|
|
||||||
vec.blend(1, 2);
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
final var ast = parseOrdinary(source);
|
|
||||||
final var supplementalTopDecls = importedAst.topDecls();
|
|
||||||
|
|
||||||
final var infoRefs = service.references(
|
|
||||||
new PbsEditorialSupportService.EditorialDocument(source, ast, supplementalTopDecls, null),
|
|
||||||
source.indexOf("info(\"a\")"),
|
|
||||||
List.of(
|
|
||||||
new PbsEditorialSupportService.EditorialDocument(source, ast, supplementalTopDecls, null),
|
|
||||||
new PbsEditorialSupportService.EditorialDocument(
|
|
||||||
importedSource,
|
|
||||||
importedAst,
|
|
||||||
ReadOnlyList.empty(),
|
|
||||||
null)));
|
|
||||||
assertEquals(2, infoRefs.stream().filter(location -> location.span().getFileId().getId() == 0).count());
|
|
||||||
assertTrue(infoRefs.stream().anyMatch(location -> location.span().contains(source.indexOf("info(\"a\")"))));
|
|
||||||
assertTrue(infoRefs.stream().anyMatch(location -> location.span().contains(source.indexOf("info(\"b\")"))));
|
|
||||||
|
|
||||||
final var blendRefs = service.references(source, ast, supplementalTopDecls, source.indexOf("blend(1"));
|
|
||||||
assertTrue(blendRefs.stream().anyMatch(location -> location.span().contains(source.indexOf("blend(1"))));
|
|
||||||
final var blendIdentity = service.definition(source, ast, supplementalTopDecls, source.indexOf("blend(1"));
|
|
||||||
assertEquals(2, blendIdentity.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<PbsEditorialLocation> requireDefinition(
|
|
||||||
final String source,
|
|
||||||
final PbsAst.File ast,
|
|
||||||
final ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
|
|
||||||
final String needle) {
|
|
||||||
final int offset = source.indexOf(needle);
|
|
||||||
assertTrue(offset >= 0, "Expected to find needle: " + needle);
|
|
||||||
final var locations = service.definition(source, ast, supplementalTopDecls, offset);
|
|
||||||
assertFalse(locations.isEmpty(), "Expected definition for needle: " + needle);
|
|
||||||
return locations;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void shouldBuildAstBackedOutlineHierarchyAndKeepOverloadsAsSiblings() {
|
|
||||||
final var source = """
|
|
||||||
import { Log } from @sdk:log;
|
|
||||||
|
|
||||||
[Init]
|
|
||||||
fn init() -> void { return; }
|
|
||||||
fn helper(a: int) -> int { return a; }
|
|
||||||
fn helper(a: int, b: int) -> int { return a; }
|
|
||||||
declare struct Point(x: int, pub y: int) {
|
|
||||||
ctor make(x: int) { return; }
|
|
||||||
fn run() -> void { return; }
|
|
||||||
}
|
|
||||||
declare contract TickLike { fn tick(dt: int) -> void; }
|
|
||||||
declare service Physics { fn tick() -> void { return; } }
|
|
||||||
declare error Io { NotFound; Busy; }
|
|
||||||
declare enum Mode(Idle = 0, Run = 1);
|
|
||||||
declare callback OnClick(x: int) -> result<Io> int;
|
|
||||||
declare global STATE: int = 10;
|
|
||||||
declare const LIMIT: int = 10;
|
|
||||||
implements TickLike for Point using s { fn tick(dt: int) -> void { return; } }
|
|
||||||
|
|
||||||
fn frame() -> void {
|
|
||||||
let local = 1;
|
|
||||||
helper(local);
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
final var ast = parseOrdinary(source);
|
|
||||||
final var symbols = service.documentSymbols(ast);
|
|
||||||
|
|
||||||
assertEquals(List.of(
|
|
||||||
"init",
|
|
||||||
"helper",
|
|
||||||
"helper",
|
|
||||||
"Point",
|
|
||||||
"TickLike",
|
|
||||||
"Physics",
|
|
||||||
"Io",
|
|
||||||
"Mode",
|
|
||||||
"OnClick",
|
|
||||||
"STATE",
|
|
||||||
"LIMIT",
|
|
||||||
"TickLike",
|
|
||||||
"frame"), names(symbols));
|
|
||||||
assertTrue(symbols.stream().noneMatch(symbol -> symbol.name().equals("Log") || symbol.name().equals("local")));
|
|
||||||
assertTrue(symbols.getFirst().name().equals("init"));
|
|
||||||
assertFalse(symbols.getFirst().name().contains("Init"));
|
|
||||||
assertEquals("(a: int) -> int", symbols.get(1).detail());
|
|
||||||
assertEquals("(a: int, b: int) -> int", symbols.get(2).detail());
|
|
||||||
|
|
||||||
final var point = requireNamed(symbols, "Point");
|
|
||||||
assertEquals(PbsEditorialSymbolKind.STRUCT, point.kind());
|
|
||||||
assertEquals(List.of("x", "y", "make", "run"), names(point.children()));
|
|
||||||
assertEquals(PbsEditorialSymbolKind.FIELD, point.children().getFirst().kind());
|
|
||||||
assertEquals("int", point.children().getFirst().detail());
|
|
||||||
assertEquals(PbsEditorialSymbolKind.CONSTRUCTOR, point.children().get(2).kind());
|
|
||||||
assertEquals("(x: int)", point.children().get(2).detail());
|
|
||||||
assertEquals(PbsEditorialSymbolKind.METHOD, point.children().get(3).kind());
|
|
||||||
|
|
||||||
final var contract = symbols.get(4);
|
|
||||||
assertEquals(PbsEditorialSymbolKind.CONTRACT, contract.kind());
|
|
||||||
assertEquals(List.of("tick"), names(contract.children()));
|
|
||||||
assertEquals("(dt: int) -> void", contract.children().getFirst().detail());
|
|
||||||
|
|
||||||
final var physics = requireNamed(symbols, "Physics");
|
|
||||||
assertEquals(PbsEditorialSymbolKind.SERVICE, physics.kind());
|
|
||||||
assertEquals(List.of("tick"), names(physics.children()));
|
|
||||||
|
|
||||||
final var error = requireNamed(symbols, "Io");
|
|
||||||
assertEquals(PbsEditorialSymbolKind.ERROR, error.kind());
|
|
||||||
assertEquals(List.of("NotFound", "Busy"), names(error.children()));
|
|
||||||
|
|
||||||
final var mode = requireNamed(symbols, "Mode");
|
|
||||||
assertEquals(PbsEditorialSymbolKind.ENUM, mode.kind());
|
|
||||||
assertEquals(List.of("Idle", "Run"), names(mode.children()));
|
|
||||||
assertEquals("0", mode.children().getFirst().detail());
|
|
||||||
|
|
||||||
final var callback = requireNamed(symbols, "OnClick");
|
|
||||||
assertEquals(PbsEditorialSymbolKind.CALLBACK, callback.kind());
|
|
||||||
assertEquals("(x: int) -> result<Io> int", callback.detail());
|
|
||||||
|
|
||||||
assertEquals(PbsEditorialSymbolKind.GLOBAL, requireNamed(symbols, "STATE").kind());
|
|
||||||
assertEquals("int", requireNamed(symbols, "STATE").detail());
|
|
||||||
assertEquals(PbsEditorialSymbolKind.CONST, requireNamed(symbols, "LIMIT").kind());
|
|
||||||
|
|
||||||
final var implementsSymbol = symbols.get(11);
|
|
||||||
assertEquals("TickLike", implementsSymbol.name());
|
|
||||||
assertEquals("Point", implementsSymbol.detail());
|
|
||||||
assertEquals(PbsEditorialSymbolKind.CONTRACT, implementsSymbol.kind());
|
|
||||||
assertEquals(List.of("tick"), names(implementsSymbol.children()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void shouldOmitInvalidDeclarationsAndKeepNamedDeclsFromRecoveredAst() {
|
|
||||||
final var source = """
|
|
||||||
declare ;
|
|
||||||
fn ok() -> int { return 1; }
|
|
||||||
""";
|
|
||||||
final var ast = parseAllowingErrors(source);
|
|
||||||
final var symbols = service.documentSymbols(ast);
|
|
||||||
assertEquals(List.of("ok"), names(symbols));
|
|
||||||
assertEquals(PbsEditorialSymbolKind.FUNCTION, symbols.getFirst().kind());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void shouldOutlineHostAndBuiltinTypeDeclarationsFromInterfaceModules() {
|
|
||||||
final var source = """
|
|
||||||
declare host Gfx {
|
|
||||||
fn clear(color: int) -> void;
|
|
||||||
}
|
|
||||||
declare builtin type Color(
|
|
||||||
pub r: int
|
|
||||||
) {
|
|
||||||
fn pack() -> int;
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
final var ast = parseInterface(source, new FileId(0));
|
|
||||||
final var symbols = service.documentSymbols(ast);
|
|
||||||
assertEquals(List.of("Gfx", "Color"), names(symbols));
|
|
||||||
assertEquals(PbsEditorialSymbolKind.HOST, symbols.getFirst().kind());
|
|
||||||
assertEquals(List.of("clear"), names(symbols.getFirst().children()));
|
|
||||||
assertEquals(PbsEditorialSymbolKind.BUILTIN_TYPE, symbols.get(1).kind());
|
|
||||||
assertEquals(List.of("r", "pack"), names(symbols.get(1).children()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private PbsEditorialResolvedSymbol requireHover(
|
private PbsEditorialResolvedSymbol requireHover(
|
||||||
final String source,
|
final String source,
|
||||||
final PbsAst.File ast,
|
final PbsAst.File ast,
|
||||||
@ -580,26 +286,6 @@ final class PbsEditorialSupportServiceTest {
|
|||||||
return ReadOnlyList.wrap(topDecls);
|
return ReadOnlyList.wrap(topDecls);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> names(final List<PbsEditorialDocumentSymbol> symbols) {
|
|
||||||
return symbols.stream().map(PbsEditorialDocumentSymbol::name).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private PbsEditorialDocumentSymbol requireNamed(
|
|
||||||
final List<PbsEditorialDocumentSymbol> symbols,
|
|
||||||
final String name) {
|
|
||||||
return symbols.stream()
|
|
||||||
.filter(symbol -> symbol.name().equals(name))
|
|
||||||
.findFirst()
|
|
||||||
.orElseThrow(() -> new AssertionError("missing symbol: " + name + " in " + names(symbols)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private PbsAst.File parseAllowingErrors(final String source) {
|
|
||||||
final var diagnostics = DiagnosticSink.empty();
|
|
||||||
final var fileId = new FileId(0);
|
|
||||||
final var tokens = PbsLexer.lex(source, fileId, diagnostics);
|
|
||||||
return PbsParser.parse(tokens, fileId, diagnostics, PbsParser.ParseMode.ORDINARY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private PbsAst.File parseOrdinary(final String source) {
|
private PbsAst.File parseOrdinary(final String source) {
|
||||||
return parse(source, new FileId(0), PbsParser.ParseMode.ORDINARY);
|
return parse(source, new FileId(0), PbsParser.ParseMode.ORDINARY);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
package p.studio.compiler.services;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record FrontendDefinitionLocation(
|
|
||||||
Path path,
|
|
||||||
int startOffset,
|
|
||||||
int endOffset) {
|
|
||||||
|
|
||||||
public FrontendDefinitionLocation {
|
|
||||||
path = Objects.requireNonNull(path, "path").toAbsolutePath().normalize();
|
|
||||||
if (startOffset < 0) {
|
|
||||||
throw new IllegalArgumentException("startOffset must not be negative");
|
|
||||||
}
|
|
||||||
if (endOffset < startOffset) {
|
|
||||||
throw new IllegalArgumentException("endOffset must not be before startOffset");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
package p.studio.compiler.services;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record FrontendDocumentSymbol(
|
|
||||||
String name,
|
|
||||||
FrontendSymbolKind kind,
|
|
||||||
String detail,
|
|
||||||
int rangeStart,
|
|
||||||
int rangeEnd,
|
|
||||||
int selectionStart,
|
|
||||||
int selectionEnd,
|
|
||||||
List<FrontendDocumentSymbol> children) {
|
|
||||||
|
|
||||||
public FrontendDocumentSymbol {
|
|
||||||
name = requireText(name, "name");
|
|
||||||
kind = Objects.requireNonNull(kind, "kind");
|
|
||||||
detail = detail == null ? "" : detail.trim();
|
|
||||||
if (rangeStart < 0) {
|
|
||||||
throw new IllegalArgumentException("rangeStart must not be negative");
|
|
||||||
}
|
|
||||||
if (rangeEnd < rangeStart) {
|
|
||||||
throw new IllegalArgumentException("rangeEnd must not be before rangeStart");
|
|
||||||
}
|
|
||||||
if (selectionStart < 0) {
|
|
||||||
throw new IllegalArgumentException("selectionStart must not be negative");
|
|
||||||
}
|
|
||||||
if (selectionEnd < selectionStart) {
|
|
||||||
throw new IllegalArgumentException("selectionEnd must not be before selectionStart");
|
|
||||||
}
|
|
||||||
children = children == null ? List.of() : List.copyOf(children);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String requireText(
|
|
||||||
final String value,
|
|
||||||
final String field) {
|
|
||||||
final String candidate = Objects.requireNonNull(value, field).trim();
|
|
||||||
if (candidate.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException(field + " must not be blank");
|
|
||||||
}
|
|
||||||
return candidate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -3,22 +3,5 @@ package p.studio.compiler.services;
|
|||||||
public record FrontendEditorialContext(
|
public record FrontendEditorialContext(
|
||||||
Object syntaxTree,
|
Object syntaxTree,
|
||||||
Object supplementalDeclarations,
|
Object supplementalDeclarations,
|
||||||
Object semanticContext,
|
Object semanticContext) {
|
||||||
Object sourceFiles,
|
|
||||||
Object projectSurface) {
|
|
||||||
|
|
||||||
public FrontendEditorialContext(
|
|
||||||
final Object syntaxTree,
|
|
||||||
final Object supplementalDeclarations,
|
|
||||||
final Object semanticContext) {
|
|
||||||
this(syntaxTree, supplementalDeclarations, semanticContext, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FrontendEditorialContext(
|
|
||||||
final Object syntaxTree,
|
|
||||||
final Object supplementalDeclarations,
|
|
||||||
final Object semanticContext,
|
|
||||||
final Object sourceFiles) {
|
|
||||||
this(syntaxTree, supplementalDeclarations, semanticContext, sourceFiles, null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -59,40 +59,4 @@ public interface FrontendLanguageService {
|
|||||||
final int offset) {
|
final int offset) {
|
||||||
return signatureHelp(request, offset);
|
return signatureHelp(request, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
default List<FrontendDefinitionLocation> definition(
|
|
||||||
final FrontendDocumentRequest request,
|
|
||||||
final int offset) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
default List<FrontendDefinitionLocation> definition(
|
|
||||||
final FrontendDocumentRequest request,
|
|
||||||
final FrontendEditorialContext editorialContext,
|
|
||||||
final int offset) {
|
|
||||||
return definition(request, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
default List<FrontendDefinitionLocation> references(
|
|
||||||
final FrontendDocumentRequest request,
|
|
||||||
final int offset) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
default List<FrontendDefinitionLocation> references(
|
|
||||||
final FrontendDocumentRequest request,
|
|
||||||
final FrontendEditorialContext editorialContext,
|
|
||||||
final int offset) {
|
|
||||||
return references(request, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
default List<FrontendDocumentSymbol> documentSymbols(final FrontendDocumentRequest request) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
default List<FrontendDocumentSymbol> documentSymbols(
|
|
||||||
final FrontendDocumentRequest request,
|
|
||||||
final FrontendEditorialContext editorialContext) {
|
|
||||||
return documentSymbols(request);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,85 +22,6 @@ class FrontendLanguageServiceTest {
|
|||||||
assertTrue(service.completion(request, 0).isEmpty());
|
assertTrue(service.completion(request, 0).isEmpty());
|
||||||
assertTrue(service.hover(request, 0).isEmpty());
|
assertTrue(service.hover(request, 0).isEmpty());
|
||||||
assertEquals(FrontendSignatureHelp.empty(), service.signatureHelp(request, 0));
|
assertEquals(FrontendSignatureHelp.empty(), service.signatureHelp(request, 0));
|
||||||
assertTrue(service.definition(request, 0).isEmpty());
|
|
||||||
assertTrue(service.references(request, 0).isEmpty());
|
|
||||||
assertTrue(service.documentSymbols(request).isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void documentSymbolRejectsBlankNameAndInvertedRangesAndCopiesChildren() {
|
|
||||||
final var child = new FrontendDocumentSymbol(
|
|
||||||
"x",
|
|
||||||
FrontendSymbolKind.FIELD,
|
|
||||||
"int",
|
|
||||||
10,
|
|
||||||
16,
|
|
||||||
10,
|
|
||||||
16,
|
|
||||||
List.of());
|
|
||||||
final var symbol = new FrontendDocumentSymbol(
|
|
||||||
" Point ",
|
|
||||||
FrontendSymbolKind.STRUCT,
|
|
||||||
" ",
|
|
||||||
0,
|
|
||||||
20,
|
|
||||||
8,
|
|
||||||
13,
|
|
||||||
List.of(child));
|
|
||||||
assertEquals("Point", symbol.name());
|
|
||||||
assertEquals("", symbol.detail());
|
|
||||||
assertEquals(1, symbol.children().size());
|
|
||||||
assertEquals("x", symbol.children().getFirst().name());
|
|
||||||
assertThrows(UnsupportedOperationException.class, () -> symbol.children().add(child));
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> new FrontendDocumentSymbol(
|
|
||||||
" ",
|
|
||||||
FrontendSymbolKind.FUNCTION,
|
|
||||||
"",
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
List.of()));
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> new FrontendDocumentSymbol(
|
|
||||||
"fn",
|
|
||||||
FrontendSymbolKind.FUNCTION,
|
|
||||||
"",
|
|
||||||
-1,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
List.of()));
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> new FrontendDocumentSymbol(
|
|
||||||
"fn",
|
|
||||||
FrontendSymbolKind.FUNCTION,
|
|
||||||
"",
|
|
||||||
5,
|
|
||||||
4,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
List.of()));
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> new FrontendDocumentSymbol(
|
|
||||||
"fn",
|
|
||||||
FrontendSymbolKind.FUNCTION,
|
|
||||||
"",
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
3,
|
|
||||||
2,
|
|
||||||
List.of()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void definitionLocationRejectsBlankPathAndInvertedOffsets() {
|
|
||||||
final Path path = Path.of("src/main.pbs").toAbsolutePath();
|
|
||||||
final var location = new FrontendDefinitionLocation(path, 4, 9);
|
|
||||||
assertEquals(path.normalize(), location.path());
|
|
||||||
assertEquals(4, location.startOffset());
|
|
||||||
assertEquals(9, location.endOffset());
|
|
||||||
|
|
||||||
assertThrows(NullPointerException.class, () -> new FrontendDefinitionLocation(null, 0, 1));
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> new FrontendDefinitionLocation(path, -1, 0));
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> new FrontendDefinitionLocation(path, 5, 4));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
package p.studio.lsp.messages;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public record BaselineDefinition(
|
|
||||||
List<BaselineDefinitionLocation> locations) {
|
|
||||||
|
|
||||||
public BaselineDefinition {
|
|
||||||
locations = locations == null ? List.of() : List.copyOf(locations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BaselineDefinition empty() {
|
|
||||||
return new BaselineDefinition(List.of());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
package p.studio.lsp.messages;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record BaselineDefinitionLocation(
|
|
||||||
String uri,
|
|
||||||
int startLine,
|
|
||||||
int startCharacter,
|
|
||||||
int endLine,
|
|
||||||
int endCharacter) {
|
|
||||||
|
|
||||||
public BaselineDefinitionLocation {
|
|
||||||
uri = requireText(uri, "uri");
|
|
||||||
if (startLine < 0 || startCharacter < 0 || endLine < 0 || endCharacter < 0) {
|
|
||||||
throw new IllegalArgumentException("definition range coordinates must not be negative");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String requireText(
|
|
||||||
final String value,
|
|
||||||
final String field) {
|
|
||||||
final String candidate = Objects.requireNonNull(value, field).trim();
|
|
||||||
if (candidate.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException(field + " must not be blank");
|
|
||||||
}
|
|
||||||
return candidate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
package p.studio.lsp.messages;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record BaselineDocumentSymbol(
|
|
||||||
String name,
|
|
||||||
BaselineDocumentSymbolKind kind,
|
|
||||||
String detail,
|
|
||||||
int startLine,
|
|
||||||
int startCharacter,
|
|
||||||
int endLine,
|
|
||||||
int endCharacter,
|
|
||||||
int selectionStartLine,
|
|
||||||
int selectionStartCharacter,
|
|
||||||
int selectionEndLine,
|
|
||||||
int selectionEndCharacter,
|
|
||||||
List<BaselineDocumentSymbol> children) {
|
|
||||||
|
|
||||||
public BaselineDocumentSymbol {
|
|
||||||
name = requireText(name, "name");
|
|
||||||
kind = Objects.requireNonNull(kind, "kind");
|
|
||||||
detail = detail == null ? "" : detail;
|
|
||||||
if (startLine < 0 || startCharacter < 0 || endLine < 0 || endCharacter < 0
|
|
||||||
|| selectionStartLine < 0 || selectionStartCharacter < 0
|
|
||||||
|| selectionEndLine < 0 || selectionEndCharacter < 0) {
|
|
||||||
throw new IllegalArgumentException("document symbol range coordinates must not be negative");
|
|
||||||
}
|
|
||||||
children = children == null ? List.of() : List.copyOf(children);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String requireText(
|
|
||||||
final String value,
|
|
||||||
final String field) {
|
|
||||||
final String candidate = Objects.requireNonNull(value, field).trim();
|
|
||||||
if (candidate.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException(field + " must not be blank");
|
|
||||||
}
|
|
||||||
return candidate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
package p.studio.lsp.messages;
|
|
||||||
|
|
||||||
public enum BaselineDocumentSymbolKind {
|
|
||||||
KEYWORD,
|
|
||||||
LOCAL,
|
|
||||||
PARAMETER,
|
|
||||||
FIELD,
|
|
||||||
FUNCTION,
|
|
||||||
METHOD,
|
|
||||||
CONSTRUCTOR,
|
|
||||||
STRUCT,
|
|
||||||
BUILTIN_TYPE,
|
|
||||||
SERVICE,
|
|
||||||
HOST,
|
|
||||||
CONTRACT,
|
|
||||||
CALLBACK,
|
|
||||||
ENUM,
|
|
||||||
ERROR,
|
|
||||||
GLOBAL,
|
|
||||||
CONST
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
package p.studio.lsp.messages;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public record BaselineDocumentSymbols(
|
|
||||||
List<BaselineDocumentSymbol> symbols) {
|
|
||||||
|
|
||||||
public BaselineDocumentSymbols {
|
|
||||||
symbols = symbols == null ? List.of() : List.copyOf(symbols);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BaselineDocumentSymbols empty() {
|
|
||||||
return new BaselineDocumentSymbols(List.of());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
package p.studio.lsp.messages;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record BaselineReferences(
|
|
||||||
List<BaselineDefinitionLocation> locations) {
|
|
||||||
|
|
||||||
public BaselineReferences {
|
|
||||||
locations = List.copyOf(Objects.requireNonNull(locations, "locations"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BaselineReferences empty() {
|
|
||||||
return new BaselineReferences(List.of());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -10,9 +10,6 @@ public record BaselineServerDescription(
|
|||||||
boolean hoverSupported,
|
boolean hoverSupported,
|
||||||
boolean completionSupported,
|
boolean completionSupported,
|
||||||
boolean signatureHelpSupported,
|
boolean signatureHelpSupported,
|
||||||
boolean definitionSupported,
|
|
||||||
boolean referencesSupported,
|
|
||||||
boolean documentSymbolsSupported,
|
|
||||||
List<String> semanticTokenTypes,
|
List<String> semanticTokenTypes,
|
||||||
List<BaselineSemanticHostProjection> semanticHostProjections,
|
List<BaselineSemanticHostProjection> semanticHostProjections,
|
||||||
List<BaselineVisualTheme> visualThemes,
|
List<BaselineVisualTheme> visualThemes,
|
||||||
|
|||||||
@ -2,10 +2,7 @@ package p.studio.lsp.services;
|
|||||||
|
|
||||||
import p.studio.lsp.messages.BaselineDocumentAnalysis;
|
import p.studio.lsp.messages.BaselineDocumentAnalysis;
|
||||||
import p.studio.lsp.messages.BaselineCompletion;
|
import p.studio.lsp.messages.BaselineCompletion;
|
||||||
import p.studio.lsp.messages.BaselineDefinition;
|
|
||||||
import p.studio.lsp.messages.BaselineHover;
|
import p.studio.lsp.messages.BaselineHover;
|
||||||
import p.studio.lsp.messages.BaselineDocumentSymbols;
|
|
||||||
import p.studio.lsp.messages.BaselineReferences;
|
|
||||||
import p.studio.lsp.messages.BaselineSemanticTokens;
|
import p.studio.lsp.messages.BaselineSemanticTokens;
|
||||||
import p.studio.lsp.messages.BaselineSignatureHelp;
|
import p.studio.lsp.messages.BaselineSignatureHelp;
|
||||||
import p.studio.lsp.messages.BaselineServerDescription;
|
import p.studio.lsp.messages.BaselineServerDescription;
|
||||||
@ -20,18 +17,6 @@ public interface LanguageServiceBridge {
|
|||||||
|
|
||||||
BaselineHover hover(LspProjectContext context, String documentUri, String text, int line, int character);
|
BaselineHover hover(LspProjectContext context, String documentUri, String text, int line, int character);
|
||||||
|
|
||||||
BaselineDefinition definition(LspProjectContext context, String documentUri, String text, int line, int character);
|
|
||||||
|
|
||||||
BaselineReferences references(
|
|
||||||
LspProjectContext context,
|
|
||||||
String documentUri,
|
|
||||||
String text,
|
|
||||||
int line,
|
|
||||||
int character,
|
|
||||||
boolean includeDeclaration);
|
|
||||||
|
|
||||||
BaselineDocumentSymbols documentSymbols(LspProjectContext context, String documentUri, String text);
|
|
||||||
|
|
||||||
BaselineSignatureHelp signatureHelp(LspProjectContext context, String documentUri, String text, int line, int character);
|
BaselineSignatureHelp signatureHelp(LspProjectContext context, String documentUri, String text, int line, int character);
|
||||||
|
|
||||||
BaselineSemanticTokens semanticTokens(LspProjectContext context, String documentUri, String text);
|
BaselineSemanticTokens semanticTokens(LspProjectContext context, String documentUri, String text);
|
||||||
|
|||||||
@ -5,9 +5,7 @@ import p.studio.compiler.FrontendRegistryService;
|
|||||||
import p.studio.compiler.messages.*;
|
import p.studio.compiler.messages.*;
|
||||||
import p.studio.compiler.models.*;
|
import p.studio.compiler.models.*;
|
||||||
import p.studio.compiler.services.FrontendCompletionCandidate;
|
import p.studio.compiler.services.FrontendCompletionCandidate;
|
||||||
import p.studio.compiler.services.FrontendDefinitionLocation;
|
|
||||||
import p.studio.compiler.services.FrontendDocumentRequest;
|
import p.studio.compiler.services.FrontendDocumentRequest;
|
||||||
import p.studio.compiler.services.FrontendDocumentSymbol;
|
|
||||||
import p.studio.compiler.services.FrontendEditorialContext;
|
import p.studio.compiler.services.FrontendEditorialContext;
|
||||||
import p.studio.compiler.services.FrontendHover;
|
import p.studio.compiler.services.FrontendHover;
|
||||||
import p.studio.compiler.services.FrontendProvider;
|
import p.studio.compiler.services.FrontendProvider;
|
||||||
@ -23,8 +21,6 @@ import p.studio.lsp.services.LanguageServiceBridge;
|
|||||||
import p.studio.utilities.logs.LogAggregator;
|
import p.studio.utilities.logs.LogAggregator;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
@ -46,7 +42,6 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
Objects.requireNonNull(context, "context");
|
Objects.requireNonNull(context, "context");
|
||||||
final var frontend = frontendSpec(context);
|
final var frontend = frontendSpec(context);
|
||||||
final var presentation = frontend.getSemanticPresentation();
|
final var presentation = frontend.getSemanticPresentation();
|
||||||
final boolean languageServicePresent = frontendProvider(context).languageService().isPresent();
|
|
||||||
return new BaselineServerDescription(
|
return new BaselineServerDescription(
|
||||||
"Prometeu Studio LSP",
|
"Prometeu Studio LSP",
|
||||||
"0.1.0",
|
"0.1.0",
|
||||||
@ -54,9 +49,6 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
languageServicePresent,
|
|
||||||
languageServicePresent,
|
|
||||||
languageServicePresent,
|
|
||||||
presentation.semanticKeys(),
|
presentation.semanticKeys(),
|
||||||
presentation.hostProjections().stream().map(this::mapSemanticHostProjection).toList(),
|
presentation.hostProjections().stream().map(this::mapSemanticHostProjection).toList(),
|
||||||
presentation.themes().stream().map(this::mapVisualTheme).toList(),
|
presentation.themes().stream().map(this::mapVisualTheme).toList(),
|
||||||
@ -95,9 +87,13 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
context.projectRoot(),
|
context.projectRoot(),
|
||||||
normalizeDocumentPath(documentUri),
|
normalizeDocumentPath(documentUri),
|
||||||
document.text());
|
document.text());
|
||||||
|
final FrontendEditorialContext editorialContext = new FrontendEditorialContext(
|
||||||
|
document.editorialContext().syntaxTree(),
|
||||||
|
document.editorialContext().supplementalDeclarations(),
|
||||||
|
document.editorialContext().semanticContext());
|
||||||
final List<FrontendCompletionCandidate> candidates = languageService.orElseThrow().completion(
|
final List<FrontendCompletionCandidate> candidates = languageService.orElseThrow().completion(
|
||||||
request,
|
request,
|
||||||
document.editorialContext(),
|
editorialContext,
|
||||||
offset);
|
offset);
|
||||||
return new BaselineCompletion(
|
return new BaselineCompletion(
|
||||||
false,
|
false,
|
||||||
@ -126,115 +122,19 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
context.projectRoot(),
|
context.projectRoot(),
|
||||||
normalizeDocumentPath(documentUri),
|
normalizeDocumentPath(documentUri),
|
||||||
document.text());
|
document.text());
|
||||||
|
final FrontendEditorialContext editorialContext = new FrontendEditorialContext(
|
||||||
|
document.editorialContext().syntaxTree(),
|
||||||
|
document.editorialContext().supplementalDeclarations(),
|
||||||
|
document.editorialContext().semanticContext());
|
||||||
return languageService.orElseThrow().hover(
|
return languageService.orElseThrow().hover(
|
||||||
request,
|
request,
|
||||||
document.editorialContext(),
|
editorialContext,
|
||||||
offset);
|
offset);
|
||||||
})
|
})
|
||||||
.map(symbol -> new BaselineHover(formatHoverMarkdown(symbol)))
|
.map(symbol -> new BaselineHover(formatHoverMarkdown(symbol)))
|
||||||
.orElseGet(() -> new BaselineHover("No symbol information."));
|
.orElseGet(() -> new BaselineHover("No symbol information."));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public BaselineDefinition definition(
|
|
||||||
final LspProjectContext context,
|
|
||||||
final String documentUri,
|
|
||||||
final String text,
|
|
||||||
final int line,
|
|
||||||
final int character) {
|
|
||||||
Objects.requireNonNull(context, "context");
|
|
||||||
final var provider = frontendProvider(context);
|
|
||||||
final var languageService = provider.languageService();
|
|
||||||
if (languageService.isEmpty()) {
|
|
||||||
return BaselineDefinition.empty();
|
|
||||||
}
|
|
||||||
return editorialDocument(context, documentUri, text)
|
|
||||||
.map(document -> {
|
|
||||||
final int offset = new DocumentPositionMapper(document.text()).byteOffsetOf(line, character);
|
|
||||||
final FrontendDocumentRequest request = new FrontendDocumentRequest(
|
|
||||||
context.projectRoot(),
|
|
||||||
normalizeDocumentPath(documentUri),
|
|
||||||
document.text());
|
|
||||||
final List<FrontendDefinitionLocation> locations = languageService.orElseThrow().definition(
|
|
||||||
request,
|
|
||||||
document.editorialContext(),
|
|
||||||
offset);
|
|
||||||
return new BaselineDefinition(mapDefinitionLocations(
|
|
||||||
locations,
|
|
||||||
request.documentPath(),
|
|
||||||
document.text()));
|
|
||||||
})
|
|
||||||
.orElseGet(BaselineDefinition::empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BaselineReferences references(
|
|
||||||
final LspProjectContext context,
|
|
||||||
final String documentUri,
|
|
||||||
final String text,
|
|
||||||
final int line,
|
|
||||||
final int character,
|
|
||||||
final boolean includeDeclaration) {
|
|
||||||
Objects.requireNonNull(context, "context");
|
|
||||||
final var provider = frontendProvider(context);
|
|
||||||
final var languageService = provider.languageService();
|
|
||||||
if (languageService.isEmpty()) {
|
|
||||||
return BaselineReferences.empty();
|
|
||||||
}
|
|
||||||
return editorialDocument(context, documentUri, text)
|
|
||||||
.map(document -> {
|
|
||||||
final int offset = new DocumentPositionMapper(document.text()).byteOffsetOf(line, character);
|
|
||||||
final FrontendDocumentRequest request = new FrontendDocumentRequest(
|
|
||||||
context.projectRoot(),
|
|
||||||
normalizeDocumentPath(documentUri),
|
|
||||||
document.text());
|
|
||||||
final LinkedHashMap<String, FrontendDefinitionLocation> merged = new LinkedHashMap<>();
|
|
||||||
for (final FrontendDefinitionLocation location : languageService.orElseThrow().references(
|
|
||||||
request,
|
|
||||||
document.editorialContext(),
|
|
||||||
offset)) {
|
|
||||||
merged.putIfAbsent(frontendLocationKey(location), location);
|
|
||||||
}
|
|
||||||
if (includeDeclaration) {
|
|
||||||
for (final FrontendDefinitionLocation location : languageService.orElseThrow().definition(
|
|
||||||
request,
|
|
||||||
document.editorialContext(),
|
|
||||||
offset)) {
|
|
||||||
merged.putIfAbsent(frontendLocationKey(location), location);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new BaselineReferences(mapDefinitionLocations(
|
|
||||||
List.copyOf(merged.values()),
|
|
||||||
request.documentPath(),
|
|
||||||
document.text()));
|
|
||||||
})
|
|
||||||
.orElseGet(BaselineReferences::empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BaselineDocumentSymbols documentSymbols(
|
|
||||||
final LspProjectContext context,
|
|
||||||
final String documentUri,
|
|
||||||
final String text) {
|
|
||||||
Objects.requireNonNull(context, "context");
|
|
||||||
final var provider = frontendProvider(context);
|
|
||||||
final var languageService = provider.languageService();
|
|
||||||
if (languageService.isEmpty()) {
|
|
||||||
return BaselineDocumentSymbols.empty();
|
|
||||||
}
|
|
||||||
return editorialDocument(context, documentUri, text)
|
|
||||||
.map(document -> {
|
|
||||||
final FrontendDocumentRequest request = new FrontendDocumentRequest(
|
|
||||||
context.projectRoot(),
|
|
||||||
normalizeDocumentPath(documentUri),
|
|
||||||
document.text());
|
|
||||||
return new BaselineDocumentSymbols(mapDocumentSymbols(
|
|
||||||
languageService.orElseThrow().documentSymbols(request, document.editorialContext()),
|
|
||||||
document.text()));
|
|
||||||
})
|
|
||||||
.orElseGet(BaselineDocumentSymbols::empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BaselineSignatureHelp signatureHelp(
|
public BaselineSignatureHelp signatureHelp(
|
||||||
final LspProjectContext context,
|
final LspProjectContext context,
|
||||||
@ -255,9 +155,13 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
context.projectRoot(),
|
context.projectRoot(),
|
||||||
normalizeDocumentPath(documentUri),
|
normalizeDocumentPath(documentUri),
|
||||||
document.text());
|
document.text());
|
||||||
|
final FrontendEditorialContext editorialContext = new FrontendEditorialContext(
|
||||||
|
document.editorialContext().syntaxTree(),
|
||||||
|
document.editorialContext().supplementalDeclarations(),
|
||||||
|
document.editorialContext().semanticContext());
|
||||||
return languageService.orElseThrow().signatureHelp(
|
return languageService.orElseThrow().signatureHelp(
|
||||||
request,
|
request,
|
||||||
document.editorialContext(),
|
editorialContext,
|
||||||
offset);
|
offset);
|
||||||
})
|
})
|
||||||
.map(this::mapSignatureHelp)
|
.map(this::mapSignatureHelp)
|
||||||
@ -514,90 +418,6 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String frontendLocationKey(final FrontendDefinitionLocation location) {
|
|
||||||
return location.path() + ":" + location.startOffset() + ":" + location.endOffset();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<BaselineDocumentSymbol> mapDocumentSymbols(
|
|
||||||
final List<FrontendDocumentSymbol> symbols,
|
|
||||||
final String documentText) {
|
|
||||||
if (symbols == null || symbols.isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
final DocumentPositionMapper mapper = new DocumentPositionMapper(documentText == null ? "" : documentText);
|
|
||||||
final ArrayList<BaselineDocumentSymbol> mapped = new ArrayList<>();
|
|
||||||
for (final FrontendDocumentSymbol symbol : symbols) {
|
|
||||||
mapped.add(mapDocumentSymbol(symbol, mapper));
|
|
||||||
}
|
|
||||||
return List.copyOf(mapped);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BaselineDocumentSymbol mapDocumentSymbol(
|
|
||||||
final FrontendDocumentSymbol symbol,
|
|
||||||
final DocumentPositionMapper mapper) {
|
|
||||||
final DocumentPosition rangeStart = mapper.positionOf(symbol.rangeStart());
|
|
||||||
final DocumentPosition rangeEnd = mapper.positionOf(symbol.rangeEnd());
|
|
||||||
final DocumentPosition selectionStart = mapper.positionOf(symbol.selectionStart());
|
|
||||||
final DocumentPosition selectionEnd = mapper.positionOf(symbol.selectionEnd());
|
|
||||||
final ArrayList<BaselineDocumentSymbol> children = new ArrayList<>();
|
|
||||||
for (final FrontendDocumentSymbol child : symbol.children()) {
|
|
||||||
children.add(mapDocumentSymbol(child, mapper));
|
|
||||||
}
|
|
||||||
return new BaselineDocumentSymbol(
|
|
||||||
symbol.name(),
|
|
||||||
BaselineDocumentSymbolKind.valueOf(symbol.kind().name()),
|
|
||||||
symbol.detail(),
|
|
||||||
rangeStart.line(),
|
|
||||||
rangeStart.character(),
|
|
||||||
rangeEnd.line(),
|
|
||||||
rangeEnd.character(),
|
|
||||||
selectionStart.line(),
|
|
||||||
selectionStart.character(),
|
|
||||||
selectionEnd.line(),
|
|
||||||
selectionEnd.character(),
|
|
||||||
children);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<BaselineDefinitionLocation> mapDefinitionLocations(
|
|
||||||
final List<FrontendDefinitionLocation> locations,
|
|
||||||
final Path currentDocumentPath,
|
|
||||||
final String currentDocumentText) {
|
|
||||||
if (locations == null || locations.isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
final ArrayList<BaselineDefinitionLocation> mapped = new ArrayList<>();
|
|
||||||
for (final FrontendDefinitionLocation location : locations) {
|
|
||||||
final String targetText = textForDefinitionTarget(location.path(), currentDocumentPath, currentDocumentText);
|
|
||||||
if (targetText == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final DocumentPositionMapper mapper = new DocumentPositionMapper(targetText);
|
|
||||||
final DocumentPosition start = mapper.positionOf(location.startOffset());
|
|
||||||
final DocumentPosition end = mapper.positionOf(location.endOffset());
|
|
||||||
mapped.add(new BaselineDefinitionLocation(
|
|
||||||
location.path().toUri().toString(),
|
|
||||||
start.line(),
|
|
||||||
start.character(),
|
|
||||||
end.line(),
|
|
||||||
end.character()));
|
|
||||||
}
|
|
||||||
return List.copyOf(mapped);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String textForDefinitionTarget(
|
|
||||||
final Path path,
|
|
||||||
final Path currentDocumentPath,
|
|
||||||
final String currentDocumentText) {
|
|
||||||
if (path.equals(currentDocumentPath)) {
|
|
||||||
return currentDocumentText == null ? "" : currentDocumentText;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return Files.readString(path, StandardCharsets.UTF_8);
|
|
||||||
} catch (final Exception ignored) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String formatHoverMarkdown(final FrontendHover symbol) {
|
private String formatHoverMarkdown(final FrontendHover symbol) {
|
||||||
final StringBuilder markdown = new StringBuilder();
|
final StringBuilder markdown = new StringBuilder();
|
||||||
final List<FrontendSignature> signatures = symbol.signatures();
|
final List<FrontendSignature> signatures = symbol.signatures();
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import p.studio.lsp.messages.LspProjectContext;
|
|||||||
import p.studio.lsp.services.LanguageServiceBridge;
|
import p.studio.lsp.services.LanguageServiceBridge;
|
||||||
import p.studio.lsp.services.protocol.mapping.ProtocolMessageMapper;
|
import p.studio.lsp.services.protocol.mapping.ProtocolMessageMapper;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
@ -98,49 +97,6 @@ public final class PrometeuTextDocumentService implements TextDocumentService {
|
|||||||
params.getPosition().getCharacter())));
|
params.getPosition().getCharacter())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> definition(
|
|
||||||
final DefinitionParams params) {
|
|
||||||
final String uri = params.getTextDocument().getUri();
|
|
||||||
final String text = documentTextByUri.get(uri);
|
|
||||||
final List<Location> locations = protocolMessageMapper.mapDefinition(languageServiceBridge.definition(
|
|
||||||
project,
|
|
||||||
uri,
|
|
||||||
text,
|
|
||||||
params.getPosition().getLine(),
|
|
||||||
params.getPosition().getCharacter()));
|
|
||||||
return CompletableFuture.completedFuture(Either.forLeft(locations));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletableFuture<List<? extends Location>> references(final ReferenceParams params) {
|
|
||||||
final String uri = params.getTextDocument().getUri();
|
|
||||||
final String text = documentTextByUri.get(uri);
|
|
||||||
final boolean includeDeclaration = params.getContext() != null
|
|
||||||
&& Boolean.TRUE.equals(params.getContext().isIncludeDeclaration());
|
|
||||||
return CompletableFuture.completedFuture(protocolMessageMapper.mapReferences(
|
|
||||||
languageServiceBridge.references(
|
|
||||||
project,
|
|
||||||
uri,
|
|
||||||
text,
|
|
||||||
params.getPosition().getLine(),
|
|
||||||
params.getPosition().getCharacter(),
|
|
||||||
includeDeclaration)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletableFuture<List<Either<SymbolInformation, DocumentSymbol>>> documentSymbol(
|
|
||||||
final DocumentSymbolParams params) {
|
|
||||||
final String uri = params.getTextDocument().getUri();
|
|
||||||
final String text = documentTextByUri.get(uri);
|
|
||||||
final List<Either<SymbolInformation, DocumentSymbol>> symbols = new ArrayList<>();
|
|
||||||
for (final DocumentSymbol symbol : protocolMessageMapper.mapDocumentSymbols(
|
|
||||||
languageServiceBridge.documentSymbols(project, uri, text))) {
|
|
||||||
symbols.add(Either.forRight(symbol));
|
|
||||||
}
|
|
||||||
return CompletableFuture.completedFuture(List.copyOf(symbols));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<SignatureHelp> signatureHelp(final SignatureHelpParams params) {
|
public CompletableFuture<SignatureHelp> signatureHelp(final SignatureHelpParams params) {
|
||||||
final String uri = params.getTextDocument().getUri();
|
final String uri = params.getTextDocument().getUri();
|
||||||
|
|||||||
@ -36,9 +36,6 @@ public final class Lsp4jProtocolMessageMapper implements ProtocolMessageMapper {
|
|||||||
signatureHelpOptions.setRetriggerCharacters(List.of(","));
|
signatureHelpOptions.setRetriggerCharacters(List.of(","));
|
||||||
capabilities.setSignatureHelpProvider(signatureHelpOptions);
|
capabilities.setSignatureHelpProvider(signatureHelpOptions);
|
||||||
}
|
}
|
||||||
capabilities.setDefinitionProvider(description.definitionSupported());
|
|
||||||
capabilities.setReferencesProvider(description.referencesSupported());
|
|
||||||
capabilities.setDocumentSymbolProvider(description.documentSymbolsSupported());
|
|
||||||
final SemanticTokensWithRegistrationOptions semanticTokens = new SemanticTokensWithRegistrationOptions();
|
final SemanticTokensWithRegistrationOptions semanticTokens = new SemanticTokensWithRegistrationOptions();
|
||||||
semanticTokens.setLegend(new SemanticTokensLegend(description.semanticTokenTypes(), List.of()));
|
semanticTokens.setLegend(new SemanticTokensLegend(description.semanticTokenTypes(), List.of()));
|
||||||
semanticTokens.setFull(true);
|
semanticTokens.setFull(true);
|
||||||
@ -86,88 +83,6 @@ public final class Lsp4jProtocolMessageMapper implements ProtocolMessageMapper {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Location> mapDefinition(final BaselineDefinition definition) {
|
|
||||||
if (definition == null || definition.locations().isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
return mapLocations(definition.locations());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Location> mapReferences(final BaselineReferences references) {
|
|
||||||
if (references == null || references.locations().isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
return mapLocations(references.locations());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<DocumentSymbol> mapDocumentSymbols(final BaselineDocumentSymbols symbols) {
|
|
||||||
if (symbols == null || symbols.symbols().isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
final ArrayList<DocumentSymbol> mapped = new ArrayList<>();
|
|
||||||
for (final BaselineDocumentSymbol symbol : symbols.symbols()) {
|
|
||||||
mapped.add(mapDocumentSymbol(symbol, null));
|
|
||||||
}
|
|
||||||
return List.copyOf(mapped);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DocumentSymbol mapDocumentSymbol(
|
|
||||||
final BaselineDocumentSymbol symbol,
|
|
||||||
final BaselineDocumentSymbolKind parentKind) {
|
|
||||||
final DocumentSymbol mapped = new DocumentSymbol();
|
|
||||||
mapped.setName(symbol.name());
|
|
||||||
mapped.setDetail(symbol.detail());
|
|
||||||
mapped.setKind(toLspSymbolKind(symbol.kind(), parentKind));
|
|
||||||
mapped.setRange(new Range(
|
|
||||||
new Position(symbol.startLine(), symbol.startCharacter()),
|
|
||||||
new Position(symbol.endLine(), symbol.endCharacter())));
|
|
||||||
mapped.setSelectionRange(new Range(
|
|
||||||
new Position(symbol.selectionStartLine(), symbol.selectionStartCharacter()),
|
|
||||||
new Position(symbol.selectionEndLine(), symbol.selectionEndCharacter())));
|
|
||||||
final ArrayList<DocumentSymbol> children = new ArrayList<>();
|
|
||||||
for (final BaselineDocumentSymbol child : symbol.children()) {
|
|
||||||
children.add(mapDocumentSymbol(child, symbol.kind()));
|
|
||||||
}
|
|
||||||
mapped.setChildren(children);
|
|
||||||
return mapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SymbolKind toLspSymbolKind(
|
|
||||||
final BaselineDocumentSymbolKind kind,
|
|
||||||
final BaselineDocumentSymbolKind parentKind) {
|
|
||||||
if (parentKind == BaselineDocumentSymbolKind.ENUM || parentKind == BaselineDocumentSymbolKind.ERROR) {
|
|
||||||
if (kind == BaselineDocumentSymbolKind.ENUM || kind == BaselineDocumentSymbolKind.ERROR) {
|
|
||||||
return SymbolKind.EnumMember;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return switch (kind) {
|
|
||||||
case FUNCTION -> SymbolKind.Function;
|
|
||||||
case METHOD -> SymbolKind.Method;
|
|
||||||
case CONSTRUCTOR -> SymbolKind.Constructor;
|
|
||||||
case FIELD -> SymbolKind.Field;
|
|
||||||
case STRUCT, BUILTIN_TYPE -> SymbolKind.Struct;
|
|
||||||
case SERVICE, HOST -> SymbolKind.Class;
|
|
||||||
case CONTRACT, CALLBACK -> SymbolKind.Interface;
|
|
||||||
case ENUM, ERROR -> SymbolKind.Enum;
|
|
||||||
case GLOBAL, LOCAL, PARAMETER -> SymbolKind.Variable;
|
|
||||||
case CONST -> SymbolKind.Constant;
|
|
||||||
case KEYWORD -> SymbolKind.Key;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Location> mapLocations(final List<BaselineDefinitionLocation> locations) {
|
|
||||||
return locations.stream()
|
|
||||||
.map(location -> new Location(
|
|
||||||
location.uri(),
|
|
||||||
new Range(
|
|
||||||
new Position(location.startLine(), location.startCharacter()),
|
|
||||||
new Position(location.endLine(), location.endCharacter()))))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletionList mapCompletion(final BaselineCompletion completion) {
|
public CompletionList mapCompletion(final BaselineCompletion completion) {
|
||||||
final CompletionList list = new CompletionList();
|
final CompletionList list = new CompletionList();
|
||||||
|
|||||||
@ -6,21 +6,14 @@ import org.eclipse.lsp4j.MessageParams;
|
|||||||
import org.eclipse.lsp4j.PublishDiagnosticsParams;
|
import org.eclipse.lsp4j.PublishDiagnosticsParams;
|
||||||
import org.eclipse.lsp4j.SignatureHelp;
|
import org.eclipse.lsp4j.SignatureHelp;
|
||||||
import org.eclipse.lsp4j.SemanticTokens;
|
import org.eclipse.lsp4j.SemanticTokens;
|
||||||
import org.eclipse.lsp4j.DocumentSymbol;
|
|
||||||
import org.eclipse.lsp4j.Location;
|
|
||||||
import p.studio.lsp.messages.BaselineDocumentAnalysis;
|
import p.studio.lsp.messages.BaselineDocumentAnalysis;
|
||||||
import p.studio.lsp.messages.BaselineDocumentSymbols;
|
|
||||||
import p.studio.lsp.messages.BaselineCompletion;
|
import p.studio.lsp.messages.BaselineCompletion;
|
||||||
import p.studio.lsp.messages.BaselineDefinition;
|
|
||||||
import p.studio.lsp.messages.BaselineReferences;
|
|
||||||
import p.studio.lsp.messages.BaselineHover;
|
import p.studio.lsp.messages.BaselineHover;
|
||||||
import p.studio.lsp.messages.BaselineSemanticTokens;
|
import p.studio.lsp.messages.BaselineSemanticTokens;
|
||||||
import p.studio.lsp.messages.BaselineSignatureHelp;
|
import p.studio.lsp.messages.BaselineSignatureHelp;
|
||||||
import p.studio.lsp.messages.BaselineServerDescription;
|
import p.studio.lsp.messages.BaselineServerDescription;
|
||||||
import org.eclipse.lsp4j.CompletionList;
|
import org.eclipse.lsp4j.CompletionList;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface ProtocolMessageMapper {
|
public interface ProtocolMessageMapper {
|
||||||
InitializeResult mapInitializeResult(BaselineServerDescription description);
|
InitializeResult mapInitializeResult(BaselineServerDescription description);
|
||||||
|
|
||||||
@ -30,12 +23,6 @@ public interface ProtocolMessageMapper {
|
|||||||
|
|
||||||
Hover mapHover(BaselineHover hover);
|
Hover mapHover(BaselineHover hover);
|
||||||
|
|
||||||
List<Location> mapDefinition(BaselineDefinition definition);
|
|
||||||
|
|
||||||
List<Location> mapReferences(BaselineReferences references);
|
|
||||||
|
|
||||||
List<DocumentSymbol> mapDocumentSymbols(BaselineDocumentSymbols symbols);
|
|
||||||
|
|
||||||
CompletionList mapCompletion(BaselineCompletion completion);
|
CompletionList mapCompletion(BaselineCompletion completion);
|
||||||
|
|
||||||
SignatureHelp mapSignatureHelp(BaselineSignatureHelp signatureHelp);
|
SignatureHelp mapSignatureHelp(BaselineSignatureHelp signatureHelp);
|
||||||
|
|||||||
@ -15,10 +15,8 @@ import p.studio.utilities.structures.ReadOnlySet;
|
|||||||
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
class CompilerLanguageServiceBridgeTest {
|
class CompilerLanguageServiceBridgeTest {
|
||||||
@ -47,15 +45,6 @@ class CompilerLanguageServiceBridgeTest {
|
|||||||
assertEquals(0, signatureHelp.activeSignature());
|
assertEquals(0, signatureHelp.activeSignature());
|
||||||
assertEquals(0, signatureHelp.activeParameter());
|
assertEquals(0, signatureHelp.activeParameter());
|
||||||
|
|
||||||
final var definition = bridge.definition(context, documentUri, "fn main() {}", 0, 0);
|
|
||||||
assertTrue(definition.locations().isEmpty());
|
|
||||||
|
|
||||||
final var references = bridge.references(context, documentUri, "fn main() {}", 0, 0, true);
|
|
||||||
assertTrue(references.locations().isEmpty());
|
|
||||||
|
|
||||||
final var documentSymbols = bridge.documentSymbols(context, documentUri, "fn main() {}");
|
|
||||||
assertTrue(documentSymbols.symbols().isEmpty());
|
|
||||||
|
|
||||||
final var semanticTokens = bridge.semanticTokens(context, documentUri, "fn main() {}");
|
final var semanticTokens = bridge.semanticTokens(context, documentUri, "fn main() {}");
|
||||||
assertTrue(semanticTokens.legend().isEmpty());
|
assertTrue(semanticTokens.legend().isEmpty());
|
||||||
assertTrue(semanticTokens.tokens().isEmpty());
|
assertTrue(semanticTokens.tokens().isEmpty());
|
||||||
@ -320,206 +309,6 @@ class CompilerLanguageServiceBridgeTest {
|
|||||||
assertTrue(helper.documentation().contains("- returns `42`"));
|
assertTrue(helper.documentation().contains("- returns `42`"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void definitionUsesEditorialLookupAndPhysicalStdlibFiles() {
|
|
||||||
final Path projectRoot = findRepoRoot(Path.of("").toAbsolutePath().normalize())
|
|
||||||
.resolve("test-projects")
|
|
||||||
.resolve("main")
|
|
||||||
.toAbsolutePath()
|
|
||||||
.normalize();
|
|
||||||
final Path documentPath = projectRoot.resolve("src").resolve("main.pbs");
|
|
||||||
final String overlay = """
|
|
||||||
import { Gfx } from @sdk:gfx;
|
|
||||||
|
|
||||||
declare struct Vec() {
|
|
||||||
fn blend(dx: int, dy: int) -> int { return dx; }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn helper() -> int { return 42; }
|
|
||||||
|
|
||||||
fn frame(vec: Vec) -> void {
|
|
||||||
helper();
|
|
||||||
vec.blend(1, 2);
|
|
||||||
Gfx.clear(5);
|
|
||||||
let n: int = 1;
|
|
||||||
n;
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
final CompilerLanguageServiceBridge bridge = new CompilerLanguageServiceBridge();
|
|
||||||
final LspProjectContext context = new LspProjectContext("main", "pbs", projectRoot);
|
|
||||||
final DocumentPositionMapper mapper = new DocumentPositionMapper(overlay);
|
|
||||||
|
|
||||||
final var helperUse = mapper.positionOf(overlay.indexOf("helper();"));
|
|
||||||
final var helperDefinition = bridge.definition(
|
|
||||||
context,
|
|
||||||
documentPath.toUri().toString(),
|
|
||||||
overlay,
|
|
||||||
helperUse.line(),
|
|
||||||
helperUse.character());
|
|
||||||
assertEquals(1, helperDefinition.locations().size());
|
|
||||||
assertEquals(documentPath.toUri().toString(), helperDefinition.locations().getFirst().uri());
|
|
||||||
assertTrue(helperDefinition.locations().getFirst().startLine() < helperUse.line());
|
|
||||||
|
|
||||||
final var blendUse = mapper.positionOf(overlay.indexOf("blend(1"));
|
|
||||||
final var blendDefinition = bridge.definition(
|
|
||||||
context,
|
|
||||||
documentPath.toUri().toString(),
|
|
||||||
overlay,
|
|
||||||
blendUse.line(),
|
|
||||||
blendUse.character());
|
|
||||||
assertEquals(1, blendDefinition.locations().size());
|
|
||||||
assertEquals(documentPath.toUri().toString(), blendDefinition.locations().getFirst().uri());
|
|
||||||
|
|
||||||
final var clearUse = mapper.positionOf(overlay.indexOf("clear"));
|
|
||||||
final var clearHover = bridge.hover(
|
|
||||||
context,
|
|
||||||
documentPath.toUri().toString(),
|
|
||||||
overlay,
|
|
||||||
clearUse.line(),
|
|
||||||
clearUse.character());
|
|
||||||
assertTrue(clearHover.markdown().contains("clear(color:"));
|
|
||||||
final var clearDefinition = bridge.definition(
|
|
||||||
context,
|
|
||||||
documentPath.toUri().toString(),
|
|
||||||
overlay,
|
|
||||||
clearUse.line(),
|
|
||||||
clearUse.character());
|
|
||||||
assertTrue(
|
|
||||||
clearDefinition.locations().isEmpty(),
|
|
||||||
"stdlib is loaded from a virtual path, so definition stays empty until a physical file exists");
|
|
||||||
|
|
||||||
final var builtinUse = mapper.positionOf(overlay.indexOf("int = 1"));
|
|
||||||
final var builtinHover = bridge.hover(
|
|
||||||
context,
|
|
||||||
documentPath.toUri().toString(),
|
|
||||||
overlay,
|
|
||||||
builtinUse.line(),
|
|
||||||
builtinUse.character());
|
|
||||||
assertTrue(builtinHover.markdown().contains("builtin"));
|
|
||||||
final var builtinDefinition = bridge.definition(
|
|
||||||
context,
|
|
||||||
documentPath.toUri().toString(),
|
|
||||||
overlay,
|
|
||||||
builtinUse.line(),
|
|
||||||
builtinUse.character());
|
|
||||||
assertTrue(builtinDefinition.locations().isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void referencesUsesDefinitionIdentityOnPhysicalFilesAndHonorsIncludeDeclaration() {
|
|
||||||
final Path projectRoot = findRepoRoot(Path.of("").toAbsolutePath().normalize())
|
|
||||||
.resolve("test-projects")
|
|
||||||
.resolve("main")
|
|
||||||
.toAbsolutePath()
|
|
||||||
.normalize();
|
|
||||||
final Path documentPath = projectRoot.resolve("src").resolve("main.pbs");
|
|
||||||
final String overlay = """
|
|
||||||
import { Gfx } from @sdk:gfx;
|
|
||||||
|
|
||||||
declare struct Vec() {
|
|
||||||
fn blend(dx: int, dy: int) -> int { return dx; }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn helper() -> int { return 42; }
|
|
||||||
|
|
||||||
fn frame(vec: Vec) -> void {
|
|
||||||
helper();
|
|
||||||
helper();
|
|
||||||
vec.blend(1, 2);
|
|
||||||
Gfx.clear(5);
|
|
||||||
let n: int = 1;
|
|
||||||
n;
|
|
||||||
// helper
|
|
||||||
let text = "helper";
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
final CompilerLanguageServiceBridge bridge = new CompilerLanguageServiceBridge();
|
|
||||||
final LspProjectContext context = new LspProjectContext("main", "pbs", projectRoot);
|
|
||||||
final DocumentPositionMapper mapper = new DocumentPositionMapper(overlay);
|
|
||||||
final String documentUri = documentPath.toUri().toString();
|
|
||||||
|
|
||||||
final var helperUse = mapper.positionOf(overlay.indexOf("helper();"));
|
|
||||||
final var helperUsages = bridge.references(
|
|
||||||
context,
|
|
||||||
documentUri,
|
|
||||||
overlay,
|
|
||||||
helperUse.line(),
|
|
||||||
helperUse.character(),
|
|
||||||
false);
|
|
||||||
assertEquals(3, helperUsages.locations().size(), helperUsages.locations().toString());
|
|
||||||
assertTrue(helperUsages.locations().stream().allMatch(location -> location.uri().equals(documentUri)));
|
|
||||||
|
|
||||||
final var helperWithDeclaration = bridge.references(
|
|
||||||
context,
|
|
||||||
documentUri,
|
|
||||||
overlay,
|
|
||||||
helperUse.line(),
|
|
||||||
helperUse.character(),
|
|
||||||
true);
|
|
||||||
assertTrue(helperWithDeclaration.locations().size() > helperUsages.locations().size());
|
|
||||||
|
|
||||||
final var clearUse = mapper.positionOf(overlay.indexOf("clear"));
|
|
||||||
final var clearReferences = bridge.references(
|
|
||||||
context,
|
|
||||||
documentUri,
|
|
||||||
overlay,
|
|
||||||
clearUse.line(),
|
|
||||||
clearUse.character(),
|
|
||||||
true);
|
|
||||||
assertTrue(clearReferences.locations().isEmpty());
|
|
||||||
|
|
||||||
final var builtinUse = mapper.positionOf(overlay.indexOf("int = 1"));
|
|
||||||
final var builtinReferences = bridge.references(
|
|
||||||
context,
|
|
||||||
documentUri,
|
|
||||||
overlay,
|
|
||||||
builtinUse.line(),
|
|
||||||
builtinUse.character(),
|
|
||||||
true);
|
|
||||||
assertTrue(builtinReferences.locations().isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void documentSymbolsUseAstHierarchyOfTheOpenPbsFile() {
|
|
||||||
final Path projectRoot = findRepoRoot(Path.of("").toAbsolutePath().normalize())
|
|
||||||
.resolve("test-projects")
|
|
||||||
.resolve("main")
|
|
||||||
.toAbsolutePath()
|
|
||||||
.normalize();
|
|
||||||
final Path documentPath = projectRoot.resolve("src").resolve("main.pbs");
|
|
||||||
final String overlay = """
|
|
||||||
import { Gfx } from @sdk:gfx;
|
|
||||||
|
|
||||||
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; }
|
|
||||||
|
|
||||||
fn frame() -> void {
|
|
||||||
let local = 1;
|
|
||||||
helper(local);
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
final CompilerLanguageServiceBridge bridge = new CompilerLanguageServiceBridge();
|
|
||||||
final var symbols = bridge.documentSymbols(
|
|
||||||
new LspProjectContext("main", "pbs", projectRoot),
|
|
||||||
documentPath.toUri().toString(),
|
|
||||||
overlay).symbols();
|
|
||||||
|
|
||||||
assertEquals(List.of("Vec", "helper", "helper", "frame"), symbols.stream().map(symbol -> symbol.name()).toList());
|
|
||||||
assertTrue(symbols.stream().noneMatch(symbol -> symbol.name().equals("Gfx") || symbol.name().equals("local")));
|
|
||||||
final var vec = symbols.getFirst();
|
|
||||||
assertEquals(p.studio.lsp.messages.BaselineDocumentSymbolKind.STRUCT, vec.kind());
|
|
||||||
assertEquals(List.of("x", "make", "blend"), vec.children().stream().map(symbol -> symbol.name()).toList());
|
|
||||||
assertEquals("(dx: int, dy: int) -> int", vec.children().get(2).detail());
|
|
||||||
assertEquals("helper", symbols.get(1).name());
|
|
||||||
assertEquals("helper", symbols.get(2).name());
|
|
||||||
assertNotEquals(symbols.get(1).detail(), symbols.get(2).detail());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void describeServerPublishesFrontendVisualThemes() {
|
void describeServerPublishesFrontendVisualThemes() {
|
||||||
final CompilerLanguageServiceBridge bridge = new CompilerLanguageServiceBridge();
|
final CompilerLanguageServiceBridge bridge = new CompilerLanguageServiceBridge();
|
||||||
@ -530,9 +319,6 @@ class CompilerLanguageServiceBridgeTest {
|
|||||||
assertEquals("pbs-default", description.activeVisualThemeId());
|
assertEquals("pbs-default", description.activeVisualThemeId());
|
||||||
assertTrue(description.completionSupported());
|
assertTrue(description.completionSupported());
|
||||||
assertTrue(description.signatureHelpSupported());
|
assertTrue(description.signatureHelpSupported());
|
||||||
assertTrue(description.definitionSupported());
|
|
||||||
assertTrue(description.referencesSupported());
|
|
||||||
assertTrue(description.documentSymbolsSupported());
|
|
||||||
assertEquals(1, description.semanticHostProjections().size());
|
assertEquals(1, description.semanticHostProjections().size());
|
||||||
assertEquals("vscode", description.semanticHostProjections().getFirst().hostId());
|
assertEquals("vscode", description.semanticHostProjections().getFirst().hostId());
|
||||||
assertTrue(description.semanticHostProjections().getFirst().tokenProjections().stream()
|
assertTrue(description.semanticHostProjections().getFirst().tokenProjections().stream()
|
||||||
|
|||||||
@ -15,7 +15,6 @@ import org.junit.jupiter.api.Test;
|
|||||||
import p.studio.lsp.messages.BaselineCompletion;
|
import p.studio.lsp.messages.BaselineCompletion;
|
||||||
import p.studio.lsp.messages.BaselineCompletionItem;
|
import p.studio.lsp.messages.BaselineCompletionItem;
|
||||||
import p.studio.lsp.messages.BaselineCompletionItemKind;
|
import p.studio.lsp.messages.BaselineCompletionItemKind;
|
||||||
import p.studio.lsp.messages.BaselineDefinition;
|
|
||||||
import p.studio.lsp.messages.BaselineDocumentAnalysis;
|
import p.studio.lsp.messages.BaselineDocumentAnalysis;
|
||||||
import p.studio.lsp.messages.BaselineEditorPalette;
|
import p.studio.lsp.messages.BaselineEditorPalette;
|
||||||
import p.studio.lsp.messages.BaselineHover;
|
import p.studio.lsp.messages.BaselineHover;
|
||||||
@ -99,9 +98,6 @@ class PrometeuLanguageServerTest {
|
|||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
List.of(),
|
List.of(),
|
||||||
List.of(new BaselineSemanticHostProjection(
|
List.of(new BaselineSemanticHostProjection(
|
||||||
"vscode",
|
"vscode",
|
||||||
@ -149,35 +145,6 @@ class PrometeuLanguageServerTest {
|
|||||||
return new BaselineHover("hover");
|
return new BaselineHover("hover");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public BaselineDefinition definition(
|
|
||||||
final LspProjectContext project,
|
|
||||||
final String documentUri,
|
|
||||||
final String text,
|
|
||||||
final int line,
|
|
||||||
final int character) {
|
|
||||||
return BaselineDefinition.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public p.studio.lsp.messages.BaselineReferences references(
|
|
||||||
final LspProjectContext project,
|
|
||||||
final String documentUri,
|
|
||||||
final String text,
|
|
||||||
final int line,
|
|
||||||
final int character,
|
|
||||||
final boolean includeDeclaration) {
|
|
||||||
return p.studio.lsp.messages.BaselineReferences.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public p.studio.lsp.messages.BaselineDocumentSymbols documentSymbols(
|
|
||||||
final LspProjectContext project,
|
|
||||||
final String documentUri,
|
|
||||||
final String text) {
|
|
||||||
return p.studio.lsp.messages.BaselineDocumentSymbols.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BaselineSignatureHelp signatureHelp(
|
public BaselineSignatureHelp signatureHelp(
|
||||||
final LspProjectContext project,
|
final LspProjectContext project,
|
||||||
@ -224,23 +191,6 @@ class PrometeuLanguageServerTest {
|
|||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public java.util.List<org.eclipse.lsp4j.Location> mapDefinition(final BaselineDefinition definition) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public java.util.List<org.eclipse.lsp4j.Location> mapReferences(
|
|
||||||
final p.studio.lsp.messages.BaselineReferences references) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public java.util.List<org.eclipse.lsp4j.DocumentSymbol> mapDocumentSymbols(
|
|
||||||
final p.studio.lsp.messages.BaselineDocumentSymbols symbols) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public org.eclipse.lsp4j.CompletionList mapCompletion(final BaselineCompletion completion) {
|
public org.eclipse.lsp4j.CompletionList mapCompletion(final BaselineCompletion completion) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
|
|||||||
@ -3,15 +3,9 @@ package p.studio.lsp.services.protocol;
|
|||||||
import org.eclipse.lsp4j.CompletionItem;
|
import org.eclipse.lsp4j.CompletionItem;
|
||||||
import org.eclipse.lsp4j.CompletionList;
|
import org.eclipse.lsp4j.CompletionList;
|
||||||
import org.eclipse.lsp4j.CompletionParams;
|
import org.eclipse.lsp4j.CompletionParams;
|
||||||
import org.eclipse.lsp4j.DefinitionParams;
|
|
||||||
import org.eclipse.lsp4j.DocumentSymbol;
|
|
||||||
import org.eclipse.lsp4j.DocumentSymbolParams;
|
|
||||||
import org.eclipse.lsp4j.ReferenceContext;
|
|
||||||
import org.eclipse.lsp4j.ReferenceParams;
|
|
||||||
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
|
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
|
||||||
import org.eclipse.lsp4j.Hover;
|
import org.eclipse.lsp4j.Hover;
|
||||||
import org.eclipse.lsp4j.HoverParams;
|
import org.eclipse.lsp4j.HoverParams;
|
||||||
import org.eclipse.lsp4j.Location;
|
|
||||||
import org.eclipse.lsp4j.MarkupContent;
|
import org.eclipse.lsp4j.MarkupContent;
|
||||||
import org.eclipse.lsp4j.Position;
|
import org.eclipse.lsp4j.Position;
|
||||||
import org.eclipse.lsp4j.SignatureHelp;
|
import org.eclipse.lsp4j.SignatureHelp;
|
||||||
@ -22,12 +16,6 @@ import org.junit.jupiter.api.Test;
|
|||||||
import p.studio.lsp.messages.BaselineCompletion;
|
import p.studio.lsp.messages.BaselineCompletion;
|
||||||
import p.studio.lsp.messages.BaselineCompletionItem;
|
import p.studio.lsp.messages.BaselineCompletionItem;
|
||||||
import p.studio.lsp.messages.BaselineCompletionItemKind;
|
import p.studio.lsp.messages.BaselineCompletionItemKind;
|
||||||
import p.studio.lsp.messages.BaselineDefinition;
|
|
||||||
import p.studio.lsp.messages.BaselineDefinitionLocation;
|
|
||||||
import p.studio.lsp.messages.BaselineDocumentSymbol;
|
|
||||||
import p.studio.lsp.messages.BaselineDocumentSymbolKind;
|
|
||||||
import p.studio.lsp.messages.BaselineDocumentSymbols;
|
|
||||||
import p.studio.lsp.messages.BaselineReferences;
|
|
||||||
import p.studio.lsp.messages.BaselineDocumentAnalysis;
|
import p.studio.lsp.messages.BaselineDocumentAnalysis;
|
||||||
import p.studio.lsp.messages.BaselineHover;
|
import p.studio.lsp.messages.BaselineHover;
|
||||||
import p.studio.lsp.messages.BaselineSemanticTokens;
|
import p.studio.lsp.messages.BaselineSemanticTokens;
|
||||||
@ -89,31 +77,6 @@ final class PrometeuTextDocumentServiceTest {
|
|||||||
assertEquals(1, bridge.lastSignatureHelpLine);
|
assertEquals(1, bridge.lastSignatureHelpLine);
|
||||||
assertEquals(6, bridge.lastSignatureHelpCharacter);
|
assertEquals(6, bridge.lastSignatureHelpCharacter);
|
||||||
assertTrue(mapper.mappedSignatureHelp.signatures().getFirst().label().contains("demo"));
|
assertTrue(mapper.mappedSignatureHelp.signatures().getFirst().label().contains("demo"));
|
||||||
|
|
||||||
final var definition = service.definition(new DefinitionParams(
|
|
||||||
new TextDocumentIdentifier(uri),
|
|
||||||
new Position(1, 2))).join();
|
|
||||||
assertSame(mapper.definitionLocations, definition.getLeft());
|
|
||||||
assertEquals(text, bridge.lastDefinitionText);
|
|
||||||
assertEquals(1, bridge.lastDefinitionLine);
|
|
||||||
assertEquals(2, bridge.lastDefinitionCharacter);
|
|
||||||
|
|
||||||
final var references = service.references(new ReferenceParams(
|
|
||||||
new TextDocumentIdentifier(uri),
|
|
||||||
new Position(1, 2),
|
|
||||||
new ReferenceContext(true))).join();
|
|
||||||
assertSame(mapper.referenceLocations, references);
|
|
||||||
assertEquals(text, bridge.lastReferencesText);
|
|
||||||
assertEquals(1, bridge.lastReferencesLine);
|
|
||||||
assertEquals(2, bridge.lastReferencesCharacter);
|
|
||||||
assertTrue(bridge.lastIncludeDeclaration);
|
|
||||||
|
|
||||||
final var documentSymbols = service.documentSymbol(new DocumentSymbolParams(
|
|
||||||
new TextDocumentIdentifier(uri))).join();
|
|
||||||
assertEquals(1, documentSymbols.size());
|
|
||||||
assertTrue(documentSymbols.getFirst().isRight());
|
|
||||||
assertSame(mapper.documentSymbols.getFirst(), documentSymbols.getFirst().getRight());
|
|
||||||
assertEquals(text, bridge.lastDocumentSymbolsText);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class RecordingBridge implements LanguageServiceBridge {
|
private static final class RecordingBridge implements LanguageServiceBridge {
|
||||||
@ -121,14 +84,6 @@ final class PrometeuTextDocumentServiceTest {
|
|||||||
private int lastCompletionLine;
|
private int lastCompletionLine;
|
||||||
private int lastCompletionCharacter;
|
private int lastCompletionCharacter;
|
||||||
private String lastHoverText;
|
private String lastHoverText;
|
||||||
private String lastDefinitionText;
|
|
||||||
private int lastDefinitionLine;
|
|
||||||
private int lastDefinitionCharacter;
|
|
||||||
private String lastReferencesText;
|
|
||||||
private int lastReferencesLine;
|
|
||||||
private int lastReferencesCharacter;
|
|
||||||
private boolean lastIncludeDeclaration;
|
|
||||||
private String lastDocumentSymbolsText;
|
|
||||||
private String lastSignatureHelpText;
|
private String lastSignatureHelpText;
|
||||||
private int lastSignatureHelpLine;
|
private int lastSignatureHelpLine;
|
||||||
private int lastSignatureHelpCharacter;
|
private int lastSignatureHelpCharacter;
|
||||||
@ -168,55 +123,6 @@ final class PrometeuTextDocumentServiceTest {
|
|||||||
return new BaselineHover("hover");
|
return new BaselineHover("hover");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public BaselineDefinition definition(
|
|
||||||
final LspProjectContext context,
|
|
||||||
final String documentUri,
|
|
||||||
final String text,
|
|
||||||
final int line,
|
|
||||||
final int character) {
|
|
||||||
lastDefinitionText = text;
|
|
||||||
lastDefinitionLine = line;
|
|
||||||
lastDefinitionCharacter = character;
|
|
||||||
return new BaselineDefinition(List.of(new BaselineDefinitionLocation(documentUri, 0, 0, 0, 6)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BaselineReferences references(
|
|
||||||
final LspProjectContext context,
|
|
||||||
final String documentUri,
|
|
||||||
final String text,
|
|
||||||
final int line,
|
|
||||||
final int character,
|
|
||||||
final boolean includeDeclaration) {
|
|
||||||
lastReferencesText = text;
|
|
||||||
lastReferencesLine = line;
|
|
||||||
lastReferencesCharacter = character;
|
|
||||||
lastIncludeDeclaration = includeDeclaration;
|
|
||||||
return new BaselineReferences(List.of(new BaselineDefinitionLocation(documentUri, 0, 0, 0, 6)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BaselineDocumentSymbols documentSymbols(
|
|
||||||
final LspProjectContext context,
|
|
||||||
final String documentUri,
|
|
||||||
final String text) {
|
|
||||||
lastDocumentSymbolsText = text;
|
|
||||||
return new BaselineDocumentSymbols(List.of(new BaselineDocumentSymbol(
|
|
||||||
"frame",
|
|
||||||
BaselineDocumentSymbolKind.FUNCTION,
|
|
||||||
"",
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
2,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
3,
|
|
||||||
0,
|
|
||||||
8,
|
|
||||||
List.of())));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BaselineSignatureHelp signatureHelp(
|
public BaselineSignatureHelp signatureHelp(
|
||||||
final LspProjectContext context,
|
final LspProjectContext context,
|
||||||
@ -245,9 +151,6 @@ final class PrometeuTextDocumentServiceTest {
|
|||||||
private static final class RecordingMapper implements ProtocolMessageMapper {
|
private static final class RecordingMapper implements ProtocolMessageMapper {
|
||||||
private final CompletionList completionList = new CompletionList(false, List.of(new CompletionItem("demo")));
|
private final CompletionList completionList = new CompletionList(false, List.of(new CompletionItem("demo")));
|
||||||
private final SignatureHelp signatureHelp = new SignatureHelp();
|
private final SignatureHelp signatureHelp = new SignatureHelp();
|
||||||
private final List<Location> definitionLocations = List.of();
|
|
||||||
private final List<Location> referenceLocations = List.of();
|
|
||||||
private final List<DocumentSymbol> documentSymbols = List.of(new DocumentSymbol());
|
|
||||||
private BaselineSignatureHelp mappedSignatureHelp;
|
private BaselineSignatureHelp mappedSignatureHelp;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -275,21 +178,6 @@ final class PrometeuTextDocumentServiceTest {
|
|||||||
return new Hover(content);
|
return new Hover(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Location> mapDefinition(final BaselineDefinition definition) {
|
|
||||||
return definitionLocations;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Location> mapReferences(final BaselineReferences references) {
|
|
||||||
return referenceLocations;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<DocumentSymbol> mapDocumentSymbols(final BaselineDocumentSymbols symbols) {
|
|
||||||
return documentSymbols;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletionList mapCompletion(final BaselineCompletion completion) {
|
public CompletionList mapCompletion(final BaselineCompletion completion) {
|
||||||
return completionList;
|
return completionList;
|
||||||
|
|||||||
@ -1,18 +1,11 @@
|
|||||||
package p.studio.lsp.services.protocol.mapping;
|
package p.studio.lsp.services.protocol.mapping;
|
||||||
|
|
||||||
import org.eclipse.lsp4j.InitializeResult;
|
import org.eclipse.lsp4j.InitializeResult;
|
||||||
import org.eclipse.lsp4j.SymbolKind;
|
|
||||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import p.studio.lsp.messages.BaselineCompletion;
|
import p.studio.lsp.messages.BaselineCompletion;
|
||||||
import p.studio.lsp.messages.BaselineCompletionItem;
|
import p.studio.lsp.messages.BaselineCompletionItem;
|
||||||
import p.studio.lsp.messages.BaselineCompletionItemKind;
|
import p.studio.lsp.messages.BaselineCompletionItemKind;
|
||||||
import p.studio.lsp.messages.BaselineDefinition;
|
|
||||||
import p.studio.lsp.messages.BaselineDefinitionLocation;
|
|
||||||
import p.studio.lsp.messages.BaselineDocumentSymbol;
|
|
||||||
import p.studio.lsp.messages.BaselineDocumentSymbolKind;
|
|
||||||
import p.studio.lsp.messages.BaselineDocumentSymbols;
|
|
||||||
import p.studio.lsp.messages.BaselineReferences;
|
|
||||||
import p.studio.lsp.messages.BaselineEditorPalette;
|
import p.studio.lsp.messages.BaselineEditorPalette;
|
||||||
import p.studio.lsp.messages.BaselineSemanticHostProjection;
|
import p.studio.lsp.messages.BaselineSemanticHostProjection;
|
||||||
import p.studio.lsp.messages.BaselineSemanticHostProjectionEntry;
|
import p.studio.lsp.messages.BaselineSemanticHostProjectionEntry;
|
||||||
@ -42,9 +35,6 @@ final class Lsp4jProtocolMessageMapperTest {
|
|||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
List.of("demo-keyword"),
|
List.of("demo-keyword"),
|
||||||
List.of(new BaselineSemanticHostProjection(
|
List.of(new BaselineSemanticHostProjection(
|
||||||
"vscode",
|
"vscode",
|
||||||
@ -65,9 +55,6 @@ final class Lsp4jProtocolMessageMapperTest {
|
|||||||
|
|
||||||
assertEquals(List.of("."), result.getCapabilities().getCompletionProvider().getTriggerCharacters());
|
assertEquals(List.of("."), result.getCapabilities().getCompletionProvider().getTriggerCharacters());
|
||||||
assertEquals(List.of("(", ","), result.getCapabilities().getSignatureHelpProvider().getTriggerCharacters());
|
assertEquals(List.of("(", ","), result.getCapabilities().getSignatureHelpProvider().getTriggerCharacters());
|
||||||
assertEquals(Boolean.TRUE, result.getCapabilities().getDefinitionProvider().getLeft());
|
|
||||||
assertEquals(Boolean.TRUE, result.getCapabilities().getReferencesProvider().getLeft());
|
|
||||||
assertEquals(Boolean.TRUE, result.getCapabilities().getDocumentSymbolProvider().getLeft());
|
|
||||||
|
|
||||||
final var experimental = assertInstanceOf(Map.class, result.getCapabilities().getExperimental());
|
final var experimental = assertInstanceOf(Map.class, result.getCapabilities().getExperimental());
|
||||||
final var semanticPayload = assertInstanceOf(Map.class, experimental.get("prometeuSemanticHostProjections"));
|
final var semanticPayload = assertInstanceOf(Map.class, experimental.get("prometeuSemanticHostProjections"));
|
||||||
@ -117,95 +104,4 @@ final class Lsp4jProtocolMessageMapperTest {
|
|||||||
assertEquals("left: int", signatureHelp.getSignatures().getFirst().getParameters().getFirst().getLabel().getLeft());
|
assertEquals("left: int", signatureHelp.getSignatures().getFirst().getParameters().getFirst().getLabel().getLeft());
|
||||||
assertEquals(1, signatureHelp.getActiveParameter());
|
assertEquals(1, signatureHelp.getActiveParameter());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void definitionMapsPhysicalFileLocations() {
|
|
||||||
final var mapper = new Lsp4jProtocolMessageMapper();
|
|
||||||
final var locations = mapper.mapDefinition(new BaselineDefinition(List.of(
|
|
||||||
new BaselineDefinitionLocation("file:///tmp/demo.pbs", 2, 3, 2, 9))));
|
|
||||||
assertEquals(1, locations.size());
|
|
||||||
assertEquals("file:///tmp/demo.pbs", locations.getFirst().getUri());
|
|
||||||
assertEquals(2, locations.getFirst().getRange().getStart().getLine());
|
|
||||||
assertEquals(3, locations.getFirst().getRange().getStart().getCharacter());
|
|
||||||
assertEquals(9, locations.getFirst().getRange().getEnd().getCharacter());
|
|
||||||
assertTrue(mapper.mapDefinition(BaselineDefinition.empty()).isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void referencesMapsPhysicalFileLocations() {
|
|
||||||
final var mapper = new Lsp4jProtocolMessageMapper();
|
|
||||||
final var locations = mapper.mapReferences(new BaselineReferences(List.of(
|
|
||||||
new BaselineDefinitionLocation("file:///tmp/demo.pbs", 2, 3, 2, 9))));
|
|
||||||
assertEquals(1, locations.size());
|
|
||||||
assertEquals("file:///tmp/demo.pbs", locations.getFirst().getUri());
|
|
||||||
assertEquals(3, locations.getFirst().getRange().getStart().getCharacter());
|
|
||||||
assertTrue(mapper.mapReferences(BaselineReferences.empty()).isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void documentSymbolsMapToHierarchicalDocumentSymbolTrees() {
|
|
||||||
final var mapper = new Lsp4jProtocolMessageMapper();
|
|
||||||
final var mapped = mapper.mapDocumentSymbols(new BaselineDocumentSymbols(List.of(
|
|
||||||
new BaselineDocumentSymbol(
|
|
||||||
"Mode",
|
|
||||||
BaselineDocumentSymbolKind.ENUM,
|
|
||||||
"",
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
28,
|
|
||||||
0,
|
|
||||||
14,
|
|
||||||
0,
|
|
||||||
18,
|
|
||||||
List.of(new BaselineDocumentSymbol(
|
|
||||||
"Idle",
|
|
||||||
BaselineDocumentSymbolKind.ENUM,
|
|
||||||
"0",
|
|
||||||
0,
|
|
||||||
19,
|
|
||||||
0,
|
|
||||||
23,
|
|
||||||
0,
|
|
||||||
19,
|
|
||||||
0,
|
|
||||||
23,
|
|
||||||
List.of()))),
|
|
||||||
new BaselineDocumentSymbol(
|
|
||||||
"Point",
|
|
||||||
BaselineDocumentSymbolKind.STRUCT,
|
|
||||||
"",
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
4,
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
15,
|
|
||||||
1,
|
|
||||||
20,
|
|
||||||
List.of(new BaselineDocumentSymbol(
|
|
||||||
"x",
|
|
||||||
BaselineDocumentSymbolKind.FIELD,
|
|
||||||
"int",
|
|
||||||
1,
|
|
||||||
21,
|
|
||||||
1,
|
|
||||||
27,
|
|
||||||
1,
|
|
||||||
21,
|
|
||||||
1,
|
|
||||||
22,
|
|
||||||
List.of()))))));
|
|
||||||
|
|
||||||
assertEquals(2, mapped.size());
|
|
||||||
assertEquals("Mode", mapped.getFirst().getName());
|
|
||||||
assertEquals(SymbolKind.Enum, mapped.getFirst().getKind());
|
|
||||||
assertEquals("Idle", mapped.getFirst().getChildren().getFirst().getName());
|
|
||||||
assertEquals(SymbolKind.EnumMember, mapped.getFirst().getChildren().getFirst().getKind());
|
|
||||||
assertEquals("Point", mapped.get(1).getName());
|
|
||||||
assertEquals(SymbolKind.Struct, mapped.get(1).getKind());
|
|
||||||
assertEquals("x", mapped.get(1).getChildren().getFirst().getName());
|
|
||||||
assertEquals(SymbolKind.Field, mapped.get(1).getChildren().getFirst().getKind());
|
|
||||||
assertTrue(mapper.mapDocumentSymbols(BaselineDocumentSymbols.empty()).isEmpty());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user