Path: blob/main/src/vs/workbench/contrib/notebook/browser/services/notebookKeymapServiceImpl.ts
5255 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 { onUnexpectedError } from '../../../../../base/common/errors.js';6import { Event } from '../../../../../base/common/event.js';7import { Disposable } from '../../../../../base/common/lifecycle.js';8import { localize } from '../../../../../nls.js';9import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js';10import { INotificationService, Severity } from '../../../../../platform/notification/common/notification.js';11import { getInstalledExtensions, IExtensionStatus } from '../../../extensions/common/extensionsUtils.js';12import { INotebookKeymapService } from '../../common/notebookKeymapService.js';13import { EnablementState, IWorkbenchExtensionEnablementService } from '../../../../services/extensionManagement/common/extensionManagement.js';14import { ILifecycleService } from '../../../../services/lifecycle/common/lifecycle.js';15import { IExtensionIdentifier, IExtensionManagementService, InstallOperation } from '../../../../../platform/extensionManagement/common/extensionManagement.js';16import { areSameExtensions } from '../../../../../platform/extensionManagement/common/extensionManagementUtil.js';17import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';18import { Memento } from '../../../../common/memento.js';19import { distinct } from '../../../../../base/common/arrays.js';2021function onExtensionChanged(accessor: ServicesAccessor): Event<IExtensionIdentifier[]> {22const extensionService = accessor.get(IExtensionManagementService);23const extensionEnablementService = accessor.get(IWorkbenchExtensionEnablementService);24const onDidInstallExtensions = Event.chain(extensionService.onDidInstallExtensions, $ =>25$.filter(e => e.some(({ operation }) => operation === InstallOperation.Install))26.map(e => e.map(({ identifier }) => identifier))27);28return Event.debounce<IExtensionIdentifier[], IExtensionIdentifier[]>(Event.any(29Event.any(onDidInstallExtensions, Event.map(extensionService.onDidUninstallExtension, e => [e.identifier])),30Event.map(extensionEnablementService.onEnablementChanged, extensions => extensions.map(e => e.identifier))31), (result: IExtensionIdentifier[] | undefined, identifiers: IExtensionIdentifier[]) => {32result = result || (identifiers.length ? [identifiers[0]] : []);33for (const identifier of identifiers) {34if (result.some(l => !areSameExtensions(l, identifier))) {35result.push(identifier);36}37}3839return result;40});41}4243const hasRecommendedKeymapKey = 'hasRecommendedKeymap';4445interface NotebookKeymapMemento {46[hasRecommendedKeymapKey]?: boolean;47}4849export class NotebookKeymapService extends Disposable implements INotebookKeymapService {50_serviceBrand: undefined;5152private notebookKeymapMemento: Memento<NotebookKeymapMemento>;53private notebookKeymap: NotebookKeymapMemento;5455constructor(56@IInstantiationService private readonly instantiationService: IInstantiationService,57@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,58@INotificationService private readonly notificationService: INotificationService,59@IStorageService storageService: IStorageService,60@ILifecycleService lifecycleService: ILifecycleService,61) {62super();6364this.notebookKeymapMemento = new Memento('notebookKeymap', storageService);65this.notebookKeymap = this.notebookKeymapMemento.getMemento(StorageScope.PROFILE, StorageTarget.USER);6667this._register(lifecycleService.onDidShutdown(() => this.dispose()));68this._register(this.instantiationService.invokeFunction(onExtensionChanged)((identifiers => {69Promise.all(identifiers.map(identifier => this.checkForOtherKeymaps(identifier)))70.then(undefined, onUnexpectedError);71})));72}7374private checkForOtherKeymaps(extensionIdentifier: IExtensionIdentifier): Promise<void> {75return this.instantiationService.invokeFunction(getInstalledExtensions).then(extensions => {76const keymaps = extensions.filter(extension => isNotebookKeymapExtension(extension));77const extension = keymaps.find(extension => areSameExtensions(extension.identifier, extensionIdentifier));78if (extension && extension.globallyEnabled) {79// there is already a keymap extension80this.notebookKeymap[hasRecommendedKeymapKey] = true;81this.notebookKeymapMemento.saveMemento();82const otherKeymaps = keymaps.filter(extension => !areSameExtensions(extension.identifier, extensionIdentifier) && extension.globallyEnabled);83if (otherKeymaps.length) {84return this.promptForDisablingOtherKeymaps(extension, otherKeymaps);85}86}87return undefined;88});89}9091private promptForDisablingOtherKeymaps(newKeymap: IExtensionStatus, oldKeymaps: IExtensionStatus[]): void {92const onPrompt = (confirmed: boolean) => {93if (confirmed) {94this.extensionEnablementService.setEnablement(oldKeymaps.map(keymap => keymap.local), EnablementState.DisabledGlobally);95}96};9798this.notificationService.prompt(Severity.Info, localize('disableOtherKeymapsConfirmation', "Disable other keymaps ({0}) to avoid conflicts between keybindings?", distinct(oldKeymaps.map(k => k.local.manifest.displayName)).map(name => `'${name}'`).join(', ')),99[{100label: localize('yes', "Yes"),101run: () => onPrompt(true)102}, {103label: localize('no', "No"),104run: () => onPrompt(false)105}]106);107}108}109110export function isNotebookKeymapExtension(extension: IExtensionStatus): boolean {111if (extension.local.manifest.extensionPack) {112return false;113}114115const keywords = extension.local.manifest.keywords;116if (!keywords) {117return false;118}119120return keywords.indexOf('notebook-keymap') !== -1;121}122123124