@workbench-kit/monaco 0.0.2-prototype.0.2.10 → 0.0.2-prototype.0.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.ts +13 -0
- package/src/monacoWorkbenchTheme.ts +270 -9
- package/src/testing/vitestMock.tsx +7 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -14,15 +14,28 @@ export {
|
|
|
14
14
|
export {
|
|
15
15
|
MONACO_DARK_THEME_ID,
|
|
16
16
|
MONACO_LIGHT_THEME_ID,
|
|
17
|
+
buildDefaultMonacoTokenRules,
|
|
17
18
|
buildMonacoThemeColors,
|
|
18
19
|
defineMonacoWorkbenchTheme,
|
|
20
|
+
defineOrUpdateWorkbenchMonacoTheme,
|
|
21
|
+
getWorkbenchMonacoTokenRules,
|
|
19
22
|
getWorkbenchThemeAppearanceSignature,
|
|
23
|
+
mergeMonacoTokenRules,
|
|
24
|
+
monacoRulesFromTokenColors,
|
|
20
25
|
monacoThemeForWorkspaceTheme,
|
|
21
26
|
readWorkbenchThemeColors,
|
|
22
27
|
resolveMonacoThemeRoot,
|
|
28
|
+
setWorkbenchMonacoTokenRules,
|
|
29
|
+
toMonacoTokenColor,
|
|
23
30
|
withAlpha,
|
|
31
|
+
buildWorkbenchMonacoThemeInput,
|
|
32
|
+
type DefineMonacoWorkbenchThemeOptions,
|
|
33
|
+
type MonacoTokenRule,
|
|
24
34
|
type MonacoWorkbenchResolvedTheme,
|
|
35
|
+
type MonacoWorkbenchThemeBase,
|
|
36
|
+
type WorkbenchMonacoThemeInput,
|
|
25
37
|
type WorkbenchThemeCssColors,
|
|
38
|
+
type WorkbenchTokenColorSetting,
|
|
26
39
|
} from './monacoWorkbenchTheme.js';
|
|
27
40
|
export { useMonacoWorkbenchThemeSync } from './useMonacoWorkbenchThemeSync.js';
|
|
28
41
|
export { configureWorkspaceEditorTypeScriptDiagnostics } from './workspaceTypeScriptDiagnostics.js';
|
|
@@ -5,6 +5,35 @@ export const MONACO_LIGHT_THEME_ID = 'workbench-kit-light';
|
|
|
5
5
|
|
|
6
6
|
export type MonacoWorkbenchResolvedTheme = 'dark' | 'light';
|
|
7
7
|
|
|
8
|
+
export type MonacoWorkbenchThemeBase = 'vs' | 'vs-dark' | 'hc-black';
|
|
9
|
+
|
|
10
|
+
/** Monaco `editor.defineTheme` token rule (syntax highlighting). */
|
|
11
|
+
export interface MonacoTokenRule {
|
|
12
|
+
readonly token: string;
|
|
13
|
+
readonly foreground?: string;
|
|
14
|
+
readonly background?: string;
|
|
15
|
+
readonly fontStyle?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* VS Code–compatible `tokenColors` entry (TextMate scope settings).
|
|
20
|
+
* Hosts may pass theme JSON `tokenColors` through `monacoRulesFromTokenColors`.
|
|
21
|
+
*/
|
|
22
|
+
export interface WorkbenchTokenColorSetting {
|
|
23
|
+
readonly scope?: string | readonly string[];
|
|
24
|
+
readonly settings?: {
|
|
25
|
+
readonly foreground?: string;
|
|
26
|
+
readonly background?: string;
|
|
27
|
+
readonly fontStyle?: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface WorkbenchMonacoThemeInput {
|
|
32
|
+
readonly base: MonacoWorkbenchThemeBase;
|
|
33
|
+
readonly colors?: Readonly<Record<string, string>>;
|
|
34
|
+
readonly rules?: readonly MonacoTokenRule[];
|
|
35
|
+
}
|
|
36
|
+
|
|
8
37
|
export interface WorkbenchThemeCssColors {
|
|
9
38
|
accent: string;
|
|
10
39
|
bg: string;
|
|
@@ -22,6 +51,17 @@ export interface WorkbenchThemeCssColors {
|
|
|
22
51
|
textSubtle: string;
|
|
23
52
|
}
|
|
24
53
|
|
|
54
|
+
/** Optional host-provided syntax rules merged on every workbench theme define. */
|
|
55
|
+
let activeHostTokenRules: readonly MonacoTokenRule[] | undefined;
|
|
56
|
+
|
|
57
|
+
export function setWorkbenchMonacoTokenRules(rules: readonly MonacoTokenRule[] | undefined): void {
|
|
58
|
+
activeHostTokenRules = rules;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function getWorkbenchMonacoTokenRules(): readonly MonacoTokenRule[] | undefined {
|
|
62
|
+
return activeHostTokenRules;
|
|
63
|
+
}
|
|
64
|
+
|
|
25
65
|
function readCssVariable(root: HTMLElement, variableName: string): string {
|
|
26
66
|
return getComputedStyle(root).getPropertyValue(variableName).trim();
|
|
27
67
|
}
|
|
@@ -132,6 +172,187 @@ export function buildMonacoThemeColors(colors: WorkbenchThemeCssColors): monaco.
|
|
|
132
172
|
};
|
|
133
173
|
}
|
|
134
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Monaco token `foreground` / `background` values omit `#`.
|
|
177
|
+
* Invalid or empty colors are skipped by callers.
|
|
178
|
+
*/
|
|
179
|
+
export function toMonacoTokenColor(color: string | undefined): string | undefined {
|
|
180
|
+
if (!color) {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
const trimmed = color.trim();
|
|
184
|
+
if (!trimmed) {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
const withoutHash = trimmed.startsWith('#') ? trimmed.slice(1) : trimmed;
|
|
188
|
+
// Drop alpha channel when present (#RRGGBBAA → RRGGBB) for token rules.
|
|
189
|
+
if (/^[0-9a-fA-F]{8}$/.test(withoutHash)) {
|
|
190
|
+
return withoutHash.slice(0, 6);
|
|
191
|
+
}
|
|
192
|
+
if (/^[0-9a-fA-F]{6}$/.test(withoutHash) || /^[0-9a-fA-F]{3}$/.test(withoutHash)) {
|
|
193
|
+
return withoutHash;
|
|
194
|
+
}
|
|
195
|
+
// Non-hex (rgb/hsl/var) — Monaco rules require hex; skip safely.
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Default syntax rules derived from chrome CSS tokens so built-in Monaco
|
|
201
|
+
* languages track the active workbench palette. Hosts that load TextMate /
|
|
202
|
+
* grammar packs should supply richer rules via `setWorkbenchMonacoTokenRules`
|
|
203
|
+
* or `defineOrUpdateWorkbenchMonacoTheme`.
|
|
204
|
+
*/
|
|
205
|
+
export function buildDefaultMonacoTokenRules(colors: WorkbenchThemeCssColors): MonacoTokenRule[] {
|
|
206
|
+
const text = toMonacoTokenColor(colors.text);
|
|
207
|
+
const muted = toMonacoTokenColor(colors.textMuted);
|
|
208
|
+
const subtle = toMonacoTokenColor(colors.textSubtle);
|
|
209
|
+
const accent = toMonacoTokenColor(colors.accent);
|
|
210
|
+
const danger = toMonacoTokenColor(colors.danger);
|
|
211
|
+
|
|
212
|
+
const rules: MonacoTokenRule[] = [];
|
|
213
|
+
const push = (token: string, foreground: string | undefined, fontStyle?: string) => {
|
|
214
|
+
if (!foreground) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
rules.push(fontStyle ? { token, foreground, fontStyle } : { token, foreground });
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
push('comment', subtle, 'italic');
|
|
221
|
+
push('string', accent);
|
|
222
|
+
push('string.escape', muted);
|
|
223
|
+
push('keyword', accent);
|
|
224
|
+
push('keyword.flow', accent);
|
|
225
|
+
push('number', danger);
|
|
226
|
+
push('regexp', danger);
|
|
227
|
+
push('type', accent);
|
|
228
|
+
push('class', accent);
|
|
229
|
+
push('function', text);
|
|
230
|
+
push('variable', text);
|
|
231
|
+
push('variable.predefined', muted);
|
|
232
|
+
push('constant', muted);
|
|
233
|
+
push('delimiter', muted);
|
|
234
|
+
push('delimiter.html', muted);
|
|
235
|
+
push('tag', accent);
|
|
236
|
+
push('metatag', muted);
|
|
237
|
+
push('attribute.name', muted);
|
|
238
|
+
push('attribute.value', accent);
|
|
239
|
+
push('invalid', danger);
|
|
240
|
+
|
|
241
|
+
return rules;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Best-effort map from VS Code `tokenColors` to Monaco rules.
|
|
246
|
+
* Uses the first scope segment as the Monaco token name; unknown / invalid
|
|
247
|
+
* entries are skipped (safe fallback to defaults / inherit).
|
|
248
|
+
*/
|
|
249
|
+
export function monacoRulesFromTokenColors(
|
|
250
|
+
tokenColors: readonly WorkbenchTokenColorSetting[],
|
|
251
|
+
): MonacoTokenRule[] {
|
|
252
|
+
const rules: MonacoTokenRule[] = [];
|
|
253
|
+
|
|
254
|
+
for (const entry of tokenColors) {
|
|
255
|
+
const scopes = normalizeTokenScopes(entry.scope);
|
|
256
|
+
if (scopes.length === 0) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
const foreground = toMonacoTokenColor(entry.settings?.foreground);
|
|
260
|
+
const background = toMonacoTokenColor(entry.settings?.background);
|
|
261
|
+
const fontStyle = entry.settings?.fontStyle?.trim() || undefined;
|
|
262
|
+
if (!foreground && !background && !fontStyle) {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
for (const scope of scopes) {
|
|
267
|
+
const token = scopeToMonacoToken(scope);
|
|
268
|
+
if (!token) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
rules.push({
|
|
272
|
+
token,
|
|
273
|
+
...(foreground ? { foreground } : {}),
|
|
274
|
+
...(background ? { background } : {}),
|
|
275
|
+
...(fontStyle ? { fontStyle } : {}),
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return rules;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function normalizeTokenScopes(scope: WorkbenchTokenColorSetting['scope']): string[] {
|
|
284
|
+
if (!scope) {
|
|
285
|
+
return [];
|
|
286
|
+
}
|
|
287
|
+
if (typeof scope === 'string') {
|
|
288
|
+
return scope
|
|
289
|
+
.split(',')
|
|
290
|
+
.map((part) => part.trim())
|
|
291
|
+
.filter(Boolean);
|
|
292
|
+
}
|
|
293
|
+
return scope.map((part) => part.trim()).filter(Boolean);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function scopeToMonacoToken(scope: string): string | undefined {
|
|
297
|
+
const trimmed = scope.trim();
|
|
298
|
+
if (!trimmed) {
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
// Prefer the leaf-most useful segment for Monaco's flatter token names.
|
|
302
|
+
const parts = trimmed.split('.');
|
|
303
|
+
if (parts[0] === 'comment') {
|
|
304
|
+
return 'comment';
|
|
305
|
+
}
|
|
306
|
+
if (parts[0] === 'string') {
|
|
307
|
+
return parts[1] === 'escape' ? 'string.escape' : 'string';
|
|
308
|
+
}
|
|
309
|
+
if (parts[0] === 'keyword') {
|
|
310
|
+
return parts[1] === 'control' || parts[1] === 'flow' ? 'keyword.flow' : 'keyword';
|
|
311
|
+
}
|
|
312
|
+
if (parts[0] === 'constant' && parts[1] === 'numeric') {
|
|
313
|
+
return 'number';
|
|
314
|
+
}
|
|
315
|
+
if (parts[0] === 'constant' && parts[1] === 'regexp') {
|
|
316
|
+
return 'regexp';
|
|
317
|
+
}
|
|
318
|
+
if (parts[0] === 'entity' && parts[1] === 'name' && parts[2] === 'type') {
|
|
319
|
+
return 'type';
|
|
320
|
+
}
|
|
321
|
+
if (parts[0] === 'entity' && parts[1] === 'name' && parts[2] === 'function') {
|
|
322
|
+
return 'function';
|
|
323
|
+
}
|
|
324
|
+
if (parts[0] === 'entity' && parts[1] === 'name' && parts[2] === 'tag') {
|
|
325
|
+
return 'tag';
|
|
326
|
+
}
|
|
327
|
+
if (parts[0] === 'entity' && parts[1] === 'other' && parts[2] === 'attribute-name') {
|
|
328
|
+
return 'attribute.name';
|
|
329
|
+
}
|
|
330
|
+
if (parts[0] === 'variable') {
|
|
331
|
+
return 'variable';
|
|
332
|
+
}
|
|
333
|
+
if (parts[0] === 'invalid') {
|
|
334
|
+
return 'invalid';
|
|
335
|
+
}
|
|
336
|
+
// Fall back to the raw scope so hosts using matching tokenizer names still work.
|
|
337
|
+
return trimmed;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** Later lists win on duplicate `token` keys. */
|
|
341
|
+
export function mergeMonacoTokenRules(
|
|
342
|
+
...groups: Array<readonly MonacoTokenRule[] | undefined>
|
|
343
|
+
): MonacoTokenRule[] {
|
|
344
|
+
const byToken = new Map<string, MonacoTokenRule>();
|
|
345
|
+
for (const group of groups) {
|
|
346
|
+
if (!group) {
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
for (const rule of group) {
|
|
350
|
+
byToken.set(rule.token, rule);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return [...byToken.values()];
|
|
354
|
+
}
|
|
355
|
+
|
|
135
356
|
export function resolveMonacoThemeRoot(root?: HTMLElement): HTMLElement | null {
|
|
136
357
|
if (root) {
|
|
137
358
|
return root;
|
|
@@ -144,24 +365,64 @@ export function resolveMonacoThemeRoot(root?: HTMLElement): HTMLElement | null {
|
|
|
144
365
|
return document.documentElement;
|
|
145
366
|
}
|
|
146
367
|
|
|
368
|
+
/**
|
|
369
|
+
* Low-level `editor.defineTheme` wrapper. Safe to call on theme switches
|
|
370
|
+
* without remounting editors that already use `themeId`.
|
|
371
|
+
*/
|
|
372
|
+
export function defineOrUpdateWorkbenchMonacoTheme(
|
|
373
|
+
monacoInstance: typeof monaco,
|
|
374
|
+
themeId: string,
|
|
375
|
+
input: WorkbenchMonacoThemeInput,
|
|
376
|
+
): void {
|
|
377
|
+
monacoInstance.editor.defineTheme(themeId, {
|
|
378
|
+
base: input.base,
|
|
379
|
+
inherit: true,
|
|
380
|
+
rules: [...(input.rules ?? [])],
|
|
381
|
+
colors: { ...(input.colors ?? {}) },
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export interface DefineMonacoWorkbenchThemeOptions {
|
|
386
|
+
/** Extra rules merged after defaults + host registry (wins on duplicate tokens). */
|
|
387
|
+
readonly rules?: readonly MonacoTokenRule[];
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Define the kit dark/light Monaco theme from live chrome CSS variables and
|
|
392
|
+
* optional host tokenColors rules. Re-callable when `data-theme` / preset changes.
|
|
393
|
+
*/
|
|
394
|
+
export function buildWorkbenchMonacoThemeInput(
|
|
395
|
+
resolvedTheme: MonacoWorkbenchResolvedTheme,
|
|
396
|
+
cssColors: WorkbenchThemeCssColors,
|
|
397
|
+
options?: DefineMonacoWorkbenchThemeOptions,
|
|
398
|
+
): WorkbenchMonacoThemeInput {
|
|
399
|
+
return {
|
|
400
|
+
base: resolvedTheme === 'light' ? 'vs' : 'vs-dark',
|
|
401
|
+
colors: buildMonacoThemeColors(cssColors) as Record<string, string>,
|
|
402
|
+
rules: mergeMonacoTokenRules(
|
|
403
|
+
buildDefaultMonacoTokenRules(cssColors),
|
|
404
|
+
getWorkbenchMonacoTokenRules(),
|
|
405
|
+
options?.rules,
|
|
406
|
+
),
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
147
410
|
export function defineMonacoWorkbenchTheme(
|
|
148
411
|
monacoInstance: typeof monaco,
|
|
149
412
|
resolvedTheme: MonacoWorkbenchResolvedTheme,
|
|
150
413
|
root?: HTMLElement,
|
|
151
|
-
|
|
414
|
+
options?: DefineMonacoWorkbenchThemeOptions,
|
|
415
|
+
): void {
|
|
152
416
|
const themeRoot = resolveMonacoThemeRoot(root);
|
|
153
417
|
if (!themeRoot) {
|
|
154
418
|
return;
|
|
155
419
|
}
|
|
156
420
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
rules: [],
|
|
163
|
-
colors: buildMonacoThemeColors(readWorkbenchThemeColors(themeRoot)),
|
|
164
|
-
});
|
|
421
|
+
defineOrUpdateWorkbenchMonacoTheme(
|
|
422
|
+
monacoInstance,
|
|
423
|
+
monacoThemeForWorkspaceTheme(resolvedTheme),
|
|
424
|
+
buildWorkbenchMonacoThemeInput(resolvedTheme, readWorkbenchThemeColors(themeRoot), options),
|
|
425
|
+
);
|
|
165
426
|
}
|
|
166
427
|
|
|
167
428
|
export function monacoThemeForWorkspaceTheme(theme: MonacoWorkbenchResolvedTheme) {
|
|
@@ -28,6 +28,13 @@ export function WorkbenchMonacoEditor({
|
|
|
28
28
|
export const useMonacoWorkbenchThemeSync = () => undefined;
|
|
29
29
|
export const prepareMonacoWorkbenchEditor = () => undefined;
|
|
30
30
|
export const defineMonacoWorkbenchTheme = () => undefined;
|
|
31
|
+
export const defineOrUpdateWorkbenchMonacoTheme = () => undefined;
|
|
32
|
+
export const setWorkbenchMonacoTokenRules = () => undefined;
|
|
33
|
+
export const getWorkbenchMonacoTokenRules = () => undefined;
|
|
34
|
+
export const buildDefaultMonacoTokenRules = () => [];
|
|
35
|
+
export const monacoRulesFromTokenColors = () => [];
|
|
36
|
+
export const mergeMonacoTokenRules = (...groups: unknown[]) => groups.flat();
|
|
37
|
+
export const toMonacoTokenColor = (color: string) => color;
|
|
31
38
|
export const configureWorkspaceEditorTypeScriptDiagnostics = () => undefined;
|
|
32
39
|
export const monacoThemeForWorkspaceTheme = (theme: string) =>
|
|
33
40
|
theme === 'light' ? MONACO_LIGHT_THEME_ID : MONACO_DARK_THEME_ID;
|