diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 4dd6d42da9b..aa4c7ad67e7 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -222,7 +222,7 @@ class SharedProcessMain extends Disposable { fileService.registerProvider(Schemas.vscodeUserData, userDataFileSystemProvider); // Configuration - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService)); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, environmentService, logService)); services.set(IConfigurationService, configurationService); // Storage (global access only) diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 762f288daba..7b95aaad618 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -167,7 +167,7 @@ class CodeMain { services.set(ILoggerService, new LoggerService(logService, fileService)); // Configuration - const configurationService = new ConfigurationService(environmentMainService.settingsResource, fileService); + const configurationService = new ConfigurationService(environmentMainService.settingsResource, fileService, environmentMainService, logService); services.set(IConfigurationService, configurationService); // Lifecycle diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 9495713716e..6ae96fc2047 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -129,7 +129,7 @@ class CliMain extends Disposable { fileService.registerProvider(Schemas.file, diskFileSystemProvider); // Configuration - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService)); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, environmentService, logService)); services.set(IConfigurationService, configurationService); // Init config diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index ef4d4407aa9..52803c25a6a 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -29,7 +29,7 @@ import { IResolvedTextEditorModel, ITextModelContentProvider, ITextModelService import { ITextResourceConfigurationService, ITextResourcePropertiesService, ITextResourceConfigurationChangeEvent } from 'vs/editor/common/services/textResourceConfiguration'; import { CommandsRegistry, ICommandEvent, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationModel, IConfigurationValue, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { Configuration, ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; +import { Configuration, ConfigurationModel, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; import { IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { IConfirmation, IConfirmationResult, IDialogOptions, IDialogService, IInputResult, IShowResult } from 'vs/platform/dialogs/common/dialogs'; import { createDecorator, IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; @@ -88,6 +88,7 @@ import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/com import { staticObservableValue } from 'vs/base/common/observableValue'; import 'vs/editor/common/services/languageFeaturesService'; +import { DefaultConfigurationModel } from 'vs/platform/configuration/common/configurations'; class SimpleModel implements IResolvedTextEditorModel { @@ -521,7 +522,7 @@ export class StandaloneConfigurationService implements IConfigurationService { private readonly _configuration: Configuration; constructor() { - this._configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel()); + this._configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); } getValue(): T; @@ -582,6 +583,7 @@ export class StandaloneConfigurationService implements IConfigurationService { }; return { defaults: emptyModel, + policy: emptyModel, user: emptyModel, workspace: emptyModel, folders: [] diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 6ed2b0c3ef8..4012d405858 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -80,6 +80,7 @@ export interface IConfigurationValue { readonly workspaceValue?: T; readonly workspaceFolderValue?: T; readonly memoryValue?: T; + readonly policyValue?: T; readonly value?: T; readonly default?: { value?: T; override?: T }; @@ -89,6 +90,7 @@ export interface IConfigurationValue { readonly workspace?: { value?: T; override?: T }; readonly workspaceFolder?: { value?: T; override?: T }; readonly memory?: { value?: T; override?: T }; + readonly policy?: { value?: T }; readonly overrideIdentifiers?: string[]; } @@ -163,6 +165,7 @@ export interface IOverrides { export interface IConfigurationData { defaults: IConfigurationModel; + policy: IConfigurationModel; user: IConfigurationModel; workspace: IConfigurationModel; folders: [UriComponents, IConfigurationModel][]; diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 8afa3b7f065..03135dbdbb4 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -232,33 +232,6 @@ export class ConfigurationModel implements IConfigurationModel { } } -export class DefaultConfigurationModel extends ConfigurationModel { - - constructor(configurationDefaultsOverrides: IStringDictionary = {}) { - const properties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - const keys = Object.keys(properties); - const contents: any = Object.create(null); - const overrides: IOverrides[] = []; - - for (const key in properties) { - const defaultOverrideValue = configurationDefaultsOverrides[key]; - const value = defaultOverrideValue !== undefined ? defaultOverrideValue : properties[key].default; - addToValueTree(contents, key, value, message => console.error(`Conflict in default settings: ${message}`)); - } - for (const key of Object.keys(contents)) { - if (OVERRIDE_PROPERTY_REGEX.test(key)) { - overrides.push({ - identifiers: overrideIdentifiersFromKey(key), - keys: Object.keys(contents[key]), - contents: toValuesTree(contents[key], message => console.error(`Conflict in default settings file: ${message}`)), - }); - } - } - - super(contents, keys, overrides); - } -} - export interface ConfigurationParseOptions { scopes: ConfigurationScope[] | undefined; skipRestricted?: boolean; @@ -473,6 +446,7 @@ export class Configuration { constructor( private _defaultConfiguration: ConfigurationModel, + private _policyConfiguration: ConfigurationModel, private _localUserConfiguration: ConfigurationModel, private _remoteUserConfiguration: ConfigurationModel = new ConfigurationModel(), private _workspaceConfiguration: ConfigurationModel = new ConfigurationModel(), @@ -483,7 +457,7 @@ export class Configuration { } getValue(section: string | undefined, overrides: IConfigurationOverrides, workspace: Workspace | undefined): any { - const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides, workspace); + const consolidateConfigurationModel = this.getConsolidatedConfigurationModel(section, overrides, workspace); return consolidateConfigurationModel.getValue(section); } @@ -511,11 +485,12 @@ export class Configuration { } inspect(key: string, overrides: IConfigurationOverrides, workspace: Workspace | undefined): IConfigurationValue { - const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides, workspace); + const consolidateConfigurationModel = this.getConsolidatedConfigurationModel(key, overrides, workspace); const folderConfigurationModel = this.getFolderConfigurationModelForResource(overrides.resource, workspace); const memoryConfigurationModel = overrides.resource ? this._memoryConfigurationByResource.get(overrides.resource) || this._memoryConfiguration : this._memoryConfiguration; const defaultValue = overrides.overrideIdentifier ? this._defaultConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this._defaultConfiguration.freeze().getValue(key); + const policyValue = this._policyConfiguration.freeze().getValue(key); const userValue = overrides.overrideIdentifier ? this.userConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this.userConfiguration.freeze().getValue(key); const userLocalValue = overrides.overrideIdentifier ? this.localUserConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this.localUserConfiguration.freeze().getValue(key); const userRemoteValue = overrides.overrideIdentifier ? this.remoteUserConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this.remoteUserConfiguration.freeze().getValue(key); @@ -523,19 +498,21 @@ export class Configuration { const workspaceFolderValue = folderConfigurationModel ? overrides.overrideIdentifier ? folderConfigurationModel.freeze().override(overrides.overrideIdentifier).getValue(key) : folderConfigurationModel.freeze().getValue(key) : undefined; const memoryValue = overrides.overrideIdentifier ? memoryConfigurationModel.override(overrides.overrideIdentifier).getValue(key) : memoryConfigurationModel.getValue(key); const value = consolidateConfigurationModel.getValue(key); - const overrideIdentifiers: string[] = arrays.distinct(arrays.flatten(consolidateConfigurationModel.overrides.map(override => override.identifiers))).filter(overrideIdentifier => consolidateConfigurationModel.getOverrideValue(key, overrideIdentifier) !== undefined); + const overrideIdentifiers: string[] = arrays.distinct(consolidateConfigurationModel.overrides.map(override => override.identifiers).flat()).filter(overrideIdentifier => consolidateConfigurationModel.getOverrideValue(key, overrideIdentifier) !== undefined); return { - defaultValue: defaultValue, - userValue: userValue, - userLocalValue: userLocalValue, - userRemoteValue: userRemoteValue, - workspaceValue: workspaceValue, - workspaceFolderValue: workspaceFolderValue, - memoryValue: memoryValue, + defaultValue, + policyValue, + userValue, + userLocalValue, + userRemoteValue, + workspaceValue, + workspaceFolderValue, + memoryValue, value, default: defaultValue !== undefined ? { value: this._defaultConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this._defaultConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, + policy: policyValue !== undefined ? { value: policyValue } : undefined, user: userValue !== undefined ? { value: this.userConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this.userConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, userLocal: userLocalValue !== undefined ? { value: this.localUserConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this.localUserConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, userRemote: userRemoteValue !== undefined ? { value: this.remoteUserConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this.remoteUserConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, @@ -568,6 +545,10 @@ export class Configuration { this._foldersConsolidatedConfigurations.clear(); } + updatePolicyConfiguration(policyConfiguration: ConfigurationModel): void { + this._policyConfiguration = policyConfiguration; + } + updateLocalUserConfiguration(localUserConfiguration: ConfigurationModel): void { this._localUserConfiguration = localUserConfiguration; this._userConfiguration = null; @@ -620,6 +601,15 @@ export class Configuration { return { keys, overrides }; } + compareAndUpdatePolicyConfiguration(policyConfiguration: ConfigurationModel): IConfigurationChange { + const { added, updated, removed } = compare(this._policyConfiguration, policyConfiguration); + const keys = [...added, ...updated, ...removed]; + if (keys.length) { + this.updatePolicyConfiguration(policyConfiguration); + } + return { keys, overrides: [] }; + } + compareAndUpdateLocalUserConfiguration(user: ConfigurationModel): IConfigurationChange { const { added, updated, removed, overrides } = compare(this.localUserConfiguration, user); const keys = [...added, ...updated, ...removed]; @@ -698,9 +688,15 @@ export class Configuration { return this._folderConfigurations; } - private getConsolidateConfigurationModel(overrides: IConfigurationOverrides, workspace: Workspace | undefined): ConfigurationModel { + private getConsolidatedConfigurationModel(section: string | undefined, overrides: IConfigurationOverrides, workspace: Workspace | undefined): ConfigurationModel { let configurationModel = this.getConsolidatedConfigurationModelForResource(overrides, workspace); - return overrides.overrideIdentifier ? configurationModel.override(overrides.overrideIdentifier) : configurationModel; + if (overrides.overrideIdentifier) { + configurationModel = configurationModel.override(overrides.overrideIdentifier); + } + if (!this._policyConfiguration.isEmpty() && this._policyConfiguration.getValue(section) !== undefined) { + configurationModel = configurationModel.merge(this._policyConfiguration); + } + return configurationModel; } private getConsolidatedConfigurationModelForResource({ resource }: IConfigurationOverrides, workspace: Workspace | undefined): ConfigurationModel { @@ -765,6 +761,11 @@ export class Configuration { overrides: this._defaultConfiguration.overrides, keys: this._defaultConfiguration.keys }, + policy: { + contents: this._policyConfiguration.contents, + overrides: this._policyConfiguration.overrides, + keys: this._policyConfiguration.keys + }, user: { contents: this.userConfiguration.contents, overrides: this.userConfiguration.overrides, @@ -812,13 +813,14 @@ export class Configuration { static parse(data: IConfigurationData): Configuration { const defaultConfiguration = this.parseConfigurationModel(data.defaults); + const policyConfiguration = this.parseConfigurationModel(data.policy); const userConfiguration = this.parseConfigurationModel(data.user); const workspaceConfiguration = this.parseConfigurationModel(data.workspace); const folders: ResourceMap = data.folders.reduce((result, value) => { result.set(URI.revive(value[0]), this.parseConfigurationModel(value[1])); return result; }, new ResourceMap()); - return new Configuration(defaultConfiguration, userConfiguration, new ConfigurationModel(), workspaceConfiguration, folders, new ConfigurationModel(), new ResourceMap(), false); + return new Configuration(defaultConfiguration, policyConfiguration, userConfiguration, new ConfigurationModel(), workspaceConfiguration, folders, new ConfigurationModel(), new ResourceMap(), false); } private static parseConfigurationModel(model: IConfigurationModel): ConfigurationModel { diff --git a/src/vs/platform/configuration/common/configurationService.ts b/src/vs/platform/configuration/common/configurationService.ts index 7860c72ea8e..b3ff50a6d0c 100644 --- a/src/vs/platform/configuration/common/configurationService.ts +++ b/src/vs/platform/configuration/common/configurationService.ts @@ -9,17 +9,20 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ConfigurationTarget, IConfigurationChange, IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationValue, isConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; -import { Configuration, ConfigurationChangeEvent, ConfigurationModel, DefaultConfigurationModel, UserSettings } from 'vs/platform/configuration/common/configurationModels'; -import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { Configuration, ConfigurationChangeEvent, ConfigurationModel, UserSettings } from 'vs/platform/configuration/common/configurationModels'; +import { DefaultConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; -import { Registry } from 'vs/platform/registry/common/platform'; +import { ILogService } from 'vs/platform/log/common/log'; export class ConfigurationService extends Disposable implements IConfigurationService, IDisposable { declare readonly _serviceBrand: undefined; private configuration: Configuration; - private userConfiguration: UserSettings; + private readonly defaultConfiguration: DefaultConfiguration; + private readonly policyConfiguration: PolicyConfiguration; + private readonly userConfiguration: UserSettings; private readonly reloadConfigurationScheduler: RunOnceScheduler; private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); @@ -27,20 +30,25 @@ export class ConfigurationService extends Disposable implements IConfigurationSe constructor( private readonly settingsResource: URI, - fileService: IFileService + fileService: IFileService, + environmentService: IEnvironmentService, + logService: ILogService, ) { super(); + this.defaultConfiguration = this._register(new DefaultConfiguration()); + this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, fileService, environmentService, logService)); this.userConfiguration = this._register(new UserSettings(this.settingsResource, undefined, extUriBiasedIgnorePathCase, fileService)); - this.configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel()); + this.configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, new ConfigurationModel()); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reloadConfiguration(), 50)); - this._register(Registry.as(Extensions.Configuration).onDidUpdateConfiguration(({ properties }) => this.onDidDefaultConfigurationChange(properties))); + this._register(this.defaultConfiguration.onDidChangeConfiguration(({ defaults, properties }) => this.onDidDefaultConfigurationChange(defaults, properties))); + this._register(this.policyConfiguration.onDidChangeConfiguration(model => this.onDidPolicyConfigurationChange(model))); this._register(this.userConfiguration.onDidChange(() => this.reloadConfigurationScheduler.schedule())); } async initialize(): Promise { - const userConfiguration = await this.userConfiguration.loadConfiguration(); - this.configuration = new Configuration(new DefaultConfigurationModel(), userConfiguration); + const [defaultModel, policyModel, userModel] = await Promise.all([this.defaultConfiguration.initialize(), this.policyConfiguration.initialize(), this.userConfiguration.loadConfiguration()]); + this.configuration = new Configuration(defaultModel, policyModel, userModel); } getConfigurationData(): IConfigurationData { @@ -89,9 +97,15 @@ export class ConfigurationService extends Disposable implements IConfigurationSe this.trigger(change, previous, ConfigurationTarget.USER); } - private onDidDefaultConfigurationChange(properties: string[]): void { + private onDidDefaultConfigurationChange(defaultConfigurationModel: ConfigurationModel, properties: string[]): void { const previous = this.configuration.toData(); - const change = this.configuration.compareAndUpdateDefaultConfiguration(new DefaultConfigurationModel(), properties); + const change = this.configuration.compareAndUpdateDefaultConfiguration(defaultConfigurationModel, properties); + this.trigger(change, previous, ConfigurationTarget.DEFAULT); + } + + private onDidPolicyConfigurationChange(policyConfiguration: ConfigurationModel): void { + const previous = this.configuration.toData(); + const change = this.configuration.compareAndUpdatePolicyConfiguration(policyConfiguration); this.trigger(change, previous, ConfigurationTarget.DEFAULT); } diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts new file mode 100644 index 00000000000..a86a1f50e49 --- /dev/null +++ b/src/vs/platform/configuration/common/configurations.ts @@ -0,0 +1,143 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IStringDictionary } from 'vs/base/common/collections'; +import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { addToValueTree, IOverrides, toValuesTree } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; +import { Extensions, IConfigurationRegistry, overrideIdentifiersFromKey, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; +import { PolicyModel } from 'vs/platform/policy/common/policy'; +import { Registry } from 'vs/platform/registry/common/platform'; + +export class DefaultConfiguration extends Disposable { + + private readonly _onDidChangeConfiguration = this._register(new Emitter<{ defaults: ConfigurationModel; properties: string[] }>()); + readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; + + private _configurationModel: ConfigurationModel | undefined; + get configurationModel(): ConfigurationModel { + if (!this._configurationModel) { + this._configurationModel = new DefaultConfigurationModel(this.getConfigurationDefaultOverrides()); + } + return this._configurationModel; + } + + async initialize(): Promise { + this._configurationModel = undefined; + this._register(Registry.as(Extensions.Configuration).onDidUpdateConfiguration(({ properties, defaultsOverrides }) => this.onDidUpdateConfiguration(properties, defaultsOverrides))); + return this.configurationModel; + } + + reload(): ConfigurationModel { + this._configurationModel = undefined; + return this.configurationModel; + } + + protected onDidUpdateConfiguration(properties: string[], defaultsOverrides?: boolean): void { + this._configurationModel = undefined; + this._onDidChangeConfiguration.fire({ defaults: this.configurationModel, properties }); + } + + protected getConfigurationDefaultOverrides(): IStringDictionary { + return {}; + } + +} + +export class DefaultConfigurationModel extends ConfigurationModel { + + constructor(configurationDefaultsOverrides: IStringDictionary = {}) { + const properties = Registry.as(Extensions.Configuration).getConfigurationProperties(); + const keys = Object.keys(properties); + const contents: any = Object.create(null); + const overrides: IOverrides[] = []; + + for (const key in properties) { + const defaultOverrideValue = configurationDefaultsOverrides[key]; + const value = defaultOverrideValue !== undefined ? defaultOverrideValue : properties[key].default; + addToValueTree(contents, key, value, message => console.error(`Conflict in default settings: ${message}`)); + } + for (const key of Object.keys(contents)) { + if (OVERRIDE_PROPERTY_REGEX.test(key)) { + overrides.push({ + identifiers: overrideIdentifiersFromKey(key), + keys: Object.keys(contents[key]), + contents: toValuesTree(contents[key], message => console.error(`Conflict in default settings file: ${message}`)), + }); + } + } + + super(contents, keys, overrides); + } +} + +export class PolicyConfiguration extends Disposable { + + private readonly _onDidChangeConfiguration = this._register(new Emitter()); + readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; + + private readonly policies: PolicyModel; + + constructor( + private readonly defaultConfiguration: DefaultConfiguration, + fileService: IFileService, + environmentService: IEnvironmentService, + logService: ILogService + ) { + super(); + this.policies = new PolicyModel(fileService, environmentService, logService); + } + + private _configurationModel: ConfigurationModel | undefined; + get configurationModel(): ConfigurationModel { + if (!this._configurationModel) { + const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); + const keys: string[] = []; + const contents: any = Object.create(null); + for (const key of this.defaultConfiguration.configurationModel.keys) { + const policyName = configurationProperties[key].policy?.name; + if (!policyName) { + continue; + } + const value = this.policies.getPolicy(policyName); + if (value === undefined) { + continue; + } + keys.push(key); + addToValueTree(contents, key, value, message => console.error(`Conflict in policy settings: ${message}`)); + } + this._configurationModel = new ConfigurationModel(contents, keys, []); + } + return this._configurationModel; + } + + async initialize(): Promise { + await this.policies.initialize(); + this._register(this.policies.onDidChange(e => this.onDidChange())); + this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties }) => this.onDidDefaultConfigurationChange(properties))); + return this.reload(); + } + + reload(): ConfigurationModel { + this._configurationModel = undefined; + return this.configurationModel; + } + + private onDidDefaultConfigurationChange(properties: string[]): void { + const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); + if (properties.some(key => configurationProperties[key].policy?.name)) { + this.onDidChange(); + } + } + + private onDidChange(): void { + this._onDidChangeConfiguration.fire(this.reload()); + } + +} diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index ae1add24a78..9887f80e184 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -6,8 +6,9 @@ import * as assert from 'assert'; import { join } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { AllKeysConfigurationChangeEvent, Configuration, ConfigurationChangeEvent, ConfigurationModel, ConfigurationModelParser, DefaultConfigurationModel, mergeChanges } from 'vs/platform/configuration/common/configurationModels'; +import { AllKeysConfigurationChangeEvent, Configuration, ConfigurationChangeEvent, ConfigurationModel, ConfigurationModelParser, mergeChanges } from 'vs/platform/configuration/common/configurationModels'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { DefaultConfigurationModel } from 'vs/platform/configuration/common/configurations'; 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'; @@ -496,7 +497,7 @@ suite('Configuration', () => { test('Test update value', () => { const parser = new ConfigurationModelParser('test'); parser.parse(JSON.stringify({ 'a': 1 })); - const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel()); + const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel(), new ConfigurationModel()); testObject.updateValue('a', 2); @@ -506,7 +507,7 @@ suite('Configuration', () => { test('Test update value after inspect', () => { const parser = new ConfigurationModelParser('test'); parser.parse(JSON.stringify({ 'a': 1 })); - const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel()); + const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel(), new ConfigurationModel()); testObject.inspect('a', {}, undefined); testObject.updateValue('a', 2); @@ -515,7 +516,7 @@ suite('Configuration', () => { }); test('Test compare and update default configuration', () => { - const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); testObject.updateDefaultConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'on', })); @@ -532,7 +533,7 @@ suite('Configuration', () => { }); test('Test compare and update user configuration', () => { - const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); testObject.updateLocalUserConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'off', 'editor.fontSize': 12, @@ -555,7 +556,7 @@ suite('Configuration', () => { }); test('Test compare and update workspace configuration', () => { - const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); testObject.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'off', 'editor.fontSize': 12, @@ -578,7 +579,7 @@ suite('Configuration', () => { }); test('Test compare and update workspace folder configuration', () => { - const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); testObject.updateFolderConfiguration(URI.file('file1'), toConfigurationModel({ 'editor.lineNumbers': 'off', 'editor.fontSize': 12, @@ -601,7 +602,7 @@ suite('Configuration', () => { }); test('Test compare and delete workspace folder configuration', () => { - const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); testObject.updateFolderConfiguration(URI.file('file1'), toConfigurationModel({ 'editor.lineNumbers': 'off', 'editor.fontSize': 12, @@ -627,7 +628,7 @@ suite('Configuration', () => { suite('ConfigurationChangeEvent', () => { test('changeEvent affecting keys with new configuration', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); const change = configuration.compareAndUpdateLocalUserConfiguration(toConfigurationModel({ 'window.zoomLevel': 1, 'workbench.editor.enablePreview': false, @@ -653,7 +654,7 @@ suite('ConfigurationChangeEvent', () => { }); test('changeEvent affecting keys when configuration changed', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); configuration.updateLocalUserConfiguration(toConfigurationModel({ 'window.zoomLevel': 2, 'workbench.editor.enablePreview': true, @@ -682,7 +683,7 @@ suite('ConfigurationChangeEvent', () => { }); test('changeEvent affecting overrides with new configuration', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); const change = configuration.compareAndUpdateLocalUserConfiguration(toConfigurationModel({ 'files.autoSave': 'off', '[markdown]': { @@ -724,7 +725,7 @@ suite('ConfigurationChangeEvent', () => { }); test('changeEvent affecting overrides when configuration changed', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); configuration.updateLocalUserConfiguration(toConfigurationModel({ 'workbench.editor.enablePreview': true, '[markdown]': { @@ -791,7 +792,7 @@ suite('ConfigurationChangeEvent', () => { }); test('changeEvent affecting workspace folders', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); configuration.updateWorkspaceConfiguration(toConfigurationModel({ 'window.title': 'custom' })); configuration.updateFolderConfiguration(URI.file('folder1'), toConfigurationModel({ 'window.zoomLevel': 2, 'window.restoreFullscreen': true })); configuration.updateFolderConfiguration(URI.file('folder2'), toConfigurationModel({ 'workbench.editor.enablePreview': true, 'window.restoreWindows': true })); @@ -881,7 +882,7 @@ suite('ConfigurationChangeEvent', () => { }); test('changeEvent - all', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); configuration.updateFolderConfiguration(URI.file('file1'), toConfigurationModel({ 'window.zoomLevel': 2, 'window.restoreFullscreen': true })); const data = configuration.toData(); const change = mergeChanges( @@ -976,7 +977,7 @@ suite('ConfigurationChangeEvent', () => { }); test('changeEvent affecting tasks and launches', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); const change = configuration.compareAndUpdateLocalUserConfiguration(toConfigurationModel({ 'launch': { 'configuraiton': {} @@ -999,7 +1000,7 @@ suite('ConfigurationChangeEvent', () => { suite('AllKeysConfigurationChangeEvent', () => { test('changeEvent', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); configuration.updateDefaultConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'off', '[markdown]': { diff --git a/src/vs/platform/configuration/test/common/configurationService.test.ts b/src/vs/platform/configuration/test/common/configurationService.test.ts index 559803b0588..04635ed013c 100644 --- a/src/vs/platform/configuration/test/common/configurationService.test.ts +++ b/src/vs/platform/configuration/test/common/configurationService.test.ts @@ -12,9 +12,11 @@ import { URI } from 'vs/base/common/uri'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { NullLogService } from 'vs/platform/log/common/log'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -22,6 +24,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; suite('ConfigurationService', () => { let fileService: IFileService; + let environmentService: IEnvironmentService; let settingsResource: URI; const disposables: DisposableStore = new DisposableStore(); @@ -30,13 +33,14 @@ suite('ConfigurationService', () => { const diskFileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); settingsResource = URI.file('settings.json'); + environmentService = new TestInstantiationService().mock(IEnvironmentService) as IEnvironmentService; }); teardown(() => disposables.clear()); test('simple', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); await testObject.initialize(); const config = testObject.getValue<{ foo: string; @@ -49,7 +53,7 @@ suite('ConfigurationService', () => { test('config gets flattened', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); await testObject.initialize(); const config = testObject.getValue<{ testworkbench: { @@ -68,7 +72,7 @@ suite('ConfigurationService', () => { test('error case does not explode', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString(',,,,')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); await testObject.initialize(); const config = testObject.getValue<{ foo: string; @@ -78,7 +82,7 @@ suite('ConfigurationService', () => { }); test('missing file does not explode', async () => { - const testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService)); + const testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService, environmentService, new NullLogService())); await testObject.initialize(); const config = testObject.getValue<{ foo: string }>(); @@ -87,7 +91,7 @@ suite('ConfigurationService', () => { }); test('trigger configuration change event when file does not exist', async () => { - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); await testObject.initialize(); return new Promise((c, e) => { disposables.add(Event.filter(testObject.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(() => { @@ -100,7 +104,7 @@ suite('ConfigurationService', () => { }); test('trigger configuration change event when file exists', async () => { - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); await testObject.initialize(); @@ -116,7 +120,7 @@ suite('ConfigurationService', () => { test('reloadConfiguration', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); await testObject.initialize(); let config = testObject.getValue<{ foo: string; @@ -155,7 +159,7 @@ suite('ConfigurationService', () => { } }); - let testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService)); + let testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService, environmentService, new NullLogService())); await testObject.initialize(); let setting = testObject.getValue(); @@ -163,7 +167,7 @@ suite('ConfigurationService', () => { assert.strictEqual(setting.configuration.service.testSetting, 'isSet'); await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }')); - testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); setting = testObject.getValue(); @@ -191,7 +195,7 @@ suite('ConfigurationService', () => { } }); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); testObject.initialize(); let res = testObject.inspect('something.missing'); @@ -226,7 +230,7 @@ suite('ConfigurationService', () => { } }); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); testObject.initialize(); let res = testObject.inspect('lookup.service.testNullSetting'); diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index b2b4500a614..3834247b45b 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -88,6 +88,7 @@ export interface NativeParsedArgs { 'sync'?: 'on' | 'off'; '__sandbox'?: boolean; 'logsPath'?: string; + 'policy-file'?: string; // chromium command line args: https://electronjs.org/docs/all#supported-chrome-command-line-switches 'no-proxy-server'?: boolean; diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 56531a3a784..e0feccde448 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -87,6 +87,9 @@ export interface IEnvironmentService { telemetryLogResource: URI; serviceMachineIdResource: URI; + // --- Policy + policyFile?: URI; + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // // NOTE: KEEP THIS INTERFACE AS SMALL AS POSSIBLE. diff --git a/src/vs/platform/environment/common/environmentService.ts b/src/vs/platform/environment/common/environmentService.ts index fcea3f58023..d3214f7036f 100644 --- a/src/vs/platform/environment/common/environmentService.ts +++ b/src/vs/platform/environment/common/environmentService.ts @@ -237,6 +237,9 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron @memoize get disableWorkspaceTrust(): boolean { return !!this.args['disable-workspace-trust']; } + @memoize + get policyFile(): URI | undefined { return this.args['policy-file'] ? URI.file(this.args['policy-file']) : undefined; } + get args(): NativeParsedArgs { return this._args; } constructor( diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 062ce48a580..0a69e9cd2c4 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -73,6 +73,7 @@ export const OPTIONS: OptionDescriptions> = { 'disable-extensions': { type: 'boolean', deprecates: ['disableExtensions'], cat: 't', description: localize('disableExtensions', "Disable all installed extensions.") }, 'disable-extension': { type: 'string[]', cat: 't', args: 'ext-id', description: localize('disableExtension', "Disable an extension.") }, 'sync': { type: 'string', cat: 't', description: localize('turn sync', "Turn sync on or off."), args: ['on | off'] }, + 'policy-file': { type: 'string', cat: 't', description: localize('policyFile', "Path of the file defining policies") }, 'inspect-extensions': { type: 'string', deprecates: ['debugPluginHost'], args: 'port', cat: 't', description: localize('inspect-extensions', "Allow debugging and profiling of extensions. Check the developer tools for the connection URI.") }, 'inspect-brk-extensions': { type: 'string', deprecates: ['debugBrkPluginHost'], args: 'port', cat: 't', description: localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection URI.") }, diff --git a/src/vs/platform/policy/common/policy.ts b/src/vs/platform/policy/common/policy.ts new file mode 100644 index 00000000000..5f9b1cfafe9 --- /dev/null +++ b/src/vs/platform/policy/common/policy.ts @@ -0,0 +1,112 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ThrottledDelayer } from 'vs/base/common/async'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; + +export type PolicyValue = string | boolean | number; +export type Policies = IStringDictionary; + +export class PolicyModel extends Disposable { + readonly _serviceBrand: undefined; + + private readonly _onDidChange = new Emitter(); + readonly onDidChange = this._onDidChange.event; + + private policies: Policies = {}; + + constructor( + @IFileService private readonly fileService: IFileService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @ILogService private readonly logService: ILogService, + ) { + super(); + } + + async initialize(): Promise { + const policy = this.environmentService.policyFile ? new FilePolicy(this.environmentService.policyFile, this.fileService, this.logService) : new NullPolicy(); + this.policies = await policy.read(); + this._register(policy.onDidChange(({ changed, policies }) => { + this.policies = policies; + this._onDidChange.fire(changed); + })); + } + + getPolicy(name: string): PolicyValue | undefined { + return this.policies[name]; + } +} + +export interface IPolicy { + readonly onDidChange: Event<{ changed: string[]; policies: Policies }>; + read(): Promise; +} + +class FilePolicy extends Disposable implements IPolicy { + + private policies: Policies = {}; + + private readonly _onDidChange = new Emitter<{ changed: string[]; policies: Policies }>(); + readonly onDidChange = this._onDidChange.event; + + private readonly throttledDelayer = this._register(new ThrottledDelayer(500)); + + constructor( + private readonly file: URI, + @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService + ) { + super(); + this._register(fileService.watch(file)); + this._register(Event.filter(fileService.onDidFilesChange, e => e.affects(file))(e => this.throttledDelayer.trigger(() => this.onDidFileChange()))); + } + + async read(): Promise { + try { + const content = await this.fileService.readFile(this.file); + this.policies = JSON.parse(content.value.toString()); + } catch (error) { + this.logService.error(error); + this.policies = {}; + } + return this.policies; + } + + private async onDidFileChange(): Promise { + const old = this.policies; + await this.read(); + const changed = this.compare(old, this.policies); + if (changed.length > 0) { + this._onDidChange.fire({ changed, policies: this.policies }); + } + } + + private compare(old: Policies, current: Policies): string[] { + const changed: string[] = []; + for (const key of Object.keys(old)) { + if (old[key] !== current[key]) { + changed.push(key); + } + } + for (const key of Object.keys(current)) { + if (old[key] === undefined) { + changed.push(key); + } + } + return changed; + } + +} + +class NullPolicy extends Disposable implements IPolicy { + readonly onDidChange = Event.None; + async read(): Promise { return {}; } +} diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index 293324b83c5..5d3ba1f21bd 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -86,7 +86,7 @@ export class UserDataSyncClient extends Disposable { this.instantiationService.stub(IStorageService, this._register(new InMemoryStorageService())); - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService)); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, environmentService, logService)); await configurationService.initialize(); this.instantiationService.stub(IConfigurationService, configurationService); this.instantiationService.stub(IUriIdentityService, this.instantiationService.createInstance(UriIdentityService)); diff --git a/src/vs/server/node/remoteExtensionHostAgentCli.ts b/src/vs/server/node/remoteExtensionHostAgentCli.ts index f1e8986f5c5..170fbafa06a 100644 --- a/src/vs/server/node/remoteExtensionHostAgentCli.ts +++ b/src/vs/server/node/remoteExtensionHostAgentCli.ts @@ -91,7 +91,7 @@ class CliMain extends Disposable { fileService.registerProvider(Schemas.file, this._register(new DiskFileSystemProvider(logService))); // Configuration - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService)); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, environmentService, logService)); await configurationService.initialize(); services.set(IConfigurationService, configurationService); diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index a072fa8bb0e..a443d3bc84c 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -107,7 +107,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken services.set(IFileService, fileService); fileService.registerProvider(Schemas.file, disposables.add(new DiskFileSystemProvider(logService))); - const configurationService = new ConfigurationService(environmentService.machineSettingsResource, fileService); + const configurationService = new ConfigurationService(environmentService.machineSettingsResource, fileService, environmentService, logService); services.set(IConfigurationService, configurationService); const extensionHostStatusService = new ExtensionHostStatusService(); diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index 3a45d62f28f..0bd5f8e9752 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -256,7 +256,7 @@ export class ExtHostConfigProvider { return { key, - defaultValue: config.default?.value, + defaultValue: config.policy?.value ?? config.default?.value, globalValue: config.user?.value, workspaceValue: config.workspace?.value, workspaceFolderValue: config.workspaceFolder?.value, diff --git a/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts b/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts index 764c1c294b1..950c278a1ff 100644 --- a/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts +++ b/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts @@ -43,6 +43,7 @@ suite('ExtHostConfiguration', function () { function createConfigurationData(contents: any): IConfigurationInitData { return { defaults: new ConfigurationModel(contents), + policy: new ConfigurationModel(), user: new ConfigurationModel(contents), workspace: new ConfigurationModel(), folders: [], @@ -279,6 +280,7 @@ suite('ExtHostConfiguration', function () { 'wordWrap': 'off' } }, ['editor.wordWrap']), + policy: new ConfigurationModel(), user: new ConfigurationModel({ 'editor': { 'wordWrap': 'on' @@ -328,6 +330,7 @@ suite('ExtHostConfiguration', function () { 'wordWrap': 'off' } }, ['editor.wordWrap']), + policy: new ConfigurationModel(), user: new ConfigurationModel({ 'editor': { 'wordWrap': 'on' @@ -405,6 +408,7 @@ suite('ExtHostConfiguration', function () { 'lineNumbers': 'on' } }, ['editor.wordWrap']), + policy: new ConfigurationModel(), user: new ConfigurationModel({ 'editor': { 'wordWrap': 'on' @@ -508,6 +512,7 @@ suite('ExtHostConfiguration', function () { 'editor.wordWrap': 'bounded', } }), + policy: new ConfigurationModel(), user: toConfigurationModel({ 'editor.wordWrap': 'bounded', '[typescript]': { diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index 606a9255884..4d2b985ec74 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -175,6 +175,7 @@ class EditSettingRenderer extends Disposable { constructor(private editor: ICodeEditor, private primarySettingsModel: ISettingsEditorModel, private settingHighlighter: SettingHighlighter, + @IConfigurationService private readonly configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextMenuService private readonly contextMenuService: IContextMenuService ) { @@ -283,6 +284,9 @@ class EditSettingRenderer extends Disposable { return this.getSettingsAtLineNumber(lineNumber).filter(setting => { const configurationNode = configurationMap[setting.key]; if (configurationNode) { + if (configurationNode.policy && this.configurationService.inspect(setting.key).policyValue !== undefined) { + return false; + } if (this.isDefaultSettings()) { if (setting.key === 'launch') { // Do not show because of https://github.com/microsoft/vscode/issues/32593 @@ -522,6 +526,9 @@ class UnsupportedSettingsRenderer extends Disposable implements languages.CodeAc for (const setting of section.settings) { const configuration = configurationRegistry[setting.key]; if (configuration) { + if (this.handlePolicyConfiguration(setting, configuration, markerData)) { + continue; + } switch (this.settingsEditorModel.configurationTarget) { case ConfigurationTarget.USER_LOCAL: this.handleLocalUserConfiguration(setting, configuration, markerData); @@ -550,6 +557,25 @@ class UnsupportedSettingsRenderer extends Disposable implements languages.CodeAc return markerData; } + private handlePolicyConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): boolean { + if (!configuration.policy) { + return false; + } + if (this.configurationService.inspect(setting.key).policyValue === undefined) { + return false; + } + if (this.settingsEditorModel.configurationTarget === ConfigurationTarget.DEFAULT) { + return false; + } + markerData.push({ + severity: MarkerSeverity.Hint, + tags: [MarkerTag.Unnecessary], + ...setting.range, + message: nls.localize('unsupportedPolicySetting', "This setting cannot be applied because it is configured in the system policy.") + }); + return true; + } + private handleLocalUserConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): void { if (this.environmentService.remoteAuthority && (configuration.scope === ConfigurationScope.MACHINE || configuration.scope === ConfigurationScope.MACHINE_OVERRIDABLE)) { markerData.push({ diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 2bd9b092c9a..39ed9b5e64c 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -9,7 +9,7 @@ import * as errors from 'vs/base/common/errors'; import { Disposable, IDisposable, dispose, toDisposable, MutableDisposable, combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; import { FileChangeType, FileChangesEvent, IFileService, whenProviderRegistered, FileOperationError, FileOperationResult, FileOperation, FileOperationEvent } from 'vs/platform/files/common/files'; -import { ConfigurationModel, ConfigurationModelParser, ConfigurationParseOptions, DefaultConfigurationModel, UserSettings } from 'vs/platform/configuration/common/configurationModels'; +import { ConfigurationModel, ConfigurationModelParser, ConfigurationParseOptions, UserSettings } from 'vs/platform/configuration/common/configurationModels'; import { WorkspaceConfigurationModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels'; import { TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, REMOTE_MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; import { IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; @@ -26,8 +26,9 @@ import { joinPath } from 'vs/base/common/resources'; import { Registry } from 'vs/platform/registry/common/platform'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { isObject } from 'vs/base/common/types'; +import { DefaultConfiguration as BaseDefaultConfiguration } from 'vs/platform/configuration/common/configurations'; -export class DefaultConfiguration extends Disposable { +export class DefaultConfiguration extends BaseDefaultConfiguration { static readonly DEFAULT_OVERRIDES_CACHE_EXISTS_KEY = 'DefaultOverridesCacheExists'; @@ -35,9 +36,6 @@ export class DefaultConfiguration extends Disposable { private cachedConfigurationDefaultsOverrides: IStringDictionary = {}; private readonly cacheKey: ConfigurationKey = { type: 'defaults', key: 'configurationDefaultsOverrides' }; - private readonly _onDidChangeConfiguration = this._register(new Emitter<{ defaults: ConfigurationModel; properties: string[] }>()); - readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; - private updateCache: boolean = false; constructor( @@ -50,27 +48,20 @@ export class DefaultConfiguration extends Disposable { } } - private _configurationModel: ConfigurationModel | undefined; - get configurationModel(): ConfigurationModel { - if (!this._configurationModel) { - this._configurationModel = new DefaultConfigurationModel(this.cachedConfigurationDefaultsOverrides); - } - return this._configurationModel; + protected override getConfigurationDefaultOverrides(): IStringDictionary { + return this.cachedConfigurationDefaultsOverrides; } - async initialize(): Promise { + override async initialize(): Promise { await this.initializeCachedConfigurationDefaultsOverrides(); - this._configurationModel = undefined; - this._register(this.configurationRegistry.onDidUpdateConfiguration(({ properties, defaultsOverrides }) => this.onDidUpdateConfiguration(properties, defaultsOverrides))); - return this.configurationModel; + return super.initialize(); } - reload(): ConfigurationModel { + override reload(): ConfigurationModel { this.updateCache = true; this.cachedConfigurationDefaultsOverrides = {}; - this._configurationModel = undefined; this.updateCachedConfigurationDefaultsOverrides(); - return this.configurationModel; + return super.reload(); } private initiaizeCachedConfigurationDefaultsOverridesPromise: Promise | undefined; @@ -92,9 +83,8 @@ export class DefaultConfiguration extends Disposable { return this.initiaizeCachedConfigurationDefaultsOverridesPromise; } - private onDidUpdateConfiguration(properties: string[], defaultsOverrides?: boolean): void { - this._configurationModel = undefined; - this._onDidChangeConfiguration.fire({ defaults: this.configurationModel, properties }); + protected override onDidUpdateConfiguration(properties: string[], defaultsOverrides?: boolean): void { + super.onDidUpdateConfiguration(properties, defaultsOverrides); if (defaultsOverrides) { this.updateCachedConfigurationDefaultsOverrides(); } diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 45af17cc175..a4c11121290 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -13,6 +13,7 @@ import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/plat import { IWorkspaceContextService, Workspace as BaseWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, WorkspaceFolder, toWorkspaceFolder, isWorkspaceFolder, IWorkspaceFoldersWillChangeEvent, IEmptyWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier, IAnyWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { ConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent, mergeChanges } from 'vs/platform/configuration/common/configurationModels'; import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange, ConfigurationTargetToString, IConfigurationUpdateOverrides, isConfigurationUpdateOverrides, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; 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'; @@ -54,7 +55,8 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat private readonly configurationCache: IConfigurationCache; private _configuration: Configuration; private initialized: boolean = false; - private defaultConfiguration: DefaultConfiguration; + private readonly defaultConfiguration: DefaultConfiguration; + private readonly policyConfiguration: PolicyConfiguration; private localUserConfiguration: UserConfiguration; private remoteUserConfiguration: RemoteUserConfiguration | null = null; private workspaceConfiguration: WorkspaceConfiguration; @@ -109,12 +111,13 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat this.initRemoteUserConfigurationBarrier = new Barrier(); this.completeWorkspaceBarrier = new Barrier(); - this.defaultConfiguration = new DefaultConfiguration(configurationCache, environmentService); + this.defaultConfiguration = this._register(new DefaultConfiguration(configurationCache, environmentService)); + this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, fileService, environmentService, logService)); this.configurationCache = configurationCache; this.fileService = fileService; this.uriIdentityService = uriIdentityService; this.logService = logService; - this._configuration = new Configuration(this.defaultConfiguration.configurationModel, new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), this.workspace); + this._configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), this.workspace); this.cachedFolderConfigs = new ResourceMap(); this.localUserConfiguration = this._register(new UserConfiguration(environmentService.settingsResource, remoteAuthority ? LOCAL_MACHINE_SCOPES : undefined, fileService, uriIdentityService, logService)); this._register(this.localUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onLocalUserConfigurationChanged(userConfiguration))); @@ -138,6 +141,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat })); this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties, defaults }) => this.onDefaultConfigurationChanged(defaults, properties))); + this._register(this.policyConfiguration.onDidChangeConfiguration(configurationModel => this.onPolicyConfigurationChanged(configurationModel))); this.workspaceEditingQueue = new Queue(); } @@ -572,12 +576,18 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat private async initializeConfiguration(): Promise { await this.defaultConfiguration.initialize(); - mark('code/willInitUserConfiguration'); - const { local, remote } = await this.initializeUserConfiguration(); - mark('code/didInitUserConfiguration'); + const [, user] = await Promise.all([ + this.policyConfiguration.initialize(), + (async () => { + mark('code/willInitUserConfiguration'); + const result = await this.initializeUserConfiguration(); + mark('code/didInitUserConfiguration'); + return result; + })() + ]); mark('code/willInitWorkspaceConfiguration'); - await this.loadConfiguration(local, remote); + await this.loadConfiguration(user.local, user.remote); mark('code/didInitWorkspaceConfiguration'); } @@ -640,7 +650,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat folderConfigurations.forEach((folderConfiguration, index) => folderConfigurationModels.set(folders[index].uri, folderConfiguration)); const currentConfiguration = this._configuration; - this._configuration = new Configuration(this.defaultConfiguration.configurationModel, userConfigurationModel, remoteUserConfigurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap(), this.workspace); + this._configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, userConfigurationModel, remoteUserConfigurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap(), this.workspace); if (this.initialized) { const change = this._configuration.compare(currentConfiguration); @@ -692,6 +702,12 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } } + private onPolicyConfigurationChanged(policyConfiguration: ConfigurationModel): void { + const previous = { data: this._configuration.toData(), workspace: this.workspace }; + const change = this._configuration.compareAndUpdatePolicyConfiguration(policyConfiguration); + this.triggerConfigurationChange(change, previous, ConfigurationTarget.DEFAULT); + } + private onLocalUserConfigurationChanged(userConfiguration: ConfigurationModel): void { const previous = { data: this._configuration.toData(), workspace: this.workspace }; const change = this._configuration.compareAndUpdateLocalUserConfiguration(userConfiguration); diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index 3e4e805c231..bf0d5f3adde 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -92,6 +92,11 @@ export const enum ConfigurationEditingErrorCode { */ ERROR_INVALID_CONFIGURATION, + /** + * Error when trying to write a policy configuration + */ + ERROR_POLICY_CONFIGURATION, + /** * Internal Error. */ @@ -359,6 +364,7 @@ export class ConfigurationEditingService { switch (error) { // API constraints + case ConfigurationEditingErrorCode.ERROR_POLICY_CONFIGURATION: return nls.localize('errorPolicyConfiguration', "Unable to write {0} because it is configured in system policy.", operation.key); case 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); case 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); case 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); @@ -492,6 +498,10 @@ export class ConfigurationEditingService { private async validate(target: EditableConfigurationTarget, operation: IConfigurationEditOperation, checkDirty: boolean, overrides: IConfigurationUpdateOverrides): Promise { + if (this.configurationService.inspect(operation.key).policyValue !== undefined) { + throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_POLICY_CONFIGURATION, target, operation); + } + const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); const configurationScope = configurationProperties[operation.key]?.scope; diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index 95a1d40ad77..807e96399df 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -98,6 +98,7 @@ export class Configuration extends BaseConfiguration { constructor( defaults: ConfigurationModel, + policy: ConfigurationModel, localUser: ConfigurationModel, remoteUser: ConfigurationModel, workspaceConfiguration: ConfigurationModel, @@ -105,7 +106,7 @@ export class Configuration extends BaseConfiguration { memoryConfiguration: ConfigurationModel, memoryConfigurationByResource: ResourceMap, private readonly _workspace?: Workspace) { - super(defaults, localUser, remoteUser, workspaceConfiguration, folders, memoryConfiguration, memoryConfigurationByResource); + super(defaults, policy, localUser, remoteUser, workspaceConfiguration, folders, memoryConfiguration, memoryConfigurationByResource); } override getValue(key: string | undefined, overrides: IConfigurationOverrides = {}): any { diff --git a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts index 228cc13113a..7f391a11ad3 100644 --- a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts +++ b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts @@ -155,14 +155,14 @@ suite('Workspace Configuration', () => { test('Test compare same configurations', () => { const workspace = new Workspace('a', [new WorkspaceFolder({ index: 0, name: 'a', uri: URI.file('folder1') }), new WorkspaceFolder({ index: 1, name: 'b', uri: URI.file('folder2') }), new WorkspaceFolder({ index: 2, name: 'c', uri: URI.file('folder3') })]); - const configuration1 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); + const configuration1 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); configuration1.updateDefaultConfiguration(defaultConfigurationModel); configuration1.updateLocalUserConfiguration(toConfigurationModel({ 'window.title': 'native', '[typescript]': { 'editor.insertSpaces': false } })); configuration1.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'on' })); configuration1.updateFolderConfiguration(URI.file('folder1'), toConfigurationModel({ 'editor.fontSize': 14 })); configuration1.updateFolderConfiguration(URI.file('folder2'), toConfigurationModel({ 'editor.wordWrap': 'on' })); - const configuration2 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); + const configuration2 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); configuration2.updateDefaultConfiguration(defaultConfigurationModel); configuration2.updateLocalUserConfiguration(toConfigurationModel({ 'window.title': 'native', '[typescript]': { 'editor.insertSpaces': false } })); configuration2.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'on' })); @@ -176,14 +176,14 @@ suite('Workspace Configuration', () => { test('Test compare different configurations', () => { const workspace = new Workspace('a', [new WorkspaceFolder({ index: 0, name: 'a', uri: URI.file('folder1') }), new WorkspaceFolder({ index: 1, name: 'b', uri: URI.file('folder2') }), new WorkspaceFolder({ index: 2, name: 'c', uri: URI.file('folder3') })]); - const configuration1 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); + const configuration1 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); configuration1.updateDefaultConfiguration(defaultConfigurationModel); configuration1.updateLocalUserConfiguration(toConfigurationModel({ 'window.title': 'native', '[typescript]': { 'editor.insertSpaces': false } })); configuration1.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'on' })); configuration1.updateFolderConfiguration(URI.file('folder1'), toConfigurationModel({ 'editor.fontSize': 14 })); configuration1.updateFolderConfiguration(URI.file('folder2'), toConfigurationModel({ 'editor.wordWrap': 'on' })); - const configuration2 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); + const configuration2 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); configuration2.updateDefaultConfiguration(defaultConfigurationModel); configuration2.updateLocalUserConfiguration(toConfigurationModel({ 'workbench.enableTabs': true, '[typescript]': { 'editor.insertSpaces': true } })); configuration2.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.fontSize': 11 }));