diff --git a/discussion/index.ndjson b/discussion/index.ndjson index a0100ba9..0b06bb11 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,5 +1,5 @@ {"type":"meta","next_id":{"DSC":32,"AGD":35,"DEC":32,"PLN":65,"LSN":47,"CLSN":1}} -{"type":"discussion","id":"DSC-0031","status":"in_progress","ticket":"studio-editor-workspace-cleanup","title":"Limpeza completa do Workspace Editor do Studio e remoção dos acoplamentos legados","created_at":"2026-05-05","updated_at":"2026-05-05","tags":["studio","editor","cleanup","vfs","lsp","migration"],"agendas":[{"id":"AGD-0034","file":"AGD-0034-vscode-editor-migration-feasibility.md","status":"accepted","created_at":"2026-05-05","updated_at":"2026-05-05"}],"decisions":[{"id":"DEC-0031","file":"DEC-0031-studio-editor-workspace-cleanup.md","status":"in_progress","created_at":"2026-05-05","updated_at":"2026-05-05","ref_agenda":"AGD-0034"}],"plans":[{"id":"PLN-0062","file":"PLN-0062-shell-session-and-state-cleanup-after-editor-removal.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0063","file":"PLN-0063-legacy-lsp-and-vfs-module-removal-with-runtime-preservation.md","status":"open","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0064","file":"PLN-0064-normative-test-and-lesson-cleanup-for-editor-stack-removal.md","status":"open","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0031"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0031","status":"in_progress","ticket":"studio-editor-workspace-cleanup","title":"Limpeza completa do Workspace Editor do Studio e remoção dos acoplamentos legados","created_at":"2026-05-05","updated_at":"2026-05-05","tags":["studio","editor","cleanup","vfs","lsp","migration"],"agendas":[{"id":"AGD-0034","file":"AGD-0034-vscode-editor-migration-feasibility.md","status":"accepted","created_at":"2026-05-05","updated_at":"2026-05-05"}],"decisions":[{"id":"DEC-0031","file":"DEC-0031-studio-editor-workspace-cleanup.md","status":"in_progress","created_at":"2026-05-05","updated_at":"2026-05-05","ref_agenda":"AGD-0034"}],"plans":[{"id":"PLN-0062","file":"PLN-0062-shell-session-and-state-cleanup-after-editor-removal.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0063","file":"PLN-0063-legacy-lsp-and-vfs-module-removal-with-runtime-preservation.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0064","file":"PLN-0064-normative-test-and-lesson-cleanup-for-editor-stack-removal.md","status":"open","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0031"]}],"lessons":[]} {"type":"discussion","id":"DSC-0030","status":"done","ticket":"studio-scene-pack-runtime-binary-contract","title":"Studio scene pack contract for runtime SCENE binary payload","created_at":"2026-04-24","updated_at":"2026-05-01","tags":["studio","packer","runtime","scene","asset-pack","binary-format","tiled"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0046","file":"discussion/lessons/DSC-0030-studio-scene-pack-runtime-binary-contract/LSN-0046-canonical-scene-owns-editorial-truth-while-pack-stays-request-driven.md","status":"done","created_at":"2026-05-01","updated_at":"2026-05-01"}]} {"type":"discussion","id":"DSC-0029","status":"done","ticket":"studio-frame-composer-syscall-and-sprite-alignment","title":"Studio Alignment with Runtime FrameComposer Syscalls and Sprite Composition","created_at":"2026-04-18","updated_at":"2026-04-18","tags":["studio","compiler","pbs","stdlib","runtime-alignment","abi","syscall","frame-composer","sprites"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0041","file":"discussion/lessons/DSC-0029-studio-frame-composer-syscall-and-sprite-alignment/LSN-0041-composer-must-own-public-sprite-composition.md","status":"done","created_at":"2026-04-18","updated_at":"2026-04-18"}]} {"type":"discussion","id":"DSC-0028","status":"done","ticket":"studio-tiled-parser-assets-scene-asset-type","title":"Tiled Parser and Scene Asset-Type Ownership in Assets Workspace","created_at":"2026-04-15","updated_at":"2026-04-23","tags":["studio","assets","scene","tiled","parser","asset-type"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0045","file":"discussion/lessons/DSC-0028-studio-tiled-parser-assets-scene-asset-type/LSN-0045-asset-owned-scene-bank-and-tiled-xml-workflow.md","status":"done","created_at":"2026-04-23","updated_at":"2026-04-23"}]} diff --git a/discussion/workflow/plans/PLN-0063-legacy-lsp-and-vfs-module-removal-with-runtime-preservation.md b/discussion/workflow/plans/PLN-0063-legacy-lsp-and-vfs-module-removal-with-runtime-preservation.md index 1429643b..62122162 100644 --- a/discussion/workflow/plans/PLN-0063-legacy-lsp-and-vfs-module-removal-with-runtime-preservation.md +++ b/discussion/workflow/plans/PLN-0063-legacy-lsp-and-vfs-module-removal-with-runtime-preservation.md @@ -2,9 +2,9 @@ id: PLN-0063 ticket: studio-editor-workspace-cleanup title: Legacy LSP and VFS Module Removal with Runtime Preservation -status: open +status: done created: 2026-05-05 -completed: +completed: 2026-05-05 tags: [studio, editor, cleanup, lsp, vfs, runtime] --- @@ -73,11 +73,11 @@ Remove the legacy `prometeu-lsp` and `prometeu-vfs` modules and all remaining co ## Acceptance Criteria -- [ ] `prometeu-lsp` is no longer part of the build graph. -- [ ] `prometeu-vfs` is no longer part of the build graph. -- [ ] No production Studio class imports legacy `LSP` or `VFS` module types. -- [ ] Compiler and shipper build flows still work. -- [ ] Play/run/debug flows still work without the removed modules. +- [x] `prometeu-lsp` is no longer part of the build graph. +- [x] `prometeu-vfs` is no longer part of the build graph. +- [x] No production Studio class imports legacy `LSP` or `VFS` module types. +- [x] Compiler and shipper build flows still work. +- [x] Play/run/debug flows still work without the removed modules. ## Dependencies @@ -89,4 +89,3 @@ Remove the legacy `prometeu-lsp` and `prometeu-vfs` modules and all remaining co - Hidden transitive dependencies may keep the build graph coupled to removed modules longer than expected. - Runtime or debug flows may have indirect event or session assumptions not obvious from imports alone. - Deleting modules too early may obscure compile errors that should instead be fixed in surviving Studio code first. - diff --git a/prometeu-app/build.gradle.kts b/prometeu-app/build.gradle.kts index 253ad5e0..59d3d29b 100644 --- a/prometeu-app/build.gradle.kts +++ b/prometeu-app/build.gradle.kts @@ -5,10 +5,6 @@ plugins { dependencies { implementation(project(":prometeu-infra")) - implementation(project(":prometeu-lsp:prometeu-lsp-api")) - implementation(project(":prometeu-lsp:prometeu-lsp-v1")) - implementation(project(":prometeu-vfs:prometeu-vfs-api")) - implementation(project(":prometeu-vfs:prometeu-vfs-v1")) implementation(project(":prometeu-packer:prometeu-packer-api")) implementation(project(":prometeu-packer:prometeu-packer-v1")) implementation(project(":prometeu-studio")) @@ -23,4 +19,4 @@ application { javafx { version = libs.versions.javafx.get() modules("javafx.controls", "javafx.fxml") -} \ No newline at end of file +} diff --git a/prometeu-lsp/prometeu-lsp-api/build.gradle.kts b/prometeu-lsp/prometeu-lsp-api/build.gradle.kts deleted file mode 100644 index cc7fab83..00000000 --- a/prometeu-lsp/prometeu-lsp-api/build.gradle.kts +++ /dev/null @@ -1,7 +0,0 @@ -plugins { - id("gradle.java-library-conventions") -} - -dependencies { - implementation(project(":prometeu-vfs:prometeu-vfs-api")) -} diff --git a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/LspService.java b/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/LspService.java deleted file mode 100644 index 7c6ca613..00000000 --- a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/LspService.java +++ /dev/null @@ -1,21 +0,0 @@ -package p.studio.lsp; - -import p.studio.lsp.dtos.LspSessionStateDTO; -import p.studio.lsp.messages.*; -import p.studio.vfs.VfsProjectDocument; - -public interface LspService extends AutoCloseable { - LspProjectContext projectContext(); - - VfsProjectDocument projectDocumentVfs(); - - LspSessionStateDTO snapshot(); - - LspAnalyzeDocumentResult analyzeDocument(LspAnalyzeDocumentRequest request); - - LspDefinitionResult definition(LspDefinitionRequest request); - - @Override - default void close() { - } -} diff --git a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/LspServiceFactory.java b/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/LspServiceFactory.java deleted file mode 100644 index 69e6c4f6..00000000 --- a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/LspServiceFactory.java +++ /dev/null @@ -1,8 +0,0 @@ -package p.studio.lsp; - -import p.studio.lsp.messages.LspProjectContext; -import p.studio.vfs.VfsProjectDocument; - -public interface LspServiceFactory { - LspService open(LspProjectContext projectContext, VfsProjectDocument vfsProjectDocument); -} diff --git a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspDefinitionTargetDTO.java b/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspDefinitionTargetDTO.java deleted file mode 100644 index 3eadeee4..00000000 --- a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspDefinitionTargetDTO.java +++ /dev/null @@ -1,16 +0,0 @@ -package p.studio.lsp.dtos; - -import java.nio.file.Path; -import java.util.Objects; - -public record LspDefinitionTargetDTO( - String name, - Path documentPath, - LspRangeDTO range) { - - public LspDefinitionTargetDTO { - name = Objects.requireNonNull(name, "name"); - documentPath = Objects.requireNonNull(documentPath, "documentPath").toAbsolutePath().normalize(); - range = Objects.requireNonNull(range, "range"); - } -} diff --git a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspDiagnosticDTO.java b/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspDiagnosticDTO.java deleted file mode 100644 index 3eb485e1..00000000 --- a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspDiagnosticDTO.java +++ /dev/null @@ -1,24 +0,0 @@ -package p.studio.lsp.dtos; - -import p.studio.lsp.messages.LspDiagnosticSeverity; - -import java.nio.file.Path; -import java.util.Objects; - -public record LspDiagnosticDTO( - Path documentPath, - LspRangeDTO range, - LspDiagnosticSeverity severity, - String phase, - String code, - String message) { - - public LspDiagnosticDTO { - documentPath = Objects.requireNonNull(documentPath, "documentPath").toAbsolutePath().normalize(); - range = Objects.requireNonNull(range, "range"); - severity = Objects.requireNonNull(severity, "severity"); - phase = phase == null ? "" : phase; - code = code == null ? "" : code; - message = Objects.requireNonNull(message, "message"); - } -} diff --git a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspHighlightSpanDTO.java b/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspHighlightSpanDTO.java deleted file mode 100644 index d0b01e3b..00000000 --- a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspHighlightSpanDTO.java +++ /dev/null @@ -1,13 +0,0 @@ -package p.studio.lsp.dtos; - -import java.util.Objects; - -public record LspHighlightSpanDTO( - LspRangeDTO range, - String semanticKey) { - - public LspHighlightSpanDTO { - range = Objects.requireNonNull(range, "range"); - semanticKey = Objects.requireNonNull(semanticKey, "semanticKey"); - } -} diff --git a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspInlineHintDTO.java b/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspInlineHintDTO.java deleted file mode 100644 index 44e170f8..00000000 --- a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspInlineHintDTO.java +++ /dev/null @@ -1,18 +0,0 @@ -package p.studio.lsp.dtos; - -import java.util.Objects; - -public record LspInlineHintDTO( - LspRangeDTO anchor, - String label, - String category) { - - public LspInlineHintDTO { - anchor = Objects.requireNonNull(anchor, "anchor"); - label = Objects.requireNonNull(label, "label").trim(); - category = Objects.requireNonNull(category, "category").trim(); - if (label.isBlank()) { - throw new IllegalArgumentException("label cannot be blank"); - } - } -} diff --git a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspRangeDTO.java b/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspRangeDTO.java deleted file mode 100644 index 108af2bd..00000000 --- a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspRangeDTO.java +++ /dev/null @@ -1,15 +0,0 @@ -package p.studio.lsp.dtos; - -public record LspRangeDTO( - int startOffset, - int endOffset) { - - public LspRangeDTO { - startOffset = Math.max(0, startOffset); - endOffset = Math.max(startOffset, endOffset); - } - - public boolean contains(final int offset) { - return startOffset <= offset && offset < endOffset; - } -} diff --git a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspSemanticPresentationDTO.java b/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspSemanticPresentationDTO.java deleted file mode 100644 index d31eaf88..00000000 --- a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspSemanticPresentationDTO.java +++ /dev/null @@ -1,14 +0,0 @@ -package p.studio.lsp.dtos; - -import java.util.List; -import java.util.Objects; - -public record LspSemanticPresentationDTO( - List semanticKeys, - List resources) { - - public LspSemanticPresentationDTO { - semanticKeys = List.copyOf(Objects.requireNonNull(semanticKeys, "semanticKeys")); - resources = List.copyOf(Objects.requireNonNull(resources, "resources")); - } -} diff --git a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspSessionStateDTO.java b/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspSessionStateDTO.java deleted file mode 100644 index 91e758b7..00000000 --- a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspSessionStateDTO.java +++ /dev/null @@ -1,13 +0,0 @@ -package p.studio.lsp.dtos; - -import java.util.List; -import java.util.Objects; - -public record LspSessionStateDTO( - boolean semanticReadReady, - List declaredCapabilities) { - - public LspSessionStateDTO { - declaredCapabilities = List.copyOf(Objects.requireNonNull(declaredCapabilities, "declaredCapabilities")); - } -} diff --git a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspStructuralAnchorDTO.java b/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspStructuralAnchorDTO.java deleted file mode 100644 index 334f6f8b..00000000 --- a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspStructuralAnchorDTO.java +++ /dev/null @@ -1,18 +0,0 @@ -package p.studio.lsp.dtos; - -import java.util.List; -import java.util.Objects; - -public record LspStructuralAnchorDTO( - LspRangeDTO range, - LspRangeDTO startAnchor, - LspRangeDTO endAnchor, - List children) { - - public LspStructuralAnchorDTO { - range = Objects.requireNonNull(range, "range"); - startAnchor = Objects.requireNonNull(startAnchor, "startAnchor"); - endAnchor = Objects.requireNonNull(endAnchor, "endAnchor"); - children = List.copyOf(Objects.requireNonNull(children, "children")); - } -} diff --git a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspSymbolDTO.java b/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspSymbolDTO.java deleted file mode 100644 index 124b69a0..00000000 --- a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspSymbolDTO.java +++ /dev/null @@ -1,23 +0,0 @@ -package p.studio.lsp.dtos; - -import p.studio.lsp.messages.LspSymbolKind; - -import java.nio.file.Path; -import java.util.List; -import java.util.Objects; - -public record LspSymbolDTO( - String name, - LspSymbolKind kind, - Path documentPath, - LspRangeDTO range, - List children) { - - public LspSymbolDTO { - name = Objects.requireNonNull(name, "name"); - kind = Objects.requireNonNull(kind, "kind"); - documentPath = Objects.requireNonNull(documentPath, "documentPath").toAbsolutePath().normalize(); - range = Objects.requireNonNull(range, "range"); - children = List.copyOf(Objects.requireNonNull(children, "children")); - } -} diff --git a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/events/LspEvent.java b/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/events/LspEvent.java deleted file mode 100644 index 7a218128..00000000 --- a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/events/LspEvent.java +++ /dev/null @@ -1,13 +0,0 @@ -package p.studio.lsp.events; - -import java.util.Objects; - -public record LspEvent( - LspEventKind kind, - String message) { - - public LspEvent { - Objects.requireNonNull(kind, "kind"); - Objects.requireNonNull(message, "message"); - } -} diff --git a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/events/LspEventKind.java b/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/events/LspEventKind.java deleted file mode 100644 index 94afdb08..00000000 --- a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/events/LspEventKind.java +++ /dev/null @@ -1,5 +0,0 @@ -package p.studio.lsp.events; - -public enum LspEventKind { - SESSION_READY -} diff --git a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/events/LspEventSink.java b/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/events/LspEventSink.java deleted file mode 100644 index eca8e3c5..00000000 --- a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/events/LspEventSink.java +++ /dev/null @@ -1,5 +0,0 @@ -package p.studio.lsp.events; - -public interface LspEventSink { - void publish(LspEvent event); -} diff --git a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/messages/LspAnalyzeDocumentRequest.java b/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/messages/LspAnalyzeDocumentRequest.java deleted file mode 100644 index c5d3c5d2..00000000 --- a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/messages/LspAnalyzeDocumentRequest.java +++ /dev/null @@ -1,12 +0,0 @@ -package p.studio.lsp.messages; - -import java.nio.file.Path; -import java.util.Objects; - -public record LspAnalyzeDocumentRequest( - Path documentPath) { - - public LspAnalyzeDocumentRequest { - documentPath = Objects.requireNonNull(documentPath, "documentPath").toAbsolutePath().normalize(); - } -} diff --git a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/messages/LspAnalyzeDocumentResult.java b/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/messages/LspAnalyzeDocumentResult.java deleted file mode 100644 index 5c647e80..00000000 --- a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/messages/LspAnalyzeDocumentResult.java +++ /dev/null @@ -1,34 +0,0 @@ -package p.studio.lsp.messages; - -import p.studio.lsp.dtos.LspDiagnosticDTO; -import p.studio.lsp.dtos.LspHighlightSpanDTO; -import p.studio.lsp.dtos.LspInlineHintDTO; -import p.studio.lsp.dtos.LspSemanticPresentationDTO; -import p.studio.lsp.dtos.LspSessionStateDTO; -import p.studio.lsp.dtos.LspStructuralAnchorDTO; -import p.studio.lsp.dtos.LspSymbolDTO; - -import java.util.List; -import java.util.Objects; - -public record LspAnalyzeDocumentResult( - LspSessionStateDTO sessionState, - LspSemanticPresentationDTO semanticPresentation, - List diagnostics, - List semanticHighlights, - List inlineHints, - List documentSymbols, - List structuralAnchors, - List workspaceSymbols) { - - public LspAnalyzeDocumentResult { - Objects.requireNonNull(sessionState, "sessionState"); - Objects.requireNonNull(semanticPresentation, "semanticPresentation"); - diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics")); - semanticHighlights = List.copyOf(Objects.requireNonNull(semanticHighlights, "semanticHighlights")); - inlineHints = List.copyOf(Objects.requireNonNull(inlineHints, "inlineHints")); - documentSymbols = List.copyOf(Objects.requireNonNull(documentSymbols, "documentSymbols")); - structuralAnchors = List.copyOf(Objects.requireNonNull(structuralAnchors, "structuralAnchors")); - workspaceSymbols = List.copyOf(Objects.requireNonNull(workspaceSymbols, "workspaceSymbols")); - } -} diff --git a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/messages/LspDefinitionRequest.java b/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/messages/LspDefinitionRequest.java deleted file mode 100644 index af205092..00000000 --- a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/messages/LspDefinitionRequest.java +++ /dev/null @@ -1,14 +0,0 @@ -package p.studio.lsp.messages; - -import java.nio.file.Path; -import java.util.Objects; - -public record LspDefinitionRequest( - Path documentPath, - int offset) { - - public LspDefinitionRequest { - documentPath = Objects.requireNonNull(documentPath, "documentPath").toAbsolutePath().normalize(); - offset = Math.max(0, offset); - } -} diff --git a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/messages/LspDefinitionResult.java b/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/messages/LspDefinitionResult.java deleted file mode 100644 index 8067702c..00000000 --- a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/messages/LspDefinitionResult.java +++ /dev/null @@ -1,19 +0,0 @@ -package p.studio.lsp.messages; - -import p.studio.lsp.dtos.LspDefinitionTargetDTO; - -import java.nio.file.Path; -import java.util.List; -import java.util.Objects; - -public record LspDefinitionResult( - Path documentPath, - int offset, - List targets) { - - public LspDefinitionResult { - documentPath = Objects.requireNonNull(documentPath, "documentPath").toAbsolutePath().normalize(); - offset = Math.max(0, offset); - targets = List.copyOf(Objects.requireNonNull(targets, "targets")); - } -} diff --git a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/messages/LspDiagnosticSeverity.java b/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/messages/LspDiagnosticSeverity.java deleted file mode 100644 index 2481f6b1..00000000 --- a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/messages/LspDiagnosticSeverity.java +++ /dev/null @@ -1,6 +0,0 @@ -package p.studio.lsp.messages; - -public enum LspDiagnosticSeverity { - ERROR, - WARNING -} diff --git a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/messages/LspProjectContext.java b/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/messages/LspProjectContext.java deleted file mode 100644 index 85b44151..00000000 --- a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/messages/LspProjectContext.java +++ /dev/null @@ -1,16 +0,0 @@ -package p.studio.lsp.messages; - -import java.nio.file.Path; -import java.util.Objects; - -public record LspProjectContext( - String projectName, - String languageId, - Path rootPath) { - - public LspProjectContext { - Objects.requireNonNull(projectName, "projectName"); - Objects.requireNonNull(languageId, "languageId"); - rootPath = Objects.requireNonNull(rootPath, "rootPath").toAbsolutePath().normalize(); - } -} diff --git a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/messages/LspSymbolKind.java b/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/messages/LspSymbolKind.java deleted file mode 100644 index 3f65c287..00000000 --- a/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/messages/LspSymbolKind.java +++ /dev/null @@ -1,24 +0,0 @@ -package p.studio.lsp.messages; - -public enum LspSymbolKind { - FUNCTION, - METHOD, - CONSTRUCTOR, - IF, - SWITCH, - FOR, - WHILE, - HANDLE, - GLOBAL, - CONST, - STRUCT, - CONTRACT, - HOST, - BUILTIN_TYPE, - SERVICE, - ERROR, - ENUM, - CALLBACK, - IMPLEMENTS, - UNKNOWN -} diff --git a/prometeu-lsp/prometeu-lsp-api/src/test/java/p/studio/lsp/dtos/LspInlineHintDTOTest.java b/prometeu-lsp/prometeu-lsp-api/src/test/java/p/studio/lsp/dtos/LspInlineHintDTOTest.java deleted file mode 100644 index f4489a17..00000000 --- a/prometeu-lsp/prometeu-lsp-api/src/test/java/p/studio/lsp/dtos/LspInlineHintDTOTest.java +++ /dev/null @@ -1,22 +0,0 @@ -package p.studio.lsp.dtos; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -final class LspInlineHintDTOTest { - @Test - void trimsLabelAndCategory() { - final var hint = new LspInlineHintDTO(new LspRangeDTO(3, 3), " int ", " type "); - - assertEquals("int", hint.label()); - assertEquals("type", hint.category()); - } - - @Test - void rejectsBlankLabels() { - assertThrows(IllegalArgumentException.class, () -> - new LspInlineHintDTO(new LspRangeDTO(1, 1), " ", "type")); - } -} diff --git a/prometeu-lsp/prometeu-lsp-v1/build.gradle.kts b/prometeu-lsp/prometeu-lsp-v1/build.gradle.kts deleted file mode 100644 index 37704dc3..00000000 --- a/prometeu-lsp/prometeu-lsp-v1/build.gradle.kts +++ /dev/null @@ -1,16 +0,0 @@ -plugins { - id("gradle.java-library-conventions") -} - -dependencies { - implementation(project(":prometeu-lsp:prometeu-lsp-api")) - implementation(project(":prometeu-vfs:prometeu-vfs-api")) - implementation(project(":prometeu-compiler:prometeu-compiler-core")) - implementation(project(":prometeu-compiler:prometeu-frontend-api")) - implementation(project(":prometeu-compiler:prometeu-frontend-registry")) - implementation(project(":prometeu-compiler:prometeu-deps")) - implementation(project(":prometeu-compiler:prometeu-build-pipeline")) - implementation(project(":prometeu-compiler:frontends:prometeu-frontend-pbs")) - - testImplementation(project(":prometeu-vfs:prometeu-vfs-v1")) -} diff --git a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/LspSemanticAnalyseService.java b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/LspSemanticAnalyseService.java deleted file mode 100644 index 73380dbf..00000000 --- a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/LspSemanticAnalyseService.java +++ /dev/null @@ -1,27 +0,0 @@ -package p.studio.lsp; - -import p.studio.lsp.dtos.LspSessionStateDTO; -import p.studio.lsp.messages.LspAnalyzeDocumentRequest; -import p.studio.lsp.messages.LspAnalyzeDocumentResult; -import p.studio.lsp.messages.SemanticSession; - -import java.util.List; - -import static p.studio.lsp.LspSemanticUtilities.normalize; - -class LspSemanticAnalyseService { - LspAnalyzeDocumentResult analyze( - final SemanticSession session, - final LspAnalyzeDocumentRequest request) { - final var normalizedRequestedDocument = normalize(request.documentPath()); - return new LspAnalyzeDocumentResult( - new LspSessionStateDTO(true, List.of("diagnostics", "symbols", "definition", "highlight", "structural-anchors")), - session.semanticPresentation(), - session.diagnosticsByDocument().getOrDefault(normalizedRequestedDocument, List.of()), - session.semanticHighlightsByDocument().getOrDefault(normalizedRequestedDocument, List.of()), - session.inlineHintsByDocument().getOrDefault(normalizedRequestedDocument, List.of()), - session.documentSymbolsByDocument().getOrDefault(normalizedRequestedDocument, List.of()), - session.structuralAnchorsByDocument().getOrDefault(normalizedRequestedDocument, List.of()), - session.workspaceSymbols()); - } -} diff --git a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/LspSemanticDefinitionService.java b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/LspSemanticDefinitionService.java deleted file mode 100644 index b727c278..00000000 --- a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/LspSemanticDefinitionService.java +++ /dev/null @@ -1,46 +0,0 @@ -package p.studio.lsp; - -import p.studio.compiler.pbs.lexer.PbsToken; -import p.studio.compiler.pbs.lexer.PbsTokenKind; -import p.studio.lsp.dtos.LspDefinitionTargetDTO; -import p.studio.lsp.dtos.LspSymbolDTO; -import p.studio.lsp.messages.LspDefinitionRequest; -import p.studio.lsp.messages.LspDefinitionResult; -import p.studio.lsp.messages.SemanticSession; - -import java.nio.file.Path; -import java.util.List; - -import static p.studio.lsp.LspSemanticUtilities.normalize; -import static p.studio.lsp.LspSemanticUtilities.tokenAt; - -class LspSemanticDefinitionService { - - LspDefinitionResult definition( - final SemanticSession session, - final LspDefinitionRequest request) { - final List targets = resolveDefinitionTargets(session, request); - return new LspDefinitionResult(request.documentPath(), request.offset(), targets); - } - - private List resolveDefinitionTargets( - final SemanticSession session, - final LspDefinitionRequest request) { - final Path documentPath = normalize(request.documentPath()); - final List tokens = session.tokensByDocument().getOrDefault(documentPath, List.of()); - final PbsToken activeToken = tokenAt(tokens, request.offset()); - if (activeToken == null || activeToken.kind() != PbsTokenKind.IDENTIFIER) { - return List.of(); - } - final String lexeme = activeToken.lexeme(); - final List sameDocumentMatches = session.symbolsByName().getOrDefault(lexeme, List.of()).stream() - .filter(symbol -> symbol.documentPath().equals(documentPath)) - .toList(); - final List effectiveMatches = sameDocumentMatches.isEmpty() - ? session.symbolsByName().getOrDefault(lexeme, List.of()) - : sameDocumentMatches; - return effectiveMatches.stream() - .map(symbol -> new LspDefinitionTargetDTO(symbol.name(), symbol.documentPath(), symbol.range())) - .toList(); - } -} diff --git a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/LspSemanticReadPhase.java b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/LspSemanticReadPhase.java deleted file mode 100644 index 0163745c..00000000 --- a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/LspSemanticReadPhase.java +++ /dev/null @@ -1,353 +0,0 @@ -package p.studio.lsp; - -import p.studio.compiler.FrontendRegistryService; -import p.studio.compiler.messages.*; -import p.studio.compiler.models.AnalysisSnapshot; -import p.studio.compiler.models.BuilderPipelineContext; -import p.studio.compiler.models.FrontendSpec; -import p.studio.compiler.models.FrontendSemanticPresentationSpec; -import p.studio.compiler.models.SourceHandle; -import p.studio.compiler.pbs.ast.PbsAst; -import p.studio.compiler.pbs.lexer.PbsLexer; -import p.studio.compiler.pbs.parser.PbsParser; -import p.studio.compiler.source.diagnostics.Diagnostic; -import p.studio.compiler.source.diagnostics.DiagnosticSink; -import p.studio.compiler.source.identifiers.FileId; -import p.studio.compiler.services.PBSFrontendPhaseService; -import p.studio.compiler.services.PbsInlineHintSurface; -import p.studio.compiler.workspaces.AssetSurfaceContextLoader; -import p.studio.compiler.workspaces.PipelineStage; -import p.studio.compiler.workspaces.stages.LoadSourcesPipelineStage; -import p.studio.compiler.workspaces.stages.ResolveDepsPipelineStage; -import p.studio.lsp.dtos.LspDiagnosticDTO; -import p.studio.lsp.dtos.LspInlineHintDTO; -import p.studio.lsp.dtos.LspRangeDTO; -import p.studio.lsp.dtos.LspSemanticPresentationDTO; -import p.studio.lsp.messages.*; -import p.studio.lsp.models.AnalysisRuntimeSnapshot; -import p.studio.lsp.models.SemanticIndex; -import p.studio.utilities.logs.LogAggregator; -import p.studio.vfs.VfsProjectDocument; - -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import static p.studio.lsp.LspSemanticUtilities.*; - -final class LspSemanticReadPhase { - private final LspSemanticAnalyseService analyseService; - private final LspSemanticDefinitionService definitionService; - - LspSemanticReadPhase() { - this.analyseService = new LspSemanticAnalyseService(); - this.definitionService = new LspSemanticDefinitionService(); - } - - public LspAnalyzeDocumentResult analyze( - final LspProjectContext projectContext, - final VfsProjectDocument vfsProjectDocument, - final LspAnalyzeDocumentRequest request) { - final var session = buildSession(projectContext, vfsProjectDocument, request.documentPath()); - return analyseService.analyze(session, request); - } - - public LspDefinitionResult definition( - final LspProjectContext projectContext, - final VfsProjectDocument vfsProjectDocument, - final LspDefinitionRequest request) { - final var session = buildSession(projectContext, vfsProjectDocument, request.documentPath()); - return definitionService.definition(session, request); - } - - public static SemanticSession buildSession( - final LspProjectContext projectContext, - final VfsProjectDocument vfsProjectDocument, - final Path requestedDocumentPath) { - final BuilderPipelineConfig config = new BuilderPipelineConfig( - false, - projectContext.rootPath().toString(), - "core-v1", - new OverlaySourceProviderFactoryImpl(vfsProjectDocument, requestedDocumentPath)); - final BuilderPipelineContext context = BuilderPipelineContext.fromConfig(config); - final AnalysisRuntimeSnapshot snapshot = runAnalysisStages(context); - return index(snapshot, context, requestedDocumentPath); - } - - private static AnalysisRuntimeSnapshot runAnalysisStages(final BuilderPipelineContext context) { - final LogAggregator logs = LogAggregator.empty(); - final List diagnostics = new ArrayList<>(); - final List stages = List.of( - new ResolveDepsPipelineStage(), - new LoadSourcesPipelineStage()); - for (final PipelineStage stage : stages) { - final BuildingIssueSink stageIssues = stage.run(context, logs); - diagnostics.addAll(stageIssues.asCollection()); - if (stageIssues.hasErrors()) { - break; - } - } - final DiagnosticSink frontendDiagnostics = DiagnosticSink.empty(); - FrontendSpec frontendSpec = FrontendRegistryService.getDefaultFrontendSpec(); - if (context.resolvedWorkspace != null && context.fileTable != null) { - final BuildingIssueSink frontendIssues = runFrontendPhase(context, logs, frontendDiagnostics); - diagnostics.addAll(frontendIssues.asCollection()); - frontendSpec = context.resolvedWorkspace.frontendSpec(); - } - return new AnalysisRuntimeSnapshot( - new AnalysisSnapshot( - frontendSpec, - diagnostics, - context.resolvedWorkspace, - context.fileTable, - context.irBackend), - List.copyOf(frontendDiagnostics.asCollection())); - } - - private static BuildingIssueSink runFrontendPhase( - final BuilderPipelineContext context, - final LogAggregator logs, - final DiagnosticSink frontendDiagnostics) { - final var frontendSpec = context.resolvedWorkspace.frontendSpec(); - final var service = FrontendRegistryService.getFrontendPhaseService(frontendSpec.getLanguageId()); - if (service.isEmpty()) { - return BuildingIssueSink.empty().report(builder -> builder - .error(true) - .message("[BUILD]: unable to find a service for frontend phase: " + frontendSpec.getLanguageId())); - } - final FESurfaceContext feSurfaceContext = new AssetSurfaceContextLoader().load(context.resolvedWorkspace.mainProject().getRootPath()); - final FrontendPhaseContext frontendPhaseContext = new FrontendPhaseContext( - context.resolvedWorkspace.graph().projectTable(), - context.fileTable, - context.resolvedWorkspace.stack(), - context.resolvedWorkspace.stdlib(), - HostAdmissionContext.permissiveDefault(), - feSurfaceContext); - final BuildingIssueSink issues = BuildingIssueSink.empty(); - context.irBackend = service.get().compile(frontendPhaseContext, frontendDiagnostics, logs, issues); - return issues; - } - - private static SemanticSession index( - final AnalysisRuntimeSnapshot runtimeSnapshot, - final BuilderPipelineContext context, - final Path requestedDocumentPath) { - final AnalysisSnapshot snapshot = runtimeSnapshot.analysisSnapshot(); - final Map> diagnosticsByDocument = diagnosticsByDocument( - snapshot.diagnostics(), - snapshot, - runtimeSnapshot.frontendDiagnostics()); - final SemanticIndex semanticIndex = new SemanticIndex(); - if (snapshot.fileTable() == null) { - return new SemanticSession( - normalize(requestedDocumentPath), - semanticPresentation(snapshot.frontendSpec()), - diagnosticsByDocument, - Map.of(), - Map.of(), - Map.of(), - Map.of(), - List.of(), - Map.of(), - Map.of()); - } - final Map> importedSupplementalTopDeclsByFile = importedSupplementalTopDeclsByFile( - snapshot.frontendSpec(), - context); - final Map> inlineHintsByDocument = inlineHintsByDocument( - snapshot, - snapshot.frontendSpec(), - context); - final List indexedDocuments = new ArrayList<>(); - for (final FileId fileId : snapshot.fileTable()) { - final SourceHandle sourceHandle = snapshot.fileTable().get(fileId); - if (!isSourceRelated(snapshot.frontendSpec(), sourceHandle)) { - continue; - } - final String source = sourceHandle.readUtf8().orElse(""); - final DiagnosticSink diagnostics = DiagnosticSink.empty(); - final var tokens = PbsLexer.lex(source, fileId, diagnostics); - final PbsAst.File ast = PbsParser.parse(tokens, fileId, diagnostics, PbsParser.ParseMode.ORDINARY); - final Path documentPath = sourceHandle.getCanonPath(); - semanticIndex.registerDocument(documentPath, ast, tokens.asList()); - indexedDocuments.add(new IndexedDocument( - fileId, - documentPath, - importedSupplementalTopDeclsByFile.getOrDefault(fileId, List.of()))); - } - for (final IndexedDocument indexedDocument : indexedDocuments) { - semanticIndex.buildHighlights( - indexedDocument.documentPath(), - indexedDocument.visibleImportedTopDecls()); - } - return new SemanticSession( - normalize(requestedDocumentPath), - semanticPresentation(snapshot.frontendSpec()), - diagnosticsByDocument, - semanticIndex.semanticHighlightsByDocument(), - inlineHintsByDocument, - semanticIndex.documentSymbolsByDocument(), - semanticIndex.structuralAnchorsByDocument(), - semanticIndex.workspaceSymbols(), - semanticIndex.symbolsByName(), - semanticIndex.tokensByDocument()); - } - - private static Map> importedSupplementalTopDeclsByFile( - final FrontendSpec frontendSpec, - final BuilderPipelineContext context) { - final var surface = semanticReadSurface(frontendSpec, context); - final Map> byFile = new LinkedHashMap<>(); - for (final var entry : surface.supplementalTopDeclsByFile().entrySet()) { - byFile.put(entry.getKey(), entry.getValue().asList()); - } - return Map.copyOf(byFile); - } - - private static Map> inlineHintsByDocument( - final AnalysisSnapshot snapshot, - final FrontendSpec frontendSpec, - final BuilderPipelineContext context) { - if (snapshot.fileTable() == null) { - return Map.of(); - } - final var surface = semanticReadSurface(frontendSpec, context); - final Map> byDocument = new LinkedHashMap<>(); - for (final var entry : surface.inlineHintsByFile().entrySet()) { - final SourceHandle sourceHandle = snapshot.fileTable().get(entry.getKey()); - final Path documentPath = sourceHandle.getCanonPath().toAbsolutePath().normalize(); - final var hints = entry.getValue().asList().stream() - .map(LspSemanticReadPhase::toInlineHintDTO) - .toList(); - if (!hints.isEmpty()) { - byDocument.put(documentPath, hints); - } - } - return Map.copyOf(byDocument); - } - - private static PBSFrontendPhaseService.PbsSemanticReadSurface semanticReadSurface( - final FrontendSpec frontendSpec, - final BuilderPipelineContext context) { - if (context.resolvedWorkspace == null || context.fileTable == null || !"pbs".equals(frontendSpec.getLanguageId())) { - return new PBSFrontendPhaseService.PbsSemanticReadSurface(Map.of(), Map.of()); - } - final FESurfaceContext feSurfaceContext = new AssetSurfaceContextLoader().load(context.resolvedWorkspace.mainProject().getRootPath()); - final FrontendPhaseContext frontendPhaseContext = new FrontendPhaseContext( - context.resolvedWorkspace.graph().projectTable(), - context.fileTable, - context.resolvedWorkspace.stack(), - context.resolvedWorkspace.stdlib(), - HostAdmissionContext.permissiveDefault(), - feSurfaceContext); - final var diagnostics = DiagnosticSink.empty(); - final var issues = BuildingIssueSink.empty(); - return PBSFrontendPhaseService.semanticReadSurface( - frontendPhaseContext, - diagnostics, - issues); - } - - private static LspInlineHintDTO toInlineHintDTO(final PbsInlineHintSurface hint) { - return new LspInlineHintDTO( - new LspRangeDTO( - (int) hint.anchorSpan().getStart(), - (int) hint.anchorSpan().getEnd()), - hint.label(), - hint.category()); - } - - private record IndexedDocument( - FileId fileId, - Path documentPath, - List visibleImportedTopDecls) { - } - - private static boolean isSourceRelated(final FrontendSpec frontendSpec, final SourceHandle sourceHandle) { - return frontendSpec.getAllowedExtensions().contains(sourceHandle.getExtension()); - } - - private static LspSemanticPresentationDTO semanticPresentation(final FrontendSpec frontendSpec) { - final FrontendSemanticPresentationSpec presentation = frontendSpec.getSemanticPresentation(); - return new LspSemanticPresentationDTO(presentation.semanticKeys(), presentation.resources()); - } - - private static Map> diagnosticsByDocument( - final List issues, - final AnalysisSnapshot snapshot, - final List frontendDiagnostics) { - final Map> diagnosticsByDocument = new LinkedHashMap<>(); - diagnosticIssues(issues, snapshot, diagnosticsByDocument); - diagnosticFrontend(frontendDiagnostics, snapshot, diagnosticsByDocument); - return freezeMapOfLists(diagnosticsByDocument); - } - - private static boolean isValidIssueDiagnostic( - final AnalysisSnapshot snapshot, - final BuildingIssue issue) { - if (snapshot.fileTable() == null || issue.getFileId() == null || issue.getFileId() < 0) { - return false; - } - return issue.getFileId() < snapshot.fileTable().size(); - } - - private static void diagnosticIssues( - final List issues, - final AnalysisSnapshot snapshot, - final Map> diagnosticsByDocument) { - for (final var issue : issues) { - if (!isValidIssueDiagnostic(snapshot, issue)) { - continue; - } - final SourceHandle sourceHandle = snapshot.fileTable().get(new FileId(issue.getFileId())); - final Path documentPath = sourceHandle.getCanonPath().toAbsolutePath().normalize(); - diagnosticsByDocument - .computeIfAbsent(documentPath, ignored -> new ArrayList<>()) - .add(new LspDiagnosticDTO( - documentPath, - new LspRangeDTO( - safeOffset(issue.getStart()), - safeEnd(issue.getStart(), issue.getEnd())), - issue.isError() - ? LspDiagnosticSeverity.ERROR - : LspDiagnosticSeverity.WARNING, - issue.getPhase(), - issue.getCode(), - issue.getMessage())); - } - } - - private static boolean isValidFEDiagnostic( - final AnalysisSnapshot snapshot, - final Diagnostic diagnostic) { - return snapshot.fileTable() != null && !diagnostic.getSpan().getFileId().isNone(); - } - - private static void diagnosticFrontend( - final List feDiagnostics, - final AnalysisSnapshot snapshot, - final Map> diagnosticsByDocument) { - for (final var feDiagnostic : feDiagnostics) { - if (!isValidFEDiagnostic(snapshot, feDiagnostic)) { - continue; - } - final SourceHandle sourceHandle = snapshot.fileTable().get(feDiagnostic.getSpan().getFileId()); - final Path documentPath = sourceHandle.getCanonPath().toAbsolutePath().normalize(); - diagnosticsByDocument - .computeIfAbsent(documentPath, ignored -> new ArrayList<>()) - .add(new LspDiagnosticDTO( - documentPath, - new LspRangeDTO( - (int) feDiagnostic.getSpan().getStart(), - (int) feDiagnostic.getSpan().getEnd()), - feDiagnostic.getSeverity().isError() - ? LspDiagnosticSeverity.ERROR - : LspDiagnosticSeverity.WARNING, - feDiagnostic.getPhase().name(), - feDiagnostic.getCode(), - feDiagnostic.getMessage())); - } - } -} diff --git a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/LspSemanticUtilities.java b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/LspSemanticUtilities.java deleted file mode 100644 index b3894df1..00000000 --- a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/LspSemanticUtilities.java +++ /dev/null @@ -1,52 +0,0 @@ -package p.studio.lsp; - -import lombok.experimental.UtilityClass; -import p.studio.compiler.pbs.lexer.PbsToken; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -@UtilityClass -public class LspSemanticUtilities { - - public static PbsToken tokenAt(final List tokens, final int offset) { - for (final PbsToken token : tokens) { - if (token.start() <= offset && offset < token.end()) { - return token; - } - } - return null; - } - - public static Path normalize(final Path path) { - final Path normalized = Objects.requireNonNull(path, "path").toAbsolutePath().normalize(); - try { - return Files.exists(normalized) ? normalized.toRealPath() : normalized; - } catch (IOException ignored) { - return normalized; - } - } - - public static Map> freezeMapOfLists(final Map> mutable) { - final Map> frozen = new LinkedHashMap<>(); - for (final var entry : mutable.entrySet()) { - frozen.put(entry.getKey(), List.copyOf(entry.getValue())); - } - return Map.copyOf(frozen); - } - - public static int safeOffset(final Integer value) { - return value == null ? 0 : Math.max(0, value); - } - - public static int safeEnd(final Integer start, final Integer end) { - final int safeStart = safeOffset(start); - final int safeEnd = safeOffset(end); - return Math.max(safeStart, safeEnd); - } -} diff --git a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/LspServiceFactoryImpl.java b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/LspServiceFactoryImpl.java deleted file mode 100644 index 79abcb93..00000000 --- a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/LspServiceFactoryImpl.java +++ /dev/null @@ -1,17 +0,0 @@ -package p.studio.lsp; - -import p.studio.lsp.messages.LspProjectContext; -import p.studio.vfs.VfsProjectDocument; - -import java.util.Objects; - -public final class LspServiceFactoryImpl implements LspServiceFactory { - @Override - public LspService open( - final LspProjectContext projectContext, - final VfsProjectDocument vfsProjectDocument) { - return new LspServiceImpl( - Objects.requireNonNull(projectContext, "projectContext"), - Objects.requireNonNull(vfsProjectDocument, "vfsProjectDocument")); - } -} diff --git a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/LspServiceImpl.java b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/LspServiceImpl.java deleted file mode 100644 index e85596c3..00000000 --- a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/LspServiceImpl.java +++ /dev/null @@ -1,50 +0,0 @@ -package p.studio.lsp; - -import p.studio.lsp.dtos.LspSessionStateDTO; -import p.studio.lsp.messages.*; -import p.studio.vfs.VfsProjectDocument; - -import java.util.List; -import java.util.Objects; - -public final class LspServiceImpl implements LspService { - private final LspProjectContext projectContext; - private final VfsProjectDocument vfsProjectDocument; - - private final LspSemanticReadPhase semanticReadPhase; - - public LspServiceImpl( - final LspProjectContext projectContext, - final VfsProjectDocument vfsProjectDocument) { - this.projectContext = Objects.requireNonNull(projectContext, "projectContext"); - this.vfsProjectDocument = Objects.requireNonNull(vfsProjectDocument, "vfsProjectDocument"); - this.semanticReadPhase = new LspSemanticReadPhase(); - } - - @Override - public LspProjectContext projectContext() { - return projectContext; - } - - @Override - public VfsProjectDocument projectDocumentVfs() { - return vfsProjectDocument; - } - - @Override - public LspSessionStateDTO snapshot() { - return new LspSessionStateDTO( - true, - List.of("diagnostics", "symbols", "definition", "highlight")); - } - - @Override - public LspAnalyzeDocumentResult analyzeDocument(final LspAnalyzeDocumentRequest request) { - return semanticReadPhase.analyze(projectContext, vfsProjectDocument, request); - } - - @Override - public LspDefinitionResult definition(final LspDefinitionRequest request) { - return semanticReadPhase.definition(projectContext, vfsProjectDocument, request); - } -} diff --git a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/OverlaySourceProviderFactoryImpl.java b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/OverlaySourceProviderFactoryImpl.java deleted file mode 100644 index e6ef0ac3..00000000 --- a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/OverlaySourceProviderFactoryImpl.java +++ /dev/null @@ -1,51 +0,0 @@ -package p.studio.lsp; - -import p.studio.compiler.utilities.SourceProvider; -import p.studio.compiler.utilities.SourceProviderFactory; -import p.studio.vfs.VfsDocumentOpenResult; -import p.studio.vfs.VfsProjectDocument; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Objects; - -final class OverlaySourceProviderFactoryImpl implements SourceProviderFactory { - private final VfsProjectDocument vfsProjectDocument; - private final Path openedDocumentPath; - - OverlaySourceProviderFactoryImpl( - final VfsProjectDocument vfsProjectDocument, - final Path openedDocumentPath) { - this.vfsProjectDocument = Objects.requireNonNull(vfsProjectDocument, "vfsProjectDocument"); - this.openedDocumentPath = openedDocumentPath == null - ? null - : normalize(openedDocumentPath); - } - - @Override - public SourceProvider create(final Path path) { - final Path normalizedPath = normalize(path); - return () -> read(normalizedPath); - } - - private byte[] read(final Path path) throws IOException { - if (openedDocumentPath != null && openedDocumentPath.equals(path)) { - final VfsDocumentOpenResult result = vfsProjectDocument.openDocument(path); - if (result instanceof VfsDocumentOpenResult.VfsTextDocument textDocument) { - return textDocument.content().getBytes(StandardCharsets.UTF_8); - } - } - return Files.readAllBytes(path); - } - - private Path normalize(final Path path) { - final Path normalized = path.toAbsolutePath().normalize(); - try { - return Files.exists(normalized) ? normalized.toRealPath() : normalized; - } catch (IOException ignored) { - return normalized; - } - } -} diff --git a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/SemanticSession.java b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/SemanticSession.java deleted file mode 100644 index 3d3f23f9..00000000 --- a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/SemanticSession.java +++ /dev/null @@ -1,26 +0,0 @@ -package p.studio.lsp.messages; - -import p.studio.compiler.pbs.lexer.PbsToken; -import p.studio.lsp.dtos.LspDiagnosticDTO; -import p.studio.lsp.dtos.LspHighlightSpanDTO; -import p.studio.lsp.dtos.LspInlineHintDTO; -import p.studio.lsp.dtos.LspSemanticPresentationDTO; -import p.studio.lsp.dtos.LspStructuralAnchorDTO; -import p.studio.lsp.dtos.LspSymbolDTO; - -import java.nio.file.Path; -import java.util.List; -import java.util.Map; - -public record SemanticSession( - Path requestedDocumentPath, - LspSemanticPresentationDTO semanticPresentation, - Map> diagnosticsByDocument, - Map> semanticHighlightsByDocument, - Map> inlineHintsByDocument, - Map> documentSymbolsByDocument, - Map> structuralAnchorsByDocument, - List workspaceSymbols, - Map> symbolsByName, - Map> tokensByDocument) { -} diff --git a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/models/AnalysisRuntimeSnapshot.java b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/models/AnalysisRuntimeSnapshot.java deleted file mode 100644 index 65a6e234..00000000 --- a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/models/AnalysisRuntimeSnapshot.java +++ /dev/null @@ -1,11 +0,0 @@ -package p.studio.lsp.models; - -import p.studio.compiler.models.AnalysisSnapshot; -import p.studio.compiler.source.diagnostics.Diagnostic; - -import java.util.List; - -public record AnalysisRuntimeSnapshot( - AnalysisSnapshot analysisSnapshot, - List frontendDiagnostics) { -} \ No newline at end of file diff --git a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/models/SemanticIndex.java b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/models/SemanticIndex.java deleted file mode 100644 index 1649d505..00000000 --- a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/models/SemanticIndex.java +++ /dev/null @@ -1,635 +0,0 @@ -package p.studio.lsp.models; - -import p.studio.compiler.pbs.PbsSemanticKind; -import p.studio.compiler.pbs.ast.PbsAst; -import p.studio.compiler.pbs.lexer.PbsToken; -import p.studio.compiler.pbs.lexer.PbsTokenKind; -import p.studio.compiler.source.Span; -import p.studio.lsp.dtos.LspHighlightSpanDTO; -import p.studio.lsp.dtos.LspRangeDTO; -import p.studio.lsp.dtos.LspStructuralAnchorDTO; -import p.studio.lsp.dtos.LspSymbolDTO; -import p.studio.lsp.messages.LspSymbolKind; - -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import static p.studio.lsp.LspSemanticUtilities.normalize; - -public final class SemanticIndex { - private final Map> semanticHighlightsByDocument = new LinkedHashMap<>(); - private final Map> documentSymbolsByDocument = new LinkedHashMap<>(); - private final Map> structuralAnchorsByDocument = new LinkedHashMap<>(); - private final List workspaceSymbols = new ArrayList<>(); - private final Map> symbolsByName = new LinkedHashMap<>(); - private final Map> tokensByDocument = new LinkedHashMap<>(); - - public void index( - final Path documentPath, - final PbsAst.File ast, - final List tokens) { - index(documentPath, ast, tokens, List.of()); - } - - public void index( - final Path documentPath, - final PbsAst.File ast, - final List tokens, - final List visibleImportedTopDecls) { - registerDocument(documentPath, ast, tokens); - buildHighlights(documentPath, visibleImportedTopDecls); - } - - public void registerDocument( - final Path documentPath, - final PbsAst.File ast, - final List tokens) { - final Path normalizedDocumentPath = normalize(documentPath); - tokensByDocument.put(normalizedDocumentPath, List.copyOf(tokens)); - final List documentSymbols = new ArrayList<>(); - for (final PbsAst.TopDecl topDecl : ast.topDecls()) { - final LspSymbolDTO symbol = symbolForTopDecl(normalizedDocumentPath, topDecl); - if (symbol == null) { - continue; - } - documentSymbols.add(symbol); - workspaceSymbols.add(symbol); - symbolsByName.computeIfAbsent(symbol.name(), ignored -> new ArrayList<>()).add(symbol); - for (final LspSymbolDTO child : symbol.children()) { - symbolsByName.computeIfAbsent(child.name(), ignored -> new ArrayList<>()).add(child); - } - } - documentSymbolsByDocument.put(normalizedDocumentPath, List.copyOf(documentSymbols)); - structuralAnchorsByDocument.put( - normalizedDocumentPath, - structuralAnchors(documentSymbols, tokens)); - } - - public void buildHighlights( - final Path documentPath, - final List visibleImportedTopDecls) { - final Path normalizedDocumentPath = normalize(documentPath); - final List tokens = tokensByDocument.getOrDefault(normalizedDocumentPath, List.of()); - final Map visibleKindsByName = new LinkedHashMap<>(); - for (final var entry : symbolsByName.entrySet()) { - if (entry.getValue().isEmpty()) { - continue; - } - visibleKindsByName.putIfAbsent(entry.getKey(), entry.getValue().getFirst().kind()); - } - for (final LspSymbolDTO symbol : documentSymbolsByDocument.getOrDefault(normalizedDocumentPath, List.of())) { - visibleKindsByName.putIfAbsent(symbol.name(), symbol.kind()); - for (final LspSymbolDTO child : symbol.children()) { - visibleKindsByName.putIfAbsent(child.name(), child.kind()); - } - } - for (final PbsAst.TopDecl topDecl : visibleImportedTopDecls) { - final LspSymbolKind visibleKind = symbolKindForTopDecl(topDecl); - final String visibleName = topDeclName(topDecl); - if (visibleKind == null || visibleName == null || visibleName.isBlank()) { - continue; - } - visibleKindsByName.putIfAbsent(visibleName, visibleKind); - } - semanticHighlightsByDocument.put( - normalizedDocumentPath, - buildSemanticHighlights(tokens, visibleKindsByName)); - } - - private List buildSemanticHighlights( - final List tokens, - final Map visibleKindsByName) { - final List highlights = new ArrayList<>(); - for (int index = 0; index < tokens.size(); index++) { - final PbsToken token = tokens.get(index); - if (token.kind() == PbsTokenKind.EOF) { - continue; - } - final String semanticKey = semanticKey(tokens, index, visibleKindsByName); - if (semanticKey == null || semanticKey.isBlank()) { - continue; - } - highlights.add(new LspHighlightSpanDTO( - new LspRangeDTO(token.start(), token.end()), - semanticKey)); - } - return List.copyOf(highlights); - } - - private String semanticKey( - final List tokens, - final int tokenIndex, - final Map visibleKindsByName) { - final PbsToken token = tokens.get(tokenIndex); - if (isLifecycleAttributeToken(tokens, tokenIndex)) { - return PbsSemanticKind.LIFECYCLE.semanticKey(); - } - final PbsSemanticKind semanticKind = token.kind() == p.studio.compiler.pbs.lexer.PbsTokenKind.IDENTIFIER - ? semanticKindForIdentifier(token.lexeme(), visibleKindsByName) - : PbsSemanticKind.forToken(token); - return semanticKind == null ? null : semanticKind.semanticKey(); - } - - private boolean isLifecycleAttributeToken(final List tokens, final int tokenIndex) { - if (tokenIndex < 0 || tokenIndex >= tokens.size()) { - return false; - } - final PbsToken token = tokens.get(tokenIndex); - return switch (token.kind()) { - case LEFT_BRACKET -> hasLifecycleAttributeWindow(tokens, tokenIndex, tokenIndex + 1, tokenIndex + 2); - case IDENTIFIER -> hasLifecycleAttributeWindow(tokens, tokenIndex - 1, tokenIndex, tokenIndex + 1); - case RIGHT_BRACKET -> hasLifecycleAttributeWindow(tokens, tokenIndex - 2, tokenIndex - 1, tokenIndex); - default -> false; - }; - } - - private boolean hasLifecycleAttributeWindow( - final List tokens, - final int leftBracketIndex, - final int identifierIndex, - final int rightBracketIndex) { - if (leftBracketIndex < 0 || rightBracketIndex >= tokens.size()) { - return false; - } - return tokens.get(leftBracketIndex).kind() == PbsTokenKind.LEFT_BRACKET - && tokens.get(identifierIndex).kind() == PbsTokenKind.IDENTIFIER - && isLifecycleAttributeName(tokens.get(identifierIndex).lexeme()) - && tokens.get(rightBracketIndex).kind() == PbsTokenKind.RIGHT_BRACKET; - } - - private boolean isLifecycleAttributeName(final String lexeme) { - return "Init".equals(lexeme) || "Frame".equals(lexeme); - } - - private PbsSemanticKind semanticKindForIdentifier( - final String lexeme, - final Map visibleKindsByName) { - final LspSymbolKind kind = visibleKindsByName.get(lexeme); - if (kind == null) { - return PbsSemanticKind.IDENTIFIER; - } - return switch (kind) { - case FUNCTION -> PbsSemanticKind.FUNCTION; - case METHOD -> PbsSemanticKind.METHOD; - case CONSTRUCTOR -> PbsSemanticKind.CONSTRUCTOR; - case STRUCT -> PbsSemanticKind.STRUCT; - case CONTRACT -> PbsSemanticKind.CONTRACT; - case HOST -> PbsSemanticKind.HOST; - case BUILTIN_TYPE -> PbsSemanticKind.BUILTIN_TYPE; - case SERVICE -> PbsSemanticKind.SERVICE; - case ERROR -> PbsSemanticKind.ERROR; - case ENUM -> PbsSemanticKind.ENUM; - case CALLBACK -> PbsSemanticKind.CALLBACK; - case GLOBAL -> PbsSemanticKind.GLOBAL; - case CONST -> PbsSemanticKind.CONST; - case IMPLEMENTS -> PbsSemanticKind.IMPLEMENTS; - default -> PbsSemanticKind.IDENTIFIER; - }; - } - - private String topDeclName(final PbsAst.TopDecl topDecl) { - if (topDecl instanceof PbsAst.FunctionDecl functionDecl) { - return functionDecl.name(); - } - if (topDecl instanceof PbsAst.StructDecl structDecl) { - return structDecl.name(); - } - if (topDecl instanceof PbsAst.ContractDecl contractDecl) { - return contractDecl.name(); - } - if (topDecl instanceof PbsAst.HostDecl hostDecl) { - return hostDecl.name(); - } - if (topDecl instanceof PbsAst.BuiltinTypeDecl builtinTypeDecl) { - return builtinTypeDecl.name(); - } - if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) { - return serviceDecl.name(); - } - if (topDecl instanceof PbsAst.ErrorDecl errorDecl) { - return errorDecl.name(); - } - if (topDecl instanceof PbsAst.EnumDecl enumDecl) { - return enumDecl.name(); - } - if (topDecl instanceof PbsAst.CallbackDecl callbackDecl) { - return callbackDecl.name(); - } - if (topDecl instanceof PbsAst.GlobalDecl globalDecl) { - return globalDecl.name(); - } - if (topDecl instanceof PbsAst.ConstDecl constDecl) { - return constDecl.name(); - } - if (topDecl instanceof PbsAst.ImplementsDecl implementsDecl) { - return implementsDecl.binderName(); - } - return null; - } - - private LspSymbolKind symbolKindForTopDecl(final PbsAst.TopDecl topDecl) { - if (topDecl instanceof PbsAst.FunctionDecl) { - return LspSymbolKind.FUNCTION; - } - if (topDecl instanceof PbsAst.StructDecl) { - return LspSymbolKind.STRUCT; - } - if (topDecl instanceof PbsAst.ContractDecl) { - return LspSymbolKind.CONTRACT; - } - if (topDecl instanceof PbsAst.HostDecl) { - return LspSymbolKind.HOST; - } - if (topDecl instanceof PbsAst.BuiltinTypeDecl) { - return LspSymbolKind.BUILTIN_TYPE; - } - if (topDecl instanceof PbsAst.ServiceDecl) { - return LspSymbolKind.SERVICE; - } - if (topDecl instanceof PbsAst.ErrorDecl) { - return LspSymbolKind.ERROR; - } - if (topDecl instanceof PbsAst.EnumDecl) { - return LspSymbolKind.ENUM; - } - if (topDecl instanceof PbsAst.CallbackDecl) { - return LspSymbolKind.CALLBACK; - } - if (topDecl instanceof PbsAst.GlobalDecl) { - return LspSymbolKind.GLOBAL; - } - if (topDecl instanceof PbsAst.ConstDecl) { - return LspSymbolKind.CONST; - } - if (topDecl instanceof PbsAst.ImplementsDecl) { - return LspSymbolKind.IMPLEMENTS; - } - return null; - } - - private LspSymbolDTO symbolForTopDecl( - final Path documentPath, - final PbsAst.TopDecl topDecl) { - if (topDecl instanceof PbsAst.FunctionDecl functionDecl) { - return symbol( - documentPath, - functionDecl.name(), - LspSymbolKind.FUNCTION, - functionDecl.span(), - localSymbolsInBlock(documentPath, functionDecl.body())); - } - if (topDecl instanceof PbsAst.StructDecl structDecl) { - return symbol(documentPath, structDecl.name(), LspSymbolKind.STRUCT, structDecl.span(), structChildren(documentPath, structDecl)); - } - if (topDecl instanceof PbsAst.ContractDecl( - String name, p.studio.utilities.structures.ReadOnlyList signatures, Span span - )) { - return symbol(documentPath, name, LspSymbolKind.CONTRACT, span, signatureChildren(documentPath, signatures.asList())); - } - if (topDecl instanceof PbsAst.HostDecl( - String name, p.studio.utilities.structures.ReadOnlyList signatures, Span span - )) { - return symbol(documentPath, name, LspSymbolKind.HOST, span, signatureChildren(documentPath, signatures.asList())); - } - if (topDecl instanceof PbsAst.BuiltinTypeDecl builtinTypeDecl) { - return symbol(documentPath, builtinTypeDecl.name(), LspSymbolKind.BUILTIN_TYPE, builtinTypeDecl.span(), signatureChildren(documentPath, builtinTypeDecl.signatures().asList())); - } - if (topDecl instanceof PbsAst.ServiceDecl( - String name, p.studio.utilities.structures.ReadOnlyList methods, Span span - )) { - return symbol(documentPath, name, LspSymbolKind.SERVICE, span, functionChildren(documentPath, methods.asList())); - } - if (topDecl instanceof PbsAst.ErrorDecl errorDecl) { - return symbol(documentPath, errorDecl.name(), LspSymbolKind.ERROR, errorDecl.span(), List.of()); - } - if (topDecl instanceof PbsAst.EnumDecl enumDecl) { - return symbol(documentPath, enumDecl.name(), LspSymbolKind.ENUM, enumDecl.span(), List.of()); - } - if (topDecl instanceof PbsAst.CallbackDecl callbackDecl) { - return symbol(documentPath, callbackDecl.name(), LspSymbolKind.CALLBACK, callbackDecl.span(), List.of()); - } - if (topDecl instanceof PbsAst.GlobalDecl globalDecl) { - return symbol(documentPath, globalDecl.name(), LspSymbolKind.GLOBAL, globalDecl.span(), List.of()); - } - if (topDecl instanceof PbsAst.ConstDecl constDecl) { - return symbol(documentPath, constDecl.name(), LspSymbolKind.CONST, constDecl.span(), List.of()); - } - if (topDecl instanceof PbsAst.ImplementsDecl implementsDecl) { - return symbol(documentPath, implementsDecl.binderName(), LspSymbolKind.IMPLEMENTS, implementsDecl.span(), functionChildren(documentPath, implementsDecl.methods().asList())); - } - return null; - } - - private List structChildren( - final Path documentPath, - final PbsAst.StructDecl structDecl) { - final List children = new ArrayList<>(functionChildren(documentPath, structDecl.methods().asList())); - for (final PbsAst.CtorDecl ctorDecl : structDecl.ctors()) { - children.add(symbol( - documentPath, - ctorDecl.name(), - LspSymbolKind.CONSTRUCTOR, - ctorDecl.span(), - localSymbolsInBlock(documentPath, ctorDecl.body()))); - } - return List.copyOf(children); - } - - private List functionChildren( - final Path documentPath, - final List functions) { - final List children = new ArrayList<>(); - for (final PbsAst.FunctionDecl functionDecl : functions) { - children.add(symbol( - documentPath, - functionDecl.name(), - LspSymbolKind.METHOD, - functionDecl.span(), - localSymbolsInBlock(documentPath, functionDecl.body()))); - } - return List.copyOf(children); - } - - private List localSymbolsInBlock( - final Path documentPath, - final PbsAst.Block block) { - final List children = new ArrayList<>(); - for (final PbsAst.Statement statement : block.statements().asList()) { - children.addAll(localSymbolsInStatement(documentPath, statement)); - } - if (block.tailExpression() != null) { - children.addAll(localSymbolsInExpression(documentPath, block.tailExpression())); - } - return List.copyOf(children); - } - - private List localSymbolsInStatement( - final Path documentPath, - final PbsAst.Statement statement) { - return switch (statement) { - case PbsAst.LetStatement letStatement -> localSymbolsInExpression(documentPath, letStatement.initializer()); - case PbsAst.AssignStatement assignStatement -> localSymbolsInExpression(documentPath, assignStatement.value()); - case PbsAst.ReturnStatement returnStatement -> localSymbolsInNullableExpression(documentPath, returnStatement.value()); - case PbsAst.IfStatement ifStatement -> List.of(symbol( - documentPath, - "if", - LspSymbolKind.IF, - ifStatement.span(), - children( - localSymbolsInBlock(documentPath, ifStatement.thenBlock()), - ifStatement.elseIf() == null ? List.of() : localSymbolsInStatement(documentPath, ifStatement.elseIf()), - ifStatement.elseBlock() == null ? List.of() : localSymbolsInBlock(documentPath, ifStatement.elseBlock())))); - case PbsAst.ForStatement forStatement -> List.of(symbol( - documentPath, - "for " + forStatement.iteratorName(), - LspSymbolKind.FOR, - forStatement.span(), - localSymbolsInBlock(documentPath, forStatement.body()))); - case PbsAst.WhileStatement whileStatement -> List.of(symbol( - documentPath, - "while", - LspSymbolKind.WHILE, - whileStatement.span(), - localSymbolsInBlock(documentPath, whileStatement.body()))); - case PbsAst.ExpressionStatement expressionStatement -> localSymbolsInExpression(documentPath, expressionStatement.expression()); - case PbsAst.BreakStatement ignored -> List.of(); - case PbsAst.ContinueStatement ignored -> List.of(); - }; - } - - private List localSymbolsInExpression( - final Path documentPath, - final PbsAst.Expression expression) { - return switch (expression) { - case PbsAst.IfExpr ifExpr -> List.of(symbol( - documentPath, - "if", - LspSymbolKind.IF, - ifExpr.span(), - children( - localSymbolsInBlock(documentPath, ifExpr.thenBlock()), - localSymbolsInExpression(documentPath, ifExpr.elseExpression())))); - case PbsAst.SwitchExpr switchExpr -> List.of(symbol( - documentPath, - "switch", - LspSymbolKind.SWITCH, - switchExpr.span(), - childrenFromSwitchArms(documentPath, switchExpr.arms().asList()))); - case PbsAst.HandleExpr handleExpr -> List.of(symbol( - documentPath, - "handle", - LspSymbolKind.HANDLE, - handleExpr.span(), - childrenFromHandleArms(documentPath, handleExpr.arms().asList()))); - case PbsAst.BlockExpr blockExpr -> localSymbolsInBlock(documentPath, blockExpr.block()); - case PbsAst.ElseExpr elseExpr -> children( - localSymbolsInExpression(documentPath, elseExpr.optionalExpression()), - localSymbolsInExpression(documentPath, elseExpr.fallbackExpression())); - case PbsAst.UnaryExpr unaryExpr -> localSymbolsInExpression(documentPath, unaryExpr.expression()); - case PbsAst.BinaryExpr binaryExpr -> children( - localSymbolsInExpression(documentPath, binaryExpr.left()), - localSymbolsInExpression(documentPath, binaryExpr.right())); - case PbsAst.ApplyExpr applyExpr -> children( - localSymbolsInExpression(documentPath, applyExpr.callee()), - localSymbolsInExpression(documentPath, applyExpr.argument())); - case PbsAst.AsExpr asExpr -> localSymbolsInExpression(documentPath, asExpr.expression()); - case PbsAst.CallExpr callExpr -> { - final List> nested = new ArrayList<>(); - nested.add(localSymbolsInExpression(documentPath, callExpr.callee())); - for (final PbsAst.Expression argument : callExpr.arguments().asList()) { - nested.add(localSymbolsInExpression(documentPath, argument)); - } - yield children(nested.toArray(List[]::new)); - } - case PbsAst.MemberExpr memberExpr -> localSymbolsInExpression(documentPath, memberExpr.receiver()); - case PbsAst.PropagateExpr propagateExpr -> localSymbolsInExpression(documentPath, propagateExpr.expression()); - case PbsAst.GroupExpr groupExpr -> localSymbolsInExpression(documentPath, groupExpr.expression()); - case PbsAst.NewExpr newExpr -> { - final List> nested = new ArrayList<>(); - for (final PbsAst.Expression argument : newExpr.arguments().asList()) { - nested.add(localSymbolsInExpression(documentPath, argument)); - } - yield children(nested.toArray(List[]::new)); - } - case PbsAst.BindExpr bindExpr -> localSymbolsInExpression(documentPath, bindExpr.contextExpression()); - case PbsAst.SomeExpr someExpr -> localSymbolsInExpression(documentPath, someExpr.value()); - case PbsAst.OkExpr okExpr -> localSymbolsInExpression(documentPath, okExpr.value()); - case PbsAst.TupleExpr tupleExpr -> { - final List> nested = new ArrayList<>(); - for (final PbsAst.TupleItem item : tupleExpr.items().asList()) { - nested.add(localSymbolsInExpression(documentPath, item.expression())); - } - yield children(nested.toArray(List[]::new)); - } - case PbsAst.IdentifierExpr ignored -> List.of(); - case PbsAst.IntLiteralExpr ignored -> List.of(); - case PbsAst.FloatLiteralExpr ignored -> List.of(); - case PbsAst.BoundedLiteralExpr ignored -> List.of(); - case PbsAst.StringLiteralExpr ignored -> List.of(); - case PbsAst.BoolLiteralExpr ignored -> List.of(); - case PbsAst.ThisExpr ignored -> List.of(); - case PbsAst.NoneExpr ignored -> List.of(); - case PbsAst.ErrExpr ignored -> List.of(); - case PbsAst.UnitExpr ignored -> List.of(); - }; - } - - private List childrenFromSwitchArms( - final Path documentPath, - final List arms) { - final List children = new ArrayList<>(); - for (final PbsAst.SwitchArm arm : arms) { - children.addAll(localSymbolsInBlock(documentPath, arm.block())); - } - return List.copyOf(children); - } - - private List childrenFromHandleArms( - final Path documentPath, - final List arms) { - final List children = new ArrayList<>(); - for (final PbsAst.HandleArm arm : arms) { - children.addAll(localSymbolsInBlock(documentPath, arm.block())); - } - return List.copyOf(children); - } - - private List localSymbolsInNullableExpression( - final Path documentPath, - final PbsAst.Expression expression) { - return expression == null ? List.of() : localSymbolsInExpression(documentPath, expression); - } - - @SafeVarargs - private static List children(final List... parts) { - final List children = new ArrayList<>(); - for (final List part : parts) { - if (part == null || part.isEmpty()) { - continue; - } - children.addAll(part); - } - return List.copyOf(children); - } - - private List signatureChildren( - final Path documentPath, - final List signatures) { - final List children = new ArrayList<>(); - for (final PbsAst.FunctionSignature signature : signatures) { - children.add(symbol(documentPath, signature.name(), LspSymbolKind.METHOD, signature.span(), List.of())); - } - return List.copyOf(children); - } - - private LspSymbolDTO symbol( - final Path documentPath, - final String name, - final LspSymbolKind kind, - final Span span, - final List children) { - return new LspSymbolDTO( - Objects.requireNonNullElse(name, kind.name().toLowerCase()), - kind, - documentPath, - new LspRangeDTO((int) span.getStart(), (int) span.getEnd()), - children); - } - - private List structuralAnchors( - final List documentSymbols, - final List tokens) { - final List anchors = new ArrayList<>(); - for (final LspSymbolDTO symbol : documentSymbols) { - final LspStructuralAnchorDTO anchor = structuralAnchor(symbol, tokens); - if (anchor != null) { - anchors.add(anchor); - } - } - return List.copyOf(anchors); - } - - private LspStructuralAnchorDTO structuralAnchor( - final LspSymbolDTO symbol, - final List tokens) { - final List children = new ArrayList<>(); - for (final LspSymbolDTO child : symbol.children()) { - final LspStructuralAnchorDTO anchorChild = structuralAnchor(child, tokens); - if (anchorChild != null) { - children.add(anchorChild); - } - } - final AnchorPair anchorPair = anchorPair(symbol.range(), tokens); - if (anchorPair == null) { - if (children.isEmpty()) { - return null; - } - return new LspStructuralAnchorDTO(symbol.range(), symbol.range(), symbol.range(), children); - } - return new LspStructuralAnchorDTO(symbol.range(), anchorPair.start(), anchorPair.end(), children); - } - - private AnchorPair anchorPair( - final LspRangeDTO range, - final List tokens) { - PbsToken firstLeftBrace = null; - PbsToken lastRightBrace = null; - for (final PbsToken token : tokens) { - if (token.kind() == PbsTokenKind.EOF) { - continue; - } - if (token.start() < range.startOffset() || token.end() > range.endOffset()) { - continue; - } - if (token.kind() == PbsTokenKind.LEFT_BRACE && firstLeftBrace == null) { - firstLeftBrace = token; - } - if (token.kind() == PbsTokenKind.RIGHT_BRACE) { - lastRightBrace = token; - } - } - if (firstLeftBrace == null || lastRightBrace == null || lastRightBrace.start() < firstLeftBrace.start()) { - return null; - } - return new AnchorPair( - new LspRangeDTO(firstLeftBrace.start(), firstLeftBrace.end()), - new LspRangeDTO(lastRightBrace.start(), lastRightBrace.end())); - } - - public Map> semanticHighlightsByDocument() { - return Map.copyOf(semanticHighlightsByDocument); - } - - public Map> documentSymbolsByDocument() { - return Map.copyOf(documentSymbolsByDocument); - } - - public Map> structuralAnchorsByDocument() { - return Map.copyOf(structuralAnchorsByDocument); - } - - public List workspaceSymbols() { - return List.copyOf(workspaceSymbols); - } - - public Map> symbolsByName() { - final Map> frozen = new LinkedHashMap<>(); - for (final var entry : symbolsByName.entrySet()) { - frozen.put(entry.getKey(), List.copyOf(entry.getValue())); - } - return Map.copyOf(frozen); - } - - public Map> tokensByDocument() { - return Map.copyOf(tokensByDocument); - } - - private record AnchorPair( - LspRangeDTO start, - LspRangeDTO end) { - } -} diff --git a/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/LspServiceImplTest.java b/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/LspServiceImplTest.java deleted file mode 100644 index 69836c6b..00000000 --- a/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/LspServiceImplTest.java +++ /dev/null @@ -1,427 +0,0 @@ -package p.studio.lsp; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import p.studio.lsp.dtos.LspStructuralAnchorDTO; -import p.studio.lsp.messages.LspProjectContext; -import p.studio.lsp.dtos.LspDefinitionTargetDTO; -import p.studio.lsp.messages.LspAnalyzeDocumentRequest; -import p.studio.lsp.messages.LspDefinitionRequest; -import p.studio.vfs.*; -import p.studio.vfs.messages.*; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -final class LspServiceImplTest { - @TempDir - Path tempDir; - - private static final String OVERLAY_SOURCE = """ - fn helper_call() -> void - { - if true { - helper(); - } - while false { - helper(); - } - let value = switch true { - true: { 1 }, - default: { 2 } - }; - let recovered = handle helper_result() { - _: { - ok(0) - } - }; - } - """; - - private static final String SERVICE_SOURCE = """ - declare service Game { - fn tick(x: int) -> int { return x; } - } - - fn main() -> void { - Game.tick(1); - } - """; - - private static final String SDK_IMPORT_SOURCE = """ - import { Composer } from @sdk:composer; - - fn main() -> void { - Composer.emit_sprite(1, 0, 0, 0, 0, 0, false, false, 0); - } - """; - - private static final String PROJECT_IMPORT_SOURCE = """ - import { Color } from @app:graphics; - - fn main(color: Color) -> Color { - return color; - } - """; - - @Test - void analyzeDocumentUsesVfsOverlayForRequestedDocumentAndFilesystemFallbackForClosedFiles() throws Exception { - final Path projectRoot = createProject(); - final Path mainFile = projectRoot.resolve("src/main.pbs"); - final Path helperFile = projectRoot.resolve("src/helper.pbs"); - Files.writeString(mainFile, "fn broken( -> void {}\n"); - Files.writeString(helperFile, "fn helper() -> void {}\n"); - - final VfsProjectDocument delegate = new FilesystemProjectDocumentVfsFactory().open(projectContext(projectRoot)); - final LspService service = new LspServiceImpl( - new LspProjectContext("Example", "pbs", projectRoot), - new OverlayVfsProjectDocument(delegate, mainFile, OVERLAY_SOURCE)); - - final var analysis = service.analyzeDocument(new LspAnalyzeDocumentRequest(mainFile)); - - assertEquals("pbs-function", semanticKeyForLexeme(analysis, OVERLAY_SOURCE, "helper_call")); - assertEquals("pbs-function", semanticKeyForLexeme(analysis, OVERLAY_SOURCE, "helper")); - assertEquals(List.of("/themes/pbs/semantic-highlighting.css"), analysis.semanticPresentation().resources()); - assertTrue(analysis.semanticPresentation().semanticKeys().contains("pbs-function")); - assertFalse(analysis.inlineHints().isEmpty(), analysis.toString()); - - assertTrue( - analysis.documentSymbols().stream().anyMatch(symbol -> symbol.name().equals("helper_call")), - analysis.toString()); - assertTrue( - flatten(analysis.documentSymbols()).stream().anyMatch(symbol -> symbol.kind() == p.studio.lsp.messages.LspSymbolKind.IF), - analysis.toString()); - assertTrue( - flatten(analysis.documentSymbols()).stream().anyMatch(symbol -> symbol.kind() == p.studio.lsp.messages.LspSymbolKind.WHILE), - analysis.toString()); - assertTrue( - flatten(analysis.documentSymbols()).stream().anyMatch(symbol -> symbol.kind() == p.studio.lsp.messages.LspSymbolKind.SWITCH), - analysis.toString()); - assertTrue( - flatten(analysis.documentSymbols()).stream().anyMatch(symbol -> symbol.kind() == p.studio.lsp.messages.LspSymbolKind.HANDLE), - analysis.toString()); - assertFalse(analysis.structuralAnchors().isEmpty(), analysis.toString()); - assertEquals(OVERLAY_SOURCE.indexOf("{"), analysis.structuralAnchors().getFirst().startAnchor().startOffset()); - assertTrue( - flattenAnchors(analysis.structuralAnchors()).stream().anyMatch(anchor -> - anchor.startAnchor().startOffset() == OVERLAY_SOURCE.indexOf("if true {") + "if true ".length()), - analysis.toString()); - assertTrue( - analysis.workspaceSymbols().stream().anyMatch(symbol -> - symbol.name().equals("helper") && symbol.documentPath().equals(normalize(helperFile))), - analysis.toString()); - - final int offset = OVERLAY_SOURCE.indexOf("helper();"); - final var definition = service.definition(new LspDefinitionRequest(mainFile, offset)); - final List targets = definition.targets(); - - assertEquals(2, targets.size()); - assertEquals(normalize(helperFile), targets.getFirst().documentPath()); - assertEquals("helper", targets.getFirst().name()); - } - - @Test - void analyzeDocumentSurfacesDiagnosticsWithoutAbortingSemanticRead() throws Exception { - final Path projectRoot = createProject(); - final Path mainFile = projectRoot.resolve("src/main.pbs"); - Files.writeString(mainFile, """ - fn main( -> void - { - helper(); - } - """); - - final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext(projectRoot)); - final LspService service = new LspServiceImpl( - new LspProjectContext("Example", "pbs", projectRoot), - vfs); - - final var analysis = service.analyzeDocument(new LspAnalyzeDocumentRequest(mainFile)); - - assertFalse(analysis.diagnostics().isEmpty(), analysis.toString()); - assertTrue(analysis.diagnostics().stream().allMatch(diagnostic -> - diagnostic.documentPath().equals(normalize(mainFile)))); - } - - @Test - void analyzeDocumentHighlightsServiceIdentifiersWithServiceSemanticKey() throws Exception { - final Path projectRoot = createProject(); - final Path mainFile = projectRoot.resolve("src/main.pbs"); - Files.writeString(mainFile, SERVICE_SOURCE); - - final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext(projectRoot)); - final LspService service = new LspServiceImpl( - new LspProjectContext("Example", "pbs", projectRoot), - vfs); - - final var analysis = service.analyzeDocument(new LspAnalyzeDocumentRequest(mainFile)); - - assertEquals("pbs-service", semanticKeyForLexeme(analysis, SERVICE_SOURCE, "Game")); - assertTrue(flatten(analysis.documentSymbols()).stream().anyMatch(symbol -> - symbol.kind() == p.studio.lsp.messages.LspSymbolKind.SERVICE && symbol.name().equals("Game"))); - } - - @Test - void analyzeDocumentHighlightsSdkImportedHostIdentifiersWithHostSemanticKey() throws Exception { - final Path projectRoot = createProject(); - final Path mainFile = projectRoot.resolve("src/main.pbs"); - Files.writeString(mainFile, SDK_IMPORT_SOURCE); - - final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext(projectRoot)); - final LspService service = new LspServiceImpl( - new LspProjectContext("Example", "pbs", projectRoot), - vfs); - - final var analysis = service.analyzeDocument(new LspAnalyzeDocumentRequest(mainFile)); - - assertEquals(List.of("pbs-service", "pbs-service"), semanticKeysForLexeme(analysis, SDK_IMPORT_SOURCE, "Composer")); - } - - @Test - void analyzeDocumentHighlightsProjectImportedStructIdentifiersWithStructSemanticKey() throws Exception { - final Path projectRoot = createProject(); - final Path mainFile = projectRoot.resolve("src/main.pbs"); - final Path graphicsDir = projectRoot.resolve("src/graphics"); - final Path graphicsFile = graphicsDir.resolve("types.pbs"); - final Path graphicsBarrel = graphicsDir.resolve("mod.barrel"); - Files.createDirectories(graphicsDir); - Files.writeString(mainFile, PROJECT_IMPORT_SOURCE); - Files.writeString(graphicsFile, "declare struct Color(raw: int);\n"); - Files.writeString(graphicsBarrel, "pub struct Color;\n"); - - final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext(projectRoot)); - final LspService service = new LspServiceImpl( - new LspProjectContext("Example", "pbs", projectRoot), - vfs); - - final var analysis = service.analyzeDocument(new LspAnalyzeDocumentRequest(mainFile)); - - assertEquals(List.of("pbs-struct", "pbs-struct", "pbs-struct"), semanticKeysForLexeme(analysis, PROJECT_IMPORT_SOURCE, "Color")); - } - - @Test - void analyzeDocumentReturnsDedicatedInlineHintTransportSurface() throws Exception { - final Path projectRoot = createProject(); - final Path mainFile = projectRoot.resolve("src/main.pbs"); - final String source = "fn main() -> void { let value = 1; }\n"; - Files.writeString(mainFile, source); - - final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext(projectRoot)); - final LspService service = new LspServiceImpl( - new LspProjectContext("Example", "pbs", projectRoot), - vfs); - - final var analysis = service.analyzeDocument(new LspAnalyzeDocumentRequest(mainFile)); - - assertEquals(1, analysis.inlineHints().size(), analysis.toString()); - assertEquals("int", analysis.inlineHints().getFirst().label()); - assertEquals("type", analysis.inlineHints().getFirst().category()); - assertEquals("value", sourceSlice(source, analysis.inlineHints().getFirst().anchor())); - } - - @Test - void analyzeDocumentPreservesValidInlineHintsUnderPartialDegradation() throws Exception { - final Path projectRoot = createProject(); - final Path mainFile = projectRoot.resolve("src/main.pbs"); - final String source = """ - fn ok() -> void { - let value = 1; - return; - } - - fn broken( -> void { - let missing = 2; - } - """; - Files.writeString(mainFile, source); - - final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext(projectRoot)); - final LspService service = new LspServiceImpl( - new LspProjectContext("Example", "pbs", projectRoot), - vfs); - - final var analysis = service.analyzeDocument(new LspAnalyzeDocumentRequest(mainFile)); - - assertFalse(analysis.diagnostics().isEmpty(), analysis.toString()); - assertEquals(1, analysis.inlineHints().size(), analysis.toString()); - assertEquals("int", analysis.inlineHints().getFirst().label()); - assertEquals("value", sourceSlice(source, analysis.inlineHints().getFirst().anchor())); - } - - private Path createProject() throws Exception { - final Path src = Files.createDirectories(tempDir.resolve("src")); - Files.writeString(tempDir.resolve("prometeu.json"), """ - { - "name": "Example", - "version": "1.0.0", - "language": "pbs", - "stdlib": "1", - "dependencies": [] - } - """); - Files.writeString(src.resolve("mod.barrel"), """ - pub fn helper() -> void; - pub fn helper_result() -> result int; - pub error MyError; - pub service Log; - """); - Files.writeString(src.resolve("helper.pbs"), "fn helper() -> void {}\n"); - Files.writeString(src.resolve("helper_result.pbs"), """ - declare error MyError { - Failed - } - - fn helper_result() -> result int { - return ok(1); - } - """); - Files.writeString(src.resolve("log.pbs"), """ - declare service Log { - fn failure(message: string) -> void; - } - """); - return tempDir; - } - - private static List flatten( - final List symbols) { - final List flattened = new java.util.ArrayList<>(); - for (final var symbol : symbols) { - flattened.add(symbol); - flattened.addAll(flatten(symbol.children())); - } - return flattened; - } - - private static List flattenAnchors( - final List anchors) { - final List flattened = new java.util.ArrayList<>(); - for (final var anchor : anchors) { - flattened.add(anchor); - flattened.addAll(flattenAnchors(anchor.children())); - } - return flattened; - } - - private VfsProjectContext projectContext(final Path projectRoot) { - return new VfsProjectContext("Example", "pbs", projectRoot); - } - - private static Path normalize(final Path path) { - try { - return path.toAbsolutePath().normalize().toRealPath(); - } catch (Exception exception) { - throw new IllegalStateException(exception); - } - } - - private static String semanticKeyForLexeme( - final p.studio.lsp.messages.LspAnalyzeDocumentResult analysis, - final String source, - final String lexeme) { - return semanticKeysForLexeme(analysis, source, lexeme).stream().findFirst().orElseThrow(); - } - - private static List semanticKeysForLexeme( - final p.studio.lsp.messages.LspAnalyzeDocumentResult analysis, - final String source, - final String lexeme) { - return analysis.semanticHighlights().stream() - .filter(highlight -> lexeme.equals(spanContent(source, highlight.range().startOffset(), highlight.range().endOffset()))) - .map(p.studio.lsp.dtos.LspHighlightSpanDTO::semanticKey) - .toList(); - } - - private static String spanContent( - final String source, - final int start, - final int end) { - if (start < 0 || end > source.length() || start >= end) { - return ""; - } - return source.substring(start, end); - } - - private static String sourceSlice( - final String source, - final p.studio.lsp.dtos.LspRangeDTO range) { - return spanContent(source, range.startOffset(), range.endOffset()); - } - - private static final class OverlayVfsProjectDocument implements VfsProjectDocument { - private final VfsProjectDocument delegate; - private final Path overlayPath; - private final String overlayContent; - - private OverlayVfsProjectDocument( - final VfsProjectDocument delegate, - final Path overlayPath, - final String overlayContent) { - this.delegate = delegate; - this.overlayPath = normalize(overlayPath); - this.overlayContent = overlayContent; - } - - @Override - public VfsProjectContext projectContext() { - return delegate.projectContext(); - } - - @Override - public VfsProjectSnapshot snapshot() { - return delegate.snapshot(); - } - - @Override - public VfsProjectSnapshot refresh() { - return delegate.refresh(); - } - - @Override - public VfsProjectSnapshot refresh(final VfsRefreshRequest request) { - return delegate.refresh(request); - } - - @Override - public VfsDocumentOpenResult openDocument(final Path path) { - final Path normalizedPath = normalize(path); - if (!overlayPath.equals(normalizedPath)) { - return delegate.openDocument(normalizedPath); - } - final VfsDocumentAccessContext accessContext = new VfsDocumentAccessContext( - normalizedPath, - "pbs", - true, - VfsDocumentAccessMode.READ_ONLY, - Map.of()); - return new VfsDocumentOpenResult.VfsTextDocument( - normalizedPath, - normalizedPath.getFileName().toString(), - "pbs", - overlayContent, - "LF", - false, - accessContext); - } - - @Override - public void close() { - delegate.close(); - } - - private static Path normalize(final Path path) { - final Path normalized = path.toAbsolutePath().normalize(); - try { - return Files.exists(normalized) ? normalized.toRealPath() : normalized; - } catch (Exception ignored) { - return normalized; - } - } - } -} diff --git a/prometeu-studio/build.gradle.kts b/prometeu-studio/build.gradle.kts index 5dadc75d..57bf98cc 100644 --- a/prometeu-studio/build.gradle.kts +++ b/prometeu-studio/build.gradle.kts @@ -5,8 +5,6 @@ plugins { dependencies { implementation(project(":prometeu-infra")) - implementation(project(":prometeu-lsp:prometeu-lsp-api")) - implementation(project(":prometeu-vfs:prometeu-vfs-api")) implementation(project(":prometeu-packer:prometeu-packer-api")) implementation(project(":prometeu-compiler:prometeu-compiler-core")) implementation(project(":prometeu-compiler:prometeu-build-pipeline")) diff --git a/prometeu-studio/src/main/java/p/studio/projects/ProjectReference.java b/prometeu-studio/src/main/java/p/studio/projects/ProjectReference.java index c0dbc8ce..20e7fb84 100644 --- a/prometeu-studio/src/main/java/p/studio/projects/ProjectReference.java +++ b/prometeu-studio/src/main/java/p/studio/projects/ProjectReference.java @@ -2,7 +2,6 @@ package p.studio.projects; import org.apache.commons.lang3.StringUtils; import p.packer.messages.PackerProjectContext; -import p.studio.vfs.messages.VfsProjectContext; import java.nio.file.Path; @@ -36,8 +35,4 @@ public record ProjectReference( public PackerProjectContext toPackerProjectContext() { return new PackerProjectContext(name, absoluteRootPath()); } - - public VfsProjectContext toVfsProjectContext() { - return new VfsProjectContext(name, languageId, absoluteRootPath()); - } } diff --git a/prometeu-vfs/prometeu-vfs-api/build.gradle.kts b/prometeu-vfs/prometeu-vfs-api/build.gradle.kts deleted file mode 100644 index a6775d6e..00000000 --- a/prometeu-vfs/prometeu-vfs-api/build.gradle.kts +++ /dev/null @@ -1,3 +0,0 @@ -plugins { - id("gradle.java-library-conventions") -} diff --git a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/ProjectDocumentVfsFactory.java b/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/ProjectDocumentVfsFactory.java deleted file mode 100644 index fe309951..00000000 --- a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/ProjectDocumentVfsFactory.java +++ /dev/null @@ -1,7 +0,0 @@ -package p.studio.vfs; - -import p.studio.vfs.messages.VfsProjectContext; - -public interface ProjectDocumentVfsFactory { - VfsProjectDocument open(VfsProjectContext projectContext); -} diff --git a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/VfsDocumentOpenResult.java b/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/VfsDocumentOpenResult.java deleted file mode 100644 index 4eee7b40..00000000 --- a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/VfsDocumentOpenResult.java +++ /dev/null @@ -1,40 +0,0 @@ -package p.studio.vfs; - -import p.studio.vfs.messages.VfsDocumentAccessContext; -import p.studio.vfs.messages.VfsUnsupportedReason; - -import java.nio.file.Path; -import java.util.Objects; - -public sealed interface VfsDocumentOpenResult permits VfsDocumentOpenResult.VfsUnsupportedDocument, VfsDocumentOpenResult.VfsTextDocument { - Path path(); - - record VfsUnsupportedDocument( - Path path, - VfsUnsupportedReason reason) implements VfsDocumentOpenResult { - - public VfsUnsupportedDocument { - path = Objects.requireNonNull(path, "path").toAbsolutePath().normalize(); - Objects.requireNonNull(reason, "reason"); - } - } - - record VfsTextDocument( - Path path, - String documentName, - String typeId, - String content, - String lineSeparator, - boolean dirty, - VfsDocumentAccessContext accessContext) implements VfsDocumentOpenResult { - - public VfsTextDocument { - path = Objects.requireNonNull(path, "path").toAbsolutePath().normalize(); - Objects.requireNonNull(documentName, "documentName"); - Objects.requireNonNull(typeId, "typeId"); - Objects.requireNonNull(content, "content"); - Objects.requireNonNull(lineSeparator, "lineSeparator"); - Objects.requireNonNull(accessContext, "accessContext"); - } - } -} diff --git a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/VfsProjectDocument.java b/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/VfsProjectDocument.java deleted file mode 100644 index 73c5fdbb..00000000 --- a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/VfsProjectDocument.java +++ /dev/null @@ -1,47 +0,0 @@ -package p.studio.vfs; - -import p.studio.vfs.messages.*; - -import java.nio.file.Path; -import java.util.List; -import java.util.Map; - -public interface VfsProjectDocument extends AutoCloseable { - VfsProjectContext projectContext(); - - VfsProjectSnapshot snapshot(); - - VfsProjectSnapshot refresh(); - - VfsProjectSnapshot refresh(VfsRefreshRequest request); - - VfsDocumentOpenResult openDocument(Path path); - - default VfsDocumentAccessContext accessContext(final Path path) { - throw new UnsupportedOperationException("Document access context is not supported by this VFS implementation."); - } - - default VfsDocumentAccessContext updateAccessContext(final Path path, final Map attributes) { - throw new UnsupportedOperationException("Document access context updates are not supported by this VFS implementation."); - } - - default VfsDocumentOpenResult.VfsTextDocument updateDocument(final Path path, final String content) { - throw new UnsupportedOperationException("Document mutation is not supported by this VFS implementation."); - } - - default VfsDocumentSaveResult saveDocument(final Path path) { - throw new UnsupportedOperationException("Document save is not supported by this VFS implementation."); - } - - default VfsDocumentOpenResult.VfsTextDocument discardDocument(final Path path) { - throw new UnsupportedOperationException("Document discard is not supported by this VFS implementation."); - } - - default List saveAllDocuments() { - return List.of(); - } - - @Override - default void close() { - } -} diff --git a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsDocumentAccessContext.java b/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsDocumentAccessContext.java deleted file mode 100644 index 40fb7cdb..00000000 --- a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsDocumentAccessContext.java +++ /dev/null @@ -1,20 +0,0 @@ -package p.studio.vfs.messages; - -import java.nio.file.Path; -import java.util.Map; -import java.util.Objects; - -public record VfsDocumentAccessContext( - Path path, - String typeId, - boolean frontendDocument, - VfsDocumentAccessMode accessMode, - Map attributes) { - - public VfsDocumentAccessContext { - path = Objects.requireNonNull(path, "path").toAbsolutePath().normalize(); - Objects.requireNonNull(typeId, "typeId"); - Objects.requireNonNull(accessMode, "accessMode"); - attributes = Map.copyOf(Objects.requireNonNull(attributes, "attributes")); - } -} diff --git a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsDocumentAccessMode.java b/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsDocumentAccessMode.java deleted file mode 100644 index 1ebbc417..00000000 --- a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsDocumentAccessMode.java +++ /dev/null @@ -1,6 +0,0 @@ -package p.studio.vfs.messages; - -public enum VfsDocumentAccessMode { - READ_ONLY, - EDITABLE -} diff --git a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsDocumentSaveResult.java b/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsDocumentSaveResult.java deleted file mode 100644 index 5ac04114..00000000 --- a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsDocumentSaveResult.java +++ /dev/null @@ -1,16 +0,0 @@ -package p.studio.vfs.messages; - -import java.nio.file.Path; -import java.util.Objects; - -public record VfsDocumentSaveResult( - Path path, - String typeId, - VfsDocumentSaveStatus status) { - - public VfsDocumentSaveResult { - path = Objects.requireNonNull(path, "path").toAbsolutePath().normalize(); - Objects.requireNonNull(typeId, "typeId"); - Objects.requireNonNull(status, "status"); - } -} diff --git a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsDocumentSaveStatus.java b/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsDocumentSaveStatus.java deleted file mode 100644 index 2a8ad4d4..00000000 --- a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsDocumentSaveStatus.java +++ /dev/null @@ -1,7 +0,0 @@ -package p.studio.vfs.messages; - -public enum VfsDocumentSaveStatus { - SAVED, - NO_CHANGES, - READ_ONLY -} diff --git a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsDocumentTypeIds.java b/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsDocumentTypeIds.java deleted file mode 100644 index f664d59a..00000000 --- a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsDocumentTypeIds.java +++ /dev/null @@ -1,11 +0,0 @@ -package p.studio.vfs.messages; - -public final class VfsDocumentTypeIds { - public static final String TEXT = "text"; - public static final String JSON = "json"; - public static final String NDJSON = "ndjson"; - public static final String BASH = "bash"; - - private VfsDocumentTypeIds() { - } -} diff --git a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsProjectContext.java b/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsProjectContext.java deleted file mode 100644 index 81ecd2fb..00000000 --- a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsProjectContext.java +++ /dev/null @@ -1,16 +0,0 @@ -package p.studio.vfs.messages; - -import java.nio.file.Path; -import java.util.Objects; - -public record VfsProjectContext( - String projectName, - String languageId, - Path rootPath) { - - public VfsProjectContext { - Objects.requireNonNull(projectName, "projectName"); - Objects.requireNonNull(languageId, "languageId"); - rootPath = Objects.requireNonNull(rootPath, "rootPath").toAbsolutePath().normalize(); - } -} diff --git a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsProjectNode.java b/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsProjectNode.java deleted file mode 100644 index 271bdadc..00000000 --- a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsProjectNode.java +++ /dev/null @@ -1,19 +0,0 @@ -package p.studio.vfs.messages; - -import java.nio.file.Path; -import java.util.List; -import java.util.Objects; - -public record VfsProjectNode( - Path path, - String displayName, - boolean directory, - boolean taggedSourceRoot, - List children) { - - public VfsProjectNode { - path = Objects.requireNonNull(path, "path").toAbsolutePath().normalize(); - Objects.requireNonNull(displayName, "displayName"); - children = List.copyOf(Objects.requireNonNull(children, "children")); - } -} diff --git a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsProjectSnapshot.java b/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsProjectSnapshot.java deleted file mode 100644 index 7995d13b..00000000 --- a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsProjectSnapshot.java +++ /dev/null @@ -1,14 +0,0 @@ -package p.studio.vfs.messages; - -import java.nio.file.Path; -import java.util.Objects; - -public record VfsProjectSnapshot( - Path projectRoot, - VfsProjectNode rootNode) { - - public VfsProjectSnapshot { - projectRoot = Objects.requireNonNull(projectRoot, "projectRoot").toAbsolutePath().normalize(); - Objects.requireNonNull(rootNode, "rootNode"); - } -} diff --git a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsRefreshRequest.java b/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsRefreshRequest.java deleted file mode 100644 index 955c0cf4..00000000 --- a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsRefreshRequest.java +++ /dev/null @@ -1,10 +0,0 @@ -package p.studio.vfs.messages; - -import java.nio.file.Path; -import java.util.Objects; - -public record VfsRefreshRequest(Path targetPath) { - public VfsRefreshRequest { - targetPath = Objects.requireNonNull(targetPath, "targetPath").toAbsolutePath().normalize(); - } -} diff --git a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsUnsupportedReason.java b/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsUnsupportedReason.java deleted file mode 100644 index beeee78a..00000000 --- a/prometeu-vfs/prometeu-vfs-api/src/main/java/p/studio/vfs/messages/VfsUnsupportedReason.java +++ /dev/null @@ -1,10 +0,0 @@ -package p.studio.vfs.messages; - -public enum VfsUnsupportedReason { - OUTSIDE_PROJECT, - NOT_FOUND, - NOT_A_FILE, - NO_HANDLER, - BINARY_CONTENT, - INVALID_UTF8 -} diff --git a/prometeu-vfs/prometeu-vfs-v1/build.gradle.kts b/prometeu-vfs/prometeu-vfs-v1/build.gradle.kts deleted file mode 100644 index 4a0f4a96..00000000 --- a/prometeu-vfs/prometeu-vfs-v1/build.gradle.kts +++ /dev/null @@ -1,9 +0,0 @@ -plugins { - id("gradle.java-library-conventions") -} - -dependencies { - implementation(project(":prometeu-vfs:prometeu-vfs-api")) - implementation(project(":prometeu-compiler:prometeu-compiler-core")) - implementation(project(":prometeu-compiler:prometeu-frontend-registry")) -} diff --git a/prometeu-vfs/prometeu-vfs-v1/src/main/java/p/studio/vfs/FilesystemProjectDocumentVfsFactory.java b/prometeu-vfs/prometeu-vfs-v1/src/main/java/p/studio/vfs/FilesystemProjectDocumentVfsFactory.java deleted file mode 100644 index a60f52ed..00000000 --- a/prometeu-vfs/prometeu-vfs-v1/src/main/java/p/studio/vfs/FilesystemProjectDocumentVfsFactory.java +++ /dev/null @@ -1,12 +0,0 @@ -package p.studio.vfs; - -import p.studio.vfs.messages.VfsProjectContext; - -import java.util.Objects; - -public final class FilesystemProjectDocumentVfsFactory implements ProjectDocumentVfsFactory { - @Override - public VfsProjectDocument open(final VfsProjectContext projectContext) { - return new FilesystemVfsProjectDocument(Objects.requireNonNull(projectContext, "projectContext")); - } -} diff --git a/prometeu-vfs/prometeu-vfs-v1/src/main/java/p/studio/vfs/FilesystemVfsProjectDocument.java b/prometeu-vfs/prometeu-vfs-v1/src/main/java/p/studio/vfs/FilesystemVfsProjectDocument.java deleted file mode 100644 index feac1f57..00000000 --- a/prometeu-vfs/prometeu-vfs-v1/src/main/java/p/studio/vfs/FilesystemVfsProjectDocument.java +++ /dev/null @@ -1,466 +0,0 @@ -package p.studio.vfs; - -import p.studio.compiler.FrontendRegistryService; -import p.studio.compiler.models.FrontendSpec; -import p.studio.vfs.messages.*; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.nio.charset.CharacterCodingException; -import java.nio.charset.CodingErrorAction; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -final class FilesystemVfsProjectDocument implements VfsProjectDocument { - private final VfsProjectContext projectContext; - private final Map editableSnapshots = new LinkedHashMap<>(); - private final Map> accessContextAttributes = new HashMap<>(); - private VfsProjectSnapshot snapshot; - - FilesystemVfsProjectDocument(final VfsProjectContext projectContext) { - this.projectContext = Objects.requireNonNull(projectContext, "projectContext"); - this.snapshot = buildSnapshot(); - } - - @Override - public VfsProjectContext projectContext() { - return projectContext; - } - - @Override - public VfsProjectSnapshot snapshot() { - return snapshot; - } - - @Override - public VfsProjectSnapshot refresh() { - snapshot = buildSnapshot(); - return snapshot; - } - - @Override - public VfsProjectSnapshot refresh(final VfsRefreshRequest request) { - final Path refreshTarget = refreshRootFor(Objects.requireNonNull(request, "request").targetPath()); - if (refreshTarget.equals(projectContext.rootPath())) { - return refresh(); - } - - final Set taggedRoots = taggedSourceRoots(); - final String displayName = refreshTarget.getFileName() == null - ? projectContext.projectName() - : refreshTarget.getFileName().toString(); - final VfsProjectNode refreshedNode = buildNode(refreshTarget, displayName, taggedRoots); - snapshot = new VfsProjectSnapshot( - projectContext.rootPath(), - replaceNode(snapshot.rootNode(), refreshedNode)); - return snapshot; - } - - @Override - public VfsDocumentOpenResult openDocument(final Path path) { - try { - final var supportedDocument = requireSupportedDocument(path); - return openSupportedDocument(supportedDocument); - } catch (UnsupportedDocumentException unsupportedDocumentException) { - return new VfsDocumentOpenResult.VfsUnsupportedDocument( - unsupportedDocumentException.path(), - unsupportedDocumentException.reason()); - } - } - - @Override - public VfsDocumentAccessContext accessContext(final Path path) { - return accessContextFor(requireSupportedDocument(path)); - } - - @Override - public VfsDocumentAccessContext updateAccessContext(final Path path, final Map attributes) { - final var supportedDocument = requireSupportedDocument(path); - accessContextAttributes.put(supportedDocument.path(), Map.copyOf(Objects.requireNonNull(attributes, "attributes"))); - return accessContextFor(supportedDocument); - } - - @Override - public VfsDocumentOpenResult.VfsTextDocument updateDocument(final Path path, final String content) { - final var supportedDocument = requireSupportedDocument(path); - if (supportedDocument.accessMode() != VfsDocumentAccessMode.EDITABLE) { - throw new IllegalStateException("Document is hard read-only: " + supportedDocument.path()); - } - final var normalizedPath = supportedDocument.path(); - final var snapshot = editableSnapshots.computeIfAbsent( - normalizedPath, - ignored -> new EditableDocumentSnapshot( - supportedDocument.content(), - supportedDocument.content(), - supportedDocument.lineSeparator())); - snapshot.content = Objects.requireNonNull(content, "content"); - snapshot.lineSeparator = lineSeparator(snapshot.content); - return toVfsTextDocument(supportedDocument.withContent(snapshot.content, snapshot.lineSeparator), snapshot.isDirty(), accessContextFor(supportedDocument)); - } - - @Override - public VfsDocumentSaveResult saveDocument(final Path path) { - final var supportedDocument = requireSupportedDocument(path); - final var accessContext = accessContextFor(supportedDocument); - if (accessContext.accessMode() != VfsDocumentAccessMode.EDITABLE) { - return new VfsDocumentSaveResult(supportedDocument.path(), supportedDocument.typeId(), VfsDocumentSaveStatus.READ_ONLY); - } - final var snapshot = editableSnapshots.computeIfAbsent( - supportedDocument.path(), - ignored -> new EditableDocumentSnapshot( - supportedDocument.content(), - supportedDocument.content(), - supportedDocument.lineSeparator())); - if (!snapshot.isDirty()) { - return new VfsDocumentSaveResult(supportedDocument.path(), supportedDocument.typeId(), VfsDocumentSaveStatus.NO_CHANGES); - } - writeUtf8(supportedDocument.path(), snapshot.content); - snapshot.savedContent = snapshot.content; - snapshot.lineSeparator = lineSeparator(snapshot.content); - return new VfsDocumentSaveResult(supportedDocument.path(), supportedDocument.typeId(), VfsDocumentSaveStatus.SAVED); - } - - @Override - public VfsDocumentOpenResult.VfsTextDocument discardDocument(final Path path) { - final var supportedDocument = requireSupportedDocument(path); - editableSnapshots.remove(supportedDocument.path()); - return toVfsTextDocument(supportedDocument, false, accessContextFor(supportedDocument)); - } - - @Override - public List saveAllDocuments() { - return editableSnapshots.keySet().stream() - .map(this::saveDocument) - .toList(); - } - - private SupportedDocument requireSupportedDocument(final Path path) { - final Path normalizedPath = normalize(path); - if (!normalizedPath.startsWith(projectContext.rootPath())) { - throw new UnsupportedDocumentException(normalizedPath, VfsUnsupportedReason.OUTSIDE_PROJECT); - } - if (!Files.exists(normalizedPath)) { - throw new UnsupportedDocumentException(normalizedPath, VfsUnsupportedReason.NOT_FOUND); - } - if (!Files.isRegularFile(normalizedPath)) { - throw new UnsupportedDocumentException(normalizedPath, VfsUnsupportedReason.NOT_A_FILE); - } - - final byte[] bytes = readAllBytes(normalizedPath); - if (containsNulByte(bytes)) { - throw new UnsupportedDocumentException(normalizedPath, VfsUnsupportedReason.BINARY_CONTENT); - } - - final String content; - try { - content = StandardCharsets.UTF_8.newDecoder() - .onMalformedInput(CodingErrorAction.REPORT) - .onUnmappableCharacter(CodingErrorAction.REPORT) - .decode(ByteBuffer.wrap(bytes)) - .toString(); - } catch (CharacterCodingException codingException) { - throw new UnsupportedDocumentException(normalizedPath, VfsUnsupportedReason.INVALID_UTF8, codingException); - } - - final DocumentKind documentKind = documentKind(normalizedPath, content); - return new SupportedDocument( - normalizedPath, - normalizedPath.getFileName().toString(), - documentKind.typeId(), - documentKind.frontendDocument(), - documentKind.accessMode(), - content, - lineSeparator(content)); - } - - private VfsDocumentOpenResult openSupportedDocument(final SupportedDocument supportedDocument) { - final var accessContext = accessContextFor(supportedDocument); - if (supportedDocument.accessMode() == VfsDocumentAccessMode.EDITABLE) { - final var snapshot = editableSnapshots.computeIfAbsent( - supportedDocument.path(), - ignored -> new EditableDocumentSnapshot( - supportedDocument.content(), - supportedDocument.content(), - supportedDocument.lineSeparator())); - return toVfsTextDocument( - supportedDocument.withContent(snapshot.content, snapshot.lineSeparator), - snapshot.isDirty(), - accessContext); - } - return toVfsTextDocument(supportedDocument, false, accessContext); - } - - private VfsDocumentOpenResult.VfsTextDocument toVfsTextDocument( - final SupportedDocument supportedDocument, - final boolean dirty, - final VfsDocumentAccessContext accessContext) { - return new VfsDocumentOpenResult.VfsTextDocument( - supportedDocument.path(), - supportedDocument.documentName(), - supportedDocument.typeId(), - supportedDocument.content(), - supportedDocument.lineSeparator(), - dirty, - accessContext); - } - - private VfsProjectSnapshot buildSnapshot() { - return new VfsProjectSnapshot( - projectContext.rootPath(), - buildNode(projectContext.rootPath(), projectContext.projectName(), taggedSourceRoots())); - } - - private Set taggedSourceRoots() { - return FrontendRegistryService.getFrontendSpec(projectContext.languageId()) - .stream() - .flatMap(frontendSpec -> frontendSpec.getSourceRoots().stream()) - .map(projectContext.rootPath()::resolve) - .map(Path::toAbsolutePath) - .map(Path::normalize) - .collect(Collectors.toSet()); - } - - private DocumentKind documentKind(final Path path, final String content) { - final String extension = extensionOf(path); - if ("json".equals(extension)) { - return new DocumentKind(VfsDocumentTypeIds.JSON, false, VfsDocumentAccessMode.EDITABLE); - } - if ("ndjson".equals(extension)) { - return new DocumentKind(VfsDocumentTypeIds.NDJSON, false, VfsDocumentAccessMode.EDITABLE); - } - if (isBashDocument(path, extension, content)) { - return new DocumentKind(VfsDocumentTypeIds.BASH, false, VfsDocumentAccessMode.EDITABLE); - } - if (isFrontendSourceDocument(extension)) { - return new DocumentKind(projectContext.languageId(), true, VfsDocumentAccessMode.EDITABLE); - } - return new DocumentKind(VfsDocumentTypeIds.TEXT, false, VfsDocumentAccessMode.EDITABLE); - } - - private VfsProjectNode buildNode( - final Path path, - final String displayName, - final Set taggedRoots) { - if (!Files.isDirectory(path)) { - return new VfsProjectNode(path, displayName, false, taggedRoots.contains(path), List.of()); - } - - final List children; - try (Stream stream = Files.list(path)) { - children = stream - .sorted(nodeComparator()) - .map(child -> buildNode(child, child.getFileName().toString(), taggedRoots)) - .toList(); - } catch (IOException ioException) { - throw new UncheckedIOException(ioException); - } - - return new VfsProjectNode(path, displayName, true, taggedRoots.contains(path), children); - } - - private VfsProjectNode replaceNode(final VfsProjectNode currentNode, final VfsProjectNode replacementNode) { - if (currentNode.path().equals(replacementNode.path())) { - return replacementNode; - } - if (!replacementNode.path().startsWith(currentNode.path())) { - return currentNode; - } - if (!currentNode.directory()) { - return currentNode; - } - - final List replacedChildren = currentNode.children() - .stream() - .map(child -> replaceNode(child, replacementNode)) - .sorted(projectNodeComparator()) - .toList(); - return new VfsProjectNode( - currentNode.path(), - currentNode.displayName(), - true, - currentNode.taggedSourceRoot(), - replacedChildren); - } - - private Path refreshRootFor(final Path targetPath) { - final Path normalizedTarget = normalize(targetPath); - if (!normalizedTarget.startsWith(projectContext.rootPath())) { - throw new IllegalArgumentException("Refresh target must stay within the project root."); - } - if (Files.exists(normalizedTarget)) { - return normalizedTarget; - } - final Path parent = normalizedTarget.getParent(); - if (parent == null || !parent.startsWith(projectContext.rootPath())) { - return projectContext.rootPath(); - } - return parent; - } - - private Comparator nodeComparator() { - return Comparator - .comparing((Path path) -> !Files.isDirectory(path)) - .thenComparing(path -> path.getFileName().toString(), String.CASE_INSENSITIVE_ORDER); - } - - private Comparator projectNodeComparator() { - return Comparator - .comparing((VfsProjectNode node) -> !node.directory()) - .thenComparing(VfsProjectNode::displayName, String.CASE_INSENSITIVE_ORDER); - } - - private Path normalize(final Path path) { - return Objects.requireNonNull(path, "path").toAbsolutePath().normalize(); - } - - private boolean isFrontendSourceDocument(final String extension) { - if (extension.isBlank()) { - return false; - } - return FrontendRegistryService.getFrontendSpec(projectContext.languageId()) - .map(FrontendSpec::getAllowedExtensions) - .stream() - .flatMap(allowedExtensions -> allowedExtensions.stream()) - .anyMatch(allowedExtension -> allowedExtension.equalsIgnoreCase(extension)); - } - - private boolean isBashDocument(final Path path, final String extension, final String content) { - if (Set.of("sh", "bash", "bashrc", "bash_profile").contains(extension)) { - return true; - } - final String fileName = path.getFileName().toString().toLowerCase(Locale.ROOT); - if (".bashrc".equals(fileName) || ".bash_profile".equals(fileName)) { - return true; - } - final int firstLineBreak = content.indexOf('\n'); - final String firstLine = firstLineBreak >= 0 ? content.substring(0, firstLineBreak) : content; - return firstLine.startsWith("#!") && (firstLine.contains("bash") || firstLine.contains("/sh")); - } - - private String extensionOf(final Path path) { - final String fileName = path.getFileName().toString(); - final int dot = fileName.lastIndexOf('.'); - if (dot < 0 || dot == fileName.length() - 1) { - return ""; - } - return fileName.substring(dot + 1).toLowerCase(Locale.ROOT); - } - - private boolean containsNulByte(final byte[] bytes) { - for (final byte value : bytes) { - if (value == 0) { - return true; - } - } - return false; - } - - private String lineSeparator(final String content) { - return content.contains("\r\n") ? "CRLF" : "LF"; - } - - private byte[] readAllBytes(final Path path) { - try { - return Files.readAllBytes(path); - } catch (IOException ioException) { - throw new UncheckedIOException(ioException); - } - } - - private void writeUtf8(final Path path, final String content) { - try { - Files.writeString(path, content, StandardCharsets.UTF_8); - } catch (IOException ioException) { - throw new UncheckedIOException(ioException); - } - } - - private VfsDocumentAccessContext accessContextFor(final SupportedDocument supportedDocument) { - final var attributes = accessContextAttributes.computeIfAbsent(supportedDocument.path(), ignored -> Map.of()); - return new VfsDocumentAccessContext( - supportedDocument.path(), - supportedDocument.typeId(), - supportedDocument.frontendDocument(), - supportedDocument.accessMode(), - attributes); - } - - private record DocumentKind( - String typeId, - boolean frontendDocument, - VfsDocumentAccessMode accessMode) { - } - - private record SupportedDocument( - Path path, - String documentName, - String typeId, - boolean frontendDocument, - VfsDocumentAccessMode accessMode, - String content, - String lineSeparator) { - - private SupportedDocument withContent(final String updatedContent, final String updatedLineSeparator) { - return new SupportedDocument( - path, - documentName, - typeId, - frontendDocument, - accessMode, - updatedContent, - updatedLineSeparator); - } - } - - private static final class EditableDocumentSnapshot { - private String savedContent; - private String content; - private String lineSeparator; - - private EditableDocumentSnapshot( - final String savedContent, - final String content, - final String lineSeparator) { - this.savedContent = savedContent; - this.content = content; - this.lineSeparator = lineSeparator; - } - - private boolean isDirty() { - return !Objects.equals(savedContent, content); - } - } - - private static final class UnsupportedDocumentException extends IllegalArgumentException { - private final Path path; - private final VfsUnsupportedReason reason; - - private UnsupportedDocumentException(final Path path, final VfsUnsupportedReason reason) { - super(reason.name() + ": " + path); - this.path = path; - this.reason = reason; - } - - private UnsupportedDocumentException( - final Path path, - final VfsUnsupportedReason reason, - final Throwable cause) { - super(reason.name() + ": " + path, cause); - this.path = path; - this.reason = reason; - } - - private Path path() { - return path; - } - - private VfsUnsupportedReason reason() { - return reason; - } - } -} diff --git a/prometeu-vfs/prometeu-vfs-v1/src/test/java/p/studio/vfs/FilesystemVfsProjectDocumentTest.java b/prometeu-vfs/prometeu-vfs-v1/src/test/java/p/studio/vfs/FilesystemVfsProjectDocumentTest.java deleted file mode 100644 index f619f924..00000000 --- a/prometeu-vfs/prometeu-vfs-v1/src/test/java/p/studio/vfs/FilesystemVfsProjectDocumentTest.java +++ /dev/null @@ -1,237 +0,0 @@ -package p.studio.vfs; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import p.studio.vfs.messages.*; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.*; - -final class FilesystemVfsProjectDocumentTest { - @TempDir - Path tempDir; - - @Test - void snapshotIncludesHiddenFilesOrdersFoldersFirstAndTagsSourceRoots() throws Exception { - Files.createDirectories(tempDir.resolve("src")); - Files.createDirectories(tempDir.resolve("assets")); - Files.writeString(tempDir.resolve(".env"), "TOKEN=1\n"); - Files.writeString(tempDir.resolve("README.md"), "# project\n"); - - final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); - - final VfsProjectSnapshot snapshot = vfs.snapshot(); - - assertEquals("Example", snapshot.rootNode().displayName()); - assertEquals("assets", snapshot.rootNode().children().get(0).displayName()); - assertEquals("src", snapshot.rootNode().children().get(1).displayName()); - assertEquals(".env", snapshot.rootNode().children().get(2).displayName()); - assertTrue(snapshot.rootNode().children().get(1).taggedSourceRoot()); - } - - @Test - void openDocumentReturnsTextDocumentForUtf8TextFile() throws Exception { - final Path file = tempDir.resolve("main.pbs"); - Files.writeString(file, "fn main(): void\n"); - - final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); - - final VfsDocumentOpenResult result = vfs.openDocument(file); - final var document = assertInstanceOf(VfsDocumentOpenResult.VfsTextDocument.class, result); - - assertEquals("main.pbs", document.documentName()); - assertEquals("pbs", document.typeId()); - assertEquals("LF", document.lineSeparator()); - assertEquals(VfsDocumentAccessMode.EDITABLE, document.accessContext().accessMode()); - assertTrue(document.accessContext().frontendDocument()); - assertTrue(document.content().contains("fn main()")); - } - - @Test - void openDocumentClassifiesJsonFilesWithJsonTypeIdentifier() throws Exception { - final Path file = tempDir.resolve("prometeu.json"); - Files.writeString(file, "{\n \"name\": \"Example\"\n}\n"); - - final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); - - final VfsDocumentOpenResult result = vfs.openDocument(file); - final var document = assertInstanceOf(VfsDocumentOpenResult.VfsTextDocument.class, result); - - assertEquals(VfsDocumentTypeIds.JSON, document.typeId()); - assertEquals(VfsDocumentAccessMode.EDITABLE, document.accessContext().accessMode()); - } - - @Test - void openDocumentClassifiesNdjsonFilesWithJsonTypeIdentifier() throws Exception { - final Path file = tempDir.resolve("events.ndjson"); - Files.writeString(file, "{\"event\":\"start\"}\n{\"event\":\"done\"}\n"); - - final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); - - final VfsDocumentOpenResult result = vfs.openDocument(file); - final var document = assertInstanceOf(VfsDocumentOpenResult.VfsTextDocument.class, result); - - assertEquals(VfsDocumentTypeIds.NDJSON, document.typeId()); - } - - @Test - void openDocumentClassifiesBashScriptsWithBashTypeIdentifier() throws Exception { - final Path file = tempDir.resolve("script.sh"); - Files.writeString(file, "#!/usr/bin/env bash\nprintf \"hi\"\n"); - - final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); - - final VfsDocumentOpenResult result = vfs.openDocument(file); - final var document = assertInstanceOf(VfsDocumentOpenResult.VfsTextDocument.class, result); - - assertEquals(VfsDocumentTypeIds.BASH, document.typeId()); - assertEquals(VfsDocumentAccessMode.EDITABLE, document.accessContext().accessMode()); - } - - @Test - void frontendDocumentsUseEditableInMemorySnapshotsUntilSavePersistsThem() throws Exception { - final Path file = tempDir.resolve("main.pbs"); - Files.writeString(file, "fn main(): void\n"); - - final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); - final var updated = vfs.updateDocument(file, "fn main(): int\n"); - - assertTrue(updated.dirty()); - assertEquals("fn main(): int\n", updated.content()); - assertEquals("fn main(): void\n", Files.readString(file)); - assertEquals("fn main(): int\n", assertInstanceOf(VfsDocumentOpenResult.VfsTextDocument.class, vfs.openDocument(file)).content()); - - final VfsDocumentSaveResult saveResult = vfs.saveDocument(file); - - assertEquals(VfsDocumentSaveStatus.SAVED, saveResult.status()); - assertEquals("fn main(): int\n", Files.readString(file)); - assertFalse(assertInstanceOf(VfsDocumentOpenResult.VfsTextDocument.class, vfs.openDocument(file)).dirty()); - } - - @Test - void editableDocumentsUseInMemorySnapshotsUntilSavePersistsThem() throws Exception { - final Path file = tempDir.resolve("notes.txt"); - Files.writeString(file, "alpha\n"); - - final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); - - final var updated = vfs.updateDocument(file, "beta\n"); - - assertTrue(updated.dirty()); - assertEquals("beta\n", updated.content()); - assertEquals("alpha\n", Files.readString(file)); - assertEquals("beta\n", assertInstanceOf(VfsDocumentOpenResult.VfsTextDocument.class, vfs.openDocument(file)).content()); - - final VfsDocumentSaveResult saveResult = vfs.saveDocument(file); - - assertEquals(VfsDocumentSaveStatus.SAVED, saveResult.status()); - assertEquals("beta\n", Files.readString(file)); - assertFalse(assertInstanceOf(VfsDocumentOpenResult.VfsTextDocument.class, vfs.openDocument(file)).dirty()); - } - - @Test - void discardDocumentDropsDirtySnapshotAndRestoresFilesystemState() throws Exception { - final Path file = tempDir.resolve("notes.txt"); - Files.writeString(file, "alpha\n"); - - final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); - vfs.updateDocument(file, "beta\n"); - - final var discarded = vfs.discardDocument(file); - - assertFalse(discarded.dirty()); - assertEquals("alpha\n", discarded.content()); - assertEquals("alpha\n", Files.readString(file)); - assertEquals("alpha\n", assertInstanceOf(VfsDocumentOpenResult.VfsTextDocument.class, vfs.openDocument(file)).content()); - } - - @Test - void saveAllDocumentsPersistsEditableDirtySnapshotsIncludingFrontendDocuments() throws Exception { - final Path editable = tempDir.resolve("notes.txt"); - final Path frontend = tempDir.resolve("main.pbs"); - Files.writeString(editable, "alpha\n"); - Files.writeString(frontend, "fn main(): void\n"); - - final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); - vfs.updateDocument(editable, "beta\n"); - vfs.updateDocument(frontend, "fn main(): int\n"); - - final var results = vfs.saveAllDocuments(); - - assertEquals(2, results.size()); - assertEquals(editable.toAbsolutePath().normalize(), results.get(0).path()); - assertEquals(VfsDocumentSaveStatus.SAVED, results.get(0).status()); - assertEquals(frontend.toAbsolutePath().normalize(), results.get(1).path()); - assertEquals(VfsDocumentSaveStatus.SAVED, results.get(1).status()); - assertEquals("beta\n", Files.readString(editable)); - assertEquals("fn main(): int\n", Files.readString(frontend)); - } - - @Test - void accessContextCanBeReadAndMutatedWithoutChangingCanonicalPolicy() throws Exception { - final Path file = tempDir.resolve("prometeu.json"); - Files.writeString(file, "{\n \"name\": \"Example\"\n}\n"); - - final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); - - final VfsDocumentAccessContext initialContext = vfs.accessContext(file); - final VfsDocumentAccessContext updatedContext = vfs.updateAccessContext(file, Map.of("statusBar", "future-toggle")); - - assertEquals(VfsDocumentAccessMode.EDITABLE, initialContext.accessMode()); - assertEquals(VfsDocumentTypeIds.JSON, initialContext.typeId()); - assertEquals(Map.of("statusBar", "future-toggle"), updatedContext.attributes()); - assertEquals(initialContext.accessMode(), updatedContext.accessMode()); - assertEquals(initialContext.typeId(), updatedContext.typeId()); - } - - @Test - void openDocumentRejectsBinaryLikeFiles() throws Exception { - final Path file = tempDir.resolve("sprite.bin"); - Files.write(file, new byte[]{0x01, 0x00, 0x02}); - - final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); - - final VfsDocumentOpenResult result = vfs.openDocument(file); - final var unsupported = assertInstanceOf(VfsDocumentOpenResult.VfsUnsupportedDocument.class, result); - - assertEquals(VfsUnsupportedReason.BINARY_CONTENT, unsupported.reason()); - } - - @Test - void openDocumentRejectsPathsOutsideProjectScope() throws Exception { - final Path outsideFile = tempDir.getParent().resolve("outside.pbs"); - Files.writeString(outsideFile, "fn stray(): void\n"); - - final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); - - final VfsDocumentOpenResult result = vfs.openDocument(outsideFile); - final var unsupported = assertInstanceOf(VfsDocumentOpenResult.VfsUnsupportedDocument.class, result); - - assertEquals(VfsUnsupportedReason.OUTSIDE_PROJECT, unsupported.reason()); - } - - @Test - void targetedRefreshUpdatesOnlyTheRequestedSubtreeInTheSnapshot() throws Exception { - final Path src = Files.createDirectories(tempDir.resolve("src")); - Files.createDirectories(tempDir.resolve("assets")); - Files.writeString(src.resolve("main.pbs"), "fn main(): void\n"); - - final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext()); - Files.writeString(src.resolve("later.pbs"), "fn later(): void\n"); - - final VfsProjectSnapshot refreshedSnapshot = vfs.refresh(new VfsRefreshRequest(src)); - final VfsProjectNode srcNode = refreshedSnapshot.rootNode().children().get(1); - - assertEquals("src", srcNode.displayName()); - assertEquals(2, srcNode.children().size()); - assertEquals("later.pbs", srcNode.children().get(0).displayName()); - assertEquals("main.pbs", srcNode.children().get(1).displayName()); - } - - private VfsProjectContext projectContext() { - return new VfsProjectContext("Example", "pbs", tempDir); - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index 0df55951..dc36dbbc 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,10 +5,6 @@ plugins { rootProject.name = "prometeu-studio" include("prometeu-infra") -include("prometeu-vfs:prometeu-vfs-api") -include("prometeu-vfs:prometeu-vfs-v1") -include("prometeu-lsp:prometeu-lsp-api") -include("prometeu-lsp:prometeu-lsp-v1") include("prometeu-packer:prometeu-packer-api") include("prometeu-packer:prometeu-packer-v1")