Path: blob/main/src/vs/workbench/contrib/extensions/common/extensionsUtils.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { localize } from '../../../../nls.js';6import { Event } from '../../../../base/common/event.js';7import { onUnexpectedError } from '../../../../base/common/errors.js';8import { Disposable } from '../../../../base/common/lifecycle.js';9import { IExtensionManagementService, ILocalExtension, IExtensionIdentifier, InstallOperation } from '../../../../platform/extensionManagement/common/extensionManagement.js';10import { IWorkbenchExtensionEnablementService, EnablementState } from '../../../services/extensionManagement/common/extensionManagement.js';11import { IExtensionRecommendationsService } from '../../../services/extensionRecommendations/common/extensionRecommendations.js';12import { ILifecycleService } from '../../../services/lifecycle/common/lifecycle.js';13import { IWorkbenchContribution } from '../../../common/contributions.js';14import { ServicesAccessor, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';15import { areSameExtensions } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js';16import { Severity, INotificationService } from '../../../../platform/notification/common/notification.js';1718export interface IExtensionStatus {19identifier: IExtensionIdentifier;20local: ILocalExtension;21globallyEnabled: boolean;22}2324export class KeymapExtensions extends Disposable implements IWorkbenchContribution {2526constructor(27@IInstantiationService private readonly instantiationService: IInstantiationService,28@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,29@IExtensionRecommendationsService private readonly tipsService: IExtensionRecommendationsService,30@ILifecycleService lifecycleService: ILifecycleService,31@INotificationService private readonly notificationService: INotificationService,32) {33super();34this._register(lifecycleService.onDidShutdown(() => this.dispose()));35this._register(instantiationService.invokeFunction(onExtensionChanged)((identifiers => {36Promise.all(identifiers.map(identifier => this.checkForOtherKeymaps(identifier)))37.then(undefined, onUnexpectedError);38})));39}4041private checkForOtherKeymaps(extensionIdentifier: IExtensionIdentifier): Promise<void> {42return this.instantiationService.invokeFunction(getInstalledExtensions).then(extensions => {43const keymaps = extensions.filter(extension => isKeymapExtension(this.tipsService, extension));44const extension = keymaps.find(extension => areSameExtensions(extension.identifier, extensionIdentifier));45if (extension && extension.globallyEnabled) {46const otherKeymaps = keymaps.filter(extension => !areSameExtensions(extension.identifier, extensionIdentifier) && extension.globallyEnabled);47if (otherKeymaps.length) {48return this.promptForDisablingOtherKeymaps(extension, otherKeymaps);49}50}51return undefined;52});53}5455private promptForDisablingOtherKeymaps(newKeymap: IExtensionStatus, oldKeymaps: IExtensionStatus[]): void {56const onPrompt = (confirmed: boolean) => {57if (confirmed) {58this.extensionEnablementService.setEnablement(oldKeymaps.map(keymap => keymap.local), EnablementState.DisabledGlobally);59}60};6162this.notificationService.prompt(Severity.Info, localize('disableOtherKeymapsConfirmation', "Disable other keymaps ({0}) to avoid conflicts between keybindings?", oldKeymaps.map(k => `'${k.local.manifest.displayName}'`).join(', ')),63[{64label: localize('yes', "Yes"),65run: () => onPrompt(true)66}, {67label: localize('no', "No"),68run: () => onPrompt(false)69}]70);71}72}7374function onExtensionChanged(accessor: ServicesAccessor): Event<IExtensionIdentifier[]> {75const extensionService = accessor.get(IExtensionManagementService);76const extensionEnablementService = accessor.get(IWorkbenchExtensionEnablementService);77const onDidInstallExtensions = Event.chain(extensionService.onDidInstallExtensions, $ =>78$.filter(e => e.some(({ operation }) => operation === InstallOperation.Install))79.map(e => e.map(({ identifier }) => identifier))80);81return Event.debounce<IExtensionIdentifier[], IExtensionIdentifier[]>(Event.any(82Event.any(onDidInstallExtensions, Event.map(extensionService.onDidUninstallExtension, e => [e.identifier])),83Event.map(extensionEnablementService.onEnablementChanged, extensions => extensions.map(e => e.identifier))84), (result: IExtensionIdentifier[] | undefined, identifiers: IExtensionIdentifier[]) => {85result = result || [];86for (const identifier of identifiers) {87if (result.some(l => !areSameExtensions(l, identifier))) {88result.push(identifier);89}90}91return result;92});93}9495export async function getInstalledExtensions(accessor: ServicesAccessor): Promise<IExtensionStatus[]> {96const extensionService = accessor.get(IExtensionManagementService);97const extensionEnablementService = accessor.get(IWorkbenchExtensionEnablementService);98const extensions = await extensionService.getInstalled();99return extensions.map(extension => {100return {101identifier: extension.identifier,102local: extension,103globallyEnabled: extensionEnablementService.isEnabled(extension)104};105});106}107108function isKeymapExtension(tipsService: IExtensionRecommendationsService, extension: IExtensionStatus): boolean {109const cats = extension.local.manifest.categories;110return cats && cats.indexOf('Keymaps') !== -1 || tipsService.getKeymapRecommendations().some(extensionId => areSameExtensions({ id: extensionId }, extension.local.identifier));111}112113114