From f6470973e7331e82563311c1f969ebeef0fd9d73 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 16 Nov 2021 17:12:21 +0100 Subject: [PATCH] #51935 support updating configuration for multiple language identifiers in configuration service --- .../configuration/common/configuration.ts | 18 +++++--- .../common/configurationModels.ts | 12 +++--- .../common/configurationRegistry.ts | 39 +++++++++-------- .../api/common/configurationExtensionPoint.ts | 6 +-- .../api/common/extHostConfiguration.ts | 4 +- .../browser/preferencesRenderers.ts | 8 ++-- .../browser/configurationService.ts | 26 +++++++---- .../common/configurationEditingService.ts | 24 +++++------ .../test/browser/configurationService.test.ts | 43 ++++++++++++++----- .../preferences/browser/preferencesService.ts | 4 +- .../preferences/common/preferencesModels.ts | 6 +-- 11 files changed, 115 insertions(+), 75 deletions(-) diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index c1fe7191f49e0..7716e2b73dd99 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -25,6 +25,16 @@ export interface IConfigurationOverrides { resource?: URI | null; } +export function isConfigurationUpdateOverrides(thing: any): thing is IConfigurationUpdateOverrides { + return thing + && typeof thing === 'object' + && (!thing.overrideIdentifiers || types.isArray(thing.overrideIdentifiers)) + && !thing.overrideIdentifier + && (!thing.resource || thing.resource instanceof URI); +} + +export type IConfigurationUpdateOverrides = Omit & { overrideIdentifiers?: string[] | null; }; + export const enum ConfigurationTarget { USER = 1, USER_LOCAL, @@ -106,9 +116,9 @@ export interface IConfigurationService { getValue(section: string, overrides: IConfigurationOverrides): T; updateValue(key: string, value: any): Promise; - updateValue(key: string, value: any, overrides: IConfigurationOverrides): Promise; + updateValue(key: string, value: any, overrides: IConfigurationOverrides | IConfigurationUpdateOverrides): Promise; updateValue(key: string, value: any, target: ConfigurationTarget): Promise; - updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget, donotNotifyError?: boolean): Promise; + updateValue(key: string, value: any, overrides: IConfigurationOverrides | IConfigurationUpdateOverrides, target: ConfigurationTarget, donotNotifyError?: boolean): Promise; inspect(key: string, overrides?: IConfigurationOverrides): IConfigurationValue>; @@ -269,10 +279,6 @@ export function getDefaultValues(): any { return valueTreeRoot; } -export function keyFromOverrideIdentifier(overrideIdentifier: string): string { - return `[${overrideIdentifier}]`; -} - export function getMigratedSettingValue(configurationService: IConfigurationService, currentSettingName: string, legacySettingName: string): T { const setting = configurationService.inspect(currentSettingName); const legacySetting = configurationService.inspect(legacySettingName); diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 4ec58398615c6..f41d440c4f9ba 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -13,8 +13,8 @@ import * as objects from 'vs/base/common/objects'; import { IExtUri } from 'vs/base/common/resources'; import * as types from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { addToValueTree, ConfigurationTarget, getConfigurationKeys, getConfigurationValue, getDefaultValues, IConfigurationChange, IConfigurationChangeEvent, IConfigurationCompareResult, IConfigurationData, IConfigurationModel, IConfigurationOverrides, IConfigurationValue, IOverrides, removeFromValueTree, toValuesTree } from 'vs/platform/configuration/common/configuration'; -import { ConfigurationScope, Extensions, IConfigurationPropertySchema, IConfigurationRegistry, overrideIdentifiersFromKey, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +import { addToValueTree, ConfigurationTarget, getConfigurationKeys, getConfigurationValue, getDefaultValues, IConfigurationChange, IConfigurationChangeEvent, IConfigurationCompareResult, IConfigurationData, IConfigurationModel, IConfigurationOverrides, IConfigurationUpdateOverrides, IConfigurationValue, IOverrides, removeFromValueTree, toValuesTree } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationScope, Extensions, IConfigurationPropertySchema, IConfigurationRegistry, overrideIdentifiersFromKey, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; import { IFileService } from 'vs/platform/files/common/files'; import { Registry } from 'vs/platform/registry/common/platform'; import { Workspace } from 'vs/platform/workspace/common/workspace'; @@ -239,7 +239,7 @@ export class DefaultConfigurationModel extends ConfigurationModel { const keys = getConfigurationKeys(); const overrides: IOverrides[] = []; for (const key of Object.keys(contents)) { - if (OVERRIDE_PROPERTY_PATTERN.test(key)) { + if (OVERRIDE_PROPERTY_REGEX.test(key)) { overrides.push({ identifiers: overrideIdentifiersFromKey(key), keys: Object.keys(contents[key]), @@ -371,7 +371,7 @@ export class ConfigurationModelParser { const raw: any = {}; const restricted: string[] = []; for (let key in properties) { - if (OVERRIDE_PROPERTY_PATTERN.test(key) && filterOverriddenProperties) { + if (OVERRIDE_PROPERTY_REGEX.test(key) && filterOverriddenProperties) { const result = this.filter(properties[key], configurationProperties, false, options); raw[key] = result.raw; restricted.push(...result.restricted); @@ -395,7 +395,7 @@ export class ConfigurationModelParser { private toOverrides(raw: any, conflictReporter: (message: string) => void): IOverrides[] { const overrides: IOverrides[] = []; for (const key of Object.keys(raw)) { - if (OVERRIDE_PROPERTY_PATTERN.test(key)) { + if (OVERRIDE_PROPERTY_REGEX.test(key)) { const overrideRaw: any = {}; for (const keyInOverrideRaw in raw[key]) { overrideRaw[keyInOverrideRaw] = raw[key][keyInOverrideRaw]; @@ -476,7 +476,7 @@ export class Configuration { return consolidateConfigurationModel.getValue(section); } - updateValue(key: string, value: any, overrides: IConfigurationOverrides = {}): void { + updateValue(key: string, value: any, overrides: IConfigurationUpdateOverrides = {}): void { let memoryConfiguration: ConfigurationModel | undefined; if (overrides.resource) { memoryConfiguration = this._memoryConfigurationByResource.get(overrides.resource); diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 5d43dd41a650f..6dc55505e20f4 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -258,7 +258,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { for (const key in defaultConfiguration) { properties.push(key); - if (OVERRIDE_PROPERTY_PATTERN.test(key)) { + if (OVERRIDE_PROPERTY_REGEX.test(key)) { this.defaultValues[key] = { ...(this.defaultValues[key] || {}), ...defaultConfiguration[key] }; const property: IConfigurationPropertySchema = { type: 'object', @@ -291,7 +291,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { for (const key in defaultConfiguration) { properties.push(key); delete this.defaultValues[key]; - if (OVERRIDE_PROPERTY_PATTERN.test(key)) { + if (OVERRIDE_PROPERTY_REGEX.test(key)) { delete this.configurationProperties[key]; delete this.defaultLanguageConfigurationOverridesNode.properties![key]; } else { @@ -371,7 +371,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { this.updatePropertyDefaultValue(key, property); // update scope - if (OVERRIDE_PROPERTY_PATTERN.test(key)) { + if (OVERRIDE_PROPERTY_REGEX.test(key)) { property.scope = undefined; // No scope for overridable properties `[${identifier}]` } else { property.scope = types.isUndefinedOrNull(property.scope) ? scope : property.scope; @@ -507,12 +507,12 @@ class ConfigurationRegistry implements IConfigurationRegistry { errorMessage: nls.localize('overrideSettings.errorMessage', "This setting does not support per-language configuration."), $ref: resourceLanguageSettingsSchemaId, }; - allSettings.patternProperties[OVERRIDE_PROPERTY] = resourceLanguagePropertiesSchema; - applicationSettings.patternProperties[OVERRIDE_PROPERTY] = resourceLanguagePropertiesSchema; - machineSettings.patternProperties[OVERRIDE_PROPERTY] = resourceLanguagePropertiesSchema; - machineOverridableSettings.patternProperties[OVERRIDE_PROPERTY] = resourceLanguagePropertiesSchema; - windowSettings.patternProperties[OVERRIDE_PROPERTY] = resourceLanguagePropertiesSchema; - resourceSettings.patternProperties[OVERRIDE_PROPERTY] = resourceLanguagePropertiesSchema; + allSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema; + applicationSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema; + machineSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema; + machineOverridableSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema; + windowSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema; + resourceSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema; this._onDidSchemaChange.fire(); } @@ -528,26 +528,30 @@ class ConfigurationRegistry implements IConfigurationRegistry { } } -const OVERRIDE_IDENTIFIER = `\\[([^\\]]+)\\]`; -const OVERRIDE_IDENTIFIER_PATTERN = new RegExp(OVERRIDE_IDENTIFIER, 'g'); -export const OVERRIDE_PROPERTY = `^(${OVERRIDE_IDENTIFIER})+$`; -export const OVERRIDE_PROPERTY_PATTERN = new RegExp(OVERRIDE_PROPERTY); +const OVERRIDE_IDENTIFIER_PATTERN = `\\[([^\\]]+)\\]`; +const OVERRIDE_IDENTIFIER_REGEX = new RegExp(OVERRIDE_IDENTIFIER_PATTERN, 'g'); +export const OVERRIDE_PROPERTY_PATTERN = `^(${OVERRIDE_IDENTIFIER_PATTERN})+$`; +export const OVERRIDE_PROPERTY_REGEX = new RegExp(OVERRIDE_PROPERTY_PATTERN); export function overrideIdentifiersFromKey(key: string): string[] { const identifiers: string[] = []; - if (OVERRIDE_PROPERTY_PATTERN.test(key)) { - let matches = OVERRIDE_IDENTIFIER_PATTERN.exec(key); + if (OVERRIDE_PROPERTY_REGEX.test(key)) { + let matches = OVERRIDE_IDENTIFIER_REGEX.exec(key); while (matches?.length) { const identifier = matches[1].trim(); if (identifier) { identifiers.push(identifier); } - matches = OVERRIDE_IDENTIFIER_PATTERN.exec(key); + matches = OVERRIDE_IDENTIFIER_REGEX.exec(key); } } return distinct(identifiers); } +export function keyFromOverrideIdentifiers(overrideIdentifiers: string[]): string { + return overrideIdentifiers.reduce((result, overrideIdentifier) => `${result}[${overrideIdentifier}]`, ''); +} + export function getDefaultValue(type: string | string[] | undefined): any { const t = Array.isArray(type) ? (type)[0] : type; switch (t) { @@ -567,7 +571,6 @@ export function getDefaultValue(type: string | string[] | undefined): any { } } - const configurationRegistry = new ConfigurationRegistry(); Registry.add(Extensions.Configuration, configurationRegistry); @@ -575,7 +578,7 @@ export function validateProperty(property: string): string | null { if (!property.trim()) { return nls.localize('config.property.empty', "Cannot register an empty property"); } - if (OVERRIDE_PROPERTY_PATTERN.test(property)) { + if (OVERRIDE_PROPERTY_REGEX.test(property)) { return nls.localize('config.property.languageDefault', "Cannot register '{0}'. This matches property pattern '\\\\[.*\\\\]$' for describing language specific editor settings. Use 'configurationDefaults' contribution.", property); } if (configurationRegistry.getConfigurationProperties()[property] !== undefined) { diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index c0d23a31be90a..5f7b9e0cd9393 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -8,7 +8,7 @@ import * as objects from 'vs/base/common/objects'; import { Registry } from 'vs/platform/registry/common/platform'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { IConfigurationNode, IConfigurationRegistry, Extensions, resourceLanguageSettingsSchemaId, validateProperty, ConfigurationScope, OVERRIDE_PROPERTY_PATTERN, OVERRIDE_PROPERTY } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationNode, IConfigurationRegistry, Extensions, resourceLanguageSettingsSchemaId, validateProperty, ConfigurationScope, OVERRIDE_PROPERTY_PATTERN, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { workspaceSettingsSchemaId, launchSchemaId, tasksSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { isObject } from 'vs/base/common/types'; @@ -110,7 +110,7 @@ const defaultConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint { const addedDefaultConfigurations = added.map>(extension => { const defaults: IStringDictionary = objects.deepClone(extension.value); for (const key of Object.keys(defaults)) { - if (!OVERRIDE_PROPERTY_PATTERN.test(key) || typeof defaults[key] !== 'object') { + if (!OVERRIDE_PROPERTY_REGEX.test(key) || typeof defaults[key] !== 'object') { extension.collector.warn(nls.localize('config.property.defaultConfiguration.warning', "Cannot register configuration defaults for '{0}'. Only defaults for language specific settings are supported.", key)); delete defaults[key]; } diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index 5c90db67dbf73..e8033334ac62c 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -11,7 +11,7 @@ import { ExtHostConfigurationShape, MainThreadConfigurationShape, IConfiguration import { ConfigurationTarget as ExtHostConfigurationTarget } from './extHostTypes'; import { ConfigurationTarget, IConfigurationChange, IConfigurationData, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { Configuration, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; -import { ConfigurationScope, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationScope, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; import { isObject } from 'vs/base/common/types'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { Barrier } from 'vs/base/common/async'; @@ -297,7 +297,7 @@ export class ExtHostConfigProvider { } private _validateConfigurationAccess(key: string, overrides?: IConfigurationOverrides, extensionId?: ExtensionIdentifier): void { - const scope = OVERRIDE_PROPERTY_PATTERN.test(key) ? ConfigurationScope.RESOURCE : this._configurationScopes.get(key); + const scope = OVERRIDE_PROPERTY_REGEX.test(key) ? ConfigurationScope.RESOURCE : this._configurationScopes.get(key); const extensionIdText = extensionId ? `[${extensionId.value}] ` : ''; if (ConfigurationScope.RESOURCE === scope) { if (typeof overrides?.resource === 'undefined') { diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index ccacfefc8b1df..7374a24b8a558 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -24,7 +24,7 @@ import * as modes from 'vs/editor/common/modes'; import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import * as nls from 'vs/nls'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationRegistry, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationRegistry, overrideIdentifiersFromKey, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IMarkerData, IMarkerService, MarkerSeverity, MarkerTag } from 'vs/platform/markers/common/markers'; @@ -76,9 +76,9 @@ export class UserSettingsRenderer extends Disposable implements IPreferencesRend } updatePreference(key: string, value: any, source: IIndexedSetting): void { - const overrideIdentifier = source.overrideOf ? source.overrideOf.key.substring(1, source.overrideOf.key.length - 1) : null; + const overrideIdentifiers = source.overrideOf ? overrideIdentifiersFromKey(source.overrideOf.key) : null; const resource = this.preferencesModel.uri; - this.configurationService.updateValue(key, value, { overrideIdentifier, resource }, this.preferencesModel.configurationTarget) + this.configurationService.updateValue(key, value, { overrideIdentifiers, resource }, this.preferencesModel.configurationTarget) .then(() => this.onSettingUpdated(source)); } @@ -533,7 +533,7 @@ class UnsupportedSettingsRenderer extends Disposable implements modes.CodeAction this.handleWorkspaceFolderConfiguration(setting, configuration, markerData); break; } - } else if (!OVERRIDE_PROPERTY_PATTERN.test(setting.key)) { // Ignore override settings (language specific settings) + } else if (!OVERRIDE_PROPERTY_REGEX.test(setting.key)) { // Ignore override settings (language specific settings) markerData.push({ severity: MarkerSeverity.Hint, tags: [MarkerTag.Unnecessary], diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index f24e3a93f1919..5c0ae2f19c6fe 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -12,11 +12,11 @@ import { Queue, Barrier, runWhenIdle, Promises } from 'vs/base/common/async'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { IWorkspaceContextService, Workspace as BaseWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, WorkspaceFolder, toWorkspaceFolder, isWorkspaceFolder, IWorkspaceFoldersWillChangeEvent } from 'vs/platform/workspace/common/workspace'; import { ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent, mergeChanges } from 'vs/platform/configuration/common/configurationModels'; -import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange, ConfigurationTargetToString } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange, ConfigurationTargetToString, IConfigurationUpdateOverrides, isConfigurationUpdateOverrides } from 'vs/platform/configuration/common/configuration'; import { Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, RestrictedSettings } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings, ConfigurationScope, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings, ConfigurationScope, IConfigurationPropertySchema, keyFromOverrideIdentifiers } from 'vs/platform/configuration/common/configurationRegistry'; import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IWorkspaceInitializationPayload, IEmptyWorkspaceIdentifier, useSlashForPath, getStoredWorkspaceFolder, isSingleFolderWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ConfigurationEditingService, EditableConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditingService'; @@ -305,18 +305,26 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } updateValue(key: string, value: any): Promise; - updateValue(key: string, value: any, overrides: IConfigurationOverrides): Promise; + updateValue(key: string, value: any, overrides: IConfigurationOverrides | IConfigurationUpdateOverrides): Promise; updateValue(key: string, value: any, target: ConfigurationTarget): Promise; - updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget): Promise; - updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget, donotNotifyError: boolean): Promise; + updateValue(key: string, value: any, overrides: IConfigurationOverrides | IConfigurationUpdateOverrides, target: ConfigurationTarget, donotNotifyError?: boolean): Promise; async updateValue(key: string, value: any, arg3?: any, arg4?: any, donotNotifyError?: any): Promise { await this.cyclicDependency; - const overrides = isConfigurationOverrides(arg3) ? arg3 : undefined; + const overrides: IConfigurationUpdateOverrides | undefined = isConfigurationUpdateOverrides(arg3) ? arg3 + : isConfigurationOverrides(arg3) ? { resource: arg3.resource, overrideIdentifiers: arg3.overrideIdentifier ? [arg3.overrideIdentifier] : undefined } : undefined; const target: ConfigurationTarget | undefined = overrides ? arg4 : arg3; const targets: ConfigurationTarget[] = target ? [target] : []; + if (overrides?.overrideIdentifiers) { + overrides.overrideIdentifiers = distinct(overrides.overrideIdentifiers); + overrides.overrideIdentifiers = overrides.overrideIdentifiers.length ? overrides.overrideIdentifiers : undefined; + } + if (!targets.length) { - const inspect = this.inspect(key, overrides); + if (overrides?.overrideIdentifiers && overrides.overrideIdentifiers.length > 1) { + throw new Error('Configuration Target is required while updating the value for multiple override identifiers'); + } + const inspect = this.inspect(key, { resource: overrides?.resource, overrideIdentifier: overrides?.overrideIdentifiers ? overrides.overrideIdentifiers[0] : undefined }); targets.push(...this.deriveConfigurationTargets(key, value, inspect)); // Remove the setting, if the value is same as default value and is updated only in user target @@ -856,7 +864,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat return validWorkspaceFolders; } - private async writeConfigurationValue(key: string, value: any, target: ConfigurationTarget, overrides: IConfigurationOverrides | undefined, donotNotifyError: boolean): Promise { + private async writeConfigurationValue(key: string, value: any, target: ConfigurationTarget, overrides: IConfigurationUpdateOverrides | undefined, donotNotifyError: boolean): Promise { if (target === ConfigurationTarget.DEFAULT) { throw new Error('Invalid configuration target'); } @@ -864,7 +872,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat if (target === ConfigurationTarget.MEMORY) { const previous = { data: this._configuration.toData(), workspace: this.workspace }; this._configuration.updateValue(key, value, overrides); - this.triggerConfigurationChange({ keys: overrides?.overrideIdentifier ? [keyFromOverrideIdentifier(overrides.overrideIdentifier), key] : [key], overrides: overrides?.overrideIdentifier ? [[overrides?.overrideIdentifier, [key]]] : [] }, previous, target); + this.triggerConfigurationChange({ keys: overrides?.overrideIdentifiers?.length ? [keyFromOverrideIdentifiers(overrides.overrideIdentifiers), key] : [key], overrides: overrides?.overrideIdentifiers?.length ? overrides.overrideIdentifiers.map(overrideIdentifier => ([overrideIdentifier, [key]])) : [] }, previous, target); return; } diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index bfaf5ecd7d61f..c18ee164b6462 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -13,11 +13,11 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IConfigurationService, IConfigurationOverrides, keyFromOverrideIdentifier } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, IConfigurationUpdateOverrides } from 'vs/platform/configuration/common/configuration'; import { FOLDER_SETTINGS_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY, USER_STANDALONE_CONFIGURATIONS, TASKS_DEFAULT, FOLDER_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; -import { OVERRIDE_PROPERTY_PATTERN, IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, keyFromOverrideIdentifiers, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IOpenSettingsOptions, IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; @@ -113,7 +113,7 @@ export interface IConfigurationEditingOptions { /** * Scope of configuration to be written into. */ - scopes?: IConfigurationOverrides; + scopes?: IConfigurationUpdateOverrides; } export const enum EditableConfigurationTarget { @@ -243,7 +243,7 @@ export class ConfigurationEditingService { return { insertSpaces, tabSize, eol }; } - private async onError(error: ConfigurationEditingError, operation: IConfigurationEditOperation, scopes: IConfigurationOverrides | undefined): Promise { + private async onError(error: ConfigurationEditingError, operation: IConfigurationEditOperation, scopes: IConfigurationUpdateOverrides | undefined): Promise { switch (error.code) { case ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION: this.onInvalidConfigurationError(error, operation); @@ -279,7 +279,7 @@ export class ConfigurationEditingService { } } - private onConfigurationFileDirtyError(error: ConfigurationEditingError, operation: IConfigurationEditOperation, scopes: IConfigurationOverrides | undefined): void { + private onConfigurationFileDirtyError(error: ConfigurationEditingError, operation: IConfigurationEditOperation, scopes: IConfigurationUpdateOverrides | undefined): void { const openStandAloneConfigurationActionLabel = operation.workspaceStandAloneConfigurationKey === TASKS_CONFIGURATION_KEY ? nls.localize('openTasksConfiguration', "Open Tasks Configuration") : operation.workspaceStandAloneConfigurationKey === LAUNCH_CONFIGURATION_KEY ? nls.localize('openLaunchConfiguration', "Open Launch Configuration") : null; @@ -475,7 +475,7 @@ export class ConfigurationEditingService { return parseErrors.length > 0; } - private async validate(target: EditableConfigurationTarget, operation: IConfigurationEditOperation, checkDirty: boolean, overrides: IConfigurationOverrides): Promise { + private async validate(target: EditableConfigurationTarget, operation: IConfigurationEditOperation, checkDirty: boolean, overrides: IConfigurationUpdateOverrides): Promise { const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); const configurationScope = configurationProperties[operation.key]?.scope; @@ -488,7 +488,7 @@ export class ConfigurationEditingService { */ if (!operation.workspaceStandAloneConfigurationKey) { const validKeys = this.configurationService.keys().default; - if (validKeys.indexOf(operation.key) < 0 && !OVERRIDE_PROPERTY_PATTERN.test(operation.key) && operation.value !== undefined) { + if (validKeys.indexOf(operation.key) < 0 && !OVERRIDE_PROPERTY_REGEX.test(operation.key) && operation.value !== undefined) { throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_UNKNOWN_KEY, target, operation); } } @@ -506,7 +506,7 @@ export class ConfigurationEditingService { } if (target === EditableConfigurationTarget.WORKSPACE) { - if (!operation.workspaceStandAloneConfigurationKey && !OVERRIDE_PROPERTY_PATTERN.test(operation.key)) { + if (!operation.workspaceStandAloneConfigurationKey && !OVERRIDE_PROPERTY_REGEX.test(operation.key)) { if (configurationScope === ConfigurationScope.APPLICATION) { throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION, target, operation); } @@ -521,14 +521,14 @@ export class ConfigurationEditingService { throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_TARGET, target, operation); } - if (!operation.workspaceStandAloneConfigurationKey && !OVERRIDE_PROPERTY_PATTERN.test(operation.key)) { + if (!operation.workspaceStandAloneConfigurationKey && !OVERRIDE_PROPERTY_REGEX.test(operation.key)) { if (configurationScope !== undefined && !FOLDER_SCOPES.includes(configurationScope)) { throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_CONFIGURATION, target, operation); } } } - if (overrides.overrideIdentifier) { + if (overrides.overrideIdentifiers?.length) { if (configurationScope !== ConfigurationScope.LANGUAGE_OVERRIDABLE) { throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_INVALID_RESOURCE_LANGUAGE_CONFIGURATION, target, operation); } @@ -544,7 +544,7 @@ export class ConfigurationEditingService { } - private getConfigurationEditOperation(target: EditableConfigurationTarget, config: IConfigurationValue, overrides: IConfigurationOverrides): IConfigurationEditOperation { + private getConfigurationEditOperation(target: EditableConfigurationTarget, config: IConfigurationValue, overrides: IConfigurationUpdateOverrides): IConfigurationEditOperation { // Check for standalone workspace configurations if (config.key) { @@ -569,7 +569,7 @@ export class ConfigurationEditingService { } let key = config.key; - let jsonPath = overrides.overrideIdentifier ? [keyFromOverrideIdentifier(overrides.overrideIdentifier), key] : [key]; + let jsonPath = overrides.overrideIdentifiers?.length ? [keyFromOverrideIdentifiers(overrides.overrideIdentifiers), key] : [key]; if (target === EditableConfigurationTarget.USER_LOCAL || target === EditableConfigurationTarget.USER_REMOTE) { return { key, jsonPath, value: config.value, resource: withNullAsUndefined(this.getConfigurationFileResource(target, '', null)), target }; } diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index 6ea7e0ccaec52..17f9dfb4c5137 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -8,7 +8,7 @@ import * as sinon from 'sinon'; import { URI } from 'vs/base/common/uri'; import { Registry } from 'vs/platform/registry/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, keyFromOverrideIdentifiers } from 'vs/platform/configuration/common/configurationRegistry'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ConfigurationEditingErrorCode } from 'vs/workbench/services/configuration/common/configurationEditingService'; @@ -1012,9 +1012,38 @@ suite('WorkspaceConfigurationService - Folder', () => { .then(() => assert.strictEqual(testObject.getValue('configurationService.folder.testSetting'), 'value')); }); - test('update resource language configuration', () => { - return testObject.updateValue('configurationService.folder.languageSetting', 'value', { resource: workspaceService.getWorkspace().folders[0].uri }, ConfigurationTarget.WORKSPACE_FOLDER) - .then(() => assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting'), 'value')); + test('update language configuration using configuration overrides', async () => { + await testObject.updateValue('configurationService.folder.languageSetting', 'abcLangValue', { overrideIdentifier: 'abclang' }); + assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting', { overrideIdentifier: 'abclang' }), 'abcLangValue'); + }); + + test('update language configuration using configuration update overrides', async () => { + await testObject.updateValue('configurationService.folder.languageSetting', 'abcLangValue', { overrideIdentifiers: ['abclang'] }); + assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting', { overrideIdentifier: 'abclang' }), 'abcLangValue'); + }); + + test('update language configuration for multiple languages', async () => { + await testObject.updateValue('configurationService.folder.languageSetting', 'multiLangValue', { overrideIdentifiers: ['deflang', 'xyzlang'] }, ConfigurationTarget.USER); + assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting', { overrideIdentifier: 'deflang' }), 'multiLangValue'); + assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting', { overrideIdentifier: 'xyzlang' }), 'multiLangValue'); + assert.deepStrictEqual(testObject.getValue(keyFromOverrideIdentifiers(['deflang', 'xyzlang'])), { 'configurationService.folder.languageSetting': 'multiLangValue' }); + }); + + test('update resource language configuration', async () => { + await testObject.updateValue('configurationService.folder.languageSetting', 'value', { resource: workspaceService.getWorkspace().folders[0].uri }, ConfigurationTarget.WORKSPACE_FOLDER); + assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting'), 'value'); + }); + + test('update resource language configuration for a language using configuration overrides', async () => { + assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting', { resource: workspaceService.getWorkspace().folders[0].uri, overrideIdentifier: 'jsonc' }), 'languageValue'); + await testObject.updateValue('configurationService.folder.languageSetting', 'languageValueUpdated', { resource: workspaceService.getWorkspace().folders[0].uri, overrideIdentifier: 'jsonc' }, ConfigurationTarget.WORKSPACE_FOLDER); + assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting', { resource: workspaceService.getWorkspace().folders[0].uri, overrideIdentifier: 'jsonc' }), 'languageValueUpdated'); + }); + + test('update resource language configuration for a language using configuration update overrides', async () => { + assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting', { resource: workspaceService.getWorkspace().folders[0].uri, overrideIdentifier: 'jsonc' }), 'languageValue'); + await testObject.updateValue('configurationService.folder.languageSetting', 'languageValueUpdated', { resource: workspaceService.getWorkspace().folders[0].uri, overrideIdentifiers: ['jsonc'] }, ConfigurationTarget.WORKSPACE_FOLDER); + assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting', { resource: workspaceService.getWorkspace().folders[0].uri, overrideIdentifier: 'jsonc' }), 'languageValueUpdated'); }); test('update application setting into workspace configuration in a workspace is not supported', () => { @@ -1058,12 +1087,6 @@ suite('WorkspaceConfigurationService - Folder', () => { .then(() => assert.ok(target.called)); }); - test('resource language configuration', async () => { - assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting', { resource: workspaceService.getWorkspace().folders[0].uri, overrideIdentifier: 'jsonc' }), 'languageValue'); - await testObject.updateValue('configurationService.folder.languageSetting', 'languageValueUpdated', { resource: workspaceService.getWorkspace().folders[0].uri, overrideIdentifier: 'jsonc' }, ConfigurationTarget.WORKSPACE_FOLDER); - assert.strictEqual(testObject.getValue('configurationService.folder.languageSetting', { resource: workspaceService.getWorkspace().folders[0].uri, overrideIdentifier: 'jsonc' }), 'languageValueUpdated'); - }); - test('remove setting from all targets', async () => { const key = 'configurationService.folder.testSetting'; await testObject.updateValue(key, 'workspaceValue', ConfigurationTarget.WORKSPACE); diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index 96370d9ee7419..7800cc1c21fa7 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -19,7 +19,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import * as nls from 'vs/nls'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { Extensions, getDefaultValue, IConfigurationRegistry, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +import { Extensions, getDefaultValue, IConfigurationRegistry, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; import { EditorResolution } from 'vs/platform/editor/common/editor'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; @@ -541,7 +541,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return null; } const schema = Registry.as(Extensions.Configuration).getConfigurationProperties()[settingKey]; - const isOverrideProperty = OVERRIDE_PROPERTY_PATTERN.test(settingKey); + const isOverrideProperty = OVERRIDE_PROPERTY_REGEX.test(settingKey); if (!schema && !isOverrideProperty) { return null; } diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 6b5f95fde2c0a..2993223a62834 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -15,7 +15,7 @@ import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/mod import { ITextEditorModel } from 'vs/editor/common/services/resolverService'; import * as nls from 'vs/nls'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationPropertySchema, IConfigurationRegistry, OVERRIDE_PROPERTY_PATTERN, IConfigurationExtensionInfo } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationPropertySchema, IConfigurationRegistry, IConfigurationExtensionInfo, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; @@ -342,7 +342,7 @@ function parse(model: ITextModel, isSettingsProperty: (currentProperty: string, }; if (previousParents.length === settingsPropertyIndex + 1) { settings.push(setting); - if (OVERRIDE_PROPERTY_PATTERN.test(name)) { + if (OVERRIDE_PROPERTY_REGEX.test(name)) { overrideSetting = setting; } } else { @@ -618,7 +618,7 @@ export class DefaultSettings extends Disposable { description = ''; } const descriptionLines = description.split('\n'); - const overrides = OVERRIDE_PROPERTY_PATTERN.test(key) ? this.parseOverrideSettings(prop.default) : []; + const overrides = OVERRIDE_PROPERTY_REGEX.test(key) ? this.parseOverrideSettings(prop.default) : []; let listItemType: string | undefined; if (prop.type === 'array' && prop.items && !isArray(prop.items) && prop.items.type) { if (prop.items.enum) {