Skip to content

Commit

Permalink
[plugin/theming] #4831: load themes from json files
Browse files Browse the repository at this point in the history
Signed-off-by: Anton Kosyakov <anton.kosyakov@typefox.io>
  • Loading branch information
akosyakov committed Oct 31, 2019
1 parent aee7427 commit 20b7382
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 6 deletions.
11 changes: 10 additions & 1 deletion packages/core/src/browser/theming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import { injectable, inject } from 'inversify';
import { CommandRegistry, CommandContribution, CommandHandler, Command } from '../common/command';
import { Emitter, Event } from '../common/event';
import { Disposable } from '../common/disposable';
import { QuickOpenModel, QuickOpenItem, QuickOpenMode } from './quick-open/quick-open-model';
import { QuickOpenService } from './quick-open/quick-open-service';
import { FrontendApplicationConfigProvider } from './frontend-application-config-provider';
Expand Down Expand Up @@ -63,10 +64,18 @@ export class ThemeService {
global[ThemeServiceSymbol] = this;
}

register(...themes: Theme[]): void {
register(...themes: Theme[]): Disposable {
for (const theme of themes) {
this.themes[theme.id] = theme;
}
return Disposable.create(() => {
for (const theme of themes) {
delete this.themes[theme.id];
}
if (this.activeTheme && !this.themes[this.activeTheme.id]) {
this.startupTheme();
}
});
}

getThemes(): Theme[] {
Expand Down
18 changes: 18 additions & 0 deletions packages/plugin-ext/src/common/plugin-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export interface PluginPackageContribution {
keybindings?: PluginPackageKeybinding | PluginPackageKeybinding[];
debuggers?: PluginPackageDebuggersContribution[];
snippets: PluginPackageSnippetsContribution[];
themes?: PluginThemeContribution[];
taskDefinitions?: PluginTaskDefinitionContribution[];
problemMatchers?: PluginProblemMatcherContribution[];
problemPatterns?: PluginProblemPatternContribution[];
Expand Down Expand Up @@ -133,6 +134,14 @@ export interface PluginPackageSnippetsContribution {
path?: string;
}

export interface PluginThemeContribution {
id?: string;
label?: string;
description?: string;
path?: string;
uiTheme?: 'vs' | 'vs-dark' | 'hc-black';
}

export interface PlatformSpecificAdapterContribution {
program?: string;
args?: string[];
Expand Down Expand Up @@ -407,6 +416,7 @@ export interface PluginContribution {
keybindings?: Keybinding[];
debuggers?: DebuggerContribution[];
snippets?: SnippetContribution[];
themes?: ThemeContribution[];
taskDefinitions?: TaskDefinition[];
problemMatchers?: ProblemMatcherContribution[];
problemPatterns?: ProblemPatternContribution[];
Expand All @@ -418,6 +428,14 @@ export interface SnippetContribution {
language?: string
}

export interface ThemeContribution {
id?: string;
label?: string;
description?: string;
uri: string;
uiTheme?: 'vs' | 'vs-dark' | 'hc-black';
}

export interface GrammarsContribution {
format: 'json' | 'plist';
language?: string;
Expand Down
28 changes: 27 additions & 1 deletion packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ import {
SnippetContribution,
PluginPackageCommand,
PluginCommand,
IconUrl
IconUrl,
ThemeContribution
} from '../../../common/plugin-protocol';
import * as fs from 'fs';
import * as path from 'path';
Expand Down Expand Up @@ -271,6 +272,12 @@ export class TheiaPluginScanner implements PluginScanner {
} catch (err) {
console.error(`Could not read '${rawPlugin.name}' contribution 'snippets'.`, rawPlugin.contributes!.snippets, err);
}

try {
contributions.themes = this.readThemes(rawPlugin);
} catch (err) {
console.error(`Could not read '${rawPlugin.name}' contribution 'themes'.`, rawPlugin.contributes.themes, err);
}
return contributions;
}

Expand All @@ -293,6 +300,25 @@ export class TheiaPluginScanner implements PluginScanner {
return PluginPackage.toPluginUrl(pck, relativePath);
}

protected readThemes(pck: PluginPackage): ThemeContribution[] | undefined {
if (!pck.contributes || !pck.contributes.themes) {
return undefined;
}
const result: ThemeContribution[] = [];
for (const contribution of pck.contributes.themes) {
if (contribution.path) {
result.push({
id: contribution.id,
uri: FileUri.create(path.join(pck.packagePath, contribution.path)).toString(),
description: contribution.description,
label: contribution.label,
uiTheme: contribution.uiTheme
});
}
}
return result;
}

protected readSnippets(pck: PluginPackage): SnippetContribution[] | undefined {
if (!pck.contributes || !pck.contributes.snippets) {
return undefined;
Expand Down
104 changes: 103 additions & 1 deletion packages/plugin-ext/src/main/browser/plugin-contribution-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import * as jsoncparser from 'jsonc-parser';
import { injectable, inject } from 'inversify';
import { ITokenTypeMap, IEmbeddedLanguagesMap, StandardTokenType } from 'vscode-textmate';
import { TextmateRegistry, getEncodedLanguageId, MonacoTextmateService, GrammarDefinition } from '@theia/monaco/lib/browser/textmate';
import { MenusContributionPointHandler } from './menus/menus-contribution-handler';
import { PluginViewRegistry } from './view/plugin-view-registry';
import { PluginContribution, IndentationRules, FoldingRules, ScopeMap, DeployedPlugin } from '../../common';
import { PluginContribution, IndentationRules, FoldingRules, ScopeMap, DeployedPlugin, ThemeContribution } from '../../common';
import { PreferenceSchemaProvider } from '@theia/core/lib/browser';
import { PreferenceSchema, PreferenceSchemaProperties } from '@theia/core/lib/browser/preferences';
import { KeybindingsContributionPointHandler } from './keybindings/keybindings-contribution-handler';
Expand All @@ -31,6 +32,10 @@ import { Emitter } from '@theia/core/lib/common/event';
import { TaskDefinitionRegistry, ProblemMatcherRegistry, ProblemPatternRegistry } from '@theia/task/lib/browser';
import { PluginDebugService } from './debug/plugin-debug-service';
import { DebugSchemaUpdater } from '@theia/debug/lib/browser/debug-schema-updater';
import { MonacoThemeRegistry } from '@theia/monaco/lib/browser/textmate/monaco-theme-registry';
import { ThemeService, BuiltinThemeProvider } from '@theia/core/lib/browser/theming';
import URI from '@theia/core/lib/common/uri';
import { FileSystem } from '@theia/filesystem/lib/common/filesystem';

@injectable()
export class PluginContributionHandler {
Expand Down Expand Up @@ -79,6 +84,9 @@ export class PluginContributionHandler {
@inject(DebugSchemaUpdater)
protected readonly debugSchema: DebugSchemaUpdater;

@inject(FileSystem)
protected readonly fileSystem: FileSystem;

protected readonly commandHandlers = new Map<string, CommandHandler['execute'] | undefined>();

protected readonly onDidRegisterCommandHandlerEmitter = new Emitter<string>();
Expand Down Expand Up @@ -230,6 +238,13 @@ export class PluginContributionHandler {
}
}

if (contributions.themes && contributions.themes.length) {
const includes = {};
for (const theme of contributions.themes) {
pushContribution(`themes.${theme.uri}`, () => this.registerTheme(theme, plugin, includes));
}
}

if (contributions.taskDefinitions) {
for (const taskDefinition of contributions.taskDefinitions) {
pushContribution(`taskDefinitions.${taskDefinition.taskType}`,
Expand Down Expand Up @@ -473,4 +488,91 @@ export class PluginContributionHandler {
}
}
}

// tslint:disable-next-line:no-any
protected registerTheme(theme: ThemeContribution, plugin: DeployedPlugin, pendingIncludes: { [uri: string]: Promise<any> }): Disposable {
const toDispose = new DisposableCollection(Disposable.create(() => { /* mark as not disposed */ }));
this.doRegisterTheme(theme, plugin, pendingIncludes, toDispose);
return toDispose;
}

protected async doRegisterTheme(
theme: ThemeContribution,
plugin: DeployedPlugin,
// tslint:disable-next-line:no-any
pendingIncludes: { [uri: string]: Promise<any> },
toDispose: DisposableCollection): Promise<void> {
try {
if (new URI(theme.uri).path.ext !== '.json') {
console.error('Unknown theme file: ' + theme.uri);
return;
}
const includes = {};
const json = await this.loadTheme(theme.uri, includes, pendingIncludes, toDispose);
if (toDispose.disposed) {
return;
}
const uiTheme = theme.uiTheme || 'vs-dark';
const label = theme.label || new URI(theme.uri).path.base;
const id = theme.id || label;
const cssSelector = this.toThemeCssSelector(theme, plugin);
const editorTheme = MonacoThemeRegistry.SINGLETON.register(json, includes, cssSelector, uiTheme).name!;
const type = uiTheme === 'vs' ? 'light' : uiTheme === 'vs-dark' ? 'dark' : 'hc';
const builtInTheme = uiTheme === 'vs' ? BuiltinThemeProvider.lightCss : BuiltinThemeProvider.darkCss;
toDispose.push(ThemeService.get().register({
type,
id,
label,
description: theme.description,
editorTheme,
activate(): void {
builtInTheme.use();
},
deactivate(): void {
builtInTheme.unuse();
}
}));
} catch (e) {
console.error('Failed to load theme from ' + theme.uri, e);
}
}

protected toThemeCssSelector(theme: ThemeContribution, plugin: DeployedPlugin): string {
const themeUri = new URI(theme.uri);
let str = `${plugin.metadata.model.id}-${themeUri.withPath(plugin.metadata.model.packagePath).relative(themeUri)}`;

// remove all characters that are not allowed in css
str = str.replace(/[^\-a-zA-Z0-9]/g, '-');
if (str.charAt(0).match(/[0-9\-]/)) {
str = '-' + str;
}
return str;
}

// tslint:disable:no-any
protected async loadTheme(
uri: string,
includes: { [include: string]: any },
pendingIncludes: { [uri: string]: Promise<any> },
toDispose: DisposableCollection
): Promise<any> {
// tslint:enabled:no-any
const { content } = await this.fileSystem.resolveContent(uri);
if (toDispose.disposed) {
return undefined;
}
const json = jsoncparser.parse(content, undefined, { disallowComments: false });
if (json.include) {
const includeUri = new URI(uri).parent.resolve(json.include).toString();
if (!pendingIncludes[includeUri]) {
pendingIncludes[includeUri] = this.loadTheme(includeUri, includes, pendingIncludes, toDispose);
}
includes[json.include] = await pendingIncludes[includeUri];
if (toDispose.disposed) {
return;
}
}
return json;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export class PluginSharedStyle {
background-position: 2px;
width: ${size}px;
height: ${size}px;
background: no-repeat url("${theme.id === BuiltinThemeProvider.lightTheme.id ? lightIconUrl : darkIconUrl}");
background: no-repeat url("${theme.type === 'light' ? lightIconUrl : darkIconUrl}");
background-size: ${size}px;
`));
return {
Expand Down
3 changes: 1 addition & 2 deletions packages/plugin-ext/src/main/browser/webview/webview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ import { open, OpenerService } from '@theia/core/lib/browser/opener-service';
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
Expand Down Expand Up @@ -246,7 +245,7 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget {
const iconClass = `${this.identifier.id}-file-icon`;
this.toDisposeOnIcon.push(this.sharedStyle.insertRule(
`.theia-webview-icon.${iconClass}::before`,
theme => `background-image: url(${theme.id === BuiltinThemeProvider.lightTheme.id ? lightIconUrl : darkIconUrl});`
theme => `background-image: url(${theme.type === 'light' ? lightIconUrl : darkIconUrl});`
));
this.title.iconClass = `theia-webview-icon ${iconClass}`;
} else {
Expand Down

0 comments on commit 20b7382

Please sign in to comment.