From ca7c4c64074fedb46a73563567cb54f512cf0a68 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Thu, 25 May 2023 14:35:14 -0700 Subject: [PATCH] Add experimental settings type (v2) (#183263) --- src/vs/base/common/product.ts | 7 ++ .../browser/media/settingsEditor2.css | 8 +- .../preferences/browser/settingsEditor2.ts | 114 ++++++++++++++++-- .../preferences/browser/settingsLayout.ts | 32 ++++- .../preferences/browser/settingsTree.ts | 66 +++++++++- .../preferences/browser/settingsTreeModels.ts | 31 +++-- .../contrib/preferences/common/preferences.ts | 47 +++++++- .../preferences/common/preferences.ts | 8 +- .../preferences/common/preferencesModels.ts | 15 ++- 9 files changed, 295 insertions(+), 33 deletions(-) diff --git a/src/vs/base/common/product.ts b/src/vs/base/common/product.ts index 264ebef4f3c9bc..ac9cd5db42ab2a 100644 --- a/src/vs/base/common/product.ts +++ b/src/vs/base/common/product.ts @@ -191,6 +191,8 @@ export interface IProductConfiguration { readonly 'editSessions.store'?: Omit; readonly darwinUniversalAssetId?: string; readonly profileTemplatesUrl?: string; + + readonly commonlyUsedSettings?: string[]; } export interface ITunnelApplicationConfig { @@ -201,6 +203,11 @@ export interface ITunnelApplicationConfig { export interface IExtensionRecommendations { readonly onFileOpen: IFileOpenCondition[]; + readonly onSettingsEditorOpen?: ISettingsEditorOpenCondition; +} + +export interface ISettingsEditorOpenCondition { + readonly prerelease: boolean | string; } export interface IExtensionRecommendationCondition { diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index 08dd68ba2167a7..eacb0db0e05839 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -160,6 +160,11 @@ color: var(--vscode-settings-headerForeground); } +.settings-editor > .settings-body .settings-tree-container .setting-item-extension-toggle .setting-item-extension-toggle-button { + display: block; + width: fit-content; +} + .settings-editor.no-results > .settings-body .settings-toc-container, .settings-editor.no-results > .settings-body .settings-tree-container { display: none; @@ -481,6 +486,7 @@ .settings-editor > .settings-body .settings-tree-container .setting-item.setting-item-number input[type=number] { /* Hide arrow button that shows in type=number fields */ -moz-appearance: textfield !important; + appearance: textfield !important; } .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown * { @@ -556,7 +562,7 @@ padding: 0px; } -.settings-editor > .settings-body .settings-tree-container .setting-item-bool .setting-value-checkbox.codicon:not(.checked)::before { +.settings-editor > .settings-body .settings-tree-container .setting-item-bool .setting-value-checkbox.codicon:not(.checked)::before { opacity: 0; } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 101a6abb5919bf..85176b9e7ae7c5 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -12,7 +12,6 @@ import { ITreeElement } from 'vs/base/browser/ui/tree/tree'; import { Action } from 'vs/base/common/actions'; import { Delayer, IntervalTimer, ThrottledDelayer, timeout } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import * as collections from 'vs/base/common/collections'; import { fromNow } from 'vs/base/common/date'; import { isCancellationError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; @@ -39,16 +38,16 @@ import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorMemento, IEditorOpenContext, IEditorPane } from 'vs/workbench/common/editor'; import { SuggestEnabledInput } from 'vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput'; import { SettingsTarget, SettingsTargetsWidget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; -import { commonlyUsedData, tocData } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; +import { getCommonlyUsedData, tocData } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; import { AbstractSettingRenderer, HeightChangeParams, ISettingLinkClickEvent, resolveConfiguredUntrustedSettings, createTocTreeForExtensionSettings, resolveSettingsTree, SettingsTree, SettingTreeRenderers } from 'vs/workbench/contrib/preferences/browser/settingsTree'; import { ISettingsEditorViewState, parseQuery, SearchResultIdx, SearchResultModel, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeModel, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; import { createTOCIterator, TOCTree, TOCTreeModel } from 'vs/workbench/contrib/preferences/browser/tocTree'; -import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, ENABLE_LANGUAGE_FILTER, EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, ID_SETTING_TAG, IPreferencesSearchService, ISearchProvider, LANGUAGE_SETTING_TAG, MODIFIED_SETTING_TAG, POLICY_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SUGGEST_FILTERS, WORKSPACE_TRUST_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; +import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, ENABLE_LANGUAGE_FILTER, EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, ID_SETTING_TAG, IPreferencesSearchService, ISearchProvider, LANGUAGE_SETTING_TAG, MODIFIED_SETTING_TAG, POLICY_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SUGGEST_FILTERS, WORKSPACE_TRUST_SETTING_TAG, getExperimentalExtensionToggleData } from 'vs/workbench/contrib/preferences/common/preferences'; import { settingsHeaderBorder, settingsSashBorder, settingsTextInputBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IOpenSettingsOptions, IPreferencesService, ISearchResult, ISettingsEditorModel, ISettingsEditorOptions, SettingMatchType, SettingValueType, validateSettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences'; +import { IOpenSettingsOptions, IPreferencesService, ISearchResult, ISetting, ISettingsEditorModel, ISettingsEditorOptions, ISettingsGroup, SettingMatchType, SettingValueType, validateSettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences'; import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; -import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; +import { Settings2EditorModel, nullRange } from 'vs/workbench/services/preferences/common/preferencesModels'; import { IUserDataSyncWorkbenchService } from 'vs/workbench/services/userDataSync/common/userDataSync'; import { preferencesClearInputIcon, preferencesFilterIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons'; import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; @@ -59,11 +58,14 @@ import { Orientation, Sizing, SplitView } from 'vs/base/browser/ui/splitview/spl import { Color } from 'vs/base/common/color'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { SettingsSearchFilterDropdownMenuActionViewItem } from 'vs/workbench/contrib/preferences/browser/settingsSearchMenu'; -import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionGalleryService, IExtensionManagementService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ISettingOverrideClickEvent } from 'vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators'; import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; export const enum SettingsFocusContext { Search, @@ -229,7 +231,11 @@ export class SettingsEditor2 extends EditorPane { @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService, @IExtensionService private readonly extensionService: IExtensionService, @ILanguageService private readonly languageService: ILanguageService, - @IExtensionManagementService extensionManagementService: IExtensionManagementService + @IExtensionManagementService extensionManagementService: IExtensionManagementService, + @IWorkbenchAssignmentService private readonly workbenchAssignmentService: IWorkbenchAssignmentService, + @IProductService private readonly productService: IProductService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, ) { super(SettingsEditor2.ID, telemetryService, themeService, storageService); this.delayedFilterLogging = new Delayer(1000); @@ -1189,14 +1195,49 @@ export class SettingsEditor2 extends EditorPane { }); } + private addOrRemoveManageExtensionSetting(setting: ISetting, extension: IGalleryExtension, groups: ISettingsGroup[]): ISettingsGroup | undefined { + const extensionId = setting.extensionId!; + const matchingGroups = groups.filter(g => g.extensionInfo?.id.toLowerCase() === extensionId.toLowerCase()); + if (!matchingGroups.length) { + const newGroup: ISettingsGroup = { + sections: [{ + settings: [setting], + }], + id: extensionId, + title: setting.extensionGroupTitle!, + titleRange: nullRange, + range: nullRange, + extensionInfo: { + id: extensionId, + displayName: extension?.displayName, + } + }; + groups.push(newGroup); + return newGroup; + } else if (matchingGroups.length >= 2) { + // Remove the group with the manage extension setting. + const matchingGroupIndex = matchingGroups.findIndex(group => + group.sections.length === 1 && group.sections[0].settings.length === 1 && group.sections[0].settings[0].extensionId); + if (matchingGroupIndex !== -1) { + groups.splice(matchingGroupIndex, 1); + } + } + return undefined; + } + private async onConfigUpdate(keys?: ReadonlySet, forceRefresh = false, schemaChange = false): Promise { if (keys && this.settingsTreeModel) { return this.updateElementsByKey(keys); } + if (!this.defaultSettingsEditorModel) { + return; + } + const groups = this.defaultSettingsEditorModel.settingsGroups.slice(1); // Without commonlyUsed - const dividedGroups = collections.groupBy(groups, g => g.extensionInfo ? 'extension' : 'core'); - const settingsResult = resolveSettingsTree(tocData, dividedGroups.core, this.logService); + + const coreSettings = groups.filter(g => !g.extensionInfo); + const settingsResult = resolveSettingsTree(tocData, coreSettings, this.logService); const resolvedSettingsRoot = settingsResult.tree; // Warn for settings not included in layout @@ -1210,10 +1251,61 @@ export class SettingsEditor2 extends EditorPane { this.hasWarnedMissingSettings = true; } - const commonlyUsed = resolveSettingsTree(commonlyUsedData, dividedGroups.core, this.logService); + const additionalGroups: ISettingsGroup[] = []; + const toggleData = await getExperimentalExtensionToggleData(this.workbenchAssignmentService, this.environmentService, this.productService); + if (toggleData && groups.filter(g => g.extensionInfo).length) { + for (const key in toggleData.settingsEditorRecommendedExtensions) { + const prerelease = toggleData.settingsEditorRecommendedExtensions[key].onSettingsEditorOpen!.prerelease; + + const extensionId = (typeof prerelease === 'string' && this.productService.quality !== 'stable') ? prerelease : key; + const [extension] = await this.extensionGalleryService.getExtensions([{ id: extensionId }], CancellationToken.None); + if (!extension) { + continue; + } + + let groupTitle: string | undefined; + const manifest = await this.extensionGalleryService.getManifest(extension, CancellationToken.None); + const contributesConfiguration = manifest?.contributes?.configuration; + if (!Array.isArray(contributesConfiguration)) { + groupTitle = contributesConfiguration?.title; + } else if (contributesConfiguration.length === 1) { + groupTitle = contributesConfiguration[0].title; + } + + const extensionName = extension?.displayName ?? extension?.name ?? extensionId; + const settingKey = `${key}.manageExtension`; + const setting: ISetting = { + range: nullRange, + key: settingKey, + keyRange: nullRange, + value: null, + valueRange: nullRange, + description: [extension?.description || ''], + descriptionIsMarkdown: false, + descriptionRanges: [], + title: localize('manageExtension', "Manage {0}", extensionName), + scope: ConfigurationScope.WINDOW, + type: 'null', + extensionId: extensionId, + extensionGroupTitle: groupTitle ?? extensionName + }; + const additionalGroup = this.addOrRemoveManageExtensionSetting(setting, extension, groups); + if (additionalGroup) { + additionalGroups.push(additionalGroup); + } + } + } + + resolvedSettingsRoot.children!.push(await createTocTreeForExtensionSettings(this.extensionService, groups.filter(g => g.extensionInfo))); + + const commonlyUsedDataToUse = await getCommonlyUsedData(this.workbenchAssignmentService, this.environmentService, this.productService); + const commonlyUsed = resolveSettingsTree(commonlyUsedDataToUse, groups, this.logService); resolvedSettingsRoot.children!.unshift(commonlyUsed.tree); - resolvedSettingsRoot.children!.push(await createTocTreeForExtensionSettings(this.extensionService, dividedGroups.extension || [])); + if (toggleData) { + // Add the additional groups to the model to help with searching. + this.defaultSettingsEditorModel.setAdditionalGroups(additionalGroups); + } if (!this.workspaceTrustManagementService.isWorkspaceTrusted() && (this.viewState.settingsTarget instanceof URI || this.viewState.settingsTarget === ConfigurationTarget.WORKSPACE)) { const configuredUntrustedWorkspaceSettings = resolveConfiguredUntrustedSettings(groups, this.viewState.settingsTarget, this.viewState.languageFilter, this.configurationService); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index 49e16879787b1e..b18507df13b9be 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -5,6 +5,10 @@ import { isWindows } from 'vs/base/common/platform'; import { localize } from 'vs/nls'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { getExperimentalExtensionToggleData } from 'vs/workbench/contrib/preferences/common/preferences'; +import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; export interface ITOCEntry { id: string; label: string; @@ -13,11 +17,29 @@ export interface ITOCEntry { settings?: Array; } -export const commonlyUsedData: ITOCEntry = { - id: 'commonlyUsed', - label: localize('commonlyUsed', "Commonly Used"), - settings: ['files.autoSave', 'editor.fontSize', 'editor.fontFamily', 'editor.tabSize', 'editor.renderWhitespace', 'editor.cursorStyle', 'editor.multiCursorModifier', 'editor.insertSpaces', 'editor.wordWrap', 'files.exclude', 'files.associations', 'workbench.editor.enablePreview'] -}; +const defaultCommonlyUsedSettings: string[] = [ + 'files.autoSave', + 'editor.fontSize', + 'editor.fontFamily', + 'editor.tabSize', + 'editor.renderWhitespace', + 'editor.cursorStyle', + 'editor.multiCursorModifier', + 'editor.insertSpaces', + 'editor.wordWrap', + 'files.exclude', + 'files.associations', + 'workbench.editor.enablePreview' +]; + +export async function getCommonlyUsedData(workbenchAssignmentService: IWorkbenchAssignmentService, environmentService: IEnvironmentService, productService: IProductService): Promise> { + const toggleData = await getExperimentalExtensionToggleData(workbenchAssignmentService, environmentService, productService); + return { + id: 'commonlyUsed', + label: localize('commonlyUsed', "Commonly Used"), + settings: toggleData ? toggleData.commonlyUsed : defaultCommonlyUsedSettings + }; +} export const tocData: ITOCEntry = { id: 'root', diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 4f1a29adeb463f..e9d2abb003b4ca 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -64,6 +64,9 @@ import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/use import { defaultButtonStyles, getInputBoxStyle, getListStyles, getSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { RenderIndentGuides } from 'vs/base/browser/ui/tree/abstractTree'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; const $ = DOM.$; @@ -502,7 +505,7 @@ function _resolveSettingsTree(tocData: ITOCEntry, allSettings: Set _resolveSettingsTree(child, allSettings, logService)) - .filter(child => (child.children && child.children.length) || (child.settings && child.settings.length)); + .filter(child => child.children?.length || child.settings?.length); } let settings: ISetting[] | undefined; @@ -603,6 +606,10 @@ interface ISettingBoolItemTemplate extends ISettingItemTemplate { checkbox: Toggle; } +interface ISettingExtensionToggleItemTemplate extends ISettingItemTemplate { + actionButton: Button; +} + interface ISettingTextItemTemplate extends ISettingItemTemplate { inputBox: InputBox; validationErrorMessageElement: HTMLElement; @@ -659,6 +666,7 @@ const SETTINGS_BOOL_OBJECT_TEMPLATE_ID = 'settings.boolObject.template'; const SETTINGS_COMPLEX_TEMPLATE_ID = 'settings.complex.template'; const SETTINGS_NEW_EXTENSIONS_TEMPLATE_ID = 'settings.newExtensions.template'; const SETTINGS_ELEMENT_TEMPLATE_ID = 'settings.group.template'; +const SETTINGS_EXTENSION_TOGGLE_TEMPLATE_ID = 'settings.extensionToggle.template'; export interface ISettingChangeEvent { key: string; @@ -758,6 +766,10 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre @IContextMenuService protected readonly _contextMenuService: IContextMenuService, @IKeybindingService protected readonly _keybindingService: IKeybindingService, @IConfigurationService protected readonly _configService: IConfigurationService, + @IExtensionService protected readonly _extensionsService: IExtensionService, + @IExtensionsWorkbenchService protected readonly _extensionsWorkbenchService: IExtensionsWorkbenchService, + @IProductService protected readonly _productService: IProductService, + @ITelemetryService protected readonly _telemetryService: ITelemetryService, ) { super(); @@ -873,7 +885,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre template.containerElement.setAttribute(AbstractSettingRenderer.SETTING_ID_ATTR, element.id); const titleTooltip = setting.key + (element.isConfigured ? ' - Modified' : ''); - template.categoryElement.textContent = element.displayCategory && (element.displayCategory + ': '); + template.categoryElement.textContent = element.displayCategory ? (element.displayCategory + ': ') : ''; template.categoryElement.title = titleTooltip; template.labelElement.text = element.displayLabel; @@ -1878,6 +1890,50 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre } } +type ManageExtensionClickTelemetryClassification = { + extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension the user went to manage.' }; + owner: 'rzhao271'; + comment: 'Event used to gain insights into when users are using an experimental extension management setting'; +}; + +export class SettingsExtensionToggleRenderer extends AbstractSettingRenderer implements ITreeRenderer { + templateId = SETTINGS_EXTENSION_TOGGLE_TEMPLATE_ID; + + renderTemplate(_container: HTMLElement): ISettingExtensionToggleItemTemplate { + const common = super.renderCommonTemplate(null, _container, 'extension-toggle'); + + const actionButton = new Button(common.containerElement, { + title: false, + ...defaultButtonStyles + }); + actionButton.element.classList.add('setting-item-extension-toggle-button'); + actionButton.label = localize('manageExtension', "Manage extension"); + + const template: ISettingExtensionToggleItemTemplate = { + ...common, + actionButton + }; + + this.addSettingElementFocusHandler(template); + + return template; + } + + renderElement(element: ITreeNode, index: number, templateData: ISettingExtensionToggleItemTemplate): void { + super.renderSettingElement(element, index, templateData); + } + + protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingExtensionToggleItemTemplate, onChange: (_: undefined) => void): void { + template.elementDisposables.clear(); + + const extensionId = dataElement.setting.extensionId!; + template.elementDisposables.add(template.actionButton.onDidClick(async () => { + this._telemetryService.publicLog2<{ extensionId: String }, ManageExtensionClickTelemetryClassification>('ManageExtensionClick', { extensionId }); + this._commandService.executeCommand('extension.open', extensionId); + })); + } +} + export class SettingTreeRenderers { readonly onDidClickOverrideElement: Event; @@ -1924,6 +1980,7 @@ export class SettingTreeRenderers { ]; const actionFactory = (setting: ISetting) => this.getActionsForSetting(setting); + const emptyActionFactory = (_: ISetting) => []; const settingRenderers = [ this._instantiationService.createInstance(SettingBoolRenderer, this.settingActions, actionFactory), this._instantiationService.createInstance(SettingNumberRenderer, this.settingActions, actionFactory), @@ -1936,6 +1993,7 @@ export class SettingTreeRenderers { this._instantiationService.createInstance(SettingEnumRenderer, this.settingActions, actionFactory), this._instantiationService.createInstance(SettingObjectRenderer, this.settingActions, actionFactory), this._instantiationService.createInstance(SettingBoolObjectRenderer, this.settingActions, actionFactory), + this._instantiationService.createInstance(SettingsExtensionToggleRenderer, [], emptyActionFactory) ]; this.onDidClickOverrideElement = Event.any(...settingRenderers.map(r => r.onDidClickOverrideElement)); @@ -2146,6 +2204,10 @@ class SettingsTreeDelegate extends CachedListVirtualDelegate; + commonlyUsed: string[]; +}; + +let cachedExtensionToggleData: ExtensionToggleData | undefined; + +export async function getExperimentalExtensionToggleData(workbenchAssignmentService: IWorkbenchAssignmentService, environmentService: IEnvironmentService, productService: IProductService): Promise { + if (!ENABLE_EXTENSION_TOGGLE_SETTINGS) { + return undefined; + } + + if (cachedExtensionToggleData) { + return cachedExtensionToggleData; + } + + const isTreatment = await workbenchAssignmentService.getTreatment('ExtensionToggleSettings'); + if ((isTreatment || !environmentService.isBuilt) && productService.extensionRecommendations && productService.commonlyUsedSettings) { + const settingsEditorRecommendedExtensions: Record = {}; + Object.keys(productService.extensionRecommendations).forEach(key => { + const value = productService.extensionRecommendations![key]; + if (value.onSettingsEditorOpen) { + settingsEditorRecommendedExtensions[key] = value; + } + }); + cachedExtensionToggleData = { + settingsEditorRecommendedExtensions, + commonlyUsed: productService.commonlyUsedSettings + }; + return cachedExtensionToggleData; + } + return undefined; +} diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index 216309898d3019..38ff3bb4879eeb 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -38,7 +38,8 @@ export enum SettingValueType { NullableNumber = 'nullable-number', Object = 'object', BooleanObject = 'boolean-object', - LanguageTag = 'language-tag' + LanguageTag = 'language-tag', + ExtensionToggle = 'extension-toggle' } export interface ISettingsGroup { @@ -94,6 +95,11 @@ export interface ISetting { isLanguageTagSetting?: boolean; categoryOrder?: number; categoryLabel?: string; + + // For ExtensionToggle settings + extensionId?: string; + title?: string; + extensionGroupTitle?: string; } export interface IExtensionSetting extends ISetting { diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index ebf1366de931b5..8b374aa28ea436 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -16,7 +16,7 @@ import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { ITextEditorModel } from 'vs/editor/common/services/resolverService'; import * as nls from 'vs/nls'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationRegistry, IExtensionInfo, IRegisteredConfigurationPropertySchema, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationPropertySchema, IConfigurationRegistry, IExtensionInfo, IRegisteredConfigurationPropertySchema, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; @@ -256,6 +256,7 @@ export class Settings2EditorModel extends AbstractSettingsModel implements ISett private readonly _onDidChangeGroups: Emitter = this._register(new Emitter()); readonly onDidChangeGroups: Event = this._onDidChangeGroups.event; + private additionalGroups: ISettingsGroup[] | undefined; private dirty = false; constructor( @@ -276,17 +277,25 @@ export class Settings2EditorModel extends AbstractSettingsModel implements ISett })); } + /** Doesn't include the "Commonly Used" group */ protected override get filterGroups(): ISettingsGroup[] { - // Don't filter "commonly used" return this.settingsGroups.slice(1); } get settingsGroups(): ISettingsGroup[] { const groups = this._defaultSettings.getSettingsGroups(this.dirty); + if (this.additionalGroups?.length) { + groups.push(...this.additionalGroups); + } this.dirty = false; return groups; } + /** For programmatically added groups outside of registered configurations */ + setAdditionalGroups(groups: ISettingsGroup[]) { + this.additionalGroups = groups; + } + findValueMatches(filter: string, setting: ISetting): IRange[] { // TODO @roblou return []; @@ -667,7 +676,7 @@ export class DefaultSettings extends Disposable { const categoryOrder = config.order; for (const key in settingsObject) { - const prop = settingsObject[key]; + const prop: IConfigurationPropertySchema = settingsObject[key]; if (this.matchesScope(prop)) { const value = prop.default; let description = (prop.markdownDescription || prop.description || '');