From 86c5fb14907f327c1eff230a719babea72208462 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Tue, 26 Jul 2022 10:34:00 +0200 Subject: [PATCH] Register custom themes after the monaco theme init Signed-off-by: Akos Kitta --- .../browser/arduino-ide-frontend-module.ts | 47 +++++--- .../src/browser/utils/window.ts | 8 ++ electron-app/patch/frontend/index.js | 100 ++++++++++++++++++ electron-app/webpack.config.js | 6 ++ electron/build/patch/frontend/index.js | 28 +++++ 5 files changed, 174 insertions(+), 15 deletions(-) create mode 100644 electron-app/patch/frontend/index.js diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index 59f2cef86..29224191d 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -82,7 +82,10 @@ import { BoardsAutoInstaller } from './boards/boards-auto-installer'; import { ShellLayoutRestorer } from './theia/core/shell-layout-restorer'; import { ListItemRenderer } from './widgets/component-list/list-item-renderer'; import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution'; -import { MonacoThemingService } from '@theia/monaco/lib/browser/monaco-theming-service'; +import { + MonacoThemeJson, + MonacoThemingService, +} from '@theia/monaco/lib/browser/monaco-theming-service'; import { ArduinoDaemonPath, ArduinoDaemon, @@ -310,20 +313,34 @@ import { SelectedBoard } from './contributions/selected-board'; import { CheckForUpdates } from './contributions/check-for-updates'; import { OpenBoardsConfig } from './contributions/open-boards-config'; import { SketchFilesTracker } from './contributions/sketch-files-tracker'; - -MonacoThemingService.register({ - id: 'arduino-theme', - label: 'Light (Arduino)', - uiTheme: 'vs', - json: require('../../src/browser/data/default.color-theme.json'), -}); - -MonacoThemingService.register({ - id: 'arduino-theme-dark', - label: 'Dark (Arduino)', - uiTheme: 'vs-dark', - json: require('../../src/browser/data/dark.color-theme.json'), -}); +import { MonacoThemeServiceIsReady } from './utils/window'; +import { Deferred } from '@theia/core/lib/common/promise-util'; + +const registerArduinoThemes = () => { + const themes: MonacoThemeJson[] = [ + { + id: 'arduino-theme', + label: 'Light (Arduino)', + uiTheme: 'vs', + json: require('../../src/browser/data/default.color-theme.json'), + }, + { + id: 'arduino-theme-dark', + label: 'Dark (Arduino)', + uiTheme: 'vs-dark', + json: require('../../src/browser/data/dark.color-theme.json'), + }, + ]; + themes.forEach((theme) => MonacoThemingService.register(theme)); +}; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const global = window as any; +const ready = global[MonacoThemeServiceIsReady] as Deferred; +if (ready) { + ready.promise.then(registerArduinoThemes); +} else { + registerArduinoThemes(); +} export default new ContainerModule((bind, unbind, isBound, rebind) => { // Commands and toolbar items diff --git a/arduino-ide-extension/src/browser/utils/window.ts b/arduino-ide-extension/src/browser/utils/window.ts index 54e046724..89a2f901a 100644 --- a/arduino-ide-extension/src/browser/utils/window.ts +++ b/arduino-ide-extension/src/browser/utils/window.ts @@ -5,3 +5,11 @@ export function setURL(url: URL, data: any = {}): void { history.pushState(data, '', url); } + +/** + * If available from the `window` object, then it means, the IDE2 has successfully patched the `MonacoThemingService#init` static method, + * and can wait the custom theme registration. + */ +export const MonacoThemeServiceIsReady = Symbol( + '@arduino-ide#monaco-theme-service-is-ready' +); diff --git a/electron-app/patch/frontend/index.js b/electron-app/patch/frontend/index.js new file mode 100644 index 000000000..26afbfedd --- /dev/null +++ b/electron-app/patch/frontend/index.js @@ -0,0 +1,100 @@ +// Patch for the startup theme. Customizes the `ThemeService.get().defaultTheme();` to dispatch the default IDE2 theme based on the OS' theme. +// For all subsequent starts of the IDE the theme applied will be the last one set by the user. + +// 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. +// Once the version of Theia is updated, this patch will be removed and this functionality will be implemented via dependency injection. +// Ideally, we should open a PR in Theia and add support for `light` and `dark` default themes in the app config. + +const { + ThemeService, + ThemeServiceSymbol, + BuiltinThemeProvider, +} = require('@theia/core/lib/browser/theming'); +const { + ApplicationProps, +} = require('@theia/application-package/lib/application-props'); +const { + FrontendApplicationConfigProvider, +} = require('@theia/core/lib/browser/frontend-application-config-provider'); + +// It is a mighty hack to support theme updates in the bundled IDE2. +// If the custom theme registration happens before the restoration of the existing monaco themes, then any custom theme changes will be ignored. +// This patch introduces a static deferred promise in the monaco-theming service that will be resolved when the restoration is ready. +// IDE2 cannot require the monaco theme service on the outer module level, as it requires the application config provider to be initialized, +// but the initialization happens only in the generated `index.js`. +// This patch customizes the monaco theme service behavior before loading the DI containers via the preload. +// 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. +const preloader = require('@theia/core/lib/browser/preloader'); +const originalPreload = preloader.preload; +preloader.preload = async function () { + const { MonacoThemingService } = require('@theia/monaco/lib/browser/monaco-theming-service'); + const { MonacoThemeServiceIsReady } = require('arduino-ide-extension/lib/browser/utils/window'); + const { Deferred } = require('@theia/core/lib/common/promise-util'); + const ready = new Deferred(); + if (!window[MonacoThemeServiceIsReady]) { + window[MonacoThemeServiceIsReady] = ready; + console.log('Registered a custom monaco-theme service initialization signal on the window object.'); + } + // Here, it is safe to patch the theme service, app config provider is ready. + MonacoThemingService.init = async function () { + this.updateBodyUiTheme(); + ThemeService.get().onDidColorThemeChange(() => this.updateBodyUiTheme()); + await this.restore(); + ready.resolve(); + }.bind(MonacoThemingService); + return originalPreload(); +}.bind(preloader); + +const lightTheme = 'arduino-theme'; +const darkTheme = 'arduino-theme-dark'; +const defaultTheme = + window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches + ? darkTheme + : lightTheme; + +const originalGet = FrontendApplicationConfigProvider.get; +FrontendApplicationConfigProvider.get = function () { + const originalProps = originalGet.bind(FrontendApplicationConfigProvider)(); + return { ...originalProps, defaultTheme }; +}.bind(FrontendApplicationConfigProvider); + +const arduinoDarkTheme = { + id: 'arduino-theme-dark', + type: 'dark', + label: 'Dark (Arduino)', + editorTheme: 'arduino-theme-dark', + activate() {}, + deactivate() {}, +}; + +const arduinoLightTheme = { + id: 'arduino-theme', + type: 'light', + label: 'Light (Arduino)', + editorTheme: 'arduino-theme', + activate() {}, + deactivate() {}, +}; + +if (!window[ThemeServiceSymbol]) { + const themeService = new ThemeService(); + Object.defineProperty(themeService, 'defaultTheme', { + get: function () { + return ( + this.themes[defaultTheme] || + this.themes[ApplicationProps.DEFAULT.frontend.config.defaultTheme] + ); + }, + }); + themeService.register( + ...BuiltinThemeProvider.themes, + arduinoDarkTheme, + arduinoLightTheme + ); + themeService.startupTheme(); + themeService.setCurrentTheme(defaultTheme); + window[ThemeServiceSymbol] = themeService; +} + +// Require the original, generated `index.js` for `webpack` as the next entry for the `bundle.js`. +require('../../src-gen/frontend/index'); diff --git a/electron-app/webpack.config.js b/electron-app/webpack.config.js index 38baa16f8..817202e5b 100644 --- a/electron-app/webpack.config.js +++ b/electron-app/webpack.config.js @@ -17,4 +17,10 @@ config.module.rules.push({ loader: require.resolve('@theia/application-manager/lib/expose-loader') }); */ + +// Load the patched `index.js` that sets the desired theme in IDE2 based on the OS' theme. +// The `patch/frontend/index.js` will require the original, generated `index.js`. +// See: https://github.com/arduino/arduino-ide/pull/1160. +config.entry.bundle = require('path').resolve(__dirname, 'patch/frontend/index.js'); + module.exports = config; \ No newline at end of file diff --git a/electron/build/patch/frontend/index.js b/electron/build/patch/frontend/index.js index 8908654be..26afbfedd 100644 --- a/electron/build/patch/frontend/index.js +++ b/electron/build/patch/frontend/index.js @@ -17,6 +17,34 @@ const { FrontendApplicationConfigProvider, } = require('@theia/core/lib/browser/frontend-application-config-provider'); +// It is a mighty hack to support theme updates in the bundled IDE2. +// If the custom theme registration happens before the restoration of the existing monaco themes, then any custom theme changes will be ignored. +// This patch introduces a static deferred promise in the monaco-theming service that will be resolved when the restoration is ready. +// IDE2 cannot require the monaco theme service on the outer module level, as it requires the application config provider to be initialized, +// but the initialization happens only in the generated `index.js`. +// This patch customizes the monaco theme service behavior before loading the DI containers via the preload. +// 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. +const preloader = require('@theia/core/lib/browser/preloader'); +const originalPreload = preloader.preload; +preloader.preload = async function () { + const { MonacoThemingService } = require('@theia/monaco/lib/browser/monaco-theming-service'); + const { MonacoThemeServiceIsReady } = require('arduino-ide-extension/lib/browser/utils/window'); + const { Deferred } = require('@theia/core/lib/common/promise-util'); + const ready = new Deferred(); + if (!window[MonacoThemeServiceIsReady]) { + window[MonacoThemeServiceIsReady] = ready; + console.log('Registered a custom monaco-theme service initialization signal on the window object.'); + } + // Here, it is safe to patch the theme service, app config provider is ready. + MonacoThemingService.init = async function () { + this.updateBodyUiTheme(); + ThemeService.get().onDidColorThemeChange(() => this.updateBodyUiTheme()); + await this.restore(); + ready.resolve(); + }.bind(MonacoThemingService); + return originalPreload(); +}.bind(preloader); + const lightTheme = 'arduino-theme'; const darkTheme = 'arduino-theme-dark'; const defaultTheme =