Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vscode: support icons contribution point #12912

Merged
merged 13 commits into from
Oct 26, 2023
96 changes: 96 additions & 0 deletions packages/core/src/browser/icon-registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// *****************************************************************************
// Copyright (C) 2023 Ericsson 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-only 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.
*--------------------------------------------------------------------------------------------*/
// code copied and modified from https://github.com/Microsoft/vscode/blob/main/src/vs/platform/theme/common/iconRegistry.ts

import { URI } from 'vscode-uri';

export interface IconDefinition {
font?: IconFontContribution;
fontCharacter: string;
}

export interface IconContribution {
readonly id: string;
description: string | undefined;
deprecationMessage?: string;
readonly defaults: ThemeIcon | IconDefinition;
}

export interface IconFontContribution {
readonly id: string;
readonly definition: IconFontDefinition;
}

export interface IconFontDefinition {
readonly weight?: string;
readonly style?: string;
readonly src: IconFontSource[];
}

export interface IconFontSource {
readonly location: URI;
readonly format: string;
}

export interface ThemeIcon {
readonly id: string;
readonly color?: ThemeColor;
}

export interface ThemeColor {
id: string;
}

export const IconRegistry = Symbol('IconRegistry');
export interface IconRegistry {
/**
* Register a icon to the registry.
* @param id The icon id
* @param defaults The default values
* @param description The description
*/
registerIcon(id: string, defaults: ThemeIcon | IconDefinition, description?: string): ThemeIcon;

/**
* Deregister a icon from the registry.
* @param id The icon id
*/
deregisterIcon(id: string): void;

/**
* Register a icon font to the registry.
* @param id The icon font id
* @param definition The icon font definition
*/
registerIconFont(id: string, definition: IconFontDefinition): IconFontDefinition;

/**
* Deregister an icon font from the registry.
* @param id The icon font id
*/
deregisterIconFont(id: string): void;

/**
* Get the icon font for the given id
* @param id The icon font id
*/
getIconFont(id: string): IconFontDefinition | undefined;
}

5 changes: 5 additions & 0 deletions packages/monaco/src/browser/monaco-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ import { MimeService } from '@theia/core/lib/browser/mime-service';
import { MonacoEditorServices } from './monaco-editor';
import { MonacoColorRegistry } from './monaco-color-registry';
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
import { MonacoIconRegistry } from './monaco-icon-registry';
import { IconRegistry } from '@theia/core/lib/browser/icon-registry';
import { MonacoThemingService } from './monaco-theming-service';
import { bindContributionProvider } from '@theia/core';
import { WorkspaceSymbolCommand } from './workspace-symbol-command';
Expand Down Expand Up @@ -184,6 +186,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {

bind(ThemeServiceWithDB).toSelf().inSingletonScope();
rebind(ThemeService).toService(ThemeServiceWithDB);

bind(MonacoIconRegistry).toSelf().inSingletonScope();
bind(IconRegistry).toService(MonacoIconRegistry);
});

export const MonacoConfigurationService = Symbol('MonacoConfigurationService');
Expand Down
47 changes: 47 additions & 0 deletions packages/monaco/src/browser/monaco-icon-registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// *****************************************************************************
// Copyright (C) 2023 Ericsson 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-only WITH Classpath-exception-2.0
// *****************************************************************************

import { injectable } from '@theia/core/shared/inversify';
import { ThemeIcon } from '@theia/monaco-editor-core/esm/vs/platform/theme/common/themeService';
import { IconRegistry } from '@theia/core/lib/browser/icon-registry';
import { IconDefinition, IconFontDefinition, getIconRegistry } from '@theia/monaco-editor-core/esm/vs/platform/theme/common/iconRegistry';

@injectable()
export class MonacoIconRegistry implements IconRegistry {
vladarama marked this conversation as resolved.
Show resolved Hide resolved

protected readonly iconRegistry = getIconRegistry();

registerIcon(id: string, defaults: ThemeIcon | IconDefinition, description?: string): ThemeIcon {
return this.iconRegistry.registerIcon(id, defaults, description);
}

deregisterIcon(id: string): void {
return this.iconRegistry.deregisterIcon(id);
}

registerIconFont(id: string, definition: IconFontDefinition): IconFontDefinition {
return this.iconRegistry.registerIconFont(id, definition);
}

deregisterIconFont(id: string): void {
return this.iconRegistry.deregisterIconFont(id);
}

getIconFont(id: string): IconFontDefinition | undefined {
return this.iconRegistry.getIconFont(id);
}
}

