Path: blob/main/src/vs/workbench/services/configuration/common/configurationEditing.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 * as nls from '../../../../nls.js';6import { URI } from '../../../../base/common/uri.js';7import * as json from '../../../../base/common/json.js';8import { setProperty } from '../../../../base/common/jsonEdit.js';9import { Queue } from '../../../../base/common/async.js';10import { Edit, FormattingOptions } from '../../../../base/common/jsonFormatter.js';11import { Registry } from '../../../../platform/registry/common/platform.js';12import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';13import { ITextFileService } from '../../textfile/common/textfiles.js';14import { IConfigurationUpdateOptions, IConfigurationUpdateOverrides } from '../../../../platform/configuration/common/configuration.js';15import { FOLDER_SETTINGS_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY, USER_STANDALONE_CONFIGURATIONS, TASKS_DEFAULT, FOLDER_SCOPES, IWorkbenchConfigurationService, APPLICATION_SCOPES, MCP_CONFIGURATION_KEY } from './configuration.js';16import { FileOperationError, FileOperationResult, IFileService } from '../../../../platform/files/common/files.js';17import { IResolvedTextEditorModel, ITextModelService } from '../../../../editor/common/services/resolverService.js';18import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, keyFromOverrideIdentifiers, OVERRIDE_PROPERTY_REGEX } from '../../../../platform/configuration/common/configurationRegistry.js';19import { IEditorService } from '../../editor/common/editorService.js';20import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js';21import { IOpenSettingsOptions, IPreferencesService } from '../../preferences/common/preferences.js';22import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';23import { ITextModel } from '../../../../editor/common/model.js';24import { IDisposable, IReference } from '../../../../base/common/lifecycle.js';25import { Range } from '../../../../editor/common/core/range.js';26import { EditOperation } from '../../../../editor/common/core/editOperation.js';27import { Selection } from '../../../../editor/common/core/selection.js';28import { IUserDataProfileService } from '../../userDataProfile/common/userDataProfile.js';29import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js';30import { ErrorNoTelemetry } from '../../../../base/common/errors.js';31import { IFilesConfigurationService } from '../../filesConfiguration/common/filesConfigurationService.js';3233export const enum ConfigurationEditingErrorCode {3435/**36* Error when trying to write a configuration key that is not registered.37*/38ERROR_UNKNOWN_KEY,3940/**41* Error when trying to write an application setting into workspace settings.42*/43ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION,4445/**46* Error when trying to write a machne setting into workspace settings.47*/48ERROR_INVALID_WORKSPACE_CONFIGURATION_MACHINE,4950/**51* Error when trying to write an invalid folder configuration key to folder settings.52*/53ERROR_INVALID_FOLDER_CONFIGURATION,5455/**56* Error when trying to write to user target but not supported for provided key.57*/58ERROR_INVALID_USER_TARGET,5960/**61* Error when trying to write to user target but not supported for provided key.62*/63ERROR_INVALID_WORKSPACE_TARGET,6465/**66* Error when trying to write a configuration key to folder target67*/68ERROR_INVALID_FOLDER_TARGET,6970/**71* Error when trying to write to language specific setting but not supported for preovided key72*/73ERROR_INVALID_RESOURCE_LANGUAGE_CONFIGURATION,7475/**76* Error when trying to write to the workspace configuration without having a workspace opened.77*/78ERROR_NO_WORKSPACE_OPENED,7980/**81* Error when trying to write and save to the configuration file while it is dirty in the editor.82*/83ERROR_CONFIGURATION_FILE_DIRTY,8485/**86* Error when trying to write and save to the configuration file while it is not the latest in the disk.87*/88ERROR_CONFIGURATION_FILE_MODIFIED_SINCE,8990/**91* Error when trying to write to a configuration file that contains JSON errors.92*/93ERROR_INVALID_CONFIGURATION,9495/**96* Error when trying to write a policy configuration97*/98ERROR_POLICY_CONFIGURATION,99100/**101* Internal Error.102*/103ERROR_INTERNAL104}105106export class ConfigurationEditingError extends ErrorNoTelemetry {107constructor(message: string, public code: ConfigurationEditingErrorCode) {108super(message);109}110}111112export interface IConfigurationValue {113key: string;114value: unknown;115}116117export interface IConfigurationEditingOptions extends IConfigurationUpdateOptions {118/**119* Scope of configuration to be written into.120*/121scopes?: IConfigurationUpdateOverrides;122}123124export const enum EditableConfigurationTarget {125USER_LOCAL = 1,126USER_REMOTE,127WORKSPACE,128WORKSPACE_FOLDER129}130131interface IConfigurationEditOperation extends IConfigurationValue {132target: EditableConfigurationTarget;133jsonPath: json.JSONPath;134resource?: URI;135workspaceStandAloneConfigurationKey?: string;136}137138export class ConfigurationEditing {139140public _serviceBrand: undefined;141142private queue: Queue<void>;143144constructor(145private readonly remoteSettingsResource: URI | null,146@IWorkbenchConfigurationService private readonly configurationService: IWorkbenchConfigurationService,147@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,148@IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService,149@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,150@IFileService private readonly fileService: IFileService,151@ITextModelService private readonly textModelResolverService: ITextModelService,152@ITextFileService private readonly textFileService: ITextFileService,153@INotificationService private readonly notificationService: INotificationService,154@IPreferencesService private readonly preferencesService: IPreferencesService,155@IEditorService private readonly editorService: IEditorService,156@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,157@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService158) {159this.queue = new Queue<void>();160}161162async writeConfiguration(target: EditableConfigurationTarget, value: IConfigurationValue, options: IConfigurationEditingOptions = {}): Promise<void> {163const operation = this.getConfigurationEditOperation(target, value, options.scopes || {});164// queue up writes to prevent race conditions165return this.queue.queue(async () => {166try {167await this.doWriteConfiguration(operation, options);168} catch (error) {169if (options.donotNotifyError) {170throw error;171}172await this.onError(error, operation, options.scopes);173}174});175}176177private async doWriteConfiguration(operation: IConfigurationEditOperation, options: IConfigurationEditingOptions): Promise<void> {178await this.validate(operation.target, operation, !options.handleDirtyFile, options.scopes || {});179const resource: URI = operation.resource!;180const reference = await this.resolveModelReference(resource);181try {182const formattingOptions = this.getFormattingOptions(reference.object.textEditorModel);183await this.updateConfiguration(operation, reference.object.textEditorModel, formattingOptions, options);184} finally {185reference.dispose();186}187}188189private async updateConfiguration(operation: IConfigurationEditOperation, model: ITextModel, formattingOptions: FormattingOptions, options: IConfigurationEditingOptions): Promise<void> {190if (this.hasParseErrors(model.getValue(), operation)) {191throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION, operation.target, operation);192}193194if (this.textFileService.isDirty(model.uri) && options.handleDirtyFile) {195switch (options.handleDirtyFile) {196case 'save': await this.save(model, operation); break;197case 'revert': await this.textFileService.revert(model.uri); break;198}199}200201const edit = this.getEdits(operation, model.getValue(), formattingOptions)[0];202if (edit) {203let disposable: IDisposable | undefined;204try {205// Optimization: we apply edits to a text model and save it206// right after. Use the files config service to signal this207// to the workbench to optimise the UI during this operation.208// For example, avoids to briefly show dirty indicators.209disposable = this.filesConfigurationService.enableAutoSaveAfterShortDelay(model.uri);210if (this.applyEditsToBuffer(edit, model)) {211await this.save(model, operation);212}213} finally {214disposable?.dispose();215}216}217}218219private async save(model: ITextModel, operation: IConfigurationEditOperation): Promise<void> {220try {221await this.textFileService.save(model.uri, { ignoreErrorHandler: true });222} catch (error) {223if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE) {224throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_MODIFIED_SINCE, operation.target, operation);225}226throw new ConfigurationEditingError(nls.localize('fsError', "Error while writing to {0}. {1}", this.stringifyTarget(operation.target), error.message), ConfigurationEditingErrorCode.ERROR_INTERNAL);227}228}229230private applyEditsToBuffer(edit: Edit, model: ITextModel): boolean {231const startPosition = model.getPositionAt(edit.offset);232const endPosition = model.getPositionAt(edit.offset + edit.length);233const range = new Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column);234const currentText = model.getValueInRange(range);235if (edit.content !== currentText) {236const editOperation = currentText ? EditOperation.replace(range, edit.content) : EditOperation.insert(startPosition, edit.content);237model.pushEditOperations([new Selection(startPosition.lineNumber, startPosition.column, startPosition.lineNumber, startPosition.column)], [editOperation], () => []);238return true;239}240return false;241}242243private getEdits({ value, jsonPath }: IConfigurationEditOperation, modelContent: string, formattingOptions: FormattingOptions): Edit[] {244if (jsonPath.length) {245return setProperty(modelContent, jsonPath, value, formattingOptions);246}247248// Without jsonPath, the entire configuration file is being replaced, so we just use JSON.stringify249const content = JSON.stringify(value, null, formattingOptions.insertSpaces && formattingOptions.tabSize ? ' '.repeat(formattingOptions.tabSize) : '\t');250return [{251content,252length: modelContent.length,253offset: 0254}];255}256257private getFormattingOptions(model: ITextModel): FormattingOptions {258const { insertSpaces, tabSize } = model.getOptions();259const eol = model.getEOL();260return { insertSpaces, tabSize, eol };261}262263private async onError(error: ConfigurationEditingError, operation: IConfigurationEditOperation, scopes: IConfigurationUpdateOverrides | undefined): Promise<void> {264switch (error.code) {265case ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION:266this.onInvalidConfigurationError(error, operation);267break;268case ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_DIRTY:269this.onConfigurationFileDirtyError(error, operation, scopes);270break;271case ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_MODIFIED_SINCE:272return this.doWriteConfiguration(operation, { scopes, handleDirtyFile: 'revert' });273default:274this.notificationService.error(error.message);275}276}277278private onInvalidConfigurationError(error: ConfigurationEditingError, operation: IConfigurationEditOperation,): void {279const openStandAloneConfigurationActionLabel = operation.workspaceStandAloneConfigurationKey === TASKS_CONFIGURATION_KEY ? nls.localize('openTasksConfiguration', "Open Tasks Configuration")280: operation.workspaceStandAloneConfigurationKey === LAUNCH_CONFIGURATION_KEY ? nls.localize('openLaunchConfiguration', "Open Launch Configuration")281: operation.workspaceStandAloneConfigurationKey === MCP_CONFIGURATION_KEY ? nls.localize('openMcpConfiguration', "Open MCP Configuration")282: null;283if (openStandAloneConfigurationActionLabel) {284this.notificationService.prompt(Severity.Error, error.message,285[{286label: openStandAloneConfigurationActionLabel,287run: () => this.openFile(operation.resource!)288}]289);290} else {291this.notificationService.prompt(Severity.Error, error.message,292[{293label: nls.localize('open', "Open Settings"),294run: () => this.openSettings(operation)295}]296);297}298}299300private onConfigurationFileDirtyError(error: ConfigurationEditingError, operation: IConfigurationEditOperation, scopes: IConfigurationUpdateOverrides | undefined): void {301const openStandAloneConfigurationActionLabel = operation.workspaceStandAloneConfigurationKey === TASKS_CONFIGURATION_KEY ? nls.localize('openTasksConfiguration', "Open Tasks Configuration")302: operation.workspaceStandAloneConfigurationKey === LAUNCH_CONFIGURATION_KEY ? nls.localize('openLaunchConfiguration', "Open Launch Configuration")303: null;304if (openStandAloneConfigurationActionLabel) {305this.notificationService.prompt(Severity.Error, error.message,306[{307label: nls.localize('saveAndRetry', "Save and Retry"),308run: () => {309const key = operation.key ? `${operation.workspaceStandAloneConfigurationKey}.${operation.key}` : operation.workspaceStandAloneConfigurationKey!;310this.writeConfiguration(operation.target, { key, value: operation.value }, { handleDirtyFile: 'save', scopes });311}312},313{314label: openStandAloneConfigurationActionLabel,315run: () => this.openFile(operation.resource!)316}]317);318} else {319this.notificationService.prompt(Severity.Error, error.message,320[{321label: nls.localize('saveAndRetry', "Save and Retry"),322run: () => this.writeConfiguration(operation.target, { key: operation.key, value: operation.value }, { handleDirtyFile: 'save', scopes })323},324{325label: nls.localize('open', "Open Settings"),326run: () => this.openSettings(operation)327}]328);329}330}331332private openSettings(operation: IConfigurationEditOperation): void {333const options: IOpenSettingsOptions = { jsonEditor: true };334switch (operation.target) {335case EditableConfigurationTarget.USER_LOCAL:336this.preferencesService.openUserSettings(options);337break;338case EditableConfigurationTarget.USER_REMOTE:339this.preferencesService.openRemoteSettings(options);340break;341case EditableConfigurationTarget.WORKSPACE:342this.preferencesService.openWorkspaceSettings(options);343break;344case EditableConfigurationTarget.WORKSPACE_FOLDER:345if (operation.resource) {346const workspaceFolder = this.contextService.getWorkspaceFolder(operation.resource);347if (workspaceFolder) {348this.preferencesService.openFolderSettings({ folderUri: workspaceFolder.uri, jsonEditor: true });349}350}351break;352}353}354355private openFile(resource: URI): void {356this.editorService.openEditor({ resource, options: { pinned: true } });357}358359private toConfigurationEditingError(code: ConfigurationEditingErrorCode, target: EditableConfigurationTarget, operation: IConfigurationEditOperation): ConfigurationEditingError {360const message = this.toErrorMessage(code, target, operation);361return new ConfigurationEditingError(message, code);362}363364private toErrorMessage(error: ConfigurationEditingErrorCode, target: EditableConfigurationTarget, operation: IConfigurationEditOperation): string {365switch (error) {366367// API constraints368case ConfigurationEditingErrorCode.ERROR_POLICY_CONFIGURATION: return nls.localize('errorPolicyConfiguration', "Unable to write {0} because it is configured in system policy.", operation.key);369case ConfigurationEditingErrorCode.ERROR_UNKNOWN_KEY: return nls.localize('errorUnknownKey', "Unable to write to {0} because {1} is not a registered configuration.", this.stringifyTarget(target), operation.key);370case ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION: return nls.localize('errorInvalidWorkspaceConfigurationApplication', "Unable to write {0} to Workspace Settings. This setting can be written only into User settings.", operation.key);371case ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_MACHINE: return nls.localize('errorInvalidWorkspaceConfigurationMachine', "Unable to write {0} to Workspace Settings. This setting can be written only into User settings.", operation.key);372case ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_CONFIGURATION: return nls.localize('errorInvalidFolderConfiguration', "Unable to write to Folder Settings because {0} does not support the folder resource scope.", operation.key);373case ConfigurationEditingErrorCode.ERROR_INVALID_USER_TARGET: return nls.localize('errorInvalidUserTarget', "Unable to write to User Settings because {0} does not support for global scope.", operation.key);374case ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_TARGET: return nls.localize('errorInvalidWorkspaceTarget', "Unable to write to Workspace Settings because {0} does not support for workspace scope in a multi folder workspace.", operation.key);375case ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_TARGET: return nls.localize('errorInvalidFolderTarget', "Unable to write to Folder Settings because no resource is provided.");376case ConfigurationEditingErrorCode.ERROR_INVALID_RESOURCE_LANGUAGE_CONFIGURATION: return nls.localize('errorInvalidResourceLanguageConfiguration', "Unable to write to Language Settings because {0} is not a resource language setting.", operation.key);377case ConfigurationEditingErrorCode.ERROR_NO_WORKSPACE_OPENED: return nls.localize('errorNoWorkspaceOpened', "Unable to write to {0} because no workspace is opened. Please open a workspace first and try again.", this.stringifyTarget(target));378379// User issues380case ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION: {381if (operation.workspaceStandAloneConfigurationKey === TASKS_CONFIGURATION_KEY) {382return nls.localize('errorInvalidTaskConfiguration', "Unable to write into the tasks configuration file. Please open it to correct errors/warnings in it and try again.");383}384if (operation.workspaceStandAloneConfigurationKey === LAUNCH_CONFIGURATION_KEY) {385return nls.localize('errorInvalidLaunchConfiguration', "Unable to write into the launch configuration file. Please open it to correct errors/warnings in it and try again.");386}387if (operation.workspaceStandAloneConfigurationKey === MCP_CONFIGURATION_KEY) {388return nls.localize('errorInvalidMCPConfiguration', "Unable to write into the MCP configuration file. Please open it to correct errors/warnings in it and try again.");389}390switch (target) {391case EditableConfigurationTarget.USER_LOCAL:392return nls.localize('errorInvalidConfiguration', "Unable to write into user settings. Please open the user settings to correct errors/warnings in it and try again.");393case EditableConfigurationTarget.USER_REMOTE:394return nls.localize('errorInvalidRemoteConfiguration', "Unable to write into remote user settings. Please open the remote user settings to correct errors/warnings in it and try again.");395case EditableConfigurationTarget.WORKSPACE:396return nls.localize('errorInvalidConfigurationWorkspace', "Unable to write into workspace settings. Please open the workspace settings to correct errors/warnings in the file and try again.");397case EditableConfigurationTarget.WORKSPACE_FOLDER: {398let workspaceFolderName: string = '<<unknown>>';399if (operation.resource) {400const folder = this.contextService.getWorkspaceFolder(operation.resource);401if (folder) {402workspaceFolderName = folder.name;403}404}405return nls.localize('errorInvalidConfigurationFolder', "Unable to write into folder settings. Please open the '{0}' folder settings to correct errors/warnings in it and try again.", workspaceFolderName);406}407default:408return '';409}410}411case ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_DIRTY: {412if (operation.workspaceStandAloneConfigurationKey === TASKS_CONFIGURATION_KEY) {413return nls.localize('errorTasksConfigurationFileDirty', "Unable to write into tasks configuration file because the file has unsaved changes. Please save it first and then try again.");414}415if (operation.workspaceStandAloneConfigurationKey === LAUNCH_CONFIGURATION_KEY) {416return nls.localize('errorLaunchConfigurationFileDirty', "Unable to write into launch configuration file because the file has unsaved changes. Please save it first and then try again.");417}418if (operation.workspaceStandAloneConfigurationKey === MCP_CONFIGURATION_KEY) {419return nls.localize('errorMCPConfigurationFileDirty', "Unable to write into MCP configuration file because the file has unsaved changes. Please save it first and then try again.");420}421switch (target) {422case EditableConfigurationTarget.USER_LOCAL:423return nls.localize('errorConfigurationFileDirty', "Unable to write into user settings because the file has unsaved changes. Please save the user settings file first and then try again.");424case EditableConfigurationTarget.USER_REMOTE:425return nls.localize('errorRemoteConfigurationFileDirty', "Unable to write into remote user settings because the file has unsaved changes. Please save the remote user settings file first and then try again.");426case EditableConfigurationTarget.WORKSPACE:427return nls.localize('errorConfigurationFileDirtyWorkspace', "Unable to write into workspace settings because the file has unsaved changes. Please save the workspace settings file first and then try again.");428case EditableConfigurationTarget.WORKSPACE_FOLDER: {429let workspaceFolderName: string = '<<unknown>>';430if (operation.resource) {431const folder = this.contextService.getWorkspaceFolder(operation.resource);432if (folder) {433workspaceFolderName = folder.name;434}435}436return nls.localize('errorConfigurationFileDirtyFolder', "Unable to write into folder settings because the file has unsaved changes. Please save the '{0}' folder settings file first and then try again.", workspaceFolderName);437}438default:439return '';440}441}442case ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_MODIFIED_SINCE:443if (operation.workspaceStandAloneConfigurationKey === TASKS_CONFIGURATION_KEY) {444return nls.localize('errorTasksConfigurationFileModifiedSince', "Unable to write into tasks configuration file because the content of the file is newer.");445}446if (operation.workspaceStandAloneConfigurationKey === LAUNCH_CONFIGURATION_KEY) {447return nls.localize('errorLaunchConfigurationFileModifiedSince', "Unable to write into launch configuration file because the content of the file is newer.");448}449if (operation.workspaceStandAloneConfigurationKey === MCP_CONFIGURATION_KEY) {450return nls.localize('errorMCPConfigurationFileModifiedSince', "Unable to write into MCP configuration file because the content of the file is newer.");451}452switch (target) {453case EditableConfigurationTarget.USER_LOCAL:454return nls.localize('errorConfigurationFileModifiedSince', "Unable to write into user settings because the content of the file is newer.");455case EditableConfigurationTarget.USER_REMOTE:456return nls.localize('errorRemoteConfigurationFileModifiedSince', "Unable to write into remote user settings because the content of the file is newer.");457case EditableConfigurationTarget.WORKSPACE:458return nls.localize('errorConfigurationFileModifiedSinceWorkspace', "Unable to write into workspace settings because the content of the file is newer.");459case EditableConfigurationTarget.WORKSPACE_FOLDER:460return nls.localize('errorConfigurationFileModifiedSinceFolder', "Unable to write into folder settings because the content of the file is newer.");461}462case ConfigurationEditingErrorCode.ERROR_INTERNAL: return nls.localize('errorUnknown', "Unable to write to {0} because of an internal error.", this.stringifyTarget(target));463}464}465466private stringifyTarget(target: EditableConfigurationTarget): string {467switch (target) {468case EditableConfigurationTarget.USER_LOCAL:469return nls.localize('userTarget', "User Settings");470case EditableConfigurationTarget.USER_REMOTE:471return nls.localize('remoteUserTarget', "Remote User Settings");472case EditableConfigurationTarget.WORKSPACE:473return nls.localize('workspaceTarget', "Workspace Settings");474case EditableConfigurationTarget.WORKSPACE_FOLDER:475return nls.localize('folderTarget', "Folder Settings");476default:477return '';478}479}480481private defaultResourceValue(resource: URI): string {482const basename: string = this.uriIdentityService.extUri.basename(resource);483const configurationValue: string = basename.substr(0, basename.length - this.uriIdentityService.extUri.extname(resource).length);484switch (configurationValue) {485case TASKS_CONFIGURATION_KEY: return TASKS_DEFAULT;486default: return '{}';487}488}489490private async resolveModelReference(resource: URI): Promise<IReference<IResolvedTextEditorModel>> {491const exists = await this.fileService.exists(resource);492if (!exists) {493await this.textFileService.write(resource, this.defaultResourceValue(resource), { encoding: 'utf8' });494}495return this.textModelResolverService.createModelReference(resource);496}497498private hasParseErrors(content: string, operation: IConfigurationEditOperation): boolean {499// If we write to a workspace standalone file and replace the entire contents (no key provided)500// we can return here because any parse errors can safely be ignored since all contents are replaced501if (operation.workspaceStandAloneConfigurationKey && !operation.key) {502return false;503}504const parseErrors: json.ParseError[] = [];505json.parse(content, parseErrors, { allowTrailingComma: true, allowEmptyContent: true });506return parseErrors.length > 0;507}508509private async validate(target: EditableConfigurationTarget, operation: IConfigurationEditOperation, checkDirty: boolean, overrides: IConfigurationUpdateOverrides): Promise<void> {510511if (this.configurationService.inspect(operation.key).policyValue !== undefined) {512throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_POLICY_CONFIGURATION, target, operation);513}514515const configurationProperties = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();516const configurationScope = configurationProperties[operation.key]?.scope;517518/**519* Key to update must be a known setting from the registry unless520* - the key is standalone configuration (eg: tasks, debug)521* - the key is an override identifier522* - the operation is to delete the key523*/524if (!operation.workspaceStandAloneConfigurationKey) {525const validKeys = this.configurationService.keys().default;526if (validKeys.indexOf(operation.key) < 0 && !OVERRIDE_PROPERTY_REGEX.test(operation.key) && operation.value !== undefined) {527throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_UNKNOWN_KEY, target, operation);528}529}530531if (operation.workspaceStandAloneConfigurationKey) {532// Global launches are not supported533if ((operation.workspaceStandAloneConfigurationKey !== TASKS_CONFIGURATION_KEY) && (operation.workspaceStandAloneConfigurationKey !== MCP_CONFIGURATION_KEY) && (target === EditableConfigurationTarget.USER_LOCAL || target === EditableConfigurationTarget.USER_REMOTE)) {534throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_USER_TARGET, target, operation);535}536}537538// Target cannot be workspace or folder if no workspace opened539if ((target === EditableConfigurationTarget.WORKSPACE || target === EditableConfigurationTarget.WORKSPACE_FOLDER) && this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {540throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_NO_WORKSPACE_OPENED, target, operation);541}542543if (target === EditableConfigurationTarget.WORKSPACE) {544if (!operation.workspaceStandAloneConfigurationKey && !OVERRIDE_PROPERTY_REGEX.test(operation.key)) {545if (configurationScope && APPLICATION_SCOPES.includes(configurationScope)) {546throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION, target, operation);547}548if (configurationScope === ConfigurationScope.MACHINE) {549throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_MACHINE, target, operation);550}551}552}553554if (target === EditableConfigurationTarget.WORKSPACE_FOLDER) {555if (!operation.resource) {556throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_TARGET, target, operation);557}558559if (!operation.workspaceStandAloneConfigurationKey && !OVERRIDE_PROPERTY_REGEX.test(operation.key)) {560if (configurationScope !== undefined && !FOLDER_SCOPES.includes(configurationScope)) {561throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_CONFIGURATION, target, operation);562}563}564}565566if (overrides.overrideIdentifiers?.length) {567if (configurationScope !== ConfigurationScope.LANGUAGE_OVERRIDABLE) {568throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_RESOURCE_LANGUAGE_CONFIGURATION, target, operation);569}570}571572if (!operation.resource) {573throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_TARGET, target, operation);574}575576if (checkDirty && this.textFileService.isDirty(operation.resource)) {577throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_DIRTY, target, operation);578}579580}581582private getConfigurationEditOperation(target: EditableConfigurationTarget, config: IConfigurationValue, overrides: IConfigurationUpdateOverrides): IConfigurationEditOperation {583584// Check for standalone workspace configurations585if (config.key) {586const standaloneConfigurationMap = target === EditableConfigurationTarget.USER_LOCAL ? USER_STANDALONE_CONFIGURATIONS : WORKSPACE_STANDALONE_CONFIGURATIONS;587const standaloneConfigurationKeys = Object.keys(standaloneConfigurationMap);588for (const key of standaloneConfigurationKeys) {589const resource = this.getConfigurationFileResource(target, key, standaloneConfigurationMap[key], overrides.resource, undefined);590591// Check for prefix592if (config.key === key) {593const jsonPath = this.isWorkspaceConfigurationResource(resource) ? [key] : [];594return { key: jsonPath[jsonPath.length - 1], jsonPath, value: config.value, resource: resource ?? undefined, workspaceStandAloneConfigurationKey: key, target };595}596597// Check for prefix.<setting>598const keyPrefix = `${key}.`;599if (config.key.indexOf(keyPrefix) === 0) {600const jsonPath = this.isWorkspaceConfigurationResource(resource) ? [key, config.key.substring(keyPrefix.length)] : [config.key.substring(keyPrefix.length)];601return { key: jsonPath[jsonPath.length - 1], jsonPath, value: config.value, resource: resource ?? undefined, workspaceStandAloneConfigurationKey: key, target };602}603}604}605606const key = config.key;607const configurationProperties = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();608const configurationScope = configurationProperties[key]?.scope;609let jsonPath = overrides.overrideIdentifiers?.length ? [keyFromOverrideIdentifiers(overrides.overrideIdentifiers), key] : [key];610if (target === EditableConfigurationTarget.USER_LOCAL || target === EditableConfigurationTarget.USER_REMOTE) {611return { key, jsonPath, value: config.value, resource: this.getConfigurationFileResource(target, key, '', null, configurationScope) ?? undefined, target };612}613614const resource = this.getConfigurationFileResource(target, key, FOLDER_SETTINGS_PATH, overrides.resource, configurationScope);615if (this.isWorkspaceConfigurationResource(resource)) {616jsonPath = ['settings', ...jsonPath];617}618return { key, jsonPath, value: config.value, resource: resource ?? undefined, target };619}620621private isWorkspaceConfigurationResource(resource: URI | null): boolean {622const workspace = this.contextService.getWorkspace();623return !!(workspace.configuration && resource && workspace.configuration.fsPath === resource.fsPath);624}625626private getConfigurationFileResource(target: EditableConfigurationTarget, key: string, relativePath: string, resource: URI | null | undefined, scope: ConfigurationScope | undefined): URI | null {627if (target === EditableConfigurationTarget.USER_LOCAL) {628if (key === TASKS_CONFIGURATION_KEY) {629return this.userDataProfileService.currentProfile.tasksResource;630} if (key === MCP_CONFIGURATION_KEY) {631return this.userDataProfileService.currentProfile.mcpResource;632} else {633if (!this.userDataProfileService.currentProfile.isDefault && this.configurationService.isSettingAppliedForAllProfiles(key)) {634return this.userDataProfilesService.defaultProfile.settingsResource;635}636return this.userDataProfileService.currentProfile.settingsResource;637}638}639if (target === EditableConfigurationTarget.USER_REMOTE) {640return this.remoteSettingsResource;641}642const workbenchState = this.contextService.getWorkbenchState();643if (workbenchState !== WorkbenchState.EMPTY) {644645const workspace = this.contextService.getWorkspace();646647if (target === EditableConfigurationTarget.WORKSPACE) {648if (workbenchState === WorkbenchState.WORKSPACE) {649return workspace.configuration ?? null;650}651if (workbenchState === WorkbenchState.FOLDER) {652return workspace.folders[0].toResource(relativePath);653}654}655656if (target === EditableConfigurationTarget.WORKSPACE_FOLDER) {657if (resource) {658const folder = this.contextService.getWorkspaceFolder(resource);659if (folder) {660return folder.toResource(relativePath);661}662}663}664}665return null;666}667}668669670