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.FrontendSemanticPresentationSpec;
|
||||||
import p.studio.compiler.models.FrontendSpec;
|
import p.studio.compiler.models.FrontendSpec;
|
||||||
import p.studio.compiler.pbs.PbsSemanticKind;
|
|
||||||
import p.studio.utilities.structures.ReadOnlySet;
|
import p.studio.utilities.structures.ReadOnlySet;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -15,7 +14,6 @@ public class PBSDefinitions {
|
|||||||
.sourceRoots(ReadOnlySet.from("src"))
|
.sourceRoots(ReadOnlySet.from("src"))
|
||||||
.stdlibVersions(List.of(FrontendSpec.Stdlib.asDefault(1)))
|
.stdlibVersions(List.of(FrontendSpec.Stdlib.asDefault(1)))
|
||||||
.semanticPresentation(new FrontendSemanticPresentationSpec(
|
.semanticPresentation(new FrontendSemanticPresentationSpec(
|
||||||
PbsSemanticKind.semanticKeys(),
|
|
||||||
List.of(PBSFrontendThemes.DEFAULT_PBS),
|
List.of(PBSFrontendThemes.DEFAULT_PBS),
|
||||||
"pbs-default",
|
"pbs-default",
|
||||||
List.of(PBSFrontendHostProjections.VSCODE)))
|
List.of(PBSFrontendHostProjections.VSCODE)))
|
||||||
|
|||||||
@ -2,46 +2,26 @@ package p.studio.compiler;
|
|||||||
|
|
||||||
import p.studio.compiler.models.FrontendHostProjectionEntrySpec;
|
import p.studio.compiler.models.FrontendHostProjectionEntrySpec;
|
||||||
import p.studio.compiler.models.FrontendHostProjectionSpec;
|
import p.studio.compiler.models.FrontendHostProjectionSpec;
|
||||||
|
import p.studio.compiler.pbs.PbsSemanticKind;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static p.studio.compiler.pbs.PbsSemanticKind.BUILTIN_TYPE;
|
import static p.studio.compiler.pbs.PbsSemanticKind.*;
|
||||||
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;
|
|
||||||
|
|
||||||
public final class PBSFrontendHostProjections {
|
public final class PBSFrontendHostProjections {
|
||||||
public static final String VSCODE_HOST_ID = "vscode";
|
public static final String VSCODE_HOST_ID = "vscode";
|
||||||
|
|
||||||
public static final FrontendHostProjectionSpec VSCODE = new FrontendHostProjectionSpec(
|
public static final FrontendHostProjectionSpec VSCODE = new FrontendHostProjectionSpec(
|
||||||
VSCODE_HOST_ID,
|
VSCODE_HOST_ID,
|
||||||
Arrays.stream(p.studio.compiler.pbs.PbsSemanticKind.values())
|
Arrays.stream(PbsSemanticKind.values())
|
||||||
.map(PBSFrontendHostProjections::mapVscodeProjection)
|
.map(PBSFrontendHostProjections::mapVscodeProjection)
|
||||||
.toList());
|
.toList());
|
||||||
|
|
||||||
private PBSFrontendHostProjections() {
|
private PBSFrontendHostProjections() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FrontendHostProjectionEntrySpec mapVscodeProjection(final p.studio.compiler.pbs.PbsSemanticKind kind) {
|
private static FrontendHostProjectionEntrySpec mapVscodeProjection(final PbsSemanticKind kind) {
|
||||||
return switch (kind) {
|
return switch (kind) {
|
||||||
case COMMENT -> projection(COMMENT.semanticKey(), "comment");
|
case COMMENT -> projection(COMMENT.semanticKey(), "comment");
|
||||||
case STRING -> projection(STRING.semanticKey(), "string");
|
case STRING -> projection(STRING.semanticKey(), "string");
|
||||||
@ -72,14 +52,14 @@ public final class PBSFrontendHostProjections {
|
|||||||
private static FrontendHostProjectionEntrySpec projection(
|
private static FrontendHostProjectionEntrySpec projection(
|
||||||
final String semanticKey,
|
final String semanticKey,
|
||||||
final String hostTokenType) {
|
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(
|
private static FrontendHostProjectionEntrySpec projection(
|
||||||
final String semanticKey,
|
final String semanticKey,
|
||||||
final String hostTokenType,
|
final String hostTokenType,
|
||||||
final String fallbackTokenType) {
|
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(
|
private static FrontendHostProjectionEntrySpec projection(
|
||||||
@ -90,8 +70,8 @@ public final class PBSFrontendHostProjections {
|
|||||||
return new FrontendHostProjectionEntrySpec(
|
return new FrontendHostProjectionEntrySpec(
|
||||||
semanticKey,
|
semanticKey,
|
||||||
hostTokenType,
|
hostTokenType,
|
||||||
java.util.List.of(modifier),
|
List.of(modifier),
|
||||||
fallbackTokenType,
|
fallbackTokenType,
|
||||||
java.util.List.of(modifier));
|
List.of(modifier));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,57 +4,80 @@ import java.util.List;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public record FrontendSemanticPresentationSpec(
|
public record FrontendSemanticPresentationSpec(
|
||||||
List<String> semanticKeys,
|
|
||||||
List<FrontendVisualThemeSpec> themes,
|
List<FrontendVisualThemeSpec> themes,
|
||||||
String defaultThemeId,
|
String defaultThemeId,
|
||||||
List<FrontendHostProjectionSpec> hostProjections) {
|
List<FrontendHostProjectionSpec> hostProjections) {
|
||||||
|
|
||||||
public FrontendSemanticPresentationSpec(
|
public FrontendSemanticPresentationSpec(
|
||||||
final List<String> semanticKeys,
|
|
||||||
final List<FrontendVisualThemeSpec> themes,
|
final List<FrontendVisualThemeSpec> themes,
|
||||||
final String defaultThemeId) {
|
final String defaultThemeId) {
|
||||||
this(semanticKeys, themes, defaultThemeId, List.of());
|
this(themes, defaultThemeId, List.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
public FrontendSemanticPresentationSpec {
|
public FrontendSemanticPresentationSpec {
|
||||||
semanticKeys = List.copyOf(Objects.requireNonNull(semanticKeys, "semanticKeys"));
|
|
||||||
themes = List.copyOf(Objects.requireNonNull(themes, "themes"));
|
themes = List.copyOf(Objects.requireNonNull(themes, "themes"));
|
||||||
defaultThemeId = Objects.requireNonNull(defaultThemeId, "defaultThemeId").trim();
|
defaultThemeId = Objects.requireNonNull(defaultThemeId, "defaultThemeId").trim();
|
||||||
hostProjections = List.copyOf(Objects.requireNonNull(hostProjections, "hostProjections"));
|
hostProjections = List.copyOf(Objects.requireNonNull(hostProjections, "hostProjections"));
|
||||||
if (semanticKeys.isEmpty() && themes.isEmpty()) {
|
if (themes.isEmpty()) {
|
||||||
if (!defaultThemeId.isEmpty()) {
|
if (!defaultThemeId.isEmpty()) {
|
||||||
throw new IllegalArgumentException("defaultThemeId must be blank when no themes are declared");
|
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 {
|
} else {
|
||||||
defaultThemeId = requireText(defaultThemeId, "defaultThemeId");
|
defaultThemeId = requireText(defaultThemeId, "defaultThemeId");
|
||||||
if (themes.isEmpty()) {
|
final String resolvedDefaultThemeId = defaultThemeId;
|
||||||
throw new IllegalArgumentException("themes must not be empty");
|
if (themes.stream().map(FrontendVisualThemeSpec::themeId).noneMatch(resolvedDefaultThemeId::equals)) {
|
||||||
}
|
|
||||||
if (themes.stream().map(FrontendVisualThemeSpec::themeId).noneMatch(defaultThemeId::equals)) {
|
|
||||||
throw new IllegalArgumentException("defaultThemeId must reference one of the declared themes");
|
throw new IllegalArgumentException("defaultThemeId must reference one of the declared themes");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
final List<String> normalizedSemanticKeys = semanticKeys.stream()
|
final List<String> derivedSemanticKeys = themes.stream()
|
||||||
.map(key -> requireText(key, "semanticKeys entry"))
|
.filter(theme -> theme.themeId().equals(resolvedDefaultThemeId))
|
||||||
.toList();
|
.findFirst()
|
||||||
final var hostIds = new java.util.LinkedHashSet<String>();
|
.orElseThrow(() -> new IllegalStateException("defaultThemeId must resolve to a declared theme"))
|
||||||
for (final FrontendHostProjectionSpec hostProjection : hostProjections) {
|
.tokenStyles()
|
||||||
if (!hostIds.add(hostProjection.hostId())) {
|
.stream()
|
||||||
throw new IllegalArgumentException("duplicate host projection for hostId: " + hostProjection.hostId());
|
.map(FrontendTokenStyleSpec::semanticKey)
|
||||||
}
|
.toList();
|
||||||
for (final FrontendHostProjectionEntrySpec tokenProjection : hostProjection.tokenProjections()) {
|
|
||||||
if (!normalizedSemanticKeys.contains(tokenProjection.semanticKey())) {
|
for (final FrontendVisualThemeSpec theme : themes) {
|
||||||
|
final List<String> themeSemanticKeys = theme.tokenStyles().stream()
|
||||||
|
.map(FrontendTokenStyleSpec::semanticKey)
|
||||||
|
.toList();
|
||||||
|
if (!themeSemanticKeys.equals(derivedSemanticKeys)) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"host projection for hostId '" + hostProjection.hostId() + "' references unknown semantic key: "
|
"theme '" + theme.themeId() + "' must publish the same semantic keys as the default theme");
|
||||||
+ tokenProjection.semanticKey());
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
public static FrontendSemanticPresentationSpec empty() {
|
||||||
return new FrontendSemanticPresentationSpec(List.of(), List.of(), "", List.of());
|
return new FrontendSemanticPresentationSpec(List.of(), "", List.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
public FrontendVisualThemeSpec defaultTheme() {
|
public FrontendVisualThemeSpec defaultTheme() {
|
||||||
@ -68,7 +91,16 @@ public record FrontendSemanticPresentationSpec(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEmpty() {
|
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) {
|
public java.util.Optional<FrontendHostProjectionSpec> hostProjection(final String hostId) {
|
||||||
|
|||||||
@ -12,7 +12,6 @@ final class FrontendSemanticPresentationSpecTest {
|
|||||||
@Test
|
@Test
|
||||||
void shouldExposeHostProjectionByHostId() {
|
void shouldExposeHostProjectionByHostId() {
|
||||||
final var presentation = new FrontendSemanticPresentationSpec(
|
final var presentation = new FrontendSemanticPresentationSpec(
|
||||||
List.of("demo-key"),
|
|
||||||
List.of(new FrontendVisualThemeSpec(
|
List.of(new FrontendVisualThemeSpec(
|
||||||
"demo-theme",
|
"demo-theme",
|
||||||
"Demo Theme",
|
"Demo Theme",
|
||||||
@ -37,7 +36,6 @@ final class FrontendSemanticPresentationSpecTest {
|
|||||||
@Test
|
@Test
|
||||||
void shouldRejectDuplicateHostIds() {
|
void shouldRejectDuplicateHostIds() {
|
||||||
assertThrows(IllegalArgumentException.class, () -> new FrontendSemanticPresentationSpec(
|
assertThrows(IllegalArgumentException.class, () -> new FrontendSemanticPresentationSpec(
|
||||||
List.of("demo-key"),
|
|
||||||
List.of(new FrontendVisualThemeSpec(
|
List.of(new FrontendVisualThemeSpec(
|
||||||
"demo-theme",
|
"demo-theme",
|
||||||
"Demo Theme",
|
"Demo Theme",
|
||||||
@ -66,7 +64,6 @@ final class FrontendSemanticPresentationSpecTest {
|
|||||||
@Test
|
@Test
|
||||||
void shouldRejectProjectionForUnknownSemanticKey() {
|
void shouldRejectProjectionForUnknownSemanticKey() {
|
||||||
assertThrows(IllegalArgumentException.class, () -> new FrontendSemanticPresentationSpec(
|
assertThrows(IllegalArgumentException.class, () -> new FrontendSemanticPresentationSpec(
|
||||||
List.of("demo-key"),
|
|
||||||
List.of(new FrontendVisualThemeSpec(
|
List.of(new FrontendVisualThemeSpec(
|
||||||
"demo-theme",
|
"demo-theme",
|
||||||
"Demo Theme",
|
"Demo Theme",
|
||||||
@ -82,4 +79,22 @@ final class FrontendSemanticPresentationSpecTest {
|
|||||||
"variable",
|
"variable",
|
||||||
List.of()))))));
|
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-const:pbs": "#f78c6c",
|
||||||
"pbs-implements:pbs": "#a1c181",
|
"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": {
|
"workbench.colorCustomizations": {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user