implements PLN-0073
This commit is contained in:
parent
9d75f2f0e2
commit
e5ab4cd021
@ -1,5 +1,5 @@
|
||||
{"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":"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-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":"done","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-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":[]}
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
id: PLN-0073
|
||||
ticket: frontend-semantic-host-projection-flexibility
|
||||
title: LSP Semantic Host Projection Transport
|
||||
status: open
|
||||
status: done
|
||||
created: 2026-05-06
|
||||
completed:
|
||||
completed: 2026-05-06
|
||||
tags: [lsp, compiler, frontend, semantics, host-projection, vscode, protocol]
|
||||
---
|
||||
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
package p.studio.lsp.messages;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public record BaselineSemanticHostProjection(
|
||||
String hostId,
|
||||
List<BaselineSemanticHostProjectionEntry> tokenProjections) {
|
||||
|
||||
public BaselineSemanticHostProjection {
|
||||
hostId = requireText(hostId, "hostId");
|
||||
tokenProjections = List.copyOf(Objects.requireNonNull(tokenProjections, "tokenProjections"));
|
||||
if (tokenProjections.isEmpty()) {
|
||||
throw new IllegalArgumentException("tokenProjections 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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package p.studio.lsp.messages;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public record BaselineSemanticHostProjectionEntry(
|
||||
String semanticKey,
|
||||
String hostTokenType,
|
||||
List<String> hostTokenModifiers,
|
||||
String fallbackTokenType,
|
||||
List<String> fallbackTokenModifiers) {
|
||||
|
||||
public BaselineSemanticHostProjectionEntry {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -9,6 +9,7 @@ public record BaselineServerDescription(
|
||||
String frontendLanguageId,
|
||||
boolean hoverSupported,
|
||||
List<String> semanticTokenTypes,
|
||||
List<BaselineSemanticHostProjection> semanticHostProjections,
|
||||
List<BaselineVisualTheme> visualThemes,
|
||||
String activeVisualThemeId) {
|
||||
|
||||
@ -17,8 +18,15 @@ public record BaselineServerDescription(
|
||||
version = requireText(version, "version");
|
||||
frontendLanguageId = requireText(frontendLanguageId, "frontendLanguageId");
|
||||
semanticTokenTypes = List.copyOf(Objects.requireNonNull(semanticTokenTypes, "semanticTokenTypes"));
|
||||
semanticHostProjections = List.copyOf(Objects.requireNonNull(semanticHostProjections, "semanticHostProjections"));
|
||||
visualThemes = List.copyOf(Objects.requireNonNull(visualThemes, "visualThemes"));
|
||||
activeVisualThemeId = requireText(activeVisualThemeId, "activeVisualThemeId");
|
||||
final var hostIds = new java.util.LinkedHashSet<String>();
|
||||
for (final BaselineSemanticHostProjection hostProjection : semanticHostProjections) {
|
||||
if (!hostIds.add(hostProjection.hostId())) {
|
||||
throw new IllegalArgumentException("duplicate semantic host projection for hostId: " + hostProjection.hostId());
|
||||
}
|
||||
}
|
||||
if (visualThemes.stream().map(BaselineVisualTheme::themeId).noneMatch(activeVisualThemeId::equals)) {
|
||||
throw new IllegalArgumentException("activeVisualThemeId must reference one of the declared visual themes");
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@ 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.models.FrontendHostProjectionEntrySpec;
|
||||
import p.studio.compiler.models.FrontendHostProjectionSpec;
|
||||
import p.studio.compiler.PBSDefinitions;
|
||||
import p.studio.compiler.models.FrontendTokenStyleSpec;
|
||||
import p.studio.compiler.models.FrontendVisualThemeSpec;
|
||||
@ -33,14 +35,16 @@ public record CompilerLanguageServiceBridge() implements LanguageServiceBridge {
|
||||
@Override
|
||||
public BaselineServerDescription describeServer(final LspProjectContext context) {
|
||||
Objects.requireNonNull(context, "context");
|
||||
final var presentation = PBSDefinitions.PBS.getSemanticPresentation();
|
||||
return new BaselineServerDescription(
|
||||
"Prometeu Studio LSP",
|
||||
"0.1.0",
|
||||
context.languageId(),
|
||||
true,
|
||||
PBSDefinitions.PBS.getSemanticPresentation().semanticKeys(),
|
||||
PBSDefinitions.PBS.getSemanticPresentation().themes().stream().map(this::mapVisualTheme).toList(),
|
||||
PBSDefinitions.PBS.getSemanticPresentation().defaultThemeId());
|
||||
presentation.semanticKeys(),
|
||||
presentation.hostProjections().stream().map(this::mapSemanticHostProjection).toList(),
|
||||
presentation.themes().stream().map(this::mapVisualTheme).toList(),
|
||||
presentation.defaultThemeId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -205,6 +209,21 @@ public record CompilerLanguageServiceBridge() implements LanguageServiceBridge {
|
||||
theme.tokenStyles().stream().map(this::mapTokenStyle).toList());
|
||||
}
|
||||
|
||||
private BaselineSemanticHostProjection mapSemanticHostProjection(final FrontendHostProjectionSpec hostProjection) {
|
||||
return new BaselineSemanticHostProjection(
|
||||
hostProjection.hostId(),
|
||||
hostProjection.tokenProjections().stream().map(this::mapSemanticHostProjectionEntry).toList());
|
||||
}
|
||||
|
||||
private BaselineSemanticHostProjectionEntry mapSemanticHostProjectionEntry(final FrontendHostProjectionEntrySpec tokenProjection) {
|
||||
return new BaselineSemanticHostProjectionEntry(
|
||||
tokenProjection.semanticKey(),
|
||||
tokenProjection.hostTokenType(),
|
||||
tokenProjection.hostTokenModifiers(),
|
||||
tokenProjection.fallbackTokenType(),
|
||||
tokenProjection.fallbackTokenModifiers());
|
||||
}
|
||||
|
||||
private BaselineEditorPalette mapEditorPalette(final FrontendEditorPaletteSpec palette) {
|
||||
return new BaselineEditorPalette(
|
||||
palette.baseForeground(),
|
||||
|
||||
@ -33,7 +33,12 @@ public final class Lsp4jProtocolMessageMapper implements ProtocolMessageMapper {
|
||||
Map.of(
|
||||
"frontendLanguageId", description.frontendLanguageId(),
|
||||
"activeVisualThemeId", description.activeVisualThemeId(),
|
||||
"visualThemes", description.visualThemes().stream().map(this::mapVisualThemePayload).toList())));
|
||||
"visualThemes", description.visualThemes().stream().map(this::mapVisualThemePayload).toList()),
|
||||
"prometeuSemanticHostProjections",
|
||||
Map.of(
|
||||
"frontendLanguageId", description.frontendLanguageId(),
|
||||
"semanticKeys", description.semanticTokenTypes(),
|
||||
"hostProjections", description.semanticHostProjections().stream().map(this::mapSemanticHostProjectionPayload).toList())));
|
||||
|
||||
final InitializeResult result = new InitializeResult(capabilities);
|
||||
final ServerInfo serverInfo = new ServerInfo();
|
||||
@ -151,4 +156,16 @@ public final class Lsp4jProtocolMessageMapper implements ProtocolMessageMapper {
|
||||
"underline", tokenStyle.underline())).toList());
|
||||
return payload;
|
||||
}
|
||||
|
||||
private Map<String, Object> mapSemanticHostProjectionPayload(final BaselineSemanticHostProjection hostProjection) {
|
||||
final Map<String, Object> payload = new LinkedHashMap<>();
|
||||
payload.put("hostId", hostProjection.hostId());
|
||||
payload.put("tokenProjections", hostProjection.tokenProjections().stream().map(tokenProjection -> Map.of(
|
||||
"semanticKey", tokenProjection.semanticKey(),
|
||||
"hostTokenType", tokenProjection.hostTokenType(),
|
||||
"hostTokenModifiers", tokenProjection.hostTokenModifiers(),
|
||||
"fallbackTokenType", tokenProjection.fallbackTokenType(),
|
||||
"fallbackTokenModifiers", tokenProjection.fallbackTokenModifiers())).toList());
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,6 +64,11 @@ class CompilerLanguageServiceBridgeTest {
|
||||
|
||||
assertEquals("pbs", description.frontendLanguageId());
|
||||
assertEquals("pbs-default", description.activeVisualThemeId());
|
||||
assertEquals(1, description.semanticHostProjections().size());
|
||||
assertEquals("vscode", description.semanticHostProjections().getFirst().hostId());
|
||||
assertTrue(description.semanticHostProjections().getFirst().tokenProjections().stream()
|
||||
.anyMatch(tokenProjection -> tokenProjection.semanticKey().equals("pbs-keyword")
|
||||
&& tokenProjection.hostTokenType().equals("keyword")));
|
||||
assertEquals(1, description.visualThemes().size());
|
||||
assertEquals("PBS Default", description.visualThemes().getFirst().displayName());
|
||||
assertEquals("#d9e2ec", description.visualThemes().getFirst().editorPalette().baseForeground());
|
||||
|
||||
@ -14,6 +14,8 @@ 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.BaselineSemanticHostProjection;
|
||||
import p.studio.lsp.messages.BaselineSemanticHostProjectionEntry;
|
||||
import p.studio.lsp.messages.BaselineSemanticTokens;
|
||||
import p.studio.lsp.messages.BaselineServerDescription;
|
||||
import p.studio.lsp.messages.BaselineTokenStyle;
|
||||
@ -89,6 +91,14 @@ class PrometeuLanguageServerTest {
|
||||
"pbs",
|
||||
true,
|
||||
List.of(),
|
||||
List.of(new BaselineSemanticHostProjection(
|
||||
"vscode",
|
||||
List.of(new BaselineSemanticHostProjectionEntry(
|
||||
"demo-keyword",
|
||||
"keyword",
|
||||
List.of(),
|
||||
"variable",
|
||||
List.of())))),
|
||||
List.of(new BaselineVisualTheme(
|
||||
"demo-default",
|
||||
"Demo Default",
|
||||
|
||||
@ -0,0 +1,62 @@
|
||||
package p.studio.lsp.services.protocol.mapping;
|
||||
|
||||
import org.eclipse.lsp4j.InitializeResult;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import p.studio.lsp.messages.BaselineEditorPalette;
|
||||
import p.studio.lsp.messages.BaselineSemanticHostProjection;
|
||||
import p.studio.lsp.messages.BaselineSemanticHostProjectionEntry;
|
||||
import p.studio.lsp.messages.BaselineServerDescription;
|
||||
import p.studio.lsp.messages.BaselineTokenStyle;
|
||||
import p.studio.lsp.messages.BaselineVisualTheme;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
|
||||
final class Lsp4jProtocolMessageMapperTest {
|
||||
|
||||
@Test
|
||||
void initializeResultPublishesCanonicalKeysAndHostProjectionPayload() {
|
||||
final var mapper = new Lsp4jProtocolMessageMapper();
|
||||
final var description = new BaselineServerDescription(
|
||||
"demo",
|
||||
"1",
|
||||
"pbs",
|
||||
true,
|
||||
List.of("demo-keyword"),
|
||||
List.of(new BaselineSemanticHostProjection(
|
||||
"vscode",
|
||||
List.of(new BaselineSemanticHostProjectionEntry(
|
||||
"demo-keyword",
|
||||
"keyword",
|
||||
List.of("declaration"),
|
||||
"variable",
|
||||
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");
|
||||
|
||||
final InitializeResult result = mapper.mapInitializeResult(description);
|
||||
|
||||
final var experimental = assertInstanceOf(Map.class, result.getCapabilities().getExperimental());
|
||||
final var semanticPayload = assertInstanceOf(Map.class, experimental.get("prometeuSemanticHostProjections"));
|
||||
assertEquals("pbs", semanticPayload.get("frontendLanguageId"));
|
||||
assertEquals(List.of("demo-keyword"), semanticPayload.get("semanticKeys"));
|
||||
|
||||
final var hostProjectionPayloads = assertInstanceOf(List.class, semanticPayload.get("hostProjections"));
|
||||
final var firstHostProjection = assertInstanceOf(Map.class, hostProjectionPayloads.getFirst());
|
||||
assertEquals("vscode", firstHostProjection.get("hostId"));
|
||||
|
||||
final var tokenProjectionPayloads = assertInstanceOf(List.class, firstHostProjection.get("tokenProjections"));
|
||||
final var firstTokenProjection = assertInstanceOf(Map.class, tokenProjectionPayloads.getFirst());
|
||||
assertEquals("demo-keyword", firstTokenProjection.get("semanticKey"));
|
||||
assertEquals("keyword", firstTokenProjection.get("hostTokenType"));
|
||||
assertEquals(List.of("declaration"), firstTokenProjection.get("hostTokenModifiers"));
|
||||
assertEquals("variable", firstTokenProjection.get("fallbackTokenType"));
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user