diff options
11 files changed, 117 insertions, 58 deletions
diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index fbb9673b535..e68a950da08 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -86,7 +86,7 @@ export class Main { } else if (argv['list-extensions']) { await this.listExtensions(!!argv['show-versions'], argv['category']); } else if (argv['install-extension']) { - await this.installExtensions(argv['install-extension'], !!argv['force'], !!argv['default']); + await this.installExtensions(argv['install-extension'], !!argv['force'], !!argv['donot-sync']); } else if (argv['uninstall-extension']) { await this.uninstallExtension(argv['uninstall-extension']); } else if (argv['locate-extension']) { @@ -126,7 +126,7 @@ export class Main { extensions.forEach(e => console.log(getId(e.manifest, showVersions))); } - private async installExtensions(extensions: string[], force: boolean, isDefault: boolean): Promise<void> { + private async installExtensions(extensions: string[], force: boolean, donotSync: boolean): Promise<void> { const failed: string[] = []; const installedExtensionsManifests: IExtensionManifest[] = []; if (extensions.length) { @@ -135,7 +135,7 @@ export class Main { for (const extension of extensions) { try { - const manifest = await this.installExtension(extension, force, isDefault); + const manifest = await this.installExtension(extension, force, donotSync); if (manifest) { installedExtensionsManifests.push(manifest); } @@ -150,7 +150,7 @@ export class Main { return failed.length ? Promise.reject(localize('installation failed', "Failed Installing Extensions: {0}", failed.join(', '))) : Promise.resolve(); } - private async installExtension(extension: string, force: boolean, isDefault: boolean): Promise<IExtensionManifest | null> { + private async installExtension(extension: string, force: boolean, donotSync: boolean): Promise<IExtensionManifest | null> { if (/\.vsix$/i.test(extension)) { extension = path.isAbsolute(extension) ? extension : path.join(process.cwd(), extension); @@ -158,7 +158,7 @@ export class Main { const valid = await this.validate(manifest, force); if (valid) { - return this.extensionManagementService.install(URI.file(extension)).then(id => { + return this.extensionManagementService.install(URI.file(extension), donotSync).then(id => { console.log(localize('successVsixInstall', "Extension '{0}' was successfully installed.", getBaseLabel(extension))); return manifest; }, error => { @@ -205,7 +205,7 @@ export class Main { } console.log(localize('updateMessage', "Updating the extension '{0}' to the version {1}", id, extension.version)); } - await this.installFromGallery(id, extension, isDefault); + await this.installFromGallery(id, extension, donotSync); return manifest; })); } @@ -227,11 +227,11 @@ export class Main { return true; } - private async installFromGallery(id: string, extension: IGalleryExtension, isDefault: boolean): Promise<void> { + private async installFromGallery(id: string, extension: IGalleryExtension, donotSync: boolean): Promise<void> { console.log(localize('installing', "Installing extension '{0}' v{1}...", id, extension.version)); try { - await this.extensionManagementService.installFromGallery(extension, isDefault); + await this.extensionManagementService.installFromGallery(extension, donotSync); console.log(localize('successInstall', "Extension '{0}' v{1} was successfully installed.", id, extension.version)); } catch (error) { if (isPromiseCanceledError(error)) { diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 5bd1488b472..c9c362f066e 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -72,7 +72,7 @@ export interface ParsedArgs { remote?: string; 'disable-user-env-probe'?: boolean; 'force'?: boolean; - 'default'?: boolean; + 'donot-sync'?: boolean; 'force-user-env'?: boolean; 'sync'?: 'on' | 'off'; @@ -188,7 +188,7 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = { 'file-chmod': { type: 'boolean' }, 'driver-verbose': { type: 'boolean' }, 'force': { type: 'boolean' }, - 'default': { type: 'boolean' }, + 'donot-sync': { type: 'boolean' }, 'trace': { type: 'boolean' }, 'trace-category-filter': { type: 'string' }, 'trace-options': { type: 'string' }, diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 36735a9b105..d5bbcec2963 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -92,7 +92,7 @@ export interface IGalleryMetadata { export interface ILocalExtension extends IExtension { readonly manifest: IExtensionManifest; - isDefault: boolean | undefined; + isMachineScoped: boolean; publisherId: string | null; publisherDisplayName: string | null; readmeUrl: URI | null; @@ -206,8 +206,8 @@ export interface IExtensionManagementService { zip(extension: ILocalExtension): Promise<URI>; unzip(zipLocation: URI): Promise<IExtensionIdentifier>; getManifest(vsix: URI): Promise<IExtensionManifest>; - install(vsix: URI, isDefault?: boolean): Promise<ILocalExtension>; - installFromGallery(extension: IGalleryExtension, isDefault?: boolean): Promise<ILocalExtension>; + install(vsix: URI, isMachineScoped?: boolean): Promise<ILocalExtension>; + installFromGallery(extension: IGalleryExtension, isMachineScoped?: boolean): Promise<ILocalExtension>; uninstall(extension: ILocalExtension, force?: boolean): Promise<void>; reinstallFromGallery(extension: ILocalExtension): Promise<void>; getInstalled(type?: ExtensionType): Promise<ILocalExtension[]>; diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 5fffb8f48e5..28da448838d 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -152,7 +152,7 @@ export class ExtensionManagementService extends Disposable implements IExtension } - install(vsix: URI, isDefault?: boolean): Promise<ILocalExtension> { + install(vsix: URI, isMachineScoped?: boolean): Promise<ILocalExtension> { this.logService.trace('ExtensionManagementService#install', vsix.toString()); return createCancelablePromise(token => { return this.downloadVsix(vsix).then(downloadLocation => { @@ -170,6 +170,7 @@ export class ExtensionManagementService extends Disposable implements IExtension .then(installedExtensions => { const existing = installedExtensions.filter(i => areSameExtensions(identifier, i.identifier))[0]; if (existing) { + isMachineScoped = isMachineScoped || existing.isMachineScoped; operation = InstallOperation.Update; if (identifierWithVersion.equals(new ExtensionIdentifierWithVersion(existing.identifier, existing.manifest.version))) { return this.extensionsScanner.removeExtension(existing, 'existing').then(null, e => Promise.reject(new Error(nls.localize('restartCode', "Please restart VS Code before reinstalling {0}.", manifest.displayName || manifest.name)))); @@ -194,8 +195,8 @@ export class ExtensionManagementService extends Disposable implements IExtension this._onInstallExtension.fire({ identifier, zipPath }); return this.getGalleryMetadata(getGalleryExtensionId(manifest.publisher, manifest.name)) .then( - metadata => this.installFromZipPath(identifierWithVersion, zipPath, { ...metadata, isDefault }, operation, token), - () => this.installFromZipPath(identifierWithVersion, zipPath, isDefault ? { isDefault } : undefined, operation, token)) + metadata => this.installFromZipPath(identifierWithVersion, zipPath, { ...metadata, isMachineScoped }, operation, token), + () => this.installFromZipPath(identifierWithVersion, zipPath, isMachineScoped ? { isMachineScoped } : undefined, operation, token)) .then( local => { this.logService.info('Successfully installed the extension:', identifier.id); return local; }, e => { @@ -239,7 +240,7 @@ export class ExtensionManagementService extends Disposable implements IExtension )); } - async installFromGallery(extension: IGalleryExtension, isDefault?: boolean): Promise<ILocalExtension> { + async installFromGallery(extension: IGalleryExtension, isMachineScoped?: boolean): Promise<ILocalExtension> { if (!this.galleryService.isEnabled()) { return Promise.reject(new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"))); } @@ -288,7 +289,7 @@ export class ExtensionManagementService extends Disposable implements IExtension this.downloadInstallableExtension(extension, operation) .then(installableExtension => { - installableExtension.metadata.isDefault = isDefault !== undefined ? isDefault : existingExtension?.isDefault; + installableExtension.metadata.isMachineScoped = isMachineScoped || existingExtension?.isMachineScoped; return this.installExtension(installableExtension, cancellationToken) .then(local => this.extensionsDownloader.delete(URI.file(installableExtension.zipPath)).finally(() => { }).then(() => local)); }) @@ -483,7 +484,7 @@ export class ExtensionManagementService extends Disposable implements IExtension async updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension> { this.logService.trace('ExtensionManagementService#updateMetadata', local.identifier.id); - local = await this.extensionsScanner.saveMetadataForLocalExtension(local, { ...metadata, isDefault: local.isDefault }); + local = await this.extensionsScanner.saveMetadataForLocalExtension(local, { ...metadata, isMachineScoped: local.isMachineScoped }); this.manifestCache.invalidate(); return local; } diff --git a/src/vs/platform/extensionManagement/node/extensionsScanner.ts b/src/vs/platform/extensionManagement/node/extensionsScanner.ts index 63c85181cc1..8c9d4ce94dd 100644 --- a/src/vs/platform/extensionManagement/node/extensionsScanner.ts +++ b/src/vs/platform/extensionManagement/node/extensionsScanner.ts @@ -32,7 +32,7 @@ const INSTALL_ERROR_EXTRACTING = 'extracting'; const INSTALL_ERROR_DELETING = 'deleting'; const INSTALL_ERROR_RENAMING = 'renaming'; -export type IMetadata = Partial<IGalleryMetadata & { isDefault: boolean; }>; +export type IMetadata = Partial<IGalleryMetadata & { isMachineScoped: boolean; }>; export class ExtensionsScanner extends Disposable { @@ -131,6 +131,9 @@ export class ExtensionsScanner extends Disposable { async saveMetadataForLocalExtension(local: ILocalExtension, metadata: IMetadata): Promise<ILocalExtension> { this.setMetadata(local, metadata); + + // unset if false + metadata.isMachineScoped = metadata.isMachineScoped || undefined; const manifestPath = path.join(local.location.fsPath, 'package.json'); const raw = await pfs.readFile(manifestPath, 'utf8'); const { manifest } = await this.parseManifest(raw); @@ -229,7 +232,7 @@ export class ExtensionsScanner extends Disposable { const changelog = children.filter(child => /^changelog(\.txt|\.md|)$/i.test(child))[0]; const changelogUrl = changelog ? URI.file(path.join(extensionPath, changelog)) : null; const identifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) }; - const local = <ILocalExtension>{ type, identifier, manifest, location: URI.file(extensionPath), readmeUrl, changelogUrl, publisherDisplayName: null, publisherId: null, isDefault: undefined }; + const local = <ILocalExtension>{ type, identifier, manifest, location: URI.file(extensionPath), readmeUrl, changelogUrl, publisherDisplayName: null, publisherId: null, isMachineScoped: false }; if (metadata) { this.setMetadata(local, metadata); } @@ -261,7 +264,7 @@ export class ExtensionsScanner extends Disposable { local.publisherDisplayName = metadata.publisherDisplayName || null; local.publisherId = metadata.publisherId || null; local.identifier.uuid = metadata.id; - local.isDefault = metadata.isDefault; + local.isMachineScoped = !!metadata.isMachineScoped; } private async removeUninstalledExtensions(): Promise<void> { diff --git a/src/vs/platform/userDataSync/common/extensionsMerge.ts b/src/vs/platform/userDataSync/common/extensionsMerge.ts index 7b6a48834fc..07c8a7bd733 100644 --- a/src/vs/platform/userDataSync/common/extensionsMerge.ts +++ b/src/vs/platform/userDataSync/common/extensionsMerge.ts @@ -8,6 +8,9 @@ import { ISyncExtension } from 'vs/platform/userDataSync/common/userDataSync'; import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { startsWith } from 'vs/base/common/strings'; import { deepClone } from 'vs/base/common/objects'; +import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { distinct } from 'vs/base/common/arrays'; export interface IMergeResult { added: ISyncExtension[]; @@ -201,3 +204,19 @@ function massageOutgoingExtension(extension: ISyncExtension, key: string): ISync } return massagedExtension; } + +export function getIgnoredExtensions(installed: ILocalExtension[], configurationService: IConfigurationService): string[] { + const defaultIgnoredExtensions = installed.filter(i => i.isMachineScoped).map(i => i.identifier.id.toLowerCase()); + const value = (configurationService.getValue<string[]>('sync.ignoredExtensions') || []).map(id => id.toLowerCase()); + const added: string[] = [], removed: string[] = []; + if (Array.isArray(value)) { + for (const key of value) { + if (startsWith(key, '-')) { + removed.push(key.substring(1)); + } else { + added.push(key); + } + } + } + return distinct([...defaultIgnoredExtensions, ...added,].filter(setting => removed.indexOf(setting) === -1)); +} diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index af62cda98db..1741086e3db 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -6,12 +6,12 @@ import { SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, ISyncResourceHandle, ISyncPreviewResult, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; import { Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IFileService } from 'vs/platform/files/common/files'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { merge } from 'vs/platform/userDataSync/common/extensionsMerge'; +import { merge, getIgnoredExtensions } from 'vs/platform/userDataSync/common/extensionsMerge'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { AbstractSynchroniser, IRemoteUserData, ISyncData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -87,9 +87,11 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const remoteUserData = await this.getRemoteUserData(lastSyncUserData); if (remoteUserData.syncData !== null) { - const localExtensions = await this.getLocalExtensions(); + const installedExtensions = await this.extensionManagementService.getInstalled(); + const localExtensions = this.getLocalExtensions(installedExtensions); const remoteExtensions = await this.parseAndMigrateExtensions(remoteUserData.syncData); - const { added, updated, remote, removed } = merge(localExtensions, remoteExtensions, localExtensions, [], this.getIgnoredExtensions()); + const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService); + const { added, updated, remote, removed } = merge(localExtensions, remoteExtensions, localExtensions, [], ignoredExtensions); await this.apply({ added, removed, updated, remote, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData, hasLocalChanged: added.length > 0 || removed.length > 0 || updated.length > 0, @@ -120,8 +122,10 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse this.logService.info(`${this.syncResourceLogLabel}: Started pushing extensions...`); this.setStatus(SyncStatus.Syncing); - const localExtensions = await this.getLocalExtensions(); - const { added, removed, updated, remote } = merge(localExtensions, null, null, [], this.getIgnoredExtensions()); + const installedExtensions = await this.extensionManagementService.getInstalled(); + const localExtensions = this.getLocalExtensions(installedExtensions); + const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService); + const { added, removed, updated, remote } = merge(localExtensions, null, null, [], ignoredExtensions); const lastSyncUserData = await this.getLastSyncUserData<ILastSyncUserData>(); const remoteUserData = await this.getRemoteUserData(lastSyncUserData); await this.apply({ @@ -145,7 +149,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse async resolveContent(uri: URI): Promise<string | null> { if (isEqual(uri, ExtensionsSynchroniser.EXTENSIONS_DATA_URI)) { - const localExtensions = await this.getLocalExtensions(); + const installedExtensions = await this.extensionManagementService.getInstalled(); + const localExtensions = this.getLocalExtensions(installedExtensions); return this.format(localExtensions); } @@ -189,7 +194,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse async hasLocalData(): Promise<boolean> { try { - const localExtensions = await this.getLocalExtensions(); + const installedExtensions = await this.extensionManagementService.getInstalled(); + const localExtensions = this.getLocalExtensions(installedExtensions); if (isNonEmptyArray(localExtensions)) { return true; } @@ -206,9 +212,11 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } protected async performReplace(syncData: ISyncData, remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<void> { - const localExtensions = await this.getLocalExtensions(); + const installedExtensions = await this.extensionManagementService.getInstalled(); + const localExtensions = this.getLocalExtensions(installedExtensions); const syncExtensions = await this.parseAndMigrateExtensions(syncData); - const { added, updated, removed } = merge(localExtensions, syncExtensions, localExtensions, [], this.getIgnoredExtensions()); + const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService); + const { added, updated, removed } = merge(localExtensions, syncExtensions, localExtensions, [], ignoredExtensions); await this.apply({ added, removed, updated, remote: syncExtensions, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData, @@ -222,7 +230,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? await this.parseAndMigrateExtensions(lastSyncUserData.syncData!) : null; const skippedExtensions: ISyncExtension[] = lastSyncUserData ? lastSyncUserData.skippedExtensions || [] : []; - const localExtensions = await this.getLocalExtensions(); + const installedExtensions = await this.extensionManagementService.getInstalled(); + const localExtensions = this.getLocalExtensions(installedExtensions); + const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService); if (remoteExtensions) { this.logService.trace(`${this.syncResourceLogLabel}: Merging remote extensions with local extensions...`); @@ -230,7 +240,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse this.logService.trace(`${this.syncResourceLogLabel}: Remote extensions does not exist. Synchronizing extensions for the first time.`); } - const { added, removed, updated, remote } = merge(localExtensions, remoteExtensions, lastSyncExtensions, skippedExtensions, this.getIgnoredExtensions()); + const { added, removed, updated, remote } = merge(localExtensions, remoteExtensions, lastSyncExtensions, skippedExtensions, ignoredExtensions); return { added, @@ -246,10 +256,6 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse }; } - private getIgnoredExtensions() { - return this.configurationService.getValue<string[]>('sync.ignoredExtensions') || []; - } - private async apply({ added, removed, updated, remote, remoteUserData, skippedExtensions, lastSyncUserData, localExtensions, hasLocalChanged, hasRemoteChanged }: IExtensionsSyncPreviewResult, forcePush?: boolean): Promise<void> { if (!hasLocalChanged && !hasRemoteChanged) { @@ -388,8 +394,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse return JSON.parse(syncData.content); } - private async getLocalExtensions(): Promise<ISyncExtension[]> { - const installedExtensions = await this.extensionManagementService.getInstalled(); + private getLocalExtensions(installedExtensions: ILocalExtension[]): ISyncExtension[] { const disabledExtensions = this.extensionEnablementService.getDisabledExtensions(); return installedExtensions .map(({ identifier, type }) => { diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 541ad44093a..e152bd1bc9c 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -45,7 +45,6 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; import { InstallExtensionQuickAccessProvider, ManageExtensionsQuickAccessProvider } from 'vs/workbench/contrib/extensions/browser/extensionsQuickAccess'; import { ExtensionRecommendationsService } from 'vs/workbench/contrib/extensions/browser/extensionRecommendationsService'; @@ -450,15 +449,11 @@ registerAction2(class extends Action2 { } async run(accessor: ServicesAccessor, id: string) { - const configurationService = accessor.get(IConfigurationService); - const ignoredExtensions = [...configurationService.getValue<string[]>('sync.ignoredExtensions')]; - const index = ignoredExtensions.findIndex(ignoredExtension => areSameExtensions({ id: ignoredExtension }, { id })); - if (index !== -1) { - ignoredExtensions.splice(index, 1); - } else { - ignoredExtensions.push(id); + const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); + const extension = extensionsWorkbenchService.local.find(e => areSameExtensions({ id }, e.identifier)); + if (extension) { + return extensionsWorkbenchService.toggleExtensionIgnoredToSync(extension); } - return configurationService.updateValue('sync.ignoredExtensions', ignoredExtensions.length ? ignoredExtensions : undefined, ConfigurationTarget.USER); } }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index a08e08da5cd..71f209daca1 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -808,7 +808,7 @@ export class MenuItemExtensionAction extends ExtensionAction { constructor( private readonly action: IAction, - @IConfigurationService private readonly configurationService: IConfigurationService + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, ) { super(action.id, action.label); } @@ -818,7 +818,7 @@ export class MenuItemExtensionAction extends ExtensionAction { return; } if (this.action.id === TOGGLE_IGNORE_EXTENSION_ACTION_ID) { - this.checked = !this.configurationService.getValue<string[]>('sync.ignoredExtensions').some(id => areSameExtensions({ id }, this.extension!.identifier)); + this.checked = !this.extensionsWorkbenchService.isExtensionIgnoredToSync(this.extension); } } @@ -2660,7 +2660,8 @@ export class SyncIgnoredIconAction extends ExtensionAction { private static readonly DISABLE_CLASS = `${SyncIgnoredIconAction.ENABLE_CLASS} hide`; constructor( - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, ) { super('extensions.syncignore', '', SyncIgnoredIconAction.DISABLE_CLASS, false); this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectedKeys.includes('sync.ignoredExtensions'))(() => this.update())); @@ -2670,11 +2671,8 @@ export class SyncIgnoredIconAction extends ExtensionAction { update(): void { this.class = SyncIgnoredIconAction.DISABLE_CLASS; - if (this.extension) { - const ignoredExtensions = this.configurationService.getValue<string[]>('sync.ignoredExtensions') || []; - if (ignoredExtensions.some(id => areSameExtensions({ id }, this.extension!.identifier))) { - this.class = SyncIgnoredIconAction.ENABLE_CLASS; - } + if (this.extension && this.extensionsWorkbenchService.isExtensionIgnoredToSync(this.extension)) { + this.class = SyncIgnoredIconAction.ENABLE_CLASS; } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 29f0c58c756..0b6d0a5f6c8 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -19,7 +19,7 @@ import { import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, getMaliciousExtensionsSet, groupByExtension, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { URI } from 'vs/base/common/uri'; import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey } from 'vs/workbench/contrib/extensions/common/extensions'; @@ -37,6 +37,7 @@ import { IExtensionManifest, ExtensionType, IExtension as IPlatformExtension, is import { IModeService } from 'vs/editor/common/services/modeService'; import { IProductService } from 'vs/platform/product/common/productService'; import { asDomUri } from 'vs/base/browser/dom'; +import { getIgnoredExtensions } from 'vs/platform/userDataSync/common/extensionsMerge'; interface IExtensionStateProvider<T> { (extension: Extension): T; @@ -863,6 +864,39 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension }, () => this.extensionService.reinstallFromGallery(toReinstall).then(() => this.local.filter(local => areSameExtensions(local.identifier, extension.identifier))[0])); } + isExtensionIgnoredToSync(extension: IExtension): boolean { + const localExtensions = (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer + ? this.local.filter(i => i.server === this.extensionManagementServerService.localExtensionManagementServer) + : this.local) + .filter(l => !!l.local) + .map(l => l.local!); + + const ignoredExtensions = getIgnoredExtensions(localExtensions, this.configurationService); + return ignoredExtensions.includes(extension.identifier.id.toLowerCase()); + } + + toggleExtensionIgnoredToSync(extension: IExtension): Promise<void> { + const isIgnored = this.isExtensionIgnoredToSync(extension); + const isDefaultIgnored = extension.local?.isMachineScoped; + const id = extension.identifier.id.toLowerCase(); + + // first remove the extension completely from ignored extensions + let currentValue = [...this.configurationService.getValue<string[]>('sync.ignoredExtensions')].map(id => id.toLowerCase()); + currentValue = currentValue.filter(v => v !== id && v !== `-${id}`); + + // If ignored, then add only if it is ignored by default + if (isIgnored && isDefaultIgnored) { + currentValue.push(`-${id}`); + } + + // If asked not to sync, then add only if it is not ignored by default + if (!isIgnored && !isDefaultIgnored) { + currentValue.push(id); + } + + return this.configurationService.updateValue('sync.ignoredExtensions', currentValue.length ? currentValue : undefined, ConfigurationTarget.USER); + } + private installWithProgress<T>(installTask: () => Promise<T>, extensionName?: string): Promise<T> { const title = extensionName ? nls.localize('installing named extension', "Installing '{0}' extension....", extensionName) : nls.localize('installing extension', 'Installing extension....'); return this.progressService.withProgress({ diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index fae5943aaf6..6119b8de80c 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -88,6 +88,10 @@ export interface IExtensionsWorkbenchService { setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise<void>; open(extension: IExtension, options?: { sideByside?: boolean, preserveFocus?: boolean, pinned?: boolean }): Promise<any>; checkForUpdates(): Promise<void>; + + // Sync APIs + isExtensionIgnoredToSync(extension: IExtension): boolean; + toggleExtensionIgnoredToSync(extension: IExtension): Promise<void>; } export const ConfigurationKey = 'extensions'; |