/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; import * as mime from 'vs/base/common/mime'; import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { ILanguageExtensionPoint, IModeService } from 'vs/editor/common/services/modeService'; import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; 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 { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export interface IRawLanguageExtensionPoint { id: string; extensions: string[]; filenames: string[]; filenamePatterns: string[]; firstLine: string; aliases: string[]; mimetypes: string[]; configuration: string; } export const languagesExtPoint: IExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'languages', jsonSchema: { description: nls.localize('vscode.extension.contributes.languages', 'Contributes language declarations.'), type: 'array', items: { type: 'object', defaultSnippets: [{ body: { id: '${1:languageId}', aliases: ['${2:label}'], extensions: ['${3:extension}'], configuration: './language-configuration.json' } }], properties: { id: { description: nls.localize('vscode.extension.contributes.languages.id', 'ID of the language.'), type: 'string' }, aliases: { description: nls.localize('vscode.extension.contributes.languages.aliases', 'Name aliases for the language.'), type: 'array', items: { type: 'string' } }, extensions: { description: nls.localize('vscode.extension.contributes.languages.extensions', 'File extensions associated to the language.'), default: ['.foo'], type: 'array', items: { type: 'string' } }, filenames: { description: nls.localize('vscode.extension.contributes.languages.filenames', 'File names associated to the language.'), type: 'array', items: { type: 'string' } }, filenamePatterns: { description: nls.localize('vscode.extension.contributes.languages.filenamePatterns', 'File name glob patterns associated to the language.'), type: 'array', items: { type: 'string' } }, mimetypes: { description: nls.localize('vscode.extension.contributes.languages.mimetypes', 'Mime types associated to the language.'), type: 'array', items: { type: 'string' } }, firstLine: { description: nls.localize('vscode.extension.contributes.languages.firstLine', 'A regular expression matching the first line of a file of the language.'), type: 'string' }, configuration: { description: nls.localize('vscode.extension.contributes.languages.configuration', 'A relative path to a file containing configuration options for the language.'), type: 'string', default: './language-configuration.json' } } } } }); export class WorkbenchModeServiceImpl extends ModeServiceImpl { private _configurationService: IConfigurationService; private _extensionService: IExtensionService; private _onReadyPromise: Promise | undefined; constructor( @IExtensionService extensionService: IExtensionService, @IConfigurationService configurationService: IConfigurationService, @IEnvironmentService environmentService: IEnvironmentService ) { super(environmentService.verbose || environmentService.isExtensionDevelopment || !environmentService.isBuilt); this._configurationService = configurationService; this._extensionService = extensionService; languagesExtPoint.setHandler((extensions: IExtensionPointUser[]) => { let allValidLanguages: ILanguageExtensionPoint[] = []; for (let i = 0, len = extensions.length; i < len; i++) { let extension = extensions[i]; if (!Array.isArray(extension.value)) { extension.collector.error(nls.localize('invalid', "Invalid `contributes.{0}`. Expected an array.", languagesExtPoint.name)); continue; } for (let j = 0, lenJ = extension.value.length; j < lenJ; j++) { let ext = extension.value[j]; if (isValidLanguageExtensionPoint(ext, extension.collector)) { let configuration: URI | undefined = undefined; if (ext.configuration) { configuration = resources.joinPath(extension.description.extensionLocation, ext.configuration); } allValidLanguages.push({ id: ext.id, extensions: ext.extensions, filenames: ext.filenames, filenamePatterns: ext.filenamePatterns, firstLine: ext.firstLine, aliases: ext.aliases, mimetypes: ext.mimetypes, configuration: configuration }); } } } ModesRegistry.setDynamicLanguages(allValidLanguages); }); this.updateMime(); this._configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(FILES_ASSOCIATIONS_CONFIG)) { this.updateMime(); } }); this._extensionService.whenInstalledExtensionsRegistered().then(() => { this.updateMime(); }); this.onDidCreateMode((mode) => { this._extensionService.activateByEvent(`onLanguage:${mode.getId()}`); }); } protected _onReady(): Promise { if (!this._onReadyPromise) { this._onReadyPromise = Promise.resolve( this._extensionService.whenInstalledExtensionsRegistered().then(() => true) ); } return this._onReadyPromise; } private updateMime(): void { const configuration = this._configurationService.getValue(); // Clear user configured mime associations mime.clearTextMimes(true /* user configured */); // Register based on settings if (configuration.files && configuration.files.associations) { Object.keys(configuration.files.associations).forEach(pattern => { const langId = configuration.files.associations[pattern]; const mimetype = this.getMimeForMode(langId) || `text/x-${langId}`; mime.registerTextMime({ id: langId, mime: mimetype, filepattern: pattern, userConfigured: true }); }); } this._onLanguagesMaybeChanged.fire(); } } function isUndefinedOrStringArray(value: string[]): boolean { if (typeof value === 'undefined') { return true; } if (!Array.isArray(value)) { return false; } return value.every(item => typeof item === 'string'); } function isValidLanguageExtensionPoint(value: IRawLanguageExtensionPoint, collector: ExtensionMessageCollector): boolean { if (!value) { collector.error(nls.localize('invalid.empty', "Empty value for `contributes.{0}`", languagesExtPoint.name)); return false; } if (typeof value.id !== 'string') { collector.error(nls.localize('require.id', "property `{0}` is mandatory and must be of type `string`", 'id')); return false; } if (!isUndefinedOrStringArray(value.extensions)) { collector.error(nls.localize('opt.extensions', "property `{0}` can be omitted and must be of type `string[]`", 'extensions')); return false; } if (!isUndefinedOrStringArray(value.filenames)) { collector.error(nls.localize('opt.filenames', "property `{0}` can be omitted and must be of type `string[]`", 'filenames')); return false; } if (typeof value.firstLine !== 'undefined' && typeof value.firstLine !== 'string') { collector.error(nls.localize('opt.firstLine', "property `{0}` can be omitted and must be of type `string`", 'firstLine')); return false; } if (typeof value.configuration !== 'undefined' && typeof value.configuration !== 'string') { collector.error(nls.localize('opt.configuration', "property `{0}` can be omitted and must be of type `string`", 'configuration')); return false; } if (!isUndefinedOrStringArray(value.aliases)) { collector.error(nls.localize('opt.aliases', "property `{0}` can be omitted and must be of type `string[]`", 'aliases')); return false; } if (!isUndefinedOrStringArray(value.mimetypes)) { collector.error(nls.localize('opt.mimetypes', "property `{0}` can be omitted and must be of type `string[]`", 'mimetypes')); return false; } return true; } registerSingleton(IModeService, WorkbenchModeServiceImpl);