implements PLN-0074
This commit is contained in:
parent
e5ab4cd021
commit
7ae2e94d63
@ -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":"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-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":"done","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-0074
|
||||
ticket: frontend-semantic-host-projection-flexibility
|
||||
title: VS Code Semantic Host Projection Adapter
|
||||
status: open
|
||||
status: done
|
||||
created: 2026-05-06
|
||||
completed:
|
||||
completed: 2026-05-06
|
||||
tags: [vscode, extension, frontend, semantics, host-projection, adapter]
|
||||
---
|
||||
|
||||
|
||||
@ -45,6 +45,7 @@ let reconnectContext;
|
||||
let stoppingClient = false;
|
||||
let lastAppliedThemeSignature;
|
||||
const RECONNECT_DELAY_MS = 2000;
|
||||
const VSCODE_HOST_ID = "vscode";
|
||||
function activate(context) {
|
||||
reconnectContext = context;
|
||||
output = vscode.window.createOutputChannel("Prometeu LSP");
|
||||
@ -207,21 +208,30 @@ function clearReconnectTimer() {
|
||||
reconnectTimer = undefined;
|
||||
}
|
||||
async function applyPrometeuVisualTheme(initializeResult) {
|
||||
const payload = extractPrometeuVisualThemesPayload(initializeResult);
|
||||
if (!payload) {
|
||||
const visualThemesPayload = extractPrometeuVisualThemesPayload(initializeResult);
|
||||
if (!visualThemesPayload) {
|
||||
output.appendLine("Prometeu visual theme payload not present in initialize result.");
|
||||
return;
|
||||
}
|
||||
const activeTheme = payload.visualThemes.find((theme) => theme.themeId === payload.activeVisualThemeId);
|
||||
const semanticProjectionPayload = extractPrometeuSemanticHostProjectionsPayload(initializeResult);
|
||||
const vscodeProjection = selectHostProjection(semanticProjectionPayload, VSCODE_HOST_ID);
|
||||
if (!semanticProjectionPayload) {
|
||||
output.appendLine("Prometeu semantic host projection payload not present in initialize result.");
|
||||
}
|
||||
else if (!vscodeProjection) {
|
||||
output.appendLine("Prometeu semantic host projection payload does not declare a vscode projection.");
|
||||
}
|
||||
const activeTheme = visualThemesPayload.visualThemes.find((theme) => theme.themeId === visualThemesPayload.activeVisualThemeId);
|
||||
if (!activeTheme) {
|
||||
output.appendLine(`Prometeu visual theme '${payload.activeVisualThemeId}' was not declared by the server.`);
|
||||
output.appendLine(`Prometeu visual theme '${visualThemesPayload.activeVisualThemeId}' was not declared by the server.`);
|
||||
return;
|
||||
}
|
||||
const themeSignature = JSON.stringify({
|
||||
languageId: payload.frontendLanguageId,
|
||||
languageId: visualThemesPayload.frontendLanguageId,
|
||||
themeId: activeTheme.themeId,
|
||||
editorPalette: activeTheme.editorPalette,
|
||||
tokenStyles: activeTheme.tokenStyles
|
||||
tokenStyles: activeTheme.tokenStyles,
|
||||
semanticProjection: vscodeProjection ?? null
|
||||
});
|
||||
if (themeSignature === lastAppliedThemeSignature) {
|
||||
output.appendLine(`Prometeu visual theme '${activeTheme.themeId}' is already applied.`);
|
||||
@ -234,7 +244,7 @@ async function applyPrometeuVisualTheme(initializeResult) {
|
||||
const existingSemanticCustomizations = configuration.get("editor.semanticTokenColorCustomizations") ?? {};
|
||||
const semanticRules = {
|
||||
...(readObject(existingSemanticCustomizations.rules)),
|
||||
...buildSemanticTokenRules(payload.frontendLanguageId, activeTheme)
|
||||
...buildSemanticTokenRules(visualThemesPayload.frontendLanguageId, activeTheme, vscodeProjection)
|
||||
};
|
||||
await configuration.update("editor.semanticTokenColorCustomizations", {
|
||||
...existingSemanticCustomizations,
|
||||
@ -249,7 +259,7 @@ async function applyPrometeuVisualTheme(initializeResult) {
|
||||
"editorLineNumber.foreground": activeTheme.editorPalette.lineNumberForeground
|
||||
}, configurationTarget);
|
||||
lastAppliedThemeSignature = themeSignature;
|
||||
output.appendLine(`Applied Prometeu visual theme '${activeTheme.themeId}' for language '${payload.frontendLanguageId}'.`);
|
||||
output.appendLine(`Applied Prometeu visual theme '${activeTheme.themeId}' for language '${visualThemesPayload.frontendLanguageId}'.`);
|
||||
}
|
||||
function extractPrometeuVisualThemesPayload(initializeResult) {
|
||||
if (!initializeResult || typeof initializeResult !== "object") {
|
||||
@ -266,23 +276,59 @@ function extractPrometeuVisualThemesPayload(initializeResult) {
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
function buildSemanticTokenRules(languageId, theme) {
|
||||
const rules = {};
|
||||
for (const tokenStyle of theme.tokenStyles) {
|
||||
const selector = `${tokenStyle.semanticKey}:${languageId}`;
|
||||
if (!tokenStyle.italic && !tokenStyle.bold && !tokenStyle.underline) {
|
||||
rules[selector] = tokenStyle.foreground;
|
||||
continue;
|
||||
function extractPrometeuSemanticHostProjectionsPayload(initializeResult) {
|
||||
if (!initializeResult || typeof initializeResult !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
rules[selector] = {
|
||||
const capabilities = readObject(initializeResult.capabilities);
|
||||
const experimental = readObject(capabilities.experimental);
|
||||
const payload = readObject(experimental.prometeuSemanticHostProjections);
|
||||
if (typeof payload.frontendLanguageId !== "string" || !Array.isArray(payload.semanticKeys) || !Array.isArray(payload.hostProjections)) {
|
||||
return undefined;
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
function selectHostProjection(payload, hostId) {
|
||||
if (!payload) {
|
||||
return undefined;
|
||||
}
|
||||
return payload.hostProjections.find((projection) => projection.hostId === hostId);
|
||||
}
|
||||
function buildSemanticTokenRules(languageId, theme, hostProjection) {
|
||||
const rules = {};
|
||||
const projectionBySemanticKey = new Map((hostProjection?.tokenProjections ?? []).map((projection) => [projection.semanticKey, projection]));
|
||||
for (const tokenStyle of theme.tokenStyles) {
|
||||
const styleValue = !tokenStyle.italic && !tokenStyle.bold && !tokenStyle.underline
|
||||
? tokenStyle.foreground
|
||||
: {
|
||||
foreground: tokenStyle.foreground,
|
||||
...(tokenStyle.italic ? { italic: true } : {}),
|
||||
...(tokenStyle.bold ? { bold: true } : {}),
|
||||
...(tokenStyle.underline ? { underline: true } : {})
|
||||
};
|
||||
rules[`${tokenStyle.semanticKey}:${languageId}`] = styleValue;
|
||||
const projection = projectionBySemanticKey.get(tokenStyle.semanticKey);
|
||||
if (!projection) {
|
||||
continue;
|
||||
}
|
||||
const projectedSelector = buildSemanticSelector(languageId, projection.hostTokenType, projection.hostTokenModifiers);
|
||||
if (!(projectedSelector in rules)) {
|
||||
rules[projectedSelector] = styleValue;
|
||||
}
|
||||
const fallbackSelector = buildSemanticSelector(languageId, projection.fallbackTokenType, projection.fallbackTokenModifiers);
|
||||
if (!(fallbackSelector in rules)) {
|
||||
rules[fallbackSelector] = styleValue;
|
||||
}
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
function buildSemanticSelector(languageId, tokenType, modifiers) {
|
||||
const normalizedModifiers = [...modifiers].sort();
|
||||
if (normalizedModifiers.length === 0) {
|
||||
return `${tokenType}:${languageId}`;
|
||||
}
|
||||
return `${tokenType}.${normalizedModifiers.join(".")}:${languageId}`;
|
||||
}
|
||||
function readObject(value) {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return {};
|
||||
|
||||
@ -53,100 +53,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"semanticTokenTypes": [
|
||||
{
|
||||
"id": "pbs-comment",
|
||||
"description": "PBS comment token"
|
||||
},
|
||||
{
|
||||
"id": "pbs-string",
|
||||
"description": "PBS string token"
|
||||
},
|
||||
{
|
||||
"id": "pbs-number",
|
||||
"description": "PBS number token"
|
||||
},
|
||||
{
|
||||
"id": "pbs-literal",
|
||||
"description": "PBS literal token"
|
||||
},
|
||||
{
|
||||
"id": "pbs-lifecycle",
|
||||
"description": "PBS lifecycle token"
|
||||
},
|
||||
{
|
||||
"id": "pbs-keyword",
|
||||
"description": "PBS keyword token"
|
||||
},
|
||||
{
|
||||
"id": "pbs-operator",
|
||||
"description": "PBS operator token"
|
||||
},
|
||||
{
|
||||
"id": "pbs-punctuation",
|
||||
"description": "PBS punctuation token"
|
||||
},
|
||||
{
|
||||
"id": "pbs-function",
|
||||
"description": "PBS function token"
|
||||
},
|
||||
{
|
||||
"id": "pbs-method",
|
||||
"description": "PBS method token"
|
||||
},
|
||||
{
|
||||
"id": "pbs-constructor",
|
||||
"description": "PBS constructor token"
|
||||
},
|
||||
{
|
||||
"id": "pbs-struct",
|
||||
"description": "PBS struct token"
|
||||
},
|
||||
{
|
||||
"id": "pbs-contract",
|
||||
"description": "PBS contract token"
|
||||
},
|
||||
{
|
||||
"id": "pbs-host",
|
||||
"description": "PBS host token"
|
||||
},
|
||||
{
|
||||
"id": "pbs-builtin-type",
|
||||
"description": "PBS builtin type token"
|
||||
},
|
||||
{
|
||||
"id": "pbs-service",
|
||||
"description": "PBS service token"
|
||||
},
|
||||
{
|
||||
"id": "pbs-error",
|
||||
"description": "PBS error token"
|
||||
},
|
||||
{
|
||||
"id": "pbs-enum",
|
||||
"description": "PBS enum token"
|
||||
},
|
||||
{
|
||||
"id": "pbs-callback",
|
||||
"description": "PBS callback token"
|
||||
},
|
||||
{
|
||||
"id": "pbs-global",
|
||||
"description": "PBS global token"
|
||||
},
|
||||
{
|
||||
"id": "pbs-const",
|
||||
"description": "PBS const token"
|
||||
},
|
||||
{
|
||||
"id": "pbs-implements",
|
||||
"description": "PBS implements token"
|
||||
},
|
||||
{
|
||||
"id": "pbs-identifier",
|
||||
"description": "PBS identifier token"
|
||||
}
|
||||
],
|
||||
"configurationDefaults": {
|
||||
"editor.semanticHighlighting.enabled": true
|
||||
}
|
||||
|
||||
@ -51,6 +51,27 @@ type PrometeuVisualThemesPayload = {
|
||||
visualThemes: PrometeuVisualTheme[];
|
||||
};
|
||||
|
||||
type PrometeuSemanticHostProjectionEntry = {
|
||||
semanticKey: string;
|
||||
hostTokenType: string;
|
||||
hostTokenModifiers: string[];
|
||||
fallbackTokenType: string;
|
||||
fallbackTokenModifiers: string[];
|
||||
};
|
||||
|
||||
type PrometeuSemanticHostProjection = {
|
||||
hostId: string;
|
||||
tokenProjections: PrometeuSemanticHostProjectionEntry[];
|
||||
};
|
||||
|
||||
type PrometeuSemanticHostProjectionsPayload = {
|
||||
frontendLanguageId: string;
|
||||
semanticKeys: string[];
|
||||
hostProjections: PrometeuSemanticHostProjection[];
|
||||
};
|
||||
|
||||
const VSCODE_HOST_ID = "vscode";
|
||||
|
||||
export function activate(context: vscode.ExtensionContext): void {
|
||||
reconnectContext = context;
|
||||
output = vscode.window.createOutputChannel("Prometeu LSP");
|
||||
@ -255,23 +276,31 @@ function clearReconnectTimer(): void {
|
||||
}
|
||||
|
||||
async function applyPrometeuVisualTheme(initializeResult: unknown): Promise<void> {
|
||||
const payload = extractPrometeuVisualThemesPayload(initializeResult);
|
||||
if (!payload) {
|
||||
const visualThemesPayload = extractPrometeuVisualThemesPayload(initializeResult);
|
||||
if (!visualThemesPayload) {
|
||||
output.appendLine("Prometeu visual theme payload not present in initialize result.");
|
||||
return;
|
||||
}
|
||||
const semanticProjectionPayload = extractPrometeuSemanticHostProjectionsPayload(initializeResult);
|
||||
const vscodeProjection = selectHostProjection(semanticProjectionPayload, VSCODE_HOST_ID);
|
||||
if (!semanticProjectionPayload) {
|
||||
output.appendLine("Prometeu semantic host projection payload not present in initialize result.");
|
||||
} else if (!vscodeProjection) {
|
||||
output.appendLine("Prometeu semantic host projection payload does not declare a vscode projection.");
|
||||
}
|
||||
|
||||
const activeTheme = payload.visualThemes.find((theme) => theme.themeId === payload.activeVisualThemeId);
|
||||
const activeTheme = visualThemesPayload.visualThemes.find((theme) => theme.themeId === visualThemesPayload.activeVisualThemeId);
|
||||
if (!activeTheme) {
|
||||
output.appendLine(`Prometeu visual theme '${payload.activeVisualThemeId}' was not declared by the server.`);
|
||||
output.appendLine(`Prometeu visual theme '${visualThemesPayload.activeVisualThemeId}' was not declared by the server.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const themeSignature = JSON.stringify({
|
||||
languageId: payload.frontendLanguageId,
|
||||
languageId: visualThemesPayload.frontendLanguageId,
|
||||
themeId: activeTheme.themeId,
|
||||
editorPalette: activeTheme.editorPalette,
|
||||
tokenStyles: activeTheme.tokenStyles
|
||||
tokenStyles: activeTheme.tokenStyles,
|
||||
semanticProjection: vscodeProjection ?? null
|
||||
});
|
||||
if (themeSignature === lastAppliedThemeSignature) {
|
||||
output.appendLine(`Prometeu visual theme '${activeTheme.themeId}' is already applied.`);
|
||||
@ -286,7 +315,7 @@ async function applyPrometeuVisualTheme(initializeResult: unknown): Promise<void
|
||||
const existingSemanticCustomizations = configuration.get<Record<string, unknown>>("editor.semanticTokenColorCustomizations") ?? {};
|
||||
const semanticRules = {
|
||||
...(readObject(existingSemanticCustomizations.rules)),
|
||||
...buildSemanticTokenRules(payload.frontendLanguageId, activeTheme)
|
||||
...buildSemanticTokenRules(visualThemesPayload.frontendLanguageId, activeTheme, vscodeProjection)
|
||||
};
|
||||
|
||||
await configuration.update(
|
||||
@ -312,7 +341,7 @@ async function applyPrometeuVisualTheme(initializeResult: unknown): Promise<void
|
||||
);
|
||||
|
||||
lastAppliedThemeSignature = themeSignature;
|
||||
output.appendLine(`Applied Prometeu visual theme '${activeTheme.themeId}' for language '${payload.frontendLanguageId}'.`);
|
||||
output.appendLine(`Applied Prometeu visual theme '${activeTheme.themeId}' for language '${visualThemesPayload.frontendLanguageId}'.`);
|
||||
}
|
||||
|
||||
function extractPrometeuVisualThemesPayload(initializeResult: unknown): PrometeuVisualThemesPayload | undefined {
|
||||
@ -334,27 +363,82 @@ function extractPrometeuVisualThemesPayload(initializeResult: unknown): Prometeu
|
||||
return payload as unknown as PrometeuVisualThemesPayload;
|
||||
}
|
||||
|
||||
function extractPrometeuSemanticHostProjectionsPayload(initializeResult: unknown): PrometeuSemanticHostProjectionsPayload | undefined {
|
||||
if (!initializeResult || typeof initializeResult !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const capabilities = readObject((initializeResult as { capabilities?: unknown }).capabilities);
|
||||
const experimental = readObject(capabilities.experimental);
|
||||
const payload = readObject(experimental.prometeuSemanticHostProjections);
|
||||
|
||||
if (typeof payload.frontendLanguageId !== "string" || !Array.isArray(payload.semanticKeys) || !Array.isArray(payload.hostProjections)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return payload as unknown as PrometeuSemanticHostProjectionsPayload;
|
||||
}
|
||||
|
||||
function selectHostProjection(
|
||||
payload: PrometeuSemanticHostProjectionsPayload | undefined,
|
||||
hostId: string
|
||||
): PrometeuSemanticHostProjection | undefined {
|
||||
if (!payload) {
|
||||
return undefined;
|
||||
}
|
||||
return payload.hostProjections.find((projection) => projection.hostId === hostId);
|
||||
}
|
||||
|
||||
function buildSemanticTokenRules(
|
||||
languageId: string,
|
||||
theme: PrometeuVisualTheme
|
||||
theme: PrometeuVisualTheme,
|
||||
hostProjection: PrometeuSemanticHostProjection | undefined
|
||||
): Record<string, string | { foreground: string; italic?: boolean; bold?: boolean; underline?: boolean }> {
|
||||
const rules: Record<string, string | { foreground: string; italic?: boolean; bold?: boolean; underline?: boolean }> = {};
|
||||
const projectionBySemanticKey = new Map<string, PrometeuSemanticHostProjectionEntry>(
|
||||
(hostProjection?.tokenProjections ?? []).map((projection) => [projection.semanticKey, projection])
|
||||
);
|
||||
for (const tokenStyle of theme.tokenStyles) {
|
||||
const selector = `${tokenStyle.semanticKey}:${languageId}`;
|
||||
if (!tokenStyle.italic && !tokenStyle.bold && !tokenStyle.underline) {
|
||||
rules[selector] = tokenStyle.foreground;
|
||||
continue;
|
||||
}
|
||||
rules[selector] = {
|
||||
const styleValue = !tokenStyle.italic && !tokenStyle.bold && !tokenStyle.underline
|
||||
? tokenStyle.foreground
|
||||
: {
|
||||
foreground: tokenStyle.foreground,
|
||||
...(tokenStyle.italic ? { italic: true } : {}),
|
||||
...(tokenStyle.bold ? { bold: true } : {}),
|
||||
...(tokenStyle.underline ? { underline: true } : {})
|
||||
};
|
||||
rules[`${tokenStyle.semanticKey}:${languageId}`] = styleValue;
|
||||
|
||||
const projection = projectionBySemanticKey.get(tokenStyle.semanticKey);
|
||||
if (!projection) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const projectedSelector = buildSemanticSelector(languageId, projection.hostTokenType, projection.hostTokenModifiers);
|
||||
if (!(projectedSelector in rules)) {
|
||||
rules[projectedSelector] = styleValue;
|
||||
}
|
||||
|
||||
const fallbackSelector = buildSemanticSelector(languageId, projection.fallbackTokenType, projection.fallbackTokenModifiers);
|
||||
if (!(fallbackSelector in rules)) {
|
||||
rules[fallbackSelector] = styleValue;
|
||||
}
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
|
||||
function buildSemanticSelector(
|
||||
languageId: string,
|
||||
tokenType: string,
|
||||
modifiers: string[]
|
||||
): string {
|
||||
const normalizedModifiers = [...modifiers].sort();
|
||||
if (normalizedModifiers.length === 0) {
|
||||
return `${tokenType}:${languageId}`;
|
||||
}
|
||||
return `${tokenType}.${normalizedModifiers.join(".")}:${languageId}`;
|
||||
}
|
||||
|
||||
function readObject(value: unknown): Record<string, unknown> {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return {};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user