- Introduce policy model with file policy

- Introduce policy configuration using policy model
- Use policy configuration while reading, inspecting and writing configuration
- Adopt json settings editor
This commit is contained in:
Sandeep Somavarapu
2022-05-10 13:49:31 +02:00
parent dcfe6b3b84
commit e4c04ea2e9
26 changed files with 458 additions and 121 deletions
@@ -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)
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
@@ -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>(): T;
@@ -582,6 +583,7 @@ export class StandaloneConfigurationService implements IConfigurationService {
};
return {
defaults: emptyModel,
policy: emptyModel,
user: emptyModel,
workspace: emptyModel,
folders: []
@@ -80,6 +80,7 @@ export interface IConfigurationValue<T> {
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<T> {
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][];
@@ -232,33 +232,6 @@ export class ConfigurationModel implements IConfigurationModel {
}
}
export class DefaultConfigurationModel extends ConfigurationModel {
constructor(configurationDefaultsOverrides: IStringDictionary<any> = {}) {
const properties = Registry.as<IConfigurationRegistry>(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<C>(key: string, overrides: IConfigurationOverrides, workspace: Workspace | undefined): IConfigurationValue<C> {
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<C>(key) : this._defaultConfiguration.freeze().getValue<C>(key);
const policyValue = this._policyConfiguration.freeze().getValue<C>(key);
const userValue = overrides.overrideIdentifier ? this.userConfiguration.freeze().override(overrides.overrideIdentifier).getValue<C>(key) : this.userConfiguration.freeze().getValue<C>(key);
const userLocalValue = overrides.overrideIdentifier ? this.localUserConfiguration.freeze().override(overrides.overrideIdentifier).getValue<C>(key) : this.localUserConfiguration.freeze().getValue<C>(key);
const userRemoteValue = overrides.overrideIdentifier ? this.remoteUserConfiguration.freeze().override(overrides.overrideIdentifier).getValue<C>(key) : this.remoteUserConfiguration.freeze().getValue<C>(key);
@@ -523,19 +498,21 @@ export class Configuration {
const workspaceFolderValue = folderConfigurationModel ? overrides.overrideIdentifier ? folderConfigurationModel.freeze().override(overrides.overrideIdentifier).getValue<C>(key) : folderConfigurationModel.freeze().getValue<C>(key) : undefined;
const memoryValue = overrides.overrideIdentifier ? memoryConfigurationModel.override(overrides.overrideIdentifier).getValue<C>(key) : memoryConfigurationModel.getValue<C>(key);
const value = consolidateConfigurationModel.getValue<C>(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<ConfigurationModel> = data.folders.reduce((result, value) => {
result.set(URI.revive(value[0]), this.parseConfigurationModel(value[1]));
return result;
}, new ResourceMap<ConfigurationModel>());
return new Configuration(defaultConfiguration, userConfiguration, new ConfigurationModel(), workspaceConfiguration, folders, new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), false);
return new Configuration(defaultConfiguration, policyConfiguration, userConfiguration, new ConfigurationModel(), workspaceConfiguration, folders, new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), false);
}
private static parseConfigurationModel(model: IConfigurationModel): ConfigurationModel {
@@ -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<IConfigurationChangeEvent> = this._register(new Emitter<IConfigurationChangeEvent>());
@@ -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<IConfigurationRegistry>(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<void> {
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);
}
@@ -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<ConfigurationModel> {
this._configurationModel = undefined;
this._register(Registry.as<IConfigurationRegistry>(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<any> {
return {};
}
}
export class DefaultConfigurationModel extends ConfigurationModel {
constructor(configurationDefaultsOverrides: IStringDictionary<any> = {}) {
const properties = Registry.as<IConfigurationRegistry>(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<ConfigurationModel>());
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<IConfigurationRegistry>(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<ConfigurationModel> {
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<IConfigurationRegistry>(Extensions.Configuration).getConfigurationProperties();
if (properties.some(key => configurationProperties[key].policy?.name)) {
this.onDidChange();
}
}
private onDidChange(): void {
this._onDidChangeConfiguration.fire(this.reload());
}
}
@@ -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]': {
@@ -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<void>((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<ITestSetting>();
@@ -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<ITestSetting>();
@@ -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');
@@ -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;
@@ -87,6 +87,9 @@ export interface IEnvironmentService {
telemetryLogResource: URI;
serviceMachineIdResource: URI;
// --- Policy
policyFile?: URI;
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// NOTE: KEEP THIS INTERFACE AS SMALL AS POSSIBLE.
@@ -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(
+1
View File
@@ -73,6 +73,7 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
'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.") },
+112
View File
@@ -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<PolicyValue>;
export class PolicyModel extends Disposable {
readonly _serviceBrand: undefined;
private readonly _onDidChange = new Emitter<readonly string[]>();
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<void> {
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<Policies>;
}
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<Policies> {
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<void> {
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<Policies> { return {}; }
}
@@ -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));
@@ -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);
+1 -1
View File
@@ -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();
@@ -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,
@@ -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]': {
@@ -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({
@@ -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<any> = {};
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<any> {
return this.cachedConfigurationDefaultsOverrides;
}
async initialize(): Promise<ConfigurationModel> {
override async initialize(): Promise<ConfigurationModel> {
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<void> | 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();
}
@@ -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<ConfigurationModel>(), this.workspace);
this._configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), this.workspace);
this.cachedFolderConfigs = new ResourceMap<FolderConfiguration>();
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<void>();
}
@@ -572,12 +576,18 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
private async initializeConfiguration(): Promise<void> {
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<ConfigurationModel>(), this.workspace);
this._configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, userConfigurationModel, remoteUserConfigurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), 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);
@@ -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<void> {
if (this.configurationService.inspect(operation.key).policyValue !== undefined) {
throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_POLICY_CONFIGURATION, target, operation);
}
const configurationProperties = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();
const configurationScope = configurationProperties[operation.key]?.scope;
@@ -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<ConfigurationModel>,
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 {
@@ -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<ConfigurationModel>(), new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), workspace);
const configuration1 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), 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<ConfigurationModel>(), new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), workspace);
const configuration2 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), 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<ConfigurationModel>(), new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), workspace);
const configuration1 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), 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<ConfigurationModel>(), new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), workspace);
const configuration2 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), workspace);
configuration2.updateDefaultConfiguration(defaultConfigurationModel);
configuration2.updateLocalUserConfiguration(toConfigurationModel({ 'workbench.enableTabs': true, '[typescript]': { 'editor.insertSpaces': true } }));
configuration2.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.fontSize': 11 }));