implements PLN-0069

This commit is contained in:
bQUARKz 2026-05-06 13:48:09 +01:00
parent 54f8c0d6d8
commit 41bcd8c52a
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
7 changed files with 162 additions and 3 deletions

View File

@ -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;
}
}

View File

@ -7,12 +7,19 @@ public record BaselineServerDescription(
String name,
String version,
boolean hoverSupported,
List<String> semanticTokenTypes) {
List<String> semanticTokenTypes,
List<BaselineVisualTheme> 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(

View File

@ -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;
}
}

View File

@ -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<BaselineTokenStyle> 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;
}
}

View File

@ -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());
}
}

View File

@ -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) {

View File

@ -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) {