From 4ab6fddf3e3ff2c19bb11a35648ede790e886c5f Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 4 Mar 2026 18:44:45 +0100 Subject: [PATCH] Revert "Adds support for stronglyRecommended extensions. Implements #299039" This reverts commit 240196b5955050f4be2d98839d02483dda0465a8. --- .vscode/extensions.json | 6 +- src/vs/platform/dialogs/common/dialogs.ts | 12 -- .../common/extensionRecommendations.ts | 2 - .../common/extensionRecommendationsIpc.ts | 9 - .../browser/parts/dialogs/dialogHandler.ts | 9 +- ...ensionRecommendationNotificationService.ts | 169 ------------------ .../extensionRecommendationsService.ts | 10 -- .../browser/extensions.contribution.ts | 11 -- .../stronglyRecommendedExtensionList.ts | 99 ---------- .../browser/workspaceRecommendations.ts | 29 +-- .../common/extensionsFileTemplate.ts | 9 - .../common/workspaceExtensionsConfig.ts | 8 - .../stronglyRecommendedDialog.fixture.ts | 85 --------- 13 files changed, 3 insertions(+), 455 deletions(-) delete mode 100644 src/vs/workbench/contrib/extensions/browser/stronglyRecommendedExtensionList.ts delete mode 100644 src/vs/workbench/test/browser/componentFixtures/stronglyRecommendedDialog.fixture.ts diff --git a/.vscode/extensions.json b/.vscode/extensions.json index bd45eb0e570..3fb87652c81 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -4,15 +4,11 @@ "recommendations": [ "dbaeumer.vscode-eslint", "editorconfig.editorconfig", + "github.vscode-pull-request-github", "ms-vscode.vscode-github-issue-notebooks", "ms-vscode.extension-test-runner", "jrieken.vscode-pr-pinger", "typescriptteam.native-preview", "ms-vscode.ts-customized-language-service" - ], - "stronglyRecommended": [ - "github.vscode-pull-request-github", - "ms-vscode.vscode-extras", - "ms-vscode.vscode-selfhost-test-provider" ] } diff --git a/src/vs/platform/dialogs/common/dialogs.ts b/src/vs/platform/dialogs/common/dialogs.ts index 925b82a8a28..fc73e57f824 100644 --- a/src/vs/platform/dialogs/common/dialogs.ts +++ b/src/vs/platform/dialogs/common/dialogs.ts @@ -5,7 +5,6 @@ import { CancellationToken } from '../../../base/common/cancellation.js'; import { Event } from '../../../base/common/event.js'; -import { DisposableStore } from '../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../base/common/themables.js'; import { IMarkdownString } from '../../../base/common/htmlContent.js'; import { basename } from '../../../base/common/resources.js'; @@ -287,23 +286,12 @@ export const IDialogService = createDecorator('dialogService'); export interface ICustomDialogOptions { readonly buttonDetails?: string[]; - readonly buttonOptions?: Array; readonly markdownDetails?: ICustomDialogMarkdown[]; - readonly renderBody?: (container: HTMLElement, disposables: DisposableStore) => void; readonly classes?: string[]; readonly icon?: ThemeIcon; readonly disableCloseAction?: boolean; } -export interface ICustomDialogButtonOptions { - readonly sublabel?: string; - readonly styleButton?: (button: ICustomDialogButtonControl) => void; -} - -export interface ICustomDialogButtonControl { - set enabled(value: boolean); -} - export interface ICustomDialogMarkdown { readonly markdown: IMarkdownString; readonly classes?: string[]; diff --git a/src/vs/platform/extensionRecommendations/common/extensionRecommendations.ts b/src/vs/platform/extensionRecommendations/common/extensionRecommendations.ts index 00b2a6ce993..fe258ed580c 100644 --- a/src/vs/platform/extensionRecommendations/common/extensionRecommendations.ts +++ b/src/vs/platform/extensionRecommendations/common/extensionRecommendations.ts @@ -45,7 +45,5 @@ export interface IExtensionRecommendationNotificationService { promptImportantExtensionsInstallNotification(recommendations: IExtensionRecommendations): Promise; promptWorkspaceRecommendations(recommendations: Array): Promise; - promptStronglyRecommendedExtensions(recommendations: Array): Promise; - resetStronglyRecommendedIgnoreState(): void; } diff --git a/src/vs/platform/extensionRecommendations/common/extensionRecommendationsIpc.ts b/src/vs/platform/extensionRecommendations/common/extensionRecommendationsIpc.ts index 28ae56a38be..da863e3c6e2 100644 --- a/src/vs/platform/extensionRecommendations/common/extensionRecommendationsIpc.ts +++ b/src/vs/platform/extensionRecommendations/common/extensionRecommendationsIpc.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from '../../../base/common/event.js'; -import { URI } from '../../../base/common/uri.js'; import { IChannel, IServerChannel } from '../../../base/parts/ipc/common/ipc.js'; import { IExtensionRecommendationNotificationService, IExtensionRecommendations, RecommendationsNotificationResult } from './extensionRecommendations.js'; @@ -24,18 +23,10 @@ export class ExtensionRecommendationNotificationServiceChannelClient implements throw new Error('not supported'); } - promptStronglyRecommendedExtensions(recommendations: Array): Promise { - throw new Error('not supported'); - } - hasToIgnoreRecommendationNotifications(): boolean { throw new Error('not supported'); } - resetStronglyRecommendedIgnoreState(): void { - throw new Error('not supported'); - } - } export class ExtensionRecommendationNotificationServiceChannel implements IServerChannel { diff --git a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts index be802600908..5af4f540bac 100644 --- a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts @@ -9,7 +9,6 @@ import { IConfirmation, IConfirmationResult, IInputResult, ICheckbox, IInputElem import { ILayoutService } from '../../../../platform/layout/browser/layoutService.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import Severity from '../../../../base/common/severity.js'; -import { IButton } from '../../../../base/browser/ui/button/button.js'; import { Dialog, IDialogResult } from '../../../../base/browser/ui/dialog/dialog.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; @@ -106,14 +105,8 @@ export class BrowserDialogHandler extends AbstractDialogHandler { parent.appendChild(result.element); result.element.classList.add(...(markdownDetail.classes || [])); }); - customOptions.renderBody?.(parent, dialogDisposables); } : undefined; - const buttonOptions = customOptions?.buttonOptions?.map(opt => opt ? { - sublabel: opt.sublabel, - styleButton: opt.styleButton ? (button: IButton) => opt.styleButton!(button) : undefined - } : undefined) ?? customOptions?.buttonDetails?.map(detail => ({ sublabel: detail })); - const dialog = new Dialog( this.layoutService.activeContainer, message, @@ -125,7 +118,7 @@ export class BrowserDialogHandler extends AbstractDialogHandler { renderBody, icon: customOptions?.icon, disableCloseAction: customOptions?.disableCloseAction, - buttonOptions, + buttonOptions: customOptions?.buttonDetails?.map(detail => ({ sublabel: detail })), checkboxLabel: checkbox?.label, checkboxChecked: checkbox?.checked, inputs diff --git a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts index 3caf43bdc45..b70a49c4016 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts @@ -6,7 +6,6 @@ import { distinct } from '../../../../base/common/arrays.js'; import { CancelablePromise, createCancelablePromise, Promises, raceCancellablePromises, raceCancellation, timeout } from '../../../../base/common/async.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; -import { Codicon } from '../../../../base/common/codicons.js'; import { isCancellationError } from '../../../../base/common/errors.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { Disposable, DisposableStore, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; @@ -14,13 +13,11 @@ import { isString } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; import { localize } from '../../../../nls.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; import { IGalleryExtension } from '../../../../platform/extensionManagement/common/extensionManagement.js'; import { areSameExtensions } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js'; import { IExtensionRecommendationNotificationService, IExtensionRecommendations, RecommendationsNotificationResult, RecommendationSource, RecommendationSourceToString } from '../../../../platform/extensionRecommendations/common/extensionRecommendations.js'; import { INotificationHandle, INotificationService, IPromptChoice, IPromptChoiceWithMenu, NotificationPriority, Severity } from '../../../../platform/notification/common/notification.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; -import { renderStronglyRecommendedExtensionList, StronglyRecommendedExtensionListResult } from './stronglyRecommendedExtensionList.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; import { IUserDataSyncEnablementService, SyncResource } from '../../../../platform/userDataSync/common/userDataSync.js'; @@ -45,18 +42,6 @@ type ExtensionWorkspaceRecommendationsNotificationClassification = { const ignoreImportantExtensionRecommendationStorageKey = 'extensionsAssistant/importantRecommendationsIgnore'; const donotShowWorkspaceRecommendationsStorageKey = 'extensionsAssistant/workspaceRecommendationsIgnore'; -const stronglyRecommendedIgnoreStorageKey = 'extensionsAssistant/stronglyRecommendedIgnore'; -const stronglyRecommendedMajorVersionIgnoreStorageKey = 'extensionsAssistant/stronglyRecommendedMajorVersionIgnore'; - -interface MajorVersionIgnoreEntry { - readonly id: string; - readonly majorVersion: number; -} - -function parseMajorVersion(version: string): number { - const major = parseInt(version.split('.')[0], 10); - return isNaN(major) ? 0 : major; -} type RecommendationsNotificationActions = { onDidInstallRecommendedExtensions(extensions: IExtension[]): void; @@ -147,7 +132,6 @@ export class ExtensionRecommendationNotificationService extends Disposable imple constructor( @IConfigurationService private readonly configurationService: IConfigurationService, - @IDialogService private readonly dialogService: IDialogService, @IStorageService private readonly storageService: IStorageService, @INotificationService private readonly notificationService: INotificationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @@ -484,159 +468,6 @@ export class ExtensionRecommendationNotificationService extends Disposable imple } } - async promptStronglyRecommendedExtensions(recommendations: Array): Promise { - if (this.hasToIgnoreRecommendationNotifications()) { - return; - } - - const ignoredList = this._getStronglyRecommendedIgnoreList(); - recommendations = recommendations.filter(rec => { - const key = isString(rec) ? rec.toLowerCase() : rec.toString(); - return !ignoredList.includes(key); - }); - if (!recommendations.length) { - return; - } - - let installed = await this.extensionManagementService.getInstalled(); - installed = installed.filter(l => this.extensionEnablementService.getEnablementState(l) !== EnablementState.DisabledByExtensionKind); - recommendations = recommendations.filter(recommendation => - installed.every(local => - isString(recommendation) ? !areSameExtensions({ id: recommendation }, local.identifier) : !this.uriIdentityService.extUri.isEqual(recommendation, local.location) - ) - ); - if (!recommendations.length) { - return; - } - - const allExtensions = await this.getInstallableExtensions(recommendations); - if (!allExtensions.length) { - return; - } - - const majorVersionIgnoreList = this._getStronglyRecommendedMajorVersionIgnoreList(); - const extensions = allExtensions.filter(ext => { - const ignored = majorVersionIgnoreList.find(e => e.id === ext.identifier.id.toLowerCase()); - return !ignored || parseMajorVersion(ext.version) > ignored.majorVersion; - }); - if (!extensions.length) { - return; - } - - const message = extensions.length === 1 - ? localize('stronglyRecommended1', "This workspace strongly recommends installing the '{0}' extension. Do you want to install?", extensions[0].displayName) - : localize('stronglyRecommendedN', "This workspace strongly recommends installing {0} extensions. Do you want to install?", extensions.length); - - let listResult!: StronglyRecommendedExtensionListResult; - - const { result } = await this.dialogService.prompt({ - message, - buttons: [ - { - label: localize('install', "Install"), - run: () => true, - }, - ], - cancelButton: localize('cancel', "Cancel"), - custom: { - icon: Codicon.extensions, - renderBody: (container, disposables) => { - listResult = renderStronglyRecommendedExtensionList(container, disposables, extensions); - }, - buttonOptions: [{ - styleButton: (button) => listResult.styleInstallButton(button), - }], - }, - }); - - if (result) { - const selected = extensions.filter(e => listResult.checkboxStates.get(e)); - const unselected = extensions.filter(e => !listResult.checkboxStates.get(e)); - if (unselected.length) { - this._addToStronglyRecommendedIgnoreList( - unselected.map(e => e.identifier.id) - ); - } - if (listResult.doNotShowAgainUnlessMajorVersionChange()) { - this._addToStronglyRecommendedIgnoreWithMajorVersion( - extensions.map(e => ({ id: e.identifier.id, majorVersion: parseMajorVersion(e.version) })) - ); - } - if (selected.length) { - const galleryExtensions: IGalleryExtension[] = []; - const resourceExtensions: IExtension[] = []; - for (const extension of selected) { - if (extension.gallery) { - galleryExtensions.push(extension.gallery); - } else if (extension.resourceExtension) { - resourceExtensions.push(extension); - } - } - await Promises.settled([ - Promises.settled(selected.map(extension => this.extensionsWorkbenchService.open(extension, { pinned: true }))), - galleryExtensions.length ? this.extensionManagementService.installGalleryExtensions(galleryExtensions.map(e => ({ extension: e, options: {} }))) : Promise.resolve(), - resourceExtensions.length ? Promise.allSettled(resourceExtensions.map(r => this.extensionsWorkbenchService.install(r))) : Promise.resolve(), - ]); - } - } - } - - private _getStronglyRecommendedIgnoreList(): string[] { - const raw = this.storageService.get(stronglyRecommendedIgnoreStorageKey, StorageScope.WORKSPACE); - if (raw === undefined) { - return []; - } - try { - const parsed = JSON.parse(raw); - return Array.isArray(parsed) ? parsed : []; - } catch { - return []; - } - } - - private _addToStronglyRecommendedIgnoreList(recommendations: Array): void { - const list = this._getStronglyRecommendedIgnoreList(); - for (const rec of recommendations) { - const key = isString(rec) ? rec.toLowerCase() : rec.toString(); - if (!list.includes(key)) { - list.push(key); - } - } - this.storageService.store(stronglyRecommendedIgnoreStorageKey, JSON.stringify(list), StorageScope.WORKSPACE, StorageTarget.MACHINE); - } - - private _getStronglyRecommendedMajorVersionIgnoreList(): MajorVersionIgnoreEntry[] { - const raw = this.storageService.get(stronglyRecommendedMajorVersionIgnoreStorageKey, StorageScope.WORKSPACE); - if (raw === undefined) { - return []; - } - try { - const parsed = JSON.parse(raw); - return Array.isArray(parsed) ? parsed : []; - } catch { - return []; - } - } - - private _addToStronglyRecommendedIgnoreWithMajorVersion(entries: MajorVersionIgnoreEntry[]): void { - const list = this._getStronglyRecommendedMajorVersionIgnoreList(); - for (const entry of entries) { - const key = entry.id.toLowerCase(); - const existing = list.findIndex(e => e.id === key); - if (existing !== -1) { - list[existing] = { id: key, majorVersion: entry.majorVersion }; - } else { - list.push({ id: key, majorVersion: entry.majorVersion }); - } - } - this.storageService.store(stronglyRecommendedMajorVersionIgnoreStorageKey, JSON.stringify(list), StorageScope.WORKSPACE, StorageTarget.MACHINE); - } - - resetStronglyRecommendedIgnoreState(): void { - this.storageService.remove(stronglyRecommendedIgnoreStorageKey, StorageScope.WORKSPACE); - this.storageService.remove(stronglyRecommendedMajorVersionIgnoreStorageKey, StorageScope.WORKSPACE); - } - private setIgnoreRecommendationsConfig(configVal: boolean) { this.configurationService.updateValue('extensions.ignoreRecommendations', configVal); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts index 9cba507bb0b..12cf0a6c61f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts @@ -110,7 +110,6 @@ export class ExtensionRecommendationsService extends Disposable implements IExte this._register(Event.any(this.workspaceRecommendations.onDidChangeRecommendations, this.configBasedRecommendations.onDidChangeRecommendations, this.extensionRecommendationsManagementService.onDidChangeIgnoredRecommendations)(() => this._onDidChangeRecommendations.fire())); this.promptWorkspaceRecommendations(); - this.promptStronglyRecommendedExtensions(); } private isEnabled(): boolean { @@ -275,15 +274,6 @@ export class ExtensionRecommendationsService extends Disposable implements IExte } } - private async promptStronglyRecommendedExtensions(): Promise { - const allowedRecommendations = this.workspaceRecommendations.stronglyRecommended - .filter(rec => !isString(rec) || this.isExtensionAllowedToBeRecommended(rec)); - - if (allowedRecommendations.length) { - await this.extensionRecommendationNotificationService.promptStronglyRecommendedExtensions(allowedRecommendations); - } - } - private _registerP(o: CancelablePromise): CancelablePromise { this._register(toDisposable(() => o.cancel())); return o; diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 218741bdd99..37e6e916e16 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -1895,17 +1895,6 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi run: () => this.commandService.executeCommand('workbench.extensions.action.addToWorkspaceIgnoredRecommendations') }); - this.registerExtensionAction({ - id: 'workbench.extensions.action.resetStronglyRecommendedIgnoreState', - title: localize2('workbench.extensions.action.resetStronglyRecommendedIgnoreState', "Reset Strongly Recommended Extensions Ignore State"), - category: EXTENSIONS_CATEGORY, - menu: { - id: MenuId.CommandPalette, - when: WorkbenchStateContext.notEqualsTo('empty'), - }, - run: async (accessor: ServicesAccessor) => accessor.get(IExtensionRecommendationNotificationService).resetStronglyRecommendedIgnoreState() - }); - this.registerExtensionAction({ id: ConfigureWorkspaceRecommendedExtensionsAction.ID, title: { value: ConfigureWorkspaceRecommendedExtensionsAction.LABEL, original: 'Configure Recommended Extensions (Workspace)' }, diff --git a/src/vs/workbench/contrib/extensions/browser/stronglyRecommendedExtensionList.ts b/src/vs/workbench/contrib/extensions/browser/stronglyRecommendedExtensionList.ts deleted file mode 100644 index c4cb34ce443..00000000000 --- a/src/vs/workbench/contrib/extensions/browser/stronglyRecommendedExtensionList.ts +++ /dev/null @@ -1,99 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { $, addDisposableListener } from '../../../../base/browser/dom.js'; -import { Checkbox } from '../../../../base/browser/ui/toggle/toggle.js'; -import { Emitter } from '../../../../base/common/event.js'; -import { DisposableStore } from '../../../../base/common/lifecycle.js'; -import { localize } from '../../../../nls.js'; -import { ICustomDialogButtonControl } from '../../../../platform/dialogs/common/dialogs.js'; -import { defaultCheckboxStyles } from '../../../../platform/theme/browser/defaultStyles.js'; - -export interface StronglyRecommendedExtensionEntry { - readonly displayName: string; - readonly publisherDisplayName: string; - readonly version: string; -} - -export interface StronglyRecommendedExtensionListResult { - readonly checkboxStates: ReadonlyMap; - readonly hasSelection: boolean; - readonly doNotShowAgainUnlessMajorVersionChange: () => boolean; - styleInstallButton(button: ICustomDialogButtonControl): void; -} - -export function renderStronglyRecommendedExtensionList( - container: HTMLElement, - disposables: DisposableStore, - extensions: readonly T[], -): StronglyRecommendedExtensionListResult { - const checkboxStates = new Map(); - const onSelectionChanged = disposables.add(new Emitter()); - - container.style.display = 'flex'; - container.style.flexDirection = 'column'; - container.style.gap = '8px'; - container.style.padding = '8px 0'; - - const updateCheckbox = (ext: T, cb: Checkbox) => { - checkboxStates.set(ext, cb.checked); - onSelectionChanged.fire(); - }; - - for (const ext of extensions) { - checkboxStates.set(ext, true); - - const row = container.appendChild($('.strongly-recommended-extension-row')); - row.style.display = 'flex'; - row.style.alignItems = 'center'; - row.style.gap = '8px'; - - const cb = disposables.add(new Checkbox(ext.displayName, true, defaultCheckboxStyles)); - disposables.add(cb.onChange(() => updateCheckbox(ext, cb))); - row.appendChild(cb.domNode); - - const label = row.appendChild($('span')); - label.textContent = `${ext.displayName} v${ext.version} \u2014 ${ext.publisherDisplayName}`; - label.style.cursor = 'pointer'; - disposables.add(addDisposableListener(label, 'click', () => { - cb.checked = !cb.checked; - updateCheckbox(ext, cb); - })); - } - - const separator = container.appendChild($('div')); - separator.style.borderTop = '1px solid var(--vscode-widget-border)'; - separator.style.marginTop = '4px'; - separator.style.paddingTop = '4px'; - - const doNotShowRow = container.appendChild($('.strongly-recommended-do-not-show-row')); - doNotShowRow.style.display = 'flex'; - doNotShowRow.style.alignItems = 'center'; - doNotShowRow.style.gap = '8px'; - - const doNotShowCb = disposables.add(new Checkbox( - localize('doNotShowAgainUnlessMajorVersionChange', "Do not show again unless major version change"), - false, - defaultCheckboxStyles, - )); - doNotShowRow.appendChild(doNotShowCb.domNode); - - const doNotShowLabel = doNotShowRow.appendChild($('span')); - doNotShowLabel.textContent = localize('doNotShowAgainUnlessMajorVersionChange', "Do not show again unless major version change"); - doNotShowLabel.style.cursor = 'pointer'; - disposables.add(addDisposableListener(doNotShowLabel, 'click', () => { doNotShowCb.checked = !doNotShowCb.checked; })); - - const hasSelection = () => [...checkboxStates.values()].some(v => v); - - return { - checkboxStates, - get hasSelection() { return hasSelection(); }, - doNotShowAgainUnlessMajorVersionChange: () => doNotShowCb.checked, - styleInstallButton(button: ICustomDialogButtonControl) { - const updateEnabled = () => { button.enabled = hasSelection(); }; - disposables.add(onSelectionChanged.event(updateEnabled)); - }, - }; -} diff --git a/src/vs/workbench/contrib/extensions/browser/workspaceRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/workspaceRecommendations.ts index 8563dcc4c15..69ff685c658 100644 --- a/src/vs/workbench/contrib/extensions/browser/workspaceRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/workspaceRecommendations.ts @@ -25,9 +25,6 @@ export class WorkspaceRecommendations extends ExtensionRecommendations { private _recommendations: ExtensionRecommendation[] = []; get recommendations(): ReadonlyArray { return this._recommendations; } - private _stronglyRecommended: Array = []; - get stronglyRecommended(): ReadonlyArray { return this._stronglyRecommended; } - private _onDidChangeRecommendations = this._register(new Emitter()); readonly onDidChangeRecommendations = this._onDidChangeRecommendations.event; @@ -35,7 +32,6 @@ export class WorkspaceRecommendations extends ExtensionRecommendations { get ignoredRecommendations(): ReadonlyArray { return this._ignoredRecommendations; } private workspaceExtensions: URI[] = []; - private workspaceExtensionIds = new Map(); private readonly onDidChangeWorkspaceExtensionsScheduler: RunOnceScheduler; constructor( @@ -94,12 +90,8 @@ export class WorkspaceRecommendations extends ExtensionRecommendations { // ignore } } - this.workspaceExtensionIds.clear(); if (workspaceExtensions.length) { const resourceExtensions = await this.workbenchExtensionManagementService.getExtensions(workspaceExtensions); - for (const ext of resourceExtensions) { - this.workspaceExtensionIds.set(ext.identifier.id.toLowerCase(), ext.location); - } return resourceExtensions.map(extension => extension.location); } return []; @@ -118,7 +110,6 @@ export class WorkspaceRecommendations extends ExtensionRecommendations { } this._recommendations = []; - this._stronglyRecommended = []; this._ignoredRecommendations = []; for (const extensionsConfig of extensionsConfigs) { @@ -142,24 +133,6 @@ export class WorkspaceRecommendations extends ExtensionRecommendations { } } } - if (extensionsConfig.stronglyRecommended) { - for (const extensionId of extensionsConfig.stronglyRecommended) { - if (invalidRecommendations.indexOf(extensionId) === -1) { - const workspaceExtUri = this.workspaceExtensionIds.get(extensionId.toLowerCase()); - const extension = workspaceExtUri ?? extensionId; - const reason = { - reasonId: ExtensionRecommendationReason.Workspace, - reasonText: localize('stronglyRecommendedExtension', "This extension is strongly recommended by users of the current workspace.") - }; - this._stronglyRecommended.push(extension); - if (workspaceExtUri) { - this._recommendations.push({ extension: workspaceExtUri, reason }); - } else { - this._recommendations.push({ extension: extensionId, reason }); - } - } - } - } } for (const extension of this.workspaceExtensions) { @@ -179,7 +152,7 @@ export class WorkspaceRecommendations extends ExtensionRecommendations { const invalidExtensions: string[] = []; let message = ''; - const allRecommendations = distinct(contents.flatMap(({ recommendations, stronglyRecommended }) => [...(recommendations || []), ...(stronglyRecommended || [])])); + const allRecommendations = distinct(contents.flatMap(({ recommendations }) => recommendations || [])); const regEx = new RegExp(EXTENSION_IDENTIFIER_PATTERN); for (const extensionId of allRecommendations) { if (regEx.test(extensionId)) { diff --git a/src/vs/workbench/contrib/extensions/common/extensionsFileTemplate.ts b/src/vs/workbench/contrib/extensions/common/extensionsFileTemplate.ts index 574806a1bc8..818e662847e 100644 --- a/src/vs/workbench/contrib/extensions/common/extensionsFileTemplate.ts +++ b/src/vs/workbench/contrib/extensions/common/extensionsFileTemplate.ts @@ -25,15 +25,6 @@ export const ExtensionsConfigurationSchema: IJSONSchema = { errorMessage: localize('app.extension.identifier.errorMessage', "Expected format '${publisher}.${name}'. Example: 'vscode.csharp'.") }, }, - stronglyRecommended: { - type: 'array', - description: localize('app.extensions.json.stronglyRecommended', "List of extensions that are strongly recommended for users of this workspace. Users will be prompted with a dialog to install these extensions. The identifier of an extension is always '${publisher}.${name}'. For example: 'vscode.csharp'."), - items: { - type: 'string', - pattern: EXTENSION_IDENTIFIER_PATTERN, - errorMessage: localize('app.extension.identifier.errorMessage', "Expected format '${publisher}.${name}'. Example: 'vscode.csharp'.") - }, - }, unwantedRecommendations: { type: 'array', description: localize('app.extensions.json.unwantedRecommendations', "List of extensions recommended by VS Code that should not be recommended for users of this workspace. The identifier of an extension is always '${publisher}.${name}'. For example: 'vscode.csharp'."), diff --git a/src/vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig.ts b/src/vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig.ts index ba02eaf679f..a48ce69a12b 100644 --- a/src/vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig.ts +++ b/src/vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig.ts @@ -24,7 +24,6 @@ export const EXTENSIONS_CONFIG = '.vscode/extensions.json'; export interface IExtensionsConfigContent { recommendations?: string[]; - stronglyRecommended?: string[]; unwantedRecommendations?: string[]; } @@ -36,7 +35,6 @@ export interface IWorkspaceExtensionsConfigService { readonly onDidChangeExtensionsConfigs: Event; getExtensionsConfigs(): Promise; getRecommendations(): Promise; - getStronglyRecommended(): Promise; getUnwantedRecommendations(): Promise; toggleRecommendation(extensionId: string): Promise; @@ -86,11 +84,6 @@ export class WorkspaceExtensionsConfigService extends Disposable implements IWor return distinct(configs.flatMap(c => c.recommendations ? c.recommendations.map(c => c.toLowerCase()) : [])); } - async getStronglyRecommended(): Promise { - const configs = await this.getExtensionsConfigs(); - return distinct(configs.flatMap(c => c.stronglyRecommended ? c.stronglyRecommended.map(c => c.toLowerCase()) : [])); - } - async getUnwantedRecommendations(): Promise { const configs = await this.getExtensionsConfigs(); return distinct(configs.flatMap(c => c.unwantedRecommendations ? c.unwantedRecommendations.map(c => c.toLowerCase()) : [])); @@ -303,7 +296,6 @@ export class WorkspaceExtensionsConfigService extends Disposable implements IWor private parseExtensionConfig(extensionsConfigContent: IExtensionsConfigContent): IExtensionsConfigContent { return { recommendations: distinct((extensionsConfigContent.recommendations || []).map(e => e.toLowerCase())), - stronglyRecommended: distinct((extensionsConfigContent.stronglyRecommended || []).map(e => e.toLowerCase())), unwantedRecommendations: distinct((extensionsConfigContent.unwantedRecommendations || []).map(e => e.toLowerCase())) }; } diff --git a/src/vs/workbench/test/browser/componentFixtures/stronglyRecommendedDialog.fixture.ts b/src/vs/workbench/test/browser/componentFixtures/stronglyRecommendedDialog.fixture.ts deleted file mode 100644 index def925aad9c..00000000000 --- a/src/vs/workbench/test/browser/componentFixtures/stronglyRecommendedDialog.fixture.ts +++ /dev/null @@ -1,85 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Dialog } from '../../../../base/browser/ui/dialog/dialog.js'; -import { localize } from '../../../../nls.js'; -import { defaultButtonStyles, defaultCheckboxStyles, defaultDialogStyles, defaultInputBoxStyles } from '../../../../platform/theme/browser/defaultStyles.js'; -import { StronglyRecommendedExtensionEntry, renderStronglyRecommendedExtensionList } from '../../../contrib/extensions/browser/stronglyRecommendedExtensionList.js'; -import { ComponentFixtureContext, defineComponentFixture, defineThemedFixtureGroup } from './fixtureUtils.js'; - -export default defineThemedFixtureGroup({ - TwoExtensions: defineComponentFixture({ render: ctx => renderDialog(ctx, twoExtensions) }), - SingleExtension: defineComponentFixture({ render: ctx => renderDialog(ctx, singleExtension) }), - ManyExtensions: defineComponentFixture({ render: ctx => renderDialog(ctx, manyExtensions) }), - NoneSelected: defineComponentFixture({ render: ctx => renderDialog(ctx, twoExtensions, { allUnchecked: true }) }), -}); - -const twoExtensions: StronglyRecommendedExtensionEntry[] = [ - { displayName: 'TypeScript Customized Language Service', publisherDisplayName: 'Microsoft', version: '3.1.0' }, - { displayName: 'VS Code Extras', publisherDisplayName: 'Microsoft', version: '1.0.5' }, -]; - -const singleExtension: StronglyRecommendedExtensionEntry[] = [ - { displayName: 'TypeScript Customized Language Service', publisherDisplayName: 'Microsoft', version: '3.1.0' }, -]; - -const manyExtensions: StronglyRecommendedExtensionEntry[] = [ - { displayName: 'TypeScript Customized Language Service', publisherDisplayName: 'Microsoft', version: '3.1.0' }, - { displayName: 'VS Code Extras', publisherDisplayName: 'Microsoft', version: '1.0.5' }, - { displayName: 'ESLint', publisherDisplayName: 'Dirk Baeumer', version: '2.4.4' }, - { displayName: 'Prettier', publisherDisplayName: 'Esben Petersen', version: '10.1.0' }, - { displayName: 'GitLens', publisherDisplayName: 'GitKraken', version: '15.6.2' }, -]; - -function renderDialog({ container, disposableStore }: ComponentFixtureContext, extensions: StronglyRecommendedExtensionEntry[], options?: { allUnchecked?: boolean }): void { - container.style.width = '700px'; - container.style.height = '500px'; - container.style.position = 'relative'; - container.style.overflow = 'hidden'; - - // The dialog uses position:fixed on its modal block, which escapes the shadow DOM container. - // Override to position:absolute so it stays within the fixture bounds. - const fixtureStyle = new CSSStyleSheet(); - fixtureStyle.replaceSync('.monaco-dialog-modal-block { position: absolute; }'); - const shadowRoot = container.getRootNode() as ShadowRoot; - shadowRoot.adoptedStyleSheets = [...shadowRoot.adoptedStyleSheets, fixtureStyle]; - - const message = extensions.length === 1 - ? localize('strongExtensionFixture', "This workspace strongly recommends installing the '{0}' extension. Do you want to install?", extensions[0].displayName) - : localize('strongExtensionsFixture', "This workspace strongly recommends installing {0} extensions. Do you want to install?", extensions.length); - - let listResult!: ReturnType; - - const dialog = disposableStore.add(new Dialog( - container, - message, - [ - localize('install', "Install"), - localize('cancel', "Cancel"), - ], - { - type: 'info', - renderBody: (bodyContainer: HTMLElement) => { - listResult = renderStronglyRecommendedExtensionList(bodyContainer, disposableStore, extensions); - }, - buttonOptions: [{ - styleButton: (button) => listResult.styleInstallButton(button), - }], - cancelId: 1, - buttonStyles: defaultButtonStyles, - checkboxStyles: defaultCheckboxStyles, - inputBoxStyles: defaultInputBoxStyles, - dialogStyles: defaultDialogStyles, - } - )); - - dialog.show(); - - if (options?.allUnchecked) { - for (const cb of container.querySelectorAll('.strongly-recommended-extension-row .monaco-custom-toggle')) { - cb.click(); - } - } -}