31 changes: 30 additions & 1 deletion packages/plugin-ext/src/common/plugin-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { RpcServer } from '@theia/core/lib/common/messaging/proxy-factory';
import { RPCProtocol } from './rpc-protocol';
import { Disposable } from '@theia/core/lib/common/disposable';
import { LogPart, KeysToAnyValues, KeysToKeysToAnyValue } from './types';
import { CharacterPair, CommentRule, PluginAPIFactory, Plugin } from './plugin-api-rpc';
import { CharacterPair, CommentRule, PluginAPIFactory, Plugin, ThemeIcon } from './plugin-api-rpc';
import { ExtPluginApi } from './plugin-ext-api-contribution';
import { IJSONSchema, IJSONSchemaSnippet } from '@theia/core/lib/common/json-schema';
import { RecursivePartial } from '@theia/core/lib/common/types';
Expand Down Expand Up @@ -90,6 +90,7 @@ export interface PluginPackageContribution {
snippets?: PluginPackageSnippetsContribution[];
themes?: PluginThemeContribution[];
iconThemes?: PluginIconThemeContribution[];
icons?: PluginIconContribution[];
colors?: PluginColorContribution[];
taskDefinitions?: PluginTaskDefinitionContribution[];
problemMatchers?: PluginProblemMatcherContribution[];
Expand Down Expand Up @@ -262,6 +263,13 @@ export interface PluginIconThemeContribution {
uiTheme?: PluginUiTheme;
}

export interface PluginIconContribution {
[id: string]: {
description: string;
default: { fontPath: string; fontCharacter: string } | string;
};
}

export interface PlatformSpecificAdapterContribution {
program?: string;
args?: string[];
Expand Down Expand Up @@ -586,6 +594,7 @@ export interface PluginContribution {
snippets?: SnippetContribution[];
themes?: ThemeContribution[];
iconThemes?: IconThemeContribution[];
icons?: IconContribution[];
colors?: ColorDefinition[];
taskDefinitions?: TaskDefinition[];
problemMatchers?: ProblemMatcherContribution[];
Expand Down Expand Up @@ -662,6 +671,26 @@ export interface IconThemeContribution {
uiTheme?: UiTheme;
}

export interface IconDefinition {
fontCharacter: string;
location: string;
}

export type IconDefaults = ThemeIcon | IconDefinition;

export interface IconContribution {
id: string;
extensionId: string;
description: string | undefined;
defaults: IconDefaults;
}

export namespace IconContribution {
export function isIconDefinition(defaults: IconDefaults): defaults is IconDefinition {
return 'fontCharacter' in defaults;
}
}

export interface GrammarsContribution {
format: 'json' | 'plist';
language?: string;
Expand Down
70 changes: 69 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 @@ -23,6 +23,7 @@ import {
buildFrontendModuleName,
DebuggerContribution,
IconThemeContribution,
IconContribution,
IconUrl,
Keybinding,
LanguageConfiguration,
Expand Down Expand Up @@ -60,7 +61,8 @@ import {
PluginPackageTranslation,
Translation,
PluginIdentifiers,
TerminalProfile
TerminalProfile,
PluginIconContribution
} from '../../../common/plugin-protocol';
import { promises as fs } from 'fs';
import * as path from 'path';
Expand All @@ -74,6 +76,7 @@ import { deepClone } from '@theia/core/lib/common/objects';
import { PreferenceSchema, PreferenceSchemaProperties } from '@theia/core/lib/common/preferences/preference-schema';
import { TaskDefinition } from '@theia/task/lib/common/task-protocol';
import { ColorDefinition } from '@theia/core/lib/common/color';
import { CSSIcon } from '@theia/core/lib/common/markdown-rendering/icon-utilities';
import { PluginUriFactory } from './plugin-uri-factory';

namespace nls {
Expand All @@ -89,6 +92,12 @@ const INTERNAL_CONSOLE_OPTIONS_SCHEMA = {
};

const colorIdPattern = '^\\w+[.\\w+]*$';
const iconIdPattern = `^${CSSIcon.iconNameSegment}(-${CSSIcon.iconNameSegment})+$`;

function getFileExtension(filePath: string): string {
const index = filePath.lastIndexOf('.');
return index === -1 ? '' : filePath.substring(index + 1);
}

@injectable()
export class TheiaPluginScanner implements PluginScanner {
Expand Down Expand Up @@ -331,6 +340,12 @@ export class TheiaPluginScanner implements PluginScanner {
console.error(`Could not read '${rawPlugin.name}' contribution 'themes'.`, rawPlugin.contributes.themes, err);
}

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

try {
contributions.iconThemes = this.readIconThemes(rawPlugin);
} catch (err) {
Expand Down Expand Up @@ -523,6 +538,59 @@ export class TheiaPluginScanner implements PluginScanner {
return result;
}

protected readIcons(pck: PluginPackage): IconContribution[] | undefined {
if (!pck.contributes || !pck.contributes.icons) {
return undefined;
}
const result: IconContribution[] = [];
const iconEntries = <PluginIconContribution>(<unknown>pck.contributes.icons);
for (const id in iconEntries) {
if (pck.contributes.icons.hasOwnProperty(id)) {
if (!id.match(iconIdPattern)) {
console.error("'configuration.icons' keys represent the icon id and can only contain letter, digits and minuses. " +
'They need to consist of at least two segments in the form `component-iconname`.', 'extension: ', pck.name, 'icon id: ', id);
return;
}
const iconContribution = iconEntries[id];
if (typeof iconContribution.description !== 'string' || iconContribution.description['length'] === 0) {
console.error('configuration.icons.description must be defined and can not be empty, ', 'extension: ', pck.name, 'icon id: ', id);
return;
}

const defaultIcon = iconContribution.default;
if (typeof defaultIcon === 'string') {
result.push({
id,
extensionId: pck.publisher + '.' + pck.name,
description: iconContribution.description,
defaults: { id: defaultIcon }
});
} else if (typeof defaultIcon === 'object' && typeof defaultIcon.fontPath === 'string' && typeof defaultIcon.fontCharacter === 'string') {
const format = getFileExtension(defaultIcon.fontPath);
if (['woff', 'woff2', 'ttf'].indexOf(format) === -1) {
console.warn("Expected `contributes.icons.default.fontPath` to have file extension 'woff', woff2' or 'ttf', is '{0}'.", format);
return;
}

const iconFontLocation = this.pluginUriFactory.createUri(pck, defaultIcon.fontPath).toString();
result.push({
id,
extensionId: pck.publisher + '.' + pck.name,
description: iconContribution.description,
defaults: {
fontCharacter: defaultIcon.fontCharacter,
location: iconFontLocation
}
});
} else {
console.error("'configuration.icons.default' must be either a reference to the id of an other theme icon (string) or a icon definition (object) with ",
'properties `fontPath` and `fontCharacter`.');
}
}
}
return result;
}

protected readSnippets(pck: PluginPackage): SnippetContribution[] | undefined {
if (!pck.contributes || !pck.contributes.snippets) {
return undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { PluginViewRegistry } from './view/plugin-view-registry';
import { PluginCustomEditorRegistry } from './custom-editors/plugin-custom-editor-registry';
import {
PluginContribution, IndentationRules, FoldingRules, ScopeMap, DeployedPlugin,
GrammarsContribution, EnterAction, OnEnterRule, RegExpOptions, PluginPackage
GrammarsContribution, EnterAction, OnEnterRule, RegExpOptions, IconContribution, PluginPackage
} from '../../common';
import {
DefaultUriLabelProviderContribution,
Expand All @@ -43,6 +43,7 @@ import { PluginDebugService } from './debug/plugin-debug-service';
import { DebugSchemaUpdater } from '@theia/debug/lib/browser/debug-schema-updater';
import { MonacoThemingService } from '@theia/monaco/lib/browser/monaco-theming-service';
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
import { PluginIconService } from './plugin-icon-service';
import { PluginIconThemeService } from './plugin-icon-theme-service';
import { ContributionProvider } from '@theia/core/lib/common';
import * as monaco from '@theia/monaco-editor-core';
Expand Down Expand Up @@ -112,6 +113,9 @@ export class PluginContributionHandler {
@inject(ColorRegistry)
protected readonly colors: ColorRegistry;

@inject(PluginIconService)
protected readonly iconService: PluginIconService;

@inject(PluginIconThemeService)
protected readonly iconThemeService: PluginIconThemeService;

Expand Down Expand Up @@ -332,6 +336,19 @@ export class PluginContributionHandler {
}
}

if (contributions.icons && contributions.icons.length) {
for (const icon of contributions.icons) {
const defaultIcon = icon.defaults;
let key: string;
if (IconContribution.isIconDefinition(defaultIcon)) {
key = defaultIcon.location;
} else {
key = defaultIcon.id;
}
pushContribution(`icons.${key}`, () => this.iconService.register(icon, plugin));
}
}

const colors = contributions.colors;
if (colors) {
pushContribution('colors', () => this.colors.register(...colors));
Expand Down
Loading
Loading