diff --git a/packages/core/src/browser/color-registry.ts b/packages/core/src/browser/color-registry.ts new file mode 100644 index 0000000000000..128c528470de0 --- /dev/null +++ b/packages/core/src/browser/color-registry.ts @@ -0,0 +1,31 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { injectable } from 'inversify'; + +/** + * It should be implemented by an extension, e.g. by the monaco extension. + */ +@injectable() +export class ColorRegistry { + + *getColors(): IterableIterator { } + + getCurrentColor(id: string): string | undefined { + return undefined; + } + +} diff --git a/packages/core/src/browser/frontend-application-module.ts b/packages/core/src/browser/frontend-application-module.ts index 20c91dbc4747d..e87520db6a401 100644 --- a/packages/core/src/browser/frontend-application-module.ts +++ b/packages/core/src/browser/frontend-application-module.ts @@ -83,6 +83,7 @@ import { ProgressStatusBarItem } from './progress-status-bar-item'; import { TabBarDecoratorService, TabBarDecorator } from './shell/tab-bar-decorator'; import { ContextMenuContext } from './menu/context-menu-context'; import { bindResourceProvider, bindMessageService, bindPreferenceService } from './frontend-application-bindings'; +import { ColorRegistry } from './color-registry'; export { bindResourceProvider, bindMessageService, bindPreferenceService }; @@ -90,6 +91,7 @@ export const frontendApplicationModule = new ContainerModule((bind, unbind, isBo const themeService = ThemeService.get(); themeService.register(...BuiltinThemeProvider.themes); themeService.startupTheme(); + bind(ColorRegistry).toSelf().inSingletonScope(); bind(FrontendApplication).toSelf().inSingletonScope(); bind(FrontendApplicationStateService).toSelf().inSingletonScope(); diff --git a/packages/core/src/browser/theming.ts b/packages/core/src/browser/theming.ts index a5c4c51473901..72949d198c16f 100644 --- a/packages/core/src/browser/theming.ts +++ b/packages/core/src/browser/theming.ts @@ -25,8 +25,11 @@ import { CommonMenus } from './common-frontend-contribution'; export const ThemeServiceSymbol = Symbol('ThemeService'); +export type ThemeType = 'light' | 'dark' | 'hc'; + export interface Theme { readonly id: string; + readonly type: ThemeType; readonly label: string; readonly description?: string; readonly editorTheme?: string; @@ -197,8 +200,9 @@ export class BuiltinThemeProvider { static readonly darkCss = require('../../src/browser/style/variables-dark.useable.css'); static readonly lightCss = require('../../src/browser/style/variables-bright.useable.css'); - static readonly darkTheme = { + static readonly darkTheme: Theme = { id: 'dark', + type: 'dark', label: 'Dark Theme', description: 'Bright fonts on dark backgrounds.', editorTheme: 'dark-plus', // loaded in /packages/monaco/src/browser/textmate/monaco-theme-registry.ts @@ -210,8 +214,9 @@ export class BuiltinThemeProvider { } }; - static readonly lightTheme = { + static readonly lightTheme: Theme = { id: 'light', + type: 'light', label: 'Light Theme', description: 'Dark fonts on light backgrounds.', editorTheme: 'light-plus', // loaded in /packages/monaco/src/browser/textmate/monaco-theme-registry.ts diff --git a/packages/monaco/src/browser/monaco-color-registry.ts b/packages/monaco/src/browser/monaco-color-registry.ts new file mode 100644 index 0000000000000..fd4f544d448ba --- /dev/null +++ b/packages/monaco/src/browser/monaco-color-registry.ts @@ -0,0 +1,37 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { injectable } from 'inversify'; +import { ColorRegistry } from '@theia/core/lib/browser/color-registry'; + +@injectable() +export class MonacoColorRegistry implements ColorRegistry { + + protected readonly monacoThemeService = monaco.services.StaticServices.standaloneThemeService.get(); + protected readonly monacoColorRegistry = monaco.color.getColorRegistry(); + + *getColors(): IterableIterator { + for (const { id } of this.monacoColorRegistry.getColors()) { + yield id; + } + } + + getCurrentColor(id: string): string | undefined { + const color = this.monacoThemeService.getTheme().getColor(id); + return color && color.toString(); + } + +} diff --git a/packages/monaco/src/browser/monaco-frontend-module.ts b/packages/monaco/src/browser/monaco-frontend-module.ts index 8d205dace3dee..91f1967f016a4 100644 --- a/packages/monaco/src/browser/monaco-frontend-module.ts +++ b/packages/monaco/src/browser/monaco-frontend-module.ts @@ -18,6 +18,7 @@ import '../../src/browser/style/index.css'; import '../../src/browser/style/symbol-sprite.svg'; import '../../src/browser/style/symbol-icons.css'; +import debounce = require('lodash.debounce'); import { ContainerModule, decorate, injectable, interfaces } from 'inversify'; import { MenuContribution, CommandContribution } from '@theia/core/lib/common'; import { PreferenceScope } from '@theia/core/lib/common/preferences/preference-scope'; @@ -58,9 +59,9 @@ import { ContextKeyService } from '@theia/core/lib/browser/context-key-service'; import { MonacoContextKeyService } from './monaco-context-key-service'; import { MonacoMimeService } from './monaco-mime-service'; import { MimeService } from '@theia/core/lib/browser/mime-service'; - -import debounce = require('lodash.debounce'); import { MonacoEditorServices } from './monaco-editor'; +import { MonacoColorRegistry } from './monaco-color-registry'; +import { ColorRegistry } from '@theia/core/lib/browser/color-registry'; decorate(injectable(), MonacoToProtocolConverter); decorate(injectable(), ProtocolToMonacoConverter); @@ -130,6 +131,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(MonacoMimeService).toSelf().inSingletonScope(); rebind(MimeService).toService(MonacoMimeService); + + bind(MonacoColorRegistry).toSelf().inSingletonScope(); + rebind(ColorRegistry).toService(MonacoColorRegistry); }); export const MonacoConfigurationService = Symbol('MonacoConfigurationService'); diff --git a/packages/monaco/src/browser/monaco-loader.ts b/packages/monaco/src/browser/monaco-loader.ts index 33304501f6aef..d55532abc7da7 100644 --- a/packages/monaco/src/browser/monaco-loader.ts +++ b/packages/monaco/src/browser/monaco-loader.ts @@ -61,6 +61,7 @@ export function loadMonaco(vsRequire: any): Promise { 'vs/base/parts/quickopen/browser/quickOpenModel', 'vs/base/common/filters', 'vs/platform/theme/common/styler', + 'vs/platform/theme/common/colorRegistry', 'vs/base/common/platform', 'vs/editor/common/modes', 'vs/editor/contrib/suggest/suggest', @@ -75,7 +76,8 @@ export function loadMonaco(vsRequire: any): Promise { ], (css: any, html: any, commands: any, actions: any, keybindingsRegistry: any, keybindingResolver: any, resolvedKeybinding: any, keybindingLabels: any, keyCodes: any, mime: any, editorExtensions: any, simpleServices: any, standaloneServices: any, quickOpenWidget: any, quickOpenModel: any, - filters: any, styler: any, platform: any, modes: any, suggest: any, snippetParser: any, + filters: any, styler: any, colorRegistry: any, + platform: any, modes: any, suggest: any, snippetParser: any, configuration: any, configurationModels: any, codeEditorService: any, codeEditorServiceImpl: any, contextKey: any, contextKeyService: any, @@ -88,6 +90,7 @@ export function loadMonaco(vsRequire: any): Promise { global.monaco.quickOpen = Object.assign({}, quickOpenWidget, quickOpenModel); global.monaco.filters = filters; global.monaco.theme = styler; + global.monaco.color = colorRegistry; global.monaco.platform = platform; global.monaco.editorExtensions = editorExtensions; global.monaco.modes = modes; diff --git a/packages/monaco/src/typings/monaco/index.d.ts b/packages/monaco/src/typings/monaco/index.d.ts index fa270f5b612a5..1d49cb348835e 100644 --- a/packages/monaco/src/typings/monaco/index.d.ts +++ b/packages/monaco/src/typings/monaco/index.d.ts @@ -477,6 +477,7 @@ declare module monaco.services { export interface IStandaloneTheme { tokenTheme: TokenTheme; + getColor(color: string): Color | undefined; } export interface TokenTheme { @@ -532,6 +533,16 @@ declare module monaco.theme { export function attachQuickOpenStyler(widget: IThemable, themeService: IThemeService): monaco.IDisposable; } +declare module monaco.color { + export interface ColorContribution { + readonly id: string; + } + export interface IColorRegistry { + getColors(): ColorContribution[]; + } + export function getColorRegistry(): IColorRegistry; +} + declare module monaco.referenceSearch { export interface Location { diff --git a/packages/plugin-ext/src/main/browser/plugin-ext-frontend-module.ts b/packages/plugin-ext/src/main/browser/plugin-ext-frontend-module.ts index 863f1b775aa45..5896b5c9755e8 100644 --- a/packages/plugin-ext/src/main/browser/plugin-ext-frontend-module.ts +++ b/packages/plugin-ext/src/main/browser/plugin-ext-frontend-module.ts @@ -65,6 +65,7 @@ import { OutputChannelRegistryMainImpl } from './output-channel-registry-main'; import { InPluginFileSystemWatcherManager } from './in-plugin-filesystem-watcher-manager'; import { WebviewWidget, WebviewWidgetIdentifier, WebviewWidgetExternalEndpoint } from './webview/webview'; import { WebviewEnvironment } from './webview/webview-environment'; +import { WebviewThemeDataProvider } from './webview/webview-theme-data-provider'; export default new ContainerModule((bind, unbind, isBound, rebind) => { @@ -149,6 +150,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { })).inSingletonScope(); bind(WebviewEnvironment).toSelf().inSingletonScope(); + bind(WebviewThemeDataProvider).toSelf().inSingletonScope(); bind(WebviewWidget).toSelf(); bind(WidgetFactory).toDynamicValue(({ container }) => ({ id: WebviewWidget.FACTORY_ID, diff --git a/packages/plugin-ext/src/main/browser/webview/webview-theme-data-provider.ts b/packages/plugin-ext/src/main/browser/webview/webview-theme-data-provider.ts new file mode 100644 index 0000000000000..b15ce8d569283 --- /dev/null +++ b/packages/plugin-ext/src/main/browser/webview/webview-theme-data-provider.ts @@ -0,0 +1,115 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// copied and modified from https://github.com/microsoft/vscode/blob/ba40bd16433d5a817bfae15f3b4350e18f144af4/src/vs/workbench/contrib/webview/common/themeing.ts + +import { inject, postConstruct, injectable } from 'inversify'; +import { Emitter } from '@theia/core/lib/common/event'; +import { EditorPreferences, EditorConfiguration } from '@theia/editor/lib/browser/editor-preferences'; +import { ThemeService } from '@theia/core/lib/browser/theming'; +import { ColorRegistry } from '@theia/core/lib/browser/color-registry'; + +export type WebviewThemeType = 'vscode-light' | 'vscode-dark' | 'vscode-high-contrast'; +export interface WebviewThemeData { + readonly activeTheme: WebviewThemeType; + readonly styles: { readonly [key: string]: string | number; }; +} + +@injectable() +export class WebviewThemeDataProvider { + + protected readonly onDidChangeThemeDataEmitter = new Emitter(); + readonly onDidChangeThemeData = this.onDidChangeThemeDataEmitter.event; + + @inject(EditorPreferences) + protected readonly editorPreferences: EditorPreferences; + + @inject(ColorRegistry) + protected readonly colorRegistry: ColorRegistry; + + protected themeData: WebviewThemeData | undefined; + + protected readonly editorStyles = new Map([ + ['editor.fontFamily', 'editor-font-family'], + ['editor.fontWeight', 'editor-font-weight'], + ['editor.fontSize', 'editor-font-size'] + ]); + + @postConstruct() + protected init(): void { + ThemeService.get().onThemeChange(() => this.reset()); + + this.editorPreferences.onPreferenceChanged(e => { + if (this.editorStyles.has(e.preferenceName)) { + this.reset(); + } + }); + } + + protected reset(): void { + if (this.themeData) { + this.themeData = undefined; + this.onDidChangeThemeDataEmitter.fire(undefined); + } + } + + getThemeData(): WebviewThemeData { + if (!this.themeData) { + this.themeData = this.computeThemeData(); + } + return this.themeData; + } + + protected computeThemeData(): WebviewThemeData { + const styles: { [key: string]: string | number; } = {}; + // tslint:disable-next-line:no-any + const addStyle = (id: string, rawValue: any) => { + if (rawValue) { + const value = typeof rawValue === 'number' || typeof rawValue === 'string' ? rawValue : String(rawValue); + styles['vscode-' + id.replace('.', '-')] = value; + styles['theia-' + id.replace('.', '-')] = value; + } + }; + + addStyle('font-family', '-apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif'); + addStyle('font-weight', 'normal'); + addStyle('font-size', '13px'); + this.editorStyles.forEach((value, key) => addStyle(value, this.editorPreferences[key])); + + for (const id of this.colorRegistry.getColors()) { + const color = this.colorRegistry.getCurrentColor(id); + if (color) { + addStyle(id, color.toString()); + } + } + + const activeTheme = this.getActiveTheme(); + return { styles, activeTheme }; + } + + protected getActiveTheme(): WebviewThemeType { + const theme = ThemeService.get().getCurrentTheme(); + switch (theme.type) { + case 'light': return 'vscode-light'; + case 'dark': return 'vscode-dark'; + default: return 'vscode-high-contrast'; + } + } + +} diff --git a/packages/plugin-ext/src/main/browser/webview/webview.ts b/packages/plugin-ext/src/main/browser/webview/webview.ts index b42f6a42257d7..670e4b462df15 100644 --- a/packages/plugin-ext/src/main/browser/webview/webview.ts +++ b/packages/plugin-ext/src/main/browser/webview/webview.ts @@ -13,6 +13,12 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the MIT License. See License.txt in the project root for license information. +*--------------------------------------------------------------------------------------------*/ +// copied and modified from https://github.com/microsoft/vscode/blob/ba40bd16433d5a817bfae15f3b4350e18f144af4/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts +// copied and modified from https://github.com/microsoft/vscode/blob/ba40bd16433d5a817bfae15f3b4350e18f144af4/src/vs/workbench/contrib/webview/browser/webviewElement.ts# import * as mime from 'mime'; import { JSONExt } from '@phosphor/coreutils/lib/json'; @@ -36,6 +42,7 @@ import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding'; import { Schemes } from '../../../common/uri-components'; import { PluginSharedStyle } from '../plugin-shared-style'; import { BuiltinThemeProvider } from '@theia/core/lib/browser/theming'; +import { WebviewThemeDataProvider } from './webview-theme-data-provider'; // tslint:disable:no-any @@ -108,6 +115,9 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget { @inject(PluginSharedStyle) protected readonly sharedStyle: PluginSharedStyle; + @inject(WebviewThemeDataProvider) + protected readonly themeDataProvider: WebviewThemeDataProvider; + viewState: WebviewPanelViewState = { visible: false, active: false, @@ -205,6 +215,9 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget { // keybinding service because these events do not bubble to the parent window anymore. this.dispatchKeyDown(data); })); + + this.style(); + this.toDispose.push(this.themeDataProvider.onDidChangeThemeData(() => this.style())); } setContentOptions(contentOptions: WebviewContentOptions): void { @@ -269,6 +282,11 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget { this.doUpdateContent(); } + protected style(): void { + const { styles, activeTheme } = this.themeDataProvider.getThemeData(); + this.doSend('styles', { styles, activeTheme }); + } + protected dispatchKeyDown(event: KeyboardEventInit): void { // Create a fake KeyboardEvent from the data provided const emulatedKeyboardEvent = new KeyboardEvent('keydown', event);