implements PLN-0131 #25
@ -1,4 +1,4 @@
|
|||||||
{"type":"meta","next_id":{"DSC":66,"AGD":69,"DEC":52,"PLN":131,"LSN":68,"CLSN":1}}
|
{"type":"meta","next_id":{"DSC":66,"AGD":69,"DEC":53,"PLN":132,"LSN":69,"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":"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-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"}]}
|
||||||
@ -25,7 +25,7 @@
|
|||||||
{"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":"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-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":"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-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":"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-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-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"}]}
|
||||||
|
|||||||
@ -0,0 +1,98 @@
|
|||||||
|
---
|
||||||
|
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,62 +0,0 @@
|
|||||||
---
|
|
||||||
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.
|
|
||||||
@ -272,6 +272,8 @@ 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.
|
||||||
|
|
||||||
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,11 +6,13 @@ 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.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.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.FrontendDocumentation;
|
import p.studio.compiler.services.FrontendDocumentation;
|
||||||
import p.studio.compiler.services.FrontendEditorialContext;
|
import p.studio.compiler.services.FrontendEditorialContext;
|
||||||
@ -20,10 +22,15 @@ 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.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@ -61,7 +68,8 @@ 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PbsEditorialCompletionCandidate> completion(
|
public List<PbsEditorialCompletionCandidate> completion(
|
||||||
@ -167,6 +175,44 @@ 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
|
@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());
|
||||||
@ -213,4 +259,35 @@ 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 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
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,6 +74,23 @@ public final class PbsEditorialSupportService {
|
|||||||
return context.resolveSymbolHover(token.lexeme(), tokenIndex, scopeState);
|
return context.resolveSymbolHover(token.lexeme(), tokenIndex, scopeState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 Optional<PbsEditorialSignatureHelp> signatureHelp(
|
public Optional<PbsEditorialSignatureHelp> signatureHelp(
|
||||||
final String sourceText,
|
final String sourceText,
|
||||||
final PbsAst.File ast,
|
final PbsAst.File ast,
|
||||||
@ -610,6 +627,228 @@ 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> 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,6 +265,87 @@ 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)")));
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
private PbsEditorialResolvedSymbol requireHover(
|
private PbsEditorialResolvedSymbol requireHover(
|
||||||
final String source,
|
final String source,
|
||||||
final PbsAst.File ast,
|
final PbsAst.File ast,
|
||||||
|
|||||||
@ -0,0 +1,20 @@
|
|||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,5 +3,13 @@ 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) {
|
||||||
|
|
||||||
|
public FrontendEditorialContext(
|
||||||
|
final Object syntaxTree,
|
||||||
|
final Object supplementalDeclarations,
|
||||||
|
final Object semanticContext) {
|
||||||
|
this(syntaxTree, supplementalDeclarations, semanticContext, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -59,4 +59,17 @@ 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,20 @@ 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
|
|||||||
@ -0,0 +1,15 @@
|
|||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,6 +10,7 @@ public record BaselineServerDescription(
|
|||||||
boolean hoverSupported,
|
boolean hoverSupported,
|
||||||
boolean completionSupported,
|
boolean completionSupported,
|
||||||
boolean signatureHelpSupported,
|
boolean signatureHelpSupported,
|
||||||
|
boolean definitionSupported,
|
||||||
List<String> semanticTokenTypes,
|
List<String> semanticTokenTypes,
|
||||||
List<BaselineSemanticHostProjection> semanticHostProjections,
|
List<BaselineSemanticHostProjection> semanticHostProjections,
|
||||||
List<BaselineVisualTheme> visualThemes,
|
List<BaselineVisualTheme> visualThemes,
|
||||||
|
|||||||
@ -2,6 +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.BaselineSemanticTokens;
|
import p.studio.lsp.messages.BaselineSemanticTokens;
|
||||||
import p.studio.lsp.messages.BaselineSignatureHelp;
|
import p.studio.lsp.messages.BaselineSignatureHelp;
|
||||||
@ -17,6 +18,8 @@ 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);
|
||||||
|
|
||||||
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,6 +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.FrontendEditorialContext;
|
import p.studio.compiler.services.FrontendEditorialContext;
|
||||||
import p.studio.compiler.services.FrontendHover;
|
import p.studio.compiler.services.FrontendHover;
|
||||||
@ -21,6 +22,8 @@ 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;
|
||||||
@ -49,6 +52,7 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
|
frontendProvider(context).languageService().isPresent(),
|
||||||
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(),
|
||||||
@ -87,13 +91,9 @@ 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,
|
||||||
editorialContext,
|
document.editorialContext(),
|
||||||
offset);
|
offset);
|
||||||
return new BaselineCompletion(
|
return new BaselineCompletion(
|
||||||
false,
|
false,
|
||||||
@ -122,19 +122,47 @@ 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,
|
||||||
editorialContext,
|
document.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
|
@Override
|
||||||
public BaselineSignatureHelp signatureHelp(
|
public BaselineSignatureHelp signatureHelp(
|
||||||
final LspProjectContext context,
|
final LspProjectContext context,
|
||||||
@ -155,13 +183,9 @@ 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,
|
||||||
editorialContext,
|
document.editorialContext(),
|
||||||
offset);
|
offset);
|
||||||
})
|
})
|
||||||
.map(this::mapSignatureHelp)
|
.map(this::mapSignatureHelp)
|
||||||
@ -418,6 +442,46 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|||||||
@ -97,6 +97,20 @@ 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
|
@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,6 +36,7 @@ public final class Lsp4jProtocolMessageMapper implements ProtocolMessageMapper {
|
|||||||
signatureHelpOptions.setRetriggerCharacters(List.of(","));
|
signatureHelpOptions.setRetriggerCharacters(List.of(","));
|
||||||
capabilities.setSignatureHelpProvider(signatureHelpOptions);
|
capabilities.setSignatureHelpProvider(signatureHelpOptions);
|
||||||
}
|
}
|
||||||
|
capabilities.setDefinitionProvider(description.definitionSupported());
|
||||||
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);
|
||||||
@ -83,6 +84,20 @@ 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 definition.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,14 +6,18 @@ 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.Location;
|
||||||
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.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);
|
||||||
|
|
||||||
@ -23,6 +27,8 @@ public interface ProtocolMessageMapper {
|
|||||||
|
|
||||||
Hover mapHover(BaselineHover hover);
|
Hover mapHover(BaselineHover hover);
|
||||||
|
|
||||||
|
List<Location> mapDefinition(BaselineDefinition definition);
|
||||||
|
|
||||||
CompletionList mapCompletion(BaselineCompletion completion);
|
CompletionList mapCompletion(BaselineCompletion completion);
|
||||||
|
|
||||||
SignatureHelp mapSignatureHelp(BaselineSignatureHelp signatureHelp);
|
SignatureHelp mapSignatureHelp(BaselineSignatureHelp signatureHelp);
|
||||||
|
|||||||
@ -45,6 +45,9 @@ 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 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());
|
||||||
@ -309,6 +312,91 @@ 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
|
@Test
|
||||||
void describeServerPublishesFrontendVisualThemes() {
|
void describeServerPublishesFrontendVisualThemes() {
|
||||||
final CompilerLanguageServiceBridge bridge = new CompilerLanguageServiceBridge();
|
final CompilerLanguageServiceBridge bridge = new CompilerLanguageServiceBridge();
|
||||||
@ -319,6 +407,7 @@ 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());
|
||||||
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,6 +15,7 @@ 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;
|
||||||
@ -98,6 +99,7 @@ class PrometeuLanguageServerTest {
|
|||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
List.of(),
|
List.of(),
|
||||||
List.of(new BaselineSemanticHostProjection(
|
List.of(new BaselineSemanticHostProjection(
|
||||||
"vscode",
|
"vscode",
|
||||||
@ -145,6 +147,16 @@ 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
|
@Override
|
||||||
public BaselineSignatureHelp signatureHelp(
|
public BaselineSignatureHelp signatureHelp(
|
||||||
final LspProjectContext project,
|
final LspProjectContext project,
|
||||||
@ -191,6 +203,11 @@ 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
|
@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,9 +3,11 @@ 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.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;
|
||||||
@ -16,6 +18,8 @@ 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.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;
|
||||||
@ -77,6 +81,14 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class RecordingBridge implements LanguageServiceBridge {
|
private static final class RecordingBridge implements LanguageServiceBridge {
|
||||||
@ -84,6 +96,9 @@ 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 lastSignatureHelpText;
|
private String lastSignatureHelpText;
|
||||||
private int lastSignatureHelpLine;
|
private int lastSignatureHelpLine;
|
||||||
private int lastSignatureHelpCharacter;
|
private int lastSignatureHelpCharacter;
|
||||||
@ -123,6 +138,19 @@ 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
|
@Override
|
||||||
public BaselineSignatureHelp signatureHelp(
|
public BaselineSignatureHelp signatureHelp(
|
||||||
final LspProjectContext context,
|
final LspProjectContext context,
|
||||||
@ -151,6 +179,7 @@ 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 BaselineSignatureHelp mappedSignatureHelp;
|
private BaselineSignatureHelp mappedSignatureHelp;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -178,6 +207,11 @@ final class PrometeuTextDocumentServiceTest {
|
|||||||
return new Hover(content);
|
return new Hover(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Location> mapDefinition(final BaselineDefinition definition) {
|
||||||
|
return definitionLocations;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletionList mapCompletion(final BaselineCompletion completion) {
|
public CompletionList mapCompletion(final BaselineCompletion completion) {
|
||||||
return completionList;
|
return completionList;
|
||||||
|
|||||||
@ -6,6 +6,8 @@ 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.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;
|
||||||
@ -35,6 +37,7 @@ final class Lsp4jProtocolMessageMapperTest {
|
|||||||
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",
|
||||||
@ -55,6 +58,7 @@ 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());
|
||||||
|
|
||||||
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"));
|
||||||
@ -104,4 +108,17 @@ 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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user