Path: blob/main/src/vs/workbench/contrib/preferences/browser/settingsTree.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 { BrowserFeatures } from '../../../../base/browser/canIUse.js';6import * as DOM from '../../../../base/browser/dom.js';7import * as domStylesheetsJs from '../../../../base/browser/domStylesheets.js';8import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js';9import { renderAsPlaintext } from '../../../../base/browser/markdownRenderer.js';10import { IMouseEvent } from '../../../../base/browser/mouseEvent.js';11import * as aria from '../../../../base/browser/ui/aria/aria.js';12import { Button } from '../../../../base/browser/ui/button/button.js';13import { SimpleIconLabel } from '../../../../base/browser/ui/iconLabel/simpleIconLabel.js';14import { IInputOptions, InputBox } from '../../../../base/browser/ui/inputbox/inputBox.js';15import { CachedListVirtualDelegate } from '../../../../base/browser/ui/list/list.js';16import { DefaultStyleController, IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js';17import { ISelectOptionItem, SelectBox } from '../../../../base/browser/ui/selectBox/selectBox.js';18import { Toggle, unthemedToggleStyles } from '../../../../base/browser/ui/toggle/toggle.js';19import { ToolBar } from '../../../../base/browser/ui/toolbar/toolbar.js';20import { RenderIndentGuides } from '../../../../base/browser/ui/tree/abstractTree.js';21import { IObjectTreeOptions } from '../../../../base/browser/ui/tree/objectTree.js';22import { ObjectTreeModel } from '../../../../base/browser/ui/tree/objectTreeModel.js';23import { ITreeFilter, ITreeModel, ITreeNode, ITreeRenderer, TreeFilterResult, TreeVisibility } from '../../../../base/browser/ui/tree/tree.js';24import { Action, IAction, Separator } from '../../../../base/common/actions.js';25import { distinct } from '../../../../base/common/arrays.js';26import { Codicon } from '../../../../base/common/codicons.js';27import { onUnexpectedError } from '../../../../base/common/errors.js';28import { Emitter, Event } from '../../../../base/common/event.js';29import { IJSONSchema } from '../../../../base/common/jsonSchema.js';30import { KeyCode } from '../../../../base/common/keyCodes.js';31import { Disposable, DisposableStore, isDisposable, toDisposable } from '../../../../base/common/lifecycle.js';32import { isIOS } from '../../../../base/common/platform.js';33import { escapeRegExpCharacters } from '../../../../base/common/strings.js';34import { isDefined, isUndefinedOrNull } from '../../../../base/common/types.js';35import { URI } from '../../../../base/common/uri.js';36import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js';37import { ILanguageService } from '../../../../editor/common/languages/language.js';38import { localize } from '../../../../nls.js';39import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';40import { ICommandService } from '../../../../platform/commands/common/commands.js';41import { ConfigurationTarget, IConfigurationService, getLanguageTagSettingPlainKey } from '../../../../platform/configuration/common/configuration.js';42import { ConfigurationScope } from '../../../../platform/configuration/common/configurationRegistry.js';43import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';44import { IContextMenuService, IContextViewService } from '../../../../platform/contextview/browser/contextView.js';45import { IHoverService } from '../../../../platform/hover/browser/hover.js';46import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';47import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';48import { IListService, WorkbenchObjectTree } from '../../../../platform/list/browser/listService.js';49import { ILogService } from '../../../../platform/log/common/log.js';50import { IOpenerService } from '../../../../platform/opener/common/opener.js';51import { IProductService } from '../../../../platform/product/common/productService.js';52import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';53import { defaultButtonStyles, getInputBoxStyle, getListStyles, getSelectBoxStyles } from '../../../../platform/theme/browser/defaultStyles.js';54import { editorBackground, foreground } from '../../../../platform/theme/common/colorRegistry.js';55import { IThemeService } from '../../../../platform/theme/common/themeService.js';56import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js';57import { getIgnoredSettings } from '../../../../platform/userDataSync/common/settingsMerge.js';58import { IUserDataSyncEnablementService, getDefaultIgnoredSettings } from '../../../../platform/userDataSync/common/userDataSync.js';59import { hasNativeContextMenu } from '../../../../platform/window/common/window.js';60import { APPLICATION_SCOPES, APPLY_ALL_PROFILES_SETTING, IWorkbenchConfigurationService } from '../../../services/configuration/common/configuration.js';61import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js';62import { IExtensionService } from '../../../services/extensions/common/extensions.js';63import { ISetting, ISettingsGroup, SETTINGS_AUTHORITY, SettingValueType } from '../../../services/preferences/common/preferences.js';64import { getInvalidTypeError } from '../../../services/preferences/common/preferencesValidation.js';65import { IExtensionsWorkbenchService } from '../../extensions/common/extensions.js';66import { LANGUAGE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU, compareTwoNullableNumbers } from '../common/preferences.js';67import { settingsNumberInputBackground, settingsNumberInputBorder, settingsNumberInputForeground, settingsSelectBackground, settingsSelectBorder, settingsSelectForeground, settingsSelectListBorder, settingsTextInputBackground, settingsTextInputBorder, settingsTextInputForeground } from '../common/settingsEditorColorRegistry.js';68import { settingsMoreActionIcon } from './preferencesIcons.js';69import { SettingsTarget } from './preferencesWidgets.js';70import { ISettingOverrideClickEvent, SettingsTreeIndicatorsLabel, getIndicatorsLabelAriaLabel } from './settingsEditorSettingIndicators.js';71import { ITOCEntry } from './settingsLayout.js';72import { ISettingsEditorViewState, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeNewExtensionsElement, SettingsTreeSettingElement, inspectSetting, objectSettingSupportsRemoveDefaultValue, settingKeyToDisplayFormat } from './settingsTreeModels.js';73import { ExcludeSettingWidget, IBoolObjectDataItem, IIncludeExcludeDataItem, IListDataItem, IObjectDataItem, IObjectEnumOption, IObjectKeySuggester, IObjectValueSuggester, IncludeSettingWidget, ListSettingWidget, ObjectSettingCheckboxWidget, ObjectSettingDropdownWidget, ObjectValue, SettingListEvent } from './settingsWidgets.js';7475const $ = DOM.$;7677function getIncludeExcludeDisplayValue(element: SettingsTreeSettingElement): IIncludeExcludeDataItem[] {78const elementDefaultValue: Record<string, unknown> = typeof element.defaultValue === 'object'79? element.defaultValue ?? {}80: {};8182const data = element.isConfigured ?83{ ...elementDefaultValue, ...element.scopeValue } :84elementDefaultValue;8586return Object.keys(data)87.filter(key => !!data[key])88.map(key => {89const defaultValue = elementDefaultValue[key];9091// Get source if it's a default value92let source: string | undefined;93if (defaultValue === data[key] && element.setting.type === 'object' && element.defaultValueSource instanceof Map) {94const defaultSource = element.defaultValueSource.get(`${element.setting.key}.${key}`);95source = typeof defaultSource === 'string' ? defaultSource : defaultSource?.displayName;96}9798const value = data[key];99const sibling = typeof value === 'boolean' ? undefined : value.when;100return {101value: {102type: 'string',103data: key104},105sibling,106elementType: element.valueType,107source108};109});110}111112function areAllPropertiesDefined(properties: string[], itemsToDisplay: IObjectDataItem[]): boolean {113const staticProperties = new Set(properties);114itemsToDisplay.forEach(({ key }) => staticProperties.delete(key.data));115return staticProperties.size === 0;116}117118function getEnumOptionsFromSchema(schema: IJSONSchema): IObjectEnumOption[] {119if (schema.anyOf) {120return schema.anyOf.map(getEnumOptionsFromSchema).flat();121}122123const enumDescriptions = schema.enumDescriptions ?? [];124125return (schema.enum ?? []).map((value, idx) => {126const description = idx < enumDescriptions.length127? enumDescriptions[idx]128: undefined;129130return { value, description };131});132}133134function getObjectValueType(schema: IJSONSchema): ObjectValue['type'] {135if (schema.anyOf) {136const subTypes = schema.anyOf.map(getObjectValueType);137if (subTypes.some(type => type === 'enum')) {138return 'enum';139}140return 'string';141}142143if (schema.type === 'boolean') {144return 'boolean';145} else if (schema.type === 'string' && isDefined(schema.enum) && schema.enum.length > 0) {146return 'enum';147} else {148return 'string';149}150}151152function getObjectEntryValueDisplayValue(type: ObjectValue['type'], data: unknown, options: IObjectEnumOption[]): ObjectValue {153if (type === 'boolean') {154return { type, data: !!data };155} else if (type === 'enum') {156return { type, data: '' + data, options };157} else {158return { type, data: '' + data };159}160}161162function getObjectDisplayValue(element: SettingsTreeSettingElement): IObjectDataItem[] {163const elementDefaultValue: Record<string, unknown> = typeof element.defaultValue === 'object'164? element.defaultValue ?? {}165: {};166167const elementScopeValue: Record<string, unknown> = typeof element.scopeValue === 'object'168? element.scopeValue ?? {}169: {};170171const data = element.isConfigured ?172{ ...elementDefaultValue, ...elementScopeValue } :173element.hasPolicyValue ? element.scopeValue :174elementDefaultValue;175176const { objectProperties, objectPatternProperties, objectAdditionalProperties } = element.setting;177const patternsAndSchemas = Object178.entries(objectPatternProperties ?? {})179.map(([pattern, schema]) => ({180pattern: new RegExp(pattern),181schema182}));183184const wellDefinedKeyEnumOptions = Object.entries(objectProperties ?? {}).map(185([key, schema]) => ({ value: key, description: schema.description })186);187188return Object.keys(data).map(key => {189const defaultValue = elementDefaultValue[key];190191// Get source if it's a default value192let source: string | undefined;193if (defaultValue === data[key] && element.setting.type === 'object' && element.defaultValueSource instanceof Map) {194const defaultSource = element.defaultValueSource.get(`${element.setting.key}.${key}`);195source = typeof defaultSource === 'string' ? defaultSource : defaultSource?.displayName;196}197198if (isDefined(objectProperties) && key in objectProperties) {199const valueEnumOptions = getEnumOptionsFromSchema(objectProperties[key]);200return {201key: {202type: 'enum',203data: key,204options: wellDefinedKeyEnumOptions,205},206value: getObjectEntryValueDisplayValue(getObjectValueType(objectProperties[key]), data[key], valueEnumOptions),207keyDescription: objectProperties[key].description,208removable: isUndefinedOrNull(defaultValue),209resetable: !isUndefinedOrNull(defaultValue),210source211} satisfies IObjectDataItem;212}213214// The row is removable if it doesn't have a default value assigned or the setting supports removing the default value.215// If a default value is assigned and the user modified the default, it can be reset back to the default.216const removable = defaultValue === undefined || objectSettingSupportsRemoveDefaultValue(element.setting.key);217const resetable = !!defaultValue && defaultValue !== data[key];218const schema = patternsAndSchemas.find(({ pattern }) => pattern.test(key))?.schema;219if (schema) {220const valueEnumOptions = getEnumOptionsFromSchema(schema);221return {222key: { type: 'string', data: key },223value: getObjectEntryValueDisplayValue(getObjectValueType(schema), data[key], valueEnumOptions),224keyDescription: schema.description,225removable,226resetable,227source228} satisfies IObjectDataItem;229}230231const additionalValueEnums = getEnumOptionsFromSchema(232typeof objectAdditionalProperties === 'boolean'233? {}234: objectAdditionalProperties ?? {}235);236237return {238key: { type: 'string', data: key },239value: getObjectEntryValueDisplayValue(240typeof objectAdditionalProperties === 'object' ? getObjectValueType(objectAdditionalProperties) : 'string',241data[key],242additionalValueEnums,243),244keyDescription: typeof objectAdditionalProperties === 'object' ? objectAdditionalProperties.description : undefined,245removable,246resetable,247source248} satisfies IObjectDataItem;249}).filter(item => !isUndefinedOrNull(item.value.data));250}251252function getBoolObjectDisplayValue(element: SettingsTreeSettingElement): IBoolObjectDataItem[] {253const elementDefaultValue: Record<string, unknown> = typeof element.defaultValue === 'object'254? element.defaultValue ?? {}255: {};256257const elementScopeValue: Record<string, unknown> = typeof element.scopeValue === 'object'258? element.scopeValue ?? {}259: {};260261const data = element.isConfigured ?262{ ...elementDefaultValue, ...elementScopeValue } :263elementDefaultValue;264265const { objectProperties } = element.setting;266const displayValues: IBoolObjectDataItem[] = [];267for (const key in objectProperties) {268const defaultValue = elementDefaultValue[key];269270// Get source if it's a default value271let source: string | undefined;272if (defaultValue === data[key] && element.setting.type === 'object' && element.defaultValueSource instanceof Map) {273const defaultSource = element.defaultValueSource.get(key);274source = typeof defaultSource === 'string' ? defaultSource : defaultSource?.displayName;275}276277displayValues.push({278key: {279type: 'string',280data: key281},282value: {283type: 'boolean',284data: !!data[key]285},286keyDescription: objectProperties[key].description,287removable: false,288resetable: true,289source290});291}292return displayValues;293}294295function createArraySuggester(element: SettingsTreeSettingElement): IObjectKeySuggester {296return (keys, idx) => {297const enumOptions: IObjectEnumOption[] = [];298299if (element.setting.enum) {300element.setting.enum.forEach((key, i) => {301// include the currently selected value, even if uniqueItems is true302if (!element.setting.uniqueItems || (idx !== undefined && key === keys[idx]) || !keys.includes(key)) {303const description = element.setting.enumDescriptions?.[i];304enumOptions.push({ value: key, description });305}306});307}308309return enumOptions.length > 0310? { type: 'enum', data: enumOptions[0].value, options: enumOptions }311: undefined;312};313}314315function createObjectKeySuggester(element: SettingsTreeSettingElement): IObjectKeySuggester {316const { objectProperties } = element.setting;317const allStaticKeys = Object.keys(objectProperties ?? {});318319return keys => {320const existingKeys = new Set(keys);321const enumOptions: IObjectEnumOption[] = [];322323allStaticKeys.forEach(staticKey => {324if (!existingKeys.has(staticKey)) {325enumOptions.push({ value: staticKey, description: objectProperties![staticKey].description });326}327});328329return enumOptions.length > 0330? { type: 'enum', data: enumOptions[0].value, options: enumOptions }331: undefined;332};333}334335function createObjectValueSuggester(element: SettingsTreeSettingElement): IObjectValueSuggester {336const { objectProperties, objectPatternProperties, objectAdditionalProperties } = element.setting;337338const patternsAndSchemas = Object339.entries(objectPatternProperties ?? {})340.map(([pattern, schema]) => ({341pattern: new RegExp(pattern),342schema343}));344345return (key: string) => {346let suggestedSchema: IJSONSchema | undefined;347348if (isDefined(objectProperties) && key in objectProperties) {349suggestedSchema = objectProperties[key];350}351352const patternSchema = suggestedSchema ?? patternsAndSchemas.find(({ pattern }) => pattern.test(key))?.schema;353354if (isDefined(patternSchema)) {355suggestedSchema = patternSchema;356} else if (isDefined(objectAdditionalProperties) && typeof objectAdditionalProperties === 'object') {357suggestedSchema = objectAdditionalProperties;358}359360if (isDefined(suggestedSchema)) {361const type = getObjectValueType(suggestedSchema);362363if (type === 'boolean') {364return { type, data: suggestedSchema.default ?? true };365} else if (type === 'enum') {366const options = getEnumOptionsFromSchema(suggestedSchema);367return { type, data: suggestedSchema.default ?? options[0].value, options };368} else {369return { type, data: suggestedSchema.default ?? '' };370}371}372373return;374};375}376377function isNonNullableNumericType(type: unknown): type is 'number' | 'integer' {378return type === 'number' || type === 'integer';379}380381function parseNumericObjectValues(dataElement: SettingsTreeSettingElement, v: Record<string, unknown>): Record<string, unknown> {382const newRecord: Record<string, unknown> = {};383for (const key in v) {384// Set to true/false once we're sure of the answer385let keyMatchesNumericProperty: boolean | undefined;386const patternProperties = dataElement.setting.objectPatternProperties;387const properties = dataElement.setting.objectProperties;388const additionalProperties = dataElement.setting.objectAdditionalProperties;389390// Match the current record key against the properties of the object391if (properties) {392for (const propKey in properties) {393if (propKey === key) {394keyMatchesNumericProperty = isNonNullableNumericType(properties[propKey].type);395break;396}397}398}399if (keyMatchesNumericProperty === undefined && patternProperties) {400for (const patternKey in patternProperties) {401if (key.match(patternKey)) {402keyMatchesNumericProperty = isNonNullableNumericType(patternProperties[patternKey].type);403break;404}405}406}407if (keyMatchesNumericProperty === undefined && additionalProperties && typeof additionalProperties !== 'boolean') {408if (isNonNullableNumericType(additionalProperties.type)) {409keyMatchesNumericProperty = true;410}411}412newRecord[key] = keyMatchesNumericProperty ? Number(v[key]) : v[key];413}414return newRecord;415}416417function getListDisplayValue(element: SettingsTreeSettingElement): IListDataItem[] {418if (!element.value || !Array.isArray(element.value)) {419return [];420}421422if (element.setting.arrayItemType === 'enum') {423let enumOptions: IObjectEnumOption[] = [];424if (element.setting.enum) {425enumOptions = element.setting.enum.map((setting, i) => {426return {427value: setting,428description: element.setting.enumDescriptions?.[i]429};430});431}432return element.value.map((key: string) => {433return {434value: {435type: 'enum',436data: key,437options: enumOptions438}439};440});441} else {442return element.value.map((key: string) => {443return {444value: {445type: 'string',446data: key447}448};449});450}451}452453function getShowAddButtonList(dataElement: SettingsTreeSettingElement, listDisplayValue: IListDataItem[]): boolean {454if (dataElement.setting.enum && dataElement.setting.uniqueItems) {455return dataElement.setting.enum.length - listDisplayValue.length > 0;456} else {457return true;458}459}460461export function resolveSettingsTree(tocData: ITOCEntry<string>, coreSettingsGroups: ISettingsGroup[], logService: ILogService): { tree: ITOCEntry<ISetting>; leftoverSettings: Set<ISetting> } {462const allSettings = getFlatSettings(coreSettingsGroups);463return {464tree: _resolveSettingsTree(tocData, allSettings, logService),465leftoverSettings: allSettings466};467}468469export function resolveConfiguredUntrustedSettings(groups: ISettingsGroup[], target: SettingsTarget, languageFilter: string | undefined, configurationService: IWorkbenchConfigurationService): ISetting[] {470const allSettings = getFlatSettings(groups);471return [...allSettings].filter(setting => setting.restricted && inspectSetting(setting.key, target, languageFilter, configurationService).isConfigured);472}473474export async function createTocTreeForExtensionSettings(extensionService: IExtensionService, groups: ISettingsGroup[]): Promise<ITOCEntry<ISetting>> {475const extGroupTree = new Map<string, ITOCEntry<ISetting>>();476const addEntryToTree = (extensionId: string, extensionName: string, childEntry: ITOCEntry<ISetting>) => {477if (!extGroupTree.has(extensionId)) {478const rootEntry = {479id: extensionId,480label: extensionName,481children: []482};483extGroupTree.set(extensionId, rootEntry);484}485extGroupTree.get(extensionId)!.children!.push(childEntry);486};487const processGroupEntry = async (group: ISettingsGroup) => {488const flatSettings = group.sections.map(section => section.settings).flat();489490const extensionId = group.extensionInfo!.id;491const extension = await extensionService.getExtension(extensionId);492const extensionName = extension?.displayName ?? extension?.name ?? extensionId;493494// There could be multiple groups with the same extension id that all belong to the same extension.495// To avoid highlighting all groups upon expanding the extension's ToC entry,496// use the group ID only if it is non-empty and isn't the extension ID.497// Ref https://github.com/microsoft/vscode/issues/241521.498const settingGroupId = (group.id && group.id !== extensionId) ? group.id : group.title;499500const childEntry: ITOCEntry<ISetting> = {501id: settingGroupId,502label: group.title,503order: group.order,504settings: flatSettings505};506addEntryToTree(extensionId, extensionName, childEntry);507};508509const processPromises = groups.map(g => processGroupEntry(g));510return Promise.all(processPromises).then(() => {511const extGroups: ITOCEntry<ISetting>[] = [];512for (const extensionRootEntry of extGroupTree.values()) {513for (const child of extensionRootEntry.children!) {514// Sort the individual settings of the child by order.515// Leave the undefined order settings untouched.516child.settings?.sort((a, b) => {517return compareTwoNullableNumbers(a.order, b.order);518});519}520521if (extensionRootEntry.children!.length === 1) {522// There is a single category for this extension.523// Push a flattened setting.524extGroups.push({525id: extensionRootEntry.id,526label: extensionRootEntry.children![0].label,527settings: extensionRootEntry.children![0].settings528});529} else {530// Sort the categories.531// Leave the undefined order categories untouched.532extensionRootEntry.children!.sort((a, b) => {533return compareTwoNullableNumbers(a.order, b.order);534});535536// If there is a category that matches the setting name,537// add the settings in manually as "ungrouped" settings.538// https://github.com/microsoft/vscode/issues/137259539const ungroupedChild = extensionRootEntry.children!.find(child => child.label === extensionRootEntry.label);540if (ungroupedChild && !ungroupedChild.children) {541const groupedChildren = extensionRootEntry.children!.filter(child => child !== ungroupedChild);542extGroups.push({543id: extensionRootEntry.id,544label: extensionRootEntry.label,545settings: ungroupedChild.settings,546children: groupedChildren547});548} else {549// Push all the groups as-is.550extGroups.push(extensionRootEntry);551}552}553}554555// Sort the outermost settings.556extGroups.sort((a, b) => a.label.localeCompare(b.label));557558return {559id: 'extensions',560label: localize('extensions', "Extensions"),561children: extGroups562};563});564}565566function _resolveSettingsTree(tocData: ITOCEntry<string>, allSettings: Set<ISetting>, logService: ILogService): ITOCEntry<ISetting> {567let children: ITOCEntry<ISetting>[] | undefined;568if (tocData.children) {569children = tocData.children570.filter(child => child.hide !== true)571.map(child => _resolveSettingsTree(child, allSettings, logService))572.filter(child => child.children?.length || child.settings?.length);573}574575let settings: ISetting[] | undefined;576if (tocData.settings) {577settings = tocData.settings.map(pattern => getMatchingSettings(allSettings, pattern, logService)).flat();578}579580if (!children && !settings) {581throw new Error(`TOC node has no child groups or settings: ${tocData.id}`);582}583584return {585id: tocData.id,586label: tocData.label,587children,588settings589};590}591592const knownDynamicSettingGroups = [593/^settingsSync\..*/,594/^sync\..*/,595/^workbench.fontAliasing$/,596];597598function getMatchingSettings(allSettings: Set<ISetting>, pattern: string, logService: ILogService): ISetting[] {599const result: ISetting[] = [];600601allSettings.forEach(s => {602if (settingMatches(s, pattern)) {603result.push(s);604allSettings.delete(s);605}606});607608if (!result.length && !knownDynamicSettingGroups.some(r => r.test(pattern))) {609logService.warn(`Settings pattern "${pattern}" doesn't match any settings`);610}611612return result.sort((a, b) => a.key.localeCompare(b.key));613}614615const settingPatternCache = new Map<string, RegExp>();616617export function createSettingMatchRegExp(pattern: string): RegExp {618pattern = escapeRegExpCharacters(pattern)619.replace(/\\\*/g, '.*');620621return new RegExp(`^${pattern}$`, 'i');622}623624function settingMatches(s: ISetting, pattern: string): boolean {625let regExp = settingPatternCache.get(pattern);626if (!regExp) {627regExp = createSettingMatchRegExp(pattern);628settingPatternCache.set(pattern, regExp);629}630631return regExp.test(s.key);632}633634function getFlatSettings(settingsGroups: ISettingsGroup[]) {635const result: Set<ISetting> = new Set();636637for (const group of settingsGroups) {638for (const section of group.sections) {639for (const s of section.settings) {640if (!s.overrides || !s.overrides.length) {641result.add(s);642}643}644}645}646647return result;648}649650interface IDisposableTemplate {651readonly toDispose: DisposableStore;652}653654interface ISettingItemTemplate<T = any> extends IDisposableTemplate {655onChange?: (value: T) => void;656657context?: SettingsTreeSettingElement;658containerElement: HTMLElement;659categoryElement: HTMLElement;660labelElement: SimpleIconLabel;661descriptionElement: HTMLElement;662controlElement: HTMLElement;663deprecationWarningElement: HTMLElement;664indicatorsLabel: SettingsTreeIndicatorsLabel;665toolbar: ToolBar;666readonly elementDisposables: DisposableStore;667}668669interface ISettingBoolItemTemplate extends ISettingItemTemplate<boolean> {670checkbox: Toggle;671}672673interface ISettingExtensionToggleItemTemplate extends ISettingItemTemplate<undefined> {674actionButton: Button;675dismissButton: Button;676}677678interface ISettingTextItemTemplate extends ISettingItemTemplate<string> {679inputBox: InputBox;680validationErrorMessageElement: HTMLElement;681}682683type ISettingNumberItemTemplate = ISettingTextItemTemplate;684685interface ISettingEnumItemTemplate extends ISettingItemTemplate<number> {686selectBox: SelectBox;687selectElement: HTMLSelectElement | null;688enumDescriptionElement: HTMLElement;689}690691interface ISettingComplexItemTemplate extends ISettingItemTemplate<void> {692button: HTMLElement;693validationErrorMessageElement: HTMLElement;694}695696interface ISettingComplexObjectItemTemplate extends ISettingComplexItemTemplate {697objectSettingWidget: ObjectSettingDropdownWidget;698}699700interface ISettingListItemTemplate extends ISettingItemTemplate<string[] | undefined> {701listWidget: ListSettingWidget<IListDataItem>;702validationErrorMessageElement: HTMLElement;703}704705interface ISettingIncludeExcludeItemTemplate extends ISettingItemTemplate<void> {706includeExcludeWidget: ListSettingWidget<IIncludeExcludeDataItem>;707}708709interface ISettingObjectItemTemplate extends ISettingItemTemplate<Record<string, unknown> | undefined> {710objectDropdownWidget?: ObjectSettingDropdownWidget;711objectCheckboxWidget?: ObjectSettingCheckboxWidget;712validationErrorMessageElement: HTMLElement;713}714715interface ISettingNewExtensionsTemplate extends IDisposableTemplate {716button: Button;717context?: SettingsTreeNewExtensionsElement;718}719720interface IGroupTitleTemplate extends IDisposableTemplate {721context?: SettingsTreeGroupElement;722parent: HTMLElement;723}724725const SETTINGS_TEXT_TEMPLATE_ID = 'settings.text.template';726const SETTINGS_MULTILINE_TEXT_TEMPLATE_ID = 'settings.multilineText.template';727const SETTINGS_NUMBER_TEMPLATE_ID = 'settings.number.template';728const SETTINGS_ENUM_TEMPLATE_ID = 'settings.enum.template';729const SETTINGS_BOOL_TEMPLATE_ID = 'settings.bool.template';730const SETTINGS_ARRAY_TEMPLATE_ID = 'settings.array.template';731const SETTINGS_EXCLUDE_TEMPLATE_ID = 'settings.exclude.template';732const SETTINGS_INCLUDE_TEMPLATE_ID = 'settings.include.template';733const SETTINGS_OBJECT_TEMPLATE_ID = 'settings.object.template';734const SETTINGS_BOOL_OBJECT_TEMPLATE_ID = 'settings.boolObject.template';735const SETTINGS_COMPLEX_TEMPLATE_ID = 'settings.complex.template';736const SETTINGS_COMPLEX_OBJECT_TEMPLATE_ID = 'settings.complexObject.template';737const SETTINGS_NEW_EXTENSIONS_TEMPLATE_ID = 'settings.newExtensions.template';738const SETTINGS_ELEMENT_TEMPLATE_ID = 'settings.group.template';739const SETTINGS_EXTENSION_TOGGLE_TEMPLATE_ID = 'settings.extensionToggle.template';740741export interface ISettingChangeEvent {742key: string;743value: any; // undefined => reset/unconfigure744type: SettingValueType | SettingValueType[];745manualReset: boolean;746scope: ConfigurationScope | undefined;747}748749export interface ISettingLinkClickEvent {750source: SettingsTreeSettingElement;751targetKey: string;752}753754function removeChildrenFromTabOrder(node: Element): void {755const focusableElements = node.querySelectorAll(`756[tabindex="0"],757input:not([tabindex="-1"]),758select:not([tabindex="-1"]),759textarea:not([tabindex="-1"]),760a:not([tabindex="-1"]),761button:not([tabindex="-1"]),762area:not([tabindex="-1"])763`);764765focusableElements.forEach(element => {766element.setAttribute(AbstractSettingRenderer.ELEMENT_FOCUSABLE_ATTR, 'true');767element.setAttribute('tabindex', '-1');768});769}770771function addChildrenToTabOrder(node: Element): void {772const focusableElements = node.querySelectorAll(773`[${AbstractSettingRenderer.ELEMENT_FOCUSABLE_ATTR}="true"]`774);775776focusableElements.forEach(element => {777element.removeAttribute(AbstractSettingRenderer.ELEMENT_FOCUSABLE_ATTR);778element.setAttribute('tabindex', '0');779});780}781782export interface HeightChangeParams {783element: SettingsTreeElement;784height: number;785}786787export abstract class AbstractSettingRenderer extends Disposable implements ITreeRenderer<SettingsTreeElement, never, any> {788/** To override */789abstract get templateId(): string;790791static readonly CONTROL_CLASS = 'setting-control-focus-target';792static readonly CONTROL_SELECTOR = '.' + this.CONTROL_CLASS;793static readonly CONTENTS_CLASS = 'setting-item-contents';794static readonly CONTENTS_SELECTOR = '.' + this.CONTENTS_CLASS;795static readonly ALL_ROWS_SELECTOR = '.monaco-list-row';796797static readonly SETTING_KEY_ATTR = 'data-key';798static readonly SETTING_ID_ATTR = 'data-id';799static readonly ELEMENT_FOCUSABLE_ATTR = 'data-focusable';800801private readonly _onDidClickOverrideElement = this._register(new Emitter<ISettingOverrideClickEvent>());802readonly onDidClickOverrideElement: Event<ISettingOverrideClickEvent> = this._onDidClickOverrideElement.event;803804protected readonly _onDidChangeSetting = this._register(new Emitter<ISettingChangeEvent>());805readonly onDidChangeSetting: Event<ISettingChangeEvent> = this._onDidChangeSetting.event;806807protected readonly _onDidOpenSettings = this._register(new Emitter<string>());808readonly onDidOpenSettings: Event<string> = this._onDidOpenSettings.event;809810private readonly _onDidClickSettingLink = this._register(new Emitter<ISettingLinkClickEvent>());811readonly onDidClickSettingLink: Event<ISettingLinkClickEvent> = this._onDidClickSettingLink.event;812813protected readonly _onDidFocusSetting = this._register(new Emitter<SettingsTreeSettingElement>());814readonly onDidFocusSetting: Event<SettingsTreeSettingElement> = this._onDidFocusSetting.event;815816private ignoredSettings: string[];817private readonly _onDidChangeIgnoredSettings = this._register(new Emitter<void>());818readonly onDidChangeIgnoredSettings: Event<void> = this._onDidChangeIgnoredSettings.event;819820protected readonly _onDidChangeSettingHeight = this._register(new Emitter<HeightChangeParams>());821readonly onDidChangeSettingHeight: Event<HeightChangeParams> = this._onDidChangeSettingHeight.event;822823protected readonly _onApplyFilter = this._register(new Emitter<string>());824readonly onApplyFilter: Event<string> = this._onApplyFilter.event;825826private readonly markdownRenderer: MarkdownRenderer;827828constructor(829private readonly settingActions: IAction[],830private readonly disposableActionFactory: (setting: ISetting, settingTarget: SettingsTarget) => IAction[],831@IThemeService protected readonly _themeService: IThemeService,832@IContextViewService protected readonly _contextViewService: IContextViewService,833@IOpenerService protected readonly _openerService: IOpenerService,834@IInstantiationService protected readonly _instantiationService: IInstantiationService,835@ICommandService protected readonly _commandService: ICommandService,836@IContextMenuService protected readonly _contextMenuService: IContextMenuService,837@IKeybindingService protected readonly _keybindingService: IKeybindingService,838@IConfigurationService protected readonly _configService: IConfigurationService,839@IExtensionService protected readonly _extensionsService: IExtensionService,840@IExtensionsWorkbenchService protected readonly _extensionsWorkbenchService: IExtensionsWorkbenchService,841@IProductService protected readonly _productService: IProductService,842@ITelemetryService protected readonly _telemetryService: ITelemetryService,843@IHoverService protected readonly _hoverService: IHoverService,844) {845super();846847this.markdownRenderer = _instantiationService.createInstance(MarkdownRenderer, {});848849this.ignoredSettings = getIgnoredSettings(getDefaultIgnoredSettings(), this._configService);850this._register(this._configService.onDidChangeConfiguration(e => {851this.ignoredSettings = getIgnoredSettings(getDefaultIgnoredSettings(), this._configService);852this._onDidChangeIgnoredSettings.fire();853}));854}855856abstract renderTemplate(container: HTMLElement): any;857858abstract renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: any): void;859860protected renderCommonTemplate(tree: any, _container: HTMLElement, typeClass: string): ISettingItemTemplate {861_container.classList.add('setting-item');862_container.classList.add('setting-item-' + typeClass);863864const toDispose = new DisposableStore();865866const container = DOM.append(_container, $(AbstractSettingRenderer.CONTENTS_SELECTOR));867container.classList.add('settings-row-inner-container');868const titleElement = DOM.append(container, $('.setting-item-title'));869const labelCategoryContainer = DOM.append(titleElement, $('.setting-item-cat-label-container'));870const categoryElement = DOM.append(labelCategoryContainer, $('span.setting-item-category'));871const labelElementContainer = DOM.append(labelCategoryContainer, $('span.setting-item-label'));872const labelElement = toDispose.add(new SimpleIconLabel(labelElementContainer));873const indicatorsLabel = toDispose.add(this._instantiationService.createInstance(SettingsTreeIndicatorsLabel, titleElement));874875const descriptionElement = DOM.append(container, $('.setting-item-description'));876const modifiedIndicatorElement = DOM.append(container, $('.setting-item-modified-indicator'));877toDispose.add(this._hoverService.setupDelayedHover(modifiedIndicatorElement, {878content: localize('modified', "The setting has been configured in the current scope.")879}));880881const valueElement = DOM.append(container, $('.setting-item-value'));882const controlElement = DOM.append(valueElement, $('div.setting-item-control'));883884const deprecationWarningElement = DOM.append(container, $('.setting-item-deprecation-message'));885886const toolbarContainer = DOM.append(container, $('.setting-toolbar-container'));887const toolbar = this.renderSettingToolbar(toolbarContainer);888889const template: ISettingItemTemplate = {890toDispose,891elementDisposables: toDispose.add(new DisposableStore()),892893containerElement: container,894categoryElement,895labelElement,896descriptionElement,897controlElement,898deprecationWarningElement,899indicatorsLabel,900toolbar901};902903// Prevent clicks from being handled by list904toDispose.add(DOM.addDisposableListener(controlElement, DOM.EventType.MOUSE_DOWN, e => e.stopPropagation()));905906toDispose.add(DOM.addDisposableListener(titleElement, DOM.EventType.MOUSE_ENTER, e => container.classList.add('mouseover')));907toDispose.add(DOM.addDisposableListener(titleElement, DOM.EventType.MOUSE_LEAVE, e => container.classList.remove('mouseover')));908909return template;910}911912protected addSettingElementFocusHandler(template: ISettingItemTemplate): void {913const focusTracker = DOM.trackFocus(template.containerElement);914template.toDispose.add(focusTracker);915template.toDispose.add(focusTracker.onDidBlur(() => {916if (template.containerElement.classList.contains('focused')) {917template.containerElement.classList.remove('focused');918}919}));920921template.toDispose.add(focusTracker.onDidFocus(() => {922template.containerElement.classList.add('focused');923924if (template.context) {925this._onDidFocusSetting.fire(template.context);926}927}));928}929930protected renderSettingToolbar(container: HTMLElement): ToolBar {931const toggleMenuKeybinding = this._keybindingService.lookupKeybinding(SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU);932let toggleMenuTitle = localize('settingsContextMenuTitle', "More Actions... ");933if (toggleMenuKeybinding) {934toggleMenuTitle += ` (${toggleMenuKeybinding && toggleMenuKeybinding.getLabel()})`;935}936937const toolbar = new ToolBar(container, this._contextMenuService, {938toggleMenuTitle,939renderDropdownAsChildElement: !isIOS,940moreIcon: settingsMoreActionIcon941});942return toolbar;943}944945protected renderSettingElement(node: ITreeNode<SettingsTreeSettingElement, never>, index: number, template: ISettingItemTemplate | ISettingBoolItemTemplate): void {946const element = node.element;947948// The element must inspect itself to get information for949// the modified indicator and the overridden Settings indicators.950element.inspectSelf();951952template.context = element;953template.toolbar.context = element;954const actions = this.disposableActionFactory(element.setting, element.settingsTarget);955actions.forEach(a => isDisposable(a) && template.elementDisposables.add(a));956template.toolbar.setActions([], [...this.settingActions, ...actions]);957958const setting = element.setting;959960template.containerElement.classList.toggle('is-configured', element.isConfigured);961template.containerElement.setAttribute(AbstractSettingRenderer.SETTING_KEY_ATTR, element.setting.key);962template.containerElement.setAttribute(AbstractSettingRenderer.SETTING_ID_ATTR, element.id);963964const titleTooltip = setting.key + (element.isConfigured ? ' - Modified' : '');965template.categoryElement.textContent = element.displayCategory ? (element.displayCategory + ': ') : '';966template.elementDisposables.add(this._hoverService.setupDelayedHover(template.categoryElement, { content: titleTooltip }));967968template.labelElement.text = element.displayLabel;969template.labelElement.title = titleTooltip;970971template.descriptionElement.innerText = '';972if (element.setting.descriptionIsMarkdown) {973const renderedDescription = this.renderSettingMarkdown(element, template.containerElement, element.description, template.elementDisposables);974template.descriptionElement.appendChild(renderedDescription);975} else {976template.descriptionElement.innerText = element.description;977}978979template.indicatorsLabel.updateScopeOverrides(element, this._onDidClickOverrideElement, this._onApplyFilter);980template.elementDisposables.add(this._configService.onDidChangeConfiguration(e => {981if (e.affectsConfiguration(APPLY_ALL_PROFILES_SETTING)) {982template.indicatorsLabel.updateScopeOverrides(element, this._onDidClickOverrideElement, this._onApplyFilter);983}984}));985986const onChange = (value: any) => this._onDidChangeSetting.fire({987key: element.setting.key,988value,989type: template.context!.valueType,990manualReset: false,991scope: element.setting.scope992});993const deprecationText = element.setting.deprecationMessage || '';994if (deprecationText && element.setting.deprecationMessageIsMarkdown) {995template.deprecationWarningElement.innerText = '';996template.deprecationWarningElement.appendChild(this.renderSettingMarkdown(element, template.containerElement, element.setting.deprecationMessage!, template.elementDisposables));997} else {998template.deprecationWarningElement.innerText = deprecationText;999}1000template.deprecationWarningElement.prepend($('.codicon.codicon-error'));1001template.containerElement.classList.toggle('is-deprecated', !!deprecationText);10021003this.renderValue(element, <ISettingItemTemplate>template, onChange);10041005template.indicatorsLabel.updateWorkspaceTrust(element);1006template.indicatorsLabel.updateSyncIgnored(element, this.ignoredSettings);1007template.indicatorsLabel.updateDefaultOverrideIndicator(element);1008template.indicatorsLabel.updatePreviewIndicator(element);1009template.elementDisposables.add(this.onDidChangeIgnoredSettings(() => {1010template.indicatorsLabel.updateSyncIgnored(element, this.ignoredSettings);1011}));10121013this.updateSettingTabbable(element, template);1014template.elementDisposables.add(element.onDidChangeTabbable(() => {1015this.updateSettingTabbable(element, template);1016}));1017}10181019private updateSettingTabbable(element: SettingsTreeSettingElement, template: ISettingItemTemplate | ISettingBoolItemTemplate): void {1020if (element.tabbable) {1021addChildrenToTabOrder(template.containerElement);1022} else {1023removeChildrenFromTabOrder(template.containerElement);1024}1025}10261027private renderSettingMarkdown(element: SettingsTreeSettingElement, container: HTMLElement, text: string, disposables: DisposableStore): HTMLElement {1028// Rewrite `#editor.fontSize#` to link format1029text = fixSettingLinks(text);10301031const renderedMarkdown = disposables.add(this.markdownRenderer.render({ value: text, isTrusted: true }, {1032actionHandler: (content: string) => {1033if (content.startsWith('#')) {1034const e: ISettingLinkClickEvent = {1035source: element,1036targetKey: content.substring(1)1037};1038this._onDidClickSettingLink.fire(e);1039} else {1040this._openerService.open(content, { allowCommands: true }).catch(onUnexpectedError);1041}1042},1043asyncRenderCallback: () => {1044const height = container.clientHeight;1045if (height) {1046this._onDidChangeSettingHeight.fire({ element, height });1047}1048},1049}));10501051renderedMarkdown.element.classList.add('setting-item-markdown');1052cleanRenderedMarkdown(renderedMarkdown.element);1053return renderedMarkdown.element;1054}10551056protected abstract renderValue(dataElement: SettingsTreeSettingElement, template: ISettingItemTemplate, onChange: (value: any) => void): void;10571058disposeTemplate(template: IDisposableTemplate): void {1059template.toDispose.dispose();1060}10611062disposeElement(_element: ITreeNode<SettingsTreeElement>, _index: number, template: IDisposableTemplate): void {1063(template as ISettingItemTemplate).elementDisposables?.clear();1064}1065}10661067class SettingGroupRenderer implements ITreeRenderer<SettingsTreeGroupElement, never, IGroupTitleTemplate> {1068templateId = SETTINGS_ELEMENT_TEMPLATE_ID;10691070renderTemplate(container: HTMLElement): IGroupTitleTemplate {1071container.classList.add('group-title');10721073const template: IGroupTitleTemplate = {1074parent: container,1075toDispose: new DisposableStore()1076};10771078return template;1079}10801081renderElement(element: ITreeNode<SettingsTreeGroupElement, never>, index: number, templateData: IGroupTitleTemplate): void {1082templateData.parent.innerText = '';1083const labelElement = DOM.append(templateData.parent, $('div.settings-group-title-label.settings-row-inner-container'));1084labelElement.classList.add(`settings-group-level-${element.element.level}`);1085labelElement.textContent = element.element.label;10861087if (element.element.isFirstGroup) {1088labelElement.classList.add('settings-group-first');1089}1090}10911092disposeTemplate(templateData: IGroupTitleTemplate): void {1093templateData.toDispose.dispose();1094}1095}10961097export class SettingNewExtensionsRenderer implements ITreeRenderer<SettingsTreeNewExtensionsElement, never, ISettingNewExtensionsTemplate> {1098templateId = SETTINGS_NEW_EXTENSIONS_TEMPLATE_ID;10991100constructor(1101@ICommandService private readonly _commandService: ICommandService,1102) {1103}11041105renderTemplate(container: HTMLElement): ISettingNewExtensionsTemplate {1106const toDispose = new DisposableStore();11071108container.classList.add('setting-item-new-extensions');11091110const button = new Button(container, { title: true, ...defaultButtonStyles });1111toDispose.add(button);1112toDispose.add(button.onDidClick(() => {1113if (template.context) {1114this._commandService.executeCommand('workbench.extensions.action.showExtensionsWithIds', template.context.extensionIds);1115}1116}));1117button.label = localize('newExtensionsButtonLabel', "Show matching extensions");1118button.element.classList.add('settings-new-extensions-button');11191120const template: ISettingNewExtensionsTemplate = {1121button,1122toDispose1123};11241125return template;1126}11271128renderElement(element: ITreeNode<SettingsTreeNewExtensionsElement, never>, index: number, templateData: ISettingNewExtensionsTemplate): void {1129templateData.context = element.element;1130}11311132disposeTemplate(template: IDisposableTemplate): void {1133template.toDispose.dispose();1134}1135}11361137export class SettingComplexRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingComplexItemTemplate> {1138private static readonly EDIT_IN_JSON_LABEL = localize('editInSettingsJson', "Edit in settings.json");11391140templateId = SETTINGS_COMPLEX_TEMPLATE_ID;11411142renderTemplate(container: HTMLElement): ISettingComplexItemTemplate {1143const common = this.renderCommonTemplate(null, container, 'complex');11441145const openSettingsButton = DOM.append(common.controlElement, $('a.edit-in-settings-button'));1146openSettingsButton.classList.add(AbstractSettingRenderer.CONTROL_CLASS);1147openSettingsButton.role = 'button';11481149const validationErrorMessageElement = $('.setting-item-validation-message');1150common.containerElement.appendChild(validationErrorMessageElement);11511152const template: ISettingComplexItemTemplate = {1153...common,1154button: openSettingsButton,1155validationErrorMessageElement1156};11571158this.addSettingElementFocusHandler(template);11591160return template;1161}11621163renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingComplexItemTemplate): void {1164super.renderSettingElement(element, index, templateData);1165}11661167protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingComplexItemTemplate, onChange: (value: string) => void): void {1168const plainKey = getLanguageTagSettingPlainKey(dataElement.setting.key);1169const editLanguageSettingLabel = localize('editLanguageSettingLabel', "Edit settings for {0}", plainKey);1170const isLanguageTagSetting = dataElement.setting.isLanguageTagSetting;1171template.button.textContent = isLanguageTagSetting1172? editLanguageSettingLabel1173: SettingComplexRenderer.EDIT_IN_JSON_LABEL;11741175const onClickOrKeydown = (e: UIEvent) => {1176if (isLanguageTagSetting) {1177this._onApplyFilter.fire(`@${LANGUAGE_SETTING_TAG}${plainKey.replaceAll(' ', '')}`);1178} else {1179this._onDidOpenSettings.fire(dataElement.setting.key);1180}1181e.preventDefault();1182e.stopPropagation();1183};1184template.elementDisposables.add(DOM.addDisposableListener(template.button, DOM.EventType.CLICK, (e) => {1185onClickOrKeydown(e);1186}));1187template.elementDisposables.add(DOM.addDisposableListener(template.button, DOM.EventType.KEY_DOWN, (e) => {1188const ev = new StandardKeyboardEvent(e);1189if (ev.equals(KeyCode.Space) || ev.equals(KeyCode.Enter)) {1190onClickOrKeydown(e);1191}1192}));11931194this.renderValidations(dataElement, template);11951196if (isLanguageTagSetting) {1197template.button.setAttribute('aria-label', editLanguageSettingLabel);1198} else {1199template.button.setAttribute('aria-label', `${SettingComplexRenderer.EDIT_IN_JSON_LABEL}: ${dataElement.setting.key}`);1200}1201}12021203private renderValidations(dataElement: SettingsTreeSettingElement, template: ISettingComplexItemTemplate) {1204const errMsg = dataElement.isConfigured && getInvalidTypeError(dataElement.value, dataElement.setting.type);1205if (errMsg) {1206template.containerElement.classList.add('invalid-input');1207template.validationErrorMessageElement.innerText = errMsg;1208return;1209}12101211template.containerElement.classList.remove('invalid-input');1212}1213}12141215class SettingComplexObjectRenderer extends SettingComplexRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingComplexObjectItemTemplate> {12161217override templateId = SETTINGS_COMPLEX_OBJECT_TEMPLATE_ID;12181219override renderTemplate(container: HTMLElement): ISettingComplexObjectItemTemplate {1220const common = this.renderCommonTemplate(null, container, 'list');12211222const objectSettingWidget = common.toDispose.add(this._instantiationService.createInstance(ObjectSettingDropdownWidget, common.controlElement));1223objectSettingWidget.domNode.classList.add(AbstractSettingRenderer.CONTROL_CLASS);12241225const openSettingsButton = DOM.append(DOM.append(common.controlElement, $('.complex-object-edit-in-settings-button-container')), $('a.complex-object.edit-in-settings-button'));1226openSettingsButton.classList.add(AbstractSettingRenderer.CONTROL_CLASS);1227openSettingsButton.role = 'button';12281229const validationErrorMessageElement = $('.setting-item-validation-message');1230common.containerElement.appendChild(validationErrorMessageElement);12311232const template: ISettingComplexObjectItemTemplate = {1233...common,1234button: openSettingsButton,1235validationErrorMessageElement,1236objectSettingWidget1237};12381239this.addSettingElementFocusHandler(template);12401241return template;1242}12431244protected override renderValue(dataElement: SettingsTreeSettingElement, template: ISettingComplexObjectItemTemplate, onChange: (value: string) => void): void {1245const items = getObjectDisplayValue(dataElement);1246template.objectSettingWidget.setValue(items, {1247settingKey: dataElement.setting.key,1248showAddButton: false,1249isReadOnly: true,1250});1251template.button.parentElement?.classList.toggle('hide', dataElement.hasPolicyValue);1252super.renderValue(dataElement, template, onChange);1253}1254}12551256class SettingArrayRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingListItemTemplate> {1257templateId = SETTINGS_ARRAY_TEMPLATE_ID;12581259renderTemplate(container: HTMLElement): ISettingListItemTemplate {1260const common = this.renderCommonTemplate(null, container, 'list');1261const descriptionElement = common.containerElement.querySelector('.setting-item-description')!;1262const validationErrorMessageElement = $('.setting-item-validation-message');1263descriptionElement.after(validationErrorMessageElement);12641265const listWidget = this._instantiationService.createInstance(ListSettingWidget, common.controlElement);1266listWidget.domNode.classList.add(AbstractSettingRenderer.CONTROL_CLASS);1267common.toDispose.add(listWidget);12681269const template: ISettingListItemTemplate = {1270...common,1271listWidget,1272validationErrorMessageElement1273};12741275this.addSettingElementFocusHandler(template);12761277common.toDispose.add(1278listWidget.onDidChangeList(e => {1279const newList = this.computeNewList(template, e);1280template.onChange?.(newList);1281})1282);12831284return template;1285}12861287private computeNewList(template: ISettingListItemTemplate, e: SettingListEvent<IListDataItem>): string[] | undefined {1288if (template.context) {1289let newValue: string[] = [];1290if (Array.isArray(template.context.scopeValue)) {1291newValue = [...template.context.scopeValue];1292} else if (Array.isArray(template.context.value)) {1293newValue = [...template.context.value];1294}12951296if (e.type === 'move') {1297// A drag and drop occurred1298const sourceIndex = e.sourceIndex;1299const targetIndex = e.targetIndex;1300const splicedElem = newValue.splice(sourceIndex, 1)[0];1301newValue.splice(targetIndex, 0, splicedElem);1302} else if (e.type === 'remove' || e.type === 'reset') {1303newValue.splice(e.targetIndex, 1);1304} else if (e.type === 'change') {1305const itemValueData = e.newItem.value.data.toString();13061307// Update value1308if (e.targetIndex > -1) {1309newValue[e.targetIndex] = itemValueData;1310}1311// For some reason, we are updating and cannot find original value1312// Just append the value in this case1313else {1314newValue.push(itemValueData);1315}1316} else if (e.type === 'add') {1317newValue.push(e.newItem.value.data.toString());1318}13191320if (1321template.context.defaultValue &&1322Array.isArray(template.context.defaultValue) &&1323template.context.defaultValue.length === newValue.length &&1324template.context.defaultValue.join() === newValue.join()1325) {1326return undefined;1327}1328return newValue;1329}13301331return undefined;1332}13331334renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingListItemTemplate): void {1335super.renderSettingElement(element, index, templateData);1336}13371338protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingListItemTemplate, onChange: (value: string[] | number[] | undefined) => void): void {1339const value = getListDisplayValue(dataElement);1340const keySuggester = dataElement.setting.enum ? createArraySuggester(dataElement) : undefined;1341template.listWidget.setValue(value, {1342showAddButton: getShowAddButtonList(dataElement, value),1343keySuggester1344});1345template.context = dataElement;13461347template.elementDisposables.add(toDisposable(() => {1348template.listWidget.cancelEdit();1349}));13501351template.onChange = (v: string[] | undefined) => {1352if (v && !renderArrayValidations(dataElement, template, v, false)) {1353const itemType = dataElement.setting.arrayItemType;1354const arrToSave = isNonNullableNumericType(itemType) ? v.map(a => +a) : v;1355onChange(arrToSave);1356} else {1357// Save the setting unparsed and containing the errors.1358// renderArrayValidations will render relevant error messages.1359onChange(v);1360}1361};13621363renderArrayValidations(dataElement, template, value.map(v => v.value.data.toString()), true);1364}1365}13661367abstract class AbstractSettingObjectRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingObjectItemTemplate> {13681369protected renderTemplateWithWidget(common: ISettingItemTemplate, widget: ObjectSettingCheckboxWidget | ObjectSettingDropdownWidget): ISettingObjectItemTemplate {1370widget.domNode.classList.add(AbstractSettingRenderer.CONTROL_CLASS);1371common.toDispose.add(widget);13721373const descriptionElement = common.containerElement.querySelector('.setting-item-description')!;1374const validationErrorMessageElement = $('.setting-item-validation-message');1375descriptionElement.after(validationErrorMessageElement);13761377const template: ISettingObjectItemTemplate = {1378...common,1379validationErrorMessageElement1380};1381if (widget instanceof ObjectSettingCheckboxWidget) {1382template.objectCheckboxWidget = widget;1383} else {1384template.objectDropdownWidget = widget;1385}13861387this.addSettingElementFocusHandler(template);1388return template;1389}13901391renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingObjectItemTemplate): void {1392super.renderSettingElement(element, index, templateData);1393}1394}13951396class SettingObjectRenderer extends AbstractSettingObjectRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingObjectItemTemplate> {1397override templateId = SETTINGS_OBJECT_TEMPLATE_ID;13981399renderTemplate(container: HTMLElement): ISettingObjectItemTemplate {1400const common = this.renderCommonTemplate(null, container, 'list');1401const widget = this._instantiationService.createInstance(ObjectSettingDropdownWidget, common.controlElement);1402const template = this.renderTemplateWithWidget(common, widget);1403common.toDispose.add(widget.onDidChangeList(e => {1404this.onDidChangeObject(template, e);1405}));1406return template;1407}14081409private onDidChangeObject(template: ISettingObjectItemTemplate, e: SettingListEvent<IObjectDataItem>): void {1410const widget = template.objectDropdownWidget!;1411if (template.context) {1412const settingSupportsRemoveDefault = objectSettingSupportsRemoveDefaultValue(template.context.setting.key);1413const defaultValue: Record<string, unknown> = typeof template.context.defaultValue === 'object'1414? template.context.defaultValue ?? {}1415: {};14161417const scopeValue: Record<string, unknown> = typeof template.context.scopeValue === 'object'1418? template.context.scopeValue ?? {}1419: {};14201421const newValue: Record<string, unknown> = { ...template.context.scopeValue }; // Initialize with scoped values as removed default values are not rendered1422const newItems: IObjectDataItem[] = [];14231424widget.items.forEach((item, idx) => {1425// Item was updated1426if ((e.type === 'change' || e.type === 'move') && e.targetIndex === idx) {1427// If the key of the default value is changed, remove the default value1428if (e.originalItem.key.data !== e.newItem.key.data && settingSupportsRemoveDefault && e.originalItem.key.data in defaultValue) {1429newValue[e.originalItem.key.data] = null;1430} else {1431delete newValue[e.originalItem.key.data];1432}1433newValue[e.newItem.key.data] = e.newItem.value.data;1434newItems.push(e.newItem);1435}1436// All remaining items, but skip the one that we just updated1437else if ((e.type !== 'change' && e.type !== 'move') || e.newItem.key.data !== item.key.data) {1438newValue[item.key.data] = item.value.data;1439newItems.push(item);1440}1441});14421443// Item was deleted1444if (e.type === 'remove' || e.type === 'reset') {1445const objectKey = e.originalItem.key.data;1446const removingDefaultValue = e.type === 'remove' && settingSupportsRemoveDefault && defaultValue[objectKey] === e.originalItem.value.data;1447if (removingDefaultValue) {1448newValue[objectKey] = null;1449} else {1450delete newValue[objectKey];1451}14521453const itemToDelete = newItems.findIndex(item => item.key.data === objectKey);1454const defaultItemValue = defaultValue[objectKey] as string | boolean;14551456// Item does not have a default or default is bing removed1457if (removingDefaultValue || isUndefinedOrNull(defaultValue[objectKey]) && itemToDelete > -1) {1458newItems.splice(itemToDelete, 1);1459} else if (!removingDefaultValue && itemToDelete > -1) {1460newItems[itemToDelete].value.data = defaultItemValue;1461}1462}1463// New item was added1464else if (e.type === 'add') {1465newValue[e.newItem.key.data] = e.newItem.value.data;1466newItems.push(e.newItem);1467}14681469Object.entries(newValue).forEach(([key, value]) => {1470// value from the scope has changed back to the default1471if (scopeValue[key] !== value && defaultValue[key] === value && !(settingSupportsRemoveDefault && value === null)) {1472delete newValue[key];1473}1474});14751476const newObject = Object.keys(newValue).length === 0 ? undefined : newValue;1477template.objectDropdownWidget!.setValue(newItems);1478template.onChange?.(newObject);1479}1480}14811482protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingObjectItemTemplate, onChange: (value: Record<string, unknown> | undefined) => void): void {1483const items = getObjectDisplayValue(dataElement);1484const { key, objectProperties, objectPatternProperties, objectAdditionalProperties } = dataElement.setting;14851486template.objectDropdownWidget!.setValue(items, {1487settingKey: key,1488showAddButton: objectAdditionalProperties === false1489? (1490!areAllPropertiesDefined(Object.keys(objectProperties ?? {}), items) ||1491isDefined(objectPatternProperties)1492)1493: true,1494keySuggester: createObjectKeySuggester(dataElement),1495valueSuggester: createObjectValueSuggester(dataElement)1496});14971498template.context = dataElement;14991500template.elementDisposables.add(toDisposable(() => {1501template.objectDropdownWidget!.cancelEdit();1502}));15031504template.onChange = (v: Record<string, unknown> | undefined) => {1505if (v && !renderArrayValidations(dataElement, template, v, false)) {1506const parsedRecord = parseNumericObjectValues(dataElement, v);1507onChange(parsedRecord);1508} else {1509// Save the setting unparsed and containing the errors.1510// renderArrayValidations will render relevant error messages.1511onChange(v);1512}1513};1514renderArrayValidations(dataElement, template, dataElement.value, true);1515}1516}15171518class SettingBoolObjectRenderer extends AbstractSettingObjectRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingObjectItemTemplate> {1519override templateId = SETTINGS_BOOL_OBJECT_TEMPLATE_ID;15201521renderTemplate(container: HTMLElement): ISettingObjectItemTemplate {1522const common = this.renderCommonTemplate(null, container, 'list');1523const widget = this._instantiationService.createInstance(ObjectSettingCheckboxWidget, common.controlElement);1524const template = this.renderTemplateWithWidget(common, widget);1525common.toDispose.add(widget.onDidChangeList(e => {1526this.onDidChangeObject(template, e);1527}));1528return template;1529}15301531protected onDidChangeObject(template: ISettingObjectItemTemplate, e: SettingListEvent<IBoolObjectDataItem>): void {1532if (template.context) {1533const widget = template.objectCheckboxWidget!;1534const defaultValue: Record<string, unknown> = typeof template.context.defaultValue === 'object'1535? template.context.defaultValue ?? {}1536: {};15371538const scopeValue: Record<string, unknown> = typeof template.context.scopeValue === 'object'1539? template.context.scopeValue ?? {}1540: {};15411542const newValue: Record<string, unknown> = { ...template.context.scopeValue }; // Initialize with scoped values as removed default values are not rendered1543const newItems: IBoolObjectDataItem[] = [];15441545if (e.type !== 'change') {1546console.warn('Unexpected event type', e.type, 'for bool object setting', template.context.setting.key);1547return;1548}15491550widget.items.forEach((item, idx) => {1551// Item was updated1552if (e.targetIndex === idx) {1553newValue[e.newItem.key.data] = e.newItem.value.data;1554newItems.push(e.newItem);1555}1556// All remaining items, but skip the one that we just updated1557else if (e.newItem.key.data !== item.key.data) {1558newValue[item.key.data] = item.value.data;1559newItems.push(item);1560}1561});15621563Object.entries(newValue).forEach(([key, value]) => {1564// value from the scope has changed back to the default1565if (scopeValue[key] !== value && defaultValue[key] === value) {1566delete newValue[key];1567}1568});15691570const newObject = Object.keys(newValue).length === 0 ? undefined : newValue;1571template.objectCheckboxWidget!.setValue(newItems);1572template.onChange?.(newObject);15731574// Focus this setting explicitly, in case we were previously1575// focused on another setting and clicked a checkbox/value container1576// for this setting.1577this._onDidFocusSetting.fire(template.context);1578}1579}15801581protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingObjectItemTemplate, onChange: (value: Record<string, unknown> | undefined) => void): void {1582const items = getBoolObjectDisplayValue(dataElement);1583const { key } = dataElement.setting;15841585template.objectCheckboxWidget!.setValue(items, {1586settingKey: key1587});15881589template.context = dataElement;1590template.onChange = (v: Record<string, unknown> | undefined) => {1591onChange(v);1592};1593}1594}15951596abstract class SettingIncludeExcludeRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingIncludeExcludeItemTemplate> {15971598protected abstract isExclude(): boolean;15991600renderTemplate(container: HTMLElement): ISettingIncludeExcludeItemTemplate {1601const common = this.renderCommonTemplate(null, container, 'list');16021603const includeExcludeWidget = this._instantiationService.createInstance(this.isExclude() ? ExcludeSettingWidget : IncludeSettingWidget, common.controlElement);1604includeExcludeWidget.domNode.classList.add(AbstractSettingRenderer.CONTROL_CLASS);1605common.toDispose.add(includeExcludeWidget);16061607const template: ISettingIncludeExcludeItemTemplate = {1608...common,1609includeExcludeWidget1610};16111612this.addSettingElementFocusHandler(template);16131614common.toDispose.add(includeExcludeWidget.onDidChangeList(e => this.onDidChangeIncludeExclude(template, e)));16151616return template;1617}16181619private onDidChangeIncludeExclude(template: ISettingIncludeExcludeItemTemplate, e: SettingListEvent<IListDataItem>): void {1620if (template.context) {1621const newValue = { ...template.context.scopeValue };16221623// first delete the existing entry, if present1624if (e.type !== 'add') {1625if (e.originalItem.value.data.toString() in template.context.defaultValue) {1626// delete a default by overriding it1627newValue[e.originalItem.value.data.toString()] = false;1628} else {1629delete newValue[e.originalItem.value.data.toString()];1630}1631}16321633// then add the new or updated entry, if present1634if (e.type === 'change' || e.type === 'add' || e.type === 'move') {1635if (e.newItem.value.data.toString() in template.context.defaultValue && !e.newItem.sibling) {1636// add a default by deleting its override1637delete newValue[e.newItem.value.data.toString()];1638} else {1639newValue[e.newItem.value.data.toString()] = e.newItem.sibling ? { when: e.newItem.sibling } : true;1640}1641}16421643function sortKeys<T extends object>(obj: T) {1644const sortedKeys = Object.keys(obj)1645.sort((a, b) => a.localeCompare(b)) as Array<keyof T>;16461647const retVal: Partial<T> = {};1648for (const key of sortedKeys) {1649retVal[key] = obj[key];1650}1651return retVal;1652}16531654this._onDidChangeSetting.fire({1655key: template.context.setting.key,1656value: Object.keys(newValue).length === 0 ? undefined : sortKeys(newValue),1657type: template.context.valueType,1658manualReset: false,1659scope: template.context.setting.scope1660});1661}1662}16631664renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingIncludeExcludeItemTemplate): void {1665super.renderSettingElement(element, index, templateData);1666}16671668protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingIncludeExcludeItemTemplate, onChange: (value: string) => void): void {1669const value = getIncludeExcludeDisplayValue(dataElement);1670template.includeExcludeWidget.setValue(value);1671template.context = dataElement;1672template.elementDisposables.add(toDisposable(() => {1673template.includeExcludeWidget.cancelEdit();1674}));1675}1676}16771678class SettingExcludeRenderer extends SettingIncludeExcludeRenderer {1679templateId = SETTINGS_EXCLUDE_TEMPLATE_ID;16801681protected override isExclude(): boolean {1682return true;1683}1684}16851686class SettingIncludeRenderer extends SettingIncludeExcludeRenderer {1687templateId = SETTINGS_INCLUDE_TEMPLATE_ID;16881689protected override isExclude(): boolean {1690return false;1691}1692}16931694const settingsInputBoxStyles = getInputBoxStyle({1695inputBackground: settingsTextInputBackground,1696inputForeground: settingsTextInputForeground,1697inputBorder: settingsTextInputBorder1698});16991700abstract class AbstractSettingTextRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingTextItemTemplate> {1701private readonly MULTILINE_MAX_HEIGHT = 150;17021703renderTemplate(_container: HTMLElement, useMultiline?: boolean): ISettingTextItemTemplate {1704const common = this.renderCommonTemplate(null, _container, 'text');1705const validationErrorMessageElement = DOM.append(common.containerElement, $('.setting-item-validation-message'));17061707const inputBoxOptions: IInputOptions = {1708flexibleHeight: useMultiline,1709flexibleWidth: false,1710flexibleMaxHeight: this.MULTILINE_MAX_HEIGHT,1711inputBoxStyles: settingsInputBoxStyles1712};1713const inputBox = new InputBox(common.controlElement, this._contextViewService, inputBoxOptions);1714common.toDispose.add(inputBox);1715common.toDispose.add(1716inputBox.onDidChange(e => {1717template.onChange?.(e);1718}));1719common.toDispose.add(inputBox);1720inputBox.inputElement.classList.add(AbstractSettingRenderer.CONTROL_CLASS);1721inputBox.inputElement.tabIndex = 0;17221723const template: ISettingTextItemTemplate = {1724...common,1725inputBox,1726validationErrorMessageElement1727};17281729this.addSettingElementFocusHandler(template);17301731return template;1732}17331734renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingTextItemTemplate): void {1735super.renderSettingElement(element, index, templateData);1736}17371738protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingTextItemTemplate, onChange: (value: string) => void): void {1739template.onChange = undefined;1740template.inputBox.value = dataElement.value;1741template.inputBox.setAriaLabel(dataElement.setting.key);1742template.onChange = value => {1743if (!renderValidations(dataElement, template, false)) {1744onChange(value);1745}1746};17471748renderValidations(dataElement, template, true);1749}1750}17511752class SettingTextRenderer extends AbstractSettingTextRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingTextItemTemplate> {1753templateId = SETTINGS_TEXT_TEMPLATE_ID;17541755override renderTemplate(_container: HTMLElement): ISettingTextItemTemplate {1756const template = super.renderTemplate(_container, false);17571758// TODO@9at8: listWidget filters out all key events from input boxes, so we need to come up with a better way1759// Disable ArrowUp and ArrowDown behaviour in favor of list navigation1760template.toDispose.add(DOM.addStandardDisposableListener(template.inputBox.inputElement, DOM.EventType.KEY_DOWN, e => {1761if (e.equals(KeyCode.UpArrow) || e.equals(KeyCode.DownArrow)) {1762e.preventDefault();1763}1764}));17651766return template;1767}1768}17691770class SettingMultilineTextRenderer extends AbstractSettingTextRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingTextItemTemplate> {1771templateId = SETTINGS_MULTILINE_TEXT_TEMPLATE_ID;17721773override renderTemplate(_container: HTMLElement): ISettingTextItemTemplate {1774return super.renderTemplate(_container, true);1775}17761777protected override renderValue(dataElement: SettingsTreeSettingElement, template: ISettingTextItemTemplate, onChange: (value: string) => void) {1778const onChangeOverride = (value: string) => {1779// Ensure the model is up to date since a different value will be rendered as different height when probing the height.1780dataElement.value = value;1781onChange(value);1782};1783super.renderValue(dataElement, template, onChangeOverride);1784template.elementDisposables.add(1785template.inputBox.onDidHeightChange(e => {1786const height = template.containerElement.clientHeight;1787// Don't fire event if height is reported as 0,1788// which sometimes happens when clicking onto a new setting.1789if (height) {1790this._onDidChangeSettingHeight.fire({1791element: dataElement,1792height: template.containerElement.clientHeight1793});1794}1795})1796);1797template.inputBox.layout();1798}1799}18001801class SettingEnumRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingEnumItemTemplate> {1802templateId = SETTINGS_ENUM_TEMPLATE_ID;18031804renderTemplate(container: HTMLElement): ISettingEnumItemTemplate {1805const common = this.renderCommonTemplate(null, container, 'enum');18061807const styles = getSelectBoxStyles({1808selectBackground: settingsSelectBackground,1809selectForeground: settingsSelectForeground,1810selectBorder: settingsSelectBorder,1811selectListBorder: settingsSelectListBorder1812});18131814const selectBox = new SelectBox([], 0, this._contextViewService, styles, {1815useCustomDrawn: !hasNativeContextMenu(this._configService) || !(isIOS && BrowserFeatures.pointerEvents)1816});18171818common.toDispose.add(selectBox);1819selectBox.render(common.controlElement);1820const selectElement = common.controlElement.querySelector('select');1821if (selectElement) {1822selectElement.classList.add(AbstractSettingRenderer.CONTROL_CLASS);1823selectElement.tabIndex = 0;1824}18251826common.toDispose.add(1827selectBox.onDidSelect(e => {1828template.onChange?.(e.index);1829}));18301831const enumDescriptionElement = common.containerElement.insertBefore($('.setting-item-enumDescription'), common.descriptionElement.nextSibling);18321833const template: ISettingEnumItemTemplate = {1834...common,1835selectBox,1836selectElement,1837enumDescriptionElement1838};18391840this.addSettingElementFocusHandler(template);18411842return template;1843}18441845renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingEnumItemTemplate): void {1846super.renderSettingElement(element, index, templateData);1847}18481849protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingEnumItemTemplate, onChange: (value: string) => void): void {1850// Make shallow copies here so that we don't modify the actual dataElement later1851const enumItemLabels = dataElement.setting.enumItemLabels ? [...dataElement.setting.enumItemLabels] : [];1852const enumDescriptions = dataElement.setting.enumDescriptions ? [...dataElement.setting.enumDescriptions] : [];1853const settingEnum = [...dataElement.setting.enum!];1854const enumDescriptionsAreMarkdown = dataElement.setting.enumDescriptionsAreMarkdown;18551856const disposables = new DisposableStore();1857template.elementDisposables.add(disposables);18581859let createdDefault = false;1860if (!settingEnum.includes(dataElement.defaultValue)) {1861// Add a new potentially blank default setting1862settingEnum.unshift(dataElement.defaultValue);1863enumDescriptions.unshift('');1864enumItemLabels.unshift('');1865createdDefault = true;1866}18671868// Use String constructor in case of null or undefined values1869const stringifiedDefaultValue = escapeInvisibleChars(String(dataElement.defaultValue));1870const displayOptions: ISelectOptionItem[] = settingEnum1871.map(String)1872.map(escapeInvisibleChars)1873.map((data, index) => {1874const description = (enumDescriptions[index] && (enumDescriptionsAreMarkdown ? fixSettingLinks(enumDescriptions[index], false) : enumDescriptions[index]));1875return {1876text: enumItemLabels[index] ? enumItemLabels[index] : data,1877detail: enumItemLabels[index] ? data : '',1878description,1879descriptionIsMarkdown: enumDescriptionsAreMarkdown,1880descriptionMarkdownActionHandler: (content) => {1881this._openerService.open(content).catch(onUnexpectedError);1882},1883decoratorRight: (((data === stringifiedDefaultValue) || (createdDefault && index === 0)) ? localize('settings.Default', "default") : '')1884} satisfies ISelectOptionItem;1885});18861887template.selectBox.setOptions(displayOptions);1888template.selectBox.setAriaLabel(dataElement.setting.key);18891890let idx = settingEnum.indexOf(dataElement.value);1891if (idx === -1) {1892idx = 0;1893}18941895template.onChange = undefined;1896template.selectBox.select(idx);1897template.onChange = (idx) => {1898if (createdDefault && idx === 0) {1899onChange(dataElement.defaultValue);1900} else {1901onChange(settingEnum[idx]);1902}1903};19041905template.enumDescriptionElement.innerText = '';1906}1907}19081909const settingsNumberInputBoxStyles = getInputBoxStyle({1910inputBackground: settingsNumberInputBackground,1911inputForeground: settingsNumberInputForeground,1912inputBorder: settingsNumberInputBorder1913});19141915class SettingNumberRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingNumberItemTemplate> {1916templateId = SETTINGS_NUMBER_TEMPLATE_ID;19171918renderTemplate(_container: HTMLElement): ISettingNumberItemTemplate {1919const common = super.renderCommonTemplate(null, _container, 'number');1920const validationErrorMessageElement = DOM.append(common.containerElement, $('.setting-item-validation-message'));19211922const inputBox = new InputBox(common.controlElement, this._contextViewService, { type: 'number', inputBoxStyles: settingsNumberInputBoxStyles });1923common.toDispose.add(inputBox);1924common.toDispose.add(1925inputBox.onDidChange(e => {1926template.onChange?.(e);1927}));1928common.toDispose.add(inputBox);1929inputBox.inputElement.classList.add(AbstractSettingRenderer.CONTROL_CLASS);1930inputBox.inputElement.tabIndex = 0;19311932const template: ISettingNumberItemTemplate = {1933...common,1934inputBox,1935validationErrorMessageElement1936};19371938this.addSettingElementFocusHandler(template);19391940return template;1941}19421943renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingNumberItemTemplate): void {1944super.renderSettingElement(element, index, templateData);1945}19461947protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingNumberItemTemplate, onChange: (value: number | null) => void): void {1948const numParseFn = (dataElement.valueType === 'integer' || dataElement.valueType === 'nullable-integer')1949? parseInt : parseFloat;19501951const nullNumParseFn = (dataElement.valueType === 'nullable-integer' || dataElement.valueType === 'nullable-number')1952? ((v: string) => v === '' ? null : numParseFn(v)) : numParseFn;19531954template.onChange = undefined;1955template.inputBox.value = typeof dataElement.value === 'number' ?1956dataElement.value.toString() : '';1957template.inputBox.step = dataElement.valueType.includes('integer') ? '1' : 'any';1958template.inputBox.setAriaLabel(dataElement.setting.key);1959template.onChange = value => {1960if (!renderValidations(dataElement, template, false)) {1961onChange(nullNumParseFn(value));1962}1963};19641965renderValidations(dataElement, template, true);1966}1967}19681969class SettingBoolRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingBoolItemTemplate> {1970templateId = SETTINGS_BOOL_TEMPLATE_ID;19711972renderTemplate(_container: HTMLElement): ISettingBoolItemTemplate {1973_container.classList.add('setting-item');1974_container.classList.add('setting-item-bool');19751976const toDispose = new DisposableStore();19771978const container = DOM.append(_container, $(AbstractSettingRenderer.CONTENTS_SELECTOR));1979container.classList.add('settings-row-inner-container');19801981const titleElement = DOM.append(container, $('.setting-item-title'));1982const categoryElement = DOM.append(titleElement, $('span.setting-item-category'));1983const labelElementContainer = DOM.append(titleElement, $('span.setting-item-label'));1984const labelElement = toDispose.add(new SimpleIconLabel(labelElementContainer));1985const indicatorsLabel = toDispose.add(this._instantiationService.createInstance(SettingsTreeIndicatorsLabel, titleElement));19861987const descriptionAndValueElement = DOM.append(container, $('.setting-item-value-description'));1988const controlElement = DOM.append(descriptionAndValueElement, $('.setting-item-bool-control'));1989const descriptionElement = DOM.append(descriptionAndValueElement, $('.setting-item-description'));1990const modifiedIndicatorElement = DOM.append(container, $('.setting-item-modified-indicator'));1991toDispose.add(this._hoverService.setupDelayedHover(modifiedIndicatorElement, {1992content: localize('modified', "The setting has been configured in the current scope.")1993}));19941995const deprecationWarningElement = DOM.append(container, $('.setting-item-deprecation-message'));19961997const checkbox = new Toggle({ icon: Codicon.check, actionClassName: 'setting-value-checkbox', isChecked: true, title: '', ...unthemedToggleStyles });1998controlElement.appendChild(checkbox.domNode);1999toDispose.add(checkbox);2000toDispose.add(checkbox.onChange(() => {2001template.onChange!(checkbox.checked);2002}));20032004checkbox.domNode.classList.add(AbstractSettingRenderer.CONTROL_CLASS);2005const toolbarContainer = DOM.append(container, $('.setting-toolbar-container'));2006const toolbar = this.renderSettingToolbar(toolbarContainer);2007toDispose.add(toolbar);20082009const template: ISettingBoolItemTemplate = {2010toDispose,2011elementDisposables: toDispose.add(new DisposableStore()),20122013containerElement: container,2014categoryElement,2015labelElement,2016controlElement,2017checkbox,2018descriptionElement,2019deprecationWarningElement,2020indicatorsLabel,2021toolbar2022};20232024this.addSettingElementFocusHandler(template);20252026// Prevent clicks from being handled by list2027toDispose.add(DOM.addDisposableListener(controlElement, 'mousedown', (e: IMouseEvent) => e.stopPropagation()));2028toDispose.add(DOM.addDisposableListener(titleElement, DOM.EventType.MOUSE_ENTER, e => container.classList.add('mouseover')));2029toDispose.add(DOM.addDisposableListener(titleElement, DOM.EventType.MOUSE_LEAVE, e => container.classList.remove('mouseover')));20302031return template;2032}20332034renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingBoolItemTemplate): void {2035super.renderSettingElement(element, index, templateData);2036}20372038protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingBoolItemTemplate, onChange: (value: boolean) => void): void {2039template.onChange = undefined;2040template.checkbox.checked = dataElement.value;2041if (dataElement.hasPolicyValue) {2042template.checkbox.disable();2043template.descriptionElement.classList.add('disabled');2044} else {2045template.checkbox.enable();2046template.descriptionElement.classList.remove('disabled');20472048// Need to listen for mouse clicks on description and toggle checkbox - use target ID for safety2049// Also have to ignore embedded links - too buried to stop propagation2050template.elementDisposables.add(DOM.addDisposableListener(template.descriptionElement, DOM.EventType.MOUSE_DOWN, (e) => {2051const targetElement = <HTMLElement>e.target;20522053// Toggle target checkbox2054if (targetElement.tagName.toLowerCase() !== 'a') {2055template.checkbox.checked = !template.checkbox.checked;2056template.onChange!(template.checkbox.checked);2057}2058DOM.EventHelper.stop(e);2059}));2060}2061template.checkbox.setTitle(dataElement.setting.key);2062template.onChange = onChange;2063}2064}20652066type ManageExtensionClickTelemetryClassification = {2067extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension the user went to manage.' };2068owner: 'rzhao271';2069comment: 'Event used to gain insights into when users interact with an extension management setting';2070};20712072class SettingsExtensionToggleRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingExtensionToggleItemTemplate> {2073templateId = SETTINGS_EXTENSION_TOGGLE_TEMPLATE_ID;20742075private readonly _onDidDismissExtensionSetting = this._register(new Emitter<string>());2076readonly onDidDismissExtensionSetting = this._onDidDismissExtensionSetting.event;20772078renderTemplate(_container: HTMLElement): ISettingExtensionToggleItemTemplate {2079const common = super.renderCommonTemplate(null, _container, 'extension-toggle');20802081const actionButton = new Button(common.containerElement, {2082title: false,2083...defaultButtonStyles2084});2085actionButton.element.classList.add('setting-item-extension-toggle-button');2086actionButton.label = localize('showExtension', "Show Extension");20872088const dismissButton = new Button(common.containerElement, {2089title: false,2090secondary: true,2091...defaultButtonStyles2092});2093dismissButton.element.classList.add('setting-item-extension-dismiss-button');2094dismissButton.label = localize('dismiss', "Dismiss");20952096const template: ISettingExtensionToggleItemTemplate = {2097...common,2098actionButton,2099dismissButton2100};21012102this.addSettingElementFocusHandler(template);21032104return template;2105}21062107renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingExtensionToggleItemTemplate): void {2108super.renderSettingElement(element, index, templateData);2109}21102111protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingExtensionToggleItemTemplate, onChange: (_: undefined) => void): void {2112template.elementDisposables.clear();21132114const extensionId = dataElement.setting.displayExtensionId!;2115template.elementDisposables.add(template.actionButton.onDidClick(async () => {2116this._telemetryService.publicLog2<{ extensionId: String }, ManageExtensionClickTelemetryClassification>('ManageExtensionClick', { extensionId });2117this._commandService.executeCommand('extension.open', extensionId);2118}));21192120template.elementDisposables.add(template.dismissButton.onDidClick(async () => {2121this._telemetryService.publicLog2<{ extensionId: String }, ManageExtensionClickTelemetryClassification>('DismissExtensionClick', { extensionId });2122this._onDidDismissExtensionSetting.fire(extensionId);2123}));2124}2125}21262127export class SettingTreeRenderers extends Disposable {2128readonly onDidClickOverrideElement: Event<ISettingOverrideClickEvent>;21292130private readonly _onDidChangeSetting = this._register(new Emitter<ISettingChangeEvent>());2131readonly onDidChangeSetting: Event<ISettingChangeEvent>;21322133readonly onDidDismissExtensionSetting: Event<string>;21342135readonly onDidOpenSettings: Event<string>;21362137readonly onDidClickSettingLink: Event<ISettingLinkClickEvent>;21382139readonly onDidFocusSetting: Event<SettingsTreeSettingElement>;21402141readonly onDidChangeSettingHeight: Event<HeightChangeParams>;21422143readonly onApplyFilter: Event<string>;21442145readonly allRenderers: ITreeRenderer<SettingsTreeElement, never, any>[];21462147private readonly settingActions: IAction[];21482149constructor(2150@IInstantiationService private readonly _instantiationService: IInstantiationService,2151@IContextMenuService private readonly _contextMenuService: IContextMenuService,2152@IContextViewService private readonly _contextViewService: IContextViewService,2153@IUserDataSyncEnablementService private readonly _userDataSyncEnablementService: IUserDataSyncEnablementService,2154) {2155super();2156this.settingActions = [2157new Action('settings.resetSetting', localize('resetSettingLabel', "Reset Setting"), undefined, undefined, async context => {2158if (context instanceof SettingsTreeSettingElement) {2159if (!context.isUntrusted) {2160this._onDidChangeSetting.fire({2161key: context.setting.key,2162value: undefined,2163type: context.setting.type as SettingValueType,2164manualReset: true,2165scope: context.setting.scope2166});2167}2168}2169}),2170new Separator(),2171this._instantiationService.createInstance(CopySettingIdAction),2172this._instantiationService.createInstance(CopySettingAsJSONAction),2173this._instantiationService.createInstance(CopySettingAsURLAction),2174];21752176const actionFactory = (setting: ISetting, settingTarget: SettingsTarget) => this.getActionsForSetting(setting, settingTarget);2177const emptyActionFactory = (_: ISetting) => [];2178const extensionRenderer = this._instantiationService.createInstance(SettingsExtensionToggleRenderer, [], emptyActionFactory);2179const settingRenderers = [2180this._instantiationService.createInstance(SettingBoolRenderer, this.settingActions, actionFactory),2181this._instantiationService.createInstance(SettingNumberRenderer, this.settingActions, actionFactory),2182this._instantiationService.createInstance(SettingArrayRenderer, this.settingActions, actionFactory),2183this._instantiationService.createInstance(SettingComplexRenderer, this.settingActions, actionFactory),2184this._instantiationService.createInstance(SettingComplexObjectRenderer, this.settingActions, actionFactory),2185this._instantiationService.createInstance(SettingTextRenderer, this.settingActions, actionFactory),2186this._instantiationService.createInstance(SettingMultilineTextRenderer, this.settingActions, actionFactory),2187this._instantiationService.createInstance(SettingExcludeRenderer, this.settingActions, actionFactory),2188this._instantiationService.createInstance(SettingIncludeRenderer, this.settingActions, actionFactory),2189this._instantiationService.createInstance(SettingEnumRenderer, this.settingActions, actionFactory),2190this._instantiationService.createInstance(SettingObjectRenderer, this.settingActions, actionFactory),2191this._instantiationService.createInstance(SettingBoolObjectRenderer, this.settingActions, actionFactory),2192extensionRenderer2193];21942195this.onDidClickOverrideElement = Event.any(...settingRenderers.map(r => r.onDidClickOverrideElement));2196this.onDidChangeSetting = Event.any(2197...settingRenderers.map(r => r.onDidChangeSetting),2198this._onDidChangeSetting.event2199);2200this.onDidDismissExtensionSetting = extensionRenderer.onDidDismissExtensionSetting;2201this.onDidOpenSettings = Event.any(...settingRenderers.map(r => r.onDidOpenSettings));2202this.onDidClickSettingLink = Event.any(...settingRenderers.map(r => r.onDidClickSettingLink));2203this.onDidFocusSetting = Event.any(...settingRenderers.map(r => r.onDidFocusSetting));2204this.onDidChangeSettingHeight = Event.any(...settingRenderers.map(r => r.onDidChangeSettingHeight));2205this.onApplyFilter = Event.any(...settingRenderers.map(r => r.onApplyFilter));22062207this.allRenderers = [2208...settingRenderers,2209this._instantiationService.createInstance(SettingGroupRenderer),2210this._instantiationService.createInstance(SettingNewExtensionsRenderer),2211];2212}22132214private getActionsForSetting(setting: ISetting, settingTarget: SettingsTarget): IAction[] {2215const actions: IAction[] = [];2216if (!(setting.scope && APPLICATION_SCOPES.includes(setting.scope)) && settingTarget === ConfigurationTarget.USER_LOCAL) {2217actions.push(this._instantiationService.createInstance(ApplySettingToAllProfilesAction, setting));2218}2219if (this._userDataSyncEnablementService.isEnabled() && !setting.disallowSyncIgnore) {2220actions.push(this._instantiationService.createInstance(SyncSettingAction, setting));2221}2222if (actions.length) {2223actions.splice(0, 0, new Separator());2224}2225return actions;2226}22272228cancelSuggesters() {2229this._contextViewService.hideContextView();2230}22312232showContextMenu(element: SettingsTreeSettingElement, settingDOMElement: HTMLElement): void {2233const toolbarElement = settingDOMElement.querySelector('.monaco-toolbar');2234if (toolbarElement) {2235this._contextMenuService.showContextMenu({2236getActions: () => this.settingActions,2237getAnchor: () => <HTMLElement>toolbarElement,2238getActionsContext: () => element2239});2240}2241}22422243getSettingDOMElementForDOMElement(domElement: HTMLElement): HTMLElement | null {2244const parent = DOM.findParentWithClass(domElement, AbstractSettingRenderer.CONTENTS_CLASS);2245if (parent) {2246return parent;2247}22482249return null;2250}22512252getDOMElementsForSettingKey(treeContainer: HTMLElement, key: string): NodeListOf<HTMLElement> {2253return treeContainer.querySelectorAll(`[${AbstractSettingRenderer.SETTING_KEY_ATTR}="${key}"]`);2254}22552256getKeyForDOMElementInSetting(element: HTMLElement): string | null {2257const settingElement = this.getSettingDOMElementForDOMElement(element);2258return settingElement && settingElement.getAttribute(AbstractSettingRenderer.SETTING_KEY_ATTR);2259}22602261getIdForDOMElementInSetting(element: HTMLElement): string | null {2262const settingElement = this.getSettingDOMElementForDOMElement(element);2263return settingElement && settingElement.getAttribute(AbstractSettingRenderer.SETTING_ID_ATTR);2264}22652266override dispose(): void {2267super.dispose();2268this.settingActions.forEach(action => {2269if (isDisposable(action)) {2270action.dispose();2271}2272});2273this.allRenderers.forEach(renderer => {2274if (isDisposable(renderer)) {2275renderer.dispose();2276}2277});2278}2279}22802281/**2282* Validate and render any error message. Returns true if the value is invalid.2283*/2284function renderValidations(dataElement: SettingsTreeSettingElement, template: ISettingTextItemTemplate, calledOnStartup: boolean): boolean {2285if (dataElement.setting.validator) {2286const errMsg = dataElement.setting.validator(template.inputBox.value);2287if (errMsg) {2288template.containerElement.classList.add('invalid-input');2289template.validationErrorMessageElement.innerText = errMsg;2290const validationError = localize('validationError', "Validation Error.");2291template.inputBox.inputElement.parentElement!.setAttribute('aria-label', [validationError, errMsg].join(' '));2292if (!calledOnStartup) { aria.status(validationError + ' ' + errMsg); }2293return true;2294} else {2295template.inputBox.inputElement.parentElement!.removeAttribute('aria-label');2296}2297}2298template.containerElement.classList.remove('invalid-input');2299return false;2300}23012302/**2303* Validate and render any error message for arrays. Returns true if the value is invalid.2304*/2305function renderArrayValidations(2306dataElement: SettingsTreeSettingElement,2307template: ISettingListItemTemplate | ISettingObjectItemTemplate,2308value: string[] | Record<string, unknown> | undefined,2309calledOnStartup: boolean2310): boolean {2311template.containerElement.classList.add('invalid-input');2312if (dataElement.setting.validator) {2313const errMsg = dataElement.setting.validator(value);2314if (errMsg && errMsg !== '') {2315template.containerElement.classList.add('invalid-input');2316template.validationErrorMessageElement.innerText = errMsg;2317const validationError = localize('validationError', "Validation Error.");2318template.containerElement.setAttribute('aria-label', [dataElement.setting.key, validationError, errMsg].join(' '));2319if (!calledOnStartup) { aria.status(validationError + ' ' + errMsg); }2320return true;2321} else {2322template.containerElement.setAttribute('aria-label', dataElement.setting.key);2323template.containerElement.classList.remove('invalid-input');2324}2325}2326return false;2327}23282329function cleanRenderedMarkdown(element: Node): void {2330for (let i = 0; i < element.childNodes.length; i++) {2331const child = element.childNodes.item(i);23322333const tagName = (<Element>child).tagName && (<Element>child).tagName.toLowerCase();2334if (tagName === 'img') {2335child.remove();2336} else {2337cleanRenderedMarkdown(child);2338}2339}2340}23412342function fixSettingLinks(text: string, linkify = true): string {2343return text.replace(/`#([^#\s`]+)#`|'#([^#\s']+)#'/g, (match, backticksGroup, quotesGroup) => {2344const settingKey: string = backticksGroup ?? quotesGroup;2345const targetDisplayFormat = settingKeyToDisplayFormat(settingKey);2346const targetName = `${targetDisplayFormat.category}: ${targetDisplayFormat.label}`;2347return linkify ?2348`[${targetName}](#${settingKey} "${settingKey}")` :2349`"${targetName}"`;2350});2351}23522353function escapeInvisibleChars(enumValue: string): string {2354return enumValue && enumValue2355.replace(/\n/g, '\\n')2356.replace(/\r/g, '\\r');2357}235823592360export class SettingsTreeFilter implements ITreeFilter<SettingsTreeElement> {2361constructor(2362private viewState: ISettingsEditorViewState,2363@IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService,2364) { }23652366filter(element: SettingsTreeElement, parentVisibility: TreeVisibility): TreeFilterResult<void> {2367// Filter during search2368if (this.viewState.filterToCategory && element instanceof SettingsTreeSettingElement) {2369if (!this.settingContainedInGroup(element.setting, this.viewState.filterToCategory)) {2370return false;2371}2372}23732374// Non-user scope selected2375if (element instanceof SettingsTreeSettingElement && this.viewState.settingsTarget !== ConfigurationTarget.USER_LOCAL) {2376const isRemote = !!this.environmentService.remoteAuthority;2377if (!element.matchesScope(this.viewState.settingsTarget, isRemote)) {2378return false;2379}2380}23812382// Group with no visible children2383if (element instanceof SettingsTreeGroupElement) {2384if (typeof element.count === 'number') {2385return element.count > 0;2386}23872388return TreeVisibility.Recurse;2389}23902391// Filtered "new extensions" button2392if (element instanceof SettingsTreeNewExtensionsElement) {2393if (this.viewState.tagFilters?.size || this.viewState.filterToCategory) {2394return false;2395}2396}23972398return true;2399}24002401private settingContainedInGroup(setting: ISetting, group: SettingsTreeGroupElement): boolean {2402return group.children.some(child => {2403if (child instanceof SettingsTreeGroupElement) {2404return this.settingContainedInGroup(setting, child);2405} else if (child instanceof SettingsTreeSettingElement) {2406return child.setting.key === setting.key;2407} else {2408return false;2409}2410});2411}2412}24132414class SettingsTreeDelegate extends CachedListVirtualDelegate<SettingsTreeGroupChild> {24152416getTemplateId(element: SettingsTreeGroupElement | SettingsTreeSettingElement | SettingsTreeNewExtensionsElement): string {2417if (element instanceof SettingsTreeGroupElement) {2418return SETTINGS_ELEMENT_TEMPLATE_ID;2419}24202421if (element instanceof SettingsTreeSettingElement) {2422if (element.valueType === SettingValueType.ExtensionToggle) {2423return SETTINGS_EXTENSION_TOGGLE_TEMPLATE_ID;2424}24252426const invalidTypeError = element.isConfigured && getInvalidTypeError(element.value, element.setting.type);2427if (invalidTypeError) {2428return SETTINGS_COMPLEX_TEMPLATE_ID;2429}24302431if (element.valueType === SettingValueType.Boolean) {2432return SETTINGS_BOOL_TEMPLATE_ID;2433}24342435if (element.valueType === SettingValueType.Integer ||2436element.valueType === SettingValueType.Number ||2437element.valueType === SettingValueType.NullableInteger ||2438element.valueType === SettingValueType.NullableNumber) {2439return SETTINGS_NUMBER_TEMPLATE_ID;2440}24412442if (element.valueType === SettingValueType.MultilineString) {2443return SETTINGS_MULTILINE_TEXT_TEMPLATE_ID;2444}24452446if (element.valueType === SettingValueType.String) {2447return SETTINGS_TEXT_TEMPLATE_ID;2448}24492450if (element.valueType === SettingValueType.Enum) {2451return SETTINGS_ENUM_TEMPLATE_ID;2452}24532454if (element.valueType === SettingValueType.Array) {2455return SETTINGS_ARRAY_TEMPLATE_ID;2456}24572458if (element.valueType === SettingValueType.Exclude) {2459return SETTINGS_EXCLUDE_TEMPLATE_ID;2460}24612462if (element.valueType === SettingValueType.Include) {2463return SETTINGS_INCLUDE_TEMPLATE_ID;2464}24652466if (element.valueType === SettingValueType.Object) {2467return SETTINGS_OBJECT_TEMPLATE_ID;2468}24692470if (element.valueType === SettingValueType.BooleanObject) {2471return SETTINGS_BOOL_OBJECT_TEMPLATE_ID;2472}24732474if (element.valueType === SettingValueType.ComplexObject) {2475return SETTINGS_COMPLEX_OBJECT_TEMPLATE_ID;2476}24772478if (element.valueType === SettingValueType.LanguageTag) {2479return SETTINGS_COMPLEX_TEMPLATE_ID;2480}24812482return SETTINGS_COMPLEX_TEMPLATE_ID;2483}24842485if (element instanceof SettingsTreeNewExtensionsElement) {2486return SETTINGS_NEW_EXTENSIONS_TEMPLATE_ID;2487}24882489throw new Error('unknown element type: ' + element);2490}24912492hasDynamicHeight(element: SettingsTreeGroupElement | SettingsTreeSettingElement | SettingsTreeNewExtensionsElement): boolean {2493return !(element instanceof SettingsTreeGroupElement);2494}24952496protected estimateHeight(element: SettingsTreeGroupChild): number {2497if (element instanceof SettingsTreeGroupElement) {2498return 42;2499}25002501return element instanceof SettingsTreeSettingElement && element.valueType === SettingValueType.Boolean ? 78 : 104;2502}2503}25042505export class NonCollapsibleObjectTreeModel<T> extends ObjectTreeModel<T> {2506override isCollapsible(element: T): boolean {2507return false;2508}25092510override setCollapsed(element: T, collapsed?: boolean, recursive?: boolean): boolean {2511return false;2512}2513}25142515class SettingsTreeAccessibilityProvider implements IListAccessibilityProvider<SettingsTreeElement> {2516constructor(private readonly configurationService: IWorkbenchConfigurationService, private readonly languageService: ILanguageService, private readonly userDataProfilesService: IUserDataProfilesService) {2517}25182519getAriaLabel(element: SettingsTreeElement) {2520if (element instanceof SettingsTreeSettingElement) {2521const ariaLabelSections: string[] = [];2522ariaLabelSections.push(`${element.displayCategory} ${element.displayLabel}.`);25232524if (element.isConfigured) {2525const modifiedText = localize('settings.Modified', 'Modified.');2526ariaLabelSections.push(modifiedText);2527}25282529const indicatorsLabelAriaLabel = getIndicatorsLabelAriaLabel(element, this.configurationService, this.userDataProfilesService, this.languageService);2530if (indicatorsLabelAriaLabel.length) {2531ariaLabelSections.push(`${indicatorsLabelAriaLabel}.`);2532}25332534const descriptionWithoutSettingLinks = renderAsPlaintext({ value: fixSettingLinks(element.description, false) });2535if (descriptionWithoutSettingLinks.length) {2536ariaLabelSections.push(descriptionWithoutSettingLinks);2537}2538return ariaLabelSections.join(' ');2539} else if (element instanceof SettingsTreeGroupElement) {2540return element.label;2541} else {2542return element.id;2543}2544}25452546getWidgetAriaLabel() {2547return localize('settings', "Settings");2548}2549}25502551export class SettingsTree extends WorkbenchObjectTree<SettingsTreeElement> {2552constructor(2553container: HTMLElement,2554viewState: ISettingsEditorViewState,2555renderers: ITreeRenderer<any, void, any>[],2556@IContextKeyService contextKeyService: IContextKeyService,2557@IListService listService: IListService,2558@IWorkbenchConfigurationService configurationService: IWorkbenchConfigurationService,2559@IInstantiationService instantiationService: IInstantiationService,2560@ILanguageService languageService: ILanguageService,2561@IUserDataProfilesService userDataProfilesService: IUserDataProfilesService2562) {2563super('SettingsTree', container,2564new SettingsTreeDelegate(),2565renderers,2566{2567horizontalScrolling: false,2568supportDynamicHeights: true,2569scrollToActiveElement: true,2570identityProvider: {2571getId(e) {2572return e.id;2573}2574},2575accessibilityProvider: new SettingsTreeAccessibilityProvider(configurationService, languageService, userDataProfilesService),2576styleController: id => new DefaultStyleController(domStylesheetsJs.createStyleSheet(container), id),2577filter: instantiationService.createInstance(SettingsTreeFilter, viewState),2578smoothScrolling: configurationService.getValue<boolean>('workbench.list.smoothScrolling'),2579multipleSelectionSupport: false,2580findWidgetEnabled: false,2581renderIndentGuides: RenderIndentGuides.None,2582transformOptimization: false // Disable transform optimization #1774702583},2584instantiationService,2585contextKeyService,2586listService,2587configurationService,2588);25892590this.getHTMLElement().classList.add('settings-editor-tree');25912592this.style(getListStyles({2593listBackground: editorBackground,2594listActiveSelectionBackground: editorBackground,2595listActiveSelectionForeground: foreground,2596listFocusAndSelectionBackground: editorBackground,2597listFocusAndSelectionForeground: foreground,2598listFocusBackground: editorBackground,2599listFocusForeground: foreground,2600listHoverForeground: foreground,2601listHoverBackground: editorBackground,2602listHoverOutline: editorBackground,2603listFocusOutline: editorBackground,2604listInactiveSelectionBackground: editorBackground,2605listInactiveSelectionForeground: foreground,2606listInactiveFocusBackground: editorBackground,2607listInactiveFocusOutline: editorBackground,2608treeIndentGuidesStroke: undefined,2609treeInactiveIndentGuidesStroke: undefined,2610}));26112612this.disposables.add(configurationService.onDidChangeConfiguration(e => {2613if (e.affectsConfiguration('workbench.list.smoothScrolling')) {2614this.updateOptions({2615smoothScrolling: configurationService.getValue<boolean>('workbench.list.smoothScrolling')2616});2617}2618}));2619}26202621protected override createModel(user: string, options: IObjectTreeOptions<SettingsTreeGroupChild>): ITreeModel<SettingsTreeGroupChild | null, void, SettingsTreeGroupChild | null> {2622return new NonCollapsibleObjectTreeModel<SettingsTreeGroupChild>(user, options);2623}2624}26252626class CopySettingIdAction extends Action {2627static readonly ID = 'settings.copySettingId';2628static readonly LABEL = localize('copySettingIdLabel', "Copy Setting ID");26292630constructor(2631@IClipboardService private readonly clipboardService: IClipboardService2632) {2633super(CopySettingIdAction.ID, CopySettingIdAction.LABEL);2634}26352636override async run(context: SettingsTreeSettingElement): Promise<void> {2637if (context) {2638await this.clipboardService.writeText(context.setting.key);2639}26402641return Promise.resolve(undefined);2642}2643}26442645class CopySettingAsJSONAction extends Action {2646static readonly ID = 'settings.copySettingAsJSON';2647static readonly LABEL = localize('copySettingAsJSONLabel', "Copy Setting as JSON");26482649constructor(2650@IClipboardService private readonly clipboardService: IClipboardService2651) {2652super(CopySettingAsJSONAction.ID, CopySettingAsJSONAction.LABEL);2653}26542655override async run(context: SettingsTreeSettingElement): Promise<void> {2656if (context) {2657const jsonResult = `"${context.setting.key}": ${JSON.stringify(context.value, undefined, ' ')}`;2658await this.clipboardService.writeText(jsonResult);2659}26602661return Promise.resolve(undefined);2662}2663}26642665class CopySettingAsURLAction extends Action {2666static readonly ID = 'settings.copySettingAsURL';2667static readonly LABEL = localize('copySettingAsURLLabel', "Copy Setting as URL");26682669constructor(2670@IClipboardService private readonly clipboardService: IClipboardService,2671@IProductService private readonly productService: IProductService,2672) {2673super(CopySettingAsURLAction.ID, CopySettingAsURLAction.LABEL);2674}26752676override async run(context: SettingsTreeSettingElement): Promise<void> {2677if (context) {2678const settingKey = context.setting.key;2679const product = this.productService.urlProtocol;2680const uri = URI.from({ scheme: product, authority: SETTINGS_AUTHORITY, path: `/${settingKey}` }, true);2681await this.clipboardService.writeText(uri.toString());2682}26832684return Promise.resolve(undefined);2685}2686}26872688class SyncSettingAction extends Action {2689static readonly ID = 'settings.stopSyncingSetting';2690static readonly LABEL = localize('stopSyncingSetting', "Sync This Setting");26912692constructor(2693private readonly setting: ISetting,2694@IConfigurationService private readonly configService: IConfigurationService,2695) {2696super(SyncSettingAction.ID, SyncSettingAction.LABEL);2697this._register(Event.filter(configService.onDidChangeConfiguration, e => e.affectsConfiguration('settingsSync.ignoredSettings'))(() => this.update()));2698this.update();2699}27002701async update() {2702const ignoredSettings = getIgnoredSettings(getDefaultIgnoredSettings(), this.configService);2703this.checked = !ignoredSettings.includes(this.setting.key);2704}27052706override async run(): Promise<void> {2707// first remove the current setting completely from ignored settings2708let currentValue = [...this.configService.getValue<string[]>('settingsSync.ignoredSettings')];2709currentValue = currentValue.filter(v => v !== this.setting.key && v !== `-${this.setting.key}`);27102711const defaultIgnoredSettings = getDefaultIgnoredSettings();2712const isDefaultIgnored = defaultIgnoredSettings.includes(this.setting.key);2713const askedToSync = !this.checked;27142715// If asked to sync, then add only if it is ignored by default2716if (askedToSync && isDefaultIgnored) {2717currentValue.push(`-${this.setting.key}`);2718}27192720// If asked not to sync, then add only if it is not ignored by default2721if (!askedToSync && !isDefaultIgnored) {2722currentValue.push(this.setting.key);2723}27242725this.configService.updateValue('settingsSync.ignoredSettings', currentValue.length ? currentValue : undefined, ConfigurationTarget.USER);27262727return Promise.resolve(undefined);2728}27292730}27312732class ApplySettingToAllProfilesAction extends Action {2733static readonly ID = 'settings.applyToAllProfiles';2734static readonly LABEL = localize('applyToAllProfiles', "Apply Setting to all Profiles");27352736constructor(2737private readonly setting: ISetting,2738@IWorkbenchConfigurationService private readonly configService: IWorkbenchConfigurationService,2739) {2740super(ApplySettingToAllProfilesAction.ID, ApplySettingToAllProfilesAction.LABEL);2741this._register(Event.filter(configService.onDidChangeConfiguration, e => e.affectsConfiguration(APPLY_ALL_PROFILES_SETTING))(() => this.update()));2742this.update();2743}27442745update() {2746const allProfilesSettings = this.configService.getValue<string[]>(APPLY_ALL_PROFILES_SETTING);2747this.checked = allProfilesSettings.includes(this.setting.key);2748}27492750override async run(): Promise<void> {2751// first remove the current setting completely from ignored settings2752const value = this.configService.getValue<string[]>(APPLY_ALL_PROFILES_SETTING) ?? [];27532754if (this.checked) {2755value.splice(value.indexOf(this.setting.key), 1);2756} else {2757value.push(this.setting.key);2758}27592760const newValue = distinct(value);2761if (this.checked) {2762await this.configService.updateValue(this.setting.key, this.configService.inspect(this.setting.key).application?.value, ConfigurationTarget.USER_LOCAL);2763await this.configService.updateValue(APPLY_ALL_PROFILES_SETTING, newValue.length ? newValue : undefined, ConfigurationTarget.USER_LOCAL);2764} else {2765await this.configService.updateValue(APPLY_ALL_PROFILES_SETTING, newValue.length ? newValue : undefined, ConfigurationTarget.USER_LOCAL);2766await this.configService.updateValue(this.setting.key, this.configService.inspect(this.setting.key).userLocal?.value, ConfigurationTarget.USER_LOCAL);2767}2768}27692770}277127722773