implements PLN-0071
This commit is contained in:
parent
ed2070c5a4
commit
69f5c4abdd
@ -6,6 +6,7 @@ import java.util.List;
|
||||
public record BaselineServerDescription(
|
||||
String name,
|
||||
String version,
|
||||
String frontendLanguageId,
|
||||
boolean hoverSupported,
|
||||
List<String> semanticTokenTypes,
|
||||
List<BaselineVisualTheme> visualThemes,
|
||||
@ -14,6 +15,7 @@ public record BaselineServerDescription(
|
||||
public BaselineServerDescription {
|
||||
name = requireText(name, "name");
|
||||
version = requireText(version, "version");
|
||||
frontendLanguageId = requireText(frontendLanguageId, "frontendLanguageId");
|
||||
semanticTokenTypes = List.copyOf(Objects.requireNonNull(semanticTokenTypes, "semanticTokenTypes"));
|
||||
visualThemes = List.copyOf(Objects.requireNonNull(visualThemes, "visualThemes"));
|
||||
activeVisualThemeId = requireText(activeVisualThemeId, "activeVisualThemeId");
|
||||
|
||||
@ -36,6 +36,7 @@ public record CompilerLanguageServiceBridge() implements LanguageServiceBridge {
|
||||
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(),
|
||||
|
||||
@ -5,7 +5,9 @@ import p.studio.lsp.messages.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public final class Lsp4jProtocolMessageMapper implements ProtocolMessageMapper {
|
||||
@Override
|
||||
@ -26,6 +28,12 @@ public final class Lsp4jProtocolMessageMapper implements ProtocolMessageMapper {
|
||||
semanticTokens.setLegend(new SemanticTokensLegend(description.semanticTokenTypes(), List.of()));
|
||||
semanticTokens.setFull(true);
|
||||
capabilities.setSemanticTokensProvider(semanticTokens);
|
||||
capabilities.setExperimental(Map.of(
|
||||
"prometeuVisualThemes",
|
||||
Map.of(
|
||||
"frontendLanguageId", description.frontendLanguageId(),
|
||||
"activeVisualThemeId", description.activeVisualThemeId(),
|
||||
"visualThemes", description.visualThemes().stream().map(this::mapVisualThemePayload).toList())));
|
||||
|
||||
final InitializeResult result = new InitializeResult(capabilities);
|
||||
final ServerInfo serverInfo = new ServerInfo();
|
||||
@ -122,4 +130,25 @@ public final class Lsp4jProtocolMessageMapper implements ProtocolMessageMapper {
|
||||
case ERROR -> DiagnosticSeverity.Error;
|
||||
};
|
||||
}
|
||||
|
||||
private Map<String, Object> mapVisualThemePayload(final BaselineVisualTheme theme) {
|
||||
final Map<String, Object> payload = new LinkedHashMap<>();
|
||||
payload.put("themeId", theme.themeId());
|
||||
payload.put("displayName", theme.displayName());
|
||||
payload.put("editorPalette", Map.of(
|
||||
"baseForeground", theme.editorPalette().baseForeground(),
|
||||
"selectionBackground", theme.editorPalette().selectionBackground(),
|
||||
"activeHighlightBackground", theme.editorPalette().activeHighlightBackground(),
|
||||
"lineNumberForeground", theme.editorPalette().lineNumberForeground(),
|
||||
"statusChipBackground", theme.editorPalette().statusChipBackground(),
|
||||
"statusChipBorder", theme.editorPalette().statusChipBorder(),
|
||||
"statusChipForeground", theme.editorPalette().statusChipForeground()));
|
||||
payload.put("tokenStyles", theme.tokenStyles().stream().map(tokenStyle -> Map.of(
|
||||
"semanticKey", tokenStyle.semanticKey(),
|
||||
"foreground", tokenStyle.foreground(),
|
||||
"italic", tokenStyle.italic(),
|
||||
"bold", tokenStyle.bold(),
|
||||
"underline", tokenStyle.underline())).toList());
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,6 +62,7 @@ class CompilerLanguageServiceBridgeTest {
|
||||
|
||||
final var description = bridge.describeServer(new LspProjectContext("main", "pbs", Path.of(".")));
|
||||
|
||||
assertEquals("pbs", description.frontendLanguageId());
|
||||
assertEquals("pbs-default", description.activeVisualThemeId());
|
||||
assertEquals(1, description.visualThemes().size());
|
||||
assertEquals("PBS Default", description.visualThemes().getFirst().displayName());
|
||||
|
||||
@ -55,6 +55,7 @@ class PrometeuLanguageServerTest {
|
||||
assertSame(expected, result);
|
||||
assertEquals(1, bridge.describeServerCalls);
|
||||
assertSame(bridge.serverDescription, mapper.lastServerDescription);
|
||||
assertEquals("pbs", bridge.serverDescription.frontendLanguageId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -85,6 +86,7 @@ class PrometeuLanguageServerTest {
|
||||
private final BaselineServerDescription serverDescription = new BaselineServerDescription(
|
||||
"demo",
|
||||
"1",
|
||||
"pbs",
|
||||
true,
|
||||
List.of(),
|
||||
List.of(new BaselineVisualTheme(
|
||||
|
||||
292
tools/vscode-extension/out/extension.js
Normal file
292
tools/vscode-extension/out/extension.js
Normal file
@ -0,0 +1,292 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.activate = activate;
|
||||
exports.deactivate = deactivate;
|
||||
const net = __importStar(require("node:net"));
|
||||
const vscode = __importStar(require("vscode"));
|
||||
const node_1 = require("vscode-languageclient/node");
|
||||
let client;
|
||||
let output;
|
||||
let reconnectTimer;
|
||||
let reconnectContext;
|
||||
let stoppingClient = false;
|
||||
let lastAppliedThemeSignature;
|
||||
const RECONNECT_DELAY_MS = 2000;
|
||||
function activate(context) {
|
||||
reconnectContext = context;
|
||||
output = vscode.window.createOutputChannel("Prometeu LSP");
|
||||
output.appendLine("Prometeu VS Code extension activated.");
|
||||
const connectCommand = vscode.commands.registerCommand("prometeu.connectStudioLsp", async () => {
|
||||
await startClient(context);
|
||||
});
|
||||
const restartCommand = vscode.commands.registerCommand("prometeu.restartStudioLsp", async () => {
|
||||
await stopClient();
|
||||
await startClient(context);
|
||||
});
|
||||
context.subscriptions.push(output, connectCommand, restartCommand);
|
||||
void startClient(context);
|
||||
}
|
||||
async function deactivate() {
|
||||
await stopClient();
|
||||
}
|
||||
async function startClient(context) {
|
||||
clearReconnectTimer();
|
||||
if (client) {
|
||||
output.appendLine("Prometeu LSP client is already running.");
|
||||
return;
|
||||
}
|
||||
const config = vscode.workspace.getConfiguration("prometeu.studio");
|
||||
const host = config.get("host", "127.0.0.1");
|
||||
const port = config.get("port", 7775);
|
||||
output.show(true);
|
||||
output.appendLine(`Connecting to Prometeu Studio LSP at ${host}:${port}...`);
|
||||
const serverOptions = async () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const socket = net.createConnection({ host, port });
|
||||
let settled = false;
|
||||
const connectTimeout = setTimeout(() => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
socket.destroy();
|
||||
reject(new Error("Connection timed out."));
|
||||
}, 3000);
|
||||
socket.once("connect", () => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
clearTimeout(connectTimeout);
|
||||
output.appendLine("TCP connection established with Prometeu Studio.");
|
||||
resolve({
|
||||
reader: socket,
|
||||
writer: socket
|
||||
});
|
||||
});
|
||||
socket.once("error", (error) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
clearTimeout(connectTimeout);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
const clientOptions = {
|
||||
documentSelector: [
|
||||
{
|
||||
scheme: "file",
|
||||
language: "pbs"
|
||||
}
|
||||
],
|
||||
synchronize: {
|
||||
fileEvents: [
|
||||
vscode.workspace.createFileSystemWatcher("**/*.pbs")
|
||||
]
|
||||
},
|
||||
initializationOptions: {
|
||||
clientKind: "vscode",
|
||||
prometeuClientVersion: context.extension.packageJSON.version
|
||||
},
|
||||
outputChannel: output,
|
||||
errorHandler: {
|
||||
error: (error) => {
|
||||
output.appendLine(`Prometeu LSP transport error: ${error.message}`);
|
||||
return { action: node_1.ErrorAction.Continue };
|
||||
},
|
||||
closed: () => {
|
||||
output.appendLine("Prometeu LSP transport closed.");
|
||||
return { action: node_1.CloseAction.DoNotRestart };
|
||||
}
|
||||
}
|
||||
};
|
||||
const nextClient = new node_1.LanguageClient("prometeuStudioLsp", "Prometeu Studio LSP", serverOptions, clientOptions);
|
||||
client = nextClient;
|
||||
nextClient.onDidChangeState((event) => {
|
||||
output.appendLine(`Prometeu LSP state changed: ${node_1.State[event.oldState]} -> ${node_1.State[event.newState]}`);
|
||||
if (event.newState !== node_1.State.Stopped) {
|
||||
return;
|
||||
}
|
||||
if (client === nextClient) {
|
||||
client = undefined;
|
||||
}
|
||||
if (!stoppingClient) {
|
||||
scheduleReconnect();
|
||||
}
|
||||
});
|
||||
try {
|
||||
await nextClient.start();
|
||||
await applyPrometeuVisualTheme(nextClient.initializeResult);
|
||||
output.appendLine("Prometeu Studio LSP client started.");
|
||||
vscode.window.showInformationMessage("Connected to Prometeu Studio LSP.");
|
||||
}
|
||||
catch (error) {
|
||||
if (client === nextClient) {
|
||||
client = undefined;
|
||||
}
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
output.appendLine(`Failed to start Prometeu LSP client: ${message}`);
|
||||
vscode.window.showErrorMessage(`Could not connect to Prometeu Studio LSP: ${message}`);
|
||||
scheduleReconnect();
|
||||
}
|
||||
}
|
||||
async function stopClient() {
|
||||
clearReconnectTimer();
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
const currentClient = client;
|
||||
client = undefined;
|
||||
stoppingClient = true;
|
||||
output.appendLine("Stopping Prometeu Studio LSP client...");
|
||||
try {
|
||||
await currentClient.stop();
|
||||
output.appendLine("Prometeu Studio LSP client stopped.");
|
||||
}
|
||||
catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
output.appendLine(`Failed to stop Prometeu LSP client cleanly: ${message}`);
|
||||
}
|
||||
finally {
|
||||
stoppingClient = false;
|
||||
}
|
||||
}
|
||||
function scheduleReconnect() {
|
||||
if (!reconnectContext) {
|
||||
return;
|
||||
}
|
||||
if (reconnectTimer || client) {
|
||||
return;
|
||||
}
|
||||
output.appendLine(`Scheduling Prometeu LSP reconnect in ${RECONNECT_DELAY_MS}ms...`);
|
||||
reconnectTimer = setTimeout(() => {
|
||||
reconnectTimer = undefined;
|
||||
void startClient(reconnectContext);
|
||||
}, RECONNECT_DELAY_MS);
|
||||
}
|
||||
function clearReconnectTimer() {
|
||||
if (!reconnectTimer) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(reconnectTimer);
|
||||
reconnectTimer = undefined;
|
||||
}
|
||||
async function applyPrometeuVisualTheme(initializeResult) {
|
||||
const payload = extractPrometeuVisualThemesPayload(initializeResult);
|
||||
if (!payload) {
|
||||
output.appendLine("Prometeu visual theme payload not present in initialize result.");
|
||||
return;
|
||||
}
|
||||
const activeTheme = payload.visualThemes.find((theme) => theme.themeId === payload.activeVisualThemeId);
|
||||
if (!activeTheme) {
|
||||
output.appendLine(`Prometeu visual theme '${payload.activeVisualThemeId}' was not declared by the server.`);
|
||||
return;
|
||||
}
|
||||
const themeSignature = JSON.stringify({
|
||||
languageId: payload.frontendLanguageId,
|
||||
themeId: activeTheme.themeId,
|
||||
editorPalette: activeTheme.editorPalette,
|
||||
tokenStyles: activeTheme.tokenStyles
|
||||
});
|
||||
if (themeSignature === lastAppliedThemeSignature) {
|
||||
output.appendLine(`Prometeu visual theme '${activeTheme.themeId}' is already applied.`);
|
||||
return;
|
||||
}
|
||||
const configurationTarget = vscode.workspace.workspaceFolders?.length
|
||||
? vscode.ConfigurationTarget.Workspace
|
||||
: vscode.ConfigurationTarget.Global;
|
||||
const configuration = vscode.workspace.getConfiguration();
|
||||
const existingSemanticCustomizations = configuration.get("editor.semanticTokenColorCustomizations") ?? {};
|
||||
const semanticRules = {
|
||||
...(readObject(existingSemanticCustomizations.rules)),
|
||||
...buildSemanticTokenRules(payload.frontendLanguageId, activeTheme)
|
||||
};
|
||||
await configuration.update("editor.semanticTokenColorCustomizations", {
|
||||
...existingSemanticCustomizations,
|
||||
enabled: true,
|
||||
rules: semanticRules
|
||||
}, configurationTarget);
|
||||
const existingWorkbenchColors = configuration.get("workbench.colorCustomizations") ?? {};
|
||||
await configuration.update("workbench.colorCustomizations", {
|
||||
...existingWorkbenchColors,
|
||||
"editor.foreground": activeTheme.editorPalette.baseForeground,
|
||||
"editor.selectionBackground": activeTheme.editorPalette.selectionBackground,
|
||||
"editorLineNumber.foreground": activeTheme.editorPalette.lineNumberForeground
|
||||
}, configurationTarget);
|
||||
lastAppliedThemeSignature = themeSignature;
|
||||
output.appendLine(`Applied Prometeu visual theme '${activeTheme.themeId}' for language '${payload.frontendLanguageId}'.`);
|
||||
}
|
||||
function extractPrometeuVisualThemesPayload(initializeResult) {
|
||||
if (!initializeResult || typeof initializeResult !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
const capabilities = readObject(initializeResult.capabilities);
|
||||
const experimental = readObject(capabilities.experimental);
|
||||
const payload = readObject(experimental.prometeuVisualThemes);
|
||||
if (typeof payload.frontendLanguageId !== "string" || typeof payload.activeVisualThemeId !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
if (!Array.isArray(payload.visualThemes)) {
|
||||
return undefined;
|
||||
}
|
||||
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;
|
||||
}
|
||||
rules[selector] = {
|
||||
foreground: tokenStyle.foreground,
|
||||
...(tokenStyle.italic ? { italic: true } : {}),
|
||||
...(tokenStyle.bold ? { bold: true } : {}),
|
||||
...(tokenStyle.underline ? { underline: true } : {})
|
||||
};
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
function readObject(value) {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return {};
|
||||
}
|
||||
return value;
|
||||
}
|
||||
//# sourceMappingURL=extension.js.map
|
||||
@ -148,41 +148,7 @@
|
||||
}
|
||||
],
|
||||
"configurationDefaults": {
|
||||
"editor.semanticHighlighting.enabled": true,
|
||||
"editor.semanticTokenColorCustomizations": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"pbs-keyword:pbs": "#569cd6",
|
||||
"pbs-lifecycle:pbs": "#ef50c0",
|
||||
"pbs-function:pbs": "#f2c14e",
|
||||
"pbs-method:pbs": "#f2c14e",
|
||||
"pbs-constructor:pbs": "#ecdcaa",
|
||||
"pbs-struct:pbs": "#4ec9b0",
|
||||
"pbs-contract:pbs": "#78dce8",
|
||||
"pbs-host:pbs": "#b7a2fa",
|
||||
"pbs-builtin-type:pbs": "#8be9fd",
|
||||
"pbs-service:pbs": "#b7a2fa",
|
||||
"pbs-error:pbs": "#ff5b5b",
|
||||
"pbs-enum:pbs": "#56cfe1",
|
||||
"pbs-callback:pbs": "#b7a2fa",
|
||||
"pbs-global:pbs": {
|
||||
"foreground": "#f790fc",
|
||||
"italic": true
|
||||
},
|
||||
"pbs-const:pbs": "#f78c6c",
|
||||
"pbs-implements:pbs": "#a1c181",
|
||||
"pbs-string:pbs": "#00c088",
|
||||
"pbs-number:pbs": "#ff90b0",
|
||||
"pbs-comment:pbs": {
|
||||
"foreground": "#c090b0",
|
||||
"italic": true
|
||||
},
|
||||
"pbs-literal:pbs": "#4fc1ff",
|
||||
"pbs-operator:pbs": "#d4d4d4",
|
||||
"pbs-punctuation:pbs": "#d4d4d4",
|
||||
"pbs-identifier:pbs": "#d4d4d4"
|
||||
}
|
||||
}
|
||||
"editor.semanticHighlighting.enabled": true
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@ -16,9 +16,41 @@ let output: vscode.OutputChannel;
|
||||
let reconnectTimer: NodeJS.Timeout | undefined;
|
||||
let reconnectContext: vscode.ExtensionContext | undefined;
|
||||
let stoppingClient = false;
|
||||
let lastAppliedThemeSignature: string | undefined;
|
||||
|
||||
const RECONNECT_DELAY_MS = 2000;
|
||||
|
||||
type PrometeuTokenStyle = {
|
||||
semanticKey: string;
|
||||
foreground: string;
|
||||
italic: boolean;
|
||||
bold: boolean;
|
||||
underline: boolean;
|
||||
};
|
||||
|
||||
type PrometeuEditorPalette = {
|
||||
baseForeground: string;
|
||||
selectionBackground: string;
|
||||
activeHighlightBackground: string;
|
||||
lineNumberForeground: string;
|
||||
statusChipBackground: string;
|
||||
statusChipBorder: string;
|
||||
statusChipForeground: string;
|
||||
};
|
||||
|
||||
type PrometeuVisualTheme = {
|
||||
themeId: string;
|
||||
displayName: string;
|
||||
editorPalette: PrometeuEditorPalette;
|
||||
tokenStyles: PrometeuTokenStyle[];
|
||||
};
|
||||
|
||||
type PrometeuVisualThemesPayload = {
|
||||
frontendLanguageId: string;
|
||||
activeVisualThemeId: string;
|
||||
visualThemes: PrometeuVisualTheme[];
|
||||
};
|
||||
|
||||
export function activate(context: vscode.ExtensionContext): void {
|
||||
reconnectContext = context;
|
||||
output = vscode.window.createOutputChannel("Prometeu LSP");
|
||||
@ -160,6 +192,7 @@ async function startClient(context: vscode.ExtensionContext): Promise<void> {
|
||||
|
||||
try {
|
||||
await nextClient.start();
|
||||
await applyPrometeuVisualTheme(nextClient.initializeResult);
|
||||
|
||||
output.appendLine("Prometeu Studio LSP client started.");
|
||||
vscode.window.showInformationMessage("Connected to Prometeu Studio LSP.");
|
||||
@ -220,3 +253,111 @@ function clearReconnectTimer(): void {
|
||||
clearTimeout(reconnectTimer);
|
||||
reconnectTimer = undefined;
|
||||
}
|
||||
|
||||
async function applyPrometeuVisualTheme(initializeResult: unknown): Promise<void> {
|
||||
const payload = extractPrometeuVisualThemesPayload(initializeResult);
|
||||
if (!payload) {
|
||||
output.appendLine("Prometeu visual theme payload not present in initialize result.");
|
||||
return;
|
||||
}
|
||||
|
||||
const activeTheme = payload.visualThemes.find((theme) => theme.themeId === payload.activeVisualThemeId);
|
||||
if (!activeTheme) {
|
||||
output.appendLine(`Prometeu visual theme '${payload.activeVisualThemeId}' was not declared by the server.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const themeSignature = JSON.stringify({
|
||||
languageId: payload.frontendLanguageId,
|
||||
themeId: activeTheme.themeId,
|
||||
editorPalette: activeTheme.editorPalette,
|
||||
tokenStyles: activeTheme.tokenStyles
|
||||
});
|
||||
if (themeSignature === lastAppliedThemeSignature) {
|
||||
output.appendLine(`Prometeu visual theme '${activeTheme.themeId}' is already applied.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const configurationTarget = vscode.workspace.workspaceFolders?.length
|
||||
? vscode.ConfigurationTarget.Workspace
|
||||
: vscode.ConfigurationTarget.Global;
|
||||
const configuration = vscode.workspace.getConfiguration();
|
||||
|
||||
const existingSemanticCustomizations = configuration.get<Record<string, unknown>>("editor.semanticTokenColorCustomizations") ?? {};
|
||||
const semanticRules = {
|
||||
...(readObject(existingSemanticCustomizations.rules)),
|
||||
...buildSemanticTokenRules(payload.frontendLanguageId, activeTheme)
|
||||
};
|
||||
|
||||
await configuration.update(
|
||||
"editor.semanticTokenColorCustomizations",
|
||||
{
|
||||
...existingSemanticCustomizations,
|
||||
enabled: true,
|
||||
rules: semanticRules
|
||||
},
|
||||
configurationTarget
|
||||
);
|
||||
|
||||
const existingWorkbenchColors = configuration.get<Record<string, unknown>>("workbench.colorCustomizations") ?? {};
|
||||
await configuration.update(
|
||||
"workbench.colorCustomizations",
|
||||
{
|
||||
...existingWorkbenchColors,
|
||||
"editor.foreground": activeTheme.editorPalette.baseForeground,
|
||||
"editor.selectionBackground": activeTheme.editorPalette.selectionBackground,
|
||||
"editorLineNumber.foreground": activeTheme.editorPalette.lineNumberForeground
|
||||
},
|
||||
configurationTarget
|
||||
);
|
||||
|
||||
lastAppliedThemeSignature = themeSignature;
|
||||
output.appendLine(`Applied Prometeu visual theme '${activeTheme.themeId}' for language '${payload.frontendLanguageId}'.`);
|
||||
}
|
||||
|
||||
function extractPrometeuVisualThemesPayload(initializeResult: unknown): PrometeuVisualThemesPayload | 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.prometeuVisualThemes);
|
||||
|
||||
if (typeof payload.frontendLanguageId !== "string" || typeof payload.activeVisualThemeId !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
if (!Array.isArray(payload.visualThemes)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return payload as unknown as PrometeuVisualThemesPayload;
|
||||
}
|
||||
|
||||
function buildSemanticTokenRules(
|
||||
languageId: string,
|
||||
theme: PrometeuVisualTheme
|
||||
): Record<string, string | { foreground: string; italic?: boolean; bold?: boolean; underline?: boolean }> {
|
||||
const rules: Record<string, string | { foreground: string; italic?: boolean; bold?: boolean; underline?: boolean }> = {};
|
||||
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] = {
|
||||
foreground: tokenStyle.foreground,
|
||||
...(tokenStyle.italic ? { italic: true } : {}),
|
||||
...(tokenStyle.bold ? { bold: true } : {}),
|
||||
...(tokenStyle.underline ? { underline: true } : {})
|
||||
};
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
|
||||
function readObject(value: unknown): Record<string, unknown> {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return {};
|
||||
}
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user