Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/preferences/browser/settingsTree.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { BrowserFeatures } from '../../../../base/browser/canIUse.js';
7
import * as DOM from '../../../../base/browser/dom.js';
8
import * as domStylesheetsJs from '../../../../base/browser/domStylesheets.js';
9
import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js';
10
import { renderAsPlaintext } from '../../../../base/browser/markdownRenderer.js';
11
import { IMouseEvent } from '../../../../base/browser/mouseEvent.js';
12
import * as aria from '../../../../base/browser/ui/aria/aria.js';
13
import { Button } from '../../../../base/browser/ui/button/button.js';
14
import { SimpleIconLabel } from '../../../../base/browser/ui/iconLabel/simpleIconLabel.js';
15
import { IInputOptions, InputBox } from '../../../../base/browser/ui/inputbox/inputBox.js';
16
import { CachedListVirtualDelegate } from '../../../../base/browser/ui/list/list.js';
17
import { DefaultStyleController, IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js';
18
import { ISelectOptionItem, SelectBox } from '../../../../base/browser/ui/selectBox/selectBox.js';
19
import { Toggle, unthemedToggleStyles } from '../../../../base/browser/ui/toggle/toggle.js';
20
import { ToolBar } from '../../../../base/browser/ui/toolbar/toolbar.js';
21
import { RenderIndentGuides } from '../../../../base/browser/ui/tree/abstractTree.js';
22
import { IObjectTreeOptions } from '../../../../base/browser/ui/tree/objectTree.js';
23
import { ObjectTreeModel } from '../../../../base/browser/ui/tree/objectTreeModel.js';
24
import { ITreeFilter, ITreeModel, ITreeNode, ITreeRenderer, TreeFilterResult, TreeVisibility } from '../../../../base/browser/ui/tree/tree.js';
25
import { Action, IAction, Separator } from '../../../../base/common/actions.js';
26
import { distinct } from '../../../../base/common/arrays.js';
27
import { Codicon } from '../../../../base/common/codicons.js';
28
import { onUnexpectedError } from '../../../../base/common/errors.js';
29
import { Emitter, Event } from '../../../../base/common/event.js';
30
import { IJSONSchema } from '../../../../base/common/jsonSchema.js';
31
import { KeyCode } from '../../../../base/common/keyCodes.js';
32
import { Disposable, DisposableStore, isDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
33
import { isIOS } from '../../../../base/common/platform.js';
34
import { escapeRegExpCharacters } from '../../../../base/common/strings.js';
35
import { isDefined, isUndefinedOrNull } from '../../../../base/common/types.js';
36
import { URI } from '../../../../base/common/uri.js';
37
import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js';
38
import { ILanguageService } from '../../../../editor/common/languages/language.js';
39
import { localize } from '../../../../nls.js';
40
import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
41
import { ICommandService } from '../../../../platform/commands/common/commands.js';
42
import { ConfigurationTarget, IConfigurationService, getLanguageTagSettingPlainKey } from '../../../../platform/configuration/common/configuration.js';
43
import { ConfigurationScope } from '../../../../platform/configuration/common/configurationRegistry.js';
44
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
45
import { IContextMenuService, IContextViewService } from '../../../../platform/contextview/browser/contextView.js';
46
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
47
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
48
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
49
import { IListService, WorkbenchObjectTree } from '../../../../platform/list/browser/listService.js';
50
import { ILogService } from '../../../../platform/log/common/log.js';
51
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
52
import { IProductService } from '../../../../platform/product/common/productService.js';
53
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
54
import { defaultButtonStyles, getInputBoxStyle, getListStyles, getSelectBoxStyles } from '../../../../platform/theme/browser/defaultStyles.js';
55
import { editorBackground, foreground } from '../../../../platform/theme/common/colorRegistry.js';
56
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
57
import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js';
58
import { getIgnoredSettings } from '../../../../platform/userDataSync/common/settingsMerge.js';
59
import { IUserDataSyncEnablementService, getDefaultIgnoredSettings } from '../../../../platform/userDataSync/common/userDataSync.js';
60
import { hasNativeContextMenu } from '../../../../platform/window/common/window.js';
61
import { APPLICATION_SCOPES, APPLY_ALL_PROFILES_SETTING, IWorkbenchConfigurationService } from '../../../services/configuration/common/configuration.js';
62
import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js';
63
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
64
import { ISetting, ISettingsGroup, SETTINGS_AUTHORITY, SettingValueType } from '../../../services/preferences/common/preferences.js';
65
import { getInvalidTypeError } from '../../../services/preferences/common/preferencesValidation.js';
66
import { IExtensionsWorkbenchService } from '../../extensions/common/extensions.js';
67
import { LANGUAGE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU, compareTwoNullableNumbers } from '../common/preferences.js';
68
import { settingsNumberInputBackground, settingsNumberInputBorder, settingsNumberInputForeground, settingsSelectBackground, settingsSelectBorder, settingsSelectForeground, settingsSelectListBorder, settingsTextInputBackground, settingsTextInputBorder, settingsTextInputForeground } from '../common/settingsEditorColorRegistry.js';
69
import { settingsMoreActionIcon } from './preferencesIcons.js';
70
import { SettingsTarget } from './preferencesWidgets.js';
71
import { ISettingOverrideClickEvent, SettingsTreeIndicatorsLabel, getIndicatorsLabelAriaLabel } from './settingsEditorSettingIndicators.js';
72
import { ITOCEntry } from './settingsLayout.js';
73
import { ISettingsEditorViewState, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeNewExtensionsElement, SettingsTreeSettingElement, inspectSetting, objectSettingSupportsRemoveDefaultValue, settingKeyToDisplayFormat } from './settingsTreeModels.js';
74
import { ExcludeSettingWidget, IBoolObjectDataItem, IIncludeExcludeDataItem, IListDataItem, IObjectDataItem, IObjectEnumOption, IObjectKeySuggester, IObjectValueSuggester, IncludeSettingWidget, ListSettingWidget, ObjectSettingCheckboxWidget, ObjectSettingDropdownWidget, ObjectValue, SettingListEvent } from './settingsWidgets.js';
75
76
const $ = DOM.$;
77
78
function getIncludeExcludeDisplayValue(element: SettingsTreeSettingElement): IIncludeExcludeDataItem[] {
79
const elementDefaultValue: Record<string, unknown> = typeof element.defaultValue === 'object'
80
? element.defaultValue ?? {}
81
: {};
82
83
const data = element.isConfigured ?
84
{ ...elementDefaultValue, ...element.scopeValue } :
85
elementDefaultValue;
86
87
return Object.keys(data)
88
.filter(key => !!data[key])
89
.map(key => {
90
const defaultValue = elementDefaultValue[key];
91
92
// Get source if it's a default value
93
let source: string | undefined;
94
if (defaultValue === data[key] && element.setting.type === 'object' && element.defaultValueSource instanceof Map) {
95
const defaultSource = element.defaultValueSource.get(`${element.setting.key}.${key}`);
96
source = typeof defaultSource === 'string' ? defaultSource : defaultSource?.displayName;
97
}
98
99
const value = data[key];
100
const sibling = typeof value === 'boolean' ? undefined : value.when;
101
return {
102
value: {
103
type: 'string',
104
data: key
105
},
106
sibling,
107
elementType: element.valueType,
108
source
109
};
110
});
111
}
112
113
function areAllPropertiesDefined(properties: string[], itemsToDisplay: IObjectDataItem[]): boolean {
114
const staticProperties = new Set(properties);
115
itemsToDisplay.forEach(({ key }) => staticProperties.delete(key.data));
116
return staticProperties.size === 0;
117
}
118
119
function getEnumOptionsFromSchema(schema: IJSONSchema): IObjectEnumOption[] {
120
if (schema.anyOf) {
121
return schema.anyOf.map(getEnumOptionsFromSchema).flat();
122
}
123
124
const enumDescriptions = schema.enumDescriptions ?? [];
125
126
return (schema.enum ?? []).map((value, idx) => {
127
const description = idx < enumDescriptions.length
128
? enumDescriptions[idx]
129
: undefined;
130
131
return { value, description };
132
});
133
}
134
135
function getObjectValueType(schema: IJSONSchema): ObjectValue['type'] {
136
if (schema.anyOf) {
137
const subTypes = schema.anyOf.map(getObjectValueType);
138
if (subTypes.some(type => type === 'enum')) {
139
return 'enum';
140
}
141
return 'string';
142
}
143
144
if (schema.type === 'boolean') {
145
return 'boolean';
146
} else if (schema.type === 'string' && isDefined(schema.enum) && schema.enum.length > 0) {
147
return 'enum';
148
} else {
149
return 'string';
150
}
151
}
152
153
function getObjectEntryValueDisplayValue(type: ObjectValue['type'], data: unknown, options: IObjectEnumOption[]): ObjectValue {
154
if (type === 'boolean') {
155
return { type, data: !!data };
156
} else if (type === 'enum') {
157
return { type, data: '' + data, options };
158
} else {
159
return { type, data: '' + data };
160
}
161
}
162
163
function getObjectDisplayValue(element: SettingsTreeSettingElement): IObjectDataItem[] {
164
const elementDefaultValue: Record<string, unknown> = typeof element.defaultValue === 'object'
165
? element.defaultValue ?? {}
166
: {};
167
168
const elementScopeValue: Record<string, unknown> = typeof element.scopeValue === 'object'
169
? element.scopeValue ?? {}
170
: {};
171
172
const data = element.isConfigured ?
173
{ ...elementDefaultValue, ...elementScopeValue } :
174
element.hasPolicyValue ? element.scopeValue :
175
elementDefaultValue;
176
177
const { objectProperties, objectPatternProperties, objectAdditionalProperties } = element.setting;
178
const patternsAndSchemas = Object
179
.entries(objectPatternProperties ?? {})
180
.map(([pattern, schema]) => ({
181
pattern: new RegExp(pattern),
182
schema
183
}));
184
185
const wellDefinedKeyEnumOptions = Object.entries(objectProperties ?? {}).map(
186
([key, schema]) => ({ value: key, description: schema.description })
187
);
188
189
return Object.keys(data).map(key => {
190
const defaultValue = elementDefaultValue[key];
191
192
// Get source if it's a default value
193
let source: string | undefined;
194
if (defaultValue === data[key] && element.setting.type === 'object' && element.defaultValueSource instanceof Map) {
195
const defaultSource = element.defaultValueSource.get(`${element.setting.key}.${key}`);
196
source = typeof defaultSource === 'string' ? defaultSource : defaultSource?.displayName;
197
}
198
199
if (isDefined(objectProperties) && key in objectProperties) {
200
const valueEnumOptions = getEnumOptionsFromSchema(objectProperties[key]);
201
return {
202
key: {
203
type: 'enum',
204
data: key,
205
options: wellDefinedKeyEnumOptions,
206
},
207
value: getObjectEntryValueDisplayValue(getObjectValueType(objectProperties[key]), data[key], valueEnumOptions),
208
keyDescription: objectProperties[key].description,
209
removable: isUndefinedOrNull(defaultValue),
210
resetable: !isUndefinedOrNull(defaultValue),
211
source
212
} satisfies IObjectDataItem;
213
}
214
215
// The row is removable if it doesn't have a default value assigned or the setting supports removing the default value.
216
// If a default value is assigned and the user modified the default, it can be reset back to the default.
217
const removable = defaultValue === undefined || objectSettingSupportsRemoveDefaultValue(element.setting.key);
218
const resetable = !!defaultValue && defaultValue !== data[key];
219
const schema = patternsAndSchemas.find(({ pattern }) => pattern.test(key))?.schema;
220
if (schema) {
221
const valueEnumOptions = getEnumOptionsFromSchema(schema);
222
return {
223
key: { type: 'string', data: key },
224
value: getObjectEntryValueDisplayValue(getObjectValueType(schema), data[key], valueEnumOptions),
225
keyDescription: schema.description,
226
removable,
227
resetable,
228
source
229
} satisfies IObjectDataItem;
230
}
231
232
const additionalValueEnums = getEnumOptionsFromSchema(
233
typeof objectAdditionalProperties === 'boolean'
234
? {}
235
: objectAdditionalProperties ?? {}
236
);
237
238
return {
239
key: { type: 'string', data: key },
240
value: getObjectEntryValueDisplayValue(
241
typeof objectAdditionalProperties === 'object' ? getObjectValueType(objectAdditionalProperties) : 'string',
242
data[key],
243
additionalValueEnums,
244
),
245
keyDescription: typeof objectAdditionalProperties === 'object' ? objectAdditionalProperties.description : undefined,
246
removable,
247
resetable,
248
source
249
} satisfies IObjectDataItem;
250
}).filter(item => !isUndefinedOrNull(item.value.data));
251
}
252
253
function getBoolObjectDisplayValue(element: SettingsTreeSettingElement): IBoolObjectDataItem[] {
254
const elementDefaultValue: Record<string, unknown> = typeof element.defaultValue === 'object'
255
? element.defaultValue ?? {}
256
: {};
257
258
const elementScopeValue: Record<string, unknown> = typeof element.scopeValue === 'object'
259
? element.scopeValue ?? {}
260
: {};
261
262
const data = element.isConfigured ?
263
{ ...elementDefaultValue, ...elementScopeValue } :
264
elementDefaultValue;
265
266
const { objectProperties } = element.setting;
267
const displayValues: IBoolObjectDataItem[] = [];
268
for (const key in objectProperties) {
269
const defaultValue = elementDefaultValue[key];
270
271
// Get source if it's a default value
272
let source: string | undefined;
273
if (defaultValue === data[key] && element.setting.type === 'object' && element.defaultValueSource instanceof Map) {
274
const defaultSource = element.defaultValueSource.get(key);
275
source = typeof defaultSource === 'string' ? defaultSource : defaultSource?.displayName;
276
}
277
278
displayValues.push({
279
key: {
280
type: 'string',
281
data: key
282
},
283
value: {
284
type: 'boolean',
285
data: !!data[key]
286
},
287
keyDescription: objectProperties[key].description,
288
removable: false,
289
resetable: true,
290
source
291
});
292
}
293
return displayValues;
294
}
295
296
function createArraySuggester(element: SettingsTreeSettingElement): IObjectKeySuggester {
297
return (keys, idx) => {
298
const enumOptions: IObjectEnumOption[] = [];
299
300
if (element.setting.enum) {
301
element.setting.enum.forEach((key, i) => {
302
// include the currently selected value, even if uniqueItems is true
303
if (!element.setting.uniqueItems || (idx !== undefined && key === keys[idx]) || !keys.includes(key)) {
304
const description = element.setting.enumDescriptions?.[i];
305
enumOptions.push({ value: key, description });
306
}
307
});
308
}
309
310
return enumOptions.length > 0
311
? { type: 'enum', data: enumOptions[0].value, options: enumOptions }
312
: undefined;
313
};
314
}
315
316
function createObjectKeySuggester(element: SettingsTreeSettingElement): IObjectKeySuggester {
317
const { objectProperties } = element.setting;
318
const allStaticKeys = Object.keys(objectProperties ?? {});
319
320
return keys => {
321
const existingKeys = new Set(keys);
322
const enumOptions: IObjectEnumOption[] = [];
323
324
allStaticKeys.forEach(staticKey => {
325
if (!existingKeys.has(staticKey)) {
326
enumOptions.push({ value: staticKey, description: objectProperties![staticKey].description });
327
}
328
});
329
330
return enumOptions.length > 0
331
? { type: 'enum', data: enumOptions[0].value, options: enumOptions }
332
: undefined;
333
};
334
}
335
336
function createObjectValueSuggester(element: SettingsTreeSettingElement): IObjectValueSuggester {
337
const { objectProperties, objectPatternProperties, objectAdditionalProperties } = element.setting;
338
339
const patternsAndSchemas = Object
340
.entries(objectPatternProperties ?? {})
341
.map(([pattern, schema]) => ({
342
pattern: new RegExp(pattern),
343
schema
344
}));
345
346
return (key: string) => {
347
let suggestedSchema: IJSONSchema | undefined;
348
349
if (isDefined(objectProperties) && key in objectProperties) {
350
suggestedSchema = objectProperties[key];
351
}
352
353
const patternSchema = suggestedSchema ?? patternsAndSchemas.find(({ pattern }) => pattern.test(key))?.schema;
354
355
if (isDefined(patternSchema)) {
356
suggestedSchema = patternSchema;
357
} else if (isDefined(objectAdditionalProperties) && typeof objectAdditionalProperties === 'object') {
358
suggestedSchema = objectAdditionalProperties;
359
}
360
361
if (isDefined(suggestedSchema)) {
362
const type = getObjectValueType(suggestedSchema);
363
364
if (type === 'boolean') {
365
return { type, data: suggestedSchema.default ?? true };
366
} else if (type === 'enum') {
367
const options = getEnumOptionsFromSchema(suggestedSchema);
368
return { type, data: suggestedSchema.default ?? options[0].value, options };
369
} else {
370
return { type, data: suggestedSchema.default ?? '' };
371
}
372
}
373
374
return;
375
};
376
}
377
378
function isNonNullableNumericType(type: unknown): type is 'number' | 'integer' {
379
return type === 'number' || type === 'integer';
380
}
381
382
function parseNumericObjectValues(dataElement: SettingsTreeSettingElement, v: Record<string, unknown>): Record<string, unknown> {
383
const newRecord: Record<string, unknown> = {};
384
for (const key in v) {
385
// Set to true/false once we're sure of the answer
386
let keyMatchesNumericProperty: boolean | undefined;
387
const patternProperties = dataElement.setting.objectPatternProperties;
388
const properties = dataElement.setting.objectProperties;
389
const additionalProperties = dataElement.setting.objectAdditionalProperties;
390
391
// Match the current record key against the properties of the object
392
if (properties) {
393
for (const propKey in properties) {
394
if (propKey === key) {
395
keyMatchesNumericProperty = isNonNullableNumericType(properties[propKey].type);
396
break;
397
}
398
}
399
}
400
if (keyMatchesNumericProperty === undefined && patternProperties) {
401
for (const patternKey in patternProperties) {
402
if (key.match(patternKey)) {
403
keyMatchesNumericProperty = isNonNullableNumericType(patternProperties[patternKey].type);
404
break;
405
}
406
}
407
}
408
if (keyMatchesNumericProperty === undefined && additionalProperties && typeof additionalProperties !== 'boolean') {
409
if (isNonNullableNumericType(additionalProperties.type)) {
410
keyMatchesNumericProperty = true;
411
}
412
}
413
newRecord[key] = keyMatchesNumericProperty ? Number(v[key]) : v[key];
414
}
415
return newRecord;
416
}
417
418
function getListDisplayValue(element: SettingsTreeSettingElement): IListDataItem[] {
419
if (!element.value || !Array.isArray(element.value)) {
420
return [];
421
}
422
423
if (element.setting.arrayItemType === 'enum') {
424
let enumOptions: IObjectEnumOption[] = [];
425
if (element.setting.enum) {
426
enumOptions = element.setting.enum.map((setting, i) => {
427
return {
428
value: setting,
429
description: element.setting.enumDescriptions?.[i]
430
};
431
});
432
}
433
return element.value.map((key: string) => {
434
return {
435
value: {
436
type: 'enum',
437
data: key,
438
options: enumOptions
439
}
440
};
441
});
442
} else {
443
return element.value.map((key: string) => {
444
return {
445
value: {
446
type: 'string',
447
data: key
448
}
449
};
450
});
451
}
452
}
453
454
function getShowAddButtonList(dataElement: SettingsTreeSettingElement, listDisplayValue: IListDataItem[]): boolean {
455
if (dataElement.setting.enum && dataElement.setting.uniqueItems) {
456
return dataElement.setting.enum.length - listDisplayValue.length > 0;
457
} else {
458
return true;
459
}
460
}
461
462
export function resolveSettingsTree(tocData: ITOCEntry<string>, coreSettingsGroups: ISettingsGroup[], logService: ILogService): { tree: ITOCEntry<ISetting>; leftoverSettings: Set<ISetting> } {
463
const allSettings = getFlatSettings(coreSettingsGroups);
464
return {
465
tree: _resolveSettingsTree(tocData, allSettings, logService),
466
leftoverSettings: allSettings
467
};
468
}
469
470
export function resolveConfiguredUntrustedSettings(groups: ISettingsGroup[], target: SettingsTarget, languageFilter: string | undefined, configurationService: IWorkbenchConfigurationService): ISetting[] {
471
const allSettings = getFlatSettings(groups);
472
return [...allSettings].filter(setting => setting.restricted && inspectSetting(setting.key, target, languageFilter, configurationService).isConfigured);
473
}
474
475
export async function createTocTreeForExtensionSettings(extensionService: IExtensionService, groups: ISettingsGroup[]): Promise<ITOCEntry<ISetting>> {
476
const extGroupTree = new Map<string, ITOCEntry<ISetting>>();
477
const addEntryToTree = (extensionId: string, extensionName: string, childEntry: ITOCEntry<ISetting>) => {
478
if (!extGroupTree.has(extensionId)) {
479
const rootEntry = {
480
id: extensionId,
481
label: extensionName,
482
children: []
483
};
484
extGroupTree.set(extensionId, rootEntry);
485
}
486
extGroupTree.get(extensionId)!.children!.push(childEntry);
487
};
488
const processGroupEntry = async (group: ISettingsGroup) => {
489
const flatSettings = group.sections.map(section => section.settings).flat();
490
491
const extensionId = group.extensionInfo!.id;
492
const extension = await extensionService.getExtension(extensionId);
493
const extensionName = extension?.displayName ?? extension?.name ?? extensionId;
494
495
// There could be multiple groups with the same extension id that all belong to the same extension.
496
// To avoid highlighting all groups upon expanding the extension's ToC entry,
497
// use the group ID only if it is non-empty and isn't the extension ID.
498
// Ref https://github.com/microsoft/vscode/issues/241521.
499
const settingGroupId = (group.id && group.id !== extensionId) ? group.id : group.title;
500
501
const childEntry: ITOCEntry<ISetting> = {
502
id: settingGroupId,
503
label: group.title,
504
order: group.order,
505
settings: flatSettings
506
};
507
addEntryToTree(extensionId, extensionName, childEntry);
508
};
509
510
const processPromises = groups.map(g => processGroupEntry(g));
511
return Promise.all(processPromises).then(() => {
512
const extGroups: ITOCEntry<ISetting>[] = [];
513
for (const extensionRootEntry of extGroupTree.values()) {
514
for (const child of extensionRootEntry.children!) {
515
// Sort the individual settings of the child by order.
516
// Leave the undefined order settings untouched.
517
child.settings?.sort((a, b) => {
518
return compareTwoNullableNumbers(a.order, b.order);
519
});
520
}
521
522
if (extensionRootEntry.children!.length === 1) {
523
// There is a single category for this extension.
524
// Push a flattened setting.
525
extGroups.push({
526
id: extensionRootEntry.id,
527
label: extensionRootEntry.children![0].label,
528
settings: extensionRootEntry.children![0].settings
529
});
530
} else {
531
// Sort the categories.
532
// Leave the undefined order categories untouched.
533
extensionRootEntry.children!.sort((a, b) => {
534
return compareTwoNullableNumbers(a.order, b.order);
535
});
536
537
// If there is a category that matches the setting name,
538
// add the settings in manually as "ungrouped" settings.
539
// https://github.com/microsoft/vscode/issues/137259
540
const ungroupedChild = extensionRootEntry.children!.find(child => child.label === extensionRootEntry.label);
541
if (ungroupedChild && !ungroupedChild.children) {
542
const groupedChildren = extensionRootEntry.children!.filter(child => child !== ungroupedChild);
543
extGroups.push({
544
id: extensionRootEntry.id,
545
label: extensionRootEntry.label,
546
settings: ungroupedChild.settings,
547
children: groupedChildren
548
});
549
} else {
550
// Push all the groups as-is.
551
extGroups.push(extensionRootEntry);
552
}
553
}
554
}
555
556
// Sort the outermost settings.
557
extGroups.sort((a, b) => a.label.localeCompare(b.label));
558
559
return {
560
id: 'extensions',
561
label: localize('extensions', "Extensions"),
562
children: extGroups
563
};
564
});
565
}
566
567
function _resolveSettingsTree(tocData: ITOCEntry<string>, allSettings: Set<ISetting>, logService: ILogService): ITOCEntry<ISetting> {
568
let children: ITOCEntry<ISetting>[] | undefined;
569
if (tocData.children) {
570
children = tocData.children
571
.filter(child => child.hide !== true)
572
.map(child => _resolveSettingsTree(child, allSettings, logService))
573
.filter(child => child.children?.length || child.settings?.length);
574
}
575
576
let settings: ISetting[] | undefined;
577
if (tocData.settings) {
578
settings = tocData.settings.map(pattern => getMatchingSettings(allSettings, pattern, logService)).flat();
579
}
580
581
if (!children && !settings) {
582
throw new Error(`TOC node has no child groups or settings: ${tocData.id}`);
583
}
584
585
return {
586
id: tocData.id,
587
label: tocData.label,
588
children,
589
settings
590
};
591
}
592
593
const knownDynamicSettingGroups = [
594
/^settingsSync\..*/,
595
/^sync\..*/,
596
/^workbench.fontAliasing$/,
597
];
598
599
function getMatchingSettings(allSettings: Set<ISetting>, pattern: string, logService: ILogService): ISetting[] {
600
const result: ISetting[] = [];
601
602
allSettings.forEach(s => {
603
if (settingMatches(s, pattern)) {
604
result.push(s);
605
allSettings.delete(s);
606
}
607
});
608
609
if (!result.length && !knownDynamicSettingGroups.some(r => r.test(pattern))) {
610
logService.warn(`Settings pattern "${pattern}" doesn't match any settings`);
611
}
612
613
return result.sort((a, b) => a.key.localeCompare(b.key));
614
}
615
616
const settingPatternCache = new Map<string, RegExp>();
617
618
export function createSettingMatchRegExp(pattern: string): RegExp {
619
pattern = escapeRegExpCharacters(pattern)
620
.replace(/\\\*/g, '.*');
621
622
return new RegExp(`^${pattern}$`, 'i');
623
}
624
625
function settingMatches(s: ISetting, pattern: string): boolean {
626
let regExp = settingPatternCache.get(pattern);
627
if (!regExp) {
628
regExp = createSettingMatchRegExp(pattern);
629
settingPatternCache.set(pattern, regExp);
630
}
631
632
return regExp.test(s.key);
633
}
634
635
function getFlatSettings(settingsGroups: ISettingsGroup[]) {
636
const result: Set<ISetting> = new Set();
637
638
for (const group of settingsGroups) {
639
for (const section of group.sections) {
640
for (const s of section.settings) {
641
if (!s.overrides || !s.overrides.length) {
642
result.add(s);
643
}
644
}
645
}
646
}
647
648
return result;
649
}
650
651
interface IDisposableTemplate {
652
readonly toDispose: DisposableStore;
653
}
654
655
interface ISettingItemTemplate<T = any> extends IDisposableTemplate {
656
onChange?: (value: T) => void;
657
658
context?: SettingsTreeSettingElement;
659
containerElement: HTMLElement;
660
categoryElement: HTMLElement;
661
labelElement: SimpleIconLabel;
662
descriptionElement: HTMLElement;
663
controlElement: HTMLElement;
664
deprecationWarningElement: HTMLElement;
665
indicatorsLabel: SettingsTreeIndicatorsLabel;
666
toolbar: ToolBar;
667
readonly elementDisposables: DisposableStore;
668
}
669
670
interface ISettingBoolItemTemplate extends ISettingItemTemplate<boolean> {
671
checkbox: Toggle;
672
}
673
674
interface ISettingExtensionToggleItemTemplate extends ISettingItemTemplate<undefined> {
675
actionButton: Button;
676
dismissButton: Button;
677
}
678
679
interface ISettingTextItemTemplate extends ISettingItemTemplate<string> {
680
inputBox: InputBox;
681
validationErrorMessageElement: HTMLElement;
682
}
683
684
type ISettingNumberItemTemplate = ISettingTextItemTemplate;
685
686
interface ISettingEnumItemTemplate extends ISettingItemTemplate<number> {
687
selectBox: SelectBox;
688
selectElement: HTMLSelectElement | null;
689
enumDescriptionElement: HTMLElement;
690
}
691
692
interface ISettingComplexItemTemplate extends ISettingItemTemplate<void> {
693
button: HTMLElement;
694
validationErrorMessageElement: HTMLElement;
695
}
696
697
interface ISettingComplexObjectItemTemplate extends ISettingComplexItemTemplate {
698
objectSettingWidget: ObjectSettingDropdownWidget;
699
}
700
701
interface ISettingListItemTemplate extends ISettingItemTemplate<string[] | undefined> {
702
listWidget: ListSettingWidget<IListDataItem>;
703
validationErrorMessageElement: HTMLElement;
704
}
705
706
interface ISettingIncludeExcludeItemTemplate extends ISettingItemTemplate<void> {
707
includeExcludeWidget: ListSettingWidget<IIncludeExcludeDataItem>;
708
}
709
710
interface ISettingObjectItemTemplate extends ISettingItemTemplate<Record<string, unknown> | undefined> {
711
objectDropdownWidget?: ObjectSettingDropdownWidget;
712
objectCheckboxWidget?: ObjectSettingCheckboxWidget;
713
validationErrorMessageElement: HTMLElement;
714
}
715
716
interface ISettingNewExtensionsTemplate extends IDisposableTemplate {
717
button: Button;
718
context?: SettingsTreeNewExtensionsElement;
719
}
720
721
interface IGroupTitleTemplate extends IDisposableTemplate {
722
context?: SettingsTreeGroupElement;
723
parent: HTMLElement;
724
}
725
726
const SETTINGS_TEXT_TEMPLATE_ID = 'settings.text.template';
727
const SETTINGS_MULTILINE_TEXT_TEMPLATE_ID = 'settings.multilineText.template';
728
const SETTINGS_NUMBER_TEMPLATE_ID = 'settings.number.template';
729
const SETTINGS_ENUM_TEMPLATE_ID = 'settings.enum.template';
730
const SETTINGS_BOOL_TEMPLATE_ID = 'settings.bool.template';
731
const SETTINGS_ARRAY_TEMPLATE_ID = 'settings.array.template';
732
const SETTINGS_EXCLUDE_TEMPLATE_ID = 'settings.exclude.template';
733
const SETTINGS_INCLUDE_TEMPLATE_ID = 'settings.include.template';
734
const SETTINGS_OBJECT_TEMPLATE_ID = 'settings.object.template';
735
const SETTINGS_BOOL_OBJECT_TEMPLATE_ID = 'settings.boolObject.template';
736
const SETTINGS_COMPLEX_TEMPLATE_ID = 'settings.complex.template';
737
const SETTINGS_COMPLEX_OBJECT_TEMPLATE_ID = 'settings.complexObject.template';
738
const SETTINGS_NEW_EXTENSIONS_TEMPLATE_ID = 'settings.newExtensions.template';
739
const SETTINGS_ELEMENT_TEMPLATE_ID = 'settings.group.template';
740
const SETTINGS_EXTENSION_TOGGLE_TEMPLATE_ID = 'settings.extensionToggle.template';
741
742
export interface ISettingChangeEvent {
743
key: string;
744
value: any; // undefined => reset/unconfigure
745
type: SettingValueType | SettingValueType[];
746
manualReset: boolean;
747
scope: ConfigurationScope | undefined;
748
}
749
750
export interface ISettingLinkClickEvent {
751
source: SettingsTreeSettingElement;
752
targetKey: string;
753
}
754
755
function removeChildrenFromTabOrder(node: Element): void {
756
const focusableElements = node.querySelectorAll(`
757
[tabindex="0"],
758
input:not([tabindex="-1"]),
759
select:not([tabindex="-1"]),
760
textarea:not([tabindex="-1"]),
761
a:not([tabindex="-1"]),
762
button:not([tabindex="-1"]),
763
area:not([tabindex="-1"])
764
`);
765
766
focusableElements.forEach(element => {
767
element.setAttribute(AbstractSettingRenderer.ELEMENT_FOCUSABLE_ATTR, 'true');
768
element.setAttribute('tabindex', '-1');
769
});
770
}
771
772
function addChildrenToTabOrder(node: Element): void {
773
const focusableElements = node.querySelectorAll(
774
`[${AbstractSettingRenderer.ELEMENT_FOCUSABLE_ATTR}="true"]`
775
);
776
777
focusableElements.forEach(element => {
778
element.removeAttribute(AbstractSettingRenderer.ELEMENT_FOCUSABLE_ATTR);
779
element.setAttribute('tabindex', '0');
780
});
781
}
782
783
export interface HeightChangeParams {
784
element: SettingsTreeElement;
785
height: number;
786
}
787
788
export abstract class AbstractSettingRenderer extends Disposable implements ITreeRenderer<SettingsTreeElement, never, any> {
789
/** To override */
790
abstract get templateId(): string;
791
792
static readonly CONTROL_CLASS = 'setting-control-focus-target';
793
static readonly CONTROL_SELECTOR = '.' + this.CONTROL_CLASS;
794
static readonly CONTENTS_CLASS = 'setting-item-contents';
795
static readonly CONTENTS_SELECTOR = '.' + this.CONTENTS_CLASS;
796
static readonly ALL_ROWS_SELECTOR = '.monaco-list-row';
797
798
static readonly SETTING_KEY_ATTR = 'data-key';
799
static readonly SETTING_ID_ATTR = 'data-id';
800
static readonly ELEMENT_FOCUSABLE_ATTR = 'data-focusable';
801
802
private readonly _onDidClickOverrideElement = this._register(new Emitter<ISettingOverrideClickEvent>());
803
readonly onDidClickOverrideElement: Event<ISettingOverrideClickEvent> = this._onDidClickOverrideElement.event;
804
805
protected readonly _onDidChangeSetting = this._register(new Emitter<ISettingChangeEvent>());
806
readonly onDidChangeSetting: Event<ISettingChangeEvent> = this._onDidChangeSetting.event;
807
808
protected readonly _onDidOpenSettings = this._register(new Emitter<string>());
809
readonly onDidOpenSettings: Event<string> = this._onDidOpenSettings.event;
810
811
private readonly _onDidClickSettingLink = this._register(new Emitter<ISettingLinkClickEvent>());
812
readonly onDidClickSettingLink: Event<ISettingLinkClickEvent> = this._onDidClickSettingLink.event;
813
814
protected readonly _onDidFocusSetting = this._register(new Emitter<SettingsTreeSettingElement>());
815
readonly onDidFocusSetting: Event<SettingsTreeSettingElement> = this._onDidFocusSetting.event;
816
817
private ignoredSettings: string[];
818
private readonly _onDidChangeIgnoredSettings = this._register(new Emitter<void>());
819
readonly onDidChangeIgnoredSettings: Event<void> = this._onDidChangeIgnoredSettings.event;
820
821
protected readonly _onDidChangeSettingHeight = this._register(new Emitter<HeightChangeParams>());
822
readonly onDidChangeSettingHeight: Event<HeightChangeParams> = this._onDidChangeSettingHeight.event;
823
824
protected readonly _onApplyFilter = this._register(new Emitter<string>());
825
readonly onApplyFilter: Event<string> = this._onApplyFilter.event;
826
827
private readonly markdownRenderer: MarkdownRenderer;
828
829
constructor(
830
private readonly settingActions: IAction[],
831
private readonly disposableActionFactory: (setting: ISetting, settingTarget: SettingsTarget) => IAction[],
832
@IThemeService protected readonly _themeService: IThemeService,
833
@IContextViewService protected readonly _contextViewService: IContextViewService,
834
@IOpenerService protected readonly _openerService: IOpenerService,
835
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
836
@ICommandService protected readonly _commandService: ICommandService,
837
@IContextMenuService protected readonly _contextMenuService: IContextMenuService,
838
@IKeybindingService protected readonly _keybindingService: IKeybindingService,
839
@IConfigurationService protected readonly _configService: IConfigurationService,
840
@IExtensionService protected readonly _extensionsService: IExtensionService,
841
@IExtensionsWorkbenchService protected readonly _extensionsWorkbenchService: IExtensionsWorkbenchService,
842
@IProductService protected readonly _productService: IProductService,
843
@ITelemetryService protected readonly _telemetryService: ITelemetryService,
844
@IHoverService protected readonly _hoverService: IHoverService,
845
) {
846
super();
847
848
this.markdownRenderer = _instantiationService.createInstance(MarkdownRenderer, {});
849
850
this.ignoredSettings = getIgnoredSettings(getDefaultIgnoredSettings(), this._configService);
851
this._register(this._configService.onDidChangeConfiguration(e => {
852
this.ignoredSettings = getIgnoredSettings(getDefaultIgnoredSettings(), this._configService);
853
this._onDidChangeIgnoredSettings.fire();
854
}));
855
}
856
857
abstract renderTemplate(container: HTMLElement): any;
858
859
abstract renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: any): void;
860
861
protected renderCommonTemplate(tree: any, _container: HTMLElement, typeClass: string): ISettingItemTemplate {
862
_container.classList.add('setting-item');
863
_container.classList.add('setting-item-' + typeClass);
864
865
const toDispose = new DisposableStore();
866
867
const container = DOM.append(_container, $(AbstractSettingRenderer.CONTENTS_SELECTOR));
868
container.classList.add('settings-row-inner-container');
869
const titleElement = DOM.append(container, $('.setting-item-title'));
870
const labelCategoryContainer = DOM.append(titleElement, $('.setting-item-cat-label-container'));
871
const categoryElement = DOM.append(labelCategoryContainer, $('span.setting-item-category'));
872
const labelElementContainer = DOM.append(labelCategoryContainer, $('span.setting-item-label'));
873
const labelElement = toDispose.add(new SimpleIconLabel(labelElementContainer));
874
const indicatorsLabel = toDispose.add(this._instantiationService.createInstance(SettingsTreeIndicatorsLabel, titleElement));
875
876
const descriptionElement = DOM.append(container, $('.setting-item-description'));
877
const modifiedIndicatorElement = DOM.append(container, $('.setting-item-modified-indicator'));
878
toDispose.add(this._hoverService.setupDelayedHover(modifiedIndicatorElement, {
879
content: localize('modified', "The setting has been configured in the current scope.")
880
}));
881
882
const valueElement = DOM.append(container, $('.setting-item-value'));
883
const controlElement = DOM.append(valueElement, $('div.setting-item-control'));
884
885
const deprecationWarningElement = DOM.append(container, $('.setting-item-deprecation-message'));
886
887
const toolbarContainer = DOM.append(container, $('.setting-toolbar-container'));
888
const toolbar = this.renderSettingToolbar(toolbarContainer);
889
890
const template: ISettingItemTemplate = {
891
toDispose,
892
elementDisposables: toDispose.add(new DisposableStore()),
893
894
containerElement: container,
895
categoryElement,
896
labelElement,
897
descriptionElement,
898
controlElement,
899
deprecationWarningElement,
900
indicatorsLabel,
901
toolbar
902
};
903
904
// Prevent clicks from being handled by list
905
toDispose.add(DOM.addDisposableListener(controlElement, DOM.EventType.MOUSE_DOWN, e => e.stopPropagation()));
906
907
toDispose.add(DOM.addDisposableListener(titleElement, DOM.EventType.MOUSE_ENTER, e => container.classList.add('mouseover')));
908
toDispose.add(DOM.addDisposableListener(titleElement, DOM.EventType.MOUSE_LEAVE, e => container.classList.remove('mouseover')));
909
910
return template;
911
}
912
913
protected addSettingElementFocusHandler(template: ISettingItemTemplate): void {
914
const focusTracker = DOM.trackFocus(template.containerElement);
915
template.toDispose.add(focusTracker);
916
template.toDispose.add(focusTracker.onDidBlur(() => {
917
if (template.containerElement.classList.contains('focused')) {
918
template.containerElement.classList.remove('focused');
919
}
920
}));
921
922
template.toDispose.add(focusTracker.onDidFocus(() => {
923
template.containerElement.classList.add('focused');
924
925
if (template.context) {
926
this._onDidFocusSetting.fire(template.context);
927
}
928
}));
929
}
930
931
protected renderSettingToolbar(container: HTMLElement): ToolBar {
932
const toggleMenuKeybinding = this._keybindingService.lookupKeybinding(SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU);
933
let toggleMenuTitle = localize('settingsContextMenuTitle', "More Actions... ");
934
if (toggleMenuKeybinding) {
935
toggleMenuTitle += ` (${toggleMenuKeybinding && toggleMenuKeybinding.getLabel()})`;
936
}
937
938
const toolbar = new ToolBar(container, this._contextMenuService, {
939
toggleMenuTitle,
940
renderDropdownAsChildElement: !isIOS,
941
moreIcon: settingsMoreActionIcon
942
});
943
return toolbar;
944
}
945
946
protected renderSettingElement(node: ITreeNode<SettingsTreeSettingElement, never>, index: number, template: ISettingItemTemplate | ISettingBoolItemTemplate): void {
947
const element = node.element;
948
949
// The element must inspect itself to get information for
950
// the modified indicator and the overridden Settings indicators.
951
element.inspectSelf();
952
953
template.context = element;
954
template.toolbar.context = element;
955
const actions = this.disposableActionFactory(element.setting, element.settingsTarget);
956
actions.forEach(a => isDisposable(a) && template.elementDisposables.add(a));
957
template.toolbar.setActions([], [...this.settingActions, ...actions]);
958
959
const setting = element.setting;
960
961
template.containerElement.classList.toggle('is-configured', element.isConfigured);
962
template.containerElement.setAttribute(AbstractSettingRenderer.SETTING_KEY_ATTR, element.setting.key);
963
template.containerElement.setAttribute(AbstractSettingRenderer.SETTING_ID_ATTR, element.id);
964
965
const titleTooltip = setting.key + (element.isConfigured ? ' - Modified' : '');
966
template.categoryElement.textContent = element.displayCategory ? (element.displayCategory + ': ') : '';
967
template.elementDisposables.add(this._hoverService.setupDelayedHover(template.categoryElement, { content: titleTooltip }));
968
969
template.labelElement.text = element.displayLabel;
970
template.labelElement.title = titleTooltip;
971
972
template.descriptionElement.innerText = '';
973
if (element.setting.descriptionIsMarkdown) {
974
const renderedDescription = this.renderSettingMarkdown(element, template.containerElement, element.description, template.elementDisposables);
975
template.descriptionElement.appendChild(renderedDescription);
976
} else {
977
template.descriptionElement.innerText = element.description;
978
}
979
980
template.indicatorsLabel.updateScopeOverrides(element, this._onDidClickOverrideElement, this._onApplyFilter);
981
template.elementDisposables.add(this._configService.onDidChangeConfiguration(e => {
982
if (e.affectsConfiguration(APPLY_ALL_PROFILES_SETTING)) {
983
template.indicatorsLabel.updateScopeOverrides(element, this._onDidClickOverrideElement, this._onApplyFilter);
984
}
985
}));
986
987
const onChange = (value: any) => this._onDidChangeSetting.fire({
988
key: element.setting.key,
989
value,
990
type: template.context!.valueType,
991
manualReset: false,
992
scope: element.setting.scope
993
});
994
const deprecationText = element.setting.deprecationMessage || '';
995
if (deprecationText && element.setting.deprecationMessageIsMarkdown) {
996
template.deprecationWarningElement.innerText = '';
997
template.deprecationWarningElement.appendChild(this.renderSettingMarkdown(element, template.containerElement, element.setting.deprecationMessage!, template.elementDisposables));
998
} else {
999
template.deprecationWarningElement.innerText = deprecationText;
1000
}
1001
template.deprecationWarningElement.prepend($('.codicon.codicon-error'));
1002
template.containerElement.classList.toggle('is-deprecated', !!deprecationText);
1003
1004
this.renderValue(element, <ISettingItemTemplate>template, onChange);
1005
1006
template.indicatorsLabel.updateWorkspaceTrust(element);
1007
template.indicatorsLabel.updateSyncIgnored(element, this.ignoredSettings);
1008
template.indicatorsLabel.updateDefaultOverrideIndicator(element);
1009
template.indicatorsLabel.updatePreviewIndicator(element);
1010
template.elementDisposables.add(this.onDidChangeIgnoredSettings(() => {
1011
template.indicatorsLabel.updateSyncIgnored(element, this.ignoredSettings);
1012
}));
1013
1014
this.updateSettingTabbable(element, template);
1015
template.elementDisposables.add(element.onDidChangeTabbable(() => {
1016
this.updateSettingTabbable(element, template);
1017
}));
1018
}
1019
1020
private updateSettingTabbable(element: SettingsTreeSettingElement, template: ISettingItemTemplate | ISettingBoolItemTemplate): void {
1021
if (element.tabbable) {
1022
addChildrenToTabOrder(template.containerElement);
1023
} else {
1024
removeChildrenFromTabOrder(template.containerElement);
1025
}
1026
}
1027
1028
private renderSettingMarkdown(element: SettingsTreeSettingElement, container: HTMLElement, text: string, disposables: DisposableStore): HTMLElement {
1029
// Rewrite `#editor.fontSize#` to link format
1030
text = fixSettingLinks(text);
1031
1032
const renderedMarkdown = disposables.add(this.markdownRenderer.render({ value: text, isTrusted: true }, {
1033
actionHandler: (content: string) => {
1034
if (content.startsWith('#')) {
1035
const e: ISettingLinkClickEvent = {
1036
source: element,
1037
targetKey: content.substring(1)
1038
};
1039
this._onDidClickSettingLink.fire(e);
1040
} else {
1041
this._openerService.open(content, { allowCommands: true }).catch(onUnexpectedError);
1042
}
1043
},
1044
asyncRenderCallback: () => {
1045
const height = container.clientHeight;
1046
if (height) {
1047
this._onDidChangeSettingHeight.fire({ element, height });
1048
}
1049
},
1050
}));
1051
1052
renderedMarkdown.element.classList.add('setting-item-markdown');
1053
cleanRenderedMarkdown(renderedMarkdown.element);
1054
return renderedMarkdown.element;
1055
}
1056
1057
protected abstract renderValue(dataElement: SettingsTreeSettingElement, template: ISettingItemTemplate, onChange: (value: any) => void): void;
1058
1059
disposeTemplate(template: IDisposableTemplate): void {
1060
template.toDispose.dispose();
1061
}
1062
1063
disposeElement(_element: ITreeNode<SettingsTreeElement>, _index: number, template: IDisposableTemplate): void {
1064
(template as ISettingItemTemplate).elementDisposables?.clear();
1065
}
1066
}
1067
1068
class SettingGroupRenderer implements ITreeRenderer<SettingsTreeGroupElement, never, IGroupTitleTemplate> {
1069
templateId = SETTINGS_ELEMENT_TEMPLATE_ID;
1070
1071
renderTemplate(container: HTMLElement): IGroupTitleTemplate {
1072
container.classList.add('group-title');
1073
1074
const template: IGroupTitleTemplate = {
1075
parent: container,
1076
toDispose: new DisposableStore()
1077
};
1078
1079
return template;
1080
}
1081
1082
renderElement(element: ITreeNode<SettingsTreeGroupElement, never>, index: number, templateData: IGroupTitleTemplate): void {
1083
templateData.parent.innerText = '';
1084
const labelElement = DOM.append(templateData.parent, $('div.settings-group-title-label.settings-row-inner-container'));
1085
labelElement.classList.add(`settings-group-level-${element.element.level}`);
1086
labelElement.textContent = element.element.label;
1087
1088
if (element.element.isFirstGroup) {
1089
labelElement.classList.add('settings-group-first');
1090
}
1091
}
1092
1093
disposeTemplate(templateData: IGroupTitleTemplate): void {
1094
templateData.toDispose.dispose();
1095
}
1096
}
1097
1098
export class SettingNewExtensionsRenderer implements ITreeRenderer<SettingsTreeNewExtensionsElement, never, ISettingNewExtensionsTemplate> {
1099
templateId = SETTINGS_NEW_EXTENSIONS_TEMPLATE_ID;
1100
1101
constructor(
1102
@ICommandService private readonly _commandService: ICommandService,
1103
) {
1104
}
1105
1106
renderTemplate(container: HTMLElement): ISettingNewExtensionsTemplate {
1107
const toDispose = new DisposableStore();
1108
1109
container.classList.add('setting-item-new-extensions');
1110
1111
const button = new Button(container, { title: true, ...defaultButtonStyles });
1112
toDispose.add(button);
1113
toDispose.add(button.onDidClick(() => {
1114
if (template.context) {
1115
this._commandService.executeCommand('workbench.extensions.action.showExtensionsWithIds', template.context.extensionIds);
1116
}
1117
}));
1118
button.label = localize('newExtensionsButtonLabel', "Show matching extensions");
1119
button.element.classList.add('settings-new-extensions-button');
1120
1121
const template: ISettingNewExtensionsTemplate = {
1122
button,
1123
toDispose
1124
};
1125
1126
return template;
1127
}
1128
1129
renderElement(element: ITreeNode<SettingsTreeNewExtensionsElement, never>, index: number, templateData: ISettingNewExtensionsTemplate): void {
1130
templateData.context = element.element;
1131
}
1132
1133
disposeTemplate(template: IDisposableTemplate): void {
1134
template.toDispose.dispose();
1135
}
1136
}
1137
1138
export class SettingComplexRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingComplexItemTemplate> {
1139
private static readonly EDIT_IN_JSON_LABEL = localize('editInSettingsJson', "Edit in settings.json");
1140
1141
templateId = SETTINGS_COMPLEX_TEMPLATE_ID;
1142
1143
renderTemplate(container: HTMLElement): ISettingComplexItemTemplate {
1144
const common = this.renderCommonTemplate(null, container, 'complex');
1145
1146
const openSettingsButton = DOM.append(common.controlElement, $('a.edit-in-settings-button'));
1147
openSettingsButton.classList.add(AbstractSettingRenderer.CONTROL_CLASS);
1148
openSettingsButton.role = 'button';
1149
1150
const validationErrorMessageElement = $('.setting-item-validation-message');
1151
common.containerElement.appendChild(validationErrorMessageElement);
1152
1153
const template: ISettingComplexItemTemplate = {
1154
...common,
1155
button: openSettingsButton,
1156
validationErrorMessageElement
1157
};
1158
1159
this.addSettingElementFocusHandler(template);
1160
1161
return template;
1162
}
1163
1164
renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingComplexItemTemplate): void {
1165
super.renderSettingElement(element, index, templateData);
1166
}
1167
1168
protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingComplexItemTemplate, onChange: (value: string) => void): void {
1169
const plainKey = getLanguageTagSettingPlainKey(dataElement.setting.key);
1170
const editLanguageSettingLabel = localize('editLanguageSettingLabel', "Edit settings for {0}", plainKey);
1171
const isLanguageTagSetting = dataElement.setting.isLanguageTagSetting;
1172
template.button.textContent = isLanguageTagSetting
1173
? editLanguageSettingLabel
1174
: SettingComplexRenderer.EDIT_IN_JSON_LABEL;
1175
1176
const onClickOrKeydown = (e: UIEvent) => {
1177
if (isLanguageTagSetting) {
1178
this._onApplyFilter.fire(`@${LANGUAGE_SETTING_TAG}${plainKey.replaceAll(' ', '')}`);
1179
} else {
1180
this._onDidOpenSettings.fire(dataElement.setting.key);
1181
}
1182
e.preventDefault();
1183
e.stopPropagation();
1184
};
1185
template.elementDisposables.add(DOM.addDisposableListener(template.button, DOM.EventType.CLICK, (e) => {
1186
onClickOrKeydown(e);
1187
}));
1188
template.elementDisposables.add(DOM.addDisposableListener(template.button, DOM.EventType.KEY_DOWN, (e) => {
1189
const ev = new StandardKeyboardEvent(e);
1190
if (ev.equals(KeyCode.Space) || ev.equals(KeyCode.Enter)) {
1191
onClickOrKeydown(e);
1192
}
1193
}));
1194
1195
this.renderValidations(dataElement, template);
1196
1197
if (isLanguageTagSetting) {
1198
template.button.setAttribute('aria-label', editLanguageSettingLabel);
1199
} else {
1200
template.button.setAttribute('aria-label', `${SettingComplexRenderer.EDIT_IN_JSON_LABEL}: ${dataElement.setting.key}`);
1201
}
1202
}
1203
1204
private renderValidations(dataElement: SettingsTreeSettingElement, template: ISettingComplexItemTemplate) {
1205
const errMsg = dataElement.isConfigured && getInvalidTypeError(dataElement.value, dataElement.setting.type);
1206
if (errMsg) {
1207
template.containerElement.classList.add('invalid-input');
1208
template.validationErrorMessageElement.innerText = errMsg;
1209
return;
1210
}
1211
1212
template.containerElement.classList.remove('invalid-input');
1213
}
1214
}
1215
1216
class SettingComplexObjectRenderer extends SettingComplexRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingComplexObjectItemTemplate> {
1217
1218
override templateId = SETTINGS_COMPLEX_OBJECT_TEMPLATE_ID;
1219
1220
override renderTemplate(container: HTMLElement): ISettingComplexObjectItemTemplate {
1221
const common = this.renderCommonTemplate(null, container, 'list');
1222
1223
const objectSettingWidget = common.toDispose.add(this._instantiationService.createInstance(ObjectSettingDropdownWidget, common.controlElement));
1224
objectSettingWidget.domNode.classList.add(AbstractSettingRenderer.CONTROL_CLASS);
1225
1226
const openSettingsButton = DOM.append(DOM.append(common.controlElement, $('.complex-object-edit-in-settings-button-container')), $('a.complex-object.edit-in-settings-button'));
1227
openSettingsButton.classList.add(AbstractSettingRenderer.CONTROL_CLASS);
1228
openSettingsButton.role = 'button';
1229
1230
const validationErrorMessageElement = $('.setting-item-validation-message');
1231
common.containerElement.appendChild(validationErrorMessageElement);
1232
1233
const template: ISettingComplexObjectItemTemplate = {
1234
...common,
1235
button: openSettingsButton,
1236
validationErrorMessageElement,
1237
objectSettingWidget
1238
};
1239
1240
this.addSettingElementFocusHandler(template);
1241
1242
return template;
1243
}
1244
1245
protected override renderValue(dataElement: SettingsTreeSettingElement, template: ISettingComplexObjectItemTemplate, onChange: (value: string) => void): void {
1246
const items = getObjectDisplayValue(dataElement);
1247
template.objectSettingWidget.setValue(items, {
1248
settingKey: dataElement.setting.key,
1249
showAddButton: false,
1250
isReadOnly: true,
1251
});
1252
template.button.parentElement?.classList.toggle('hide', dataElement.hasPolicyValue);
1253
super.renderValue(dataElement, template, onChange);
1254
}
1255
}
1256
1257
class SettingArrayRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingListItemTemplate> {
1258
templateId = SETTINGS_ARRAY_TEMPLATE_ID;
1259
1260
renderTemplate(container: HTMLElement): ISettingListItemTemplate {
1261
const common = this.renderCommonTemplate(null, container, 'list');
1262
const descriptionElement = common.containerElement.querySelector('.setting-item-description')!;
1263
const validationErrorMessageElement = $('.setting-item-validation-message');
1264
descriptionElement.after(validationErrorMessageElement);
1265
1266
const listWidget = this._instantiationService.createInstance(ListSettingWidget, common.controlElement);
1267
listWidget.domNode.classList.add(AbstractSettingRenderer.CONTROL_CLASS);
1268
common.toDispose.add(listWidget);
1269
1270
const template: ISettingListItemTemplate = {
1271
...common,
1272
listWidget,
1273
validationErrorMessageElement
1274
};
1275
1276
this.addSettingElementFocusHandler(template);
1277
1278
common.toDispose.add(
1279
listWidget.onDidChangeList(e => {
1280
const newList = this.computeNewList(template, e);
1281
template.onChange?.(newList);
1282
})
1283
);
1284
1285
return template;
1286
}
1287
1288
private computeNewList(template: ISettingListItemTemplate, e: SettingListEvent<IListDataItem>): string[] | undefined {
1289
if (template.context) {
1290
let newValue: string[] = [];
1291
if (Array.isArray(template.context.scopeValue)) {
1292
newValue = [...template.context.scopeValue];
1293
} else if (Array.isArray(template.context.value)) {
1294
newValue = [...template.context.value];
1295
}
1296
1297
if (e.type === 'move') {
1298
// A drag and drop occurred
1299
const sourceIndex = e.sourceIndex;
1300
const targetIndex = e.targetIndex;
1301
const splicedElem = newValue.splice(sourceIndex, 1)[0];
1302
newValue.splice(targetIndex, 0, splicedElem);
1303
} else if (e.type === 'remove' || e.type === 'reset') {
1304
newValue.splice(e.targetIndex, 1);
1305
} else if (e.type === 'change') {
1306
const itemValueData = e.newItem.value.data.toString();
1307
1308
// Update value
1309
if (e.targetIndex > -1) {
1310
newValue[e.targetIndex] = itemValueData;
1311
}
1312
// For some reason, we are updating and cannot find original value
1313
// Just append the value in this case
1314
else {
1315
newValue.push(itemValueData);
1316
}
1317
} else if (e.type === 'add') {
1318
newValue.push(e.newItem.value.data.toString());
1319
}
1320
1321
if (
1322
template.context.defaultValue &&
1323
Array.isArray(template.context.defaultValue) &&
1324
template.context.defaultValue.length === newValue.length &&
1325
template.context.defaultValue.join() === newValue.join()
1326
) {
1327
return undefined;
1328
}
1329
return newValue;
1330
}
1331
1332
return undefined;
1333
}
1334
1335
renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingListItemTemplate): void {
1336
super.renderSettingElement(element, index, templateData);
1337
}
1338
1339
protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingListItemTemplate, onChange: (value: string[] | number[] | undefined) => void): void {
1340
const value = getListDisplayValue(dataElement);
1341
const keySuggester = dataElement.setting.enum ? createArraySuggester(dataElement) : undefined;
1342
template.listWidget.setValue(value, {
1343
showAddButton: getShowAddButtonList(dataElement, value),
1344
keySuggester
1345
});
1346
template.context = dataElement;
1347
1348
template.elementDisposables.add(toDisposable(() => {
1349
template.listWidget.cancelEdit();
1350
}));
1351
1352
template.onChange = (v: string[] | undefined) => {
1353
if (v && !renderArrayValidations(dataElement, template, v, false)) {
1354
const itemType = dataElement.setting.arrayItemType;
1355
const arrToSave = isNonNullableNumericType(itemType) ? v.map(a => +a) : v;
1356
onChange(arrToSave);
1357
} else {
1358
// Save the setting unparsed and containing the errors.
1359
// renderArrayValidations will render relevant error messages.
1360
onChange(v);
1361
}
1362
};
1363
1364
renderArrayValidations(dataElement, template, value.map(v => v.value.data.toString()), true);
1365
}
1366
}
1367
1368
abstract class AbstractSettingObjectRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingObjectItemTemplate> {
1369
1370
protected renderTemplateWithWidget(common: ISettingItemTemplate, widget: ObjectSettingCheckboxWidget | ObjectSettingDropdownWidget): ISettingObjectItemTemplate {
1371
widget.domNode.classList.add(AbstractSettingRenderer.CONTROL_CLASS);
1372
common.toDispose.add(widget);
1373
1374
const descriptionElement = common.containerElement.querySelector('.setting-item-description')!;
1375
const validationErrorMessageElement = $('.setting-item-validation-message');
1376
descriptionElement.after(validationErrorMessageElement);
1377
1378
const template: ISettingObjectItemTemplate = {
1379
...common,
1380
validationErrorMessageElement
1381
};
1382
if (widget instanceof ObjectSettingCheckboxWidget) {
1383
template.objectCheckboxWidget = widget;
1384
} else {
1385
template.objectDropdownWidget = widget;
1386
}
1387
1388
this.addSettingElementFocusHandler(template);
1389
return template;
1390
}
1391
1392
renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingObjectItemTemplate): void {
1393
super.renderSettingElement(element, index, templateData);
1394
}
1395
}
1396
1397
class SettingObjectRenderer extends AbstractSettingObjectRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingObjectItemTemplate> {
1398
override templateId = SETTINGS_OBJECT_TEMPLATE_ID;
1399
1400
renderTemplate(container: HTMLElement): ISettingObjectItemTemplate {
1401
const common = this.renderCommonTemplate(null, container, 'list');
1402
const widget = this._instantiationService.createInstance(ObjectSettingDropdownWidget, common.controlElement);
1403
const template = this.renderTemplateWithWidget(common, widget);
1404
common.toDispose.add(widget.onDidChangeList(e => {
1405
this.onDidChangeObject(template, e);
1406
}));
1407
return template;
1408
}
1409
1410
private onDidChangeObject(template: ISettingObjectItemTemplate, e: SettingListEvent<IObjectDataItem>): void {
1411
const widget = template.objectDropdownWidget!;
1412
if (template.context) {
1413
const settingSupportsRemoveDefault = objectSettingSupportsRemoveDefaultValue(template.context.setting.key);
1414
const defaultValue: Record<string, unknown> = typeof template.context.defaultValue === 'object'
1415
? template.context.defaultValue ?? {}
1416
: {};
1417
1418
const scopeValue: Record<string, unknown> = typeof template.context.scopeValue === 'object'
1419
? template.context.scopeValue ?? {}
1420
: {};
1421
1422
const newValue: Record<string, unknown> = { ...template.context.scopeValue }; // Initialize with scoped values as removed default values are not rendered
1423
const newItems: IObjectDataItem[] = [];
1424
1425
widget.items.forEach((item, idx) => {
1426
// Item was updated
1427
if ((e.type === 'change' || e.type === 'move') && e.targetIndex === idx) {
1428
// If the key of the default value is changed, remove the default value
1429
if (e.originalItem.key.data !== e.newItem.key.data && settingSupportsRemoveDefault && e.originalItem.key.data in defaultValue) {
1430
newValue[e.originalItem.key.data] = null;
1431
} else {
1432
delete newValue[e.originalItem.key.data];
1433
}
1434
newValue[e.newItem.key.data] = e.newItem.value.data;
1435
newItems.push(e.newItem);
1436
}
1437
// All remaining items, but skip the one that we just updated
1438
else if ((e.type !== 'change' && e.type !== 'move') || e.newItem.key.data !== item.key.data) {
1439
newValue[item.key.data] = item.value.data;
1440
newItems.push(item);
1441
}
1442
});
1443
1444
// Item was deleted
1445
if (e.type === 'remove' || e.type === 'reset') {
1446
const objectKey = e.originalItem.key.data;
1447
const removingDefaultValue = e.type === 'remove' && settingSupportsRemoveDefault && defaultValue[objectKey] === e.originalItem.value.data;
1448
if (removingDefaultValue) {
1449
newValue[objectKey] = null;
1450
} else {
1451
delete newValue[objectKey];
1452
}
1453
1454
const itemToDelete = newItems.findIndex(item => item.key.data === objectKey);
1455
const defaultItemValue = defaultValue[objectKey] as string | boolean;
1456
1457
// Item does not have a default or default is bing removed
1458
if (removingDefaultValue || isUndefinedOrNull(defaultValue[objectKey]) && itemToDelete > -1) {
1459
newItems.splice(itemToDelete, 1);
1460
} else if (!removingDefaultValue && itemToDelete > -1) {
1461
newItems[itemToDelete].value.data = defaultItemValue;
1462
}
1463
}
1464
// New item was added
1465
else if (e.type === 'add') {
1466
newValue[e.newItem.key.data] = e.newItem.value.data;
1467
newItems.push(e.newItem);
1468
}
1469
1470
Object.entries(newValue).forEach(([key, value]) => {
1471
// value from the scope has changed back to the default
1472
if (scopeValue[key] !== value && defaultValue[key] === value && !(settingSupportsRemoveDefault && value === null)) {
1473
delete newValue[key];
1474
}
1475
});
1476
1477
const newObject = Object.keys(newValue).length === 0 ? undefined : newValue;
1478
template.objectDropdownWidget!.setValue(newItems);
1479
template.onChange?.(newObject);
1480
}
1481
}
1482
1483
protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingObjectItemTemplate, onChange: (value: Record<string, unknown> | undefined) => void): void {
1484
const items = getObjectDisplayValue(dataElement);
1485
const { key, objectProperties, objectPatternProperties, objectAdditionalProperties } = dataElement.setting;
1486
1487
template.objectDropdownWidget!.setValue(items, {
1488
settingKey: key,
1489
showAddButton: objectAdditionalProperties === false
1490
? (
1491
!areAllPropertiesDefined(Object.keys(objectProperties ?? {}), items) ||
1492
isDefined(objectPatternProperties)
1493
)
1494
: true,
1495
keySuggester: createObjectKeySuggester(dataElement),
1496
valueSuggester: createObjectValueSuggester(dataElement)
1497
});
1498
1499
template.context = dataElement;
1500
1501
template.elementDisposables.add(toDisposable(() => {
1502
template.objectDropdownWidget!.cancelEdit();
1503
}));
1504
1505
template.onChange = (v: Record<string, unknown> | undefined) => {
1506
if (v && !renderArrayValidations(dataElement, template, v, false)) {
1507
const parsedRecord = parseNumericObjectValues(dataElement, v);
1508
onChange(parsedRecord);
1509
} else {
1510
// Save the setting unparsed and containing the errors.
1511
// renderArrayValidations will render relevant error messages.
1512
onChange(v);
1513
}
1514
};
1515
renderArrayValidations(dataElement, template, dataElement.value, true);
1516
}
1517
}
1518
1519
class SettingBoolObjectRenderer extends AbstractSettingObjectRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingObjectItemTemplate> {
1520
override templateId = SETTINGS_BOOL_OBJECT_TEMPLATE_ID;
1521
1522
renderTemplate(container: HTMLElement): ISettingObjectItemTemplate {
1523
const common = this.renderCommonTemplate(null, container, 'list');
1524
const widget = this._instantiationService.createInstance(ObjectSettingCheckboxWidget, common.controlElement);
1525
const template = this.renderTemplateWithWidget(common, widget);
1526
common.toDispose.add(widget.onDidChangeList(e => {
1527
this.onDidChangeObject(template, e);
1528
}));
1529
return template;
1530
}
1531
1532
protected onDidChangeObject(template: ISettingObjectItemTemplate, e: SettingListEvent<IBoolObjectDataItem>): void {
1533
if (template.context) {
1534
const widget = template.objectCheckboxWidget!;
1535
const defaultValue: Record<string, unknown> = typeof template.context.defaultValue === 'object'
1536
? template.context.defaultValue ?? {}
1537
: {};
1538
1539
const scopeValue: Record<string, unknown> = typeof template.context.scopeValue === 'object'
1540
? template.context.scopeValue ?? {}
1541
: {};
1542
1543
const newValue: Record<string, unknown> = { ...template.context.scopeValue }; // Initialize with scoped values as removed default values are not rendered
1544
const newItems: IBoolObjectDataItem[] = [];
1545
1546
if (e.type !== 'change') {
1547
console.warn('Unexpected event type', e.type, 'for bool object setting', template.context.setting.key);
1548
return;
1549
}
1550
1551
widget.items.forEach((item, idx) => {
1552
// Item was updated
1553
if (e.targetIndex === idx) {
1554
newValue[e.newItem.key.data] = e.newItem.value.data;
1555
newItems.push(e.newItem);
1556
}
1557
// All remaining items, but skip the one that we just updated
1558
else if (e.newItem.key.data !== item.key.data) {
1559
newValue[item.key.data] = item.value.data;
1560
newItems.push(item);
1561
}
1562
});
1563
1564
Object.entries(newValue).forEach(([key, value]) => {
1565
// value from the scope has changed back to the default
1566
if (scopeValue[key] !== value && defaultValue[key] === value) {
1567
delete newValue[key];
1568
}
1569
});
1570
1571
const newObject = Object.keys(newValue).length === 0 ? undefined : newValue;
1572
template.objectCheckboxWidget!.setValue(newItems);
1573
template.onChange?.(newObject);
1574
1575
// Focus this setting explicitly, in case we were previously
1576
// focused on another setting and clicked a checkbox/value container
1577
// for this setting.
1578
this._onDidFocusSetting.fire(template.context);
1579
}
1580
}
1581
1582
protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingObjectItemTemplate, onChange: (value: Record<string, unknown> | undefined) => void): void {
1583
const items = getBoolObjectDisplayValue(dataElement);
1584
const { key } = dataElement.setting;
1585
1586
template.objectCheckboxWidget!.setValue(items, {
1587
settingKey: key
1588
});
1589
1590
template.context = dataElement;
1591
template.onChange = (v: Record<string, unknown> | undefined) => {
1592
onChange(v);
1593
};
1594
}
1595
}
1596
1597
abstract class SettingIncludeExcludeRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingIncludeExcludeItemTemplate> {
1598
1599
protected abstract isExclude(): boolean;
1600
1601
renderTemplate(container: HTMLElement): ISettingIncludeExcludeItemTemplate {
1602
const common = this.renderCommonTemplate(null, container, 'list');
1603
1604
const includeExcludeWidget = this._instantiationService.createInstance(this.isExclude() ? ExcludeSettingWidget : IncludeSettingWidget, common.controlElement);
1605
includeExcludeWidget.domNode.classList.add(AbstractSettingRenderer.CONTROL_CLASS);
1606
common.toDispose.add(includeExcludeWidget);
1607
1608
const template: ISettingIncludeExcludeItemTemplate = {
1609
...common,
1610
includeExcludeWidget
1611
};
1612
1613
this.addSettingElementFocusHandler(template);
1614
1615
common.toDispose.add(includeExcludeWidget.onDidChangeList(e => this.onDidChangeIncludeExclude(template, e)));
1616
1617
return template;
1618
}
1619
1620
private onDidChangeIncludeExclude(template: ISettingIncludeExcludeItemTemplate, e: SettingListEvent<IListDataItem>): void {
1621
if (template.context) {
1622
const newValue = { ...template.context.scopeValue };
1623
1624
// first delete the existing entry, if present
1625
if (e.type !== 'add') {
1626
if (e.originalItem.value.data.toString() in template.context.defaultValue) {
1627
// delete a default by overriding it
1628
newValue[e.originalItem.value.data.toString()] = false;
1629
} else {
1630
delete newValue[e.originalItem.value.data.toString()];
1631
}
1632
}
1633
1634
// then add the new or updated entry, if present
1635
if (e.type === 'change' || e.type === 'add' || e.type === 'move') {
1636
if (e.newItem.value.data.toString() in template.context.defaultValue && !e.newItem.sibling) {
1637
// add a default by deleting its override
1638
delete newValue[e.newItem.value.data.toString()];
1639
} else {
1640
newValue[e.newItem.value.data.toString()] = e.newItem.sibling ? { when: e.newItem.sibling } : true;
1641
}
1642
}
1643
1644
function sortKeys<T extends object>(obj: T) {
1645
const sortedKeys = Object.keys(obj)
1646
.sort((a, b) => a.localeCompare(b)) as Array<keyof T>;
1647
1648
const retVal: Partial<T> = {};
1649
for (const key of sortedKeys) {
1650
retVal[key] = obj[key];
1651
}
1652
return retVal;
1653
}
1654
1655
this._onDidChangeSetting.fire({
1656
key: template.context.setting.key,
1657
value: Object.keys(newValue).length === 0 ? undefined : sortKeys(newValue),
1658
type: template.context.valueType,
1659
manualReset: false,
1660
scope: template.context.setting.scope
1661
});
1662
}
1663
}
1664
1665
renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingIncludeExcludeItemTemplate): void {
1666
super.renderSettingElement(element, index, templateData);
1667
}
1668
1669
protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingIncludeExcludeItemTemplate, onChange: (value: string) => void): void {
1670
const value = getIncludeExcludeDisplayValue(dataElement);
1671
template.includeExcludeWidget.setValue(value);
1672
template.context = dataElement;
1673
template.elementDisposables.add(toDisposable(() => {
1674
template.includeExcludeWidget.cancelEdit();
1675
}));
1676
}
1677
}
1678
1679
class SettingExcludeRenderer extends SettingIncludeExcludeRenderer {
1680
templateId = SETTINGS_EXCLUDE_TEMPLATE_ID;
1681
1682
protected override isExclude(): boolean {
1683
return true;
1684
}
1685
}
1686
1687
class SettingIncludeRenderer extends SettingIncludeExcludeRenderer {
1688
templateId = SETTINGS_INCLUDE_TEMPLATE_ID;
1689
1690
protected override isExclude(): boolean {
1691
return false;
1692
}
1693
}
1694
1695
const settingsInputBoxStyles = getInputBoxStyle({
1696
inputBackground: settingsTextInputBackground,
1697
inputForeground: settingsTextInputForeground,
1698
inputBorder: settingsTextInputBorder
1699
});
1700
1701
abstract class AbstractSettingTextRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingTextItemTemplate> {
1702
private readonly MULTILINE_MAX_HEIGHT = 150;
1703
1704
renderTemplate(_container: HTMLElement, useMultiline?: boolean): ISettingTextItemTemplate {
1705
const common = this.renderCommonTemplate(null, _container, 'text');
1706
const validationErrorMessageElement = DOM.append(common.containerElement, $('.setting-item-validation-message'));
1707
1708
const inputBoxOptions: IInputOptions = {
1709
flexibleHeight: useMultiline,
1710
flexibleWidth: false,
1711
flexibleMaxHeight: this.MULTILINE_MAX_HEIGHT,
1712
inputBoxStyles: settingsInputBoxStyles
1713
};
1714
const inputBox = new InputBox(common.controlElement, this._contextViewService, inputBoxOptions);
1715
common.toDispose.add(inputBox);
1716
common.toDispose.add(
1717
inputBox.onDidChange(e => {
1718
template.onChange?.(e);
1719
}));
1720
common.toDispose.add(inputBox);
1721
inputBox.inputElement.classList.add(AbstractSettingRenderer.CONTROL_CLASS);
1722
inputBox.inputElement.tabIndex = 0;
1723
1724
const template: ISettingTextItemTemplate = {
1725
...common,
1726
inputBox,
1727
validationErrorMessageElement
1728
};
1729
1730
this.addSettingElementFocusHandler(template);
1731
1732
return template;
1733
}
1734
1735
renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingTextItemTemplate): void {
1736
super.renderSettingElement(element, index, templateData);
1737
}
1738
1739
protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingTextItemTemplate, onChange: (value: string) => void): void {
1740
template.onChange = undefined;
1741
template.inputBox.value = dataElement.value;
1742
template.inputBox.setAriaLabel(dataElement.setting.key);
1743
template.onChange = value => {
1744
if (!renderValidations(dataElement, template, false)) {
1745
onChange(value);
1746
}
1747
};
1748
1749
renderValidations(dataElement, template, true);
1750
}
1751
}
1752
1753
class SettingTextRenderer extends AbstractSettingTextRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingTextItemTemplate> {
1754
templateId = SETTINGS_TEXT_TEMPLATE_ID;
1755
1756
override renderTemplate(_container: HTMLElement): ISettingTextItemTemplate {
1757
const template = super.renderTemplate(_container, false);
1758
1759
// TODO@9at8: listWidget filters out all key events from input boxes, so we need to come up with a better way
1760
// Disable ArrowUp and ArrowDown behaviour in favor of list navigation
1761
template.toDispose.add(DOM.addStandardDisposableListener(template.inputBox.inputElement, DOM.EventType.KEY_DOWN, e => {
1762
if (e.equals(KeyCode.UpArrow) || e.equals(KeyCode.DownArrow)) {
1763
e.preventDefault();
1764
}
1765
}));
1766
1767
return template;
1768
}
1769
}
1770
1771
class SettingMultilineTextRenderer extends AbstractSettingTextRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingTextItemTemplate> {
1772
templateId = SETTINGS_MULTILINE_TEXT_TEMPLATE_ID;
1773
1774
override renderTemplate(_container: HTMLElement): ISettingTextItemTemplate {
1775
return super.renderTemplate(_container, true);
1776
}
1777
1778
protected override renderValue(dataElement: SettingsTreeSettingElement, template: ISettingTextItemTemplate, onChange: (value: string) => void) {
1779
const onChangeOverride = (value: string) => {
1780
// Ensure the model is up to date since a different value will be rendered as different height when probing the height.
1781
dataElement.value = value;
1782
onChange(value);
1783
};
1784
super.renderValue(dataElement, template, onChangeOverride);
1785
template.elementDisposables.add(
1786
template.inputBox.onDidHeightChange(e => {
1787
const height = template.containerElement.clientHeight;
1788
// Don't fire event if height is reported as 0,
1789
// which sometimes happens when clicking onto a new setting.
1790
if (height) {
1791
this._onDidChangeSettingHeight.fire({
1792
element: dataElement,
1793
height: template.containerElement.clientHeight
1794
});
1795
}
1796
})
1797
);
1798
template.inputBox.layout();
1799
}
1800
}
1801
1802
class SettingEnumRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingEnumItemTemplate> {
1803
templateId = SETTINGS_ENUM_TEMPLATE_ID;
1804
1805
renderTemplate(container: HTMLElement): ISettingEnumItemTemplate {
1806
const common = this.renderCommonTemplate(null, container, 'enum');
1807
1808
const styles = getSelectBoxStyles({
1809
selectBackground: settingsSelectBackground,
1810
selectForeground: settingsSelectForeground,
1811
selectBorder: settingsSelectBorder,
1812
selectListBorder: settingsSelectListBorder
1813
});
1814
1815
const selectBox = new SelectBox([], 0, this._contextViewService, styles, {
1816
useCustomDrawn: !hasNativeContextMenu(this._configService) || !(isIOS && BrowserFeatures.pointerEvents)
1817
});
1818
1819
common.toDispose.add(selectBox);
1820
selectBox.render(common.controlElement);
1821
const selectElement = common.controlElement.querySelector('select');
1822
if (selectElement) {
1823
selectElement.classList.add(AbstractSettingRenderer.CONTROL_CLASS);
1824
selectElement.tabIndex = 0;
1825
}
1826
1827
common.toDispose.add(
1828
selectBox.onDidSelect(e => {
1829
template.onChange?.(e.index);
1830
}));
1831
1832
const enumDescriptionElement = common.containerElement.insertBefore($('.setting-item-enumDescription'), common.descriptionElement.nextSibling);
1833
1834
const template: ISettingEnumItemTemplate = {
1835
...common,
1836
selectBox,
1837
selectElement,
1838
enumDescriptionElement
1839
};
1840
1841
this.addSettingElementFocusHandler(template);
1842
1843
return template;
1844
}
1845
1846
renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingEnumItemTemplate): void {
1847
super.renderSettingElement(element, index, templateData);
1848
}
1849
1850
protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingEnumItemTemplate, onChange: (value: string) => void): void {
1851
// Make shallow copies here so that we don't modify the actual dataElement later
1852
const enumItemLabels = dataElement.setting.enumItemLabels ? [...dataElement.setting.enumItemLabels] : [];
1853
const enumDescriptions = dataElement.setting.enumDescriptions ? [...dataElement.setting.enumDescriptions] : [];
1854
const settingEnum = [...dataElement.setting.enum!];
1855
const enumDescriptionsAreMarkdown = dataElement.setting.enumDescriptionsAreMarkdown;
1856
1857
const disposables = new DisposableStore();
1858
template.elementDisposables.add(disposables);
1859
1860
let createdDefault = false;
1861
if (!settingEnum.includes(dataElement.defaultValue)) {
1862
// Add a new potentially blank default setting
1863
settingEnum.unshift(dataElement.defaultValue);
1864
enumDescriptions.unshift('');
1865
enumItemLabels.unshift('');
1866
createdDefault = true;
1867
}
1868
1869
// Use String constructor in case of null or undefined values
1870
const stringifiedDefaultValue = escapeInvisibleChars(String(dataElement.defaultValue));
1871
const displayOptions: ISelectOptionItem[] = settingEnum
1872
.map(String)
1873
.map(escapeInvisibleChars)
1874
.map((data, index) => {
1875
const description = (enumDescriptions[index] && (enumDescriptionsAreMarkdown ? fixSettingLinks(enumDescriptions[index], false) : enumDescriptions[index]));
1876
return {
1877
text: enumItemLabels[index] ? enumItemLabels[index] : data,
1878
detail: enumItemLabels[index] ? data : '',
1879
description,
1880
descriptionIsMarkdown: enumDescriptionsAreMarkdown,
1881
descriptionMarkdownActionHandler: (content) => {
1882
this._openerService.open(content).catch(onUnexpectedError);
1883
},
1884
decoratorRight: (((data === stringifiedDefaultValue) || (createdDefault && index === 0)) ? localize('settings.Default', "default") : '')
1885
} satisfies ISelectOptionItem;
1886
});
1887
1888
template.selectBox.setOptions(displayOptions);
1889
template.selectBox.setAriaLabel(dataElement.setting.key);
1890
1891
let idx = settingEnum.indexOf(dataElement.value);
1892
if (idx === -1) {
1893
idx = 0;
1894
}
1895
1896
template.onChange = undefined;
1897
template.selectBox.select(idx);
1898
template.onChange = (idx) => {
1899
if (createdDefault && idx === 0) {
1900
onChange(dataElement.defaultValue);
1901
} else {
1902
onChange(settingEnum[idx]);
1903
}
1904
};
1905
1906
template.enumDescriptionElement.innerText = '';
1907
}
1908
}
1909
1910
const settingsNumberInputBoxStyles = getInputBoxStyle({
1911
inputBackground: settingsNumberInputBackground,
1912
inputForeground: settingsNumberInputForeground,
1913
inputBorder: settingsNumberInputBorder
1914
});
1915
1916
class SettingNumberRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingNumberItemTemplate> {
1917
templateId = SETTINGS_NUMBER_TEMPLATE_ID;
1918
1919
renderTemplate(_container: HTMLElement): ISettingNumberItemTemplate {
1920
const common = super.renderCommonTemplate(null, _container, 'number');
1921
const validationErrorMessageElement = DOM.append(common.containerElement, $('.setting-item-validation-message'));
1922
1923
const inputBox = new InputBox(common.controlElement, this._contextViewService, { type: 'number', inputBoxStyles: settingsNumberInputBoxStyles });
1924
common.toDispose.add(inputBox);
1925
common.toDispose.add(
1926
inputBox.onDidChange(e => {
1927
template.onChange?.(e);
1928
}));
1929
common.toDispose.add(inputBox);
1930
inputBox.inputElement.classList.add(AbstractSettingRenderer.CONTROL_CLASS);
1931
inputBox.inputElement.tabIndex = 0;
1932
1933
const template: ISettingNumberItemTemplate = {
1934
...common,
1935
inputBox,
1936
validationErrorMessageElement
1937
};
1938
1939
this.addSettingElementFocusHandler(template);
1940
1941
return template;
1942
}
1943
1944
renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingNumberItemTemplate): void {
1945
super.renderSettingElement(element, index, templateData);
1946
}
1947
1948
protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingNumberItemTemplate, onChange: (value: number | null) => void): void {
1949
const numParseFn = (dataElement.valueType === 'integer' || dataElement.valueType === 'nullable-integer')
1950
? parseInt : parseFloat;
1951
1952
const nullNumParseFn = (dataElement.valueType === 'nullable-integer' || dataElement.valueType === 'nullable-number')
1953
? ((v: string) => v === '' ? null : numParseFn(v)) : numParseFn;
1954
1955
template.onChange = undefined;
1956
template.inputBox.value = typeof dataElement.value === 'number' ?
1957
dataElement.value.toString() : '';
1958
template.inputBox.step = dataElement.valueType.includes('integer') ? '1' : 'any';
1959
template.inputBox.setAriaLabel(dataElement.setting.key);
1960
template.onChange = value => {
1961
if (!renderValidations(dataElement, template, false)) {
1962
onChange(nullNumParseFn(value));
1963
}
1964
};
1965
1966
renderValidations(dataElement, template, true);
1967
}
1968
}
1969
1970
class SettingBoolRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingBoolItemTemplate> {
1971
templateId = SETTINGS_BOOL_TEMPLATE_ID;
1972
1973
renderTemplate(_container: HTMLElement): ISettingBoolItemTemplate {
1974
_container.classList.add('setting-item');
1975
_container.classList.add('setting-item-bool');
1976
1977
const toDispose = new DisposableStore();
1978
1979
const container = DOM.append(_container, $(AbstractSettingRenderer.CONTENTS_SELECTOR));
1980
container.classList.add('settings-row-inner-container');
1981
1982
const titleElement = DOM.append(container, $('.setting-item-title'));
1983
const categoryElement = DOM.append(titleElement, $('span.setting-item-category'));
1984
const labelElementContainer = DOM.append(titleElement, $('span.setting-item-label'));
1985
const labelElement = toDispose.add(new SimpleIconLabel(labelElementContainer));
1986
const indicatorsLabel = toDispose.add(this._instantiationService.createInstance(SettingsTreeIndicatorsLabel, titleElement));
1987
1988
const descriptionAndValueElement = DOM.append(container, $('.setting-item-value-description'));
1989
const controlElement = DOM.append(descriptionAndValueElement, $('.setting-item-bool-control'));
1990
const descriptionElement = DOM.append(descriptionAndValueElement, $('.setting-item-description'));
1991
const modifiedIndicatorElement = DOM.append(container, $('.setting-item-modified-indicator'));
1992
toDispose.add(this._hoverService.setupDelayedHover(modifiedIndicatorElement, {
1993
content: localize('modified', "The setting has been configured in the current scope.")
1994
}));
1995
1996
const deprecationWarningElement = DOM.append(container, $('.setting-item-deprecation-message'));
1997
1998
const checkbox = new Toggle({ icon: Codicon.check, actionClassName: 'setting-value-checkbox', isChecked: true, title: '', ...unthemedToggleStyles });
1999
controlElement.appendChild(checkbox.domNode);
2000
toDispose.add(checkbox);
2001
toDispose.add(checkbox.onChange(() => {
2002
template.onChange!(checkbox.checked);
2003
}));
2004
2005
checkbox.domNode.classList.add(AbstractSettingRenderer.CONTROL_CLASS);
2006
const toolbarContainer = DOM.append(container, $('.setting-toolbar-container'));
2007
const toolbar = this.renderSettingToolbar(toolbarContainer);
2008
toDispose.add(toolbar);
2009
2010
const template: ISettingBoolItemTemplate = {
2011
toDispose,
2012
elementDisposables: toDispose.add(new DisposableStore()),
2013
2014
containerElement: container,
2015
categoryElement,
2016
labelElement,
2017
controlElement,
2018
checkbox,
2019
descriptionElement,
2020
deprecationWarningElement,
2021
indicatorsLabel,
2022
toolbar
2023
};
2024
2025
this.addSettingElementFocusHandler(template);
2026
2027
// Prevent clicks from being handled by list
2028
toDispose.add(DOM.addDisposableListener(controlElement, 'mousedown', (e: IMouseEvent) => e.stopPropagation()));
2029
toDispose.add(DOM.addDisposableListener(titleElement, DOM.EventType.MOUSE_ENTER, e => container.classList.add('mouseover')));
2030
toDispose.add(DOM.addDisposableListener(titleElement, DOM.EventType.MOUSE_LEAVE, e => container.classList.remove('mouseover')));
2031
2032
return template;
2033
}
2034
2035
renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingBoolItemTemplate): void {
2036
super.renderSettingElement(element, index, templateData);
2037
}
2038
2039
protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingBoolItemTemplate, onChange: (value: boolean) => void): void {
2040
template.onChange = undefined;
2041
template.checkbox.checked = dataElement.value;
2042
if (dataElement.hasPolicyValue) {
2043
template.checkbox.disable();
2044
template.descriptionElement.classList.add('disabled');
2045
} else {
2046
template.checkbox.enable();
2047
template.descriptionElement.classList.remove('disabled');
2048
2049
// Need to listen for mouse clicks on description and toggle checkbox - use target ID for safety
2050
// Also have to ignore embedded links - too buried to stop propagation
2051
template.elementDisposables.add(DOM.addDisposableListener(template.descriptionElement, DOM.EventType.MOUSE_DOWN, (e) => {
2052
const targetElement = <HTMLElement>e.target;
2053
2054
// Toggle target checkbox
2055
if (targetElement.tagName.toLowerCase() !== 'a') {
2056
template.checkbox.checked = !template.checkbox.checked;
2057
template.onChange!(template.checkbox.checked);
2058
}
2059
DOM.EventHelper.stop(e);
2060
}));
2061
}
2062
template.checkbox.setTitle(dataElement.setting.key);
2063
template.onChange = onChange;
2064
}
2065
}
2066
2067
type ManageExtensionClickTelemetryClassification = {
2068
extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension the user went to manage.' };
2069
owner: 'rzhao271';
2070
comment: 'Event used to gain insights into when users interact with an extension management setting';
2071
};
2072
2073
class SettingsExtensionToggleRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingExtensionToggleItemTemplate> {
2074
templateId = SETTINGS_EXTENSION_TOGGLE_TEMPLATE_ID;
2075
2076
private readonly _onDidDismissExtensionSetting = this._register(new Emitter<string>());
2077
readonly onDidDismissExtensionSetting = this._onDidDismissExtensionSetting.event;
2078
2079
renderTemplate(_container: HTMLElement): ISettingExtensionToggleItemTemplate {
2080
const common = super.renderCommonTemplate(null, _container, 'extension-toggle');
2081
2082
const actionButton = new Button(common.containerElement, {
2083
title: false,
2084
...defaultButtonStyles
2085
});
2086
actionButton.element.classList.add('setting-item-extension-toggle-button');
2087
actionButton.label = localize('showExtension', "Show Extension");
2088
2089
const dismissButton = new Button(common.containerElement, {
2090
title: false,
2091
secondary: true,
2092
...defaultButtonStyles
2093
});
2094
dismissButton.element.classList.add('setting-item-extension-dismiss-button');
2095
dismissButton.label = localize('dismiss', "Dismiss");
2096
2097
const template: ISettingExtensionToggleItemTemplate = {
2098
...common,
2099
actionButton,
2100
dismissButton
2101
};
2102
2103
this.addSettingElementFocusHandler(template);
2104
2105
return template;
2106
}
2107
2108
renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingExtensionToggleItemTemplate): void {
2109
super.renderSettingElement(element, index, templateData);
2110
}
2111
2112
protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingExtensionToggleItemTemplate, onChange: (_: undefined) => void): void {
2113
template.elementDisposables.clear();
2114
2115
const extensionId = dataElement.setting.displayExtensionId!;
2116
template.elementDisposables.add(template.actionButton.onDidClick(async () => {
2117
this._telemetryService.publicLog2<{ extensionId: String }, ManageExtensionClickTelemetryClassification>('ManageExtensionClick', { extensionId });
2118
this._commandService.executeCommand('extension.open', extensionId);
2119
}));
2120
2121
template.elementDisposables.add(template.dismissButton.onDidClick(async () => {
2122
this._telemetryService.publicLog2<{ extensionId: String }, ManageExtensionClickTelemetryClassification>('DismissExtensionClick', { extensionId });
2123
this._onDidDismissExtensionSetting.fire(extensionId);
2124
}));
2125
}
2126
}
2127
2128
export class SettingTreeRenderers extends Disposable {
2129
readonly onDidClickOverrideElement: Event<ISettingOverrideClickEvent>;
2130
2131
private readonly _onDidChangeSetting = this._register(new Emitter<ISettingChangeEvent>());
2132
readonly onDidChangeSetting: Event<ISettingChangeEvent>;
2133
2134
readonly onDidDismissExtensionSetting: Event<string>;
2135
2136
readonly onDidOpenSettings: Event<string>;
2137
2138
readonly onDidClickSettingLink: Event<ISettingLinkClickEvent>;
2139
2140
readonly onDidFocusSetting: Event<SettingsTreeSettingElement>;
2141
2142
readonly onDidChangeSettingHeight: Event<HeightChangeParams>;
2143
2144
readonly onApplyFilter: Event<string>;
2145
2146
readonly allRenderers: ITreeRenderer<SettingsTreeElement, never, any>[];
2147
2148
private readonly settingActions: IAction[];
2149
2150
constructor(
2151
@IInstantiationService private readonly _instantiationService: IInstantiationService,
2152
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
2153
@IContextViewService private readonly _contextViewService: IContextViewService,
2154
@IUserDataSyncEnablementService private readonly _userDataSyncEnablementService: IUserDataSyncEnablementService,
2155
) {
2156
super();
2157
this.settingActions = [
2158
new Action('settings.resetSetting', localize('resetSettingLabel', "Reset Setting"), undefined, undefined, async context => {
2159
if (context instanceof SettingsTreeSettingElement) {
2160
if (!context.isUntrusted) {
2161
this._onDidChangeSetting.fire({
2162
key: context.setting.key,
2163
value: undefined,
2164
type: context.setting.type as SettingValueType,
2165
manualReset: true,
2166
scope: context.setting.scope
2167
});
2168
}
2169
}
2170
}),
2171
new Separator(),
2172
this._instantiationService.createInstance(CopySettingIdAction),
2173
this._instantiationService.createInstance(CopySettingAsJSONAction),
2174
this._instantiationService.createInstance(CopySettingAsURLAction),
2175
];
2176
2177
const actionFactory = (setting: ISetting, settingTarget: SettingsTarget) => this.getActionsForSetting(setting, settingTarget);
2178
const emptyActionFactory = (_: ISetting) => [];
2179
const extensionRenderer = this._instantiationService.createInstance(SettingsExtensionToggleRenderer, [], emptyActionFactory);
2180
const settingRenderers = [
2181
this._instantiationService.createInstance(SettingBoolRenderer, this.settingActions, actionFactory),
2182
this._instantiationService.createInstance(SettingNumberRenderer, this.settingActions, actionFactory),
2183
this._instantiationService.createInstance(SettingArrayRenderer, this.settingActions, actionFactory),
2184
this._instantiationService.createInstance(SettingComplexRenderer, this.settingActions, actionFactory),
2185
this._instantiationService.createInstance(SettingComplexObjectRenderer, this.settingActions, actionFactory),
2186
this._instantiationService.createInstance(SettingTextRenderer, this.settingActions, actionFactory),
2187
this._instantiationService.createInstance(SettingMultilineTextRenderer, this.settingActions, actionFactory),
2188
this._instantiationService.createInstance(SettingExcludeRenderer, this.settingActions, actionFactory),
2189
this._instantiationService.createInstance(SettingIncludeRenderer, this.settingActions, actionFactory),
2190
this._instantiationService.createInstance(SettingEnumRenderer, this.settingActions, actionFactory),
2191
this._instantiationService.createInstance(SettingObjectRenderer, this.settingActions, actionFactory),
2192
this._instantiationService.createInstance(SettingBoolObjectRenderer, this.settingActions, actionFactory),
2193
extensionRenderer
2194
];
2195
2196
this.onDidClickOverrideElement = Event.any(...settingRenderers.map(r => r.onDidClickOverrideElement));
2197
this.onDidChangeSetting = Event.any(
2198
...settingRenderers.map(r => r.onDidChangeSetting),
2199
this._onDidChangeSetting.event
2200
);
2201
this.onDidDismissExtensionSetting = extensionRenderer.onDidDismissExtensionSetting;
2202
this.onDidOpenSettings = Event.any(...settingRenderers.map(r => r.onDidOpenSettings));
2203
this.onDidClickSettingLink = Event.any(...settingRenderers.map(r => r.onDidClickSettingLink));
2204
this.onDidFocusSetting = Event.any(...settingRenderers.map(r => r.onDidFocusSetting));
2205
this.onDidChangeSettingHeight = Event.any(...settingRenderers.map(r => r.onDidChangeSettingHeight));
2206
this.onApplyFilter = Event.any(...settingRenderers.map(r => r.onApplyFilter));
2207
2208
this.allRenderers = [
2209
...settingRenderers,
2210
this._instantiationService.createInstance(SettingGroupRenderer),
2211
this._instantiationService.createInstance(SettingNewExtensionsRenderer),
2212
];
2213
}
2214
2215
private getActionsForSetting(setting: ISetting, settingTarget: SettingsTarget): IAction[] {
2216
const actions: IAction[] = [];
2217
if (!(setting.scope && APPLICATION_SCOPES.includes(setting.scope)) && settingTarget === ConfigurationTarget.USER_LOCAL) {
2218
actions.push(this._instantiationService.createInstance(ApplySettingToAllProfilesAction, setting));
2219
}
2220
if (this._userDataSyncEnablementService.isEnabled() && !setting.disallowSyncIgnore) {
2221
actions.push(this._instantiationService.createInstance(SyncSettingAction, setting));
2222
}
2223
if (actions.length) {
2224
actions.splice(0, 0, new Separator());
2225
}
2226
return actions;
2227
}
2228
2229
cancelSuggesters() {
2230
this._contextViewService.hideContextView();
2231
}
2232
2233
showContextMenu(element: SettingsTreeSettingElement, settingDOMElement: HTMLElement): void {
2234
const toolbarElement = settingDOMElement.querySelector('.monaco-toolbar');
2235
if (toolbarElement) {
2236
this._contextMenuService.showContextMenu({
2237
getActions: () => this.settingActions,
2238
getAnchor: () => <HTMLElement>toolbarElement,
2239
getActionsContext: () => element
2240
});
2241
}
2242
}
2243
2244
getSettingDOMElementForDOMElement(domElement: HTMLElement): HTMLElement | null {
2245
const parent = DOM.findParentWithClass(domElement, AbstractSettingRenderer.CONTENTS_CLASS);
2246
if (parent) {
2247
return parent;
2248
}
2249
2250
return null;
2251
}
2252
2253
getDOMElementsForSettingKey(treeContainer: HTMLElement, key: string): NodeListOf<HTMLElement> {
2254
return treeContainer.querySelectorAll(`[${AbstractSettingRenderer.SETTING_KEY_ATTR}="${key}"]`);
2255
}
2256
2257
getKeyForDOMElementInSetting(element: HTMLElement): string | null {
2258
const settingElement = this.getSettingDOMElementForDOMElement(element);
2259
return settingElement && settingElement.getAttribute(AbstractSettingRenderer.SETTING_KEY_ATTR);
2260
}
2261
2262
getIdForDOMElementInSetting(element: HTMLElement): string | null {
2263
const settingElement = this.getSettingDOMElementForDOMElement(element);
2264
return settingElement && settingElement.getAttribute(AbstractSettingRenderer.SETTING_ID_ATTR);
2265
}
2266
2267
override dispose(): void {
2268
super.dispose();
2269
this.settingActions.forEach(action => {
2270
if (isDisposable(action)) {
2271
action.dispose();
2272
}
2273
});
2274
this.allRenderers.forEach(renderer => {
2275
if (isDisposable(renderer)) {
2276
renderer.dispose();
2277
}
2278
});
2279
}
2280
}
2281
2282
/**
2283
* Validate and render any error message. Returns true if the value is invalid.
2284
*/
2285
function renderValidations(dataElement: SettingsTreeSettingElement, template: ISettingTextItemTemplate, calledOnStartup: boolean): boolean {
2286
if (dataElement.setting.validator) {
2287
const errMsg = dataElement.setting.validator(template.inputBox.value);
2288
if (errMsg) {
2289
template.containerElement.classList.add('invalid-input');
2290
template.validationErrorMessageElement.innerText = errMsg;
2291
const validationError = localize('validationError', "Validation Error.");
2292
template.inputBox.inputElement.parentElement!.setAttribute('aria-label', [validationError, errMsg].join(' '));
2293
if (!calledOnStartup) { aria.status(validationError + ' ' + errMsg); }
2294
return true;
2295
} else {
2296
template.inputBox.inputElement.parentElement!.removeAttribute('aria-label');
2297
}
2298
}
2299
template.containerElement.classList.remove('invalid-input');
2300
return false;
2301
}
2302
2303
/**
2304
* Validate and render any error message for arrays. Returns true if the value is invalid.
2305
*/
2306
function renderArrayValidations(
2307
dataElement: SettingsTreeSettingElement,
2308
template: ISettingListItemTemplate | ISettingObjectItemTemplate,
2309
value: string[] | Record<string, unknown> | undefined,
2310
calledOnStartup: boolean
2311
): boolean {
2312
template.containerElement.classList.add('invalid-input');
2313
if (dataElement.setting.validator) {
2314
const errMsg = dataElement.setting.validator(value);
2315
if (errMsg && errMsg !== '') {
2316
template.containerElement.classList.add('invalid-input');
2317
template.validationErrorMessageElement.innerText = errMsg;
2318
const validationError = localize('validationError', "Validation Error.");
2319
template.containerElement.setAttribute('aria-label', [dataElement.setting.key, validationError, errMsg].join(' '));
2320
if (!calledOnStartup) { aria.status(validationError + ' ' + errMsg); }
2321
return true;
2322
} else {
2323
template.containerElement.setAttribute('aria-label', dataElement.setting.key);
2324
template.containerElement.classList.remove('invalid-input');
2325
}
2326
}
2327
return false;
2328
}
2329
2330
function cleanRenderedMarkdown(element: Node): void {
2331
for (let i = 0; i < element.childNodes.length; i++) {
2332
const child = element.childNodes.item(i);
2333
2334
const tagName = (<Element>child).tagName && (<Element>child).tagName.toLowerCase();
2335
if (tagName === 'img') {
2336
child.remove();
2337
} else {
2338
cleanRenderedMarkdown(child);
2339
}
2340
}
2341
}
2342
2343
function fixSettingLinks(text: string, linkify = true): string {
2344
return text.replace(/`#([^#\s`]+)#`|'#([^#\s']+)#'/g, (match, backticksGroup, quotesGroup) => {
2345
const settingKey: string = backticksGroup ?? quotesGroup;
2346
const targetDisplayFormat = settingKeyToDisplayFormat(settingKey);
2347
const targetName = `${targetDisplayFormat.category}: ${targetDisplayFormat.label}`;
2348
return linkify ?
2349
`[${targetName}](#${settingKey} "${settingKey}")` :
2350
`"${targetName}"`;
2351
});
2352
}
2353
2354
function escapeInvisibleChars(enumValue: string): string {
2355
return enumValue && enumValue
2356
.replace(/\n/g, '\\n')
2357
.replace(/\r/g, '\\r');
2358
}
2359
2360
2361
export class SettingsTreeFilter implements ITreeFilter<SettingsTreeElement> {
2362
constructor(
2363
private viewState: ISettingsEditorViewState,
2364
@IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService,
2365
) { }
2366
2367
filter(element: SettingsTreeElement, parentVisibility: TreeVisibility): TreeFilterResult<void> {
2368
// Filter during search
2369
if (this.viewState.filterToCategory && element instanceof SettingsTreeSettingElement) {
2370
if (!this.settingContainedInGroup(element.setting, this.viewState.filterToCategory)) {
2371
return false;
2372
}
2373
}
2374
2375
// Non-user scope selected
2376
if (element instanceof SettingsTreeSettingElement && this.viewState.settingsTarget !== ConfigurationTarget.USER_LOCAL) {
2377
const isRemote = !!this.environmentService.remoteAuthority;
2378
if (!element.matchesScope(this.viewState.settingsTarget, isRemote)) {
2379
return false;
2380
}
2381
}
2382
2383
// Group with no visible children
2384
if (element instanceof SettingsTreeGroupElement) {
2385
if (typeof element.count === 'number') {
2386
return element.count > 0;
2387
}
2388
2389
return TreeVisibility.Recurse;
2390
}
2391
2392
// Filtered "new extensions" button
2393
if (element instanceof SettingsTreeNewExtensionsElement) {
2394
if (this.viewState.tagFilters?.size || this.viewState.filterToCategory) {
2395
return false;
2396
}
2397
}
2398
2399
return true;
2400
}
2401
2402
private settingContainedInGroup(setting: ISetting, group: SettingsTreeGroupElement): boolean {
2403
return group.children.some(child => {
2404
if (child instanceof SettingsTreeGroupElement) {
2405
return this.settingContainedInGroup(setting, child);
2406
} else if (child instanceof SettingsTreeSettingElement) {
2407
return child.setting.key === setting.key;
2408
} else {
2409
return false;
2410
}
2411
});
2412
}
2413
}
2414
2415
class SettingsTreeDelegate extends CachedListVirtualDelegate<SettingsTreeGroupChild> {
2416
2417
getTemplateId(element: SettingsTreeGroupElement | SettingsTreeSettingElement | SettingsTreeNewExtensionsElement): string {
2418
if (element instanceof SettingsTreeGroupElement) {
2419
return SETTINGS_ELEMENT_TEMPLATE_ID;
2420
}
2421
2422
if (element instanceof SettingsTreeSettingElement) {
2423
if (element.valueType === SettingValueType.ExtensionToggle) {
2424
return SETTINGS_EXTENSION_TOGGLE_TEMPLATE_ID;
2425
}
2426
2427
const invalidTypeError = element.isConfigured && getInvalidTypeError(element.value, element.setting.type);
2428
if (invalidTypeError) {
2429
return SETTINGS_COMPLEX_TEMPLATE_ID;
2430
}
2431
2432
if (element.valueType === SettingValueType.Boolean) {
2433
return SETTINGS_BOOL_TEMPLATE_ID;
2434
}
2435
2436
if (element.valueType === SettingValueType.Integer ||
2437
element.valueType === SettingValueType.Number ||
2438
element.valueType === SettingValueType.NullableInteger ||
2439
element.valueType === SettingValueType.NullableNumber) {
2440
return SETTINGS_NUMBER_TEMPLATE_ID;
2441
}
2442
2443
if (element.valueType === SettingValueType.MultilineString) {
2444
return SETTINGS_MULTILINE_TEXT_TEMPLATE_ID;
2445
}
2446
2447
if (element.valueType === SettingValueType.String) {
2448
return SETTINGS_TEXT_TEMPLATE_ID;
2449
}
2450
2451
if (element.valueType === SettingValueType.Enum) {
2452
return SETTINGS_ENUM_TEMPLATE_ID;
2453
}
2454
2455
if (element.valueType === SettingValueType.Array) {
2456
return SETTINGS_ARRAY_TEMPLATE_ID;
2457
}
2458
2459
if (element.valueType === SettingValueType.Exclude) {
2460
return SETTINGS_EXCLUDE_TEMPLATE_ID;
2461
}
2462
2463
if (element.valueType === SettingValueType.Include) {
2464
return SETTINGS_INCLUDE_TEMPLATE_ID;
2465
}
2466
2467
if (element.valueType === SettingValueType.Object) {
2468
return SETTINGS_OBJECT_TEMPLATE_ID;
2469
}
2470
2471
if (element.valueType === SettingValueType.BooleanObject) {
2472
return SETTINGS_BOOL_OBJECT_TEMPLATE_ID;
2473
}
2474
2475
if (element.valueType === SettingValueType.ComplexObject) {
2476
return SETTINGS_COMPLEX_OBJECT_TEMPLATE_ID;
2477
}
2478
2479
if (element.valueType === SettingValueType.LanguageTag) {
2480
return SETTINGS_COMPLEX_TEMPLATE_ID;
2481
}
2482
2483
return SETTINGS_COMPLEX_TEMPLATE_ID;
2484
}
2485
2486
if (element instanceof SettingsTreeNewExtensionsElement) {
2487
return SETTINGS_NEW_EXTENSIONS_TEMPLATE_ID;
2488
}
2489
2490
throw new Error('unknown element type: ' + element);
2491
}
2492
2493
hasDynamicHeight(element: SettingsTreeGroupElement | SettingsTreeSettingElement | SettingsTreeNewExtensionsElement): boolean {
2494
return !(element instanceof SettingsTreeGroupElement);
2495
}
2496
2497
protected estimateHeight(element: SettingsTreeGroupChild): number {
2498
if (element instanceof SettingsTreeGroupElement) {
2499
return 42;
2500
}
2501
2502
return element instanceof SettingsTreeSettingElement && element.valueType === SettingValueType.Boolean ? 78 : 104;
2503
}
2504
}
2505
2506
export class NonCollapsibleObjectTreeModel<T> extends ObjectTreeModel<T> {
2507
override isCollapsible(element: T): boolean {
2508
return false;
2509
}
2510
2511
override setCollapsed(element: T, collapsed?: boolean, recursive?: boolean): boolean {
2512
return false;
2513
}
2514
}
2515
2516
class SettingsTreeAccessibilityProvider implements IListAccessibilityProvider<SettingsTreeElement> {
2517
constructor(private readonly configurationService: IWorkbenchConfigurationService, private readonly languageService: ILanguageService, private readonly userDataProfilesService: IUserDataProfilesService) {
2518
}
2519
2520
getAriaLabel(element: SettingsTreeElement) {
2521
if (element instanceof SettingsTreeSettingElement) {
2522
const ariaLabelSections: string[] = [];
2523
ariaLabelSections.push(`${element.displayCategory} ${element.displayLabel}.`);
2524
2525
if (element.isConfigured) {
2526
const modifiedText = localize('settings.Modified', 'Modified.');
2527
ariaLabelSections.push(modifiedText);
2528
}
2529
2530
const indicatorsLabelAriaLabel = getIndicatorsLabelAriaLabel(element, this.configurationService, this.userDataProfilesService, this.languageService);
2531
if (indicatorsLabelAriaLabel.length) {
2532
ariaLabelSections.push(`${indicatorsLabelAriaLabel}.`);
2533
}
2534
2535
const descriptionWithoutSettingLinks = renderAsPlaintext({ value: fixSettingLinks(element.description, false) });
2536
if (descriptionWithoutSettingLinks.length) {
2537
ariaLabelSections.push(descriptionWithoutSettingLinks);
2538
}
2539
return ariaLabelSections.join(' ');
2540
} else if (element instanceof SettingsTreeGroupElement) {
2541
return element.label;
2542
} else {
2543
return element.id;
2544
}
2545
}
2546
2547
getWidgetAriaLabel() {
2548
return localize('settings', "Settings");
2549
}
2550
}
2551
2552
export class SettingsTree extends WorkbenchObjectTree<SettingsTreeElement> {
2553
constructor(
2554
container: HTMLElement,
2555
viewState: ISettingsEditorViewState,
2556
renderers: ITreeRenderer<any, void, any>[],
2557
@IContextKeyService contextKeyService: IContextKeyService,
2558
@IListService listService: IListService,
2559
@IWorkbenchConfigurationService configurationService: IWorkbenchConfigurationService,
2560
@IInstantiationService instantiationService: IInstantiationService,
2561
@ILanguageService languageService: ILanguageService,
2562
@IUserDataProfilesService userDataProfilesService: IUserDataProfilesService
2563
) {
2564
super('SettingsTree', container,
2565
new SettingsTreeDelegate(),
2566
renderers,
2567
{
2568
horizontalScrolling: false,
2569
supportDynamicHeights: true,
2570
scrollToActiveElement: true,
2571
identityProvider: {
2572
getId(e) {
2573
return e.id;
2574
}
2575
},
2576
accessibilityProvider: new SettingsTreeAccessibilityProvider(configurationService, languageService, userDataProfilesService),
2577
styleController: id => new DefaultStyleController(domStylesheetsJs.createStyleSheet(container), id),
2578
filter: instantiationService.createInstance(SettingsTreeFilter, viewState),
2579
smoothScrolling: configurationService.getValue<boolean>('workbench.list.smoothScrolling'),
2580
multipleSelectionSupport: false,
2581
findWidgetEnabled: false,
2582
renderIndentGuides: RenderIndentGuides.None,
2583
transformOptimization: false // Disable transform optimization #177470
2584
},
2585
instantiationService,
2586
contextKeyService,
2587
listService,
2588
configurationService,
2589
);
2590
2591
this.getHTMLElement().classList.add('settings-editor-tree');
2592
2593
this.style(getListStyles({
2594
listBackground: editorBackground,
2595
listActiveSelectionBackground: editorBackground,
2596
listActiveSelectionForeground: foreground,
2597
listFocusAndSelectionBackground: editorBackground,
2598
listFocusAndSelectionForeground: foreground,
2599
listFocusBackground: editorBackground,
2600
listFocusForeground: foreground,
2601
listHoverForeground: foreground,
2602
listHoverBackground: editorBackground,
2603
listHoverOutline: editorBackground,
2604
listFocusOutline: editorBackground,
2605
listInactiveSelectionBackground: editorBackground,
2606
listInactiveSelectionForeground: foreground,
2607
listInactiveFocusBackground: editorBackground,
2608
listInactiveFocusOutline: editorBackground,
2609
treeIndentGuidesStroke: undefined,
2610
treeInactiveIndentGuidesStroke: undefined,
2611
}));
2612
2613
this.disposables.add(configurationService.onDidChangeConfiguration(e => {
2614
if (e.affectsConfiguration('workbench.list.smoothScrolling')) {
2615
this.updateOptions({
2616
smoothScrolling: configurationService.getValue<boolean>('workbench.list.smoothScrolling')
2617
});
2618
}
2619
}));
2620
}
2621
2622
protected override createModel(user: string, options: IObjectTreeOptions<SettingsTreeGroupChild>): ITreeModel<SettingsTreeGroupChild | null, void, SettingsTreeGroupChild | null> {
2623
return new NonCollapsibleObjectTreeModel<SettingsTreeGroupChild>(user, options);
2624
}
2625
}
2626
2627
class CopySettingIdAction extends Action {
2628
static readonly ID = 'settings.copySettingId';
2629
static readonly LABEL = localize('copySettingIdLabel', "Copy Setting ID");
2630
2631
constructor(
2632
@IClipboardService private readonly clipboardService: IClipboardService
2633
) {
2634
super(CopySettingIdAction.ID, CopySettingIdAction.LABEL);
2635
}
2636
2637
override async run(context: SettingsTreeSettingElement): Promise<void> {
2638
if (context) {
2639
await this.clipboardService.writeText(context.setting.key);
2640
}
2641
2642
return Promise.resolve(undefined);
2643
}
2644
}
2645
2646
class CopySettingAsJSONAction extends Action {
2647
static readonly ID = 'settings.copySettingAsJSON';
2648
static readonly LABEL = localize('copySettingAsJSONLabel', "Copy Setting as JSON");
2649
2650
constructor(
2651
@IClipboardService private readonly clipboardService: IClipboardService
2652
) {
2653
super(CopySettingAsJSONAction.ID, CopySettingAsJSONAction.LABEL);
2654
}
2655
2656
override async run(context: SettingsTreeSettingElement): Promise<void> {
2657
if (context) {
2658
const jsonResult = `"${context.setting.key}": ${JSON.stringify(context.value, undefined, ' ')}`;
2659
await this.clipboardService.writeText(jsonResult);
2660
}
2661
2662
return Promise.resolve(undefined);
2663
}
2664
}
2665
2666
class CopySettingAsURLAction extends Action {
2667
static readonly ID = 'settings.copySettingAsURL';
2668
static readonly LABEL = localize('copySettingAsURLLabel', "Copy Setting as URL");
2669
2670
constructor(
2671
@IClipboardService private readonly clipboardService: IClipboardService,
2672
@IProductService private readonly productService: IProductService,
2673
) {
2674
super(CopySettingAsURLAction.ID, CopySettingAsURLAction.LABEL);
2675
}
2676
2677
override async run(context: SettingsTreeSettingElement): Promise<void> {
2678
if (context) {
2679
const settingKey = context.setting.key;
2680
const product = this.productService.urlProtocol;
2681
const uri = URI.from({ scheme: product, authority: SETTINGS_AUTHORITY, path: `/${settingKey}` }, true);
2682
await this.clipboardService.writeText(uri.toString());
2683
}
2684
2685
return Promise.resolve(undefined);
2686
}
2687
}
2688
2689
class SyncSettingAction extends Action {
2690
static readonly ID = 'settings.stopSyncingSetting';
2691
static readonly LABEL = localize('stopSyncingSetting', "Sync This Setting");
2692
2693
constructor(
2694
private readonly setting: ISetting,
2695
@IConfigurationService private readonly configService: IConfigurationService,
2696
) {
2697
super(SyncSettingAction.ID, SyncSettingAction.LABEL);
2698
this._register(Event.filter(configService.onDidChangeConfiguration, e => e.affectsConfiguration('settingsSync.ignoredSettings'))(() => this.update()));
2699
this.update();
2700
}
2701
2702
async update() {
2703
const ignoredSettings = getIgnoredSettings(getDefaultIgnoredSettings(), this.configService);
2704
this.checked = !ignoredSettings.includes(this.setting.key);
2705
}
2706
2707
override async run(): Promise<void> {
2708
// first remove the current setting completely from ignored settings
2709
let currentValue = [...this.configService.getValue<string[]>('settingsSync.ignoredSettings')];
2710
currentValue = currentValue.filter(v => v !== this.setting.key && v !== `-${this.setting.key}`);
2711
2712
const defaultIgnoredSettings = getDefaultIgnoredSettings();
2713
const isDefaultIgnored = defaultIgnoredSettings.includes(this.setting.key);
2714
const askedToSync = !this.checked;
2715
2716
// If asked to sync, then add only if it is ignored by default
2717
if (askedToSync && isDefaultIgnored) {
2718
currentValue.push(`-${this.setting.key}`);
2719
}
2720
2721
// If asked not to sync, then add only if it is not ignored by default
2722
if (!askedToSync && !isDefaultIgnored) {
2723
currentValue.push(this.setting.key);
2724
}
2725
2726
this.configService.updateValue('settingsSync.ignoredSettings', currentValue.length ? currentValue : undefined, ConfigurationTarget.USER);
2727
2728
return Promise.resolve(undefined);
2729
}
2730
2731
}
2732
2733
class ApplySettingToAllProfilesAction extends Action {
2734
static readonly ID = 'settings.applyToAllProfiles';
2735
static readonly LABEL = localize('applyToAllProfiles', "Apply Setting to all Profiles");
2736
2737
constructor(
2738
private readonly setting: ISetting,
2739
@IWorkbenchConfigurationService private readonly configService: IWorkbenchConfigurationService,
2740
) {
2741
super(ApplySettingToAllProfilesAction.ID, ApplySettingToAllProfilesAction.LABEL);
2742
this._register(Event.filter(configService.onDidChangeConfiguration, e => e.affectsConfiguration(APPLY_ALL_PROFILES_SETTING))(() => this.update()));
2743
this.update();
2744
}
2745
2746
update() {
2747
const allProfilesSettings = this.configService.getValue<string[]>(APPLY_ALL_PROFILES_SETTING);
2748
this.checked = allProfilesSettings.includes(this.setting.key);
2749
}
2750
2751
override async run(): Promise<void> {
2752
// first remove the current setting completely from ignored settings
2753
const value = this.configService.getValue<string[]>(APPLY_ALL_PROFILES_SETTING) ?? [];
2754
2755
if (this.checked) {
2756
value.splice(value.indexOf(this.setting.key), 1);
2757
} else {
2758
value.push(this.setting.key);
2759
}
2760
2761
const newValue = distinct(value);
2762
if (this.checked) {
2763
await this.configService.updateValue(this.setting.key, this.configService.inspect(this.setting.key).application?.value, ConfigurationTarget.USER_LOCAL);
2764
await this.configService.updateValue(APPLY_ALL_PROFILES_SETTING, newValue.length ? newValue : undefined, ConfigurationTarget.USER_LOCAL);
2765
} else {
2766
await this.configService.updateValue(APPLY_ALL_PROFILES_SETTING, newValue.length ? newValue : undefined, ConfigurationTarget.USER_LOCAL);
2767
await this.configService.updateValue(this.setting.key, this.configService.inspect(this.setting.key).userLocal?.value, ConfigurationTarget.USER_LOCAL);
2768
}
2769
}
2770
2771
}
2772
2773