diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/PBSDefinitions.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/PBSDefinitions.java index e041973a..27b9781c 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/PBSDefinitions.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/PBSDefinitions.java @@ -1,6 +1,8 @@ package p.studio.compiler; import p.studio.compiler.models.FrontendSpec; +import p.studio.compiler.models.FrontendSemanticPresentationSpec; +import p.studio.compiler.pbs.PbsSemanticKind; import p.studio.utilities.structures.ReadOnlySet; import java.util.List; @@ -12,5 +14,8 @@ public class PBSDefinitions { .allowedExtensions(ReadOnlySet.from("pbs", "barrel")) .sourceRoots(ReadOnlySet.from("src")) .stdlibVersions(List.of(FrontendSpec.Stdlib.asDefault(1))) + .semanticPresentation(new FrontendSemanticPresentationSpec( + PbsSemanticKind.semanticKeys(), + List.of("/themes/pbs/semantic-highlighting.css"))) .build(); } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsSemanticKind.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsSemanticKind.java new file mode 100644 index 00000000..d2f677a2 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsSemanticKind.java @@ -0,0 +1,56 @@ +package p.studio.compiler.pbs; + +import p.studio.compiler.pbs.lexer.PbsToken; + +import java.util.Arrays; +import java.util.List; + +public enum PbsSemanticKind { + COMMENT("pbs-comment"), + STRING("pbs-string"), + NUMBER("pbs-number"), + LITERAL("pbs-literal"), + KEYWORD("pbs-keyword"), + OPERATOR("pbs-operator"), + PUNCTUATION("pbs-punctuation"), + FUNCTION("pbs-function"), + TYPE("pbs-type"), + BINDING("pbs-binding"), + IDENTIFIER("pbs-identifier"); + + private final String semanticKey; + + PbsSemanticKind(final String semanticKey) { + this.semanticKey = semanticKey; + } + + public String semanticKey() { + return semanticKey; + } + + public static List semanticKeys() { + return Arrays.stream(values()) + .map(PbsSemanticKind::semanticKey) + .toList(); + } + + public static PbsSemanticKind forToken(final PbsToken token) { + return switch (token.kind()) { + case COMMENT -> COMMENT; + case STRING_LITERAL -> STRING; + case INT_LITERAL, FLOAT_LITERAL, BOUNDED_LITERAL -> NUMBER; + case TRUE, FALSE, NONE -> LITERAL; + case IMPORT, FROM, AS, SERVICE, HOST, FN, APPLY, BIND, NEW, IMPLEMENTS, USING, CTOR, + DECLARE, LET, CONST, GLOBAL, STRUCT, CONTRACT, ERROR, ENUM, CALLBACK, BUILTIN, + SELF, THIS, PUB, MUT, MOD, TYPE, IF, ELSE, SWITCH, DEFAULT, FOR, UNTIL, STEP, + WHILE, BREAK, CONTINUE, RETURN, VOID, OPTIONAL, RESULT, SOME, OK, ERR, HANDLE, + AND, OR, NOT, SPAWN, YIELD, SLEEP, MATCH -> KEYWORD; + case PLUS, MINUS, STAR, SLASH, PERCENT, BANG, PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, + SLASH_EQUAL, PERCENT_EQUAL, BANG_EQUAL, EQUAL, EQUAL_EQUAL, LESS, LESS_EQUAL, + GREATER, GREATER_EQUAL, AND_AND, OR_OR, ARROW, QUESTION -> OPERATOR; + case LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, LEFT_BRACKET, RIGHT_BRACKET, + COMMA, COLON, SEMICOLON, AT, DOT, DOT_DOT -> PUNCTUATION; + case IDENTIFIER, EOF -> null; + }; + } +} diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/themes/pbs/semantic-highlighting.css b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/themes/pbs/semantic-highlighting.css new file mode 100644 index 00000000..72186934 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/themes/pbs/semantic-highlighting.css @@ -0,0 +1,61 @@ +.editor-workspace-code-area-type-pbs { + -fx-highlight-fill: #1b3244; +} + +.editor-workspace-code-area-type-pbs .text { + -fx-fill: #edf4fb; +} + +.editor-workspace-code-area-type-pbs .lineno { + -fx-text-fill: #71859a; +} + +.editor-workspace-code-area-type-pbs .text.editor-syntax-pbs-keyword { + -fx-fill: #8dc7ff; +} + +.editor-workspace-code-area-type-pbs .text.editor-syntax-pbs-function { + -fx-fill: #f0cb79; +} + +.editor-workspace-code-area-type-pbs .text.editor-syntax-pbs-type { + -fx-fill: #9ddba8; +} + +.editor-workspace-code-area-type-pbs .text.editor-syntax-pbs-binding { + -fx-fill: #ffb1c8; +} + +.editor-workspace-code-area-type-pbs .text.editor-syntax-pbs-string { + -fx-fill: #e2c48c; +} + +.editor-workspace-code-area-type-pbs .text.editor-syntax-pbs-number { + -fx-fill: #c4e58a; +} + +.editor-workspace-code-area-type-pbs .text.editor-syntax-pbs-comment { + -fx-fill: #6f8192; +} + +.editor-workspace-code-area-type-pbs .text.editor-syntax-pbs-literal { + -fx-fill: #c8a2ff; +} + +.editor-workspace-code-area-type-pbs .text.editor-syntax-pbs-operator { + -fx-fill: #dbe6f1; +} + +.editor-workspace-code-area-type-pbs .text.editor-syntax-pbs-punctuation { + -fx-fill: #adc1d4; +} + +.editor-workspace-code-area-type-pbs .text.editor-syntax-pbs-identifier { + -fx-fill: #edf4fb; +} + +.editor-workspace-status-chip-type-pbs { + -fx-background-color: #152432; + -fx-border-color: #4d8db9; + -fx-text-fill: #e8f5ff; +} diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsSemanticPresentationContractTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsSemanticPresentationContractTest.java new file mode 100644 index 00000000..761a4cff --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsSemanticPresentationContractTest.java @@ -0,0 +1,33 @@ +package p.studio.compiler.pbs; + +import org.junit.jupiter.api.Test; +import p.studio.compiler.PBSDefinitions; + +import java.net.URL; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +final class PbsSemanticPresentationContractTest { + + @Test + void shouldPublishSemanticPresentationContractThroughFrontendSpec() { + final var presentation = PBSDefinitions.PBS.getSemanticPresentation(); + + assertFalse(presentation.semanticKeys().isEmpty()); + assertEquals(PbsSemanticKind.semanticKeys(), presentation.semanticKeys()); + assertEquals(1, presentation.resources().size()); + assertEquals("/themes/pbs/semantic-highlighting.css", presentation.resources().getFirst()); + } + + @Test + void shouldPublishResolvableSemanticPresentationResources() { + final var resourcePath = PBSDefinitions.PBS.getSemanticPresentation().resources().getFirst(); + final URL resource = PBSDefinitions.class.getResource(resourcePath); + + assertNotNull(resource, resourcePath); + assertTrue(resource.toExternalForm().endsWith("themes/pbs/semantic-highlighting.css")); + } +} diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/models/FrontendSemanticPresentationSpec.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/models/FrontendSemanticPresentationSpec.java new file mode 100644 index 00000000..73691d86 --- /dev/null +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/models/FrontendSemanticPresentationSpec.java @@ -0,0 +1,18 @@ +package p.studio.compiler.models; + +import java.util.List; +import java.util.Objects; + +public record FrontendSemanticPresentationSpec( + List semanticKeys, + List resources) { + + public FrontendSemanticPresentationSpec { + semanticKeys = List.copyOf(Objects.requireNonNull(semanticKeys, "semanticKeys")); + resources = List.copyOf(Objects.requireNonNull(resources, "resources")); + } + + public static FrontendSemanticPresentationSpec empty() { + return new FrontendSemanticPresentationSpec(List.of(), List.of()); + } +} diff --git a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/models/FrontendSpec.java b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/models/FrontendSpec.java index 3b747eb1..f09e24fc 100644 --- a/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/models/FrontendSpec.java +++ b/prometeu-compiler/prometeu-compiler-core/src/main/java/p/studio/compiler/models/FrontendSpec.java @@ -15,6 +15,8 @@ public class FrontendSpec { private final boolean caseSensitive; @Builder.Default private final List stdlibVersions = List.of(); + @Builder.Default + private final FrontendSemanticPresentationSpec semanticPresentation = FrontendSemanticPresentationSpec.empty(); public String toString() { return String.format("FrontendSpec(language=%s)", languageId); 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 new file mode 100644 index 00000000..d31eaf88 --- /dev/null +++ b/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/LspSemanticPresentationDTO.java @@ -0,0 +1,14 @@ +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/messages/LspAnalyzeDocumentResult.java b/prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/messages/LspAnalyzeDocumentResult.java index abf5b230..d144f0a7 100644 --- 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 @@ -2,6 +2,7 @@ package p.studio.lsp.messages; import p.studio.lsp.dtos.LspDiagnosticDTO; import p.studio.lsp.dtos.LspHighlightSpanDTO; +import p.studio.lsp.dtos.LspSemanticPresentationDTO; import p.studio.lsp.dtos.LspSessionStateDTO; import p.studio.lsp.dtos.LspSymbolDTO; @@ -10,6 +11,7 @@ import java.util.Objects; public record LspAnalyzeDocumentResult( LspSessionStateDTO sessionState, + LspSemanticPresentationDTO semanticPresentation, List diagnostics, List semanticHighlights, List documentSymbols, @@ -17,6 +19,7 @@ public record LspAnalyzeDocumentResult( public LspAnalyzeDocumentResult { Objects.requireNonNull(sessionState, "sessionState"); + Objects.requireNonNull(semanticPresentation, "semanticPresentation"); diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics")); semanticHighlights = List.copyOf(Objects.requireNonNull(semanticHighlights, "semanticHighlights")); documentSymbols = List.copyOf(Objects.requireNonNull(documentSymbols, "documentSymbols")); 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 index 60df1f7a..b9886740 100644 --- 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 @@ -16,6 +16,7 @@ class LspSemanticAnalyseService { final var normalizedRequestedDocument = normalize(request.documentPath()); return new LspAnalyzeDocumentResult( new LspSessionStateDTO(true, List.of("diagnostics", "symbols", "definition", "highlight")), + session.semanticPresentation(), session.diagnosticsByDocument().getOrDefault(normalizedRequestedDocument, List.of()), session.semanticHighlightsByDocument().getOrDefault(normalizedRequestedDocument, List.of()), session.documentSymbolsByDocument().getOrDefault(normalizedRequestedDocument, List.of()), 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 index 4fbe31c8..f2a9a09a 100644 --- 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 @@ -5,6 +5,7 @@ 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; @@ -18,6 +19,7 @@ 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.LspRangeDTO; +import p.studio.lsp.dtos.LspSemanticPresentationDTO; import p.studio.lsp.messages.*; import p.studio.lsp.models.AnalysisRuntimeSnapshot; import p.studio.lsp.models.SemanticIndex; @@ -137,6 +139,7 @@ final class LspSemanticReadPhase { if (snapshot.fileTable() == null) { return new SemanticSession( normalize(requestedDocumentPath), + semanticPresentation(snapshot.frontendSpec()), diagnosticsByDocument, Map.of(), Map.of(), @@ -157,6 +160,7 @@ final class LspSemanticReadPhase { } return new SemanticSession( normalize(requestedDocumentPath), + semanticPresentation(snapshot.frontendSpec()), diagnosticsByDocument, semanticIndex.semanticHighlightsByDocument(), semanticIndex.documentSymbolsByDocument(), @@ -169,6 +173,11 @@ final class LspSemanticReadPhase { 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, 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 index afbc5899..5c4721fd 100644 --- 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 @@ -3,6 +3,7 @@ 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.LspSemanticPresentationDTO; import p.studio.lsp.dtos.LspSymbolDTO; import java.nio.file.Path; @@ -11,6 +12,7 @@ import java.util.Map; public record SemanticSession( Path requestedDocumentPath, + LspSemanticPresentationDTO semanticPresentation, Map> diagnosticsByDocument, Map> semanticHighlightsByDocument, Map> documentSymbolsByDocument, 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 index dc64067b..8addf7fa 100644 --- 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 @@ -1,5 +1,6 @@ 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; @@ -71,39 +72,25 @@ public final class SemanticIndex { private String semanticKey( final PbsToken token, final Map> indexedSymbolsByName) { - return switch (token.kind()) { - case COMMENT -> "fe-comment"; - case STRING_LITERAL -> "fe-string"; - case INT_LITERAL, FLOAT_LITERAL, BOUNDED_LITERAL -> "fe-number"; - case TRUE, FALSE, NONE -> "fe-literal"; - case IDENTIFIER -> semanticKeyForIdentifier(token.lexeme(), indexedSymbolsByName); - case IMPORT, FROM, AS, SERVICE, HOST, FN, APPLY, BIND, NEW, IMPLEMENTS, USING, CTOR, - DECLARE, LET, CONST, GLOBAL, STRUCT, CONTRACT, ERROR, ENUM, CALLBACK, BUILTIN, - SELF, THIS, PUB, MUT, MOD, TYPE, IF, ELSE, SWITCH, DEFAULT, FOR, UNTIL, STEP, - WHILE, BREAK, CONTINUE, RETURN, VOID, OPTIONAL, RESULT, SOME, OK, ERR, HANDLE, - AND, OR, NOT -> "fe-keyword"; - case PLUS, MINUS, STAR, SLASH, PERCENT, BANG, PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL, - SLASH_EQUAL, PERCENT_EQUAL, BANG_EQUAL, EQUAL, EQUAL_EQUAL, LESS, LESS_EQUAL, - GREATER, GREATER_EQUAL, AND_AND, OR_OR, ARROW, QUESTION -> "fe-operator"; - case LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, LEFT_BRACKET, RIGHT_BRACKET, - COMMA, COLON, SEMICOLON, AT, DOT, DOT_DOT -> "fe-punctuation"; - default -> null; - }; + final PbsSemanticKind semanticKind = token.kind() == p.studio.compiler.pbs.lexer.PbsTokenKind.IDENTIFIER + ? semanticKindForIdentifier(token.lexeme(), indexedSymbolsByName) + : PbsSemanticKind.forToken(token); + return semanticKind == null ? null : semanticKind.semanticKey(); } - private String semanticKeyForIdentifier( + private PbsSemanticKind semanticKindForIdentifier( final String lexeme, final Map> indexedSymbolsByName) { final List symbols = indexedSymbolsByName.getOrDefault(lexeme, List.of()); if (symbols.isEmpty()) { - return "fe-identifier"; + return PbsSemanticKind.IDENTIFIER; } final LspSymbolKind kind = symbols.getFirst().kind(); return switch (kind) { - case FUNCTION, METHOD, CALLBACK -> "fe-callable"; - case STRUCT, CONTRACT, HOST, BUILTIN_TYPE, SERVICE, ERROR, ENUM -> "fe-type"; - case GLOBAL, CONST -> "fe-binding"; - default -> "fe-identifier"; + case FUNCTION, METHOD, CALLBACK -> PbsSemanticKind.FUNCTION; + case STRUCT, CONTRACT, HOST, BUILTIN_TYPE, SERVICE, ERROR, ENUM -> PbsSemanticKind.TYPE; + case GLOBAL, CONST -> PbsSemanticKind.BINDING; + default -> PbsSemanticKind.IDENTIFIER; }; } 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 index 2e0584bf..e158c20e 100644 --- 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 @@ -22,6 +22,13 @@ final class LspServiceImplTest { @TempDir Path tempDir; + private static final String OVERLAY_SOURCE = """ + fn helper_call() -> void + { + helper(); + } + """; + @Test void analyzeDocumentUsesVfsOverlayForRequestedDocumentAndFilesystemFallbackForClosedFiles() throws Exception { final Path projectRoot = createProject(); @@ -31,18 +38,17 @@ final class LspServiceImplTest { Files.writeString(helperFile, "fn helper() -> void {}\n"); final VfsProjectDocument delegate = new FilesystemProjectDocumentVfsFactory().open(projectContext(projectRoot)); - final String overlaySource = """ - fn helper_call() -> void - { - helper(); - } - """; final LspService service = new LspServiceImpl( new LspProjectContext("Example", "pbs", projectRoot), - new OverlayVfsProjectDocument(delegate, mainFile, overlaySource)); + 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")); + assertTrue( analysis.documentSymbols().stream().anyMatch(symbol -> symbol.name().equals("helper_call")), analysis.toString()); @@ -51,7 +57,7 @@ final class LspServiceImplTest { symbol.name().equals("helper") && symbol.documentPath().equals(normalize(helperFile))), analysis.toString()); - final int offset = overlaySource.indexOf("helper();"); + final int offset = OVERLAY_SOURCE.indexOf("helper();"); final var definition = service.definition(new LspDefinitionRequest(mainFile, offset)); final List targets = definition.targets(); @@ -110,6 +116,27 @@ final class LspServiceImplTest { } } + private static String semanticKeyForLexeme( + 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) + .findFirst() + .orElseThrow(); + } + + 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 final class OverlayVfsProjectDocument implements VfsProjectDocument { private final VfsProjectDocument delegate; private final Path overlayPath; diff --git a/prometeu-studio/src/test/java/p/studio/workspaces/editor/EditorDocumentHighlightingRouterTest.java b/prometeu-studio/src/test/java/p/studio/workspaces/editor/EditorDocumentHighlightingRouterTest.java index 760e98c0..944ddfb9 100644 --- a/prometeu-studio/src/test/java/p/studio/workspaces/editor/EditorDocumentHighlightingRouterTest.java +++ b/prometeu-studio/src/test/java/p/studio/workspaces/editor/EditorDocumentHighlightingRouterTest.java @@ -2,6 +2,7 @@ package p.studio.workspaces.editor; import org.junit.jupiter.api.Test; import p.studio.lsp.dtos.LspHighlightSpanDTO; +import p.studio.lsp.dtos.LspSemanticPresentationDTO; import p.studio.lsp.dtos.LspRangeDTO; import p.studio.lsp.dtos.LspSessionStateDTO; import p.studio.lsp.messages.LspAnalyzeDocumentResult; @@ -28,6 +29,7 @@ final class EditorDocumentHighlightingRouterTest { final LspAnalyzeDocumentResult analysis = new LspAnalyzeDocumentResult( new LspSessionStateDTO(true, List.of("highlight")), + new LspSemanticPresentationDTO(List.of(), List.of()), List.of(), List.of(new LspHighlightSpanDTO(new LspRangeDTO(0, 2), "fe-keyword")), List.of(), @@ -56,6 +58,7 @@ final class EditorDocumentHighlightingRouterTest { final LspAnalyzeDocumentResult analysis = new LspAnalyzeDocumentResult( new LspSessionStateDTO(true, List.of("highlight")), + new LspSemanticPresentationDTO(List.of(), List.of()), List.of(), List.of(new LspHighlightSpanDTO(new LspRangeDTO(0, 1), "fe-punctuation")), List.of(),