From 340338b3b3b4d05958895aa73fecfe7391ff8ba5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 16 Nov 2021 15:03:22 +0100 Subject: [PATCH] #51935 support multi lang specific settings --- .../configuration/common/configuration.ts | 20 +------ .../common/configurationModels.ts | 30 +++++++--- .../common/configurationRegistry.ts | 38 ++++++++++-- .../test/common/configurationModels.test.ts | 60 ++++++++++++++++++- .../api/common/configurationExtensionPoint.ts | 4 +- .../browser/preferencesRenderers.ts | 4 +- 6 files changed, 120 insertions(+), 36 deletions(-) diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 6160cb333d0e5..c1fe7191f49e0 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -6,7 +6,7 @@ import { Event } from 'vs/base/common/event'; import * as types from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { Extensions, IConfigurationRegistry, overrideIdentifierFromKey, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -149,24 +149,6 @@ export interface IConfigurationCompareResult { overrides: [string, string[]][]; } -export function toOverrides(raw: any, conflictReporter: (message: string) => void): IOverrides[] { - const overrides: IOverrides[] = []; - for (const key of Object.keys(raw)) { - if (OVERRIDE_PROPERTY_PATTERN.test(key)) { - const overrideRaw: any = {}; - for (const keyInOverrideRaw in raw[key]) { - overrideRaw[keyInOverrideRaw] = raw[key][keyInOverrideRaw]; - } - overrides.push({ - identifiers: [overrideIdentifierFromKey(key).trim()], - keys: Object.keys(overrideRaw), - contents: toValuesTree(overrideRaw, conflictReporter) - }); - } - } - return overrides; -} - export function toValuesTree(properties: { [qualifiedKey: string]: any }, conflictReporter: (message: string) => void): any { const root = Object.create(null); diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 0cb02c481bd06..4ec58398615c6 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, toOverrides, toValuesTree } from 'vs/platform/configuration/common/configuration'; -import { ConfigurationScope, Extensions, IConfigurationPropertySchema, IConfigurationRegistry, overrideIdentifierFromKey, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +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 { IFileService } from 'vs/platform/files/common/files'; import { Registry } from 'vs/platform/registry/common/platform'; import { Workspace } from 'vs/platform/workspace/common/workspace'; @@ -241,7 +241,7 @@ export class DefaultConfigurationModel extends ConfigurationModel { for (const key of Object.keys(contents)) { if (OVERRIDE_PROPERTY_PATTERN.test(key)) { overrides.push({ - identifiers: [overrideIdentifierFromKey(key).trim()], + identifiers: overrideIdentifiersFromKey(key), keys: Object.keys(contents[key]), contents: toValuesTree(contents[key], message => console.error(`Conflict in default settings file: ${message}`)), }); @@ -360,7 +360,7 @@ export class ConfigurationModelParser { raw = filtered.raw; const contents = toValuesTree(raw, message => console.error(`Conflict in settings file ${this._name}: ${message}`)); const keys = Object.keys(raw); - const overrides: IOverrides[] = toOverrides(raw, message => console.error(`Conflict in settings file ${this._name}: ${message}`)); + const overrides = this.toOverrides(raw, message => console.error(`Conflict in settings file ${this._name}: ${message}`)); return { contents, keys, overrides, restricted: filtered.restricted }; } @@ -392,6 +392,24 @@ export class ConfigurationModelParser { return { raw, restricted }; } + 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)) { + const overrideRaw: any = {}; + for (const keyInOverrideRaw in raw[key]) { + overrideRaw[keyInOverrideRaw] = raw[key][keyInOverrideRaw]; + } + overrides.push({ + identifiers: overrideIdentifiersFromKey(key), + keys: Object.keys(overrideRaw), + contents: toValuesTree(overrideRaw, conflictReporter) + }); + } + } + return overrides; + } + } export class UserSettings extends Disposable { @@ -572,8 +590,7 @@ export class Configuration { compareAndUpdateDefaultConfiguration(defaults: ConfigurationModel, keys: string[]): IConfigurationChange { const overrides: [string, string[]][] = []; for (const key of keys) { - if (OVERRIDE_PROPERTY_PATTERN.test(key)) { - const overrideIdentifier = overrideIdentifierFromKey(key); + for (const overrideIdentifier of overrideIdentifiersFromKey(key)) { const fromKeys = this._defaultConfiguration.getKeysForOverrideIdentifier(overrideIdentifier); const toKeys = defaults.getKeysForOverrideIdentifier(overrideIdentifier); const keys = [ @@ -932,4 +949,3 @@ function compareConfigurationContents(to: { keys: string[], contents: any } | un } return { added, removed, updated }; } - diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 16ae308adf1a2..5d43dd41a650f 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -217,6 +217,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { this.excludedConfigurationProperties = {}; contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema); + this.registerOverridePropertyPatternKey(); } public registerConfiguration(configuration: IConfigurationNode, validate: boolean = true): void { @@ -265,7 +266,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { description: nls.localize('defaultLanguageConfiguration.description', "Configure settings to be overridden for {0} language.", key), $ref: resourceLanguageSettingsSchemaId }; - overrideIdentifiers.push(overrideIdentifierFromKey(key)); + overrideIdentifiers.push(...overrideIdentifiersFromKey(key)); this.configurationProperties[key] = property; this.defaultLanguageConfigurationOverridesNode.properties![key] = property; } else { @@ -499,6 +500,22 @@ class ConfigurationRegistry implements IConfigurationRegistry { this._onDidSchemaChange.fire(); } + private registerOverridePropertyPatternKey(): void { + const resourceLanguagePropertiesSchema: IJSONSchema = { + type: 'object', + description: nls.localize('overrideSettings.defaultDescription', "Configure editor settings to be overridden for a language."), + 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; + this._onDidSchemaChange.fire(); + } + private updatePropertyDefaultValue(key: string, property: IConfigurationPropertySchema): void { let defaultValue = this.defaultValues[key]; if (types.isUndefined(defaultValue)) { @@ -511,11 +528,24 @@ class ConfigurationRegistry implements IConfigurationRegistry { } } -const OVERRIDE_PROPERTY = '\\[.*\\]$'; +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); -export function overrideIdentifierFromKey(key: string): string { - return key.substring(1, key.length - 1); +export function overrideIdentifiersFromKey(key: string): string[] { + const identifiers: string[] = []; + if (OVERRIDE_PROPERTY_PATTERN.test(key)) { + let matches = OVERRIDE_IDENTIFIER_PATTERN.exec(key); + while (matches?.length) { + const identifier = matches[1].trim(); + if (identifier) { + identifiers.push(identifier); + } + matches = OVERRIDE_IDENTIFIER_PATTERN.exec(key); + } + } + return distinct(identifiers); } export function getDefaultValue(type: string | string[] | undefined): any { diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index ab8ae1dc7418b..406f48b809219 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -12,6 +12,35 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace'; +suite('ConfigurationModelParser', () => { + + test('parse configuration model with single override identifier', () => { + const testObject = new ConfigurationModelParser(''); + + testObject.parse(JSON.stringify({ '[x]': { 'a': 1 } })); + + assert.deepStrictEqual(JSON.stringify(testObject.configurationModel.overrides), JSON.stringify([{ identifiers: ['x'], keys: ['a'], contents: { 'a': 1 } }])); + }); + + test('parse configuration model with multiple override identifiers', () => { + const testObject = new ConfigurationModelParser(''); + + testObject.parse(JSON.stringify({ '[x][y]': { 'a': 1 } })); + + assert.deepStrictEqual(JSON.stringify(testObject.configurationModel.overrides), JSON.stringify([{ identifiers: ['x', 'y'], keys: ['a'], contents: { 'a': 1 } }])); + }); + + test('parse configuration model with multiple duplicate override identifiers', () => { + const testObject = new ConfigurationModelParser(''); + + testObject.parse(JSON.stringify({ '[x][y][x][z]': { 'a': 1 } })); + + assert.deepStrictEqual(JSON.stringify(testObject.configurationModel.overrides), JSON.stringify([{ identifiers: ['x', 'y', 'z'], keys: ['a'], contents: { 'a': 1 } }])); + }); + + +}); + suite('ConfigurationModel', () => { test('setValue for a key that has no sections and not defined', () => { @@ -621,11 +650,14 @@ suite('ConfigurationChangeEvent', () => { 'files.autoSave': 'off', '[markdown]': { 'editor.wordWrap': 'off' + }, + '[typescript][jsonc]': { + 'editor.lineNumbers': 'off' } })); let testObject = new ConfigurationChangeEvent(change, undefined, configuration); - assert.deepStrictEqual(testObject.affectedKeys, ['files.autoSave', '[markdown]', 'editor.wordWrap']); + assert.deepStrictEqual(testObject.affectedKeys, ['files.autoSave', '[markdown]', '[typescript][jsonc]', 'editor.wordWrap', 'editor.lineNumbers']); assert.ok(testObject.affectsConfiguration('files')); assert.ok(testObject.affectsConfiguration('files.autoSave')); @@ -637,8 +669,16 @@ suite('ConfigurationChangeEvent', () => { assert.ok(testObject.affectsConfiguration('editor')); assert.ok(testObject.affectsConfiguration('editor.wordWrap')); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers')); assert.ok(testObject.affectsConfiguration('editor', { overrideIdentifier: 'markdown' })); + assert.ok(testObject.affectsConfiguration('editor', { overrideIdentifier: 'jsonc' })); + assert.ok(testObject.affectsConfiguration('editor', { overrideIdentifier: 'typescript' })); assert.ok(testObject.affectsConfiguration('editor.wordWrap', { overrideIdentifier: 'markdown' })); + assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { overrideIdentifier: 'jsonc' })); + assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { overrideIdentifier: 'typescript' })); + assert.ok(!testObject.affectsConfiguration('editor.lineNumbers', { overrideIdentifier: 'markdown' })); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { overrideIdentifier: 'typescript' })); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { overrideIdentifier: 'jsonc' })); assert.ok(!testObject.affectsConfiguration('editor', { overrideIdentifier: 'json' })); assert.ok(!testObject.affectsConfiguration('editor.fontSize', { overrideIdentifier: 'markdown' })); @@ -654,6 +694,10 @@ suite('ConfigurationChangeEvent', () => { 'editor.fontSize': 12, 'editor.wordWrap': 'off' }, + '[css][scss]': { + 'editor.lineNumbers': 'off', + 'css.lint.emptyRules': 'error' + }, 'files.autoSave': 'off', })); const data = configuration.toData(); @@ -663,11 +707,15 @@ suite('ConfigurationChangeEvent', () => { 'editor.fontSize': 13, 'editor.wordWrap': 'off' }, + '[css][scss]': { + 'editor.lineNumbers': 'relative', + 'css.lint.emptyRules': 'error' + }, 'window.zoomLevel': 1, })); let testObject = new ConfigurationChangeEvent(change, { data }, configuration); - assert.deepStrictEqual(testObject.affectedKeys, ['window.zoomLevel', '[markdown]', 'workbench.editor.enablePreview', 'editor.fontSize']); + assert.deepStrictEqual(testObject.affectedKeys, ['window.zoomLevel', '[markdown]', '[css][scss]', 'workbench.editor.enablePreview', 'editor.fontSize', 'editor.lineNumbers']); assert.ok(!testObject.affectsConfiguration('files')); @@ -676,10 +724,18 @@ suite('ConfigurationChangeEvent', () => { assert.ok(!testObject.affectsConfiguration('[markdown].editor.fontSize')); assert.ok(!testObject.affectsConfiguration('[markdown].editor.wordWrap')); assert.ok(!testObject.affectsConfiguration('[markdown].workbench')); + assert.ok(testObject.affectsConfiguration('[css][scss]')); assert.ok(testObject.affectsConfiguration('editor')); assert.ok(testObject.affectsConfiguration('editor', { overrideIdentifier: 'markdown' })); + assert.ok(testObject.affectsConfiguration('editor', { overrideIdentifier: 'css' })); + assert.ok(testObject.affectsConfiguration('editor', { overrideIdentifier: 'scss' })); assert.ok(testObject.affectsConfiguration('editor.fontSize', { overrideIdentifier: 'markdown' })); + assert.ok(!testObject.affectsConfiguration('editor.fontSize', { overrideIdentifier: 'css' })); + assert.ok(!testObject.affectsConfiguration('editor.fontSize', { overrideIdentifier: 'scss' })); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { overrideIdentifier: 'scss' })); + assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { overrideIdentifier: 'css' })); + assert.ok(!testObject.affectsConfiguration('editor.lineNumbers', { overrideIdentifier: 'markdown' })); assert.ok(!testObject.affectsConfiguration('editor.wordWrap')); assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { overrideIdentifier: 'markdown' })); assert.ok(!testObject.affectsConfiguration('editor', { overrideIdentifier: 'json' })); diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index 5d483b337aa30..c0d23a31be90a 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 } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationNode, IConfigurationRegistry, Extensions, resourceLanguageSettingsSchemaId, validateProperty, ConfigurationScope, OVERRIDE_PROPERTY_PATTERN, OVERRIDE_PROPERTY } 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 this.onSettingUpdated(source));