Path: blob/main/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { EventHelper, getDomNodePagePosition } from '../../../../base/browser/dom.js';6import { IAction, SubmenuAction } from '../../../../base/common/actions.js';7import { Delayer } from '../../../../base/common/async.js';8import { CancellationToken } from '../../../../base/common/cancellation.js';9import { IStringDictionary } from '../../../../base/common/collections.js';10import { Emitter, Event } from '../../../../base/common/event.js';11import { IJSONSchema } from '../../../../base/common/jsonSchema.js';12import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js';13import { ResourceMap } from '../../../../base/common/map.js';14import { isEqual } from '../../../../base/common/resources.js';15import { ThemeIcon } from '../../../../base/common/themables.js';16import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from '../../../../editor/browser/editorBrowser.js';17import { EditorOption } from '../../../../editor/common/config/editorOptions.js';18import { Position } from '../../../../editor/common/core/position.js';19import { IRange, Range } from '../../../../editor/common/core/range.js';20import { Selection } from '../../../../editor/common/core/selection.js';21import { ICursorPositionChangedEvent } from '../../../../editor/common/cursorEvents.js';22import * as editorCommon from '../../../../editor/common/editorCommon.js';23import * as languages from '../../../../editor/common/languages.js';24import { IModelDeltaDecoration, ITextModel, TrackedRangeStickiness } from '../../../../editor/common/model.js';25import { ModelDecorationOptions } from '../../../../editor/common/model/textModel.js';26import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js';27import { CodeActionKind } from '../../../../editor/contrib/codeAction/common/types.js';28import * as nls from '../../../../nls.js';29import { ConfigurationTarget, IConfigurationService } from '../../../../platform/configuration/common/configuration.js';30import { Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationPropertySchema, IConfigurationRegistry, IRegisteredConfigurationPropertySchema, OVERRIDE_PROPERTY_REGEX, overrideIdentifiersFromKey } from '../../../../platform/configuration/common/configurationRegistry.js';31import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';32import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';33import { IMarkerData, IMarkerService, MarkerSeverity, MarkerTag } from '../../../../platform/markers/common/markers.js';34import { Registry } from '../../../../platform/registry/common/platform.js';35import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';36import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js';37import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';38import { IWorkspaceTrustManagementService } from '../../../../platform/workspace/common/workspaceTrust.js';39import { RangeHighlightDecorations } from '../../../browser/codeeditor.js';40import { settingsEditIcon } from './preferencesIcons.js';41import { EditPreferenceWidget } from './preferencesWidgets.js';42import { APPLICATION_SCOPES, APPLY_ALL_PROFILES_SETTING, IWorkbenchConfigurationService } from '../../../services/configuration/common/configuration.js';43import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js';44import { IPreferencesEditorModel, IPreferencesService, ISetting, ISettingsEditorModel, ISettingsGroup } from '../../../services/preferences/common/preferences.js';45import { DefaultSettingsEditorModel, SettingsEditorModel, WorkspaceConfigurationEditorModel } from '../../../services/preferences/common/preferencesModels.js';46import { IUserDataProfileService } from '../../../services/userDataProfile/common/userDataProfile.js';47import { EXPERIMENTAL_INDICATOR_DESCRIPTION, PREVIEW_INDICATOR_DESCRIPTION } from '../common/preferences.js';48import { mcpConfigurationSection } from '../../mcp/common/mcpConfiguration.js';49import { McpCommandIds } from '../../mcp/common/mcpCommandIds.js';5051export interface IPreferencesRenderer extends IDisposable {52render(): void;53updatePreference(key: string, value: any, source: ISetting): void;54focusPreference(setting: ISetting): void;55clearFocus(setting: ISetting): void;56editPreference(setting: ISetting): boolean;57}5859export class UserSettingsRenderer extends Disposable implements IPreferencesRenderer {6061private settingHighlighter: SettingHighlighter;62private editSettingActionRenderer: EditSettingRenderer;63private modelChangeDelayer: Delayer<void> = new Delayer<void>(200);64private associatedPreferencesModel!: IPreferencesEditorModel<ISetting>;6566private unsupportedSettingsRenderer: UnsupportedSettingsRenderer;67private mcpSettingsRenderer: McpSettingsRenderer;6869constructor(protected editor: ICodeEditor, readonly preferencesModel: SettingsEditorModel,70@IPreferencesService protected preferencesService: IPreferencesService,71@IConfigurationService private readonly configurationService: IConfigurationService,72@IInstantiationService protected instantiationService: IInstantiationService73) {74super();75this.settingHighlighter = this._register(instantiationService.createInstance(SettingHighlighter, editor));76this.editSettingActionRenderer = this._register(this.instantiationService.createInstance(EditSettingRenderer, this.editor, this.preferencesModel, this.settingHighlighter));77this._register(this.editSettingActionRenderer.onUpdateSetting(({ key, value, source }) => this.updatePreference(key, value, source)));78this._register(this.editor.getModel()!.onDidChangeContent(() => this.modelChangeDelayer.trigger(() => this.onModelChanged())));79this.unsupportedSettingsRenderer = this._register(instantiationService.createInstance(UnsupportedSettingsRenderer, editor, preferencesModel));80this.mcpSettingsRenderer = this._register(instantiationService.createInstance(McpSettingsRenderer, editor, preferencesModel));81}8283render(): void {84this.editSettingActionRenderer.render(this.preferencesModel.settingsGroups, this.associatedPreferencesModel);85this.unsupportedSettingsRenderer.render();86this.mcpSettingsRenderer.render();87}8889updatePreference(key: string, value: any, source: IIndexedSetting): void {90const overrideIdentifiers = source.overrideOf ? overrideIdentifiersFromKey(source.overrideOf.key) : null;91const resource = this.preferencesModel.uri;92this.configurationService.updateValue(key, value, { overrideIdentifiers, resource }, this.preferencesModel.configurationTarget)93.then(() => this.onSettingUpdated(source));94}9596private onModelChanged(): void {97if (!this.editor.hasModel()) {98// model could have been disposed during the delay99return;100}101this.render();102}103104private onSettingUpdated(setting: ISetting) {105this.editor.focus();106setting = this.getSetting(setting)!;107if (setting) {108// TODO:@sandy Selection range should be template range109this.editor.setSelection(setting.valueRange);110this.settingHighlighter.highlight(setting, true);111}112}113114private getSetting(setting: ISetting): ISetting | undefined {115const { key, overrideOf } = setting;116if (overrideOf) {117const setting = this.getSetting(overrideOf);118for (const override of setting!.overrides!) {119if (override.key === key) {120return override;121}122}123return undefined;124}125126return this.preferencesModel.getPreference(key);127}128129focusPreference(setting: ISetting): void {130const s = this.getSetting(setting);131if (s) {132this.settingHighlighter.highlight(s, true);133this.editor.setPosition({ lineNumber: s.keyRange.startLineNumber, column: s.keyRange.startColumn });134} else {135this.settingHighlighter.clear(true);136}137}138139clearFocus(setting: ISetting): void {140this.settingHighlighter.clear(true);141}142143editPreference(setting: ISetting): boolean {144const editableSetting = this.getSetting(setting);145return !!(editableSetting && this.editSettingActionRenderer.activateOnSetting(editableSetting));146}147148}149150export class WorkspaceSettingsRenderer extends UserSettingsRenderer implements IPreferencesRenderer {151152private workspaceConfigurationRenderer: WorkspaceConfigurationRenderer;153154constructor(editor: ICodeEditor, preferencesModel: SettingsEditorModel,155@IPreferencesService preferencesService: IPreferencesService,156@IConfigurationService configurationService: IConfigurationService,157@IInstantiationService instantiationService: IInstantiationService158) {159super(editor, preferencesModel, preferencesService, configurationService, instantiationService);160this.workspaceConfigurationRenderer = this._register(instantiationService.createInstance(WorkspaceConfigurationRenderer, editor, preferencesModel));161}162163override render(): void {164super.render();165this.workspaceConfigurationRenderer.render();166}167}168169export interface IIndexedSetting extends ISetting {170index: number;171groupId: string;172}173174class EditSettingRenderer extends Disposable {175176private editPreferenceWidgetForCursorPosition: EditPreferenceWidget<IIndexedSetting>;177private editPreferenceWidgetForMouseMove: EditPreferenceWidget<IIndexedSetting>;178179private settingsGroups: ISettingsGroup[] = [];180associatedPreferencesModel!: IPreferencesEditorModel<ISetting>;181private toggleEditPreferencesForMouseMoveDelayer: Delayer<void>;182183private readonly _onUpdateSetting: Emitter<{ key: string; value: any; source: IIndexedSetting }> = this._register(new Emitter<{ key: string; value: any; source: IIndexedSetting }>());184readonly onUpdateSetting: Event<{ key: string; value: any; source: IIndexedSetting }> = this._onUpdateSetting.event;185186constructor(private editor: ICodeEditor, private primarySettingsModel: ISettingsEditorModel,187private settingHighlighter: SettingHighlighter,188@IConfigurationService private readonly configurationService: IConfigurationService,189@IInstantiationService private readonly instantiationService: IInstantiationService,190@IContextMenuService private readonly contextMenuService: IContextMenuService191) {192super();193194this.editPreferenceWidgetForCursorPosition = this._register(this.instantiationService.createInstance(EditPreferenceWidget<IIndexedSetting>, editor));195this.editPreferenceWidgetForMouseMove = this._register(this.instantiationService.createInstance(EditPreferenceWidget<IIndexedSetting>, editor));196this.toggleEditPreferencesForMouseMoveDelayer = new Delayer<void>(75);197198this._register(this.editPreferenceWidgetForCursorPosition.onClick(e => this.onEditSettingClicked(this.editPreferenceWidgetForCursorPosition, e)));199this._register(this.editPreferenceWidgetForMouseMove.onClick(e => this.onEditSettingClicked(this.editPreferenceWidgetForMouseMove, e)));200201this._register(this.editor.onDidChangeCursorPosition(positionChangeEvent => this.onPositionChanged(positionChangeEvent)));202this._register(this.editor.onMouseMove(mouseMoveEvent => this.onMouseMoved(mouseMoveEvent)));203this._register(this.editor.onDidChangeConfiguration(() => this.onConfigurationChanged()));204}205206render(settingsGroups: ISettingsGroup[], associatedPreferencesModel: IPreferencesEditorModel<ISetting>): void {207this.editPreferenceWidgetForCursorPosition.hide();208this.editPreferenceWidgetForMouseMove.hide();209this.settingsGroups = settingsGroups;210this.associatedPreferencesModel = associatedPreferencesModel;211212const settings = this.getSettings(this.editor.getPosition()!.lineNumber);213if (settings.length) {214this.showEditPreferencesWidget(this.editPreferenceWidgetForCursorPosition, settings);215}216}217218private isDefaultSettings(): boolean {219return this.primarySettingsModel instanceof DefaultSettingsEditorModel;220}221222private onConfigurationChanged(): void {223if (!this.editor.getOption(EditorOption.glyphMargin)) {224this.editPreferenceWidgetForCursorPosition.hide();225this.editPreferenceWidgetForMouseMove.hide();226}227}228229private onPositionChanged(positionChangeEvent: ICursorPositionChangedEvent) {230this.editPreferenceWidgetForMouseMove.hide();231const settings = this.getSettings(positionChangeEvent.position.lineNumber);232if (settings.length) {233this.showEditPreferencesWidget(this.editPreferenceWidgetForCursorPosition, settings);234} else {235this.editPreferenceWidgetForCursorPosition.hide();236}237}238239private onMouseMoved(mouseMoveEvent: IEditorMouseEvent): void {240const editPreferenceWidget = this.getEditPreferenceWidgetUnderMouse(mouseMoveEvent);241if (editPreferenceWidget) {242this.onMouseOver(editPreferenceWidget);243return;244}245this.settingHighlighter.clear();246this.toggleEditPreferencesForMouseMoveDelayer.trigger(() => this.toggleEditPreferenceWidgetForMouseMove(mouseMoveEvent));247}248249private getEditPreferenceWidgetUnderMouse(mouseMoveEvent: IEditorMouseEvent): EditPreferenceWidget<ISetting> | undefined {250if (mouseMoveEvent.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN) {251const line = mouseMoveEvent.target.position.lineNumber;252if (this.editPreferenceWidgetForMouseMove.getLine() === line && this.editPreferenceWidgetForMouseMove.isVisible()) {253return this.editPreferenceWidgetForMouseMove;254}255if (this.editPreferenceWidgetForCursorPosition.getLine() === line && this.editPreferenceWidgetForCursorPosition.isVisible()) {256return this.editPreferenceWidgetForCursorPosition;257}258}259return undefined;260}261262private toggleEditPreferenceWidgetForMouseMove(mouseMoveEvent: IEditorMouseEvent): void {263const settings = mouseMoveEvent.target.position ? this.getSettings(mouseMoveEvent.target.position.lineNumber) : null;264if (settings && settings.length) {265this.showEditPreferencesWidget(this.editPreferenceWidgetForMouseMove, settings);266} else {267this.editPreferenceWidgetForMouseMove.hide();268}269}270271private showEditPreferencesWidget(editPreferencesWidget: EditPreferenceWidget<ISetting>, settings: IIndexedSetting[]) {272const line = settings[0].valueRange.startLineNumber;273if (this.editor.getOption(EditorOption.glyphMargin) && this.marginFreeFromOtherDecorations(line)) {274editPreferencesWidget.show(line, nls.localize('editTtile', "Edit"), settings);275const editPreferenceWidgetToHide = editPreferencesWidget === this.editPreferenceWidgetForCursorPosition ? this.editPreferenceWidgetForMouseMove : this.editPreferenceWidgetForCursorPosition;276editPreferenceWidgetToHide.hide();277}278}279280private marginFreeFromOtherDecorations(line: number): boolean {281const decorations = this.editor.getLineDecorations(line);282if (decorations) {283for (const { options } of decorations) {284if (options.glyphMarginClassName && options.glyphMarginClassName.indexOf(ThemeIcon.asClassName(settingsEditIcon)) === -1) {285return false;286}287}288}289return true;290}291292private getSettings(lineNumber: number): IIndexedSetting[] {293const configurationMap = this.getConfigurationsMap();294return this.getSettingsAtLineNumber(lineNumber).filter(setting => {295const configurationNode = configurationMap[setting.key];296if (configurationNode) {297if (configurationNode.policy && this.configurationService.inspect(setting.key).policyValue !== undefined) {298return false;299}300if (this.isDefaultSettings()) {301if (setting.key === 'launch') {302// Do not show because of https://github.com/microsoft/vscode/issues/32593303return false;304}305return true;306}307if (configurationNode.type === 'boolean' || configurationNode.enum) {308if ((<SettingsEditorModel>this.primarySettingsModel).configurationTarget !== ConfigurationTarget.WORKSPACE_FOLDER) {309return true;310}311if (configurationNode.scope === ConfigurationScope.RESOURCE || configurationNode.scope === ConfigurationScope.LANGUAGE_OVERRIDABLE) {312return true;313}314}315}316return false;317});318}319320private getSettingsAtLineNumber(lineNumber: number): IIndexedSetting[] {321// index of setting, across all groups/sections322let index = 0;323324const settings: IIndexedSetting[] = [];325for (const group of this.settingsGroups) {326if (group.range.startLineNumber > lineNumber) {327break;328}329if (lineNumber >= group.range.startLineNumber && lineNumber <= group.range.endLineNumber) {330for (const section of group.sections) {331for (const setting of section.settings) {332if (setting.range.startLineNumber > lineNumber) {333break;334}335if (lineNumber >= setting.range.startLineNumber && lineNumber <= setting.range.endLineNumber) {336if (!this.isDefaultSettings() && setting.overrides!.length) {337// Only one level because override settings cannot have override settings338for (const overrideSetting of setting.overrides!) {339if (lineNumber >= overrideSetting.range.startLineNumber && lineNumber <= overrideSetting.range.endLineNumber) {340settings.push({ ...overrideSetting, index, groupId: group.id });341}342}343} else {344settings.push({ ...setting, index, groupId: group.id });345}346}347348index++;349}350}351}352}353return settings;354}355356private onMouseOver(editPreferenceWidget: EditPreferenceWidget<ISetting>): void {357this.settingHighlighter.highlight(editPreferenceWidget.preferences[0]);358}359360private onEditSettingClicked(editPreferenceWidget: EditPreferenceWidget<IIndexedSetting>, e: IEditorMouseEvent): void {361EventHelper.stop(e.event, true);362363const actions = this.getSettings(editPreferenceWidget.getLine()).length === 1 ? this.getActions(editPreferenceWidget.preferences[0], this.getConfigurationsMap()[editPreferenceWidget.preferences[0].key])364: editPreferenceWidget.preferences.map(setting => new SubmenuAction(`preferences.submenu.${setting.key}`, setting.key, this.getActions(setting, this.getConfigurationsMap()[setting.key])));365this.contextMenuService.showContextMenu({366getAnchor: () => e.event,367getActions: () => actions368});369}370371activateOnSetting(setting: ISetting): boolean {372const startLine = setting.keyRange.startLineNumber;373const settings = this.getSettings(startLine);374if (!settings.length) {375return false;376}377378this.editPreferenceWidgetForMouseMove.show(startLine, '', settings);379const actions = this.getActions(this.editPreferenceWidgetForMouseMove.preferences[0], this.getConfigurationsMap()[this.editPreferenceWidgetForMouseMove.preferences[0].key]);380this.contextMenuService.showContextMenu({381getAnchor: () => this.toAbsoluteCoords(new Position(startLine, 1)),382getActions: () => actions383});384385return true;386}387388private toAbsoluteCoords(position: Position): { x: number; y: number } {389const positionCoords = this.editor.getScrolledVisiblePosition(position);390const editorCoords = getDomNodePagePosition(this.editor.getDomNode()!);391const x = editorCoords.left + positionCoords!.left;392const y = editorCoords.top + positionCoords!.top + positionCoords!.height;393394return { x, y: y + 10 };395}396397private getConfigurationsMap(): { [qualifiedKey: string]: IConfigurationPropertySchema } {398return Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();399}400401private getActions(setting: IIndexedSetting, jsonSchema: IJSONSchema): IAction[] {402if (jsonSchema.type === 'boolean') {403return [{404id: 'truthyValue',405label: 'true',406tooltip: 'true',407enabled: true,408run: () => this.updateSetting(setting.key, true, setting),409class: undefined410}, {411id: 'falsyValue',412label: 'false',413tooltip: 'false',414enabled: true,415run: () => this.updateSetting(setting.key, false, setting),416class: undefined417}];418}419if (jsonSchema.enum) {420return jsonSchema.enum.map(value => {421return {422id: value,423label: JSON.stringify(value),424tooltip: JSON.stringify(value),425enabled: true,426run: () => this.updateSetting(setting.key, value, setting),427class: undefined428};429});430}431return this.getDefaultActions(setting);432}433434private getDefaultActions(setting: IIndexedSetting): IAction[] {435if (this.isDefaultSettings()) {436const settingInOtherModel = this.associatedPreferencesModel.getPreference(setting.key);437return [{438id: 'setDefaultValue',439label: settingInOtherModel ? nls.localize('replaceDefaultValue', "Replace in Settings") : nls.localize('copyDefaultValue', "Copy to Settings"),440tooltip: settingInOtherModel ? nls.localize('replaceDefaultValue', "Replace in Settings") : nls.localize('copyDefaultValue', "Copy to Settings"),441enabled: true,442run: () => this.updateSetting(setting.key, setting.value, setting),443class: undefined444}];445}446return [];447}448449private updateSetting(key: string, value: any, source: IIndexedSetting): void {450this._onUpdateSetting.fire({ key, value, source });451}452}453454class SettingHighlighter extends Disposable {455456private fixedHighlighter: RangeHighlightDecorations;457private volatileHighlighter: RangeHighlightDecorations;458459constructor(private editor: ICodeEditor, @IInstantiationService instantiationService: IInstantiationService) {460super();461this.fixedHighlighter = this._register(instantiationService.createInstance(RangeHighlightDecorations));462this.volatileHighlighter = this._register(instantiationService.createInstance(RangeHighlightDecorations));463}464465highlight(setting: ISetting, fix: boolean = false) {466this.volatileHighlighter.removeHighlightRange();467this.fixedHighlighter.removeHighlightRange();468469const highlighter = fix ? this.fixedHighlighter : this.volatileHighlighter;470highlighter.highlightRange({471range: setting.valueRange,472resource: this.editor.getModel()!.uri473}, this.editor);474475this.editor.revealLineInCenterIfOutsideViewport(setting.valueRange.startLineNumber, editorCommon.ScrollType.Smooth);476}477478clear(fix: boolean = false): void {479this.volatileHighlighter.removeHighlightRange();480if (fix) {481this.fixedHighlighter.removeHighlightRange();482}483}484}485486class UnsupportedSettingsRenderer extends Disposable implements languages.CodeActionProvider {487488private renderingDelayer: Delayer<void> = new Delayer<void>(200);489490private readonly codeActions = new ResourceMap<[Range, languages.CodeAction[]][]>(uri => this.uriIdentityService.extUri.getComparisonKey(uri));491492constructor(493private readonly editor: ICodeEditor,494private readonly settingsEditorModel: SettingsEditorModel,495@IMarkerService private readonly markerService: IMarkerService,496@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,497@IWorkbenchConfigurationService private readonly configurationService: IWorkbenchConfigurationService,498@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService,499@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,500@ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService,501@IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService,502@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,503) {504super();505this._register(this.editor.getModel()!.onDidChangeContent(() => this.delayedRender()));506this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.source === ConfigurationTarget.DEFAULT)(() => this.delayedRender()));507this._register(languageFeaturesService.codeActionProvider.register({ pattern: settingsEditorModel.uri.path }, this));508this._register(userDataProfileService.onDidChangeCurrentProfile(() => this.delayedRender()));509}510511private delayedRender(): void {512this.renderingDelayer.trigger(() => this.render());513}514515public render(): void {516this.codeActions.clear();517const markerData: IMarkerData[] = this.generateMarkerData();518if (markerData.length) {519this.markerService.changeOne('UnsupportedSettingsRenderer', this.settingsEditorModel.uri, markerData);520} else {521this.markerService.remove('UnsupportedSettingsRenderer', [this.settingsEditorModel.uri]);522}523}524525async provideCodeActions(model: ITextModel, range: Range | Selection, context: languages.CodeActionContext, token: CancellationToken): Promise<languages.CodeActionList> {526const actions: languages.CodeAction[] = [];527const codeActionsByRange = this.codeActions.get(model.uri);528if (codeActionsByRange) {529for (const [codeActionsRange, codeActions] of codeActionsByRange) {530if (codeActionsRange.containsRange(range)) {531actions.push(...codeActions);532}533}534}535return {536actions,537dispose: () => { }538};539}540541private generateMarkerData(): IMarkerData[] {542const markerData: IMarkerData[] = [];543const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();544for (const settingsGroup of this.settingsEditorModel.settingsGroups) {545for (const section of settingsGroup.sections) {546for (const setting of section.settings) {547if (OVERRIDE_PROPERTY_REGEX.test(setting.key)) {548if (setting.overrides) {549this.handleOverrides(setting.overrides, configurationRegistry, markerData);550}551continue;552}553const configuration = configurationRegistry[setting.key];554if (configuration) {555this.handleUnstableSettingConfiguration(setting, configuration, markerData);556if (this.handlePolicyConfiguration(setting, configuration, markerData)) {557continue;558}559switch (this.settingsEditorModel.configurationTarget) {560case ConfigurationTarget.USER_LOCAL:561this.handleLocalUserConfiguration(setting, configuration, markerData);562break;563case ConfigurationTarget.USER_REMOTE:564this.handleRemoteUserConfiguration(setting, configuration, markerData);565break;566case ConfigurationTarget.WORKSPACE:567this.handleWorkspaceConfiguration(setting, configuration, markerData);568break;569case ConfigurationTarget.WORKSPACE_FOLDER:570this.handleWorkspaceFolderConfiguration(setting, configuration, markerData);571break;572}573} else {574markerData.push(this.generateUnknownConfigurationMarker(setting));575}576}577}578}579return markerData;580}581582private handlePolicyConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): boolean {583if (!configuration.policy) {584return false;585}586if (this.configurationService.inspect(setting.key).policyValue === undefined) {587return false;588}589if (this.settingsEditorModel.configurationTarget === ConfigurationTarget.DEFAULT) {590return false;591}592markerData.push({593severity: MarkerSeverity.Hint,594tags: [MarkerTag.Unnecessary],595...setting.range,596message: nls.localize('unsupportedPolicySetting', "This setting cannot be applied because it is configured in the system policy.")597});598return true;599}600601private handleOverrides(overrides: ISetting[], configurationRegistry: IStringDictionary<IRegisteredConfigurationPropertySchema>, markerData: IMarkerData[]): void {602for (const setting of overrides || []) {603const configuration = configurationRegistry[setting.key];604if (configuration) {605if (configuration.scope !== ConfigurationScope.LANGUAGE_OVERRIDABLE) {606markerData.push({607severity: MarkerSeverity.Hint,608tags: [MarkerTag.Unnecessary],609...setting.range,610message: nls.localize('unsupportLanguageOverrideSetting', "This setting cannot be applied because it is not registered as language override setting.")611});612}613} else {614markerData.push(this.generateUnknownConfigurationMarker(setting));615}616}617}618619private handleLocalUserConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): void {620if (!this.userDataProfileService.currentProfile.isDefault && !this.userDataProfileService.currentProfile.useDefaultFlags?.settings) {621if (isEqual(this.userDataProfilesService.defaultProfile.settingsResource, this.settingsEditorModel.uri) && !this.configurationService.isSettingAppliedForAllProfiles(setting.key)) {622// If we're in the default profile setting file, and the setting cannot be applied in all profiles623markerData.push({624severity: MarkerSeverity.Hint,625tags: [MarkerTag.Unnecessary],626...setting.range,627message: 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.")628});629} else if (isEqual(this.userDataProfileService.currentProfile.settingsResource, this.settingsEditorModel.uri)) {630if (configuration.scope && APPLICATION_SCOPES.includes(configuration.scope)) {631// If we're in a profile setting file, and the setting is application-scoped, fade it out.632markerData.push(this.generateUnsupportedApplicationSettingMarker(setting));633} else if (this.configurationService.isSettingAppliedForAllProfiles(setting.key)) {634// If we're in the non-default profile setting file, and the setting can be applied in all profiles, fade it out.635markerData.push({636severity: MarkerSeverity.Hint,637tags: [MarkerTag.Unnecessary],638...setting.range,639message: 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)640});641}642}643}644if (this.environmentService.remoteAuthority && (configuration.scope === ConfigurationScope.MACHINE || configuration.scope === ConfigurationScope.APPLICATION_MACHINE || configuration.scope === ConfigurationScope.MACHINE_OVERRIDABLE)) {645markerData.push({646severity: MarkerSeverity.Hint,647tags: [MarkerTag.Unnecessary],648...setting.range,649message: nls.localize('unsupportedRemoteMachineSetting', "This setting cannot be applied in this window. It will be applied when you open a local window.")650});651}652}653654private handleRemoteUserConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): void {655if (configuration.scope === ConfigurationScope.APPLICATION) {656markerData.push(this.generateUnsupportedApplicationSettingMarker(setting));657}658}659660private handleWorkspaceConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): void {661if (configuration.scope && APPLICATION_SCOPES.includes(configuration.scope)) {662markerData.push(this.generateUnsupportedApplicationSettingMarker(setting));663}664665if (configuration.scope === ConfigurationScope.MACHINE) {666markerData.push(this.generateUnsupportedMachineSettingMarker(setting));667}668669if (!this.workspaceTrustManagementService.isWorkspaceTrusted() && configuration.restricted) {670const marker = this.generateUntrustedSettingMarker(setting);671markerData.push(marker);672const codeActions = this.generateUntrustedSettingCodeActions([marker]);673this.addCodeActions(marker, codeActions);674}675}676677private handleWorkspaceFolderConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): void {678if (configuration.scope && APPLICATION_SCOPES.includes(configuration.scope)) {679markerData.push(this.generateUnsupportedApplicationSettingMarker(setting));680}681682if (configuration.scope === ConfigurationScope.MACHINE) {683markerData.push(this.generateUnsupportedMachineSettingMarker(setting));684}685686if (configuration.scope === ConfigurationScope.WINDOW) {687markerData.push({688severity: MarkerSeverity.Hint,689tags: [MarkerTag.Unnecessary],690...setting.range,691message: nls.localize('unsupportedWindowSetting', "This setting cannot be applied in this workspace. It will be applied when you open the containing workspace folder directly.")692});693}694695if (!this.workspaceTrustManagementService.isWorkspaceTrusted() && configuration.restricted) {696const marker = this.generateUntrustedSettingMarker(setting);697markerData.push(marker);698const codeActions = this.generateUntrustedSettingCodeActions([marker]);699this.addCodeActions(marker, codeActions);700}701}702703private handleUnstableSettingConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): void {704if (configuration.tags?.includes('preview')) {705markerData.push(this.generatePreviewSettingMarker(setting));706} else if (configuration.tags?.includes('experimental')) {707markerData.push(this.generateExperimentalSettingMarker(setting));708}709}710711private generateUnsupportedApplicationSettingMarker(setting: ISetting): IMarkerData {712return {713severity: MarkerSeverity.Hint,714tags: [MarkerTag.Unnecessary],715...setting.range,716message: nls.localize('unsupportedApplicationSetting', "This setting has an application scope and can only be set in the settings file from the Default profile.")717};718}719720private generateUnsupportedMachineSettingMarker(setting: ISetting): IMarkerData {721return {722severity: MarkerSeverity.Hint,723tags: [MarkerTag.Unnecessary],724...setting.range,725message: nls.localize('unsupportedMachineSetting', "This setting can only be applied in user settings in local window or in remote settings in remote window.")726};727}728729private generateUntrustedSettingMarker(setting: ISetting): IMarkerData {730return {731severity: MarkerSeverity.Warning,732...setting.range,733message: nls.localize('untrustedSetting', "This setting can only be applied in a trusted workspace.")734};735}736737private generateUnknownConfigurationMarker(setting: ISetting): IMarkerData {738return {739severity: MarkerSeverity.Hint,740tags: [MarkerTag.Unnecessary],741...setting.range,742message: nls.localize('unknown configuration setting', "Unknown Configuration Setting")743};744}745746private generateUntrustedSettingCodeActions(diagnostics: IMarkerData[]): languages.CodeAction[] {747return [{748title: nls.localize('manage workspace trust', "Manage Workspace Trust"),749command: {750id: 'workbench.trust.manage',751title: nls.localize('manage workspace trust', "Manage Workspace Trust")752},753diagnostics,754kind: CodeActionKind.QuickFix.value755}];756}757758private generatePreviewSettingMarker(setting: ISetting): IMarkerData {759return {760severity: MarkerSeverity.Hint,761...setting.range,762message: PREVIEW_INDICATOR_DESCRIPTION763};764}765766private generateExperimentalSettingMarker(setting: ISetting): IMarkerData {767return {768severity: MarkerSeverity.Hint,769...setting.range,770message: EXPERIMENTAL_INDICATOR_DESCRIPTION771};772}773774private addCodeActions(range: IRange, codeActions: languages.CodeAction[]): void {775let actions = this.codeActions.get(this.settingsEditorModel.uri);776if (!actions) {777actions = [];778this.codeActions.set(this.settingsEditorModel.uri, actions);779}780actions.push([Range.lift(range), codeActions]);781}782783public override dispose(): void {784this.markerService.remove('UnsupportedSettingsRenderer', [this.settingsEditorModel.uri]);785this.codeActions.clear();786super.dispose();787}788789}790791class McpSettingsRenderer extends Disposable implements languages.CodeActionProvider {792793private renderingDelayer: Delayer<void> = new Delayer<void>(200);794private readonly codeActions = new ResourceMap<[Range, languages.CodeAction[]][]>(uri => this.uriIdentityService.extUri.getComparisonKey(uri));795796constructor(797private readonly editor: ICodeEditor,798private readonly settingsEditorModel: SettingsEditorModel,799@IMarkerService private readonly markerService: IMarkerService,800@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,801@ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService,802) {803super();804this._register(this.editor.getModel()!.onDidChangeContent(() => this.delayedRender()));805this._register(languageFeaturesService.codeActionProvider.register({ pattern: settingsEditorModel.uri.path }, this));806}807808private delayedRender(): void {809this.renderingDelayer.trigger(() => this.render());810}811812public render(): void {813this.codeActions.clear();814const markerData: IMarkerData[] = this.generateMarkerData();815if (markerData.length) {816this.markerService.changeOne('McpSettingsRenderer', this.settingsEditorModel.uri, markerData);817} else {818this.markerService.remove('McpSettingsRenderer', [this.settingsEditorModel.uri]);819}820}821822async provideCodeActions(model: ITextModel, range: Range | Selection, context: languages.CodeActionContext, token: CancellationToken): Promise<languages.CodeActionList> {823const actions: languages.CodeAction[] = [];824const codeActionsByRange = this.codeActions.get(model.uri);825if (codeActionsByRange) {826for (const [codeActionsRange, codeActions] of codeActionsByRange) {827if (codeActionsRange.containsRange(range)) {828actions.push(...codeActions);829}830}831}832return {833actions,834dispose: () => { }835};836}837838private generateMarkerData(): IMarkerData[] {839const markerData: IMarkerData[] = [];840841// Only check for MCP configuration in user local and user remote settings842if (this.settingsEditorModel.configurationTarget !== ConfigurationTarget.USER_LOCAL &&843this.settingsEditorModel.configurationTarget !== ConfigurationTarget.USER_REMOTE) {844return markerData;845}846847for (const settingsGroup of this.settingsEditorModel.settingsGroups) {848for (const section of settingsGroup.sections) {849for (const setting of section.settings) {850if (setting.key === mcpConfigurationSection) {851const marker = this.generateMcpConfigurationMarker(setting);852markerData.push(marker);853const codeActions = this.generateMcpConfigurationCodeActions([marker]);854this.addCodeActions(setting.range, codeActions);855}856}857}858}859return markerData;860}861862private generateMcpConfigurationMarker(setting: ISetting): IMarkerData {863const isRemote = this.settingsEditorModel.configurationTarget === ConfigurationTarget.USER_REMOTE;864const message = isRemote865? nls.localize('mcp.renderer.remoteConfigFound', 'MCP servers should not be configured in remote user settings. Use the dedicated MCP configuration instead.')866: nls.localize('mcp.renderer.userConfigFound', 'MCP servers should not be configured in user settings. Use the dedicated MCP configuration instead.');867868return {869severity: MarkerSeverity.Warning,870...setting.range,871message872};873}874875private generateMcpConfigurationCodeActions(diagnostics: IMarkerData[]): languages.CodeAction[] {876const isRemote = this.settingsEditorModel.configurationTarget === ConfigurationTarget.USER_REMOTE;877const openConfigLabel = isRemote878? nls.localize('mcp.renderer.openRemoteConfig', 'Open Remote User MCP Configuration')879: nls.localize('mcp.renderer.openUserConfig', 'Open User MCP Configuration');880881const commandId = isRemote ? McpCommandIds.OpenRemoteUserMcp : McpCommandIds.OpenUserMcp;882883return [{884title: openConfigLabel,885command: {886id: commandId,887title: openConfigLabel888},889diagnostics,890kind: CodeActionKind.QuickFix.value891}];892}893894private addCodeActions(range: IRange, codeActions: languages.CodeAction[]): void {895let actions = this.codeActions.get(this.settingsEditorModel.uri);896if (!actions) {897actions = [];898this.codeActions.set(this.settingsEditorModel.uri, actions);899}900actions.push([Range.lift(range), codeActions]);901}902903public override dispose(): void {904this.markerService.remove('McpSettingsRenderer', [this.settingsEditorModel.uri]);905this.codeActions.clear();906super.dispose();907}908909}910911class WorkspaceConfigurationRenderer extends Disposable {912private static readonly supportedKeys = ['folders', 'tasks', 'launch', 'extensions', 'settings', 'remoteAuthority', 'transient'];913914private readonly decorations: editorCommon.IEditorDecorationsCollection;915private renderingDelayer: Delayer<void> = new Delayer<void>(200);916917constructor(private editor: ICodeEditor, private workspaceSettingsEditorModel: SettingsEditorModel,918@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,919@IMarkerService private readonly markerService: IMarkerService920) {921super();922this.decorations = this.editor.createDecorationsCollection();923this._register(this.editor.getModel()!.onDidChangeContent(() => this.renderingDelayer.trigger(() => this.render())));924}925926render(): void {927const markerData: IMarkerData[] = [];928if (this.workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE && this.workspaceSettingsEditorModel instanceof WorkspaceConfigurationEditorModel) {929const ranges: IRange[] = [];930for (const settingsGroup of this.workspaceSettingsEditorModel.configurationGroups) {931for (const section of settingsGroup.sections) {932for (const setting of section.settings) {933if (!WorkspaceConfigurationRenderer.supportedKeys.includes(setting.key)) {934markerData.push({935severity: MarkerSeverity.Hint,936tags: [MarkerTag.Unnecessary],937...setting.range,938message: nls.localize('unsupportedProperty', "Unsupported Property")939});940}941}942}943}944this.decorations.set(ranges.map(range => this.createDecoration(range)));945}946if (markerData.length) {947this.markerService.changeOne('WorkspaceConfigurationRenderer', this.workspaceSettingsEditorModel.uri, markerData);948} else {949this.markerService.remove('WorkspaceConfigurationRenderer', [this.workspaceSettingsEditorModel.uri]);950}951}952953private static readonly _DIM_CONFIGURATION_ = ModelDecorationOptions.register({954description: 'dim-configuration',955stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,956inlineClassName: 'dim-configuration'957});958959private createDecoration(range: IRange): IModelDeltaDecoration {960return {961range,962options: WorkspaceConfigurationRenderer._DIM_CONFIGURATION_963};964}965966override dispose(): void {967this.markerService.remove('WorkspaceConfigurationRenderer', [this.workspaceSettingsEditorModel.uri]);968this.decorations.clear();969super.dispose();970}971}972973974