implements PLN-0068
This commit is contained in:
parent
baf9f90c93
commit
54f8c0d6d8
@ -1,7 +1,10 @@
|
||||
package p.studio.compiler;
|
||||
|
||||
import p.studio.compiler.models.FrontendSpec;
|
||||
import p.studio.compiler.models.FrontendEditorPaletteSpec;
|
||||
import p.studio.compiler.models.FrontendSemanticPresentationSpec;
|
||||
import p.studio.compiler.models.FrontendTokenStyleSpec;
|
||||
import p.studio.compiler.models.FrontendVisualThemeSpec;
|
||||
import p.studio.compiler.pbs.PbsSemanticKind;
|
||||
import p.studio.utilities.structures.ReadOnlySet;
|
||||
|
||||
@ -16,6 +19,41 @@ public class PBSDefinitions {
|
||||
.stdlibVersions(List.of(FrontendSpec.Stdlib.asDefault(1)))
|
||||
.semanticPresentation(new FrontendSemanticPresentationSpec(
|
||||
PbsSemanticKind.semanticKeys(),
|
||||
List.of("/themes/pbs/semantic-highlighting.css")))
|
||||
List.of(new FrontendVisualThemeSpec(
|
||||
"pbs-default",
|
||||
"PBS Default",
|
||||
new FrontendEditorPaletteSpec(
|
||||
"#d9e2ec",
|
||||
"#2d4f6b",
|
||||
"#2d4f6b",
|
||||
"#6f8193",
|
||||
"#2c2140",
|
||||
"#a47dff",
|
||||
"#efe6ff"),
|
||||
List.of(
|
||||
new FrontendTokenStyleSpec("pbs-comment", "#c090b0", true, false, false),
|
||||
new FrontendTokenStyleSpec("pbs-string", "#00c088", false, false, false),
|
||||
new FrontendTokenStyleSpec("pbs-number", "#ff90b0", false, false, false),
|
||||
new FrontendTokenStyleSpec("pbs-literal", "#4fc1ff", false, false, false),
|
||||
new FrontendTokenStyleSpec("pbs-lifecycle", "#ef50c0", false, false, false),
|
||||
new FrontendTokenStyleSpec("pbs-keyword", "#569cd6", false, false, false),
|
||||
new FrontendTokenStyleSpec("pbs-operator", "#d4d4d4", false, false, false),
|
||||
new FrontendTokenStyleSpec("pbs-punctuation", "#d4d4d4", false, false, false),
|
||||
new FrontendTokenStyleSpec("pbs-function", "#f2c14e", false, false, false),
|
||||
new FrontendTokenStyleSpec("pbs-method", "#f2c14e", false, false, false),
|
||||
new FrontendTokenStyleSpec("pbs-constructor", "#ecdcaa", false, false, false),
|
||||
new FrontendTokenStyleSpec("pbs-struct", "#4ec9b0", false, false, false),
|
||||
new FrontendTokenStyleSpec("pbs-contract", "#78dce8", false, false, false),
|
||||
new FrontendTokenStyleSpec("pbs-host", "#b7a2fa", false, false, false),
|
||||
new FrontendTokenStyleSpec("pbs-builtin-type", "#8be9fd", false, false, false),
|
||||
new FrontendTokenStyleSpec("pbs-service", "#b7a2fa", false, false, false),
|
||||
new FrontendTokenStyleSpec("pbs-error", "#ff5b5b", false, false, false),
|
||||
new FrontendTokenStyleSpec("pbs-enum", "#56cfe1", false, false, false),
|
||||
new FrontendTokenStyleSpec("pbs-callback", "#b7a2fa", false, false, false),
|
||||
new FrontendTokenStyleSpec("pbs-global", "#f790fc", true, false, false),
|
||||
new FrontendTokenStyleSpec("pbs-const", "#f78c6c", false, false, false),
|
||||
new FrontendTokenStyleSpec("pbs-implements", "#a1c181", false, false, false),
|
||||
new FrontendTokenStyleSpec("pbs-identifier", "#d4d4d4", false, false, false)))),
|
||||
"pbs-default"))
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -3,11 +3,8 @@ 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 {
|
||||
@ -18,16 +15,21 @@ final class PbsSemanticPresentationContractTest {
|
||||
|
||||
assertFalse(presentation.semanticKeys().isEmpty());
|
||||
assertEquals(PbsSemanticKind.semanticKeys(), presentation.semanticKeys());
|
||||
assertEquals(1, presentation.resources().size());
|
||||
assertEquals("/themes/pbs/semantic-highlighting.css", presentation.resources().getFirst());
|
||||
assertEquals("pbs-default", presentation.defaultThemeId());
|
||||
assertEquals(1, presentation.themes().size());
|
||||
assertEquals("PBS Default", presentation.defaultTheme().displayName());
|
||||
assertEquals("#d9e2ec", presentation.defaultTheme().editorPalette().baseForeground());
|
||||
assertEquals("#569cd6", presentation.defaultTheme().requireTokenStyle("pbs-keyword").foreground());
|
||||
assertTrue(presentation.defaultTheme().requireTokenStyle("pbs-comment").italic());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPublishResolvableSemanticPresentationResources() {
|
||||
final var resourcePath = PBSDefinitions.PBS.getSemanticPresentation().resources().getFirst();
|
||||
final URL resource = PBSDefinitions.class.getResource(resourcePath);
|
||||
void shouldCoverTheWholePbsSemanticVocabularyInTheDefaultTheme() {
|
||||
final var presentation = PBSDefinitions.PBS.getSemanticPresentation();
|
||||
final var defaultTheme = presentation.defaultTheme();
|
||||
|
||||
assertNotNull(resource, resourcePath);
|
||||
assertTrue(resource.toExternalForm().endsWith("themes/pbs/semantic-highlighting.css"));
|
||||
assertEquals(
|
||||
PbsSemanticKind.semanticKeys(),
|
||||
defaultTheme.tokenStyles().stream().map(tokenStyle -> tokenStyle.semanticKey()).toList());
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
package p.studio.compiler.models;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record FrontendEditorPaletteSpec(
|
||||
String baseForeground,
|
||||
String selectionBackground,
|
||||
String activeHighlightBackground,
|
||||
String lineNumberForeground,
|
||||
String statusChipBackground,
|
||||
String statusChipBorder,
|
||||
String statusChipForeground) {
|
||||
|
||||
public FrontendEditorPaletteSpec {
|
||||
baseForeground = requireColor(baseForeground, "baseForeground");
|
||||
selectionBackground = requireColor(selectionBackground, "selectionBackground");
|
||||
activeHighlightBackground = requireColor(activeHighlightBackground, "activeHighlightBackground");
|
||||
lineNumberForeground = requireColor(lineNumberForeground, "lineNumberForeground");
|
||||
statusChipBackground = requireColor(statusChipBackground, "statusChipBackground");
|
||||
statusChipBorder = requireColor(statusChipBorder, "statusChipBorder");
|
||||
statusChipForeground = requireColor(statusChipForeground, "statusChipForeground");
|
||||
}
|
||||
|
||||
private static String requireColor(
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -5,14 +5,53 @@ import java.util.Objects;
|
||||
|
||||
public record FrontendSemanticPresentationSpec(
|
||||
List<String> semanticKeys,
|
||||
List<String> resources) {
|
||||
List<FrontendVisualThemeSpec> themes,
|
||||
String defaultThemeId) {
|
||||
|
||||
public FrontendSemanticPresentationSpec {
|
||||
semanticKeys = List.copyOf(Objects.requireNonNull(semanticKeys, "semanticKeys"));
|
||||
resources = List.copyOf(Objects.requireNonNull(resources, "resources"));
|
||||
themes = List.copyOf(Objects.requireNonNull(themes, "themes"));
|
||||
defaultThemeId = Objects.requireNonNull(defaultThemeId, "defaultThemeId").trim();
|
||||
if (semanticKeys.isEmpty() && themes.isEmpty()) {
|
||||
if (!defaultThemeId.isEmpty()) {
|
||||
throw new IllegalArgumentException("defaultThemeId must be blank when no themes are declared");
|
||||
}
|
||||
} else {
|
||||
defaultThemeId = requireText(defaultThemeId, "defaultThemeId");
|
||||
if (themes.isEmpty()) {
|
||||
throw new IllegalArgumentException("themes must not be empty");
|
||||
}
|
||||
if (themes.stream().map(FrontendVisualThemeSpec::themeId).noneMatch(defaultThemeId::equals)) {
|
||||
throw new IllegalArgumentException("defaultThemeId must reference one of the declared themes");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static FrontendSemanticPresentationSpec empty() {
|
||||
return new FrontendSemanticPresentationSpec(List.of(), List.of());
|
||||
return new FrontendSemanticPresentationSpec(List.of(), List.of(), "");
|
||||
}
|
||||
|
||||
public FrontendVisualThemeSpec defaultTheme() {
|
||||
if (themes.isEmpty()) {
|
||||
throw new IllegalStateException("empty semantic presentation has no default theme");
|
||||
}
|
||||
return themes.stream()
|
||||
.filter(theme -> theme.themeId().equals(defaultThemeId))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException("defaultThemeId must resolve to a declared theme"));
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return semanticKeys.isEmpty() && themes.isEmpty() && defaultThemeId.isEmpty();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
package p.studio.compiler.models;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record FrontendTokenStyleSpec(
|
||||
String semanticKey,
|
||||
String foreground,
|
||||
boolean italic,
|
||||
boolean bold,
|
||||
boolean underline) {
|
||||
|
||||
public FrontendTokenStyleSpec {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
package p.studio.compiler.models;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public record FrontendVisualThemeSpec(
|
||||
String themeId,
|
||||
String displayName,
|
||||
FrontendEditorPaletteSpec editorPalette,
|
||||
List<FrontendTokenStyleSpec> tokenStyles) {
|
||||
|
||||
public FrontendVisualThemeSpec {
|
||||
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");
|
||||
}
|
||||
final Map<String, FrontendTokenStyleSpec> bySemanticKey = new LinkedHashMap<>();
|
||||
for (final FrontendTokenStyleSpec tokenStyle : tokenStyles) {
|
||||
final FrontendTokenStyleSpec previous = bySemanticKey.put(tokenStyle.semanticKey(), tokenStyle);
|
||||
if (previous != null) {
|
||||
throw new IllegalArgumentException("duplicate token style for semantic key: " + tokenStyle.semanticKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FrontendTokenStyleSpec requireTokenStyle(final String semanticKey) {
|
||||
return tokenStyles.stream()
|
||||
.filter(tokenStyle -> tokenStyle.semanticKey().equals(semanticKey))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("missing token style for semantic key: " + semanticKey));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user