Frontend Visual Theme Spec and Retirement of Host-Consumed Semantic CSS
This commit is contained in:
parent
7ae2e94d63
commit
fb13e166f1
@ -2,7 +2,6 @@ package p.studio.compiler;
|
||||
|
||||
import p.studio.compiler.models.FrontendSemanticPresentationSpec;
|
||||
import p.studio.compiler.models.FrontendSpec;
|
||||
import p.studio.compiler.pbs.PbsSemanticKind;
|
||||
import p.studio.utilities.structures.ReadOnlySet;
|
||||
|
||||
import java.util.List;
|
||||
@ -15,7 +14,6 @@ public class PBSDefinitions {
|
||||
.sourceRoots(ReadOnlySet.from("src"))
|
||||
.stdlibVersions(List.of(FrontendSpec.Stdlib.asDefault(1)))
|
||||
.semanticPresentation(new FrontendSemanticPresentationSpec(
|
||||
PbsSemanticKind.semanticKeys(),
|
||||
List.of(PBSFrontendThemes.DEFAULT_PBS),
|
||||
"pbs-default",
|
||||
List.of(PBSFrontendHostProjections.VSCODE)))
|
||||
|
||||
@ -2,46 +2,26 @@ package p.studio.compiler;
|
||||
|
||||
import p.studio.compiler.models.FrontendHostProjectionEntrySpec;
|
||||
import p.studio.compiler.models.FrontendHostProjectionSpec;
|
||||
import p.studio.compiler.pbs.PbsSemanticKind;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static p.studio.compiler.pbs.PbsSemanticKind.BUILTIN_TYPE;
|
||||
import static p.studio.compiler.pbs.PbsSemanticKind.CALLBACK;
|
||||
import static p.studio.compiler.pbs.PbsSemanticKind.COMMENT;
|
||||
import static p.studio.compiler.pbs.PbsSemanticKind.CONST;
|
||||
import static p.studio.compiler.pbs.PbsSemanticKind.CONSTRUCTOR;
|
||||
import static p.studio.compiler.pbs.PbsSemanticKind.CONTRACT;
|
||||
import static p.studio.compiler.pbs.PbsSemanticKind.ENUM;
|
||||
import static p.studio.compiler.pbs.PbsSemanticKind.ERROR;
|
||||
import static p.studio.compiler.pbs.PbsSemanticKind.FUNCTION;
|
||||
import static p.studio.compiler.pbs.PbsSemanticKind.GLOBAL;
|
||||
import static p.studio.compiler.pbs.PbsSemanticKind.HOST;
|
||||
import static p.studio.compiler.pbs.PbsSemanticKind.IDENTIFIER;
|
||||
import static p.studio.compiler.pbs.PbsSemanticKind.IMPLEMENTS;
|
||||
import static p.studio.compiler.pbs.PbsSemanticKind.KEYWORD;
|
||||
import static p.studio.compiler.pbs.PbsSemanticKind.LIFECYCLE;
|
||||
import static p.studio.compiler.pbs.PbsSemanticKind.LITERAL;
|
||||
import static p.studio.compiler.pbs.PbsSemanticKind.METHOD;
|
||||
import static p.studio.compiler.pbs.PbsSemanticKind.NUMBER;
|
||||
import static p.studio.compiler.pbs.PbsSemanticKind.OPERATOR;
|
||||
import static p.studio.compiler.pbs.PbsSemanticKind.PUNCTUATION;
|
||||
import static p.studio.compiler.pbs.PbsSemanticKind.SERVICE;
|
||||
import static p.studio.compiler.pbs.PbsSemanticKind.STRING;
|
||||
import static p.studio.compiler.pbs.PbsSemanticKind.STRUCT;
|
||||
import static p.studio.compiler.pbs.PbsSemanticKind.*;
|
||||
|
||||
public final class PBSFrontendHostProjections {
|
||||
public static final String VSCODE_HOST_ID = "vscode";
|
||||
|
||||
public static final FrontendHostProjectionSpec VSCODE = new FrontendHostProjectionSpec(
|
||||
VSCODE_HOST_ID,
|
||||
Arrays.stream(p.studio.compiler.pbs.PbsSemanticKind.values())
|
||||
Arrays.stream(PbsSemanticKind.values())
|
||||
.map(PBSFrontendHostProjections::mapVscodeProjection)
|
||||
.toList());
|
||||
|
||||
private PBSFrontendHostProjections() {
|
||||
}
|
||||
|
||||
private static FrontendHostProjectionEntrySpec mapVscodeProjection(final p.studio.compiler.pbs.PbsSemanticKind kind) {
|
||||
private static FrontendHostProjectionEntrySpec mapVscodeProjection(final PbsSemanticKind kind) {
|
||||
return switch (kind) {
|
||||
case COMMENT -> projection(COMMENT.semanticKey(), "comment");
|
||||
case STRING -> projection(STRING.semanticKey(), "string");
|
||||
@ -72,14 +52,14 @@ public final class PBSFrontendHostProjections {
|
||||
private static FrontendHostProjectionEntrySpec projection(
|
||||
final String semanticKey,
|
||||
final String hostTokenType) {
|
||||
return new FrontendHostProjectionEntrySpec(semanticKey, hostTokenType, java.util.List.of(), hostTokenType, java.util.List.of());
|
||||
return new FrontendHostProjectionEntrySpec(semanticKey, hostTokenType, List.of(), hostTokenType, List.of());
|
||||
}
|
||||
|
||||
private static FrontendHostProjectionEntrySpec projection(
|
||||
final String semanticKey,
|
||||
final String hostTokenType,
|
||||
final String fallbackTokenType) {
|
||||
return new FrontendHostProjectionEntrySpec(semanticKey, hostTokenType, java.util.List.of(), fallbackTokenType, java.util.List.of());
|
||||
return new FrontendHostProjectionEntrySpec(semanticKey, hostTokenType, List.of(), fallbackTokenType, List.of());
|
||||
}
|
||||
|
||||
private static FrontendHostProjectionEntrySpec projection(
|
||||
@ -90,8 +70,8 @@ public final class PBSFrontendHostProjections {
|
||||
return new FrontendHostProjectionEntrySpec(
|
||||
semanticKey,
|
||||
hostTokenType,
|
||||
java.util.List.of(modifier),
|
||||
List.of(modifier),
|
||||
fallbackTokenType,
|
||||
java.util.List.of(modifier));
|
||||
List.of(modifier));
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,57 +4,80 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public record FrontendSemanticPresentationSpec(
|
||||
List<String> semanticKeys,
|
||||
List<FrontendVisualThemeSpec> themes,
|
||||
String defaultThemeId,
|
||||
List<FrontendHostProjectionSpec> hostProjections) {
|
||||
|
||||
public FrontendSemanticPresentationSpec(
|
||||
final List<String> semanticKeys,
|
||||
final List<FrontendVisualThemeSpec> themes,
|
||||
final String defaultThemeId) {
|
||||
this(semanticKeys, themes, defaultThemeId, List.of());
|
||||
this(themes, defaultThemeId, List.of());
|
||||
}
|
||||
|
||||
public FrontendSemanticPresentationSpec {
|
||||
semanticKeys = List.copyOf(Objects.requireNonNull(semanticKeys, "semanticKeys"));
|
||||
themes = List.copyOf(Objects.requireNonNull(themes, "themes"));
|
||||
defaultThemeId = Objects.requireNonNull(defaultThemeId, "defaultThemeId").trim();
|
||||
hostProjections = List.copyOf(Objects.requireNonNull(hostProjections, "hostProjections"));
|
||||
if (semanticKeys.isEmpty() && themes.isEmpty()) {
|
||||
if (themes.isEmpty()) {
|
||||
if (!defaultThemeId.isEmpty()) {
|
||||
throw new IllegalArgumentException("defaultThemeId must be blank when no themes are declared");
|
||||
}
|
||||
if (!hostProjections.isEmpty()) {
|
||||
throw new IllegalArgumentException("hostProjections must be empty 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)) {
|
||||
final String resolvedDefaultThemeId = defaultThemeId;
|
||||
if (themes.stream().map(FrontendVisualThemeSpec::themeId).noneMatch(resolvedDefaultThemeId::equals)) {
|
||||
throw new IllegalArgumentException("defaultThemeId must reference one of the declared themes");
|
||||
}
|
||||
}
|
||||
final List<String> normalizedSemanticKeys = semanticKeys.stream()
|
||||
.map(key -> requireText(key, "semanticKeys entry"))
|
||||
.toList();
|
||||
final var hostIds = new java.util.LinkedHashSet<String>();
|
||||
for (final FrontendHostProjectionSpec hostProjection : hostProjections) {
|
||||
if (!hostIds.add(hostProjection.hostId())) {
|
||||
throw new IllegalArgumentException("duplicate host projection for hostId: " + hostProjection.hostId());
|
||||
}
|
||||
for (final FrontendHostProjectionEntrySpec tokenProjection : hostProjection.tokenProjections()) {
|
||||
if (!normalizedSemanticKeys.contains(tokenProjection.semanticKey())) {
|
||||
|
||||
final List<String> derivedSemanticKeys = themes.stream()
|
||||
.filter(theme -> theme.themeId().equals(resolvedDefaultThemeId))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException("defaultThemeId must resolve to a declared theme"))
|
||||
.tokenStyles()
|
||||
.stream()
|
||||
.map(FrontendTokenStyleSpec::semanticKey)
|
||||
.toList();
|
||||
|
||||
for (final FrontendVisualThemeSpec theme : themes) {
|
||||
final List<String> themeSemanticKeys = theme.tokenStyles().stream()
|
||||
.map(FrontendTokenStyleSpec::semanticKey)
|
||||
.toList();
|
||||
if (!themeSemanticKeys.equals(derivedSemanticKeys)) {
|
||||
throw new IllegalArgumentException(
|
||||
"host projection for hostId '" + hostProjection.hostId() + "' references unknown semantic key: "
|
||||
+ tokenProjection.semanticKey());
|
||||
"theme '" + theme.themeId() + "' must publish the same semantic keys as the default theme");
|
||||
}
|
||||
}
|
||||
|
||||
final var hostIds = new java.util.LinkedHashSet<String>();
|
||||
for (final FrontendHostProjectionSpec hostProjection : hostProjections) {
|
||||
if (!hostIds.add(hostProjection.hostId())) {
|
||||
throw new IllegalArgumentException(
|
||||
"duplicate host projection for hostId: " + hostProjection.hostId());
|
||||
}
|
||||
final List<String> projectedSemanticKeys = hostProjection.tokenProjections().stream()
|
||||
.map(FrontendHostProjectionEntrySpec::semanticKey)
|
||||
.toList();
|
||||
if (!projectedSemanticKeys.equals(derivedSemanticKeys)) {
|
||||
throw new IllegalArgumentException(
|
||||
"host projection for hostId '" + hostProjection.hostId()
|
||||
+ "' must publish the same semantic keys as the default theme");
|
||||
}
|
||||
for (final FrontendHostProjectionEntrySpec tokenProjection : hostProjection.tokenProjections()) {
|
||||
if (!derivedSemanticKeys.contains(tokenProjection.semanticKey())) {
|
||||
throw new IllegalArgumentException(
|
||||
"host projection for hostId '" + hostProjection.hostId() + "' references unknown semantic key: "
|
||||
+ tokenProjection.semanticKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
semanticKeys = normalizedSemanticKeys;
|
||||
}
|
||||
|
||||
public static FrontendSemanticPresentationSpec empty() {
|
||||
return new FrontendSemanticPresentationSpec(List.of(), List.of(), "", List.of());
|
||||
return new FrontendSemanticPresentationSpec(List.of(), "", List.of());
|
||||
}
|
||||
|
||||
public FrontendVisualThemeSpec defaultTheme() {
|
||||
@ -68,7 +91,16 @@ public record FrontendSemanticPresentationSpec(
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return semanticKeys.isEmpty() && themes.isEmpty() && defaultThemeId.isEmpty() && hostProjections.isEmpty();
|
||||
return themes.isEmpty() && defaultThemeId.isEmpty() && hostProjections.isEmpty();
|
||||
}
|
||||
|
||||
public List<String> semanticKeys() {
|
||||
if (themes.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
return defaultTheme().tokenStyles().stream()
|
||||
.map(FrontendTokenStyleSpec::semanticKey)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public java.util.Optional<FrontendHostProjectionSpec> hostProjection(final String hostId) {
|
||||
|
||||
@ -12,7 +12,6 @@ final class FrontendSemanticPresentationSpecTest {
|
||||
@Test
|
||||
void shouldExposeHostProjectionByHostId() {
|
||||
final var presentation = new FrontendSemanticPresentationSpec(
|
||||
List.of("demo-key"),
|
||||
List.of(new FrontendVisualThemeSpec(
|
||||
"demo-theme",
|
||||
"Demo Theme",
|
||||
@ -37,7 +36,6 @@ final class FrontendSemanticPresentationSpecTest {
|
||||
@Test
|
||||
void shouldRejectDuplicateHostIds() {
|
||||
assertThrows(IllegalArgumentException.class, () -> new FrontendSemanticPresentationSpec(
|
||||
List.of("demo-key"),
|
||||
List.of(new FrontendVisualThemeSpec(
|
||||
"demo-theme",
|
||||
"Demo Theme",
|
||||
@ -66,7 +64,6 @@ final class FrontendSemanticPresentationSpecTest {
|
||||
@Test
|
||||
void shouldRejectProjectionForUnknownSemanticKey() {
|
||||
assertThrows(IllegalArgumentException.class, () -> new FrontendSemanticPresentationSpec(
|
||||
List.of("demo-key"),
|
||||
List.of(new FrontendVisualThemeSpec(
|
||||
"demo-theme",
|
||||
"Demo Theme",
|
||||
@ -82,4 +79,22 @@ final class FrontendSemanticPresentationSpecTest {
|
||||
"variable",
|
||||
List.of()))))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectThemeSetThatDriftsFromDefaultThemeVocabulary() {
|
||||
assertThrows(IllegalArgumentException.class, () -> new FrontendSemanticPresentationSpec(
|
||||
List.of(
|
||||
new FrontendVisualThemeSpec(
|
||||
"demo-theme",
|
||||
"Demo Theme",
|
||||
new FrontendEditorPaletteSpec("#111111", "#eeeeee", "#333333", "#777777", "#111111", "#222222", "#eeeeee"),
|
||||
List.of(new FrontendTokenStyleSpec("demo-key", "#eeeeee", false, false, false))),
|
||||
new FrontendVisualThemeSpec(
|
||||
"demo-theme-2",
|
||||
"Demo Theme 2",
|
||||
new FrontendEditorPaletteSpec("#111111", "#eeeeee", "#333333", "#777777", "#111111", "#222222", "#eeeeee"),
|
||||
List.of(new FrontendTokenStyleSpec("other-key", "#eeeeee", false, false, false)))),
|
||||
"demo-theme",
|
||||
List.of()));
|
||||
}
|
||||
}
|
||||
|
||||
29
test-projects/main/.vscode/settings.json
vendored
29
test-projects/main/.vscode/settings.json
vendored
@ -30,7 +30,34 @@
|
||||
},
|
||||
"pbs-const:pbs": "#f78c6c",
|
||||
"pbs-implements:pbs": "#a1c181",
|
||||
"pbs-identifier:pbs": "#d4d4d4"
|
||||
"pbs-identifier:pbs": "#d4d4d4",
|
||||
"comment:pbs": {
|
||||
"foreground": "#c090b0",
|
||||
"italic": true
|
||||
},
|
||||
"string:pbs": "#00c088",
|
||||
"number:pbs": "#ff90b0",
|
||||
"enumMember:pbs": "#4fc1ff",
|
||||
"variable:pbs": "#4fc1ff",
|
||||
"function.async:pbs": "#ef50c0",
|
||||
"variable.async:pbs": "#ef50c0",
|
||||
"keyword:pbs": "#569cd6",
|
||||
"operator:pbs": "#d4d4d4",
|
||||
"function:pbs": "#f2c14e",
|
||||
"method:pbs": "#f2c14e",
|
||||
"class.declaration:pbs": "#ecdcaa",
|
||||
"type.declaration:pbs": "#ecdcaa",
|
||||
"struct:pbs": "#4ec9b0",
|
||||
"type:pbs": "#4ec9b0",
|
||||
"interface:pbs": "#78dce8",
|
||||
"class.defaultLibrary:pbs": "#b7a2fa",
|
||||
"type.defaultLibrary:pbs": "#b7a2fa",
|
||||
"variable.defaultLibrary:pbs": "#8be9fd",
|
||||
"enum:pbs": "#ff5b5b",
|
||||
"variable.readonly:pbs": {
|
||||
"foreground": "#f790fc",
|
||||
"italic": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"workbench.colorCustomizations": {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user