diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index 201cfc7378f135132ec9538b8ec1fb8344da8323..f5a5adb12bffbf5449a5bd10ee16e81294717076 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -61,7 +61,15 @@ { "fileMatch": "/.vscode/extensions.json", "url": "vscode://schemas/extensions" + }, + { + "fileMatch": "%APP_SETTINGS_HOME%/extensions.json", + "url": "vscode://schemas/extensionsstorage" + }, + { + "fileMatch": "%APP_SETTINGS_HOME%/**/extensions.json", + "url": "vscode://schemas/extensionsstorage" } ] } -} +} \ No newline at end of file diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts index 14c83b3584c001b22c26fad27c257ae6f21ff115..7ce8cdcc7204f35328fa0f2ecc7f92e9013fca61 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts @@ -20,7 +20,7 @@ import { IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/co import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { VIEWLET_ID, IExtensionsWorkbenchService } from './extensions'; import { ExtensionsWorkbenchService } from './extensionsWorkbenchService'; -import { OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowWorkspaceRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowInstalledExtensionsAction, UpdateAllAction, OpenExtensionsFolderAction, ConfigureWorkspaceRecommendedExtensionsAction, InstallVSIXAction } from './extensionsActions'; +import { OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowWorkspaceRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowInstalledExtensionsAction, UpdateAllAction, OpenExtensionsFolderAction, ConfigureWorkspaceRecommendedExtensionsAction, OpenWorkspaceExtensionsStorageFile, OpenGlobalExtensionsStorageFile, InstallVSIXAction } from './extensionsActions'; import { ExtensionsInput } from './extensionsInput'; import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { ExtensionEditor } from './extensionEditor'; @@ -28,7 +28,7 @@ import { StatusUpdater } from './extensionsViewlet'; import { IQuickOpenRegistry, Extensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import jsonContributionRegistry = require('vs/platform/jsonschemas/common/jsonContributionRegistry'); -import { Schema, SchemaId } from 'vs/workbench/parts/extensions/electron-browser/extensionsFileTemplate'; +import { ExtensionsConfigurationSchema, ExtensionsConfigurationSchemaId, ExtensionsStorageSchema, ExtensionsStorageSchemaId } from 'vs/workbench/parts/extensions/electron-browser/extensionsFileTemplate'; // Singletons registerSingleton(IExtensionGalleryService, ExtensionGalleryService); @@ -119,6 +119,12 @@ actionRegistry.registerWorkbenchAction(openExtensionsFolderActionDescriptor, 'Ex const openExtensionsFileActionDescriptor = new SyncActionDescriptor(ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceRecommendedExtensionsAction.ID, ConfigureWorkspaceRecommendedExtensionsAction.LABEL); actionRegistry.registerWorkbenchAction(openExtensionsFileActionDescriptor, 'Extensions: Open Extensions File', ExtensionsLabel); +const disableExtensionsActionDescriptor = new SyncActionDescriptor(OpenGlobalExtensionsStorageFile, OpenGlobalExtensionsStorageFile.ID, localize('disableGlobalExtensions', "Disable Extensions")); +actionRegistry.registerWorkbenchAction(disableExtensionsActionDescriptor, 'Extensions: Disable Extensions', ExtensionsLabel); + +const disableWorkspaceExtensionsActionDescriptor = new SyncActionDescriptor(OpenWorkspaceExtensionsStorageFile, OpenWorkspaceExtensionsStorageFile.ID, localize('disableWorkspaceExtensions', "Disable Extensions (Workspace)")); +actionRegistry.registerWorkbenchAction(disableWorkspaceExtensionsActionDescriptor, 'Extensions: Disable Extensions (Workspace)', ExtensionsLabel); + const installVSIXActionDescriptor = new SyncActionDescriptor(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL); actionRegistry.registerWorkbenchAction(installVSIXActionDescriptor, 'Extensions: Install from VSIX...', ExtensionsLabel); @@ -138,4 +144,5 @@ Registry.as(ConfigurationExtensions.Configuration) }); const jsonRegistry = Registry.as(jsonContributionRegistry.Extensions.JSONContribution); -jsonRegistry.registerSchema(SchemaId, Schema); \ No newline at end of file +jsonRegistry.registerSchema(ExtensionsConfigurationSchemaId, ExtensionsConfigurationSchema); +jsonRegistry.registerSchema(ExtensionsStorageSchemaId, ExtensionsStorageSchema); \ No newline at end of file diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index f33ea41d0ca7b4f7199d05dd84aab5c254df90f9..fa57cb0fadb66df315921fdbccbd8e5e9906ad36 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -14,6 +14,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ReloadWindowAction } from 'vs/workbench/electron-browser/actions'; import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewlet, ConfigurationKey } from './extensions'; import { LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionsRuntimeService } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IMessageService, LaterAction } from 'vs/platform/message/common/message'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -22,10 +23,11 @@ import { IViewletService } from 'vs/workbench/services/viewlet/common/viewletSer import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Query } from '../common/extensionQuery'; import { shell, remote } from 'electron'; -import { InitialContent } from 'vs/workbench/parts/extensions/electron-browser/extensionsFileTemplate'; +import { ExtensionsConfigurationInitialContent, ExtensionStorageInitialContent } from 'vs/workbench/parts/extensions/electron-browser/extensionsFileTemplate'; import { IFileService } from 'vs/platform/files/common/files'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import URI from 'vs/base/common/uri'; +import { StorageScope } from 'vs/platform/storage/common/storage'; const dialog = remote.dialog; @@ -629,13 +631,87 @@ export class ConfigureWorkspaceRecommendedExtensionsAction extends Action { return this.fileService.resolveContent(extensionsFileResource).then(content => { return { created: false, extensionsFileResource }; }, err => { - return this.fileService.updateContent(extensionsFileResource, InitialContent).then(() => { + return this.fileService.updateContent(extensionsFileResource, ExtensionsConfigurationInitialContent).then(() => { return { created: true, extensionsFileResource }; }); }); } } +export abstract class OpenExtensionsStorageFile extends Action { + + constructor( + id: string, + label: string, + enabled: boolean, + private scope: StorageScope, + @IFileService private fileService: IFileService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, + @IExtensionsRuntimeService private extensionsRuntimeService: IExtensionsRuntimeService + ) { + super(id, label, null, enabled); + } + + public run(event: any): TPromise { + return this.openExtensionsStorageFile(); + } + + private openExtensionsStorageFile(): TPromise { + return this.getOrCreateExtensionsFile().then(value => { + return this.editorService.openEditor({ + resource: value.extensionsFileResource, + options: { + forceOpen: true, + pinned: value.created + }, + }); + }, (error) => TPromise.wrapError(new Error(localize('OpenGlobalExtensionsStorageFile.failed', "Unable to create 'extensions.json' file inside the '{0}' folder ({1}).", this.extensionsRuntimeService.getStoragePath(this.scope), error)))); + } + + private getOrCreateExtensionsFile(): TPromise<{ created: boolean, extensionsFileResource: URI }> { + const extensionsFileResource = URI.file(this.extensionsRuntimeService.getStoragePath(this.scope)); + + return this.fileService.resolveContent(extensionsFileResource).then(content => { + return { created: false, extensionsFileResource }; + }, err => { + return this.fileService.updateContent(extensionsFileResource, ExtensionStorageInitialContent).then(() => { + return { created: true, extensionsFileResource }; + }); + }); + } +} + +export class OpenWorkspaceExtensionsStorageFile extends OpenExtensionsStorageFile { + + static ID = 'workbench.extensions.action.openWorkspaceExtensionsStorageFile'; + + constructor( + id: string, + label: string, + @IFileService fileService: IFileService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IWorkbenchEditorService editorService: IWorkbenchEditorService, + @IExtensionsRuntimeService extensionsRuntimeService: IExtensionsRuntimeService + ) { + super(id, label, !!contextService.getWorkspace(), StorageScope.WORKSPACE, fileService, editorService, extensionsRuntimeService); + } +} + +export class OpenGlobalExtensionsStorageFile extends OpenExtensionsStorageFile { + + static ID = 'workbench.extensions.action.openGlobalExtensionsStorageFile'; + + constructor( + id: string, + label: string, + @IFileService fileService: IFileService, + @IWorkbenchEditorService editorService: IWorkbenchEditorService, + @IExtensionsRuntimeService extensionsRuntimeService: IExtensionsRuntimeService + ) { + super(id, label, true, StorageScope.GLOBAL, fileService, editorService, extensionsRuntimeService); + } +} + export class InstallVSIXAction extends Action { static ID = 'workbench.extensions.action.installVSIX'; diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsFileTemplate.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsFileTemplate.ts index 6588bff30cad48c2e25b988baf5ae459a85526b5..8aab06a3a996f4b47e51046893f5a9d10607e533 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsFileTemplate.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsFileTemplate.ts @@ -7,9 +7,9 @@ import { localize } from 'vs/nls'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { EXTENSION_IDENTIFIER_PATTERN } from 'vs/platform/extensionManagement/common/extensionManagement'; -export const SchemaId = 'vscode://schemas/extensions'; -export const Schema: IJSONSchema = { - id: SchemaId, +export const ExtensionsConfigurationSchemaId = 'vscode://schemas/extensions'; +export const ExtensionsConfigurationSchema: IJSONSchema = { + id: ExtensionsConfigurationSchemaId, type: 'object', title: localize('app.extensions.json.title', "Extensions"), properties: { @@ -26,7 +26,7 @@ export const Schema: IJSONSchema = { } }; -export const InitialContent: string = [ +export const ExtensionsConfigurationInitialContent: string = [ '{', '\t// See http://go.microsoft.com/fwlink/?LinkId=827846', '\t// for the documentation about the extensions.json format', @@ -35,4 +35,32 @@ export const InitialContent: string = [ '\t\t', '\t]', '}' +].join('\n'); + +export const ExtensionsStorageSchemaId = 'vscode://schemas/extensionsstorage'; +export const ExtensionsStorageSchema: IJSONSchema = { + id: ExtensionsStorageSchemaId, + type: 'object', + title: localize('app.extensionsstorage.json.title', "Extensions Storage"), + properties: { + disabled: { + type: 'array', + description: localize('app.extensionsstorage.json.disabled', "List of disabled extensions. The identifier of an extension is always '${publisher}.${name}'. For example: 'vscode.csharp'."), + items: { + type: 'string', + defaultSnippets: [{ label: 'Example', body: 'vscode.csharp' }], + pattern: EXTENSION_IDENTIFIER_PATTERN, + errorMessage: localize('app.extension.identifier.errorMessage', "Expected format '${publisher}.${name}'. Example: 'vscode.csharp'.") + }, + }, + } +}; + +export const ExtensionStorageInitialContent: string = [ + '{', + '\t"disabled": [', + '\t\t// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp', + '\t\t', + '\t]', + '}' ].join('\n'); \ No newline at end of file