implements PLN-0072
This commit is contained in:
parent
cab0e92e60
commit
9d75f2f0e2
@ -1,5 +1,5 @@
|
|||||||
{"type":"meta","next_id":{"DSC":35,"AGD":38,"DEC":35,"PLN":75,"LSN":47,"CLSN":1}}
|
{"type":"meta","next_id":{"DSC":35,"AGD":38,"DEC":35,"PLN":75,"LSN":47,"CLSN":1}}
|
||||||
{"type":"discussion","id":"DSC-0034","status":"in_progress","ticket":"frontend-semantic-host-projection-flexibility","title":"Frontend semantic vocabulary flexibility and declarative host projection","created_at":"2026-05-06","updated_at":"2026-05-06","tags":["compiler","compiler-general","frontend","semantics","vscode","host-projection","lsp"],"agendas":[{"id":"AGD-0037","file":"AGD-0037-frontend-semantic-host-projection-flexibility.md","status":"accepted","created_at":"2026-05-06","updated_at":"2026-05-06"}],"decisions":[{"id":"DEC-0034","file":"DEC-0034-frontend-semantic-host-projection-flexibility.md","status":"accepted","created_at":"2026-05-06","updated_at":"2026-05-06","ref_agenda":"AGD-0037"}],"plans":[{"id":"PLN-0072","file":"PLN-0072-frontend-semantic-host-projection-contract.md","status":"open","created_at":"2026-05-06","updated_at":"2026-05-06","ref_decisions":["DEC-0034"]},{"id":"PLN-0073","file":"PLN-0073-lsp-semantic-host-projection-transport.md","status":"open","created_at":"2026-05-06","updated_at":"2026-05-06","ref_decisions":["DEC-0034"]},{"id":"PLN-0074","file":"PLN-0074-vscode-semantic-host-projection-adapter.md","status":"open","created_at":"2026-05-06","updated_at":"2026-05-06","ref_decisions":["DEC-0034"]}],"lessons":[]}
|
{"type":"discussion","id":"DSC-0034","status":"in_progress","ticket":"frontend-semantic-host-projection-flexibility","title":"Frontend semantic vocabulary flexibility and declarative host projection","created_at":"2026-05-06","updated_at":"2026-05-06","tags":["compiler","compiler-general","frontend","semantics","vscode","host-projection","lsp"],"agendas":[{"id":"AGD-0037","file":"AGD-0037-frontend-semantic-host-projection-flexibility.md","status":"accepted","created_at":"2026-05-06","updated_at":"2026-05-06"}],"decisions":[{"id":"DEC-0034","file":"DEC-0034-frontend-semantic-host-projection-flexibility.md","status":"accepted","created_at":"2026-05-06","updated_at":"2026-05-06","ref_agenda":"AGD-0037"}],"plans":[{"id":"PLN-0072","file":"PLN-0072-frontend-semantic-host-projection-contract.md","status":"done","created_at":"2026-05-06","updated_at":"2026-05-06","ref_decisions":["DEC-0034"]},{"id":"PLN-0073","file":"PLN-0073-lsp-semantic-host-projection-transport.md","status":"open","created_at":"2026-05-06","updated_at":"2026-05-06","ref_decisions":["DEC-0034"]},{"id":"PLN-0074","file":"PLN-0074-vscode-semantic-host-projection-adapter.md","status":"open","created_at":"2026-05-06","updated_at":"2026-05-06","ref_decisions":["DEC-0034"]}],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0033","status":"in_progress","ticket":"frontend-visual-theme-spec-and-css-retirement","title":"Frontend visual theme spec and retirement of host-consumed semantic CSS","created_at":"2026-05-06","updated_at":"2026-05-06","tags":["compiler","compiler-general","frontend","presentation","theming","studio","vscode","lsp","pbs"],"agendas":[{"id":"AGD-0036","file":"AGD-0036-frontend-visual-theme-spec-and-css-retirement.md","status":"accepted","created_at":"2026-05-06","updated_at":"2026-05-06"}],"decisions":[{"id":"DEC-0033","file":"DEC-0033-frontend-visual-theme-spec-and-css-retirement.md","status":"accepted","created_at":"2026-05-06","updated_at":"2026-05-06","ref_agenda":"AGD-0036"}],"plans":[{"id":"PLN-0068","file":"PLN-0068-frontend-visual-contract-and-pbs-migration.md","status":"open","created_at":"2026-05-06","updated_at":"2026-05-06","ref_decisions":["DEC-0033"]},{"id":"PLN-0069","file":"PLN-0069-lsp-visual-theme-transport-and-selection.md","status":"open","created_at":"2026-05-06","updated_at":"2026-05-06","ref_decisions":["DEC-0033"]},{"id":"PLN-0070","file":"PLN-0070-studio-visual-theme-adapter-and-css-retirement.md","status":"open","created_at":"2026-05-06","updated_at":"2026-05-06","ref_decisions":["DEC-0033"]},{"id":"PLN-0071","file":"PLN-0071-vscode-dynamic-theme-translation-adapter.md","status":"open","created_at":"2026-05-06","updated_at":"2026-05-06","ref_decisions":["DEC-0033"]}],"lessons":[]}
|
{"type":"discussion","id":"DSC-0033","status":"in_progress","ticket":"frontend-visual-theme-spec-and-css-retirement","title":"Frontend visual theme spec and retirement of host-consumed semantic CSS","created_at":"2026-05-06","updated_at":"2026-05-06","tags":["compiler","compiler-general","frontend","presentation","theming","studio","vscode","lsp","pbs"],"agendas":[{"id":"AGD-0036","file":"AGD-0036-frontend-visual-theme-spec-and-css-retirement.md","status":"accepted","created_at":"2026-05-06","updated_at":"2026-05-06"}],"decisions":[{"id":"DEC-0033","file":"DEC-0033-frontend-visual-theme-spec-and-css-retirement.md","status":"accepted","created_at":"2026-05-06","updated_at":"2026-05-06","ref_agenda":"AGD-0036"}],"plans":[{"id":"PLN-0068","file":"PLN-0068-frontend-visual-contract-and-pbs-migration.md","status":"open","created_at":"2026-05-06","updated_at":"2026-05-06","ref_decisions":["DEC-0033"]},{"id":"PLN-0069","file":"PLN-0069-lsp-visual-theme-transport-and-selection.md","status":"open","created_at":"2026-05-06","updated_at":"2026-05-06","ref_decisions":["DEC-0033"]},{"id":"PLN-0070","file":"PLN-0070-studio-visual-theme-adapter-and-css-retirement.md","status":"open","created_at":"2026-05-06","updated_at":"2026-05-06","ref_decisions":["DEC-0033"]},{"id":"PLN-0071","file":"PLN-0071-vscode-dynamic-theme-translation-adapter.md","status":"open","created_at":"2026-05-06","updated_at":"2026-05-06","ref_decisions":["DEC-0033"]}],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0032","status":"in_progress","ticket":"studio-new-lsp-api-and-v1-boundary","title":"Novo boundary entre lsp-api, lsp-v1 e a extensao VS Code","created_at":"2026-05-05","updated_at":"2026-05-05","tags":["studio","lsp","vscode","protocol","api","boundary"],"agendas":[{"id":"AGD-0035","file":"AGD-0035-studio-new-lsp-api-and-v1-boundary.md","status":"accepted","created_at":"2026-05-05","updated_at":"2026-05-05"}],"decisions":[{"id":"DEC-0032","file":"DEC-0032-studio-new-lsp-api-and-v1-boundary.md","status":"accepted","created_at":"2026-05-05","updated_at":"2026-05-05","ref_agenda":"AGD-0035"}],"plans":[{"id":"PLN-0065","file":"PLN-0065-lsp-boundary-and-module-scaffolding.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0032"]},{"id":"PLN-0066","file":"PLN-0066-project-scoped-lsp-server-lifecycle-in-studio.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0032"]},{"id":"PLN-0067","file":"PLN-0067-compiler-backed-dumb-lsp-server-baseline.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0032"]}],"lessons":[]}
|
{"type":"discussion","id":"DSC-0032","status":"in_progress","ticket":"studio-new-lsp-api-and-v1-boundary","title":"Novo boundary entre lsp-api, lsp-v1 e a extensao VS Code","created_at":"2026-05-05","updated_at":"2026-05-05","tags":["studio","lsp","vscode","protocol","api","boundary"],"agendas":[{"id":"AGD-0035","file":"AGD-0035-studio-new-lsp-api-and-v1-boundary.md","status":"accepted","created_at":"2026-05-05","updated_at":"2026-05-05"}],"decisions":[{"id":"DEC-0032","file":"DEC-0032-studio-new-lsp-api-and-v1-boundary.md","status":"accepted","created_at":"2026-05-05","updated_at":"2026-05-05","ref_agenda":"AGD-0035"}],"plans":[{"id":"PLN-0065","file":"PLN-0065-lsp-boundary-and-module-scaffolding.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0032"]},{"id":"PLN-0066","file":"PLN-0066-project-scoped-lsp-server-lifecycle-in-studio.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0032"]},{"id":"PLN-0067","file":"PLN-0067-compiler-backed-dumb-lsp-server-baseline.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0032"]}],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0031","status":"in_progress","ticket":"studio-editor-workspace-cleanup","title":"Limpeza completa do Workspace Editor do Studio e remoção dos acoplamentos legados","created_at":"2026-05-05","updated_at":"2026-05-05","tags":["studio","editor","cleanup","vfs","lsp","migration"],"agendas":[{"id":"AGD-0034","file":"AGD-0034-vscode-editor-migration-feasibility.md","status":"accepted","created_at":"2026-05-05","updated_at":"2026-05-05"}],"decisions":[{"id":"DEC-0031","file":"DEC-0031-studio-editor-workspace-cleanup.md","status":"in_progress","created_at":"2026-05-05","updated_at":"2026-05-05","ref_agenda":"AGD-0034"}],"plans":[{"id":"PLN-0062","file":"PLN-0062-shell-session-and-state-cleanup-after-editor-removal.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0063","file":"PLN-0063-legacy-lsp-and-vfs-module-removal-with-runtime-preservation.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0064","file":"PLN-0064-normative-test-and-lesson-cleanup-for-editor-stack-removal.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0031"]}],"lessons":[]}
|
{"type":"discussion","id":"DSC-0031","status":"in_progress","ticket":"studio-editor-workspace-cleanup","title":"Limpeza completa do Workspace Editor do Studio e remoção dos acoplamentos legados","created_at":"2026-05-05","updated_at":"2026-05-05","tags":["studio","editor","cleanup","vfs","lsp","migration"],"agendas":[{"id":"AGD-0034","file":"AGD-0034-vscode-editor-migration-feasibility.md","status":"accepted","created_at":"2026-05-05","updated_at":"2026-05-05"}],"decisions":[{"id":"DEC-0031","file":"DEC-0031-studio-editor-workspace-cleanup.md","status":"in_progress","created_at":"2026-05-05","updated_at":"2026-05-05","ref_agenda":"AGD-0034"}],"plans":[{"id":"PLN-0062","file":"PLN-0062-shell-session-and-state-cleanup-after-editor-removal.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0063","file":"PLN-0063-legacy-lsp-and-vfs-module-removal-with-runtime-preservation.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0064","file":"PLN-0064-normative-test-and-lesson-cleanup-for-editor-stack-removal.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0031"]}],"lessons":[]}
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
id: PLN-0072
|
id: PLN-0072
|
||||||
ticket: frontend-semantic-host-projection-flexibility
|
ticket: frontend-semantic-host-projection-flexibility
|
||||||
title: Frontend Semantic Host Projection Contract
|
title: Frontend Semantic Host Projection Contract
|
||||||
status: open
|
status: done
|
||||||
created: 2026-05-06
|
created: 2026-05-06
|
||||||
completed:
|
completed: 2026-05-06
|
||||||
tags: [compiler, compiler-general, frontend, semantics, host-projection, vscode, pbs]
|
tags: [compiler, compiler-general, frontend, semantics, host-projection, vscode, pbs]
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ public class PBSDefinitions {
|
|||||||
.semanticPresentation(new FrontendSemanticPresentationSpec(
|
.semanticPresentation(new FrontendSemanticPresentationSpec(
|
||||||
PbsSemanticKind.semanticKeys(),
|
PbsSemanticKind.semanticKeys(),
|
||||||
List.of(PBSFrontendThemes.DEFAULT_PBS),
|
List.of(PBSFrontendThemes.DEFAULT_PBS),
|
||||||
"pbs-default"))
|
"pbs-default",
|
||||||
|
List.of(PBSFrontendHostProjections.VSCODE)))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,97 @@
|
|||||||
|
package p.studio.compiler;
|
||||||
|
|
||||||
|
import p.studio.compiler.models.FrontendHostProjectionEntrySpec;
|
||||||
|
import p.studio.compiler.models.FrontendHostProjectionSpec;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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())
|
||||||
|
.map(PBSFrontendHostProjections::mapVscodeProjection)
|
||||||
|
.toList());
|
||||||
|
|
||||||
|
private PBSFrontendHostProjections() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FrontendHostProjectionEntrySpec mapVscodeProjection(final p.studio.compiler.pbs.PbsSemanticKind kind) {
|
||||||
|
return switch (kind) {
|
||||||
|
case COMMENT -> projection(COMMENT.semanticKey(), "comment");
|
||||||
|
case STRING -> projection(STRING.semanticKey(), "string");
|
||||||
|
case NUMBER -> projection(NUMBER.semanticKey(), "number");
|
||||||
|
case LITERAL -> projection(LITERAL.semanticKey(), "enumMember", "variable");
|
||||||
|
case LIFECYCLE -> projection(LIFECYCLE.semanticKey(), "function", "variable", "async");
|
||||||
|
case KEYWORD -> projection(KEYWORD.semanticKey(), "keyword");
|
||||||
|
case OPERATOR -> projection(OPERATOR.semanticKey(), "operator", "keyword");
|
||||||
|
case PUNCTUATION -> projection(PUNCTUATION.semanticKey(), "operator", "keyword");
|
||||||
|
case FUNCTION -> projection(FUNCTION.semanticKey(), "function");
|
||||||
|
case METHOD -> projection(METHOD.semanticKey(), "method", "function");
|
||||||
|
case CONSTRUCTOR -> projection(CONSTRUCTOR.semanticKey(), "class", "type", "declaration");
|
||||||
|
case STRUCT -> projection(STRUCT.semanticKey(), "struct", "type");
|
||||||
|
case CONTRACT -> projection(CONTRACT.semanticKey(), "interface", "type");
|
||||||
|
case HOST -> projection(HOST.semanticKey(), "class", "type", "defaultLibrary");
|
||||||
|
case BUILTIN_TYPE -> projection(BUILTIN_TYPE.semanticKey(), "type", "variable", "defaultLibrary");
|
||||||
|
case SERVICE -> projection(SERVICE.semanticKey(), "class", "type", "defaultLibrary");
|
||||||
|
case ERROR -> projection(ERROR.semanticKey(), "enum", "type");
|
||||||
|
case ENUM -> projection(ENUM.semanticKey(), "enum", "type");
|
||||||
|
case CALLBACK -> projection(CALLBACK.semanticKey(), "interface", "type");
|
||||||
|
case GLOBAL -> projection(GLOBAL.semanticKey(), "variable", "variable", "readonly");
|
||||||
|
case CONST -> projection(CONST.semanticKey(), "variable", "variable", "readonly");
|
||||||
|
case IMPLEMENTS -> projection(IMPLEMENTS.semanticKey(), "keyword");
|
||||||
|
case IDENTIFIER -> projection(IDENTIFIER.semanticKey(), "variable");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FrontendHostProjectionEntrySpec projection(
|
||||||
|
final String semanticKey,
|
||||||
|
final String hostTokenType) {
|
||||||
|
return new FrontendHostProjectionEntrySpec(semanticKey, hostTokenType, java.util.List.of(), hostTokenType, java.util.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());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FrontendHostProjectionEntrySpec projection(
|
||||||
|
final String semanticKey,
|
||||||
|
final String hostTokenType,
|
||||||
|
final String fallbackTokenType,
|
||||||
|
final String modifier) {
|
||||||
|
return new FrontendHostProjectionEntrySpec(
|
||||||
|
semanticKey,
|
||||||
|
hostTokenType,
|
||||||
|
java.util.List.of(modifier),
|
||||||
|
fallbackTokenType,
|
||||||
|
java.util.List.of(modifier));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
package p.studio.compiler.pbs;
|
package p.studio.compiler.pbs;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import p.studio.compiler.PBSFrontendHostProjections;
|
||||||
import p.studio.compiler.PBSDefinitions;
|
import p.studio.compiler.PBSDefinitions;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
@ -21,6 +22,7 @@ final class PbsSemanticPresentationContractTest {
|
|||||||
assertEquals("#d9e2ec", presentation.defaultTheme().editorPalette().baseForeground());
|
assertEquals("#d9e2ec", presentation.defaultTheme().editorPalette().baseForeground());
|
||||||
assertEquals("#569cd6", presentation.defaultTheme().requireTokenStyle("pbs-keyword").foreground());
|
assertEquals("#569cd6", presentation.defaultTheme().requireTokenStyle("pbs-keyword").foreground());
|
||||||
assertTrue(presentation.defaultTheme().requireTokenStyle("pbs-comment").italic());
|
assertTrue(presentation.defaultTheme().requireTokenStyle("pbs-comment").italic());
|
||||||
|
assertEquals(PBSFrontendHostProjections.VSCODE_HOST_ID, presentation.requireHostProjection("vscode").hostId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -32,4 +34,19 @@ final class PbsSemanticPresentationContractTest {
|
|||||||
PbsSemanticKind.semanticKeys(),
|
PbsSemanticKind.semanticKeys(),
|
||||||
defaultTheme.tokenStyles().stream().map(tokenStyle -> tokenStyle.semanticKey()).toList());
|
defaultTheme.tokenStyles().stream().map(tokenStyle -> tokenStyle.semanticKey()).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCoverTheWholePbsSemanticVocabularyInTheVscodeProjection() {
|
||||||
|
final var presentation = PBSDefinitions.PBS.getSemanticPresentation();
|
||||||
|
final var vscodeProjection = presentation.requireHostProjection(PBSFrontendHostProjections.VSCODE_HOST_ID);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
PbsSemanticKind.semanticKeys(),
|
||||||
|
vscodeProjection.tokenProjections().stream().map(tokenProjection -> tokenProjection.semanticKey()).toList());
|
||||||
|
assertEquals("keyword", vscodeProjection.requireProjection(PbsSemanticKind.KEYWORD.semanticKey()).hostTokenType());
|
||||||
|
assertEquals("type", vscodeProjection.requireProjection(PbsSemanticKind.STRUCT.semanticKey()).fallbackTokenType());
|
||||||
|
assertEquals(
|
||||||
|
java.util.List.of("readonly"),
|
||||||
|
vscodeProjection.requireProjection(PbsSemanticKind.GLOBAL.semanticKey()).hostTokenModifiers());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,38 @@
|
|||||||
|
package p.studio.compiler.models;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record FrontendHostProjectionEntrySpec(
|
||||||
|
String semanticKey,
|
||||||
|
String hostTokenType,
|
||||||
|
List<String> hostTokenModifiers,
|
||||||
|
String fallbackTokenType,
|
||||||
|
List<String> fallbackTokenModifiers) {
|
||||||
|
|
||||||
|
public FrontendHostProjectionEntrySpec {
|
||||||
|
semanticKey = requireText(semanticKey, "semanticKey");
|
||||||
|
hostTokenType = requireText(hostTokenType, "hostTokenType");
|
||||||
|
hostTokenModifiers = normalizeList(hostTokenModifiers, "hostTokenModifiers");
|
||||||
|
fallbackTokenType = requireText(fallbackTokenType, "fallbackTokenType");
|
||||||
|
fallbackTokenModifiers = normalizeList(fallbackTokenModifiers, "fallbackTokenModifiers");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> normalizeList(
|
||||||
|
final List<String> values,
|
||||||
|
final String field) {
|
||||||
|
return List.copyOf(Objects.requireNonNull(values, field).stream()
|
||||||
|
.map(value -> requireText(value, field + " entry"))
|
||||||
|
.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
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,48 @@
|
|||||||
|
package p.studio.compiler.models;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public record FrontendHostProjectionSpec(
|
||||||
|
String hostId,
|
||||||
|
List<FrontendHostProjectionEntrySpec> tokenProjections) {
|
||||||
|
|
||||||
|
public FrontendHostProjectionSpec {
|
||||||
|
hostId = requireText(hostId, "hostId");
|
||||||
|
tokenProjections = List.copyOf(Objects.requireNonNull(tokenProjections, "tokenProjections"));
|
||||||
|
if (tokenProjections.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("tokenProjections must not be empty");
|
||||||
|
}
|
||||||
|
final Map<String, FrontendHostProjectionEntrySpec> bySemanticKey = new LinkedHashMap<>();
|
||||||
|
for (final FrontendHostProjectionEntrySpec tokenProjection : tokenProjections) {
|
||||||
|
final FrontendHostProjectionEntrySpec previous = bySemanticKey.put(tokenProjection.semanticKey(), tokenProjection);
|
||||||
|
if (previous != null) {
|
||||||
|
throw new IllegalArgumentException("duplicate host projection for semantic key: " + tokenProjection.semanticKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<FrontendHostProjectionEntrySpec> projectionFor(final String semanticKey) {
|
||||||
|
return tokenProjections.stream()
|
||||||
|
.filter(tokenProjection -> tokenProjection.semanticKey().equals(semanticKey))
|
||||||
|
.findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrontendHostProjectionEntrySpec requireProjection(final String semanticKey) {
|
||||||
|
return projectionFor(semanticKey)
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("missing host projection 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,12 +6,21 @@ import java.util.Objects;
|
|||||||
public record FrontendSemanticPresentationSpec(
|
public record FrontendSemanticPresentationSpec(
|
||||||
List<String> semanticKeys,
|
List<String> semanticKeys,
|
||||||
List<FrontendVisualThemeSpec> themes,
|
List<FrontendVisualThemeSpec> themes,
|
||||||
String defaultThemeId) {
|
String defaultThemeId,
|
||||||
|
List<FrontendHostProjectionSpec> hostProjections) {
|
||||||
|
|
||||||
|
public FrontendSemanticPresentationSpec(
|
||||||
|
final List<String> semanticKeys,
|
||||||
|
final List<FrontendVisualThemeSpec> themes,
|
||||||
|
final String defaultThemeId) {
|
||||||
|
this(semanticKeys, themes, defaultThemeId, List.of());
|
||||||
|
}
|
||||||
|
|
||||||
public FrontendSemanticPresentationSpec {
|
public FrontendSemanticPresentationSpec {
|
||||||
semanticKeys = List.copyOf(Objects.requireNonNull(semanticKeys, "semanticKeys"));
|
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"));
|
||||||
if (semanticKeys.isEmpty() && themes.isEmpty()) {
|
if (semanticKeys.isEmpty() && 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");
|
||||||
@ -25,10 +34,27 @@ public record FrontendSemanticPresentationSpec(
|
|||||||
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()
|
||||||
|
.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())) {
|
||||||
|
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(), "");
|
return new FrontendSemanticPresentationSpec(List.of(), List.of(), "", List.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
public FrontendVisualThemeSpec defaultTheme() {
|
public FrontendVisualThemeSpec defaultTheme() {
|
||||||
@ -42,7 +68,18 @@ public record FrontendSemanticPresentationSpec(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
return semanticKeys.isEmpty() && themes.isEmpty() && defaultThemeId.isEmpty();
|
return semanticKeys.isEmpty() && themes.isEmpty() && defaultThemeId.isEmpty() && hostProjections.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public java.util.Optional<FrontendHostProjectionSpec> hostProjection(final String hostId) {
|
||||||
|
return hostProjections.stream()
|
||||||
|
.filter(hostProjection -> hostProjection.hostId().equals(requireText(hostId, "hostId")))
|
||||||
|
.findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrontendHostProjectionSpec requireHostProjection(final String hostId) {
|
||||||
|
return hostProjection(hostId)
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("missing host projection for hostId: " + hostId));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String requireText(
|
private static String requireText(
|
||||||
|
|||||||
@ -0,0 +1,85 @@
|
|||||||
|
package p.studio.compiler.models;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
final class FrontendSemanticPresentationSpecTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeHostProjectionByHostId() {
|
||||||
|
final var presentation = new FrontendSemanticPresentationSpec(
|
||||||
|
List.of("demo-key"),
|
||||||
|
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)))),
|
||||||
|
"demo-theme",
|
||||||
|
List.of(new FrontendHostProjectionSpec(
|
||||||
|
"vscode",
|
||||||
|
List.of(new FrontendHostProjectionEntrySpec(
|
||||||
|
"demo-key",
|
||||||
|
"keyword",
|
||||||
|
List.of("declaration"),
|
||||||
|
"variable",
|
||||||
|
List.of())))));
|
||||||
|
|
||||||
|
final var vscodeProjection = presentation.requireHostProjection("vscode").requireProjection("demo-key");
|
||||||
|
assertEquals("keyword", vscodeProjection.hostTokenType());
|
||||||
|
assertEquals(List.of("declaration"), vscodeProjection.hostTokenModifiers());
|
||||||
|
assertEquals("variable", vscodeProjection.fallbackTokenType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRejectDuplicateHostIds() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> new FrontendSemanticPresentationSpec(
|
||||||
|
List.of("demo-key"),
|
||||||
|
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)))),
|
||||||
|
"demo-theme",
|
||||||
|
List.of(
|
||||||
|
new FrontendHostProjectionSpec(
|
||||||
|
"vscode",
|
||||||
|
List.of(new FrontendHostProjectionEntrySpec(
|
||||||
|
"demo-key",
|
||||||
|
"keyword",
|
||||||
|
List.of(),
|
||||||
|
"variable",
|
||||||
|
List.of()))),
|
||||||
|
new FrontendHostProjectionSpec(
|
||||||
|
"vscode",
|
||||||
|
List.of(new FrontendHostProjectionEntrySpec(
|
||||||
|
"demo-key",
|
||||||
|
"type",
|
||||||
|
List.of(),
|
||||||
|
"variable",
|
||||||
|
List.of()))))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRejectProjectionForUnknownSemanticKey() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> new FrontendSemanticPresentationSpec(
|
||||||
|
List.of("demo-key"),
|
||||||
|
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)))),
|
||||||
|
"demo-theme",
|
||||||
|
List.of(new FrontendHostProjectionSpec(
|
||||||
|
"vscode",
|
||||||
|
List.of(new FrontendHostProjectionEntrySpec(
|
||||||
|
"unknown-key",
|
||||||
|
"keyword",
|
||||||
|
List.of(),
|
||||||
|
"variable",
|
||||||
|
List.of()))))));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user