@opensumi/ide-theme 2.21.13 → 2.22.0

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.
Files changed (117) hide show
  1. package/lib/browser/icon-theme-data.js.map +1 -1
  2. package/lib/browser/icon-theme-store.js.map +1 -1
  3. package/lib/browser/icon.service.d.ts +3 -1
  4. package/lib/browser/icon.service.d.ts.map +1 -1
  5. package/lib/browser/icon.service.js +56 -24
  6. package/lib/browser/icon.service.js.map +1 -1
  7. package/lib/browser/index.js.map +1 -1
  8. package/lib/browser/semantic-tokens-registry.js.map +1 -1
  9. package/lib/browser/style.service.js.map +1 -1
  10. package/lib/browser/theme-data.js +7 -7
  11. package/lib/browser/theme-data.js.map +1 -1
  12. package/lib/browser/theme-store.js.map +1 -1
  13. package/lib/browser/theme.contribution.js +3 -3
  14. package/lib/browser/theme.contribution.js.map +1 -1
  15. package/lib/browser/workbench.theme.service.js +2 -2
  16. package/lib/browser/workbench.theme.service.js.map +1 -1
  17. package/lib/common/color-tokens/basic-color.d.ts +1 -1
  18. package/lib/common/color-tokens/basic-color.d.ts.map +1 -1
  19. package/lib/common/color-tokens/editor.d.ts +13 -0
  20. package/lib/common/color-tokens/editor.d.ts.map +1 -1
  21. package/lib/common/color-tokens/editor.js +35 -1
  22. package/lib/common/color-tokens/editor.js.map +1 -1
  23. package/lib/common/color.js +40 -40
  24. package/lib/common/color.js.map +1 -1
  25. package/lib/common/mocks/theme.service.js.map +1 -1
  26. package/lib/common/plistParser.js +40 -40
  27. package/lib/common/plistParser.js.map +1 -1
  28. package/lib/common/semantic-tokens-registry.d.ts +7 -7
  29. package/lib/common/semantic-tokens-registry.d.ts.map +1 -1
  30. package/lib/common/theme.service.d.ts +26 -6
  31. package/lib/common/theme.service.d.ts.map +1 -1
  32. package/lib/common/theme.service.js.map +1 -1
  33. package/package.json +11 -10
  34. package/src/browser/default-theme.ts +547 -0
  35. package/src/browser/icon-theme-data.ts +294 -0
  36. package/src/browser/icon-theme-store.ts +38 -0
  37. package/src/browser/icon.less +15 -0
  38. package/src/browser/icon.service.ts +457 -0
  39. package/src/browser/index.ts +45 -0
  40. package/src/browser/semantic-tokens-registry.ts +217 -0
  41. package/src/browser/style.service.ts +51 -0
  42. package/src/browser/theme-data.ts +719 -0
  43. package/src/browser/theme-store.ts +95 -0
  44. package/src/browser/theme.contribution.ts +343 -0
  45. package/src/browser/workbench.theme.service.ts +703 -0
  46. package/src/common/color-registry.ts +52 -0
  47. package/src/common/color-tokens/activity-bar.ts +122 -0
  48. package/src/common/color-tokens/badge.ts +31 -0
  49. package/src/common/color-tokens/base.ts +90 -0
  50. package/src/common/color-tokens/basic-color.ts +9 -0
  51. package/src/common/color-tokens/breadcrumb.ts +60 -0
  52. package/src/common/color-tokens/button.ts +69 -0
  53. package/src/common/color-tokens/charts.ts +68 -0
  54. package/src/common/color-tokens/checkbox.ts +23 -0
  55. package/src/common/color-tokens/custom/actionbar.ts +51 -0
  56. package/src/common/color-tokens/custom/activity-bar.ts +16 -0
  57. package/src/common/color-tokens/custom/badge.ts +30 -0
  58. package/src/common/color-tokens/custom/base.ts +111 -0
  59. package/src/common/color-tokens/custom/button.ts +359 -0
  60. package/src/common/color-tokens/custom/checkbox.ts +36 -0
  61. package/src/common/color-tokens/custom/decoration.ts +71 -0
  62. package/src/common/color-tokens/custom/editor.ts +27 -0
  63. package/src/common/color-tokens/custom/extension.ts +9 -0
  64. package/src/common/color-tokens/custom/icon.ts +30 -0
  65. package/src/common/color-tokens/custom/index.ts +26 -0
  66. package/src/common/color-tokens/custom/input.ts +48 -0
  67. package/src/common/color-tokens/custom/menu.ts +61 -0
  68. package/src/common/color-tokens/custom/modal.ts +57 -0
  69. package/src/common/color-tokens/custom/notification.ts +16 -0
  70. package/src/common/color-tokens/custom/panel.ts +112 -0
  71. package/src/common/color-tokens/custom/popover.ts +28 -0
  72. package/src/common/color-tokens/custom/select.ts +155 -0
  73. package/src/common/color-tokens/custom/settings.ts +32 -0
  74. package/src/common/color-tokens/custom/statusbar.ts +16 -0
  75. package/src/common/color-tokens/custom/tab.ts +31 -0
  76. package/src/common/color-tokens/custom/tooltip.ts +55 -0
  77. package/src/common/color-tokens/custom/tree.ts +106 -0
  78. package/src/common/color-tokens/debug.ts +103 -0
  79. package/src/common/color-tokens/debugToolbar.ts +134 -0
  80. package/src/common/color-tokens/dropdown.ts +27 -0
  81. package/src/common/color-tokens/editor.ts +945 -0
  82. package/src/common/color-tokens/index.ts +35 -0
  83. package/src/common/color-tokens/input.ts +105 -0
  84. package/src/common/color-tokens/list-tree.ts +205 -0
  85. package/src/common/color-tokens/menu-bar.ts +43 -0
  86. package/src/common/color-tokens/menu.ts +53 -0
  87. package/src/common/color-tokens/merge-conflict.ts +145 -0
  88. package/src/common/color-tokens/minimap.ts +99 -0
  89. package/src/common/color-tokens/notification.ts +169 -0
  90. package/src/common/color-tokens/panel.ts +177 -0
  91. package/src/common/color-tokens/pick-view.ts +96 -0
  92. package/src/common/color-tokens/picker.ts +15 -0
  93. package/src/common/color-tokens/progress-bar.ts +12 -0
  94. package/src/common/color-tokens/quick-input.ts +57 -0
  95. package/src/common/color-tokens/scrollbar.ts +42 -0
  96. package/src/common/color-tokens/settings.ts +126 -0
  97. package/src/common/color-tokens/sidebar.ts +121 -0
  98. package/src/common/color-tokens/snippet.ts +33 -0
  99. package/src/common/color-tokens/status-bar.ts +350 -0
  100. package/src/common/color-tokens/tab.ts +346 -0
  101. package/src/common/color-tokens/testing.ts +105 -0
  102. package/src/common/color-tokens/text.ts +41 -0
  103. package/src/common/color-tokens/title-bar.ts +62 -0
  104. package/src/common/color-tokens/toolbar.ts +28 -0
  105. package/src/common/color-tokens/welcome-page.ts +27 -0
  106. package/src/common/color.ts +647 -0
  107. package/src/common/default-themes.ts +273 -0
  108. package/src/common/event.ts +9 -0
  109. package/src/common/index.ts +8 -0
  110. package/src/common/mocks/theme.service.ts +55 -0
  111. package/src/common/plistParser.ts +525 -0
  112. package/src/common/semantic-tokens-registry.ts +439 -0
  113. package/src/common/style.ts +9 -0
  114. package/src/common/theme.service.ts +363 -0
  115. package/src/common/themeCompatibility.ts +95 -0
  116. package/src/common/utils.ts +195 -0
  117. package/src/index.ts +1 -0
