#120860 Implement trusted workspace settings

This commit is contained in:
Sandeep Somavarapu 2021-04-08 22:26:54 +02:00
parent f689971195
commit 7c26f27d90
No known key found for this signature in database
GPG key ID: 1FED25EC4646638B
10 changed files with 351 additions and 187 deletions

View file

@ -214,42 +214,53 @@ export class DefaultConfigurationModel extends ConfigurationModel {
}
}
export interface ConfigurationParseOptions {
scopes: ConfigurationScope[] | undefined;
excludeUnsafeConfigurations?: boolean;
}
export class ConfigurationModelParser {
private _raw: any = null;
private _configurationModel: ConfigurationModel | null = null;
private _excludedUnsafeConfigurations: string[] = [];
private _parseErrors: any[] = [];
constructor(protected readonly _name: string, private _scopes?: ConfigurationScope[]) { }
constructor(protected readonly _name: string) { }
get configurationModel(): ConfigurationModel {
return this._configurationModel || new ConfigurationModel();
}
get excludedUnsafeConfigurations(): string[] {
return this._excludedUnsafeConfigurations;
}
get errors(): any[] {
return this._parseErrors;
}
public parseContent(content: string | null | undefined): void {
public parse(content: string | null | undefined, options?: ConfigurationParseOptions): void {
if (!types.isUndefinedOrNull(content)) {
const raw = this.doParseContent(content);
this.parseRaw(raw);
this.parseRaw(raw, options);
}
}
public parseRaw(raw: any): void {
this._raw = raw;
const configurationModel = this.doParseRaw(raw);
this._configurationModel = new ConfigurationModel(configurationModel.contents, configurationModel.keys, configurationModel.overrides);
}
public parse(): void {
public reparse(options: ConfigurationParseOptions): void {
if (this._raw) {
this.parseRaw(this._raw);
this.parseRaw(this._raw, options);
}
}
protected doParseContent(content: string): any {
public parseRaw(raw: any, options?: ConfigurationParseOptions): void {
this._raw = raw;
const { contents, keys, overrides, unsafeConfigurations } = this.doParseRaw(raw, options);
this._configurationModel = new ConfigurationModel(contents, keys, overrides);
this._excludedUnsafeConfigurations = unsafeConfigurations || [];
}
private doParseContent(content: string): any {
let raw: any = {};
let currentProperty: string | null = null;
let currentParent: any = [];
@ -306,42 +317,49 @@ export class ConfigurationModelParser {
return raw;
}
protected doParseRaw(raw: any): IConfigurationModel {
if (this._scopes) {
const configurationProperties = Registry.as<IConfigurationRegistry>(Extensions.Configuration).getConfigurationProperties();
raw = this.filterByScope(raw, configurationProperties, true, this._scopes);
}
protected doParseRaw(raw: any, options?: ConfigurationParseOptions): IConfigurationModel & { unsafeConfigurations?: string[] } {
const configurationProperties = Registry.as<IConfigurationRegistry>(Extensions.Configuration).getConfigurationProperties();
const filtered = this.filter(raw, configurationProperties, true, options);
raw = filtered.raw;
const contents = toValuesTree(raw, message => console.error(`Conflict in settings file ${this._name}: ${message}`));
const keys = Object.keys(raw);
const overrides: IOverrides[] = toOverrides(raw, message => console.error(`Conflict in settings file ${this._name}: ${message}`));
return { contents, keys, overrides };
return { contents, keys, overrides, unsafeConfigurations: filtered.unsafeConfigurations };
}
private filterByScope(properties: any, configurationProperties: { [qualifiedKey: string]: IConfigurationPropertySchema }, filterOverriddenProperties: boolean, scopes: ConfigurationScope[]): {} {
const result: any = {};
private filter(properties: any, configurationProperties: { [qualifiedKey: string]: IConfigurationPropertySchema | undefined }, filterOverriddenProperties: boolean, options?: ConfigurationParseOptions): { raw: {}, unsafeConfigurations: string[] } {
if (!options?.scopes && !options?.excludeUnsafeConfigurations) {
return { raw: properties, unsafeConfigurations: [] };
}
const raw: any = {};
const unsafeConfigurations: string[] = [];
for (let key in properties) {
if (OVERRIDE_PROPERTY_PATTERN.test(key) && filterOverriddenProperties) {
result[key] = this.filterByScope(properties[key], configurationProperties, false, scopes);
const result = this.filter(properties[key], configurationProperties, false, options);
raw[key] = result.raw;
unsafeConfigurations.push(...result.unsafeConfigurations);
} else {
const scope = this.getScope(key, configurationProperties);
const propertySchema = configurationProperties[key];
const scope = propertySchema ? typeof propertySchema.scope !== 'undefined' ? propertySchema.scope : ConfigurationScope.WINDOW : undefined;
// Load unregistered configurations always.
if (scope === undefined || scopes.indexOf(scope) !== -1) {
result[key] = properties[key];
if (scope === undefined || options.scopes === undefined || options.scopes.includes(scope)) {
if (options.excludeUnsafeConfigurations && propertySchema?.requiresTrustedWorkspace) {
unsafeConfigurations.push(key);
} else {
raw[key] = properties[key];
}
}
}
}
return result;
return { raw, unsafeConfigurations };
}
private getScope(key: string, configurationProperties: { [qualifiedKey: string]: IConfigurationPropertySchema }): ConfigurationScope | undefined {
const propertySchema = configurationProperties[key];
return propertySchema ? typeof propertySchema.scope !== 'undefined' ? propertySchema.scope : ConfigurationScope.WINDOW : undefined;
}
}
export class UserSettings extends Disposable {
private readonly parser: ConfigurationModelParser;
private readonly parseOptions: ConfigurationParseOptions;
protected readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChange: Event<void> = this._onDidChange.event;
@ -352,7 +370,8 @@ export class UserSettings extends Disposable {
private readonly fileService: IFileService
) {
super();
this.parser = new ConfigurationModelParser(this.userSettingsResource.toString(), this.scopes);
this.parser = new ConfigurationModelParser(this.userSettingsResource.toString());
this.parseOptions = { scopes: this.scopes };
this._register(this.fileService.watch(extUri.dirname(this.userSettingsResource)));
// Also listen to the resource incase the resource is a symlink - https://github.com/microsoft/vscode/issues/118134
this._register(this.fileService.watch(this.userSettingsResource));
@ -362,15 +381,15 @@ export class UserSettings extends Disposable {
async loadConfiguration(): Promise<ConfigurationModel> {
try {
const content = await this.fileService.readFile(this.userSettingsResource);
this.parser.parseContent(content.value.toString() || '{}');
this.parser.parse(content.value.toString() || '{}', this.parseOptions);
return this.parser.configurationModel;
} catch (e) {
return new ConfigurationModel();
}
}
reprocess(): ConfigurationModel {
this.parser.parse();
reparse(): ConfigurationModel {
this.parser.reparse(this.parseOptions);
return this.parser.configurationModel;
}
}
@ -802,5 +821,4 @@ export class AllKeysConfigurationChangeEvent extends ConfigurationChangeEvent {
constructor(configuration: Configuration, workspace: Workspace, public source: ConfigurationTarget, public sourceConfig: any) {
super({ keys: configuration.allKeys(), overrides: [] }, undefined, configuration, workspace);
}
}

View file

@ -110,6 +110,7 @@ export const enum ConfigurationScope {
export interface IConfigurationPropertySchema extends IJSONSchema {
scope?: ConfigurationScope;
requiresTrustedWorkspace?: boolean;
included?: boolean;
tags?: string[];
/**
@ -136,6 +137,7 @@ export interface IConfigurationNode {
properties?: { [path: string]: IConfigurationPropertySchema; };
allOf?: IConfigurationNode[];
scope?: ConfigurationScope;
requiresTrustedWorkspace?: boolean;
extensionInfo?: IConfigurationExtensionInfo;
}
@ -297,8 +299,9 @@ class ConfigurationRegistry implements IConfigurationRegistry {
this.updateOverridePropertyPatternKey();
}
private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, scope: ConfigurationScope = ConfigurationScope.WINDOW): string[] {
private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, scope: ConfigurationScope = ConfigurationScope.WINDOW, requiresTrustedWorkspace?: boolean): string[] {
scope = types.isUndefinedOrNull(configuration.scope) ? scope : configuration.scope;
requiresTrustedWorkspace = types.isUndefinedOrNull(configuration.requiresTrustedWorkspace) ? types.isUndefined(requiresTrustedWorkspace) ? false : requiresTrustedWorkspace : configuration.requiresTrustedWorkspace;
let propertyKeys: string[] = [];
let properties = configuration.properties;
if (properties) {
@ -318,6 +321,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
property.scope = undefined; // No scope for overridable properties `[${identifier}]`
} else {
property.scope = types.isUndefinedOrNull(property.scope) ? scope : property.scope;
property.requiresTrustedWorkspace = types.isUndefinedOrNull(property.requiresTrustedWorkspace) ? requiresTrustedWorkspace : property.requiresTrustedWorkspace;
}
// Add to properties maps
@ -341,7 +345,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
let subNodes = configuration.allOf;
if (subNodes) {
for (let node of subNodes) {
propertyKeys.push(...this.validateAndRegisterProperties(node, validate, scope));
propertyKeys.push(...this.validateAndRegisterProperties(node, validate, scope, requiresTrustedWorkspace));
}
}
return propertyKeys;

View file

@ -242,10 +242,10 @@ suite('CustomConfigurationModel', () => {
test('simple merge using models', () => {
let base = new ConfigurationModelParser('base');
base.parseContent(JSON.stringify({ 'a': 1, 'b': 2 }));
base.parse(JSON.stringify({ 'a': 1, 'b': 2 }));
let add = new ConfigurationModelParser('add');
add.parseContent(JSON.stringify({ 'a': 3, 'c': 4 }));
add.parse(JSON.stringify({ 'a': 3, 'c': 4 }));
let result = base.configurationModel.merge(add.configurationModel);
assert.deepStrictEqual(result.contents, { 'a': 3, 'b': 2, 'c': 4 });
@ -253,14 +253,14 @@ suite('CustomConfigurationModel', () => {
test('simple merge with an undefined contents', () => {
let base = new ConfigurationModelParser('base');
base.parseContent(JSON.stringify({ 'a': 1, 'b': 2 }));
base.parse(JSON.stringify({ 'a': 1, 'b': 2 }));
let add = new ConfigurationModelParser('add');
let result = base.configurationModel.merge(add.configurationModel);
assert.deepStrictEqual(result.contents, { 'a': 1, 'b': 2 });
base = new ConfigurationModelParser('base');
add = new ConfigurationModelParser('add');
add.parseContent(JSON.stringify({ 'a': 1, 'b': 2 }));
add.parse(JSON.stringify({ 'a': 1, 'b': 2 }));
result = base.configurationModel.merge(add.configurationModel);
assert.deepStrictEqual(result.contents, { 'a': 1, 'b': 2 });
@ -272,25 +272,25 @@ suite('CustomConfigurationModel', () => {
test('Recursive merge using config models', () => {
let base = new ConfigurationModelParser('base');
base.parseContent(JSON.stringify({ 'a': { 'b': 1 } }));
base.parse(JSON.stringify({ 'a': { 'b': 1 } }));
let add = new ConfigurationModelParser('add');
add.parseContent(JSON.stringify({ 'a': { 'b': 2 } }));
add.parse(JSON.stringify({ 'a': { 'b': 2 } }));
let result = base.configurationModel.merge(add.configurationModel);
assert.deepStrictEqual(result.contents, { 'a': { 'b': 2 } });
});
test('Test contents while getting an existing property', () => {
let testObject = new ConfigurationModelParser('test');
testObject.parseContent(JSON.stringify({ 'a': 1 }));
testObject.parse(JSON.stringify({ 'a': 1 }));
assert.deepStrictEqual(testObject.configurationModel.getValue('a'), 1);
testObject.parseContent(JSON.stringify({ 'a': { 'b': 1 } }));
testObject.parse(JSON.stringify({ 'a': { 'b': 1 } }));
assert.deepStrictEqual(testObject.configurationModel.getValue('a'), { 'b': 1 });
});
test('Test contents are undefined for non existing properties', () => {
const testObject = new ConfigurationModelParser('test');
testObject.parseContent(JSON.stringify({
testObject.parse(JSON.stringify({
awesome: true
}));
@ -305,7 +305,7 @@ suite('CustomConfigurationModel', () => {
test('Test configWithOverrides gives all content merged with overrides', () => {
const testObject = new ConfigurationModelParser('test');
testObject.parseContent(JSON.stringify({ 'a': 1, 'c': 1, '[b]': { 'a': 2 } }));
testObject.parse(JSON.stringify({ 'a': 1, 'c': 1, '[b]': { 'a': 2 } }));
assert.deepStrictEqual(testObject.configurationModel.override('b').contents, { 'a': 2, 'c': 1, '[b]': { 'a': 2 } });
});
@ -318,17 +318,17 @@ suite('CustomConfigurationModel', () => {
test('Test update with empty data', () => {
const testObject = new ConfigurationModelParser('test');
testObject.parseContent('');
testObject.parse('');
assert.deepStrictEqual(testObject.configurationModel.contents, Object.create(null));
assert.deepStrictEqual(testObject.configurationModel.keys, []);
testObject.parseContent(null!);
testObject.parse(null!);
assert.deepStrictEqual(testObject.configurationModel.contents, Object.create(null));
assert.deepStrictEqual(testObject.configurationModel.keys, []);
testObject.parseContent(undefined!);
testObject.parse(undefined!);
assert.deepStrictEqual(testObject.configurationModel.contents, Object.create(null));
assert.deepStrictEqual(testObject.configurationModel.keys, []);
@ -380,7 +380,7 @@ suite('Configuration', () => {
test('Test update value', () => {
const parser = new ConfigurationModelParser('test');
parser.parseContent(JSON.stringify({ 'a': 1 }));
parser.parse(JSON.stringify({ 'a': 1 }));
const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel());
testObject.updateValue('a', 2);
@ -390,7 +390,7 @@ suite('Configuration', () => {
test('Test update value after inspect', () => {
const parser = new ConfigurationModelParser('test');
parser.parseContent(JSON.stringify({ 'a': 1 }));
parser.parse(JSON.stringify({ 'a': 1 }));
const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel());
testObject.inspect('a', {}, undefined);
@ -503,7 +503,7 @@ suite('Configuration', () => {
function parseConfigurationModel(content: any): ConfigurationModel {
const parser = new ConfigurationModelParser('test');
parser.parseContent(JSON.stringify(content));
parser.parse(JSON.stringify(content));
return parser.configurationModel;
}
@ -951,6 +951,6 @@ suite('AllKeysConfigurationChangeEvent', () => {
function toConfigurationModel(obj: any): ConfigurationModel {
const parser = new ConfigurationModelParser('test');
parser.parseContent(JSON.stringify(obj));
parser.parse(JSON.stringify(obj));
return parser.configurationModel;
}

View file

@ -181,10 +181,10 @@ configurationExtPoint.setHandler((extensions, { added, removed }) => {
for (let extension of added) {
const configurations: IConfigurationNode[] = [];
const value = <IConfigurationNode | IConfigurationNode[]>extension.value;
if (!Array.isArray(value)) {
configurations.push(...handleConfiguration(value, extension));
} else {
if (Array.isArray(value)) {
value.forEach(v => configurations.push(...handleConfiguration(v, extension)));
} else {
configurations.push(...handleConfiguration(value, extension));
}
extensionConfigurations.set(ExtensionIdentifier.toKey(extension.description.identifier), configurations);
addedConfigurations.push(...configurations);

View file

@ -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 } from 'vs/platform/files/common/files';
import { ConfigurationModel, ConfigurationModelParser, 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, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
@ -23,6 +23,11 @@ import { hash } from 'vs/base/common/hash';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { ILogService } from 'vs/platform/log/common/log';
import { IStringDictionary } from 'vs/base/common/collections';
import { WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust';
function hasToExcludeUnsafeConfigurations(workspaceTrustState: WorkspaceTrustState): boolean {
return workspaceTrustState !== WorkspaceTrustState.Trusted;
}
export class UserConfiguration extends Disposable {
@ -32,17 +37,20 @@ export class UserConfiguration extends Disposable {
private readonly userConfiguration: MutableDisposable<UserSettings | FileServiceBasedConfiguration> = this._register(new MutableDisposable<UserSettings | FileServiceBasedConfiguration>());
private readonly reloadConfigurationScheduler: RunOnceScheduler;
private readonly configurationParseOptions: ConfigurationParseOptions;
get hasTasksLoaded(): boolean { return this.userConfiguration.value instanceof FileServiceBasedConfiguration; }
constructor(
private readonly userSettingsResource: URI,
private readonly scopes: ConfigurationScope[] | undefined,
scopes: ConfigurationScope[] | undefined,
private readonly fileService: IFileService,
private readonly uriIdentityService: IUriIdentityService,
private readonly logService: ILogService,
) {
super();
this.userConfiguration.value = new UserSettings(this.userSettingsResource, this.scopes, uriIdentityService.extUri, this.fileService);
this.configurationParseOptions = { scopes, excludeUnsafeConfigurations: false };
this.userConfiguration.value = new UserSettings(this.userSettingsResource, scopes, uriIdentityService.extUri, this.fileService);
this._register(this.userConfiguration.value.onDidChange(() => this.reloadConfigurationScheduler.schedule()));
this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50));
}
@ -58,7 +66,7 @@ export class UserConfiguration extends Disposable {
const folder = this.uriIdentityService.extUri.dirname(this.userSettingsResource);
const standAloneConfigurationResources: [string, URI][] = [TASKS_CONFIGURATION_KEY].map(name => ([name, this.uriIdentityService.extUri.joinPath(folder, `${name}.json`)]));
const fileServiceBasedConfiguration = new FileServiceBasedConfiguration(folder.toString(), this.userSettingsResource, standAloneConfigurationResources, this.scopes, this.fileService, this.uriIdentityService, this.logService);
const fileServiceBasedConfiguration = new FileServiceBasedConfiguration(folder.toString(), this.userSettingsResource, standAloneConfigurationResources, this.configurationParseOptions, this.fileService, this.uriIdentityService, this.logService);
const configurationModel = await fileServiceBasedConfiguration.loadConfiguration();
this.userConfiguration.value = fileServiceBasedConfiguration;
@ -70,8 +78,8 @@ export class UserConfiguration extends Disposable {
return configurationModel;
}
reprocess(): ConfigurationModel {
return this.userConfiguration.value!.reprocess();
reparse(): ConfigurationModel {
return this.userConfiguration.value!.reparse(this.configurationParseOptions);
}
}
@ -79,6 +87,7 @@ class FileServiceBasedConfiguration extends Disposable {
private readonly allResources: URI[];
private _folderSettingsModelParser: ConfigurationModelParser;
private _folderSettingsParseOptions: ConfigurationParseOptions;
private _standAloneConfigurations: ConfigurationModel[];
private _cache: ConfigurationModel;
@ -89,7 +98,7 @@ class FileServiceBasedConfiguration extends Disposable {
name: string,
private readonly settingsResource: URI,
private readonly standAloneConfigurationResources: [string, URI][],
private readonly scopes: ConfigurationScope[] | undefined,
configurationParseOptions: ConfigurationParseOptions,
private readonly fileService: IFileService,
private readonly uriIdentityService: IUriIdentityService,
private readonly logService: ILogService,
@ -102,7 +111,8 @@ class FileServiceBasedConfiguration extends Disposable {
this.fileService.watch(resource)
))));
this._folderSettingsModelParser = new ConfigurationModelParser(name, this.scopes);
this._folderSettingsModelParser = new ConfigurationModelParser(name);
this._folderSettingsParseOptions = configurationParseOptions;
this._standAloneConfigurations = [];
this._cache = new ConfigurationModel();
@ -144,17 +154,17 @@ class FileServiceBasedConfiguration extends Disposable {
// reset
this._standAloneConfigurations = [];
this._folderSettingsModelParser.parseContent('');
this._folderSettingsModelParser.parse('', this._folderSettingsParseOptions);
// parse
if (settingsContent !== undefined) {
this._folderSettingsModelParser.parseContent(settingsContent);
this._folderSettingsModelParser.parse(settingsContent, this._folderSettingsParseOptions);
}
for (let index = 0; index < standAloneConfigurationContents.length; index++) {
const contents = standAloneConfigurationContents[index][1];
if (contents !== undefined) {
const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(this.standAloneConfigurationResources[index][1].toString(), this.standAloneConfigurationResources[index][0]);
standAloneConfigurationModelParser.parseContent(contents);
standAloneConfigurationModelParser.parse(contents);
this._standAloneConfigurations.push(standAloneConfigurationModelParser.configurationModel);
}
}
@ -165,9 +175,14 @@ class FileServiceBasedConfiguration extends Disposable {
return this._cache;
}
reprocess(): ConfigurationModel {
getExcludedUnsafeSettings(): string[] {
return this._folderSettingsModelParser.excludedUnsafeConfigurations;
}
reparse(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel {
const oldContents = this._folderSettingsModelParser.configurationModel.contents;
this._folderSettingsModelParser.parse();
this._folderSettingsParseOptions = configurationParseOptions;
this._folderSettingsModelParser.reparse(this._folderSettingsParseOptions);
if (!equals(oldContents, this._folderSettingsModelParser.configurationModel.contents)) {
this.consolidate();
}
@ -249,8 +264,8 @@ export class RemoteUserConfiguration extends Disposable {
return this._userConfiguration.reload();
}
reprocess(): ConfigurationModel {
return this._userConfiguration.reprocess();
reparse(): ConfigurationModel {
return this._userConfiguration.reparse();
}
private onDidUserConfigurationChange(configurationModel: ConfigurationModel): void {
@ -277,6 +292,7 @@ export class RemoteUserConfiguration extends Disposable {
class FileServiceBasedRemoteUserConfiguration extends Disposable {
private readonly parser: ConfigurationModelParser;
private readonly parseOptions: ConfigurationParseOptions;
private readonly reloadConfigurationScheduler: RunOnceScheduler;
protected readonly _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());
readonly onDidChangeConfiguration: Event<ConfigurationModel> = this._onDidChangeConfiguration.event;
@ -286,13 +302,14 @@ class FileServiceBasedRemoteUserConfiguration extends Disposable {
constructor(
private readonly configurationResource: URI,
private readonly scopes: ConfigurationScope[] | undefined,
scopes: ConfigurationScope[] | undefined,
private readonly fileService: IFileService,
private readonly uriIdentityService: IUriIdentityService,
) {
super();
this.parser = new ConfigurationModelParser(this.configurationResource.toString(), this.scopes);
this.parser = new ConfigurationModelParser(this.configurationResource.toString());
this.parseOptions = { scopes };
this._register(fileService.onDidFilesChange(e => this.handleFileEvents(e)));
this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50));
this._register(toDisposable(() => {
@ -334,15 +351,15 @@ class FileServiceBasedRemoteUserConfiguration extends Disposable {
async reload(): Promise<ConfigurationModel> {
try {
const content = await this.resolveContent();
this.parser.parseContent(content);
this.parser.parse(content, this.parseOptions);
return this.parser.configurationModel;
} catch (e) {
return new ConfigurationModel();
}
}
reprocess(): ConfigurationModel {
this.parser.parse();
reparse(): ConfigurationModel {
this.parser.reparse(this.parseOptions);
return this.parser.configurationModel;
}
@ -381,6 +398,7 @@ class CachedRemoteUserConfiguration extends Disposable {
private readonly key: ConfigurationKey;
private readonly parser: ConfigurationModelParser;
private readonly parseOptions: ConfigurationParseOptions;
private configurationModel: ConfigurationModel;
constructor(
@ -390,7 +408,8 @@ class CachedRemoteUserConfiguration extends Disposable {
) {
super();
this.key = { type: 'user', key: remoteAuthority };
this.parser = new ConfigurationModelParser('CachedRemoteUserConfiguration', scopes);
this.parser = new ConfigurationModelParser('CachedRemoteUserConfiguration');
this.parseOptions = { scopes };
this.configurationModel = new ConfigurationModel();
}
@ -402,8 +421,8 @@ class CachedRemoteUserConfiguration extends Disposable {
return this.reload();
}
reprocess(): ConfigurationModel {
this.parser.parse();
reparse(): ConfigurationModel {
this.parser.reparse(this.parseOptions);
this.configurationModel = this.parser.configurationModel;
return this.configurationModel;
}
@ -413,7 +432,7 @@ class CachedRemoteUserConfiguration extends Disposable {
const content = await this.configurationCache.read(this.key);
const parsed: { content: string } = JSON.parse(content);
if (parsed.content) {
this.parser.parseContent(parsed.content);
this.parser.parse(parsed.content, this.parseOptions);
this.configurationModel = this.parser.configurationModel;
}
} catch (e) { /* Ignore error */ }
@ -436,6 +455,7 @@ export class WorkspaceConfiguration extends Disposable {
private _workspaceConfiguration: CachedWorkspaceConfiguration | FileServiceBasedWorkspaceConfiguration;
private _workspaceConfigurationDisposables = this._register(new DisposableStore());
private _workspaceIdentifier: IWorkspaceIdentifier | null = null;
private _workspaceTrustState: WorkspaceTrustState | null = null;
private readonly _onDidUpdateConfiguration: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidUpdateConfiguration: Event<void> = this._onDidUpdateConfiguration.event;
@ -451,8 +471,9 @@ export class WorkspaceConfiguration extends Disposable {
this._workspaceConfiguration = this._cachedConfiguration = new CachedWorkspaceConfiguration(configurationCache);
}
async initialize(workspaceIdentifier: IWorkspaceIdentifier): Promise<void> {
async initialize(workspaceIdentifier: IWorkspaceIdentifier, workspaceTrustState: WorkspaceTrustState): Promise<void> {
this._workspaceIdentifier = workspaceIdentifier;
this._workspaceTrustState = workspaceTrustState;
if (!this._initialized) {
if (this.configurationCache.needsCaching(this._workspaceIdentifier.configPath)) {
this._workspaceConfiguration = this._cachedConfiguration;
@ -466,7 +487,7 @@ export class WorkspaceConfiguration extends Disposable {
async reload(): Promise<void> {
if (this._workspaceIdentifier) {
await this._workspaceConfiguration.load(this._workspaceIdentifier);
await this._workspaceConfiguration.load(this._workspaceIdentifier, { scopes: WORKSPACE_SCOPES, excludeUnsafeConfigurations: this.hasToExcludeUnsafeConfigurations() });
}
}
@ -486,16 +507,25 @@ export class WorkspaceConfiguration extends Disposable {
return this._workspaceConfiguration.getWorkspaceSettings();
}
reprocessWorkspaceSettings(): ConfigurationModel {
this._workspaceConfiguration.reprocessWorkspaceSettings();
updateWorkspaceTrustState(workspaceTrustState: WorkspaceTrustState): ConfigurationModel {
this._workspaceTrustState = workspaceTrustState;
return this.reparseWorkspaceSettings();
}
reparseWorkspaceSettings(): ConfigurationModel {
this._workspaceConfiguration.reparseWorkspaceSettings({ scopes: WORKSPACE_SCOPES, excludeUnsafeConfigurations: this.hasToExcludeUnsafeConfigurations() });
return this.getConfiguration();
}
getExcludedUnsafeSettings(): string[] {
return this._workspaceConfiguration.getExcludedUnsafeSettings();
}
private async waitAndInitialize(workspaceIdentifier: IWorkspaceIdentifier): Promise<void> {
await whenProviderRegistered(workspaceIdentifier.configPath, this._fileService);
if (!(this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration)) {
const fileServiceBasedWorkspaceConfiguration = this._register(new FileServiceBasedWorkspaceConfiguration(this._fileService));
await fileServiceBasedWorkspaceConfiguration.load(workspaceIdentifier);
await fileServiceBasedWorkspaceConfiguration.load(workspaceIdentifier, { scopes: WORKSPACE_SCOPES, excludeUnsafeConfigurations: this.hasToExcludeUnsafeConfigurations() });
this.doInitialize(fileServiceBasedWorkspaceConfiguration);
this.onDidWorkspaceConfigurationChange(false);
}
@ -508,6 +538,10 @@ export class WorkspaceConfiguration extends Disposable {
this._initialized = true;
}
private hasToExcludeUnsafeConfigurations(): boolean | undefined {
return this._workspaceTrustState !== null ? hasToExcludeUnsafeConfigurations(this._workspaceTrustState) : undefined;
}
private async onDidWorkspaceConfigurationChange(reload: boolean): Promise<void> {
if (reload) {
await this.reload();
@ -555,7 +589,7 @@ class FileServiceBasedWorkspaceConfiguration extends Disposable {
return content.value.toString();
}
async load(workspaceIdentifier: IWorkspaceIdentifier): Promise<void> {
async load(workspaceIdentifier: IWorkspaceIdentifier, configurationParseOptions: ConfigurationParseOptions): Promise<void> {
if (!this._workspaceIdentifier || this._workspaceIdentifier.id !== workspaceIdentifier.id) {
this._workspaceIdentifier = workspaceIdentifier;
this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(this._workspaceIdentifier.id);
@ -571,7 +605,7 @@ class FileServiceBasedWorkspaceConfiguration extends Disposable {
errors.onUnexpectedError(error);
}
}
this.workspaceConfigurationModelParser.parseContent(contents);
this.workspaceConfigurationModelParser.parse(contents, configurationParseOptions);
this.consolidate();
}
@ -587,12 +621,16 @@ class FileServiceBasedWorkspaceConfiguration extends Disposable {
return this.workspaceSettings;
}
reprocessWorkspaceSettings(): ConfigurationModel {
this.workspaceConfigurationModelParser.reprocessWorkspaceSettings();
reparseWorkspaceSettings(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel {
this.workspaceConfigurationModelParser.reparseWorkspaceSettings(configurationParseOptions);
this.consolidate();
return this.getWorkspaceSettings();
}
getExcludedUnsafeSettings(): string[] {
return this.workspaceConfigurationModelParser.excludedUnsafeConfigurations;
}
private consolidate(): void {
this.workspaceSettings = this.workspaceConfigurationModelParser.settingsModel.merge(this.workspaceConfigurationModelParser.launchModel, this.workspaceConfigurationModelParser.tasksModel);
}
@ -624,14 +662,14 @@ class CachedWorkspaceConfiguration {
this.workspaceSettings = new ConfigurationModel();
}
async load(workspaceIdentifier: IWorkspaceIdentifier): Promise<void> {
async load(workspaceIdentifier: IWorkspaceIdentifier, configurationParseOptions: ConfigurationParseOptions): Promise<void> {
try {
const key = this.getKey(workspaceIdentifier);
const contents = await this.configurationCache.read(key);
const parsed: { content: string } = JSON.parse(contents);
if (parsed.content) {
this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(key.key);
this.workspaceConfigurationModelParser.parseContent(parsed.content);
this.workspaceConfigurationModelParser.parse(parsed.content, configurationParseOptions);
this.consolidate();
}
} catch (e) {
@ -654,12 +692,16 @@ class CachedWorkspaceConfiguration {
return this.workspaceSettings;
}
reprocessWorkspaceSettings(): ConfigurationModel {
this.workspaceConfigurationModelParser.reprocessWorkspaceSettings();
reparseWorkspaceSettings(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel {
this.workspaceConfigurationModelParser.reparseWorkspaceSettings(configurationParseOptions);
this.consolidate();
return this.getWorkspaceSettings();
}
getExcludedUnsafeSettings(): string[] {
return this.workspaceConfigurationModelParser.excludedUnsafeConfigurations;
}
private consolidate(): void {
this.workspaceSettings = this.workspaceConfigurationModelParser.settingsModel.merge(this.workspaceConfigurationModelParser.launchModel, this.workspaceConfigurationModelParser.tasksModel);
}
@ -689,6 +731,7 @@ class CachedFolderConfiguration {
readonly onDidChange = Event.None;
private _folderSettingsModelParser: ConfigurationModelParser;
private _folderSettingsParseOptions: ConfigurationParseOptions;
private _standAloneConfigurations: ConfigurationModel[];
private configurationModel: ConfigurationModel;
private readonly key: ConfigurationKey;
@ -696,11 +739,12 @@ class CachedFolderConfiguration {
constructor(
folder: URI,
configFolderRelativePath: string,
configurationParseOptions: ConfigurationParseOptions,
private readonly configurationCache: IConfigurationCache,
scopes: ConfigurationScope[],
) {
this.key = { type: 'folder', key: hash(join(folder.path, configFolderRelativePath)).toString(16) };
this._folderSettingsModelParser = new ConfigurationModelParser('CachedFolderConfiguration', scopes);
this._folderSettingsModelParser = new ConfigurationModelParser('CachedFolderConfiguration');
this._folderSettingsParseOptions = configurationParseOptions;
this._standAloneConfigurations = [];
this.configurationModel = new ConfigurationModel();
}
@ -712,10 +756,10 @@ class CachedFolderConfiguration {
if (configurationContents) {
for (const key of Object.keys(configurationContents)) {
if (key === FOLDER_SETTINGS_NAME) {
this._folderSettingsModelParser.parseContent(configurationContents[key]);
this._folderSettingsModelParser.parse(configurationContents[key], this._folderSettingsParseOptions);
} else {
const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(key, key);
standAloneConfigurationModelParser.parseContent(configurationContents[key]);
standAloneConfigurationModelParser.parse(configurationContents[key]);
this._standAloneConfigurations.push(standAloneConfigurationModelParser.configurationModel);
}
}
@ -743,8 +787,13 @@ class CachedFolderConfiguration {
}
}
reprocess(): ConfigurationModel {
this._folderSettingsModelParser.parse();
getExcludedUnsafeSettings(): string[] {
return this._folderSettingsModelParser.excludedUnsafeConfigurations;
}
reparse(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel {
this._folderSettingsParseOptions = configurationParseOptions;
this._folderSettingsModelParser.reparse(this._folderSettingsParseOptions);
this.consolidate();
return this.configurationModel;
}
@ -764,6 +813,7 @@ export class FolderConfiguration extends Disposable {
readonly onDidChange: Event<void> = this._onDidChange.event;
private folderConfiguration: CachedFolderConfiguration | FileServiceBasedConfiguration;
private readonly scopes: ConfigurationScope[];
private readonly configurationFolder: URI;
private cachedFolderConfiguration: CachedFolderConfiguration;
@ -771,6 +821,7 @@ export class FolderConfiguration extends Disposable {
readonly workspaceFolder: IWorkspaceFolder,
configFolderRelativePath: string,
private readonly workbenchState: WorkbenchState,
private workspaceTrustState: WorkspaceTrustState,
fileService: IFileService,
uriIdentityService: IUriIdentityService,
logService: ILogService,
@ -778,19 +829,19 @@ export class FolderConfiguration extends Disposable {
) {
super();
const scopes = WorkbenchState.WORKSPACE === this.workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES;
this.scopes = WorkbenchState.WORKSPACE === this.workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES;
this.configurationFolder = uriIdentityService.extUri.joinPath(workspaceFolder.uri, configFolderRelativePath);
this.cachedFolderConfiguration = new CachedFolderConfiguration(workspaceFolder.uri, configFolderRelativePath, configurationCache, scopes);
this.cachedFolderConfiguration = new CachedFolderConfiguration(workspaceFolder.uri, configFolderRelativePath, { scopes: this.scopes, excludeUnsafeConfigurations: this.hasToExcludeUnsafeConfigurations() }, configurationCache);
if (this.configurationCache.needsCaching(workspaceFolder.uri)) {
this.folderConfiguration = this.cachedFolderConfiguration;
whenProviderRegistered(workspaceFolder.uri, fileService)
.then(() => {
this.folderConfiguration = this._register(this.createFileServiceBasedConfiguration(scopes, fileService, uriIdentityService, logService));
this.folderConfiguration = this._register(this.createFileServiceBasedConfiguration(fileService, uriIdentityService, logService));
this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange()));
this.onDidFolderConfigurationChange();
});
} else {
this.folderConfiguration = this._register(this.createFileServiceBasedConfiguration(scopes, fileService, uriIdentityService, logService));
this.folderConfiguration = this._register(this.createFileServiceBasedConfiguration(fileService, uriIdentityService, logService));
this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange()));
}
}
@ -799,21 +850,34 @@ export class FolderConfiguration extends Disposable {
return this.folderConfiguration.loadConfiguration();
}
reprocess(): ConfigurationModel {
const configurationModel = this.folderConfiguration.reprocess();
updateWorkspaceTrustState(workspaceTrustState: WorkspaceTrustState): ConfigurationModel {
this.workspaceTrustState = workspaceTrustState;
return this.reparse();
}
reparse(): ConfigurationModel {
const configurationModel = this.folderConfiguration.reparse({ scopes: this.scopes, excludeUnsafeConfigurations: this.hasToExcludeUnsafeConfigurations() });
this.updateCache();
return configurationModel;
}
getExcludedUnsafeSettings(): string[] {
return this.folderConfiguration.getExcludedUnsafeSettings();
}
private hasToExcludeUnsafeConfigurations(): boolean {
return hasToExcludeUnsafeConfigurations(this.workspaceTrustState);
}
private onDidFolderConfigurationChange(): void {
this.updateCache();
this._onDidChange.fire();
}
private createFileServiceBasedConfiguration(scopes: ConfigurationScope[], fileService: IFileService, uriIdentityService: IUriIdentityService, logService: ILogService) {
private createFileServiceBasedConfiguration(fileService: IFileService, uriIdentityService: IUriIdentityService, logService: ILogService) {
const settingsResource = uriIdentityService.extUri.joinPath(this.configurationFolder, `${FOLDER_SETTINGS_NAME}.json`);
const standAloneConfigurationResources: [string, URI][] = [TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY].map(name => ([name, uriIdentityService.extUri.joinPath(this.configurationFolder, `${name}.json`)]));
return new FileServiceBasedConfiguration(this.configurationFolder.toString(), settingsResource, standAloneConfigurationResources, scopes, fileService, uriIdentityService, logService);
return new FileServiceBasedConfiguration(this.configurationFolder.toString(), settingsResource, standAloneConfigurationResources, { scopes: this.scopes, excludeUnsafeConfigurations: this.hasToExcludeUnsafeConfigurations() }, fileService, uriIdentityService, logService);
}
private async updateCache(): Promise<void> {

View file

@ -12,7 +12,7 @@ import { Queue, Barrier, runWhenIdle, Promises } from 'vs/base/common/async';
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { IWorkspaceContextService, Workspace as BaseWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, WorkspaceFolder, toWorkspaceFolder, isWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent, mergeChanges } from 'vs/platform/configuration/common/configurationModels';
import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange, ConfigurationTargetToString } from 'vs/platform/configuration/common/configuration';
import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange, ConfigurationTargetToString, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Configuration } from 'vs/workbench/services/configuration/common/configurationModels';
import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { Registry } from 'vs/platform/registry/common/platform';
@ -32,6 +32,8 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle
import { ILogService } from 'vs/platform/log/common/log';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { IWorkspaceTrustService, WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust';
import { delta, distinct } from 'vs/base/common/arrays';
class Workspace extends BaseWorkspace {
initialized: boolean = false;
@ -58,18 +60,25 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
private readonly fileService: IFileService;
private readonly uriIdentityService: IUriIdentityService;
protected readonly _onDidChangeConfiguration: Emitter<IConfigurationChangeEvent> = this._register(new Emitter<IConfigurationChangeEvent>());
private readonly _onDidChangeConfiguration: Emitter<IConfigurationChangeEvent> = this._register(new Emitter<IConfigurationChangeEvent>());
public readonly onDidChangeConfiguration: Event<IConfigurationChangeEvent> = this._onDidChangeConfiguration.event;
protected readonly _onDidChangeWorkspaceFolders: Emitter<IWorkspaceFoldersChangeEvent> = this._register(new Emitter<IWorkspaceFoldersChangeEvent>());
private readonly _onDidChangeWorkspaceFolders: Emitter<IWorkspaceFoldersChangeEvent> = this._register(new Emitter<IWorkspaceFoldersChangeEvent>());
public readonly onDidChangeWorkspaceFolders: Event<IWorkspaceFoldersChangeEvent> = this._onDidChangeWorkspaceFolders.event;
protected readonly _onDidChangeWorkspaceName: Emitter<void> = this._register(new Emitter<void>());
private readonly _onDidChangeWorkspaceName: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidChangeWorkspaceName: Event<void> = this._onDidChangeWorkspaceName.event;
protected readonly _onDidChangeWorkbenchState: Emitter<WorkbenchState> = this._register(new Emitter<WorkbenchState>());
private readonly _onDidChangeWorkbenchState: Emitter<WorkbenchState> = this._register(new Emitter<WorkbenchState>());
public readonly onDidChangeWorkbenchState: Event<WorkbenchState> = this._onDidChangeWorkbenchState.event;
private readonly _onDidChangeUnsafeWorkspaceSettings = this._register(new Emitter<ReadonlyArray<string>>());
public readonly onDidChangeUnsafeWorkspaceSettings = this._onDidChangeUnsafeWorkspaceSettings.event;
private workspaceTrustState: WorkspaceTrustState = WorkspaceTrustState.Trusted;
private _unSafeWorkspaceSettings: string[] = [];
get unSafeWorkspaceSettings() { return this._unSafeWorkspaceSettings; }
private readonly configurationRegistry: IConfigurationRegistry;
// TODO@sandeep debt with cyclic dependencies
@ -390,6 +399,34 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
mark('code/didInitWorkspaceService');
}
updateWorkspaceTrustState(workspaceTrustState: WorkspaceTrustState): void {
if (this.workspaceTrustState !== workspaceTrustState) {
this.workspaceTrustState = workspaceTrustState;
const data = this._configuration.toData();
const folderConfigurationModels: (ConfigurationModel | undefined)[] = [];
for (const folder of this.workspace.folders) {
const folderConfiguration = this.cachedFolderConfigs.get(folder.uri);
let configurationModel: ConfigurationModel | undefined;
if (folderConfiguration) {
configurationModel = folderConfiguration.updateWorkspaceTrustState(this.workspaceTrustState);
this._configuration.updateFolderConfiguration(folder.uri, configurationModel);
}
folderConfigurationModels.push(configurationModel);
}
if (this.getWorkbenchState() === WorkbenchState.FOLDER) {
if (folderConfigurationModels[0]) {
this._configuration.updateWorkspaceConfiguration(folderConfigurationModels[0]);
}
} else {
this._configuration.updateWorkspaceConfiguration(this.workspaceConfiguration.updateWorkspaceTrustState(this.workspaceTrustState));
}
const keys = this.updateUnsafeWorkpsaceSettings();
if (keys.length) {
this.triggerConfigurationChange({ keys, overrides: [] }, { data, workspace: this.workspace }, ConfigurationTarget.WORKSPACE);
}
}
}
acquireInstantiationService(instantiationService: IInstantiationService): void {
this.configurationEditingService = instantiationService.createInstance(ConfigurationEditingService);
this.jsonEditingService = instantiationService.createInstance(JSONEditingService);
@ -401,7 +438,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
}
}
private createWorkspace(arg: IWorkspaceInitializationPayload): Promise<Workspace> {
private async createWorkspace(arg: IWorkspaceInitializationPayload): Promise<Workspace> {
if (isWorkspaceIdentifier(arg)) {
return this.createMultiFolderWorkspace(arg);
}
@ -413,22 +450,20 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
return this.createEmptyWorkspace(arg);
}
private createMultiFolderWorkspace(workspaceIdentifier: IWorkspaceIdentifier): Promise<Workspace> {
return this.workspaceConfiguration.initialize({ id: workspaceIdentifier.id, configPath: workspaceIdentifier.configPath })
.then(() => {
const workspaceConfigPath = workspaceIdentifier.configPath;
const workspaceFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), workspaceConfigPath, this.uriIdentityService.extUri);
const workspaceId = workspaceIdentifier.id;
const workspace = new Workspace(workspaceId, workspaceFolders, workspaceConfigPath, uri => this.uriIdentityService.extUri.ignorePathCasing(uri));
workspace.initialized = this.workspaceConfiguration.initialized;
return workspace;
});
private async createMultiFolderWorkspace(workspaceIdentifier: IWorkspaceIdentifier): Promise<Workspace> {
await this.workspaceConfiguration.initialize({ id: workspaceIdentifier.id, configPath: workspaceIdentifier.configPath }, this.workspaceTrustState);
const workspaceConfigPath = workspaceIdentifier.configPath;
const workspaceFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), workspaceConfigPath, this.uriIdentityService.extUri);
const workspaceId = workspaceIdentifier.id;
const workspace = new Workspace(workspaceId, workspaceFolders, workspaceConfigPath, uri => this.uriIdentityService.extUri.ignorePathCasing(uri));
workspace.initialized = this.workspaceConfiguration.initialized;
return workspace;
}
private createSingleFolderWorkspace(singleFolderWorkspaceIdentifier: ISingleFolderWorkspaceIdentifier): Promise<Workspace> {
private createSingleFolderWorkspace(singleFolderWorkspaceIdentifier: ISingleFolderWorkspaceIdentifier): Workspace {
const workspace = new Workspace(singleFolderWorkspaceIdentifier.id, [toWorkspaceFolder(singleFolderWorkspaceIdentifier.uri)], null, uri => this.uriIdentityService.extUri.ignorePathCasing(uri));
workspace.initialized = true;
return Promise.resolve(workspace);
return workspace;
}
private createEmptyWorkspace(emptyWorkspaceIdentifier: IEmptyWorkspaceIdentifier): Promise<Workspace> {
@ -505,9 +540,9 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
return result;
}
private initializeConfiguration(): Promise<void> {
return this.initializeUserConfiguration()
.then(({ local, remote }) => this.loadConfiguration(local, remote));
private async initializeConfiguration(): Promise<void> {
const { local, remote } = await this.initializeUserConfiguration();
await this.loadConfiguration(local, remote);
}
private initializeUserConfiguration(): Promise<{ local: ConfigurationModel, remote: ConfigurationModel }> {
@ -553,29 +588,29 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
return this.onWorkspaceFolderConfigurationChanged(folder);
}
private loadConfiguration(userConfigurationModel: ConfigurationModel, remoteUserConfigurationModel: ConfigurationModel): Promise<void> {
private async loadConfiguration(userConfigurationModel: ConfigurationModel, remoteUserConfigurationModel: ConfigurationModel): Promise<void> {
// reset caches
this.cachedFolderConfigs = new ResourceMap<FolderConfiguration>();
const folders = this.workspace.folders;
return this.loadFolderConfigurations(folders)
.then((folderConfigurations) => {
const folderConfigurations = await this.loadFolderConfigurations(folders);
let workspaceConfiguration = this.getWorkspaceConfigurationModel(folderConfigurations);
const folderConfigurationModels = new ResourceMap<ConfigurationModel>();
folderConfigurations.forEach((folderConfiguration, index) => folderConfigurationModels.set(folders[index].uri, folderConfiguration));
let workspaceConfiguration = this.getWorkspaceConfigurationModel(folderConfigurations);
const folderConfigurationModels = new ResourceMap<ConfigurationModel>();
folderConfigurations.forEach((folderConfiguration, index) => folderConfigurationModels.set(folders[index].uri, folderConfiguration));
const currentConfiguration = this._configuration;
this._configuration = new Configuration(this.defaultConfiguration, userConfigurationModel, remoteUserConfigurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), this.workspace);
const currentConfiguration = this._configuration;
this._configuration = new Configuration(this.defaultConfiguration, userConfigurationModel, remoteUserConfigurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), this.workspace);
if (this.initialized) {
const change = this._configuration.compare(currentConfiguration);
this.triggerConfigurationChange(change, { data: currentConfiguration.toData(), workspace: this.workspace }, ConfigurationTarget.WORKSPACE);
} else {
this._onDidChangeConfiguration.fire(new AllKeysConfigurationChangeEvent(this._configuration, this.workspace, ConfigurationTarget.WORKSPACE, this.getTargetConfiguration(ConfigurationTarget.WORKSPACE)));
this.initialized = true;
}
});
if (this.initialized) {
const change = this._configuration.compare(currentConfiguration);
this.triggerConfigurationChange(change, { data: currentConfiguration.toData(), workspace: this.workspace }, ConfigurationTarget.WORKSPACE);
} else {
this._onDidChangeConfiguration.fire(new AllKeysConfigurationChangeEvent(this._configuration, this.workspace, ConfigurationTarget.WORKSPACE, this.getTargetConfiguration(ConfigurationTarget.WORKSPACE)));
this.initialized = true;
}
this.updateUnsafeWorkpsaceSettings();
}
private getWorkspaceConfigurationModel(folderConfigurations: ConfigurationModel[]): ConfigurationModel {
@ -595,25 +630,26 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
const previousData = this._configuration.toData();
const change = this._configuration.compareAndUpdateDefaultConfiguration(this.defaultConfiguration, keys);
if (this.remoteUserConfiguration) {
this._configuration.updateLocalUserConfiguration(this.localUserConfiguration.reprocess());
this._configuration.updateRemoteUserConfiguration(this.remoteUserConfiguration.reprocess());
this._configuration.updateLocalUserConfiguration(this.localUserConfiguration.reparse());
this._configuration.updateRemoteUserConfiguration(this.remoteUserConfiguration.reparse());
}
if (this.getWorkbenchState() === WorkbenchState.FOLDER) {
const folderConfiguration = this.cachedFolderConfigs.get(this.workspace.folders[0].uri);
if (folderConfiguration) {
this._configuration.updateWorkspaceConfiguration(folderConfiguration.reprocess());
this._configuration.updateFolderConfiguration(this.workspace.folders[0].uri, folderConfiguration.reprocess());
this._configuration.updateWorkspaceConfiguration(folderConfiguration.reparse());
this._configuration.updateFolderConfiguration(this.workspace.folders[0].uri, folderConfiguration.reparse());
}
} else {
this._configuration.updateWorkspaceConfiguration(this.workspaceConfiguration.reprocessWorkspaceSettings());
this._configuration.updateWorkspaceConfiguration(this.workspaceConfiguration.reparseWorkspaceSettings());
for (const folder of this.workspace.folders) {
const folderConfiguration = this.cachedFolderConfigs.get(folder.uri);
if (folderConfiguration) {
this._configuration.updateFolderConfiguration(folder.uri, folderConfiguration.reprocess());
this._configuration.updateFolderConfiguration(folder.uri, folderConfiguration.reparse());
}
}
}
this.triggerConfigurationChange(change, { data: previousData, workspace: this.workspace }, ConfigurationTarget.DEFAULT);
this.updateUnsafeWorkpsaceSettings();
}
}
@ -651,6 +687,24 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
}
}
private updateUnsafeWorkpsaceSettings(): string[] {
let unsafeWorkspaceSettings = [];
if (this.getWorkbenchState() === WorkbenchState.WORKSPACE) {
unsafeWorkspaceSettings.push(...this.workspaceConfiguration.getExcludedUnsafeSettings());
}
for (const folderConfig of this.cachedFolderConfigs.values()) {
unsafeWorkspaceSettings.push(...folderConfig.getExcludedUnsafeSettings());
}
unsafeWorkspaceSettings = distinct(unsafeWorkspaceSettings).sort((a, b) => a.localeCompare(b));
const { removed, added } = delta(unsafeWorkspaceSettings, this._unSafeWorkspaceSettings, (a, b) => a.localeCompare(b));
const changed = [...removed, ...added];
if (changed.length) {
this._unSafeWorkspaceSettings = unsafeWorkspaceSettings;
this._onDidChangeUnsafeWorkspaceSettings.fire(this.unSafeWorkspaceSettings);
}
return changed;
}
private async updateWorkspaceConfiguration(workspaceFolders: WorkspaceFolder[], configuration: ConfigurationModel): Promise<void> {
const previous = { data: this._configuration.toData(), workspace: this.workspace };
const change = this._configuration.compareAndUpdateWorkspaceConfiguration(configuration);
@ -663,20 +717,20 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
} else {
this.triggerConfigurationChange(change, previous, ConfigurationTarget.WORKSPACE);
}
this.updateUnsafeWorkpsaceSettings();
}
private onWorkspaceFolderConfigurationChanged(folder: IWorkspaceFolder): Promise<void> {
return this.loadFolderConfigurations([folder])
.then(([folderConfiguration]) => {
const previous = { data: this._configuration.toData(), workspace: this.workspace };
const folderConfiguraitonChange = this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration);
if (this.getWorkbenchState() === WorkbenchState.FOLDER) {
const workspaceConfigurationChange = this._configuration.compareAndUpdateWorkspaceConfiguration(folderConfiguration);
this.triggerConfigurationChange(mergeChanges(folderConfiguraitonChange, workspaceConfigurationChange), previous, ConfigurationTarget.WORKSPACE);
} else {
this.triggerConfigurationChange(folderConfiguraitonChange, previous, ConfigurationTarget.WORKSPACE_FOLDER);
}
});
private async onWorkspaceFolderConfigurationChanged(folder: IWorkspaceFolder): Promise<void> {
const [folderConfiguration] = await this.loadFolderConfigurations([folder]);
const previous = { data: this._configuration.toData(), workspace: this.workspace };
const folderConfiguraitonChange = this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration);
if (this.getWorkbenchState() === WorkbenchState.FOLDER) {
const workspaceConfigurationChange = this._configuration.compareAndUpdateWorkspaceConfiguration(folderConfiguration);
this.triggerConfigurationChange(mergeChanges(folderConfiguraitonChange, workspaceConfigurationChange), previous, ConfigurationTarget.WORKSPACE);
} else {
this.triggerConfigurationChange(folderConfiguraitonChange, previous, ConfigurationTarget.WORKSPACE_FOLDER);
}
this.updateUnsafeWorkpsaceSettings();
}
private async onFoldersChanged(): Promise<IConfigurationChange> {
@ -706,7 +760,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
return Promise.all([...folders.map(folder => {
let folderConfiguration = this.cachedFolderConfigs.get(folder.uri);
if (!folderConfiguration) {
folderConfiguration = new FolderConfiguration(folder, FOLDER_CONFIG_FOLDER_NAME, this.getWorkbenchState(), this.fileService, this.uriIdentityService, this.logService, this.configurationCache);
folderConfiguration = new FolderConfiguration(folder, FOLDER_CONFIG_FOLDER_NAME, this.getWorkbenchState(), this.workspaceTrustState, this.fileService, this.uriIdentityService, this.logService, this.configurationCache);
this._register(folderConfiguration.onDidChange(() => this.onWorkspaceFolderConfigurationChanged(folder)));
this.cachedFolderConfigs.set(folder.uri, this._register(folderConfiguration));
}
@ -860,6 +914,17 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
}
}
class ConfigurationWorkspaceTrustContribution extends Disposable implements IWorkbenchContribution {
constructor(
@IWorkspaceTrustService workspaceTrustService: IWorkspaceTrustService,
@IConfigurationService configurationService: WorkspaceService
) {
super();
configurationService.updateWorkspaceTrustState(workspaceTrustService.getWorkspaceTrustState());
this._register(workspaceTrustService.onDidChangeTrustState(e => configurationService.updateWorkspaceTrustState(workspaceTrustService.getWorkspaceTrustState())));
}
}
class RegisterConfigurationSchemasContribution extends Disposable implements IWorkbenchContribution {
constructor(
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@ -900,4 +965,6 @@ class RegisterConfigurationSchemasContribution extends Disposable implements IWo
}
}
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(RegisterConfigurationSchemasContribution, LifecyclePhase.Restored);
const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchContributionsRegistry.registerWorkbenchContribution(RegisterConfigurationSchemasContribution, LifecyclePhase.Restored);
workbenchContributionsRegistry.registerWorkbenchContribution(ConfigurationWorkspaceTrustContribution, LifecyclePhase.Restored);

View file

@ -7,6 +7,7 @@ import { ConfigurationScope } from 'vs/platform/configuration/common/configurati
import { URI } from 'vs/base/common/uri';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Event } from 'vs/base/common/event';
export const FOLDER_CONFIG_FOLDER_NAME = '.vscode';
export const FOLDER_SETTINGS_NAME = 'settings';
@ -47,6 +48,16 @@ export interface IConfigurationCache {
export const IWorkbenchConfigurationService = refineServiceDecorator<IConfigurationService, IWorkbenchConfigurationService>(IConfigurationService);
export interface IWorkbenchConfigurationService extends IConfigurationService {
/**
* List of unsafe workspace settings found in current workspace
*/
readonly unSafeWorkspaceSettings: ReadonlyArray<string>;
/**
* Event that triggers when the list of unsafe workspace settings changes
*/
readonly onDidChangeUnsafeWorkspaceSettings: Event<ReadonlyArray<string>>;
/**
* A promise that resolves when the remote configuration is loaded in a remote window.
* The promise is resolved immediately if the window is not remote.

View file

@ -5,12 +5,11 @@
import { equals } from 'vs/base/common/objects';
import { toValuesTree, IConfigurationModel, IConfigurationOverrides, IConfigurationValue, IConfigurationChange } from 'vs/platform/configuration/common/configuration';
import { Configuration as BaseConfiguration, ConfigurationModelParser, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels';
import { Configuration as BaseConfiguration, ConfigurationModelParser, ConfigurationModel, ConfigurationParseOptions } from 'vs/platform/configuration/common/configurationModels';
import { IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
import { Workspace } from 'vs/platform/workspace/common/workspace';
import { ResourceMap } from 'vs/base/common/map';
import { URI } from 'vs/base/common/uri';
import { WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration';
import { OVERRIDE_PROPERTY_PATTERN, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configurationRegistry';
export class WorkspaceConfigurationModelParser extends ConfigurationModelParser {
@ -22,7 +21,7 @@ export class WorkspaceConfigurationModelParser extends ConfigurationModelParser
constructor(name: string) {
super(name);
this._settingsModelParser = new ConfigurationModelParser(name, WORKSPACE_SCOPES);
this._settingsModelParser = new ConfigurationModelParser(name);
this._launchModel = new ConfigurationModel();
this._tasksModel = new ConfigurationModel();
}
@ -43,16 +42,16 @@ export class WorkspaceConfigurationModelParser extends ConfigurationModelParser
return this._tasksModel;
}
reprocessWorkspaceSettings(): void {
this._settingsModelParser.parse();
reparseWorkspaceSettings(configurationParseOptions: ConfigurationParseOptions): void {
this._settingsModelParser.reparse(configurationParseOptions);
}
protected override doParseRaw(raw: any): IConfigurationModel {
protected override doParseRaw(raw: any, configurationParseOptions?: ConfigurationParseOptions): IConfigurationModel {
this._folders = (raw['folders'] || []) as IStoredWorkspaceFolder[];
this._settingsModelParser.parseRaw(raw['settings']);
this._settingsModelParser.parseRaw(raw['settings'], configurationParseOptions);
this._launchModel = this.createConfigurationModelFrom(raw, 'launch');
this._tasksModel = this.createConfigurationModelFrom(raw, 'tasks');
return super.doParseRaw(raw);
return super.doParseRaw(raw, configurationParseOptions);
}
private createConfigurationModelFrom(raw: any, key: string): ConfigurationModel {
@ -74,7 +73,7 @@ export class StandaloneConfigurationModelParser extends ConfigurationModelParser
super(name);
}
protected override doParseRaw(raw: any): IConfigurationModel {
protected override doParseRaw(raw: any, configurationParseOptions?: ConfigurationParseOptions): IConfigurationModel {
const contents = toValuesTree(raw, message => console.error(`Conflict in settings file ${this._name}: ${message}`));
const scopedContents = Object.create(null);
scopedContents[this.scope] = contents;

View file

@ -5,7 +5,7 @@
import * as assert from 'assert';
import { Registry } from 'vs/platform/registry/common/platform';
import { StandaloneConfigurationModelParser, Configuration } from 'vs/workbench/services/configuration/common/configurationModels';
import { ConfigurationModelParser, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels';
import { ConfigurationModelParser, ConfigurationModel, ConfigurationParseOptions } from 'vs/platform/configuration/common/configurationModels';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { ResourceMap } from 'vs/base/common/map';
import { WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
@ -49,9 +49,9 @@ suite('FolderSettingsModelParser', () => {
});
test('parse all folder settings', () => {
const testObject = new ConfigurationModelParser('settings', [ConfigurationScope.RESOURCE, ConfigurationScope.WINDOW]);
const testObject = new ConfigurationModelParser('settings');
testObject.parseContent(JSON.stringify({ 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.application': 'application', 'FolderSettingsModelParser.machine': 'executable' }));
testObject.parse(JSON.stringify({ 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.application': 'application', 'FolderSettingsModelParser.machine': 'executable' }), { scopes: [ConfigurationScope.RESOURCE, ConfigurationScope.WINDOW] });
const expected = Object.create(null);
expected['FolderSettingsModelParser'] = Object.create(null);
@ -61,9 +61,9 @@ suite('FolderSettingsModelParser', () => {
});
test('parse resource folder settings', () => {
const testObject = new ConfigurationModelParser('settings', [ConfigurationScope.RESOURCE]);
const testObject = new ConfigurationModelParser('settings');
testObject.parseContent(JSON.stringify({ 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.application': 'application', 'FolderSettingsModelParser.machine': 'executable' }));
testObject.parse(JSON.stringify({ 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.application': 'application', 'FolderSettingsModelParser.machine': 'executable' }), { scopes: [ConfigurationScope.RESOURCE] });
const expected = Object.create(null);
expected['FolderSettingsModelParser'] = Object.create(null);
@ -72,9 +72,9 @@ suite('FolderSettingsModelParser', () => {
});
test('parse resource and resource language settings', () => {
const testObject = new ConfigurationModelParser('settings', [ConfigurationScope.RESOURCE, ConfigurationScope.LANGUAGE_OVERRIDABLE]);
const testObject = new ConfigurationModelParser('settings');
testObject.parseContent(JSON.stringify({ '[json]': { 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.resourceLanguage': 'resourceLanguage', 'FolderSettingsModelParser.application': 'application', 'FolderSettingsModelParser.machine': 'executable' } }));
testObject.parse(JSON.stringify({ '[json]': { 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.resourceLanguage': 'resourceLanguage', 'FolderSettingsModelParser.application': 'application', 'FolderSettingsModelParser.machine': 'executable' } }), { scopes: [ConfigurationScope.RESOURCE, ConfigurationScope.LANGUAGE_OVERRIDABLE] });
const expected = Object.create(null);
expected['FolderSettingsModelParser'] = Object.create(null);
@ -83,10 +83,11 @@ suite('FolderSettingsModelParser', () => {
assert.deepStrictEqual(testObject.configurationModel.overrides, [{ 'contents': expected, 'identifiers': ['json'], 'keys': ['FolderSettingsModelParser.resource', 'FolderSettingsModelParser.resourceLanguage'] }]);
});
test('reprocess folder settings excludes application and machine setting', () => {
const testObject = new ConfigurationModelParser('settings', [ConfigurationScope.RESOURCE, ConfigurationScope.WINDOW]);
test('reparse folder settings excludes application and machine setting', () => {
const parseOptions: ConfigurationParseOptions = { scopes: [ConfigurationScope.RESOURCE, ConfigurationScope.WINDOW] };
const testObject = new ConfigurationModelParser('settings');
testObject.parseContent(JSON.stringify({ 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.anotherApplicationSetting': 'executable' }));
testObject.parse(JSON.stringify({ 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.anotherApplicationSetting': 'executable' }), parseOptions);
let expected = Object.create(null);
expected['FolderSettingsModelParser'] = Object.create(null);
@ -112,7 +113,7 @@ suite('FolderSettingsModelParser', () => {
}
});
testObject.parse();
testObject.reparse(parseOptions);
expected = Object.create(null);
expected['FolderSettingsModelParser'] = Object.create(null);
@ -127,7 +128,7 @@ suite('StandaloneConfigurationModelParser', () => {
test('parse tasks stand alone configuration model', () => {
const testObject = new StandaloneConfigurationModelParser('tasks', 'tasks');
testObject.parseContent(JSON.stringify({ 'version': '1.1.1', 'tasks': [] }));
testObject.parse(JSON.stringify({ 'version': '1.1.1', 'tasks': [] }));
const expected = Object.create(null);
expected['tasks'] = Object.create(null);
@ -204,6 +205,6 @@ suite('Workspace Configuration', () => {
function toConfigurationModel(obj: any): ConfigurationModel {
const parser = new ConfigurationModelParser('test');
parser.parseContent(JSON.stringify(obj));
parser.parse(JSON.stringify(obj));
return parser.configurationModel;
}

View file

@ -732,7 +732,7 @@ suite('ExtHostConfiguration', function () {
function toConfigurationModel(obj: any): ConfigurationModel {
const parser = new ConfigurationModelParser('test');
parser.parseContent(JSON.stringify(obj));
parser.parse(JSON.stringify(obj));
return parser.configurationModel;
}