diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 738cf077e5f..c08aceb3d2c 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -3072,8 +3072,11 @@ declare module 'vscode' { * @param section Configuration name, supports _dotted_ names. * @param value The new value. * @param configurationTarget The [configuration target](#ConfigurationTarget) or a boolean value. - * If `undefined` or `null` or `false` configuration target is `ConfigurationTarget.Workspace`. - * If `true` configuration target is `ConfigurationTarget.Global`. + * - If `true` configuration target is `ConfigurationTarget.Global`. + * - If `false` configuration target is `ConfigurationTarget.Workspace`. + * - If `undefined` or `null` configuration target is + * `ConfigurationTarget.WorkspaceFolder` when configuration is resource specific + * `ConfigurationTarget.Workspace` otherwise. */ update(section: string, value: any, configurationTarget?: ConfigurationTarget | boolean): Thenable; diff --git a/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts b/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts index d13e22840aa..d1eb9a7daf6 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts @@ -7,6 +7,9 @@ import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; import { MainThreadConfigurationShape, MainContext, ExtHostContext, IExtHostContext } from '../node/extHost.protocol'; @@ -15,15 +18,14 @@ import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostC @extHostNamedCustomer(MainContext.MainThreadConfiguration) export class MainThreadConfiguration implements MainThreadConfigurationShape { - private readonly _configurationEditingService: IConfigurationEditingService; private readonly _configurationListener: IDisposable; constructor( extHostContext: IExtHostContext, - @IConfigurationEditingService configurationEditingService: IConfigurationEditingService, + @IConfigurationEditingService private readonly _configurationEditingService: IConfigurationEditingService, + @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, @IWorkspaceConfigurationService configurationService: IWorkspaceConfigurationService ) { - this._configurationEditingService = configurationEditingService; const proxy = extHostContext.get(ExtHostContext.ExtHostConfiguration); this._configurationListener = configurationService.onDidUpdateConfiguration(() => { @@ -36,10 +38,24 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape { } $updateConfigurationOption(target: ConfigurationTarget, key: string, value: any, resource: URI): TPromise { - return this._configurationEditingService.writeConfiguration(target, { key, value }, { donotNotifyError: true, scopes: { resource } }); + return this.writeConfiguration(target, key, value, resource); } $removeConfigurationOption(target: ConfigurationTarget, key: string, resource: URI): TPromise { - return this._configurationEditingService.writeConfiguration(target, { key, value: undefined }, { donotNotifyError: true, scopes: { resource } }); + return this.writeConfiguration(target, key, undefined, resource); + } + + private writeConfiguration(target: ConfigurationTarget, key: string, value: any, resource: URI): TPromise { + return this._configurationEditingService.writeConfiguration(target ? target : this.deriveConfigurationTarget(key, resource), { key, value }, { donotNotifyError: true, scopes: { resource } }); + } + + private deriveConfigurationTarget(key: string, resource: URI): ConfigurationTarget { + if (resource && this._workspaceContextService.hasMultiFolderWorkspace()) { + const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); + if (configurationProperties[key] && configurationProperties[key].scope === ConfigurationScope.RESOURCE) { + return ConfigurationTarget.FOLDER; + } + } + return ConfigurationTarget.WORKSPACE; } } diff --git a/src/vs/workbench/api/node/extHostConfiguration.ts b/src/vs/workbench/api/node/extHostConfiguration.ts index f12546ed3da..931b01fc875 100644 --- a/src/vs/workbench/api/node/extHostConfiguration.ts +++ b/src/vs/workbench/api/node/extHostConfiguration.ts @@ -62,7 +62,7 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { function parseConfigurationTarget(arg: boolean | ExtHostConfigurationTarget): ConfigurationTarget { if (arg === void 0 || arg === null) { - return ConfigurationTarget.WORKSPACE; + return null; } if (typeof arg === 'boolean') { return arg ? ConfigurationTarget.USER : ConfigurationTarget.WORKSPACE; diff --git a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts index bfc78fb68cf..8f7c6e4163f 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts @@ -336,6 +336,21 @@ suite('ExtHostConfiguration', function () { assert.throws(() => config['get'] = 'get-prop'); }); + test('update: no target passes null', function () { + const shape = new RecordingShape(); + const allConfig = createExtHostConfiguration({ + 'foo': { + 'bar': 1, + 'far': 1 + } + }, shape); + + let config = allConfig.getConfiguration('foo'); + config.update('bar', 42); + + assert.equal(shape.lastArgs[0], null); + }); + test('update/section to key', function () { const shape = new RecordingShape(); @@ -349,6 +364,7 @@ suite('ExtHostConfiguration', function () { let config = allConfig.getConfiguration('foo'); config.update('bar', 42, true); + assert.equal(shape.lastArgs[0], ConfigurationTarget.USER); assert.equal(shape.lastArgs[1], 'foo.bar'); assert.equal(shape.lastArgs[2], 42); diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts new file mode 100644 index 00000000000..694d441b86f --- /dev/null +++ b/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts @@ -0,0 +1,210 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import URI from 'vs/base/common/uri'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions, IConfigurationRegistry, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { MainThreadConfiguration } from 'vs/workbench/api/electron-browser/mainThreadConfiguration'; +import { ConfigurationTarget, IConfigurationEditingService } from 'vs/workbench/services/configuration/common/configurationEditing'; +import { ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService'; +import { OneGetThreadService } from './testThreadService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; + +suite('ExtHostConfiguration', function () { + + let instantiationService: TestInstantiationService; + let target: sinon.SinonSpy; + + suiteSetup(() => { + Registry.as(Extensions.Configuration).registerConfiguration({ + 'id': 'extHostConfiguration', + 'title': 'a', + 'type': 'object', + 'properties': { + 'extHostConfiguration.resource': { + 'description': 'extHostConfiguration.resource', + 'type': 'boolean', + 'default': true, + 'scope': ConfigurationScope.RESOURCE + }, + 'extHostConfiguration.window': { + 'description': 'extHostConfiguration.resource', + 'type': 'boolean', + 'default': true, + 'scope': ConfigurationScope.WINDOW + } + } + }); + }); + + setup(() => { + instantiationService = new TestInstantiationService(); + instantiationService.stub(IConfigurationService, new TestConfigurationService()); + + target = sinon.spy(); + instantiationService.stub(IConfigurationEditingService, ConfigurationEditingService); + instantiationService.stub(IConfigurationEditingService, 'writeConfiguration', target); + }); + + test('update resource configuration without configuration target defaults to workspace in multi root workspace when no resource is provided', function () { + instantiationService.stub(IWorkspaceContextService, { hasMultiFolderWorkspace: () => true }); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + + testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', null); + + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + }); + + test('update resource configuration without configuration target defaults to workspace in folder workspace when resource is provider', function () { + instantiationService.stub(IWorkspaceContextService, { hasMultiFolderWorkspace: () => false }); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + + testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', URI.file('abc')); + + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + }); + + test('update resource configuration without configuration target defaults to workspace in folder workspace when no resource is provider', function () { + instantiationService.stub(IWorkspaceContextService, { hasMultiFolderWorkspace: () => false }); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + + testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', null); + + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + }); + + test('update window configuration without configuration target defaults to workspace in multi root workspace when no resource is provided', function () { + instantiationService.stub(IWorkspaceContextService, { hasMultiFolderWorkspace: () => true }); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + + testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', null); + + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + }); + + test('update window configuration without configuration target defaults to workspace in multi root workspace when resource is provided', function () { + instantiationService.stub(IWorkspaceContextService, { hasMultiFolderWorkspace: () => true }); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + + testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', URI.file('abc')); + + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + }); + + test('update window configuration without configuration target defaults to workspace in folder workspace when resource is provider', function () { + instantiationService.stub(IWorkspaceContextService, { hasMultiFolderWorkspace: () => false }); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + + testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', URI.file('abc')); + + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + }); + + test('update window configuration without configuration target defaults to workspace in folder workspace when no resource is provider', function () { + instantiationService.stub(IWorkspaceContextService, { hasMultiFolderWorkspace: () => false }); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + + testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', null); + + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + }); + + test('update resource configuration without configuration target defaults to folder', function () { + instantiationService.stub(IWorkspaceContextService, { hasMultiFolderWorkspace: () => true }); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + + testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', URI.file('abc')); + + assert.equal(ConfigurationTarget.FOLDER, target.args[0][0]); + }); + + test('update configuration with configuration target', function () { + instantiationService.stub(IWorkspaceContextService, { hasMultiFolderWorkspace: () => false }); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + + testObject.$updateConfigurationOption(ConfigurationTarget.FOLDER, 'extHostConfiguration.window', 'value', URI.file('abc')); + + assert.equal(ConfigurationTarget.FOLDER, target.args[0][0]); + }); + + test('remove resource configuration without configuration target defaults to workspace in multi root workspace when no resource is provided', function () { + instantiationService.stub(IWorkspaceContextService, { hasMultiFolderWorkspace: () => true }); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + + testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', null); + + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + }); + + test('remove resource configuration without configuration target defaults to workspace in folder workspace when resource is provider', function () { + instantiationService.stub(IWorkspaceContextService, { hasMultiFolderWorkspace: () => false }); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + + testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', URI.file('abc')); + + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + }); + + test('remove resource configuration without configuration target defaults to workspace in folder workspace when no resource is provider', function () { + instantiationService.stub(IWorkspaceContextService, { hasMultiFolderWorkspace: () => false }); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + + testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', null); + + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + }); + + test('remove window configuration without configuration target defaults to workspace in multi root workspace when no resource is provided', function () { + instantiationService.stub(IWorkspaceContextService, { hasMultiFolderWorkspace: () => true }); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + + testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', null); + + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + }); + + test('remove window configuration without configuration target defaults to workspace in multi root workspace when resource is provided', function () { + instantiationService.stub(IWorkspaceContextService, { hasMultiFolderWorkspace: () => true }); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + + testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', URI.file('abc')); + + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + }); + + test('remove window configuration without configuration target defaults to workspace in folder workspace when resource is provider', function () { + instantiationService.stub(IWorkspaceContextService, { hasMultiFolderWorkspace: () => false }); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + + testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', URI.file('abc')); + + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + }); + + test('remove window configuration without configuration target defaults to workspace in folder workspace when no resource is provider', function () { + instantiationService.stub(IWorkspaceContextService, { hasMultiFolderWorkspace: () => false }); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + + testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', null); + + assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][0]); + }); + + test('remove configuration without configuration target defaults to folder', function () { + instantiationService.stub(IWorkspaceContextService, { hasMultiFolderWorkspace: () => true }); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + + testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', URI.file('abc')); + + assert.equal(ConfigurationTarget.FOLDER, target.args[0][0]); + }); +});