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

Adding default fileicon support to language contributions #118846

Merged
merged 13 commits into from
Jan 3, 2022
Merged
14 changes: 14 additions & 0 deletions src/vs/editor/common/services/language.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ export interface ILanguageExtensionPoint {
aliases?: string[];
mimetypes?: string[];
configuration?: URI;
/**
* @internal
*/
icon?: ILanguageIcon;
}

export interface ILanguageSelection {
Expand All @@ -31,6 +35,11 @@ export interface ILanguageNameIdPair {
readonly languageId: string;
}

export interface ILanguageIcon {
readonly light: URI;
readonly dark: URI;
}

export interface ILanguageService {
readonly _serviceBrand: undefined;

Expand Down Expand Up @@ -76,6 +85,11 @@ export interface ILanguageService {
*/
getMimeType(languageId: string): string | null;

/**
* Get the default icon for the language.
*/
getIcon(languageId: string): ILanguageIcon | null;

/**
* Get all file extensions for a language.
*/
Expand Down
6 changes: 5 additions & 1 deletion src/vs/editor/common/services/languageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { LanguagesRegistry } from 'vs/editor/common/services/languagesRegistry';
import { ILanguageNameIdPair, ILanguageSelection, ILanguageService } from 'vs/editor/common/services/language';
import { ILanguageNameIdPair, ILanguageSelection, ILanguageService, ILanguageIcon } from 'vs/editor/common/services/language';
import { firstOrDefault } from 'vs/base/common/arrays';
import { ILanguageIdCodec, TokenizationRegistry } from 'vs/editor/common/languages';
import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry';
Expand Down Expand Up @@ -61,6 +61,10 @@ export class LanguageService extends Disposable implements ILanguageService {
return this._registry.getMimeType(languageId);
}

public getIcon(languageId: string): ILanguageIcon | null {
return this._registry.getIcon(languageId);
}

public getExtensions(languageId: string): ReadonlyArray<string> {
return this._registry.getExtensions(languageId);
}
Expand Down
18 changes: 16 additions & 2 deletions src/vs/editor/common/services/languagesRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { clearLanguageAssociations, getMimeTypes, registerLanguageAssociation }
import { URI } from 'vs/base/common/uri';
import { ILanguageIdCodec, LanguageId } from 'vs/editor/common/languages';
import { ModesRegistry, PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry';
import { ILanguageExtensionPoint, ILanguageNameIdPair } from 'vs/editor/common/services/language';
import { ILanguageExtensionPoint, ILanguageNameIdPair, ILanguageIcon } from 'vs/editor/common/services/language';
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { Registry } from 'vs/platform/registry/common/platform';

Expand All @@ -27,6 +27,7 @@ export interface IResolvedLanguage {
extensions: string[];
filenames: string[];
configurationFiles: URI[];
icons: ILanguageIcon[];
}

export class LanguageIdCodec implements ILanguageIdCodec {
Expand Down Expand Up @@ -162,7 +163,8 @@ export class LanguagesRegistry extends Disposable {
aliases: [],
extensions: [],
filenames: [],
configurationFiles: []
configurationFiles: [],
icons: []
};
this._languages[langId] = resolvedLanguage;
}
Expand Down Expand Up @@ -260,6 +262,10 @@ export class LanguagesRegistry extends Disposable {
if (lang.configuration) {
resolvedLanguage.configurationFiles.push(lang.configuration);
}

if (lang.icon) {
resolvedLanguage.icons.push(lang.icon);
}
}

public isRegisteredLanguageId(languageId: string | null | undefined): boolean {
Expand Down Expand Up @@ -316,6 +322,14 @@ export class LanguagesRegistry extends Disposable {
return this._languages[languageId].filenames;
}

public getIcon(languageId: string): ILanguageIcon | null {
if (!hasOwnProperty.call(this._languages, languageId)) {
return null;
}
const language = this._languages[languageId];
return (language.icons[0] || null);
}

public getConfigurationFiles(languageId: string): ReadonlyArray<URI> {
if (!hasOwnProperty.call(this._languages, languageId)) {
return [];
Expand Down
1 change: 0 additions & 1 deletion src/vs/platform/theme/browser/iconsStyleSheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { Emitter, Event } from 'vs/base/common/event';
import { getIconRegistry, IconContribution, IconFontDefinition } from 'vs/platform/theme/common/iconRegistry';
import { IProductIconTheme, IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';


export interface IIconsStyleSheet {
getCSS(): string;
readonly onDidChange: Event<void>;
Expand Down
14 changes: 14 additions & 0 deletions src/vs/platform/theme/common/iconRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ export interface IconContribution {
readonly defaults: IconDefaults;
}

export namespace IconContribution {
export function getDefinition(contribution: IconContribution, registry: IIconRegistry): IconDefinition | undefined {
let definition = contribution.defaults;
while (ThemeIcon.isThemeIcon(definition)) {
const c = iconRegistry.getIcon(definition.id);
if (!c) {
return undefined;
}
definition = c.defaults;
}
return definition;
}
}

export interface IconFontContribution {
readonly id: string;
getDefinition(): IconFontDefinition | undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const allApiProposals = Object.freeze({
fsChunks: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fsChunks.d.ts',
inlayHints: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlayHints.d.ts',
inlineCompletions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts',
languageIcon: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageIcon.d.ts',
languageStatus: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageStatus.d.ts',
notebookCellExecutionState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCellExecutionState.d.ts',
notebookConcatTextDocument: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookConcatTextDocument.d.ts',
Expand Down
39 changes: 35 additions & 4 deletions src/vs/workbench/services/language/common/languageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import { LanguageService } from 'vs/editor/common/services/languageService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { FILES_ASSOCIATIONS_CONFIG, IFilesConfiguration } from 'vs/platform/files/common/files';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionService, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';

export interface IRawLanguageExtensionPoint {
id: string;
Expand All @@ -25,6 +26,7 @@ export interface IRawLanguageExtensionPoint {
aliases: string[];
mimetypes: string[];
configuration: string;
icon: { light: string; dark: string };
}

export const languagesExtPoint: IExtensionPoint<IRawLanguageExtensionPoint[]> = ExtensionsRegistry.registerExtensionPoint<IRawLanguageExtensionPoint[]>({
Expand Down Expand Up @@ -84,6 +86,20 @@ export const languagesExtPoint: IExtensionPoint<IRawLanguageExtensionPoint[]> =
description: localize('vscode.extension.contributes.languages.configuration', 'A relative path to a file containing configuration options for the language.'),
type: 'string',
default: './language-configuration.json'
},
icon: {
type: 'object',
description: localize('vscode.extension.contributes.languages.icon', 'A icon to use as file icon, if no icon theme provides one for the language.'),
properties: {
light: {
description: localize('vscode.extension.contributes.languages.icon.light', 'Icon path when a light theme is used'),
type: 'string'
},
dark: {
description: localize('vscode.extension.contributes.languages.icon.dark', 'Icon path when a dark theme is used'),
type: 'string'
}
}
}
}
}
Expand Down Expand Up @@ -116,7 +132,7 @@ export class WorkbenchLanguageService extends LanguageService {

for (let j = 0, lenJ = extension.value.length; j < lenJ; j++) {
let ext = extension.value[j];
if (isValidLanguageExtensionPoint(ext, extension.collector)) {
if (isValidLanguageExtensionPoint(ext, extension.description, extension.collector)) {
let configuration: URI | undefined = undefined;
if (ext.configuration) {
configuration = joinPath(extension.description.extensionLocation, ext.configuration);
Expand All @@ -129,7 +145,11 @@ export class WorkbenchLanguageService extends LanguageService {
firstLine: ext.firstLine,
aliases: ext.aliases,
mimetypes: ext.mimetypes,
configuration: configuration
configuration: configuration,
icon: ext.icon && {
light: joinPath(extension.description.extensionLocation, ext.icon.light),
dark: joinPath(extension.description.extensionLocation, ext.icon.dark)
}
});
}
}
Expand Down Expand Up @@ -184,7 +204,7 @@ function isUndefinedOrStringArray(value: string[]): boolean {
return value.every(item => typeof item === 'string');
}

function isValidLanguageExtensionPoint(value: IRawLanguageExtensionPoint, collector: ExtensionMessageCollector): boolean {
function isValidLanguageExtensionPoint(value: IRawLanguageExtensionPoint, extension: IExtensionDescription, collector: ExtensionMessageCollector): boolean {
if (!value) {
collector.error(localize('invalid.empty', "Empty value for `contributes.{0}`", languagesExtPoint.name));
return false;
Expand Down Expand Up @@ -217,6 +237,17 @@ function isValidLanguageExtensionPoint(value: IRawLanguageExtensionPoint, collec
collector.error(localize('opt.mimetypes', "property `{0}` can be omitted and must be of type `string[]`", 'mimetypes'));
return false;
}
if (typeof value.icon !== 'undefined') {
const proposal = 'languageIcon';
if (!isProposedApiEnabled(extension, proposal)) {
collector.error(`Extension '${extension.identifier.value}' CANNOT use API proposal: ${proposal}.\nIts package.json#enabledApiProposals-property declares: ${extension.enabledApiProposals?.join(', ') ?? '[]'} but NOT ${proposal}.\n The missing proposal MUST be added and you must start in extension development mode or use the following command line switch: --enable-proposed-api ${extension.identifier.value}`);
return false;
}
if (typeof value.icon !== 'object' || typeof value.icon.light !== 'string' || typeof value.icon.dark !== 'string') {
collector.error(localize('opt.icon', "property `{0}` can be omitted and must be of type `object` with properties `{1}` and `{2}` of type `string`", 'icon', 'light', 'dark'));
return false;
}
}
return true;
}

Expand Down
Loading