@@ -0,0 +1,703 @@
1
+ import { Autowired, Injectable } from '@opensumi/di';
2
+ import {
3
+ Logger,
4
+ PreferenceService,
5
+ PreferenceSchemaProvider,
6
+ IPreferenceSettingsService,
7
+ } from '@opensumi/ide-core-browser';
8
+ import {
9
+ Event,
10
+ URI,
11
+ WithEventBus,
12
+ localize,
13
+ Emitter,
14
+ isObject,
15
+ DisposableCollection,
16
+ uuid,
17
+ isLinux,
18
+ isWindows,
19
+ IThemeColor,
20
+ OnEvent,
21
+ ExtensionDidContributes,
22
+ Deferred,
23
+ } from '@opensumi/ide-core-common';
24
+
25
+ import { ICSSStyleService } from '../common';
26
+ import { Color } from '../common/color';
27
+ import {
28
+ editorSelectionBackground,
29
+ getColorRegistry,
30
+ ktInputSelectionBackground,
31
+ menuDisableForeground,
32
+ selectionBackground,
33
+ } from '../common/color-registry';
34
+ import { ThemeChangedEvent } from '../common/event';
35
+ import {
36
+ ITheme,
37
+ ThemeType,
38
+ ColorIdentifier,
39
+ getBuiltinRules,
40
+ getThemeType,
41
+ ThemeContribution,
42
+ IColorMap,
43
+ ThemeInfo,
44
+ IThemeService,
45
+ ExtColorContribution,
46
+ getThemeId,
47
+ getThemeTypeSelector,
48
+ IColorCustomizations,
49
+ ITokenColorizationRule,
50
+ ITokenColorCustomizations,
51
+ DEFAULT_THEME_ID,
52
+ colorIdPattern,
53
+ } from '../common/theme.service';
54
+
55
+ import { ThemeData } from './theme-data';
56
+ import { ThemeStore } from './theme-store';
57
+
58
+ export const CUSTOM_WORKBENCH_COLORS_SETTING = 'workbench.colorCustomizations';
59
+ export const CUSTOM_EDITOR_COLORS_SETTING = 'editor.tokenColorCustomizations';
60
+ export const COLOR_THEME_SETTING = 'general.theme';
61
+
62
+ const tokenGroupToScopesMap = {
63
+ comments: ['comment', 'punctuation.definition.comment'],
64
+ strings: ['string', 'meta.embedded.assembly'],
65
+ keywords: ['keyword - keyword.operator', 'keyword.control', 'storage', 'storage.type'],
66
+ numbers: ['constant.numeric'],
67
+ types: ['entity.name.type', 'entity.name.class', 'support.type', 'support.class'],
68
+ functions: ['entity.name.function', 'support.function'],
69
+ variables: ['variable', 'entity.name.variable'],
70
+ };
71
+
72
+ @Injectable()
73
+ export class WorkbenchThemeService extends WithEventBus implements IThemeService {
74
+ private colorRegistry = getColorRegistry();
75
+
76
+ private colorClassNameMap = new Map<string, string>();
77
+
78
+ colorThemeLoaded: Deferred<void> = new Deferred();
79
+
80
+ public currentThemeId: string;
81
+ private currentTheme?: Theme;
82
+ private latestApplyTheme: string;
83
+
84
+ private themes: Map<string, ThemeData> = new Map();
85
+ private themeContributionRegistry: Map<string, { contribution: ThemeContribution; basePath: URI }> = new Map();
86
+
87
+ private themeChangeEmitter: Emitter<ITheme> = new Emitter();
88
+ protected extensionReady: boolean;
89
+
90
+ public onThemeChange: Event<ITheme> = this.themeChangeEmitter.event;
91
+
92
+ @Autowired()
93
+ private themeStore: ThemeStore;
94
+
95
+ @Autowired()
96
+ private logger: Logger;
97
+
98
+ @Autowired(PreferenceService)
99
+ private preferenceService: PreferenceService;
100
+
101
+ @Autowired(PreferenceSchemaProvider)
102
+ private preferenceSchemaProvider: PreferenceSchemaProvider;
103
+
104
+ @Autowired(IPreferenceSettingsService)
105
+ private preferenceSettings: IPreferenceSettingsService;
106
+
107
+ @Autowired(ICSSStyleService)
108
+ private readonly styleService: ICSSStyleService;
109
+
110
+ constructor() {
111
+ super();
112
+ this.listen();
113
+ this.applyPlatformClass();
114
+ }
115
+
116
+ @OnEvent(ExtensionDidContributes)
117
+ async onDidExtensionContributes() {
118
+ const themeMap = this.getAvailableThemeInfos().reduce((pre: Map<string, string>, cur: ThemeInfo) => {
119
+ if (!pre.has(cur.themeId)) {
120
+ pre.set(cur.themeId, cur.name);
121
+ }
122
+ return pre;
123
+ }, new Map());
124
+
125
+ this.preferenceSettings.setEnumLabels(COLOR_THEME_SETTING, Object.fromEntries(themeMap.entries()));
126
+ if (!this.currentThemeId && !this.currentTheme) {
127
+ this.colorThemeLoaded.resolve();
128
+ }
129
+ }
130
+
131
+ get preferenceThemeId(): string | undefined {
132
+ return this.preferenceService.get<string>(COLOR_THEME_SETTING);
133
+ }
134
+
135
+ public registerThemes(themeContributions: ThemeContribution[], extPath: URI) {
136
+ const disposables = new DisposableCollection();
137
+ disposables.push({
138
+ dispose: () => this.doSetPreferenceSchema(),
139
+ });
140
+
141
+ themeContributions.forEach(async (contribution) => {
142
+ const themeExtContribution = { basePath: extPath, contribution };
143
+ const themeId = getThemeId(contribution);
144
+
145
+ this.themeContributionRegistry.set(themeId, themeExtContribution);
146
+
147
+ if (this.preferenceThemeId === themeId) {
148
+ await this.applyTheme(this.preferenceThemeId);
149
+ }
150
+
151
+ disposables.push({
152
+ dispose: () => {
153
+ this.themeContributionRegistry.delete(themeId);
154
+ },
155
+ });
156
+
157
+ disposables.push({
158
+ dispose: () => {
159
+ if (this.currentThemeId === themeId) {
160
+ this.applyTheme(DEFAULT_THEME_ID);
161
+ }
162
+ },
163
+ });
164
+ });
165
+
166
+ this.doSetPreferenceSchema();
167
+
168
+ return disposables;
169
+ }
170
+
171
+ public async applyTheme(themeId: string) {
172
+ if (this.currentThemeId === themeId) {
173
+ return;
174
+ }
175
+
176
+ const prevThemeType = this.currentTheme ? this.currentTheme.type : 'dark';
177
+ this.currentThemeId = themeId;
178
+ /**
179
+ * 这里 `applyTheme` 默认应该按照最后一个应用的主题进行加载
180
+ * 但由于 `getTheme` 存在时序问题,例如:
181
+ * 主题 A,E,分别由插件 A,E 贡献
182
+ * 这里先调用 applyTheme(E), 再调用 applyTheme(A)
183
+ * 旧的逻辑由于插件 A ... E 的加载顺序问题,会存在 A 比 E 快加载的情况导致最终应用了错误的主题
184
+ *
185
+ * 故这里增加额外判断,保障最后一个加载的主题应用
186
+ */
187
+ this.latestApplyTheme = themeId;
188
+ const theme = await this.getTheme(themeId);
189
+ if (this.latestApplyTheme !== themeId) {
190
+ return;
191
+ }
192
+ const themeType = getThemeType(theme.base);
193
+ this.currentTheme = new Theme(themeType, theme);
194
+ this.currentTheme.setCustomColors(this.colorCustomizations);
195
+ this.currentTheme.setCustomTokenColors(this.tokenColorCustomizations);
196
+
197
+ const currentThemeType = this.currentTheme.type;
198
+
199
+ this.toggleBaseThemeClass(prevThemeType, currentThemeType);
200
+ this.doApplyTheme(this.currentTheme);
201
+
202
+ this.colorThemeLoaded.resolve();
203
+ }
204
+
205
+ public registerColor(contribution: ExtColorContribution) {
206
+ if (!this.checkColorContribution(contribution)) {
207
+ return;
208
+ }
209
+ const { defaults } = contribution;
210
+ this.colorRegistry.registerColor(
211
+ contribution.id,
212
+ {
213
+ light: this.parseColorValue(defaults.light, 'configuration.colors.defaults.light'),
214
+ dark: this.parseColorValue(defaults.dark, 'configuration.colors.defaults.dark'),
215
+ hcDark: this.parseColorValue(
216
+ defaults.highContrast ?? defaults.dark,
217
+ 'configuration.colors.defaults.highContrast',
218
+ ),
219
+ hcLight: this.parseColorValue(
220
+ defaults.highContrastLight ?? defaults.light,
221
+ 'configuration.colors.defaults.highContrastLight',
222
+ ),
223
+ },
224
+ contribution.description,
225
+ );
226
+ }
227
+
228
+ // @deprecated 请直接使用sync方法,主题加载由时序保障
229
+ public async getCurrentTheme() {
230
+ if (this.currentTheme) {
231
+ return this.currentTheme;
232
+ } else {
233
+ const themeData = await this.getTheme(this.currentThemeId);
234
+ return new Theme(getThemeType(themeData.base), themeData);
235
+ }
236
+ }
237
+
238
+ public getCurrentThemeSync() {
239
+ if (this.currentTheme) {
240
+ return this.currentTheme;
241
+ } else {
242
+ const themeData = this.themeStore.loadDefaultTheme();
243
+ return new Theme(getThemeType(themeData.base), themeData);
244
+ }
245
+ }
246
+
247
+ public getColor(colorId: string | IThemeColor | undefined): string | undefined {
248
+ if (!colorId) {
249
+ return undefined;
250
+ }
251
+ if (typeof colorId === 'string') {
252
+ return colorId;
253
+ }
254
+ const color = this.currentTheme?.getColor(colorId.id);
255
+ return color ? Color.Format.CSS.formatHexA(color) : '';
256
+ }
257
+
258
+ // 将 colorId 转换成 css 变量
259
+ public getColorVar(colorId: string | IThemeColor | undefined): string | undefined {
260
+ if (!colorId) {
261
+ return undefined;
262
+ }
263
+ if (typeof colorId === 'string') {
264
+ return colorId;
265
+ }
266
+ const colorKey = colorId.id;
267
+ return colorKey ? `var(--${colorKey.replace(/\./g, '-')})` : undefined;
268
+ }
269
+
270
+ public getColorClassNameByColorToken(colorId: string | IThemeColor): string | undefined {
271
+ if (!colorId) {
272
+ return undefined;
273
+ }
274
+ const id = typeof colorId === 'string' ? colorId : colorId.id;
275
+ if (this.colorClassNameMap.has(id)) {
276
+ return this.colorClassNameMap.get(id)!;
277
+ }
278
+ const className = `color-token-${uuid()}`;
279
+ this.styleService.addClass(className, {
280
+ color: this.getColorVar({ id })!,
281
+ });
282
+ this.colorClassNameMap.set(id, className);
283
+ return className;
284
+ }
285
+
286
+ public getAvailableThemeInfos(): ThemeInfo[] {
287
+ const themeInfos: ThemeInfo[] = [];
288
+ for (const { contribution } of this.themeContributionRegistry.values()) {
289
+ const { label, uiTheme } = contribution;
290
+ themeInfos.push({
291
+ themeId: getThemeId(contribution),
292
+ name: label,
293
+ base: uiTheme || 'vs',
294
+ });
295
+ }
296
+ return themeInfos;
297
+ }
298
+
299
+ protected doSetPreferenceSchema() {
300
+ this.preferenceSchemaProvider.setSchema(
301
+ {
302
+ properties: {
303
+ [COLOR_THEME_SETTING]: {
304
+ type: 'string',
305
+ default: 'Default Dark+',
306
+ enum: this.getAvailableThemeInfos().map((info) => info.themeId),
307
+ },
308
+ },
309
+ },
310
+ true,
311
+ );
312
+ }
313
+
314
+ private get colorCustomizations(): IColorCustomizations {
315
+ return this.preferenceService.getValid(CUSTOM_WORKBENCH_COLORS_SETTING, {});
316
+ }
317
+
318
+ private get tokenColorCustomizations(): ITokenColorCustomizations {
319
+ return this.preferenceService.getValid<ITokenColorCustomizations>(CUSTOM_EDITOR_COLORS_SETTING, {});
320
+ }
321
+
322
+ private listen() {
323
+ this.addDispose(
324
+ this.eventBus.on(ThemeChangedEvent, (e) => {
325
+ this.themeChangeEmitter.fire(e.payload.theme);
326
+ }),
327
+ );
328
+
329
+ this.addDispose(
330
+ Event.debounce(
331
+ this.preferenceService.onPreferenceChanged,
332
+ (_, e) => e,
333
+ 50,
334
+ )(async (e) => {
335
+ if (e.preferenceName === COLOR_THEME_SETTING) {
336
+ // make sure the theme is registered
337
+ if (this.themeContributionRegistry.has(e.newValue)) {
338
+ await this.applyTheme(e.newValue);
339
+ }
340
+ }
341
+
342
+ if (this.currentTheme) {
343
+ switch (e.preferenceName) {
344
+ case CUSTOM_WORKBENCH_COLORS_SETTING: {
345
+ this.currentTheme.setCustomColors(e.newValue);
346
+ this.doApplyTheme(this.currentTheme);
347
+ break;
348
+ }
349
+ case CUSTOM_EDITOR_COLORS_SETTING: {
350
+ this.currentTheme.setCustomTokenColors(e.newValue);
351
+ this.eventBus.fire(
352
+ new ThemeChangedEvent({
353
+ theme: this.currentTheme,
354
+ }),
355
+ );
356
+ break;
357
+ }
358
+ default:
359
+ break;
360
+ }
361
+ }
362
+ }),
363
+ );
364
+
365
+ this.addDispose(
366
+ this.colorRegistry.onDidColorChangedEvent((e) => {
367
+ if (this.currentTheme) {
368
+ this.doApplyTheme.apply(this, [this.currentTheme]);
369
+ }
370
+ }),
371
+ );
372
+ }
373
+
374
+ private checkColorContribution(contribution: ExtColorContribution) {
375
+ if (typeof contribution.id !== 'string' || contribution.id.length === 0) {
376
+ this.logger.error(localize('invalid.id', "'configuration.colors.id' must be defined and can not be empty"));
377
+ return false;
378
+ }
379
+ if (!contribution.id.match(colorIdPattern)) {
380
+ this.logger.error(localize('invalid.id.format', "'configuration.colors.id' must follow the word[.word]*"));
381
+ return false;
382
+ }
383
+ if (typeof contribution.description !== 'string' || contribution.id.length === 0) {
384
+ this.logger.error(
385
+ localize('invalid.description', "'configuration.colors.description' must be defined and can not be empty"),
386
+ );
387
+ return false;
388
+ }
389
+ const defaults = contribution.defaults;
390
+ if (
391
+ !defaults ||
392
+ typeof defaults !== 'object' ||
393
+ typeof defaults.light !== 'string' ||
394
+ typeof defaults.dark !== 'string' ||
395
+ typeof defaults.highContrast !== 'string'
396
+ ) {
397
+ this.logger.error(
398
+ localize(
399
+ 'invalid.defaults',
400
+ "'configuration.colors.defaults' must be defined and must contain 'light', 'dark' and 'highContrast'",
401
+ ),
402
+ );
403
+ return false;
404
+ }
405
+ return true;
406
+ }
407
+
408
+ private parseColorValue = (s: string, name: string) => {
409
+ if (s.length > 0) {
410
+ if (s[0] === '#') {
411
+ return Color.Format.CSS.parseHex(s);
412
+ } else {
413
+ return s;
414
+ }
415
+ }
416
+ this.logger.error(
417
+ localize(
418
+ 'invalid.default.colorType',
419
+ '{0} must be either a color value in hex (#RRGGBB[AA] or #RGB[A]) or the identifier of a themable color which provides the default.',
420
+ name,
421
+ ),
422
+ );
423
+ return Color.red;
424
+ };
425
+
426
+ private async getTheme(id: string): Promise<ThemeData> {
427
+ const theme = this.themes.get(id);
428
+ if (theme) {
429
+ return theme;
430
+ }
431
+ const themeInfo = this.themeContributionRegistry.get(id);
432
+ if (themeInfo) {
433
+ const { contribution, basePath } = themeInfo;
434
+ return await this.themeStore.getThemeData(contribution, basePath);
435
+ }
436
+ return await this.themeStore.getThemeData();
437
+ }
438
+
439
+ private doApplyTheme(theme: Theme) {
440
+ const colorContributions = this.colorRegistry.getColors();
441
+ const colors = {};
442
+ colorContributions.forEach((contribution) => {
443
+ const colorId = contribution.id;
444
+ const color = theme.getColor(colorId);
445
+ colors[colorId] = color ? color.toString() : '';
446
+ });
447
+ /** @deprecated Needs to be removed start */
448
+ const foreground = theme.getColor('foreground');
449
+ if (foreground) {
450
+ colors['foreground.secondary'] = foreground.darken(0.2).toString();
451
+ }
452
+ if (theme.getColor('menu.foreground')) {
453
+ colors[menuDisableForeground] = theme.getColor('menu.foreground')!.darken(0.4).toString();
454
+ }
455
+ /** Needs to be removed end */
456
+
457
+ // fallback to editorSelectionBackground when global selectionBackgroun is null.
458
+ if (!theme.getColor(selectionBackground)) {
459
+ colors[selectionBackground] = theme.getColor(editorSelectionBackground)?.toString();
460
+ colors[ktInputSelectionBackground] = colors[selectionBackground];
461
+ }
462
+
463
+ let cssVariables = ':root{';
464
+ for (const colorKey of Object.keys(colors)) {
465
+ const targetColor = colors[colorKey] || theme.getColor(colorKey);
466
+ if (targetColor) {
467
+ const hexRule = `--${colorKey.replace(/\./g, '-')}: ${targetColor.toString()};\n`;
468
+ cssVariables += hexRule;
469
+ }
470
+ }
471
+ let styleNode = document.getElementById('theme-style');
472
+ if (styleNode) {
473
+ styleNode.innerHTML = cssVariables + '}';
474
+ } else {
475
+ styleNode = document.createElement('style');
476
+ styleNode.id = 'theme-style';
477
+ styleNode.innerHTML = cssVariables + '}';
478
+ document.getElementsByTagName('head')[0].appendChild(styleNode);
479
+ }
480
+ if (this.currentTheme) {
481
+ this.eventBus.fire(
482
+ new ThemeChangedEvent({
483
+ theme: this.currentTheme,
484
+ }),
485
+ );
486
+ }
487
+ }
488
+
489
+ protected toggleBaseThemeClass(prevThemeType: ThemeType, themeType: ThemeType) {
490
+ const htmlNode = document.getElementsByTagName('html')[0];
491
+ htmlNode.classList.remove(getThemeTypeSelector(prevThemeType));
492
+ htmlNode.classList.add(getThemeTypeSelector(themeType));
493
+ }
494
+
495
+ protected applyPlatformClass() {
496
+ const platformClass = isWindows ? 'windows' : isLinux ? 'linux' : 'mac';
497
+ const rootNode = document.getElementsByTagName('body')[0]!;
498
+ rootNode.classList.add(platformClass);
499
+ }
500
+ }
501
+
502
+ export class Themable extends WithEventBus {
503
+ @Autowired(IThemeService)
504
+ themeService: WorkbenchThemeService;
505
+
506
+ protected theme: ITheme;
507
+
508
+ constructor() {
509
+ super();
510
+ this.listenThemeChange();
511
+ }
512
+
513
+ get currentTheme() {
514
+ return this.theme;
515
+ }
516
+
517
+ // 传入色值ID,主题色未定义时,若useDefault为false则返回undefined
518
+ protected async getColor(id: ColorIdentifier, useDefault?: boolean) {
519
+ if (!this.theme) {
520
+ this.theme = await this.themeService.getCurrentTheme();
521
+ }
522
+ return this.theme.getColor(id, useDefault);
523
+ }
524
+
525
+ protected getColorSync(id: ColorIdentifier, useDefault?: boolean) {
526
+ if (!this.theme) {
527
+ this.theme = this.themeService.getCurrentThemeSync();
528
+ }
529
+ return this.theme.getColor(id, useDefault);
530
+ }
531
+
532
+ private listenThemeChange() {
533
+ this.themeService.onThemeChange((theme) => {
534
+ this.theme = theme;
535
+ this.style();
536
+ });
537
+ }
538
+
539
+ // themeChange时自动调用,首次调用时机需要模块自己判断
540
+ async style(): Promise<void> {
541
+ // use theme
542
+ }
543
+ }
544
+
545
+ class Theme implements ITheme {
546
+ readonly type: ThemeType;
547
+ readonly themeData: ThemeData;
548
+ private readonly colorRegistry = getColorRegistry();
549
+ private readonly defaultColors: { [colorId: string]: Color | undefined } = Object.create(null);
550
+
551
+ private colorMap: IColorMap;
552
+ private customColorMap: IColorMap = {};
553
+ private customTokenColors: ITokenColorizationRule[] = [];
554
+
555
+ constructor(type: ThemeType, themeData: ThemeData) {
556
+ this.type = type;
557
+ this.themeData = themeData;
558
+ this.patchColors();
559
+ this.patchTokenColors();
560
+ this.themeData.loadCustomTokens(this.customTokenColors);
561
+ }
562
+
563
+ getColor(colorId: ColorIdentifier, useDefault?: boolean): Color | undefined {
564
+ const color = this.customColorMap[colorId] || this.getColors()[colorId];
565
+ if (color) {
566
+ return color;
567
+ }
568
+ if (useDefault !== false) {
569
+ return this.getDefault(colorId);
570
+ }
571
+ return undefined;
572
+ }
573
+
574
+ defines(color: ColorIdentifier): boolean {
575
+ if (this.customColorMap[color] || this.themeData.colors[color]) {
576
+ return true;
577
+ }
578
+ return false;
579
+ }
580
+
581
+ setCustomColors(colors: IColorCustomizations) {
582
+ this.customColorMap = {};
583
+ this.overwriteCustomColors(colors);
584
+
585
+ const themeSpecificColors = colors[`[${this.themeData.name}]`] as IColorCustomizations;
586
+ if (isObject(themeSpecificColors)) {
587
+ this.overwriteCustomColors(themeSpecificColors);
588
+ }
589
+ }
590
+
591
+ setCustomTokenColors(customTokenColors: ITokenColorCustomizations) {
592
+ this.customTokenColors = [];
593
+
594
+ // first add the non-theme specific settings
595
+ this.addCustomTokenColors(customTokenColors);
596
+
597
+ // append theme specific settings. Last rules will win.
598
+ const themeSpecificTokenColors = customTokenColors[`[${this.themeData.name}]`] as ITokenColorCustomizations;
599
+ if (isObject(themeSpecificTokenColors)) {
600
+ this.addCustomTokenColors(themeSpecificTokenColors);
601
+ }
602
+ this.themeData.loadCustomTokens(this.customTokenColors);
603
+ }
604
+
605
+ private addCustomTokenColors(customTokenColors: ITokenColorCustomizations) {
606
+ // Put the general customizations such as comments, strings, etc. first so that
607
+ // they can be overridden by specific customizations like "string.interpolated"
608
+ // eslint-disable-next-line guard-for-in
609
+ for (const tokenGroup in tokenGroupToScopesMap) {
610
+ const group = tokenGroup as keyof typeof tokenGroupToScopesMap; // TS doesn't type 'tokenGroup' properly
611
+ const value = customTokenColors[group];
612
+ if (value) {
613
+ const settings = typeof value === 'string' ? { foreground: value } : value;
614
+ const scopes = tokenGroupToScopesMap[group];
615
+ for (const scope of scopes) {
616
+ this.customTokenColors.push({ scope, settings });
617
+ }
618
+ }
619
+ }
620
+
621
+ // specific customizations
622
+ if (Array.isArray(customTokenColors.textMateRules)) {
623
+ for (const rule of customTokenColors.textMateRules) {
624
+ if (rule.scope && rule.settings) {
625
+ this.customTokenColors.push(rule);
626
+ }
627
+ }
628
+ }
629
+ }
630
+
631
+ private overwriteCustomColors(colors: IColorCustomizations) {
632
+ // eslint-disable-next-line guard-for-in
633
+ for (const id in colors) {
634
+ const colorVal = colors[id];
635
+ if (typeof colorVal === 'string') {
636
+ this.customColorMap[id] = Color.fromHex(colorVal);
637
+ }
638
+ }
639
+ }
640
+
641
+ protected patchColors() {
642
+ const colorContributions = this.colorRegistry.getColors();
643
+ for (const colorContribution of colorContributions) {
644
+ const id = colorContribution.id;
645
+ const colorMap = this.themeData.colorMap;
646
+ if (!colorMap[id]) {
647
+ const color = this.colorRegistry.resolveDefaultColor(id, this);
648
+ if (color) {
649
+ colorMap[id] = color;
650
+ this.themeData.colors[id] = Color.Format.CSS.formatHexA(color);
651
+ }
652
+ }
653
+ }
654
+ }
655
+
656
+ // 将encodedTokensColors转为monaco可用的形式
657
+ private patchTokenColors() {
658
+ // 当默认颜色不在settings当中时,此处不能使用之前那种直接给encodedTokenColors赋值的做法,会导致monaco使用时颜色错位(theia的bug
659
+ if (this.themeData.themeSettings.filter((setting) => !setting.scope).length === 0) {
660
+ this.themeData.themeSettings.unshift({
661
+ settings: {
662
+ foreground: this.themeData.colors['editor.foreground']
663
+ ? this.themeData.colors['editor.foreground'].substr(0, 7)
664
+ : Color.Format.CSS.formatHexA(this.colorRegistry.resolveDefaultColor('editor.foreground', this)!), // 这里要去掉透明度信息
665
+ background: this.themeData.colors['editor.background']
666
+ ? this.themeData.colors['editor.background'].substr(0, 7)
667
+ : Color.Format.CSS.formatHexA(this.colorRegistry.resolveDefaultColor('editor.background', this)!),
668
+ },
669
+ });
670
+ }
671
+ }
672
+
673
+ // 返回主题内的颜色值
674
+ private getColors(): IColorMap {
675
+ if (!this.colorMap) {
676
+ const colorMap = Object.create(null);
677
+ // eslint-disable-next-line guard-for-in
678
+ for (const id in this.themeData.colorMap) {
679
+ colorMap[id] = this.themeData.colorMap[id];
680
+ }
681
+ if (this.themeData.inherit) {
682
+ const baseData = getBuiltinRules(this.themeData.base);
683
+ for (const id in baseData.colors) {
684
+ if (!colorMap[id]) {
685
+ colorMap[id] = Color.fromHex(baseData.colors[id]);
686
+ }
687
+ }
688
+ }
689
+ this.colorMap = colorMap;
690
+ }
691
+ return this.colorMap;
692
+ }
693
+
694
+ private getDefault(colorId: ColorIdentifier): Color | undefined {
695
+ let color = this.defaultColors[colorId];
696
+ if (color) {
697
+ return color;
698
+ }
699
+ color = this.colorRegistry.resolveDefaultColor(colorId, this);
700
+ this.defaultColors[colorId] = color;
701
+ return color;
702
+ }
703
+ }