From 41bcd8c52a3678da19fff3be48af4b49033308f9 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Wed, 6 May 2026 13:48:09 +0100 Subject: [PATCH] implements PLN-0069 --- .../lsp/messages/BaselineEditorPalette.java | 33 +++++++++++++++++ .../messages/BaselineServerDescription.java | 9 ++++- .../lsp/messages/BaselineTokenStyle.java | 26 ++++++++++++++ .../lsp/messages/BaselineVisualTheme.java | 31 ++++++++++++++++ .../CompilerLanguageServiceBridge.java | 35 ++++++++++++++++++- .../CompilerLanguageServiceBridgeTest.java | 16 +++++++++ .../protocol/PrometeuLanguageServerTest.java | 15 +++++++- 7 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineEditorPalette.java create mode 100644 prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineTokenStyle.java create mode 100644 prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineVisualTheme.java diff --git a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineEditorPalette.java b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineEditorPalette.java new file mode 100644 index 00000000..0e638149 --- /dev/null +++ b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineEditorPalette.java @@ -0,0 +1,33 @@ +package p.studio.lsp.messages; + +import java.util.Objects; + +public record BaselineEditorPalette( + String baseForeground, + String selectionBackground, + String activeHighlightBackground, + String lineNumberForeground, + String statusChipBackground, + String statusChipBorder, + String statusChipForeground) { + + public BaselineEditorPalette { + baseForeground = requireText(baseForeground, "baseForeground"); + selectionBackground = requireText(selectionBackground, "selectionBackground"); + activeHighlightBackground = requireText(activeHighlightBackground, "activeHighlightBackground"); + lineNumberForeground = requireText(lineNumberForeground, "lineNumberForeground"); + statusChipBackground = requireText(statusChipBackground, "statusChipBackground"); + statusChipBorder = requireText(statusChipBorder, "statusChipBorder"); + statusChipForeground = requireText(statusChipForeground, "statusChipForeground"); + } + + private static String requireText( + final String value, + final String field) { + final String candidate = Objects.requireNonNull(value, field).trim(); + if (candidate.isEmpty()) { + throw new IllegalArgumentException(field + " must not be blank"); + } + return candidate; + } +} diff --git a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineServerDescription.java b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineServerDescription.java index af31b775..daf47a62 100644 --- a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineServerDescription.java +++ b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineServerDescription.java @@ -7,12 +7,19 @@ public record BaselineServerDescription( String name, String version, boolean hoverSupported, - List semanticTokenTypes) { + List semanticTokenTypes, + List visualThemes, + String activeVisualThemeId) { public BaselineServerDescription { name = requireText(name, "name"); version = requireText(version, "version"); semanticTokenTypes = List.copyOf(Objects.requireNonNull(semanticTokenTypes, "semanticTokenTypes")); + visualThemes = List.copyOf(Objects.requireNonNull(visualThemes, "visualThemes")); + activeVisualThemeId = requireText(activeVisualThemeId, "activeVisualThemeId"); + if (visualThemes.stream().map(BaselineVisualTheme::themeId).noneMatch(activeVisualThemeId::equals)) { + throw new IllegalArgumentException("activeVisualThemeId must reference one of the declared visual themes"); + } } private static String requireText( diff --git a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineTokenStyle.java b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineTokenStyle.java new file mode 100644 index 00000000..7fbcaab1 --- /dev/null +++ b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineTokenStyle.java @@ -0,0 +1,26 @@ +package p.studio.lsp.messages; + +import java.util.Objects; + +public record BaselineTokenStyle( + String semanticKey, + String foreground, + boolean italic, + boolean bold, + boolean underline) { + + public BaselineTokenStyle { + semanticKey = requireText(semanticKey, "semanticKey"); + foreground = requireText(foreground, "foreground"); + } + + private static String requireText( + final String value, + final String field) { + final String candidate = Objects.requireNonNull(value, field).trim(); + if (candidate.isEmpty()) { + throw new IllegalArgumentException(field + " must not be blank"); + } + return candidate; + } +} diff --git a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineVisualTheme.java b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineVisualTheme.java new file mode 100644 index 00000000..63145a86 --- /dev/null +++ b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineVisualTheme.java @@ -0,0 +1,31 @@ +package p.studio.lsp.messages; + +import java.util.List; +import java.util.Objects; + +public record BaselineVisualTheme( + String themeId, + String displayName, + BaselineEditorPalette editorPalette, + List tokenStyles) { + + public BaselineVisualTheme { + themeId = requireText(themeId, "themeId"); + displayName = requireText(displayName, "displayName"); + editorPalette = Objects.requireNonNull(editorPalette, "editorPalette"); + tokenStyles = List.copyOf(Objects.requireNonNull(tokenStyles, "tokenStyles")); + if (tokenStyles.isEmpty()) { + throw new IllegalArgumentException("tokenStyles must not be empty"); + } + } + + private static String requireText( + final String value, + final String field) { + final String candidate = Objects.requireNonNull(value, field).trim(); + if (candidate.isEmpty()) { + throw new IllegalArgumentException(field + " must not be blank"); + } + return candidate; + } +} diff --git a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/compiler/CompilerLanguageServiceBridge.java b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/compiler/CompilerLanguageServiceBridge.java index c3a7c27f..787aa02c 100644 --- a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/compiler/CompilerLanguageServiceBridge.java +++ b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/compiler/CompilerLanguageServiceBridge.java @@ -5,7 +5,10 @@ import p.studio.compiler.messages.BuilderPipelineConfig; import p.studio.compiler.messages.BuildingIssue; import p.studio.compiler.models.AnalysisSnapshot; import p.studio.compiler.models.BuilderPipelineContext; +import p.studio.compiler.models.FrontendEditorPaletteSpec; import p.studio.compiler.PBSDefinitions; +import p.studio.compiler.models.FrontendTokenStyleSpec; +import p.studio.compiler.models.FrontendVisualThemeSpec; import p.studio.compiler.pbs.PbsSemanticKind; import p.studio.compiler.pbs.lexer.PbsLexer; import p.studio.compiler.pbs.lexer.PbsToken; @@ -34,7 +37,9 @@ public record CompilerLanguageServiceBridge() implements LanguageServiceBridge { "Prometeu Studio LSP", "0.1.0", true, - PBSDefinitions.PBS.getSemanticPresentation().semanticKeys()); + PBSDefinitions.PBS.getSemanticPresentation().semanticKeys(), + PBSDefinitions.PBS.getSemanticPresentation().themes().stream().map(this::mapVisualTheme).toList(), + PBSDefinitions.PBS.getSemanticPresentation().defaultThemeId()); } @Override @@ -190,4 +195,32 @@ public record CompilerLanguageServiceBridge() implements LanguageServiceBridge { return tokens.get(index - 1).kind() == p.studio.compiler.pbs.lexer.PbsTokenKind.LEFT_BRACKET && tokens.get(index + 1).kind() == p.studio.compiler.pbs.lexer.PbsTokenKind.RIGHT_BRACKET; } + + private BaselineVisualTheme mapVisualTheme(final FrontendVisualThemeSpec theme) { + return new BaselineVisualTheme( + theme.themeId(), + theme.displayName(), + mapEditorPalette(theme.editorPalette()), + theme.tokenStyles().stream().map(this::mapTokenStyle).toList()); + } + + private BaselineEditorPalette mapEditorPalette(final FrontendEditorPaletteSpec palette) { + return new BaselineEditorPalette( + palette.baseForeground(), + palette.selectionBackground(), + palette.activeHighlightBackground(), + palette.lineNumberForeground(), + palette.statusChipBackground(), + palette.statusChipBorder(), + palette.statusChipForeground()); + } + + private BaselineTokenStyle mapTokenStyle(final FrontendTokenStyleSpec tokenStyle) { + return new BaselineTokenStyle( + tokenStyle.semanticKey(), + tokenStyle.foreground(), + tokenStyle.italic(), + tokenStyle.bold(), + tokenStyle.underline()); + } } diff --git a/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/services/compiler/CompilerLanguageServiceBridgeTest.java b/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/services/compiler/CompilerLanguageServiceBridgeTest.java index c95d6c36..05e009cc 100644 --- a/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/services/compiler/CompilerLanguageServiceBridgeTest.java +++ b/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/services/compiler/CompilerLanguageServiceBridgeTest.java @@ -8,6 +8,7 @@ import java.nio.file.Files; import java.nio.file.Path; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; class CompilerLanguageServiceBridgeTest { @@ -55,6 +56,21 @@ class CompilerLanguageServiceBridgeTest { assertTrue(semanticTokens.tokens().stream().anyMatch(token -> token.semanticKey().equals("pbs-number"))); } + @Test + void describeServerPublishesFrontendVisualThemes() { + final CompilerLanguageServiceBridge bridge = new CompilerLanguageServiceBridge(); + + final var description = bridge.describeServer(new LspProjectContext("main", "pbs", Path.of("."))); + + assertEquals("pbs-default", description.activeVisualThemeId()); + assertEquals(1, description.visualThemes().size()); + assertEquals("PBS Default", description.visualThemes().getFirst().displayName()); + assertEquals("#d9e2ec", description.visualThemes().getFirst().editorPalette().baseForeground()); + assertTrue(description.visualThemes().getFirst().tokenStyles().stream() + .anyMatch(tokenStyle -> tokenStyle.semanticKey().equals("pbs-keyword") + && tokenStyle.foreground().equals("#569cd6"))); + } + private Path findRepoRoot(final Path start) { var current = start; while (current != null) { diff --git a/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/services/protocol/PrometeuLanguageServerTest.java b/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/services/protocol/PrometeuLanguageServerTest.java index 33ae0009..10e8e769 100644 --- a/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/services/protocol/PrometeuLanguageServerTest.java +++ b/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/services/protocol/PrometeuLanguageServerTest.java @@ -12,9 +12,12 @@ import org.eclipse.lsp4j.services.LanguageClient; import org.eclipse.lsp4j.services.TextDocumentService; import org.junit.jupiter.api.Test; import p.studio.lsp.messages.BaselineDocumentAnalysis; +import p.studio.lsp.messages.BaselineEditorPalette; import p.studio.lsp.messages.BaselineHover; import p.studio.lsp.messages.BaselineSemanticTokens; import p.studio.lsp.messages.BaselineServerDescription; +import p.studio.lsp.messages.BaselineTokenStyle; +import p.studio.lsp.messages.BaselineVisualTheme; import p.studio.lsp.messages.LspProjectContext; import p.studio.lsp.services.LanguageServiceBridge; import p.studio.lsp.services.protocol.mapping.ProtocolMessageMapper; @@ -79,7 +82,17 @@ class PrometeuLanguageServerTest { private static final class RecordingBridge implements LanguageServiceBridge { private int describeServerCalls; - private final BaselineServerDescription serverDescription = new BaselineServerDescription("demo", "1", true, List.of()); + private final BaselineServerDescription serverDescription = new BaselineServerDescription( + "demo", + "1", + true, + List.of(), + List.of(new BaselineVisualTheme( + "demo-default", + "Demo Default", + new BaselineEditorPalette("#111111", "#222222", "#333333", "#444444", "#555555", "#666666", "#777777"), + List.of(new BaselineTokenStyle("demo-keyword", "#888888", false, false, false)))), + "demo-default"); @Override public BaselineServerDescription describeServer(final LspProjectContext project) {