Skip to content

Commit 59b8a2d

Browse files
Alberto IannacconeAkos Kitta
Alberto Iannaccone
and
Akos Kitta
authored
Register custom themes after the monaco theme init (#1257)
Signed-off-by: Akos Kitta <a.kitta@arduino.cc> Co-authored-by: Akos Kitta <a.kitta@arduino.cc>
1 parent 124738d commit 59b8a2d

File tree

5 files changed

+174
-15
lines changed

5 files changed

+174
-15
lines changed

Diff for: arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts

+32-15
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,10 @@ import { BoardsAutoInstaller } from './boards/boards-auto-installer';
8282
import { ShellLayoutRestorer } from './theia/core/shell-layout-restorer';
8383
import { ListItemRenderer } from './widgets/component-list/list-item-renderer';
8484
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
85-
import { MonacoThemingService } from '@theia/monaco/lib/browser/monaco-theming-service';
85+
import {
86+
MonacoThemeJson,
87+
MonacoThemingService,
88+
} from '@theia/monaco/lib/browser/monaco-theming-service';
8689
import {
8790
ArduinoDaemonPath,
8891
ArduinoDaemon,
@@ -310,20 +313,34 @@ import { SelectedBoard } from './contributions/selected-board';
310313
import { CheckForUpdates } from './contributions/check-for-updates';
311314
import { OpenBoardsConfig } from './contributions/open-boards-config';
312315
import { SketchFilesTracker } from './contributions/sketch-files-tracker';
313-
314-
MonacoThemingService.register({
315-
id: 'arduino-theme',
316-
label: 'Light (Arduino)',
317-
uiTheme: 'vs',
318-
json: require('../../src/browser/data/default.color-theme.json'),
319-
});
320-
321-
MonacoThemingService.register({
322-
id: 'arduino-theme-dark',
323-
label: 'Dark (Arduino)',
324-
uiTheme: 'vs-dark',
325-
json: require('../../src/browser/data/dark.color-theme.json'),
326-
});
316+
import { MonacoThemeServiceIsReady } from './utils/window';
317+
import { Deferred } from '@theia/core/lib/common/promise-util';
318+
319+
const registerArduinoThemes = () => {
320+
const themes: MonacoThemeJson[] = [
321+
{
322+
id: 'arduino-theme',
323+
label: 'Light (Arduino)',
324+
uiTheme: 'vs',
325+
json: require('../../src/browser/data/default.color-theme.json'),
326+
},
327+
{
328+
id: 'arduino-theme-dark',
329+
label: 'Dark (Arduino)',
330+
uiTheme: 'vs-dark',
331+
json: require('../../src/browser/data/dark.color-theme.json'),
332+
},
333+
];
334+
themes.forEach((theme) => MonacoThemingService.register(theme));
335+
};
336+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
337+
const global = window as any;
338+
const ready = global[MonacoThemeServiceIsReady] as Deferred;
339+
if (ready) {
340+
ready.promise.then(registerArduinoThemes);
341+
} else {
342+
registerArduinoThemes();
343+
}
327344

328345
export default new ContainerModule((bind, unbind, isBound, rebind) => {
329346
// Commands and toolbar items

Diff for: arduino-ide-extension/src/browser/utils/window.ts

+8
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,11 @@
55
export function setURL(url: URL, data: any = {}): void {
66
history.pushState(data, '', url);
77
}
8+
9+
/**
10+
* If available from the `window` object, then it means, the IDE2 has successfully patched the `MonacoThemingService#init` static method,
11+
* and can wait the custom theme registration.
12+
*/
13+
export const MonacoThemeServiceIsReady = Symbol(
14+
'@arduino-ide#monaco-theme-service-is-ready'
15+
);

Diff for: electron-app/patch/frontend/index.js

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Patch for the startup theme. Customizes the `ThemeService.get().defaultTheme();` to dispatch the default IDE2 theme based on the OS' theme.
2+
// For all subsequent starts of the IDE the theme applied will be the last one set by the user.
3+
4+
// With the current version of Theia adopted (1.25) it is not possible to extend the `ThemeService`, it will be possible starting from Theia 1.27.
5+
// Once the version of Theia is updated, this patch will be removed and this functionality will be implemented via dependency injection.
6+
// Ideally, we should open a PR in Theia and add support for `light` and `dark` default themes in the app config.
7+
8+
const {
9+
ThemeService,
10+
ThemeServiceSymbol,
11+
BuiltinThemeProvider,
12+
} = require('@theia/core/lib/browser/theming');
13+
const {
14+
ApplicationProps,
15+
} = require('@theia/application-package/lib/application-props');
16+
const {
17+
FrontendApplicationConfigProvider,
18+
} = require('@theia/core/lib/browser/frontend-application-config-provider');
19+
20+
// It is a mighty hack to support theme updates in the bundled IDE2.
21+
// If the custom theme registration happens before the restoration of the existing monaco themes, then any custom theme changes will be ignored.
22+
// This patch introduces a static deferred promise in the monaco-theming service that will be resolved when the restoration is ready.
23+
// IDE2 cannot require the monaco theme service on the outer module level, as it requires the application config provider to be initialized,
24+
// but the initialization happens only in the generated `index.js`.
25+
// This patch customizes the monaco theme service behavior before loading the DI containers via the preload.
26+
// The preload is called only once before the app loads. The Theia extensions are not loaded at that point, but the app config provider is ready.
27+
const preloader = require('@theia/core/lib/browser/preloader');
28+
const originalPreload = preloader.preload;
29+
preloader.preload = async function () {
30+
const { MonacoThemingService } = require('@theia/monaco/lib/browser/monaco-theming-service');
31+
const { MonacoThemeServiceIsReady } = require('arduino-ide-extension/lib/browser/utils/window');
32+
const { Deferred } = require('@theia/core/lib/common/promise-util');
33+
const ready = new Deferred();
34+
if (!window[MonacoThemeServiceIsReady]) {
35+
window[MonacoThemeServiceIsReady] = ready;
36+
console.log('Registered a custom monaco-theme service initialization signal on the window object.');
37+
}
38+
// Here, it is safe to patch the theme service, app config provider is ready.
39+
MonacoThemingService.init = async function () {
40+
this.updateBodyUiTheme();
41+
ThemeService.get().onDidColorThemeChange(() => this.updateBodyUiTheme());
42+
await this.restore();
43+
ready.resolve();
44+
}.bind(MonacoThemingService);
45+
return originalPreload();
46+
}.bind(preloader);
47+
48+
const lightTheme = 'arduino-theme';
49+
const darkTheme = 'arduino-theme-dark';
50+
const defaultTheme =
51+
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
52+
? darkTheme
53+
: lightTheme;
54+
55+
const originalGet = FrontendApplicationConfigProvider.get;
56+
FrontendApplicationConfigProvider.get = function () {
57+
const originalProps = originalGet.bind(FrontendApplicationConfigProvider)();
58+
return { ...originalProps, defaultTheme };
59+
}.bind(FrontendApplicationConfigProvider);
60+
61+
const arduinoDarkTheme = {
62+
id: 'arduino-theme-dark',
63+
type: 'dark',
64+
label: 'Dark (Arduino)',
65+
editorTheme: 'arduino-theme-dark',
66+
activate() {},
67+
deactivate() {},
68+
};
69+
70+
const arduinoLightTheme = {
71+
id: 'arduino-theme',
72+
type: 'light',
73+
label: 'Light (Arduino)',
74+
editorTheme: 'arduino-theme',
75+
activate() {},
76+
deactivate() {},
77+
};
78+
79+
if (!window[ThemeServiceSymbol]) {
80+
const themeService = new ThemeService();
81+
Object.defineProperty(themeService, 'defaultTheme', {
82+
get: function () {
83+
return (
84+
this.themes[defaultTheme] ||
85+
this.themes[ApplicationProps.DEFAULT.frontend.config.defaultTheme]
86+
);
87+
},
88+
});
89+
themeService.register(
90+
...BuiltinThemeProvider.themes,
91+
arduinoDarkTheme,
92+
arduinoLightTheme
93+
);
94+
themeService.startupTheme();
95+
themeService.setCurrentTheme(defaultTheme);
96+
window[ThemeServiceSymbol] = themeService;
97+
}
98+
99+
// Require the original, generated `index.js` for `webpack` as the next entry for the `bundle.js`.
100+
require('../../src-gen/frontend/index');

Diff for: electron-app/webpack.config.js

+6
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,10 @@ config.module.rules.push({
1717
loader: require.resolve('@theia/application-manager/lib/expose-loader')
1818
}); */
1919

20+
21+
// Load the patched `index.js` that sets the desired theme in IDE2 based on the OS' theme.
22+
// The `patch/frontend/index.js` will require the original, generated `index.js`.
23+
// See: https://github.com/arduino/arduino-ide/pull/1160.
24+
config.entry.bundle = require('path').resolve(__dirname, 'patch/frontend/index.js');
25+
2026
module.exports = config;

Diff for: electron/build/patch/frontend/index.js

+28
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,34 @@ const {
1717
FrontendApplicationConfigProvider,
1818
} = require('@theia/core/lib/browser/frontend-application-config-provider');
1919

20+
// It is a mighty hack to support theme updates in the bundled IDE2.
21+
// If the custom theme registration happens before the restoration of the existing monaco themes, then any custom theme changes will be ignored.
22+
// This patch introduces a static deferred promise in the monaco-theming service that will be resolved when the restoration is ready.
23+
// IDE2 cannot require the monaco theme service on the outer module level, as it requires the application config provider to be initialized,
24+
// but the initialization happens only in the generated `index.js`.
25+
// This patch customizes the monaco theme service behavior before loading the DI containers via the preload.
26+
// The preload is called only once before the app loads. The Theia extensions are not loaded at that point, but the app config provider is ready.
27+
const preloader = require('@theia/core/lib/browser/preloader');
28+
const originalPreload = preloader.preload;
29+
preloader.preload = async function () {
30+
const { MonacoThemingService } = require('@theia/monaco/lib/browser/monaco-theming-service');
31+
const { MonacoThemeServiceIsReady } = require('arduino-ide-extension/lib/browser/utils/window');
32+
const { Deferred } = require('@theia/core/lib/common/promise-util');
33+
const ready = new Deferred();
34+
if (!window[MonacoThemeServiceIsReady]) {
35+
window[MonacoThemeServiceIsReady] = ready;
36+
console.log('Registered a custom monaco-theme service initialization signal on the window object.');
37+
}
38+
// Here, it is safe to patch the theme service, app config provider is ready.
39+
MonacoThemingService.init = async function () {
40+
this.updateBodyUiTheme();
41+
ThemeService.get().onDidColorThemeChange(() => this.updateBodyUiTheme());
42+
await this.restore();
43+
ready.resolve();
44+
}.bind(MonacoThemingService);
45+
return originalPreload();
46+
}.bind(preloader);
47+
2048
const lightTheme = 'arduino-theme';
2149
const darkTheme = 'arduino-theme-dark';
2250
const defaultTheme =

0 commit comments

Comments
 (0)