Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.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 { EventHelper, getDomNodePagePosition } from '../../../../base/browser/dom.js';
7
import { IAction, SubmenuAction } from '../../../../base/common/actions.js';
8
import { Delayer } from '../../../../base/common/async.js';
9
import { CancellationToken } from '../../../../base/common/cancellation.js';
10
import { IStringDictionary } from '../../../../base/common/collections.js';
11
import { Emitter, Event } from '../../../../base/common/event.js';
12
import { IJSONSchema } from '../../../../base/common/jsonSchema.js';
13
import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js';
14
import { ResourceMap } from '../../../../base/common/map.js';
15
import { isEqual } from '../../../../base/common/resources.js';
16
import { ThemeIcon } from '../../../../base/common/themables.js';
17
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from '../../../../editor/browser/editorBrowser.js';
18
import { EditorOption } from '../../../../editor/common/config/editorOptions.js';
19
import { Position } from '../../../../editor/common/core/position.js';
20
import { IRange, Range } from '../../../../editor/common/core/range.js';
21
import { Selection } from '../../../../editor/common/core/selection.js';
22
import { ICursorPositionChangedEvent } from '../../../../editor/common/cursorEvents.js';
23
import * as editorCommon from '../../../../editor/common/editorCommon.js';
24
import * as languages from '../../../../editor/common/languages.js';
25
import { IModelDeltaDecoration, ITextModel, TrackedRangeStickiness } from '../../../../editor/common/model.js';
26
import { ModelDecorationOptions } from '../../../../editor/common/model/textModel.js';
27
import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js';
28
import { CodeActionKind } from '../../../../editor/contrib/codeAction/common/types.js';
29
import * as nls from '../../../../nls.js';
30
import { ConfigurationTarget, IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
31
import { Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationPropertySchema, IConfigurationRegistry, IRegisteredConfigurationPropertySchema, OVERRIDE_PROPERTY_REGEX, overrideIdentifiersFromKey } from '../../../../platform/configuration/common/configurationRegistry.js';
32
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
33
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
34
import { IMarkerData, IMarkerService, MarkerSeverity, MarkerTag } from '../../../../platform/markers/common/markers.js';
35
import { Registry } from '../../../../platform/registry/common/platform.js';
36
import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';
37
import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js';
38
import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';
39
import { IWorkspaceTrustManagementService } from '../../../../platform/workspace/common/workspaceTrust.js';
40
import { RangeHighlightDecorations } from '../../../browser/codeeditor.js';
41
import { settingsEditIcon } from './preferencesIcons.js';
42
import { EditPreferenceWidget } from './preferencesWidgets.js';
43
import { APPLICATION_SCOPES, APPLY_ALL_PROFILES_SETTING, IWorkbenchConfigurationService } from '../../../services/configuration/common/configuration.js';
44
import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js';
45
import { IPreferencesEditorModel, IPreferencesService, ISetting, ISettingsEditorModel, ISettingsGroup } from '../../../services/preferences/common/preferences.js';
46
import { DefaultSettingsEditorModel, SettingsEditorModel, WorkspaceConfigurationEditorModel } from '../../../services/preferences/common/preferencesModels.js';
47
import { IUserDataProfileService } from '../../../services/userDataProfile/common/userDataProfile.js';
48
import { EXPERIMENTAL_INDICATOR_DESCRIPTION, PREVIEW_INDICATOR_DESCRIPTION } from '../common/preferences.js';
49
import { mcpConfigurationSection } from '../../mcp/common/mcpConfiguration.js';
50
import { McpCommandIds } from '../../mcp/common/mcpCommandIds.js';
51
52
export interface IPreferencesRenderer extends IDisposable {
53
render(): void;
54
updatePreference(key: string, value: any, source: ISetting): void;
55
focusPreference(setting: ISetting): void;
56
clearFocus(setting: ISetting): void;
57
editPreference(setting: ISetting): boolean;
58
}
59
60
export class UserSettingsRenderer extends Disposable implements IPreferencesRenderer {
61
62
private settingHighlighter: SettingHighlighter;
63
private editSettingActionRenderer: EditSettingRenderer;
64
private modelChangeDelayer: Delayer<void> = new Delayer<void>(200);
65
private associatedPreferencesModel!: IPreferencesEditorModel<ISetting>;
66
67
private unsupportedSettingsRenderer: UnsupportedSettingsRenderer;
68
private mcpSettingsRenderer: McpSettingsRenderer;
69
70
constructor(protected editor: ICodeEditor, readonly preferencesModel: SettingsEditorModel,
71
@IPreferencesService protected preferencesService: IPreferencesService,
72
@IConfigurationService private readonly configurationService: IConfigurationService,
73
@IInstantiationService protected instantiationService: IInstantiationService
74
) {
75
super();
76
this.settingHighlighter = this._register(instantiationService.createInstance(SettingHighlighter, editor));
77
this.editSettingActionRenderer = this._register(this.instantiationService.createInstance(EditSettingRenderer, this.editor, this.preferencesModel, this.settingHighlighter));
78
this._register(this.editSettingActionRenderer.onUpdateSetting(({ key, value, source }) => this.updatePreference(key, value, source)));
79
this._register(this.editor.getModel()!.onDidChangeContent(() => this.modelChangeDelayer.trigger(() => this.onModelChanged())));
80
this.unsupportedSettingsRenderer = this._register(instantiationService.createInstance(UnsupportedSettingsRenderer, editor, preferencesModel));
81
this.mcpSettingsRenderer = this._register(instantiationService.createInstance(McpSettingsRenderer, editor, preferencesModel));
82
}
83
84
render(): void {
85
this.editSettingActionRenderer.render(this.preferencesModel.settingsGroups, this.associatedPreferencesModel);
86
this.unsupportedSettingsRenderer.render();
87
this.mcpSettingsRenderer.render();
88
}
89
90
updatePreference(key: string, value: any, source: IIndexedSetting): void {
91
const overrideIdentifiers = source.overrideOf ? overrideIdentifiersFromKey(source.overrideOf.key) : null;
92
const resource = this.preferencesModel.uri;
93
this.configurationService.updateValue(key, value, { overrideIdentifiers, resource }, this.preferencesModel.configurationTarget)
94
.then(() => this.onSettingUpdated(source));
95
}
96
97
private onModelChanged(): void {
98
if (!this.editor.hasModel()) {
99
// model could have been disposed during the delay
100
return;
101
}
102
this.render();
103
}
104
105
private onSettingUpdated(setting: ISetting) {
106
this.editor.focus();
107
setting = this.getSetting(setting)!;
108
if (setting) {
109
// TODO:@sandy Selection range should be template range
110
this.editor.setSelection(setting.valueRange);
111
this.settingHighlighter.highlight(setting, true);
112
}
113
}
114
115
private getSetting(setting: ISetting): ISetting | undefined {
116
const { key, overrideOf } = setting;
117
if (overrideOf) {
118
const setting = this.getSetting(overrideOf);
119
for (const override of setting!.overrides!) {
120
if (override.key === key) {
121
return override;
122
}
123
}
124
return undefined;
125
}
126
127
return this.preferencesModel.getPreference(key);
128
}
129
130
focusPreference(setting: ISetting): void {
131
const s = this.getSetting(setting);
132
if (s) {
133
this.settingHighlighter.highlight(s, true);
134
this.editor.setPosition({ lineNumber: s.keyRange.startLineNumber, column: s.keyRange.startColumn });
135
} else {
136
this.settingHighlighter.clear(true);
137
}
138
}
139
140
clearFocus(setting: ISetting): void {
141
this.settingHighlighter.clear(true);
142
}
143
144
editPreference(setting: ISetting): boolean {
145
const editableSetting = this.getSetting(setting);
146
return !!(editableSetting && this.editSettingActionRenderer.activateOnSetting(editableSetting));
147
}
148
149
}
150
151
export class WorkspaceSettingsRenderer extends UserSettingsRenderer implements IPreferencesRenderer {
152
153
private workspaceConfigurationRenderer: WorkspaceConfigurationRenderer;
154
155
constructor(editor: ICodeEditor, preferencesModel: SettingsEditorModel,
156
@IPreferencesService preferencesService: IPreferencesService,
157
@IConfigurationService configurationService: IConfigurationService,
158
@IInstantiationService instantiationService: IInstantiationService
159
) {
160
super(editor, preferencesModel, preferencesService, configurationService, instantiationService);
161
this.workspaceConfigurationRenderer = this._register(instantiationService.createInstance(WorkspaceConfigurationRenderer, editor, preferencesModel));
162
}
163
164
override render(): void {
165
super.render();
166
this.workspaceConfigurationRenderer.render();
167
}
168
}
169
170
export interface IIndexedSetting extends ISetting {
171
index: number;
172
groupId: string;
173
}
174
175
class EditSettingRenderer extends Disposable {
176
177
private editPreferenceWidgetForCursorPosition: EditPreferenceWidget<IIndexedSetting>;
178
private editPreferenceWidgetForMouseMove: EditPreferenceWidget<IIndexedSetting>;
179
180
private settingsGroups: ISettingsGroup[] = [];
181
associatedPreferencesModel!: IPreferencesEditorModel<ISetting>;
182
private toggleEditPreferencesForMouseMoveDelayer: Delayer<void>;
183
184
private readonly _onUpdateSetting: Emitter<{ key: string; value: any; source: IIndexedSetting }> = this._register(new Emitter<{ key: string; value: any; source: IIndexedSetting }>());
185
readonly onUpdateSetting: Event<{ key: string; value: any; source: IIndexedSetting }> = this._onUpdateSetting.event;
186
187
constructor(private editor: ICodeEditor, private primarySettingsModel: ISettingsEditorModel,
188
private settingHighlighter: SettingHighlighter,
189
@IConfigurationService private readonly configurationService: IConfigurationService,
190
@IInstantiationService private readonly instantiationService: IInstantiationService,
191
@IContextMenuService private readonly contextMenuService: IContextMenuService
192
) {
193
super();
194
195
this.editPreferenceWidgetForCursorPosition = this._register(this.instantiationService.createInstance(EditPreferenceWidget<IIndexedSetting>, editor));
196
this.editPreferenceWidgetForMouseMove = this._register(this.instantiationService.createInstance(EditPreferenceWidget<IIndexedSetting>, editor));
197
this.toggleEditPreferencesForMouseMoveDelayer = new Delayer<void>(75);
198
199
this._register(this.editPreferenceWidgetForCursorPosition.onClick(e => this.onEditSettingClicked(this.editPreferenceWidgetForCursorPosition, e)));
200
this._register(this.editPreferenceWidgetForMouseMove.onClick(e => this.onEditSettingClicked(this.editPreferenceWidgetForMouseMove, e)));
201
202
this._register(this.editor.onDidChangeCursorPosition(positionChangeEvent => this.onPositionChanged(positionChangeEvent)));
203
this._register(this.editor.onMouseMove(mouseMoveEvent => this.onMouseMoved(mouseMoveEvent)));
204
this._register(this.editor.onDidChangeConfiguration(() => this.onConfigurationChanged()));
205
}
206
207
render(settingsGroups: ISettingsGroup[], associatedPreferencesModel: IPreferencesEditorModel<ISetting>): void {
208
this.editPreferenceWidgetForCursorPosition.hide();
209
this.editPreferenceWidgetForMouseMove.hide();
210
this.settingsGroups = settingsGroups;
211
this.associatedPreferencesModel = associatedPreferencesModel;
212
213
const settings = this.getSettings(this.editor.getPosition()!.lineNumber);
214
if (settings.length) {
215
this.showEditPreferencesWidget(this.editPreferenceWidgetForCursorPosition, settings);
216
}
217
}
218
219
private isDefaultSettings(): boolean {
220
return this.primarySettingsModel instanceof DefaultSettingsEditorModel;
221
}
222
223
private onConfigurationChanged(): void {
224
if (!this.editor.getOption(EditorOption.glyphMargin)) {
225
this.editPreferenceWidgetForCursorPosition.hide();
226
this.editPreferenceWidgetForMouseMove.hide();
227
}
228
}
229
230
private onPositionChanged(positionChangeEvent: ICursorPositionChangedEvent) {
231
this.editPreferenceWidgetForMouseMove.hide();
232
const settings = this.getSettings(positionChangeEvent.position.lineNumber);
233
if (settings.length) {
234
this.showEditPreferencesWidget(this.editPreferenceWidgetForCursorPosition, settings);
235
} else {
236
this.editPreferenceWidgetForCursorPosition.hide();
237
}
238
}
239
240
private onMouseMoved(mouseMoveEvent: IEditorMouseEvent): void {
241
const editPreferenceWidget = this.getEditPreferenceWidgetUnderMouse(mouseMoveEvent);
242
if (editPreferenceWidget) {
243
this.onMouseOver(editPreferenceWidget);
244
return;
245
}
246
this.settingHighlighter.clear();
247
this.toggleEditPreferencesForMouseMoveDelayer.trigger(() => this.toggleEditPreferenceWidgetForMouseMove(mouseMoveEvent));
248
}
249
250
private getEditPreferenceWidgetUnderMouse(mouseMoveEvent: IEditorMouseEvent): EditPreferenceWidget<ISetting> | undefined {
251
if (mouseMoveEvent.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN) {
252
const line = mouseMoveEvent.target.position.lineNumber;
253
if (this.editPreferenceWidgetForMouseMove.getLine() === line && this.editPreferenceWidgetForMouseMove.isVisible()) {
254
return this.editPreferenceWidgetForMouseMove;
255
}
256
if (this.editPreferenceWidgetForCursorPosition.getLine() === line && this.editPreferenceWidgetForCursorPosition.isVisible()) {
257
return this.editPreferenceWidgetForCursorPosition;
258
}
259
}
260
return undefined;
261
}
262
263
private toggleEditPreferenceWidgetForMouseMove(mouseMoveEvent: IEditorMouseEvent): void {
264
const settings = mouseMoveEvent.target.position ? this.getSettings(mouseMoveEvent.target.position.lineNumber) : null;
265
if (settings && settings.length) {
266
this.showEditPreferencesWidget(this.editPreferenceWidgetForMouseMove, settings);
267
} else {
268
this.editPreferenceWidgetForMouseMove.hide();
269
}
270
}
271
272
private showEditPreferencesWidget(editPreferencesWidget: EditPreferenceWidget<ISetting>, settings: IIndexedSetting[]) {
273
const line = settings[0].valueRange.startLineNumber;
274
if (this.editor.getOption(EditorOption.glyphMargin) && this.marginFreeFromOtherDecorations(line)) {
275
editPreferencesWidget.show(line, nls.localize('editTtile', "Edit"), settings);
276
const editPreferenceWidgetToHide = editPreferencesWidget === this.editPreferenceWidgetForCursorPosition ? this.editPreferenceWidgetForMouseMove : this.editPreferenceWidgetForCursorPosition;
277
editPreferenceWidgetToHide.hide();
278
}
279
}
280
281
private marginFreeFromOtherDecorations(line: number): boolean {
282
const decorations = this.editor.getLineDecorations(line);
283
if (decorations) {
284
for (const { options } of decorations) {
285
if (options.glyphMarginClassName && options.glyphMarginClassName.indexOf(ThemeIcon.asClassName(settingsEditIcon)) === -1) {
286
return false;
287
}
288
}
289
}
290
return true;
291
}
292
293
private getSettings(lineNumber: number): IIndexedSetting[] {
294
const configurationMap = this.getConfigurationsMap();
295
return this.getSettingsAtLineNumber(lineNumber).filter(setting => {
296
const configurationNode = configurationMap[setting.key];
297
if (configurationNode) {
298
if (configurationNode.policy && this.configurationService.inspect(setting.key).policyValue !== undefined) {
299
return false;
300
}
301
if (this.isDefaultSettings()) {
302
if (setting.key === 'launch') {
303
// Do not show because of https://github.com/microsoft/vscode/issues/32593
304
return false;
305
}
306
return true;
307
}
308
if (configurationNode.type === 'boolean' || configurationNode.enum) {
309
if ((<SettingsEditorModel>this.primarySettingsModel).configurationTarget !== ConfigurationTarget.WORKSPACE_FOLDER) {
310
return true;
311
}
312
if (configurationNode.scope === ConfigurationScope.RESOURCE || configurationNode.scope === ConfigurationScope.LANGUAGE_OVERRIDABLE) {
313
return true;
314
}
315
}
316
}
317
return false;
318
});
319
}
320
321
private getSettingsAtLineNumber(lineNumber: number): IIndexedSetting[] {
322
// index of setting, across all groups/sections
323
let index = 0;
324
325
const settings: IIndexedSetting[] = [];
326
for (const group of this.settingsGroups) {
327
if (group.range.startLineNumber > lineNumber) {
328
break;
329
}
330
if (lineNumber >= group.range.startLineNumber && lineNumber <= group.range.endLineNumber) {
331
for (const section of group.sections) {
332
for (const setting of section.settings) {
333
if (setting.range.startLineNumber > lineNumber) {
334
break;
335
}
336
if (lineNumber >= setting.range.startLineNumber && lineNumber <= setting.range.endLineNumber) {
337
if (!this.isDefaultSettings() && setting.overrides!.length) {
338
// Only one level because override settings cannot have override settings
339
for (const overrideSetting of setting.overrides!) {
340
if (lineNumber >= overrideSetting.range.startLineNumber && lineNumber <= overrideSetting.range.endLineNumber) {
341
settings.push({ ...overrideSetting, index, groupId: group.id });
342
}
343
}
344
} else {
345
settings.push({ ...setting, index, groupId: group.id });
346
}
347
}
348
349
index++;
350
}
351
}
352
}
353
}
354
return settings;
355
}
356
357
private onMouseOver(editPreferenceWidget: EditPreferenceWidget<ISetting>): void {
358
this.settingHighlighter.highlight(editPreferenceWidget.preferences[0]);
359
}
360
361
private onEditSettingClicked(editPreferenceWidget: EditPreferenceWidget<IIndexedSetting>, e: IEditorMouseEvent): void {
362
EventHelper.stop(e.event, true);
363
364
const actions = this.getSettings(editPreferenceWidget.getLine()).length === 1 ? this.getActions(editPreferenceWidget.preferences[0], this.getConfigurationsMap()[editPreferenceWidget.preferences[0].key])
365
: editPreferenceWidget.preferences.map(setting => new SubmenuAction(`preferences.submenu.${setting.key}`, setting.key, this.getActions(setting, this.getConfigurationsMap()[setting.key])));
366
this.contextMenuService.showContextMenu({
367
getAnchor: () => e.event,
368
getActions: () => actions
369
});
370
}
371
372
activateOnSetting(setting: ISetting): boolean {
373
const startLine = setting.keyRange.startLineNumber;
374
const settings = this.getSettings(startLine);
375
if (!settings.length) {
376
return false;
377
}
378
379
this.editPreferenceWidgetForMouseMove.show(startLine, '', settings);
380
const actions = this.getActions(this.editPreferenceWidgetForMouseMove.preferences[0], this.getConfigurationsMap()[this.editPreferenceWidgetForMouseMove.preferences[0].key]);
381
this.contextMenuService.showContextMenu({
382
getAnchor: () => this.toAbsoluteCoords(new Position(startLine, 1)),
383
getActions: () => actions
384
});
385
386
return true;
387
}
388
389
private toAbsoluteCoords(position: Position): { x: number; y: number } {
390
const positionCoords = this.editor.getScrolledVisiblePosition(position);
391
const editorCoords = getDomNodePagePosition(this.editor.getDomNode()!);
392
const x = editorCoords.left + positionCoords!.left;
393
const y = editorCoords.top + positionCoords!.top + positionCoords!.height;
394
395
return { x, y: y + 10 };
396
}
397
398
private getConfigurationsMap(): { [qualifiedKey: string]: IConfigurationPropertySchema } {
399
return Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();
400
}
401
402
private getActions(setting: IIndexedSetting, jsonSchema: IJSONSchema): IAction[] {
403
if (jsonSchema.type === 'boolean') {
404
return [{
405
id: 'truthyValue',
406
label: 'true',
407
tooltip: 'true',
408
enabled: true,
409
run: () => this.updateSetting(setting.key, true, setting),
410
class: undefined
411
}, {
412
id: 'falsyValue',
413
label: 'false',
414
tooltip: 'false',
415
enabled: true,
416
run: () => this.updateSetting(setting.key, false, setting),
417
class: undefined
418
}];
419
}
420
if (jsonSchema.enum) {
421
return jsonSchema.enum.map(value => {
422
return {
423
id: value,
424
label: JSON.stringify(value),
425
tooltip: JSON.stringify(value),
426
enabled: true,
427
run: () => this.updateSetting(setting.key, value, setting),
428
class: undefined
429
};
430
});
431
}
432
return this.getDefaultActions(setting);
433
}
434
435
private getDefaultActions(setting: IIndexedSetting): IAction[] {
436
if (this.isDefaultSettings()) {
437
const settingInOtherModel = this.associatedPreferencesModel.getPreference(setting.key);
438
return [{
439
id: 'setDefaultValue',
440
label: settingInOtherModel ? nls.localize('replaceDefaultValue', "Replace in Settings") : nls.localize('copyDefaultValue', "Copy to Settings"),
441
tooltip: settingInOtherModel ? nls.localize('replaceDefaultValue', "Replace in Settings") : nls.localize('copyDefaultValue', "Copy to Settings"),
442
enabled: true,
443
run: () => this.updateSetting(setting.key, setting.value, setting),
444
class: undefined
445
}];
446
}
447
return [];
448
}
449
450
private updateSetting(key: string, value: any, source: IIndexedSetting): void {
451
this._onUpdateSetting.fire({ key, value, source });
452
}
453
}
454
455
class SettingHighlighter extends Disposable {
456
457
private fixedHighlighter: RangeHighlightDecorations;
458
private volatileHighlighter: RangeHighlightDecorations;
459
460
constructor(private editor: ICodeEditor, @IInstantiationService instantiationService: IInstantiationService) {
461
super();
462
this.fixedHighlighter = this._register(instantiationService.createInstance(RangeHighlightDecorations));
463
this.volatileHighlighter = this._register(instantiationService.createInstance(RangeHighlightDecorations));
464
}
465
466
highlight(setting: ISetting, fix: boolean = false) {
467
this.volatileHighlighter.removeHighlightRange();
468
this.fixedHighlighter.removeHighlightRange();
469
470
const highlighter = fix ? this.fixedHighlighter : this.volatileHighlighter;
471
highlighter.highlightRange({
472
range: setting.valueRange,
473
resource: this.editor.getModel()!.uri
474
}, this.editor);
475
476
this.editor.revealLineInCenterIfOutsideViewport(setting.valueRange.startLineNumber, editorCommon.ScrollType.Smooth);
477
}
478
479
clear(fix: boolean = false): void {
480
this.volatileHighlighter.removeHighlightRange();
481
if (fix) {
482
this.fixedHighlighter.removeHighlightRange();
483
}
484
}
485
}
486
487
class UnsupportedSettingsRenderer extends Disposable implements languages.CodeActionProvider {
488
489
private renderingDelayer: Delayer<void> = new Delayer<void>(200);
490
491
private readonly codeActions = new ResourceMap<[Range, languages.CodeAction[]][]>(uri => this.uriIdentityService.extUri.getComparisonKey(uri));
492
493
constructor(
494
private readonly editor: ICodeEditor,
495
private readonly settingsEditorModel: SettingsEditorModel,
496
@IMarkerService private readonly markerService: IMarkerService,
497
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
498
@IWorkbenchConfigurationService private readonly configurationService: IWorkbenchConfigurationService,
499
@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService,
500
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
501
@ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService,
502
@IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService,
503
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
504
) {
505
super();
506
this._register(this.editor.getModel()!.onDidChangeContent(() => this.delayedRender()));
507
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.source === ConfigurationTarget.DEFAULT)(() => this.delayedRender()));
508
this._register(languageFeaturesService.codeActionProvider.register({ pattern: settingsEditorModel.uri.path }, this));
509
this._register(userDataProfileService.onDidChangeCurrentProfile(() => this.delayedRender()));
510
}
511
512
private delayedRender(): void {
513
this.renderingDelayer.trigger(() => this.render());
514
}
515
516
public render(): void {
517
this.codeActions.clear();
518
const markerData: IMarkerData[] = this.generateMarkerData();
519
if (markerData.length) {
520
this.markerService.changeOne('UnsupportedSettingsRenderer', this.settingsEditorModel.uri, markerData);
521
} else {
522
this.markerService.remove('UnsupportedSettingsRenderer', [this.settingsEditorModel.uri]);
523
}
524
}
525
526
async provideCodeActions(model: ITextModel, range: Range | Selection, context: languages.CodeActionContext, token: CancellationToken): Promise<languages.CodeActionList> {
527
const actions: languages.CodeAction[] = [];
528
const codeActionsByRange = this.codeActions.get(model.uri);
529
if (codeActionsByRange) {
530
for (const [codeActionsRange, codeActions] of codeActionsByRange) {
531
if (codeActionsRange.containsRange(range)) {
532
actions.push(...codeActions);
533
}
534
}
535
}
536
return {
537
actions,
538
dispose: () => { }
539
};
540
}
541
542
private generateMarkerData(): IMarkerData[] {
543
const markerData: IMarkerData[] = [];
544
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();
545
for (const settingsGroup of this.settingsEditorModel.settingsGroups) {
546
for (const section of settingsGroup.sections) {
547
for (const setting of section.settings) {
548
if (OVERRIDE_PROPERTY_REGEX.test(setting.key)) {
549
if (setting.overrides) {
550
this.handleOverrides(setting.overrides, configurationRegistry, markerData);
551
}
552
continue;
553
}
554
const configuration = configurationRegistry[setting.key];
555
if (configuration) {
556
this.handleUnstableSettingConfiguration(setting, configuration, markerData);
557
if (this.handlePolicyConfiguration(setting, configuration, markerData)) {
558
continue;
559
}
560
switch (this.settingsEditorModel.configurationTarget) {
561
case ConfigurationTarget.USER_LOCAL:
562
this.handleLocalUserConfiguration(setting, configuration, markerData);
563
break;
564
case ConfigurationTarget.USER_REMOTE:
565
this.handleRemoteUserConfiguration(setting, configuration, markerData);
566
break;
567
case ConfigurationTarget.WORKSPACE:
568
this.handleWorkspaceConfiguration(setting, configuration, markerData);
569
break;
570
case ConfigurationTarget.WORKSPACE_FOLDER:
571
this.handleWorkspaceFolderConfiguration(setting, configuration, markerData);
572
break;
573
}
574
} else {
575
markerData.push(this.generateUnknownConfigurationMarker(setting));
576
}
577
}
578
}
579
}
580
return markerData;
581
}
582
583
private handlePolicyConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): boolean {
584
if (!configuration.policy) {
585
return false;
586
}
587
if (this.configurationService.inspect(setting.key).policyValue === undefined) {
588
return false;
589
}
590
if (this.settingsEditorModel.configurationTarget === ConfigurationTarget.DEFAULT) {
591
return false;
592
}
593
markerData.push({
594
severity: MarkerSeverity.Hint,
595
tags: [MarkerTag.Unnecessary],
596
...setting.range,
597
message: nls.localize('unsupportedPolicySetting', "This setting cannot be applied because it is configured in the system policy.")
598
});
599
return true;
600
}
601
602
private handleOverrides(overrides: ISetting[], configurationRegistry: IStringDictionary<IRegisteredConfigurationPropertySchema>, markerData: IMarkerData[]): void {
603
for (const setting of overrides || []) {
604
const configuration = configurationRegistry[setting.key];
605
if (configuration) {
606
if (configuration.scope !== ConfigurationScope.LANGUAGE_OVERRIDABLE) {
607
markerData.push({
608
severity: MarkerSeverity.Hint,
609
tags: [MarkerTag.Unnecessary],
610
...setting.range,
611
message: nls.localize('unsupportLanguageOverrideSetting', "This setting cannot be applied because it is not registered as language override setting.")
612
});
613
}
614
} else {
615
markerData.push(this.generateUnknownConfigurationMarker(setting));
616
}
617
}
618
}
619
620
private handleLocalUserConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): void {
621
if (!this.userDataProfileService.currentProfile.isDefault && !this.userDataProfileService.currentProfile.useDefaultFlags?.settings) {
622
if (isEqual(this.userDataProfilesService.defaultProfile.settingsResource, this.settingsEditorModel.uri) && !this.configurationService.isSettingAppliedForAllProfiles(setting.key)) {
623
// If we're in the default profile setting file, and the setting cannot be applied in all profiles
624
markerData.push({
625
severity: MarkerSeverity.Hint,
626
tags: [MarkerTag.Unnecessary],
627
...setting.range,
628
message: nls.localize('defaultProfileSettingWhileNonDefaultActive', "This setting cannot be applied while a non-default profile is active. It will be applied when the default profile is active.")
629
});
630
} else if (isEqual(this.userDataProfileService.currentProfile.settingsResource, this.settingsEditorModel.uri)) {
631
if (configuration.scope && APPLICATION_SCOPES.includes(configuration.scope)) {
632
// If we're in a profile setting file, and the setting is application-scoped, fade it out.
633
markerData.push(this.generateUnsupportedApplicationSettingMarker(setting));
634
} else if (this.configurationService.isSettingAppliedForAllProfiles(setting.key)) {
635
// If we're in the non-default profile setting file, and the setting can be applied in all profiles, fade it out.
636
markerData.push({
637
severity: MarkerSeverity.Hint,
638
tags: [MarkerTag.Unnecessary],
639
...setting.range,
640
message: nls.localize('allProfileSettingWhileInNonDefaultProfileSetting', "This setting cannot be applied because it is configured to be applied in all profiles using setting {0}. Value from the default profile will be used instead.", APPLY_ALL_PROFILES_SETTING)
641
});
642
}
643
}
644
}
645
if (this.environmentService.remoteAuthority && (configuration.scope === ConfigurationScope.MACHINE || configuration.scope === ConfigurationScope.APPLICATION_MACHINE || configuration.scope === ConfigurationScope.MACHINE_OVERRIDABLE)) {
646
markerData.push({
647
severity: MarkerSeverity.Hint,
648
tags: [MarkerTag.Unnecessary],
649
...setting.range,
650
message: nls.localize('unsupportedRemoteMachineSetting', "This setting cannot be applied in this window. It will be applied when you open a local window.")
651
});
652
}
653
}
654
655
private handleRemoteUserConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): void {
656
if (configuration.scope === ConfigurationScope.APPLICATION) {
657
markerData.push(this.generateUnsupportedApplicationSettingMarker(setting));
658
}
659
}
660
661
private handleWorkspaceConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): void {
662
if (configuration.scope && APPLICATION_SCOPES.includes(configuration.scope)) {
663
markerData.push(this.generateUnsupportedApplicationSettingMarker(setting));
664
}
665
666
if (configuration.scope === ConfigurationScope.MACHINE) {
667
markerData.push(this.generateUnsupportedMachineSettingMarker(setting));
668
}
669
670
if (!this.workspaceTrustManagementService.isWorkspaceTrusted() && configuration.restricted) {
671
const marker = this.generateUntrustedSettingMarker(setting);
672
markerData.push(marker);
673
const codeActions = this.generateUntrustedSettingCodeActions([marker]);
674
this.addCodeActions(marker, codeActions);
675
}
676
}
677
678
private handleWorkspaceFolderConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): void {
679
if (configuration.scope && APPLICATION_SCOPES.includes(configuration.scope)) {
680
markerData.push(this.generateUnsupportedApplicationSettingMarker(setting));
681
}
682
683
if (configuration.scope === ConfigurationScope.MACHINE) {
684
markerData.push(this.generateUnsupportedMachineSettingMarker(setting));
685
}
686
687
if (configuration.scope === ConfigurationScope.WINDOW) {
688
markerData.push({
689
severity: MarkerSeverity.Hint,
690
tags: [MarkerTag.Unnecessary],
691
...setting.range,
692
message: nls.localize('unsupportedWindowSetting', "This setting cannot be applied in this workspace. It will be applied when you open the containing workspace folder directly.")
693
});
694
}
695
696
if (!this.workspaceTrustManagementService.isWorkspaceTrusted() && configuration.restricted) {
697
const marker = this.generateUntrustedSettingMarker(setting);
698
markerData.push(marker);
699
const codeActions = this.generateUntrustedSettingCodeActions([marker]);
700
this.addCodeActions(marker, codeActions);
701
}
702
}
703
704
private handleUnstableSettingConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): void {
705
if (configuration.tags?.includes('preview')) {
706
markerData.push(this.generatePreviewSettingMarker(setting));
707
} else if (configuration.tags?.includes('experimental')) {
708
markerData.push(this.generateExperimentalSettingMarker(setting));
709
}
710
}
711
712
private generateUnsupportedApplicationSettingMarker(setting: ISetting): IMarkerData {
713
return {
714
severity: MarkerSeverity.Hint,
715
tags: [MarkerTag.Unnecessary],
716
...setting.range,
717
message: nls.localize('unsupportedApplicationSetting', "This setting has an application scope and can only be set in the settings file from the Default profile.")
718
};
719
}
720
721
private generateUnsupportedMachineSettingMarker(setting: ISetting): IMarkerData {
722
return {
723
severity: MarkerSeverity.Hint,
724
tags: [MarkerTag.Unnecessary],
725
...setting.range,
726
message: nls.localize('unsupportedMachineSetting', "This setting can only be applied in user settings in local window or in remote settings in remote window.")
727
};
728
}
729
730
private generateUntrustedSettingMarker(setting: ISetting): IMarkerData {
731
return {
732
severity: MarkerSeverity.Warning,
733
...setting.range,
734
message: nls.localize('untrustedSetting', "This setting can only be applied in a trusted workspace.")
735
};
736
}
737
738
private generateUnknownConfigurationMarker(setting: ISetting): IMarkerData {
739
return {
740
severity: MarkerSeverity.Hint,
741
tags: [MarkerTag.Unnecessary],
742
...setting.range,
743
message: nls.localize('unknown configuration setting', "Unknown Configuration Setting")
744
};
745
}
746
747
private generateUntrustedSettingCodeActions(diagnostics: IMarkerData[]): languages.CodeAction[] {
748
return [{
749
title: nls.localize('manage workspace trust', "Manage Workspace Trust"),
750
command: {
751
id: 'workbench.trust.manage',
752
title: nls.localize('manage workspace trust', "Manage Workspace Trust")
753
},
754
diagnostics,
755
kind: CodeActionKind.QuickFix.value
756
}];
757
}
758
759
private generatePreviewSettingMarker(setting: ISetting): IMarkerData {
760
return {
761
severity: MarkerSeverity.Hint,
762
...setting.range,
763
message: PREVIEW_INDICATOR_DESCRIPTION
764
};
765
}
766
767
private generateExperimentalSettingMarker(setting: ISetting): IMarkerData {
768
return {
769
severity: MarkerSeverity.Hint,
770
...setting.range,
771
message: EXPERIMENTAL_INDICATOR_DESCRIPTION
772
};
773
}
774
775
private addCodeActions(range: IRange, codeActions: languages.CodeAction[]): void {
776
let actions = this.codeActions.get(this.settingsEditorModel.uri);
777
if (!actions) {
778
actions = [];
779
this.codeActions.set(this.settingsEditorModel.uri, actions);
780
}
781
actions.push([Range.lift(range), codeActions]);
782
}
783
784
public override dispose(): void {
785
this.markerService.remove('UnsupportedSettingsRenderer', [this.settingsEditorModel.uri]);
786
this.codeActions.clear();
787
super.dispose();
788
}
789
790
}
791
792
class McpSettingsRenderer extends Disposable implements languages.CodeActionProvider {
793
794
private renderingDelayer: Delayer<void> = new Delayer<void>(200);
795
private readonly codeActions = new ResourceMap<[Range, languages.CodeAction[]][]>(uri => this.uriIdentityService.extUri.getComparisonKey(uri));
796
797
constructor(
798
private readonly editor: ICodeEditor,
799
private readonly settingsEditorModel: SettingsEditorModel,
800
@IMarkerService private readonly markerService: IMarkerService,
801
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
802
@ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService,
803
) {
804
super();
805
this._register(this.editor.getModel()!.onDidChangeContent(() => this.delayedRender()));
806
this._register(languageFeaturesService.codeActionProvider.register({ pattern: settingsEditorModel.uri.path }, this));
807
}
808
809
private delayedRender(): void {
810
this.renderingDelayer.trigger(() => this.render());
811
}
812
813
public render(): void {
814
this.codeActions.clear();
815
const markerData: IMarkerData[] = this.generateMarkerData();
816
if (markerData.length) {
817
this.markerService.changeOne('McpSettingsRenderer', this.settingsEditorModel.uri, markerData);
818
} else {
819
this.markerService.remove('McpSettingsRenderer', [this.settingsEditorModel.uri]);
820
}
821
}
822
823
async provideCodeActions(model: ITextModel, range: Range | Selection, context: languages.CodeActionContext, token: CancellationToken): Promise<languages.CodeActionList> {
824
const actions: languages.CodeAction[] = [];
825
const codeActionsByRange = this.codeActions.get(model.uri);
826
if (codeActionsByRange) {
827
for (const [codeActionsRange, codeActions] of codeActionsByRange) {
828
if (codeActionsRange.containsRange(range)) {
829
actions.push(...codeActions);
830
}
831
}
832
}
833
return {
834
actions,
835
dispose: () => { }
836
};
837
}
838
839
private generateMarkerData(): IMarkerData[] {
840
const markerData: IMarkerData[] = [];
841
842
// Only check for MCP configuration in user local and user remote settings
843
if (this.settingsEditorModel.configurationTarget !== ConfigurationTarget.USER_LOCAL &&
844
this.settingsEditorModel.configurationTarget !== ConfigurationTarget.USER_REMOTE) {
845
return markerData;
846
}
847
848
for (const settingsGroup of this.settingsEditorModel.settingsGroups) {
849
for (const section of settingsGroup.sections) {
850
for (const setting of section.settings) {
851
if (setting.key === mcpConfigurationSection) {
852
const marker = this.generateMcpConfigurationMarker(setting);
853
markerData.push(marker);
854
const codeActions = this.generateMcpConfigurationCodeActions([marker]);
855
this.addCodeActions(setting.range, codeActions);
856
}
857
}
858
}
859
}
860
return markerData;
861
}
862
863
private generateMcpConfigurationMarker(setting: ISetting): IMarkerData {
864
const isRemote = this.settingsEditorModel.configurationTarget === ConfigurationTarget.USER_REMOTE;
865
const message = isRemote
866
? nls.localize('mcp.renderer.remoteConfigFound', 'MCP servers should not be configured in remote user settings. Use the dedicated MCP configuration instead.')
867
: nls.localize('mcp.renderer.userConfigFound', 'MCP servers should not be configured in user settings. Use the dedicated MCP configuration instead.');
868
869
return {
870
severity: MarkerSeverity.Warning,
871
...setting.range,
872
message
873
};
874
}
875
876
private generateMcpConfigurationCodeActions(diagnostics: IMarkerData[]): languages.CodeAction[] {
877
const isRemote = this.settingsEditorModel.configurationTarget === ConfigurationTarget.USER_REMOTE;
878
const openConfigLabel = isRemote
879
? nls.localize('mcp.renderer.openRemoteConfig', 'Open Remote User MCP Configuration')
880
: nls.localize('mcp.renderer.openUserConfig', 'Open User MCP Configuration');
881
882
const commandId = isRemote ? McpCommandIds.OpenRemoteUserMcp : McpCommandIds.OpenUserMcp;
883
884
return [{
885
title: openConfigLabel,
886
command: {
887
id: commandId,
888
title: openConfigLabel
889
},
890
diagnostics,
891
kind: CodeActionKind.QuickFix.value
892
}];
893
}
894
895
private addCodeActions(range: IRange, codeActions: languages.CodeAction[]): void {
896
let actions = this.codeActions.get(this.settingsEditorModel.uri);
897
if (!actions) {
898
actions = [];
899
this.codeActions.set(this.settingsEditorModel.uri, actions);
900
}
901
actions.push([Range.lift(range), codeActions]);
902
}
903
904
public override dispose(): void {
905
this.markerService.remove('McpSettingsRenderer', [this.settingsEditorModel.uri]);
906
this.codeActions.clear();
907
super.dispose();
908
}
909
910
}
911
912
class WorkspaceConfigurationRenderer extends Disposable {
913
private static readonly supportedKeys = ['folders', 'tasks', 'launch', 'extensions', 'settings', 'remoteAuthority', 'transient'];
914
915
private readonly decorations: editorCommon.IEditorDecorationsCollection;
916
private renderingDelayer: Delayer<void> = new Delayer<void>(200);
917
918
constructor(private editor: ICodeEditor, private workspaceSettingsEditorModel: SettingsEditorModel,
919
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
920
@IMarkerService private readonly markerService: IMarkerService
921
) {
922
super();
923
this.decorations = this.editor.createDecorationsCollection();
924
this._register(this.editor.getModel()!.onDidChangeContent(() => this.renderingDelayer.trigger(() => this.render())));
925
}
926
927
render(): void {
928
const markerData: IMarkerData[] = [];
929
if (this.workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE && this.workspaceSettingsEditorModel instanceof WorkspaceConfigurationEditorModel) {
930
const ranges: IRange[] = [];
931
for (const settingsGroup of this.workspaceSettingsEditorModel.configurationGroups) {
932
for (const section of settingsGroup.sections) {
933
for (const setting of section.settings) {
934
if (!WorkspaceConfigurationRenderer.supportedKeys.includes(setting.key)) {
935
markerData.push({
936
severity: MarkerSeverity.Hint,
937
tags: [MarkerTag.Unnecessary],
938
...setting.range,
939
message: nls.localize('unsupportedProperty', "Unsupported Property")
940
});
941
}
942
}
943
}
944
}
945
this.decorations.set(ranges.map(range => this.createDecoration(range)));
946
}
947
if (markerData.length) {
948
this.markerService.changeOne('WorkspaceConfigurationRenderer', this.workspaceSettingsEditorModel.uri, markerData);
949
} else {
950
this.markerService.remove('WorkspaceConfigurationRenderer', [this.workspaceSettingsEditorModel.uri]);
951
}
952
}
953
954
private static readonly _DIM_CONFIGURATION_ = ModelDecorationOptions.register({
955
description: 'dim-configuration',
956
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
957
inlineClassName: 'dim-configuration'
958
});
959
960
private createDecoration(range: IRange): IModelDeltaDecoration {
961
return {
962
range,
963
options: WorkspaceConfigurationRenderer._DIM_CONFIGURATION_
964
};
965
}
966
967
override dispose(): void {
968
this.markerService.remove('WorkspaceConfigurationRenderer', [this.workspaceSettingsEditorModel.uri]);
969
this.decorations.clear();
970
super.dispose();
971
}
972
}
973
974