src/vs/workbench/services/preferences/common/preferencesModels.ts

1219 LOC · 437 covered · 782 uncovered · 106 ranges · 12 concepts · 10 introducers · 8 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the filesrc/vs/workbench/common/editor/editorModel.ts · 50 LOCeditor/editorModel.tspreferencesModels.ts ×2 · 4 introduced LOCpreferencesModels.ts ×2preferencesModels.test|title=DefaultSettings separates groups with same id but different titles|occurrence=1, preferencesModels.test|title=DefaultSettings groups settings correctly when extension id is same as group id|occurrence=1 · 0 introduced LOCpreferencesModels.test|t…preferencesModels.ts ×2 · 2 introduced LOCpreferencesModels.ts ×2preferencesModels.test|title=DefaultSettings groups settings by title when they share the same extension id|occurrence=1 · 0 introduced LOCpreferencesModels.test|t…preferencesModels.ts ×2 · 6 introduced LOCpreferencesModels.ts ×2preferencesModels.ts ×1 · 1 introduced LOCpreferencesModels.ts ×1preferencesModels.ts ×2 · 7 introduced LOCpreferencesModels.ts ×2preferencesModels.ts ×1 · 2 introduced LOCpreferencesModels.ts ×1preferencesModels.ts ×3 · 3 introduced LOCpreferencesModels.ts ×3preferencesModels.ts ×2 · 3 introduced LOCpreferencesModels.ts ×2preferencesModels.ts ×1 · 3 introduced LOCpreferencesModels.ts ×1preferencesModels.ts ×90 · 441 introduced LOCpreferencesModels.ts ×90preferencesModels.test|title=DefaultSettings groups settings by id when they share the same extension id and have no title|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/preferences/test/common/preferencesModels.test|title=DefaultSettings groups settings by id when they share the same extension id and have no title|occurrence=1preferencesModels.test|t…preferencesModels.test|title=DefaultSettings groups settings by title when they share the same extension id|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/preferences/test/common/preferencesModels.test|title=DefaultSettings groups settings by title when they share the same extension id|occurrence=1preferencesModels.test|t…preferencesModels.test|title=DefaultSettings groups settings correctly when extension id is same as group id|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/preferences/test/common/preferencesModels.test|title=DefaultSettings groups settings correctly when extension id is same as group id|occurrence=1preferencesModels.test|t…preferencesModels.test|title=DefaultSettings merges untitled group into titled group if id matches|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/preferences/test/common/preferencesModels.test|title=DefaultSettings merges untitled group into titled group if id matches|occurrence=1preferencesModels.test|t…preferencesModels.test|title=DefaultSettings separates groups with same id (no title) but different extension ids|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/preferences/test/common/preferencesModels.test|title=DefaultSettings separates groups with same id (no title) but different extension ids|occurrence=1preferencesModels.test|t…preferencesModels.test|title=DefaultSettings separates groups with same id and title but different extension ids|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/preferences/test/common/preferencesModels.test|title=DefaultSettings separates groups with same id and title but different extension ids|occurrence=1preferencesModels.test|t…preferencesModels.test|title=DefaultSettings separates groups with same id but different titles|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/preferences/test/common/preferencesModels.test|title=DefaultSettings separates groups with same id but different titles|occurrence=1preferencesModels.test|t…preferencesModels.test|title=DefaultSettings sorts groups by order|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/preferences/test/common/preferencesModels.test|title=DefaultSettings sorts groups by order|occurrence=1preferencesModels.test|t…Focused file · src/vs/workbench/services/preferences/common/preferencesModels.ts · 1219 LOCcommon/preferencesModels…

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- preferencesModels.ts ×90
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 { coalesce } from '../../../../base/common/arrays.js';
7 > import { IStringDictionary } from '../../../../base/common/collections.js';
8 > import { Emitter, Event } from '../../../../base/common/event.js';
9 > import { JSONVisitor, visit } from '../../../../base/common/json.js';
10 > import { Disposable, IReference } from '../../../../base/common/lifecycle.js';
11 > import { URI } from '../../../../base/common/uri.js';
12 > import { IRange, Range } from '../../../../editor/common/core/range.js';
13 > import { Selection } from '../../../../editor/common/core/selection.js';
14 > import { ITextModel } from '../../../../editor/common/model.js';
15 > import { ISingleEditOperation } from '../../../../editor/common/core/editOperation.js';
16 > import { ITextEditorModel } from '../../../../editor/common/services/resolverService.js';
17 > import * as nls from '../../../../nls.js';
18 > import { ConfigurationTarget, IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
19 > import { ConfigurationDefaultValueSource, ConfigurationScope, Extensions, IConfigurationNode, IConfigurationRegistry, IRegisteredConfigurationPropertySchema, OVERRIDE_PROPERTY_REGEX } from '../../../../platform/configuration/common/configurationRegistry.js';
20 > import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
21 > import { Registry } from '../../../../platform/registry/common/platform.js';
22 > import { EditorModel } from '../../../common/editor/editorModel.js';
23 > import { IFilterMetadata, IFilterResult, IGroupFilter, IKeybindingsEditorModel, ISearchResultGroup, ISetting, ISettingMatch, ISettingMatcher, ISettingsEditorModel, ISettingsGroup, SettingMatchType } from './preferences.js';
24 > import { FOLDER_SCOPES, WORKSPACE_SCOPES } from '../../configuration/common/configuration.js';
25 > import { createValidator } from './preferencesValidation.js';
26 > import { isString } from '../../../../base/common/types.js';
27 >
28 > export const nullRange: IRange = { startLineNumber: -1, startColumn: -1, endLineNumber: -1, endColumn: -1 };
29 function isNullRange(range: IRange): boolean { return range.startLineNumber === -1 && range.startColumn === -1 && range.endLineNumber === -1 && range.endColumn === -1; }
31 > /**
32 > * Strips VS Code's custom `#settingId#` link syntax from a markdown string so the setting key
33 > * remains as inline code (e.g. `` `settingId` ``). Useful for contexts that don't render markdown links.
34 > */
35 > export function fixSettingLinks(text: string): string {
36 return text.replace(/`#([^#`]*)#`/g, (_, settingName) => `\`${settingName}\``);
37 }
39 abstract class AbstractSettingsModel extends EditorModel {
40
41 protected _currentResultGroups = new Map<string, ISearchResultGroup>();
43 > updateResultGroup(id: string, resultGroup: ISearchResultGroup | undefined): IFilterResult | undefined {
44 if (resultGroup) {
45 this._currentResultGroups.set(id, resultGroup);
46 } else {
47 this._currentResultGroups.delete(id);
48 }
49
50 this.removeDuplicateResults();
51 return this.update();
52 }
54 > /**
55 > * Remove duplicates between result groups, preferring results in earlier groups
56 > */
57 > private removeDuplicateResults(): void {
58 const settingKeys = new Set<string>();
59 [...this._currentResultGroups.keys()]
60 .sort((a, b) => this._currentResultGroups.get(a)!.order - this._currentResultGroups.get(b)!.order)
61 .forEach(groupId => {
62 const group = this._currentResultGroups.get(groupId)!;
63 group.result.filterMatches = group.result.filterMatches.filter(s => !settingKeys.has(s.setting.key));
64 group.result.filterMatches.forEach(s => settingKeys.add(s.setting.key));
65 });
66 }
68 > filterSettings(filter: string, groupFilter: IGroupFilter, settingMatcher: ISettingMatcher): ISettingMatch[] {
69 const allGroups = this.filterGroups;
70
71 const filterMatches: ISettingMatch[] = [];
72 for (const group of allGroups) {
73 const groupMatched = groupFilter(group);
74 for (const section of group.sections) {
75 for (const setting of section.settings) {
76 const settingMatchResult = settingMatcher(setting, group);
77
78 if (groupMatched || settingMatchResult) {
79 filterMatches.push({
80 setting,
81 matches: settingMatchResult && settingMatchResult.matches,
82 matchType: settingMatchResult?.matchType ?? SettingMatchType.None,
83 keyMatchScore: settingMatchResult?.keyMatchScore ?? 0,
84 score: settingMatchResult?.score ?? 0
85 });
86 }
87 }
88 }
89 }
90
91 return filterMatches;
92 }
94 > getPreference(key: string): ISetting | undefined {
95 for (const group of this.settingsGroups) {
96 for (const section of group.sections) {
97 for (const setting of section.settings) {
98 if (key === setting.key) {
99 return setting;
100 }
101 }
102 }
103 }
104
105 return undefined;
106 }
108 > protected collectMetadata(groups: ISearchResultGroup[]): IStringDictionary<IFilterMetadata> | null {
109 const metadata = Object.create(null);
110 let hasMetadata = false;
111 groups.forEach(g => {
112 if (g.result.metadata) {
113 metadata[g.id] = g.result.metadata;
114 hasMetadata = true;
115 }
116 });
117
118 return hasMetadata ? metadata : null;
119 }
121 >
122 > protected get filterGroups(): ISettingsGroup[] {
123 return this.settingsGroups;
124 }
126 > abstract settingsGroups: ISettingsGroup[];
127 >
128 > protected abstract update(): IFilterResult | undefined;
129 > }
130 >
131 > export class SettingsEditorModel extends AbstractSettingsModel implements ISettingsEditorModel {
132 >
133 > private _settingsGroups: ISettingsGroup[] | undefined;
134 > protected settingsModel: ITextModel;
135 >
136 > private readonly _onDidChangeGroups: Emitter<void> = this._register(new Emitter<void>());
137 > readonly onDidChangeGroups: Event<void> = this._onDidChangeGroups.event;
138 >
139 > constructor(reference: IReference<ITextEditorModel>, private _configurationTarget: ConfigurationTarget) {
140 super();
141 this.settingsModel = reference.object.textEditorModel!;
142 this._register(this.onWillDispose(() => reference.dispose()));
143 this._register(this.settingsModel.onDidChangeContent(() => {
144 this._settingsGroups = undefined;
145 this._onDidChangeGroups.fire();
146 }));
147 }
149 > get uri(): URI {
150 return this.settingsModel.uri;
151 }
153 > get configurationTarget(): ConfigurationTarget {
154 return this._configurationTarget;
155 }
157 > get settingsGroups(): ISettingsGroup[] {
158 if (!this._settingsGroups) {
159 this.parse();
160 }
161 return this._settingsGroups!;
162 }
164 > get content(): string {
165 return this.settingsModel.getValue();
166 }
168 > protected isSettingsProperty(property: string, previousParents: string[]): boolean {
169 return previousParents.length === 0; // Settings is root
170 }
172 > protected parse(): void {
173 this._settingsGroups = parse(this.settingsModel, (property: string, previousParents: string[]): boolean => this.isSettingsProperty(property, previousParents));
174 }
176 > protected update(): IFilterResult | undefined {
177 const resultGroups = [...this._currentResultGroups.values()];
178 if (!resultGroups.length) {
179 return undefined;
180 }
181
182 // Transform resultGroups into IFilterResult - ISetting ranges are already correct here
183 const filteredSettings: ISetting[] = [];
184 const matches: IRange[] = [];
185 resultGroups.forEach(group => {
186 group.result.filterMatches.forEach(filterMatch => {
187 filteredSettings.push(filterMatch.setting);
188 if (filterMatch.matches) {
189 matches.push(...filterMatch.matches);
190 }
191 });
192 });
193
194 let filteredGroup: ISettingsGroup | undefined;
195 const modelGroup = this.settingsGroups[0]; // Editable model has one or zero groups
196 if (modelGroup) {
197 filteredGroup = {
198 id: modelGroup.id,
199 range: modelGroup.range,
200 sections: [{
201 settings: filteredSettings
202 }],
203 title: modelGroup.title,
204 titleRange: modelGroup.titleRange,
205 order: modelGroup.order,
206 extensionInfo: modelGroup.extensionInfo
207 };
208 }
209
210 const metadata = this.collectMetadata(resultGroups);
211 return {
212 allGroups: this.settingsGroups,
213 filteredGroups: filteredGroup ? [filteredGroup] : [],
214 matches,
215 metadata: metadata ?? undefined
216 };
217 }
219 >
220 > export class Settings2EditorModel extends AbstractSettingsModel implements ISettingsEditorModel {
221 > private readonly _onDidChangeGroups: Emitter<void> = this._register(new Emitter<void>());
222 > readonly onDidChangeGroups: Event<void> = this._onDidChangeGroups.event;
223 >
224 > private additionalGroups: ISettingsGroup[] = [];
225 > private dirty = false;
226 >
227 > constructor(
228 private _defaultSettings: DefaultSettings,
229 @IConfigurationService configurationService: IConfigurationService,
230 ) {
231 super();
232
233 this._register(configurationService.onDidChangeConfiguration(e => {
234 if (e.source === ConfigurationTarget.DEFAULT) {
235 this.dirty = true;
236 this._onDidChangeGroups.fire();
237 }
238 }));
239 this._register(Registry.as<IConfigurationRegistry>(Extensions.Configuration).onDidSchemaChange(e => {
240 this.dirty = true;
241 this._onDidChangeGroups.fire();
242 }));
243 }
245 > /** Doesn't include the "Commonly Used" group */
246 > protected override get filterGroups(): ISettingsGroup[] {
247 return this.settingsGroups.slice(1);
248 }
250 > get settingsGroups(): ISettingsGroup[] {
251 const groups = this._defaultSettings.getSettingsGroups(this.dirty);
252 this.dirty = false;
253 return [...groups, ...this.additionalGroups];
254 }
256 > /** For programmatically added groups outside of registered configurations */
257 > setAdditionalGroups(groups: ISettingsGroup[]) {
258 this.additionalGroups = groups;
259 }
261 > protected update(): IFilterResult {
262 throw new Error('Not supported');
263 }
265 >
266 function parse(model: ITextModel, isSettingsProperty: (currentProperty: string, previousParents: string[]) => boolean): ISettingsGroup[] {
267 const settings: ISetting[] = [];
268 let overrideSetting: ISetting | null = null;
269
270 let currentProperty: string | null = null;
271 let currentParent: any = [];
272 const previousParents: any[] = [];
273 let settingsPropertyIndex: number = -1;
274 const range = {
275 startLineNumber: 0,
276 startColumn: 0,
277 endLineNumber: 0,
278 endColumn: 0
279 };
280
281 function onValue(value: any, offset: number, length: number) {
282 if (Array.isArray(currentParent)) {
283 (<any[]>currentParent).push(value);
284 } else if (currentProperty) {
285 currentParent[currentProperty] = value;
286 }
287 if (previousParents.length === settingsPropertyIndex + 1 || (previousParents.length === settingsPropertyIndex + 2 && overrideSetting !== null)) {
288 // settings value started
289 const setting = previousParents.length === settingsPropertyIndex + 1 ? settings[settings.length - 1] : overrideSetting!.overrides![overrideSetting!.overrides!.length - 1];
290 if (setting) {
291 const valueStartPosition = model.getPositionAt(offset);
292 const valueEndPosition = model.getPositionAt(offset + length);
293 setting.value = value;
294 setting.valueRange = {
295 startLineNumber: valueStartPosition.lineNumber,
296 startColumn: valueStartPosition.column,
297 endLineNumber: valueEndPosition.lineNumber,
298 endColumn: valueEndPosition.column
299 };
300 setting.range = Object.assign(setting.range, {
301 endLineNumber: valueEndPosition.lineNumber,
302 endColumn: valueEndPosition.column
303 });
304 }
305 }
306 }
307 const visitor: JSONVisitor = {
308 onObjectBegin: (offset: number, length: number) => {
309 if (isSettingsProperty(currentProperty!, previousParents)) {
310 // Settings started
311 settingsPropertyIndex = previousParents.length;
312 const position = model.getPositionAt(offset);
313 range.startLineNumber = position.lineNumber;
314 range.startColumn = position.column;
315 }
316 const object = {};
317 onValue(object, offset, length);
318 currentParent = object;
319 currentProperty = null;
320 previousParents.push(currentParent);
321 },
322 onObjectProperty: (name: string, offset: number, length: number) => {
323 currentProperty = name;
324 if (previousParents.length === settingsPropertyIndex + 1 || (previousParents.length === settingsPropertyIndex + 2 && overrideSetting !== null)) {
325 // setting started
326 const settingStartPosition = model.getPositionAt(offset);
327 const setting: ISetting = {
328 description: [],
329 descriptionIsMarkdown: false,
330 key: name,
331 keyRange: {
332 startLineNumber: settingStartPosition.lineNumber,
333 startColumn: settingStartPosition.column + 1,
334 endLineNumber: settingStartPosition.lineNumber,
335 endColumn: settingStartPosition.column + length
336 },
337 range: {
338 startLineNumber: settingStartPosition.lineNumber,
339 startColumn: settingStartPosition.column,
340 endLineNumber: 0,
341 endColumn: 0
342 },
343 value: null,
344 valueRange: nullRange,
345 descriptionRanges: [],
346 overrides: [],
347 overrideOf: overrideSetting ?? undefined,
348 };
349 if (previousParents.length === settingsPropertyIndex + 1) {
350 settings.push(setting);
351 if (OVERRIDE_PROPERTY_REGEX.test(name)) {
352 overrideSetting = setting;
353 }
354 } else {
355 overrideSetting!.overrides!.push(setting);
356 }
357 }
358 },
359 onObjectEnd: (offset: number, length: number) => {
360 currentParent = previousParents.pop();
361 if (settingsPropertyIndex !== -1 && (previousParents.length === settingsPropertyIndex + 1 || (previousParents.length === settingsPropertyIndex + 2 && overrideSetting !== null))) {
362 // setting ended
363 const setting = previousParents.length === settingsPropertyIndex + 1 ? settings[settings.length - 1] : overrideSetting!.overrides![overrideSetting!.overrides!.length - 1];
364 if (setting) {
365 const valueEndPosition = model.getPositionAt(offset + length);
366 setting.valueRange = Object.assign(setting.valueRange, {
367 endLineNumber: valueEndPosition.lineNumber,
368 endColumn: valueEndPosition.column
369 });
370 setting.range = Object.assign(setting.range, {
371 endLineNumber: valueEndPosition.lineNumber,
372 endColumn: valueEndPosition.column
373 });
374 }
375
376 if (previousParents.length === settingsPropertyIndex + 1) {
377 overrideSetting = null;
378 }
379 }
380 if (previousParents.length === settingsPropertyIndex) {
381 // settings ended
382 const position = model.getPositionAt(offset);
383 range.endLineNumber = position.lineNumber;
384 range.endColumn = position.column;
385 settingsPropertyIndex = -1;
386 }
387 },
388 onArrayBegin: (offset: number, length: number) => {
389 const array: any[] = [];
390 onValue(array, offset, length);
391 previousParents.push(currentParent);
392 currentParent = array;
393 currentProperty = null;
394 },
395 onArrayEnd: (offset: number, length: number) => {
396 currentParent = previousParents.pop();
397 if (previousParents.length === settingsPropertyIndex + 1 || (previousParents.length === settingsPropertyIndex + 2 && overrideSetting !== null)) {
398 // setting value ended
399 const setting = previousParents.length === settingsPropertyIndex + 1 ? settings[settings.length - 1] : overrideSetting!.overrides![overrideSetting!.overrides!.length - 1];
400 if (setting) {
401 const valueEndPosition = model.getPositionAt(offset + length);
402 setting.valueRange = Object.assign(setting.valueRange, {
403 endLineNumber: valueEndPosition.lineNumber,
404 endColumn: valueEndPosition.column
405 });
406 setting.range = Object.assign(setting.range, {
407 endLineNumber: valueEndPosition.lineNumber,
408 endColumn: valueEndPosition.column
409 });
410 }
411 }
412 },
413 onLiteralValue: onValue,
414 onError: (error) => {
415 const setting = settings[settings.length - 1];
416 if (setting && (isNullRange(setting.range) || isNullRange(setting.keyRange) || isNullRange(setting.valueRange))) {
417 settings.pop();
418 }
419 }
420 };
421 if (!model.isDisposed()) {
422 visit(model.getValue(), visitor);
423 }
424 return settings.length > 0 ? [{
425 id: model.isDisposed() ? '' : model.id,
426 sections: [
427 {
428 settings
429 }
430 ],
431 title: '',
432 titleRange: nullRange,
433 range
434 } satisfies ISettingsGroup] : [];
435 }
437 > export class WorkspaceConfigurationEditorModel extends SettingsEditorModel {
438
439 private _configurationGroups: ISettingsGroup[] = [];
441 > get configurationGroups(): ISettingsGroup[] {
442 return this._configurationGroups;
443 }
445 > protected override parse(): void {
446 super.parse();
447 this._configurationGroups = parse(this.settingsModel, (property: string, previousParents: string[]): boolean => previousParents.length === 0);
448 }
450 > protected override isSettingsProperty(property: string, previousParents: string[]): boolean {
451 return property === 'settings' && previousParents.length === 1;
452 }
454 > }
455 >
456 > export class DefaultSettings extends Disposable {
457 >
458 > private _allSettingsGroups: ISettingsGroup[] | undefined;
459 > private _content: string | undefined;
460 > private _contentWithoutMostCommonlyUsed: string | undefined;
461 > private _settingsByName = new Map<string, ISetting>();
462 >
463 > private readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
464 > readonly onDidChange: Event<void> = this._onDidChange.event;
465 >
466 > constructor(
467 > private _mostCommonlyUsedSettingsKeys: string[],
468 > readonly target: ConfigurationTarget,
469 > readonly configurationService: IConfigurationService
470 > ) {
471 > super();
472 > this._register(configurationService.onDidChangeConfiguration(e => {
473 if (e.source === ConfigurationTarget.DEFAULT) {
474 this.reset();
475 this._onDidChange.fire();
476 }
478 > }
479 >
480 > getContent(forceUpdate = false): string {
481 if (!this._content || forceUpdate) {
482 this.initialize();
483 }
484
485 return this._content!;
486 }
488 > getContentWithoutMostCommonlyUsed(forceUpdate = false): string {
489 if (!this._contentWithoutMostCommonlyUsed || forceUpdate) {
490 this.initialize();
491 }
492
493 return this._contentWithoutMostCommonlyUsed!;
494 }
496 > getSettingsGroups(forceUpdate = false): ISettingsGroup[] {
497 if (!this._allSettingsGroups || forceUpdate) {
498 this.initialize();
499 }
500
501 return this._allSettingsGroups!;
502 }
504 > private initialize(): void {
505 this._allSettingsGroups = this.parse();
506 this._content = this.toContent(this._allSettingsGroups, 0);
507 this._contentWithoutMostCommonlyUsed = this.toContent(this._allSettingsGroups, 1);
508 }
510 > private reset(): void {
511 this._content = undefined;
512 this._contentWithoutMostCommonlyUsed = undefined;
513 this._allSettingsGroups = undefined;
514 }
516 > private parse(): ISettingsGroup[] {
517 const settingsGroups = this.getRegisteredGroups();
518 this.initAllSettingsMap(settingsGroups);
519 const mostCommonlyUsed = this.getMostCommonlyUsedSettings();
520 return [mostCommonlyUsed, ...settingsGroups];
521 }
523 > getRegisteredGroups(): ISettingsGroup[] {
524 > const registry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
525 > const allConfigurations: IStringDictionary<IRegisteredConfigurationPropertySchema> = { ...registry.getConfigurationProperties() };
526 > const excludedConfigurations = registry.getExcludedConfigurationProperties();
527 >
528 > for (const policyKey of this.configurationService.keys().policy ?? []) {
529 const policyConfiguration = excludedConfigurations[policyKey];
530 if (policyConfiguration) {
531 allConfigurations[policyKey] = policyConfiguration;
532 }
533 }
535 > const groups = this.removeEmptySettingsGroups(this.parseProperties(allConfigurations).sort(this.compareGroups));
536 > return this.sortGroups(groups);
537 > }
538 >
539 > private sortGroups(groups: ISettingsGroup[]): ISettingsGroup[] {
540 > groups.forEach(group => {
541 > group.sections.forEach(section => {
542 > section.settings.sort((a, b) => a.key.localeCompare(b.key));
543 > });
544 > });
545 >
546 > return groups;
547 > }
548 >
549 > private initAllSettingsMap(allSettingsGroups: ISettingsGroup[]): void {
550 this._settingsByName = new Map<string, ISetting>();
551 for (const group of allSettingsGroups) {
552 for (const section of group.sections) {
553 for (const setting of section.settings) {
554 this._settingsByName.set(setting.key, setting);
555 }
556 }
557 }
558 }
560 > private getMostCommonlyUsedSettings(): ISettingsGroup {
561 const settings = coalesce(this._mostCommonlyUsedSettingsKeys.map(key => {
562 const setting = this._settingsByName.get(key);
563 if (setting) {
564 return {
565 description: setting.description,
566 key: setting.key,
567 value: setting.value,
568 keyRange: nullRange,
569 range: nullRange,
570 valueRange: nullRange,
571 overrides: [],
572 scope: ConfigurationScope.RESOURCE,
573 type: setting.type,
574 enum: setting.enum,
575 enumDescriptions: setting.enumDescriptions,
576 descriptionRanges: []
577 } satisfies ISetting;
578 }
579 return null;
580 }));
581
582 return {
583 id: 'mostCommonlyUsed',
584 range: nullRange,
585 title: nls.localize('commonlyUsed', "Commonly Used"),
586 titleRange: nullRange,
587 sections: [
588 {
589 settings
590 }
591 ]
592 } satisfies ISettingsGroup;
593 }
595 > private parseProperties(properties: IStringDictionary<IRegisteredConfigurationPropertySchema>): ISettingsGroup[] {
596 > const result: ISettingsGroup[] = [];
597 > const byTitle = new Map<string, ISettingsGroup[]>();
598 > const byId = new Map<string, ISettingsGroup[]>();
599 > for (const [key, property] of Object.entries(properties)) {
600 > if (!property.section) {
601 continue;
602 }
604 > let settingsGroup: ISettingsGroup | undefined;
605 >
606 > if (property.section.title) {
607 > const groups = byTitle.get(property.section.title); preferencesModels.ts ×2
608 > if (groups) {
609 > const extensionId = property.section.extensionInfo?.id; preferencesModels.ts ×2
610 > settingsGroup = groups.find(g => g.extensionInfo?.id === extensionId);
611 > }
614 > if (!settingsGroup && property.section.id) {
615 > const groups = byId.get(property.section.id);
616 > if (groups) {
617 > const extensionId = property.section.extensionInfo?.id; preferencesModels.ts ×1
618 > settingsGroup = groups.find(g => g.extensionInfo?.id === extensionId && !g.title);
619 > }
620 > if (settingsGroup && !settingsGroup?.title && property.section.title) { preferencesModels.ts ×90
621 > settingsGroup.title = property.section.title; preferencesModels.ts ×2
622 > const byTitleGroups = byTitle.get(property.section.title);
623 > if (byTitleGroups) {
624 byTitleGroups.push(settingsGroup);
625 > } else { preferencesModels.ts ×2
626 > byTitle.set(property.section.title, [settingsGroup]);
627 > }
628 > }
630 >
631 > if (!settingsGroup) {
632 > settingsGroup = { sections: [{ title: property.section.title, settings: [] }], id: property.section.id || '', title: property.section.title ?? '', titleRange: nullRange, order: property.section.order, range: nullRange, extensionInfo: isString(property.source) ? undefined : property.source };
633 > result.push(settingsGroup);
634 > if (property.section.title) {
635 > const byTitleGroups = byTitle.get(property.section.title); preferencesModels.ts ×2
636 > if (byTitleGroups) {
637 > byTitleGroups.push(settingsGroup); preferencesModels.ts ×2
638 > } else { preferencesModels.ts ×2
639 > byTitle.set(property.section.title, [settingsGroup]);
640 > }
641 > }
642 > if (property.section.id) { preferencesModels.ts ×90
643 > const byIdGroups = byId.get(property.section.id);
644 > if (byIdGroups) {
645 > byIdGroups.push(settingsGroup); preferencesModels.ts ×1
646 > } else { preferencesModels.ts ×90
647 > byId.set(property.section.id, [settingsGroup]);
648 > }
649 > }
650 > }
651 >
652 > const setting = this.parseSetting(key, property);
653 > if (setting) {
654 > settingsGroup.sections[0].settings.push(setting);
655 > }
656 > }
657 > return result;
658 > }
659 >
660 > private removeEmptySettingsGroups(settingsGroups: ISettingsGroup[]): ISettingsGroup[] {
661 > const result: ISettingsGroup[] = [];
662 > for (const settingsGroup of settingsGroups) {
663 > settingsGroup.sections = settingsGroup.sections.filter(section => section.settings.length > 0);
664 > if (settingsGroup.sections.length) {
665 > result.push(settingsGroup);
666 > }
667 > }
668 > return result;
669 > }
670 >
671 > private parseSetting(key: string, prop: IRegisteredConfigurationPropertySchema): ISetting | undefined {
672 > if (!this.matchesScope(prop)) {
673 return undefined;
674 }
676 > const value = prop.default;
677 > let description = (prop.markdownDescription || prop.description || '');
678 > if (typeof description !== 'string') {
679 description = '';
680 }
681 > const descriptionLines = description.split('\n'); preferencesModels.ts ×90
682 > const overrides = OVERRIDE_PROPERTY_REGEX.test(key) ? this.parseOverrideSettings(prop.default) : [];
683 > let listItemType: string | undefined;
684 > if (prop.type === 'array' && prop.items && !Array.isArray(prop.items) && prop.items.type) {
685 if (prop.items.enum) {
686 listItemType = 'enum';
687 } else if (!Array.isArray(prop.items.type)) {
688 listItemType = prop.items.type;
689 }
690 }
692 > const objectProperties = prop.type === 'object' ? prop.properties : undefined;
693 > const objectPatternProperties = prop.type === 'object' ? prop.patternProperties : undefined;
694 > const objectAdditionalProperties = prop.type === 'object' ? prop.additionalProperties : undefined;
695 > const propertyNames = prop.type === 'object' ? prop.propertyNames : undefined;
696 >
697 > let enumToUse = prop.enum;
698 > let enumDescriptions = prop.markdownEnumDescriptions ?? prop.enumDescriptions;
699 > let enumDescriptionsAreMarkdown = !!prop.markdownEnumDescriptions;
700 > if (listItemType === 'enum' && !Array.isArray(prop.items)) {
701 enumToUse = prop.items!.enum;
702 enumDescriptions = prop.items!.markdownEnumDescriptions ?? prop.items!.enumDescriptions;
703 enumDescriptionsAreMarkdown = !!prop.items!.markdownEnumDescriptions;
704 }
706 > let allKeysAreBoolean = false;
707 > if (prop.type === 'object' && !prop.additionalProperties && prop.properties && Object.keys(prop.properties).length) {
708 allKeysAreBoolean = Object.keys(prop.properties).every(key => {
709 return prop.properties![key].type === 'boolean';
710 });
711 }
713 > let isLanguageTagSetting = false;
714 > if (OVERRIDE_PROPERTY_REGEX.test(key)) {
715 isLanguageTagSetting = true;
716 }
718 > let defaultValueSource: ConfigurationDefaultValueSource | undefined;
719 > if (!isLanguageTagSetting) {
720 > const registeredConfigurationProp = prop as IRegisteredConfigurationPropertySchema;
721 > if (registeredConfigurationProp && registeredConfigurationProp.defaultValueSource) {
722 defaultValueSource = registeredConfigurationProp.defaultValueSource;
723 }
725 >
726 > if (!enumToUse && (prop.enumItemLabels || enumDescriptions || enumDescriptionsAreMarkdown)) {
727 console.error(`The setting ${key} has enum-related fields, but doesn't have an enum field. This setting may render improperly in the Settings editor.`);
728 }
730 > return {
731 > key,
732 > value,
733 > description: descriptionLines,
734 > descriptionIsMarkdown: !!prop.markdownDescription,
735 > keywords: prop.keywords,
736 > range: nullRange,
737 > keyRange: nullRange,
738 > valueRange: nullRange,
739 > descriptionRanges: [],
740 > overrides,
741 > scope: prop.scope,
742 > type: prop.type,
743 > arrayItemType: listItemType,
744 > objectProperties,
745 > objectPatternProperties,
746 > objectAdditionalProperties,
747 > propertyNames,
748 > enum: enumToUse,
749 > enumDescriptions: enumDescriptions,
750 > enumDescriptionsAreMarkdown: enumDescriptionsAreMarkdown,
751 > enumItemLabels: prop.enumItemLabels,
752 > uniqueItems: prop.uniqueItems,
753 > tags: prop.tags,
754 > disallowSyncIgnore: prop.disallowSyncIgnore,
755 > restricted: prop.restricted,
756 > extensionInfo: isString(prop.source) ? undefined : prop.source,
757 > deprecationMessage: prop.markdownDeprecationMessage || prop.deprecationMessage,
758 > deprecationMessageIsMarkdown: !!prop.markdownDeprecationMessage,
759 > validator: createValidator(prop),
760 > allKeysAreBoolean,
761 > editPresentation: prop.editPresentation,
762 > order: prop.order,
763 > nonLanguageSpecificDefaultValueSource: defaultValueSource,
764 > isLanguageTagSetting,
765 > categoryLabel: (isString(prop.source) ? undefined : prop.source?.id) === prop.section?.id ? prop.title : prop.section?.id
766 > };
767 > }
768 >
769 > private parseOverrideSettings(overrideSettings: any): ISetting[] {
770 return Object.keys(overrideSettings).map((key) => ({
771 key,
772 value: overrideSettings[key],
773 description: [],
774 descriptionIsMarkdown: false,
775 range: nullRange,
776 keyRange: nullRange,
777 valueRange: nullRange,
778 descriptionRanges: [],
779 overrides: []
780 }));
781 }
783 > private matchesScope(property: IConfigurationNode): boolean {
784 > if (!property.scope) {
785 return true;
786 }
787 > if (this.target === ConfigurationTarget.WORKSPACE_FOLDER) { preferencesModels.ts ×90
788 return FOLDER_SCOPES.indexOf(property.scope) !== -1;
789 }
790 > if (this.target === ConfigurationTarget.WORKSPACE) { preferencesModels.ts ×90
791 return WORKSPACE_SCOPES.indexOf(property.scope) !== -1;
792 }
793 > return true; preferencesModels.ts ×90
794 > }
795 >
796 > private compareGroups(c1: ISettingsGroup, c2: ISettingsGroup): number {
797 > if (typeof c1?.order !== 'number') { preferencesModels.ts ×3
798 > return 1; preferencesModels.ts ×1
799 > }
800 > if (typeof c2?.order !== 'number') { preferencesModels.ts ×3
801 return -1;
802 }
803 > if (c1.order === c2.order) { preferencesModels.ts ×2
804 const title1 = c1.title || '';
805 const title2 = c2.title || '';
806 return title1.localeCompare(title2);
807 }
808 > return c1.order - c2.order; preferencesModels.ts ×2
811 > private toContent(settingsGroups: ISettingsGroup[], startIndex: number): string {
812 const builder = new SettingsContentBuilder();
813 for (let i = startIndex; i < settingsGroups.length; i++) {
814 builder.pushGroup(settingsGroups[i], i === startIndex, i === settingsGroups.length - 1);
815 }
816 return builder.getContent();
817 }
819 > }
820 >
821 > export class DefaultSettingsEditorModel extends AbstractSettingsModel implements ISettingsEditorModel {
822 >
823 > private _model: ITextModel;
824 >
825 > private readonly _onDidChangeGroups: Emitter<void> = this._register(new Emitter<void>());
826 > readonly onDidChangeGroups: Event<void> = this._onDidChangeGroups.event;
827 >
828 > constructor(
829 private _uri: URI,
830 reference: IReference<ITextEditorModel>,
831 private readonly defaultSettings: DefaultSettings
832 ) {
833 super();
834
835 this._register(defaultSettings.onDidChange(() => this._onDidChangeGroups.fire()));
836 this._model = reference.object.textEditorModel!;
837 this._register(this.onWillDispose(() => reference.dispose()));
838 }
840 > get uri(): URI {
841 return this._uri;
842 }
844 > get target(): ConfigurationTarget {
845 return this.defaultSettings.target;
846 }
848 > get settingsGroups(): ISettingsGroup[] {
849 return this.defaultSettings.getSettingsGroups();
850 }
852 > protected override get filterGroups(): ISettingsGroup[] {
853 // Don't look at "commonly used" for filter
854 return this.settingsGroups.slice(1);
855 }
857 > protected update(): IFilterResult | undefined {
858 if (this._model.isDisposed()) {
859 return undefined;
860 }
861
862 // Grab current result groups, only render non-empty groups
863 const resultGroups = [...this._currentResultGroups.values()]
864 .sort((a, b) => a.order - b.order);
865 const nonEmptyResultGroups = resultGroups.filter(group => group.result.filterMatches.length);
866
867 const startLine = this.settingsGroups.at(-1)!.range.endLineNumber + 2;
868 const { settingsGroups: filteredGroups, matches } = this.writeResultGroups(nonEmptyResultGroups, startLine);
869
870 const metadata = this.collectMetadata(resultGroups);
871 return resultGroups.length ?
872 {
873 allGroups: this.settingsGroups,
874 filteredGroups,
875 matches,
876 metadata: metadata ?? undefined
877 } :
878 undefined;
879 }
881 > /**
882 > * Translate the ISearchResultGroups to text, and write it to the editor model
883 > */
884 > private writeResultGroups(groups: ISearchResultGroup[], startLine: number): { matches: IRange[]; settingsGroups: ISettingsGroup[] } {
885 const contentBuilderOffset = startLine - 1;
886 const builder = new SettingsContentBuilder(contentBuilderOffset);
887
888 const settingsGroups: ISettingsGroup[] = [];
889 const matches: IRange[] = [];
890 if (groups.length) {
891 builder.pushLine(',');
892 groups.forEach(resultGroup => {
893 const settingsGroup = this.getGroup(resultGroup);
894 settingsGroups.push(settingsGroup);
895 matches.push(...this.writeSettingsGroupToBuilder(builder, settingsGroup, resultGroup.result.filterMatches));
896 });
897 }
898
899 // note: 1-indexed line numbers here
900 const groupContent = builder.getContent() + '\n';
901 const groupEndLine = this._model.getLineCount();
902 const cursorPosition = new Selection(startLine, 1, startLine, 1);
903 const edit: ISingleEditOperation = {
904 text: groupContent,
905 forceMoveMarkers: true,
906 range: new Range(startLine, 1, groupEndLine, 1)
907 };
908
909 this._model.pushEditOperations([cursorPosition], [edit], () => [cursorPosition]);
910
911 // Force tokenization now - otherwise it may be slightly delayed, causing a flash of white text
912 const tokenizeTo = Math.min(startLine + 60, this._model.getLineCount());
913 this._model.tokenization.forceTokenization(tokenizeTo);
914
915 return { matches, settingsGroups };
916 }
918 > private writeSettingsGroupToBuilder(builder: SettingsContentBuilder, settingsGroup: ISettingsGroup, filterMatches: ISettingMatch[]): IRange[] {
919 filterMatches = filterMatches
920 .map(filteredMatch => {
921 // Fix match ranges to offset from setting start line
922 return {
923 setting: filteredMatch.setting,
924 score: filteredMatch.score,
925 matchType: filteredMatch.matchType,
926 keyMatchScore: filteredMatch.keyMatchScore,
927 matches: filteredMatch.matches && filteredMatch.matches.map(match => {
928 return new Range(
929 match.startLineNumber - filteredMatch.setting.range.startLineNumber,
930 match.startColumn,
931 match.endLineNumber - filteredMatch.setting.range.startLineNumber,
932 match.endColumn);
933 })
934 };
935 });
936
937 builder.pushGroup(settingsGroup);
938
939 // builder has rewritten settings ranges, fix match ranges
940 const fixedMatches = filterMatches
941 .map(m => m.matches || [])
942 .flatMap((settingMatches, i) => {
943 const setting = settingsGroup.sections[0].settings[i];
944 return settingMatches.map(range => {
945 return new Range(
946 range.startLineNumber + setting.range.startLineNumber,
947 range.startColumn,
948 range.endLineNumber + setting.range.startLineNumber,
949 range.endColumn);
950 });
951 });
952
953 return fixedMatches;
954 }
956 > private copySetting(setting: ISetting): ISetting {
957 return {
958 description: setting.description,
959 scope: setting.scope,
960 type: setting.type,
961 enum: setting.enum,
962 enumDescriptions: setting.enumDescriptions,
963 key: setting.key,
964 value: setting.value,
965 range: setting.range,
966 overrides: [],
967 overrideOf: setting.overrideOf,
968 tags: setting.tags,
969 deprecationMessage: setting.deprecationMessage,
970 keyRange: nullRange,
971 valueRange: nullRange,
972 descriptionIsMarkdown: undefined,
973 descriptionRanges: []
974 };
975 }
977 > override getPreference(key: string): ISetting | undefined {
978 for (const group of this.settingsGroups) {
979 for (const section of group.sections) {
980 for (const setting of section.settings) {
981 if (setting.key === key) {
982 return setting;
983 }
984 }
985 }
986 }
987 return undefined;
988 }
990 > private getGroup(resultGroup: ISearchResultGroup): ISettingsGroup {
991 return {
992 id: resultGroup.id,
993 range: nullRange,
994 title: resultGroup.label,
995 titleRange: nullRange,
996 sections: [
997 {
998 settings: resultGroup.result.filterMatches.map(m => this.copySetting(m.setting))
999 }
1000 ]
1001 };
1002 }
1004 >
1005 > class SettingsContentBuilder {
1006 > private _contentByLines: string[];
1007 >
1008 > private get lineCountWithOffset(): number {
1009 > return this._contentByLines.length + this._rangeOffset;
1010 > }
1011 >
1012 > private get lastLine(): string {
1013 return this._contentByLines[this._contentByLines.length - 1] || '';
1014 }
1016 > constructor(private _rangeOffset = 0) {
1017 this._contentByLines = [];
1018 }
1020 > pushLine(...lineText: string[]): void {
1021 this._contentByLines.push(...lineText);
1022 }
1024 > pushGroup(settingsGroups: ISettingsGroup, isFirst?: boolean, isLast?: boolean): void {
1025 this._contentByLines.push(isFirst ? '[{' : '{');
1026 const lastSetting = this._pushGroup(settingsGroups, ' ');
1027
1028 if (lastSetting) {
1029 // Strip the comma from the last setting
1030 const lineIdx = lastSetting.range.endLineNumber - this._rangeOffset;
1031 const content = this._contentByLines[lineIdx - 2];
1032 this._contentByLines[lineIdx - 2] = content.substring(0, content.length - 1);
1033 }
1034
1035 this._contentByLines.push(isLast ? '}]' : '},');
1036 }
1038 > protected _pushGroup(group: ISettingsGroup, indent: string): ISetting | null {
1039 let lastSetting: ISetting | null = null;
1040 const groupStart = this.lineCountWithOffset + 1;
1041 for (const section of group.sections) {
1042 if (section.title) {
1043 this.addDescription([section.title], indent, this._contentByLines);
1044 }
1045
1046 if (section.settings.length) {
1047 for (const setting of section.settings) {
1048 this.pushSetting(setting, indent);
1049 lastSetting = setting;
1050 }
1051 }
1052
1053 }
1054 group.range = { startLineNumber: groupStart, startColumn: 1, endLineNumber: this.lineCountWithOffset, endColumn: this.lastLine.length };
1055 return lastSetting;
1056 }
1058 > getContent(): string {
1059 return this._contentByLines.join('\n');
1060 }
1062 > private pushSetting(setting: ISetting, indent: string): void {
1063 const settingStart = this.lineCountWithOffset + 1;
1064
1065 this.pushSettingDescription(setting, indent);
1066
1067 let preValueContent = indent;
1068 const keyString = JSON.stringify(setting.key);
1069 preValueContent += keyString;
1070 setting.keyRange = { startLineNumber: this.lineCountWithOffset + 1, startColumn: preValueContent.indexOf(setting.key) + 1, endLineNumber: this.lineCountWithOffset + 1, endColumn: setting.key.length };
1071
1072 preValueContent += ': ';
1073 const valueStart = this.lineCountWithOffset + 1;
1074 this.pushValue(setting, preValueContent, indent);
1075
1076 setting.valueRange = { startLineNumber: valueStart, startColumn: preValueContent.length + 1, endLineNumber: this.lineCountWithOffset, endColumn: this.lastLine.length + 1 };
1077 this._contentByLines[this._contentByLines.length - 1] += ',';
1078 this._contentByLines.push('');
1079 setting.range = { startLineNumber: settingStart, startColumn: 1, endLineNumber: this.lineCountWithOffset, endColumn: this.lastLine.length };
1080 }
1082 > private pushSettingDescription(setting: ISetting, indent: string): void {
1083 setting.descriptionRanges = [];
1084 const descriptionPreValue = indent + '// ';
1085 const deprecationMessageLines = setting.deprecationMessage?.split(/\n/g) ?? [];
1086 for (let line of [...deprecationMessageLines, ...setting.description]) {
1087 line = fixSettingLinks(line);
1088
1089 this._contentByLines.push(descriptionPreValue + line);
1090 setting.descriptionRanges.push({ startLineNumber: this.lineCountWithOffset, startColumn: this.lastLine.indexOf(line) + 1, endLineNumber: this.lineCountWithOffset, endColumn: this.lastLine.length });
1091 }
1092
1093 if (setting.enum && setting.enumDescriptions?.some(desc => !!desc)) {
1094 setting.enumDescriptions.forEach((desc, i) => {
1095 const displayEnum = escapeInvisibleChars(String(setting.enum![i]));
1096 const line = desc ?
1097 `${displayEnum}: ${fixSettingLinks(desc)}` :
1098 displayEnum;
1099
1100 const lines = line.split(/\n/g);
1101 lines[0] = ' - ' + lines[0];
1102 this._contentByLines.push(...lines.map(l => `${indent}// ${l}`));
1103
1104 setting.descriptionRanges.push({ startLineNumber: this.lineCountWithOffset, startColumn: this.lastLine.indexOf(line) + 1, endLineNumber: this.lineCountWithOffset, endColumn: this.lastLine.length });
1105 });
1106 }
1107 }
1109 > private pushValue(setting: ISetting, preValueConent: string, indent: string): void {
1110 const valueString = JSON.stringify(setting.value, null, indent);
1111 if (valueString && (typeof setting.value === 'object')) {
1112 if (setting.overrides && setting.overrides.length) {
1113 this._contentByLines.push(preValueConent + ' {');
1114 for (const subSetting of setting.overrides) {
1115 this.pushSetting(subSetting, indent + indent);
1116 this._contentByLines.pop();
1117 }
1118 const lastSetting = setting.overrides[setting.overrides.length - 1];
1119 const content = this._contentByLines[lastSetting.range.endLineNumber - 2];
1120 this._contentByLines[lastSetting.range.endLineNumber - 2] = content.substring(0, content.length - 1);
1121 this._contentByLines.push(indent + '}');
1122 } else {
1123 const mulitLineValue = valueString.split('\n');
1124 this._contentByLines.push(preValueConent + mulitLineValue[0]);
1125 for (let i = 1; i < mulitLineValue.length; i++) {
1126 this._contentByLines.push(indent + mulitLineValue[i]);
1127 }
1128 }
1129 } else {
1130 this._contentByLines.push(preValueConent + valueString);
1131 }
1132 }
1134 > private addDescription(description: string[], indent: string, result: string[]) {
1135 for (const line of description) {
1136 result.push(indent + '// ' + line);
1137 }
1138 }
1140 >
1141 > class RawSettingsContentBuilder extends SettingsContentBuilder {
1142 >
1143 > constructor(private indent: string = '\t') {
1144 super(0);
1145 }
1147 > override pushGroup(settingsGroups: ISettingsGroup): void {
1148 this._pushGroup(settingsGroups, this.indent);
1149 }
1151 > }
1152 >
1153 > export class DefaultRawSettingsEditorModel extends Disposable {
1154 >
1155 > private _content: string | null = null;
1156 >
1157 > private readonly _onDidContentChanged = this._register(new Emitter<void>());
1158 > readonly onDidContentChanged = this._onDidContentChanged.event;
1159 >
1160 > constructor(private defaultSettings: DefaultSettings) {
1161 super();
1162 this._register(defaultSettings.onDidChange(() => {
1163 this._content = null;
1164 this._onDidContentChanged.fire();
1165 }));
1166 }
1168 > get content(): string {
1169 if (this._content === null) {
1170 const builder = new RawSettingsContentBuilder();
1171 builder.pushLine('{');
1172 for (const settingsGroup of this.defaultSettings.getRegisteredGroups()) {
1173 builder.pushGroup(settingsGroup);
1174 }
1175 builder.pushLine('}');
1176 this._content = builder.getContent();
1177 }
1178 return this._content;
1179 }
1181 >
1182 function escapeInvisibleChars(enumValue: string): string {
1183 return enumValue && enumValue
1184 .replace(/\n/g, '\\n')
1185 .replace(/\r/g, '\\r');
1186 }
1188 > export function defaultKeybindingsContents(keybindingService: IKeybindingService): string {
1189 const defaultsHeader = '// ' + nls.localize('defaultKeybindingsHeader', "Override key bindings by placing them into your key bindings file.");
1190 return defaultsHeader + '\n' + keybindingService.getDefaultKeybindingsContent();
1191 }
1193 > export class DefaultKeybindingsEditorModel implements IKeybindingsEditorModel<any> {
1194 >
1195 > private _content: string | undefined;
1196 >
1197 > constructor(private _uri: URI,
1198 @IKeybindingService private readonly keybindingService: IKeybindingService) {
1199 }
1201 > get uri(): URI {
1202 return this._uri;
1203 }
1205 > get content(): string {
1206 if (!this._content) {
1207 this._content = defaultKeybindingsContents(this.keybindingService);
1208 }
1209 return this._content;
1210 }
1212 > getPreference(): any {
1213 return null;
1214 }
1216 > dispose(): void {
1217 // Not disposable
1218 }