Path: blob/main/src/vs/workbench/contrib/externalUriOpener/common/contributedOpeners.ts
4780 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 { Disposable } from '../../../../base/common/lifecycle.js';6import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';7import { Memento } from '../../../common/memento.js';8import { updateContributedOpeners } from './configuration.js';9import { IExtensionService } from '../../../services/extensions/common/extensions.js';1011interface RegisteredExternalOpener {12readonly extensionId: string;1314isCurrentlyRegistered: boolean;15}1617interface OpenersMemento {18[id: string]: RegisteredExternalOpener | undefined;19}2021export class ContributedExternalUriOpenersStore extends Disposable {2223private static readonly STORAGE_ID = 'externalUriOpeners';2425private readonly _openers = new Map<string, RegisteredExternalOpener>();26private readonly _memento: Memento<OpenersMemento>;27private _mementoObject: OpenersMemento;2829constructor(30@IStorageService storageService: IStorageService,31@IExtensionService private readonly _extensionService: IExtensionService32) {33super();3435this._memento = new Memento(ContributedExternalUriOpenersStore.STORAGE_ID, storageService);36this._mementoObject = this._memento.getMemento(StorageScope.PROFILE, StorageTarget.MACHINE);37for (const [id, value] of Object.entries(this._mementoObject || {})) {38if (value) {39this.add(id, value.extensionId, { isCurrentlyRegistered: false });40}41}4243this.invalidateOpenersOnExtensionsChanged();4445this._register(this._extensionService.onDidChangeExtensions(() => this.invalidateOpenersOnExtensionsChanged()));46this._register(this._extensionService.onDidChangeExtensionsStatus(() => this.invalidateOpenersOnExtensionsChanged()));47}4849public didRegisterOpener(id: string, extensionId: string): void {50this.add(id, extensionId, {51isCurrentlyRegistered: true52});53}5455private add(id: string, extensionId: string, options: { isCurrentlyRegistered: boolean }): void {56const existing = this._openers.get(id);57if (existing) {58existing.isCurrentlyRegistered = existing.isCurrentlyRegistered || options.isCurrentlyRegistered;59return;60}6162const entry = {63extensionId,64isCurrentlyRegistered: options.isCurrentlyRegistered65};66this._openers.set(id, entry);6768this._mementoObject[id] = entry;69this._memento.saveMemento();7071this.updateSchema();72}7374public delete(id: string): void {75this._openers.delete(id);7677delete this._mementoObject[id];78this._memento.saveMemento();7980this.updateSchema();81}8283private async invalidateOpenersOnExtensionsChanged() {84await this._extensionService.whenInstalledExtensionsRegistered();85const registeredExtensions = this._extensionService.extensions;8687for (const [id, entry] of this._openers) {88const extension = registeredExtensions.find(r => r.identifier.value === entry.extensionId);89if (extension) {90if (!this._extensionService.canRemoveExtension(extension)) {91// The extension is running. We should have registered openers at this point92if (!entry.isCurrentlyRegistered) {93this.delete(id);94}95}96} else {97// The opener came from an extension that is no longer enabled/installed98this.delete(id);99}100}101}102103private updateSchema() {104const ids: string[] = [];105const descriptions: string[] = [];106107for (const [id, entry] of this._openers) {108ids.push(id);109descriptions.push(entry.extensionId);110}111112updateContributedOpeners(ids, descriptions);113}114}115116117