diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 8ef3e3914517ccd12fefc11d2151ee9bf6059881..1abb7678d0a8c918be488fec2a561ae7e9b5e52a 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -9,6 +9,7 @@ import { TrieMap } from 'vs/base/common/map'; import { score } from 'vs/editor/common/modes/languageSelector'; import * as Platform from 'vs/base/common/platform'; import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; +import { WorkspaceConfigurationNode } from 'vs/workbench/services/configuration/common/configuration'; import * as errors from 'vs/base/common/errors'; import product from 'vs/platform/product'; import pkg from 'vs/platform/package'; @@ -42,7 +43,7 @@ import * as vscode from 'vscode'; import * as paths from 'vs/base/common/paths'; import { realpathSync } from 'fs'; import { ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; -import { MainContext, ExtHostContext, InstanceCollection, IInitConfiguration } from './extHost.protocol'; +import { MainContext, ExtHostContext, InstanceCollection } from './extHost.protocol'; import * as languageConfiguration from 'vs/editor/common/modes/languageConfiguration'; @@ -63,7 +64,7 @@ function proposedApiFunction(extension: IExtensionDescription, fn: T): T { /** * This method instantiates and returns the extension API surface */ -export function createApiFactory(initDataConfiguration: IInitConfiguration, initTelemetryInfo: ITelemetryInfo, threadService: IThreadService, extensionService: ExtHostExtensionService, contextService: IWorkspaceContextService): IExtensionApiFactory { +export function createApiFactory(initDataConfiguration: WorkspaceConfigurationNode, initTelemetryInfo: ITelemetryInfo, threadService: IThreadService, extensionService: ExtHostExtensionService, contextService: IWorkspaceContextService): IExtensionApiFactory { // Addressable instances const col = new InstanceCollection(); diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 403f1f96615cf349984424de82407eacb36899e7..012d6603b64ff497ea7cc9b85556b462cbffa5c8 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -29,6 +29,7 @@ import * as modes from 'vs/editor/common/modes'; import { IResourceEdit } from 'vs/editor/common/services/bulkEdit'; import { ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; +import { WorkspaceConfigurationNode } from 'vs/workbench/services/configuration/common/configuration'; import { IPickOpenEntry, IPickOptions } from 'vs/workbench/services/quickopen/common/quickOpenService'; import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; @@ -43,10 +44,6 @@ export interface IEnvironment { extensionTestsPath: string; } -export interface IInitConfiguration { - _initConfigurationBrand: void; -} - export interface IInitData { parentPid: number; environment: IEnvironment; @@ -54,7 +51,7 @@ export interface IInitData { workspace: IWorkspace; }; extensions: IExtensionDescription[]; - configuration: IInitConfiguration; + configuration: WorkspaceConfigurationNode; telemetryInfo: ITelemetryInfo; } @@ -232,7 +229,7 @@ export abstract class ExtHostCommandsShape { } export abstract class ExtHostConfigurationShape { - $acceptConfigurationChanged(config: any) { throw ni(); } + $acceptConfigurationChanged(config: WorkspaceConfigurationNode) { throw ni(); } } export abstract class ExtHostDiagnosticsShape { diff --git a/src/vs/workbench/api/node/extHostConfiguration.ts b/src/vs/workbench/api/node/extHostConfiguration.ts index 662c14b0a04a9f18a93b600a9229498132463c48..1d490d00643f1105ea14a4517c8b3ec3574194e6 100644 --- a/src/vs/workbench/api/node/extHostConfiguration.ts +++ b/src/vs/workbench/api/node/extHostConfiguration.ts @@ -9,24 +9,25 @@ import Event, { Emitter } from 'vs/base/common/event'; import { WorkspaceConfiguration } from 'vscode'; import { ExtHostConfigurationShape, MainThreadConfigurationShape } from './extHost.protocol'; import { ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; +import { WorkspaceConfigurationNode, IWorkspaceConfigurationValue } from 'vs/workbench/services/configuration/common/configuration'; export class ExtHostConfiguration extends ExtHostConfigurationShape { private _proxy: MainThreadConfigurationShape; - private _config: any; + private _config: WorkspaceConfigurationNode; private _onDidChangeConfiguration = new Emitter(); - constructor(proxy: MainThreadConfigurationShape, configuration: any) { + constructor(proxy: MainThreadConfigurationShape, config: WorkspaceConfigurationNode) { super(); this._proxy = proxy; - this._config = configuration; + this._config = config; } get onDidChangeConfiguration(): Event { return this._onDidChangeConfiguration && this._onDidChangeConfiguration.event; } - public $acceptConfigurationChanged(config: any) { + public $acceptConfigurationChanged(config: WorkspaceConfigurationNode) { this._config = config; this._onDidChangeConfiguration.fire(undefined); } @@ -39,14 +40,17 @@ export class ExtHostConfiguration extends ExtHostConfigurationShape { const result: WorkspaceConfiguration = { has(key: string): boolean { - return typeof ExtHostConfiguration._lookUp(key, config) !== 'undefined'; + return typeof ExtHostConfiguration._lookUp(key, config) !== 'undefined'; }, - get(key: string, defaultValue?: T): T { - let result = ExtHostConfiguration._lookUp(key, config); + get(key: string, defaultValue?: T): any { + let result = ExtHostConfiguration._lookUp(key, config); if (typeof result === 'undefined') { - result = defaultValue; + return defaultValue; + } else if (isConfigurationValue(result)) { + return result.value; + } else { + return ExtHostConfiguration._values(result); } - return result; }, update: (key: string, value: any, global: boolean = false) => { key = section ? `${section}.${key}` : key; @@ -59,23 +63,52 @@ export class ExtHostConfiguration extends ExtHostConfigurationShape { } }; - if (typeof config === 'object') { - mixin(result, config, false); + if (!isConfigurationValue(config)) { + mixin(result, ExtHostConfiguration._values(config), false); } return Object.freeze(result); + } - private static _lookUp(section: string, config: any) { + private static _lookUp(section: string, config: WorkspaceConfigurationNode): WorkspaceConfigurationNode | IWorkspaceConfigurationValue { if (!section) { return; } let parts = section.split('.'); let node = config; while (node && parts.length) { - node = node[parts.shift()]; + let child = node[parts.shift()]; + if (isConfigurationValue(child)) { + return child; + } else { + node = child; + } } return node; } + + private static _values(node: WorkspaceConfigurationNode): any { + let target = Object.create(null); + for (let key in node) { + let child = node[key]; + if (isConfigurationValue(child)) { + target[key] = child.value; + } else { + target[key] = ExtHostConfiguration._values(child); + } + } + return target; + } +} + +function isConfigurationValue(thing: any): thing is IWorkspaceConfigurationValue { + return typeof thing === 'object' + // must have 'value' + && typeof (>thing).value !== 'undefined' + // and at least one source 'default', 'user', or 'workspace' + && (typeof (>thing).default !== 'undefined' + || typeof (>thing).user !== 'undefined' + || typeof (>thing).workspace !== 'undefined'); } diff --git a/src/vs/workbench/api/node/mainThreadConfiguration.ts b/src/vs/workbench/api/node/mainThreadConfiguration.ts index 39a5c31f124710697c781e3a9fed6e5bd5ba8687..45b530920f82ac7633716d743c4efafb7571d5dd 100644 --- a/src/vs/workbench/api/node/mainThreadConfiguration.ts +++ b/src/vs/workbench/api/node/mainThreadConfiguration.ts @@ -7,7 +7,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; +import { IWorkspaceConfigurationService, getWorkspaceConfigurationTree } from 'vs/workbench/services/configuration/common/configuration'; import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing'; import { MainThreadConfigurationShape, ExtHostContext } from './extHost.protocol'; @@ -24,7 +24,11 @@ export class MainThreadConfiguration extends MainThreadConfigurationShape { super(); this._configurationEditingService = configurationEditingService; const proxy = threadService.get(ExtHostContext.ExtHostConfiguration); - this._toDispose = configurationService.onDidUpdateConfiguration(event => proxy.$acceptConfigurationChanged(event.config)); + + this._toDispose = configurationService.onDidUpdateConfiguration(() => { + const tree = getWorkspaceConfigurationTree(configurationService); + proxy.$acceptConfigurationChanged(tree); + }); } public dispose(): void { diff --git a/src/vs/workbench/electron-browser/extensionHost.ts b/src/vs/workbench/electron-browser/extensionHost.ts index 86d5825406192c6f113791cf5474b4d380d44add..c28d31ad57dc7ba63fbbcc98d07e5909d34cea87 100644 --- a/src/vs/workbench/electron-browser/extensionHost.ts +++ b/src/vs/workbench/electron-browser/extensionHost.ts @@ -28,9 +28,9 @@ import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import Event, { Emitter } from 'vs/base/common/event'; import { WatchDog } from 'vs/base/common/watchDog'; import { createQueuedSender, IQueuedSender } from 'vs/base/node/processes'; -import { IInitData, IInitConfiguration } from 'vs/workbench/api/node/extHost.protocol'; +import { IInitData } from 'vs/workbench/api/node/extHost.protocol'; import { MainProcessExtensionService } from 'vs/workbench/api/node/mainThreadExtensionService'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; +import { IWorkspaceConfigurationService, getWorkspaceConfigurationTree } from 'vs/workbench/services/configuration/common/configuration'; export const EXTENSION_LOG_BROADCAST_CHANNEL = 'vscode:extensionLog'; export const EXTENSION_ATTACH_BROADCAST_CHANNEL = 'vscode:extensionAttach'; @@ -258,7 +258,7 @@ export class ExtensionHostProcessWorker { workspace: this.contextService.getWorkspace() }, extensions: extensionDescriptions, - configuration: this.configurationService.getConfiguration(), + configuration: getWorkspaceConfigurationTree(this.configurationService), telemetryInfo }; this.extensionHostProcessQueuedSender.send(stringify(initData)); diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index ae7f337ca195affec709163d83959fbf7ae4fbfe..ed2ae1d8d559ad22638e681ead03d2a208386d96 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -42,4 +42,42 @@ export const WORKSPACE_STANDALONE_CONFIGURATIONS = { 'tasks': `${WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME}/tasks.json`, 'launch': `${WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME}/launch.json`, 'extensions': `${WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME}/extensions.json` -}; \ No newline at end of file +}; + +export interface WorkspaceConfigurationNode { + [part: string]: IWorkspaceConfigurationValue | WorkspaceConfigurationNode; +} + +export function getWorkspaceConfigurationTree(configurationService: IWorkspaceConfigurationService): WorkspaceConfigurationNode { + const result: WorkspaceConfigurationNode = Object.create(null); + const keyset = configurationService.keys(); + const keys = [...keyset.workspace, ...keyset.user, ...keyset.default].sort(); + let lastKey: string; + for (const key of keys) { + if (key !== lastKey) { + lastKey = key; + const config = configurationService.lookup(key); + insert(result, key, config); + } + } + return result; +} + +function insert(root: WorkspaceConfigurationNode, key: string, value: any): void { + const parts = key.split('.'); + let i = 0; + while (i < parts.length - 1) { + let child = root[parts[i]]; + if (child) { + root = child; + i += 1; + } else { + break; + } + } + while (i < parts.length - 1) { + root = root[parts[i]] = Object.create(null); + i += 1; + } + root[parts[parts.length - 1]] = value; +} diff --git a/src/vs/workbench/test/node/api/extHostConfiguration.test.ts b/src/vs/workbench/test/node/api/extHostConfiguration.test.ts index 33ef31ce171da7c588d4e04abac33d03b3f3b652..88acb2b0b189afaa22b755ce46f3412e86df14fd 100644 --- a/src/vs/workbench/test/node/api/extHostConfiguration.test.ts +++ b/src/vs/workbench/test/node/api/extHostConfiguration.test.ts @@ -10,6 +10,7 @@ import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration import { MainThreadConfigurationShape } from 'vs/workbench/api/node/extHost.protocol'; import { TPromise } from 'vs/base/common/winjs.base'; import { ConfigurationTarget, ConfigurationEditingErrorCode, IConfigurationEditingError } from 'vs/workbench/services/configuration/common/configurationEditing'; +import { WorkspaceConfigurationNode, IWorkspaceConfigurationValue } from 'vs/workbench/services/configuration/common/configuration'; suite('ExtHostConfiguration', function () { @@ -21,23 +22,32 @@ suite('ExtHostConfiguration', function () { } }; - function createExtHostConfiguration(data: any = {}, shape?: MainThreadConfigurationShape) { + function createExtHostConfiguration(data: WorkspaceConfigurationNode = {}, shape?: MainThreadConfigurationShape) { if (!shape) { shape = new class extends MainThreadConfigurationShape { }; } return new ExtHostConfiguration(shape, data); } + function createConfigurationValue(value: T): IWorkspaceConfigurationValue { + return { + value, + default: value, + user: undefined, + workspace: undefined + }; + } + test('has/get', function () { const all = createExtHostConfiguration({ farboo: { - config0: true, + config0: createConfigurationValue(true), nested: { - config1: 42, - config2: 'Das Pferd frisst kein Reis.' + config1: createConfigurationValue(42), + config2: createConfigurationValue('Das Pferd frisst kein Reis.'), }, - config4: '' + config4: createConfigurationValue('') } }); @@ -49,18 +59,22 @@ suite('ExtHostConfiguration', function () { assert.equal(config['config0'], true); assert.equal(config['config4'], ''); assert.throws(() => config['config4'] = 'valuevalue'); + assert.ok(config.has('nested.config1')); assert.equal(config.get('nested.config1'), 42); assert.ok(config.has('nested.config2')); assert.equal(config.get('nested.config2'), 'Das Pferd frisst kein Reis.'); + + assert.ok(config.has('nested')); + assert.deepEqual(config.get('nested'), { config1: 42, config2: 'Das Pferd frisst kein Reis.' }); }); test('getConfiguration vs get', function () { const all = createExtHostConfiguration({ farboo: { - config0: true, - config4: '38' + config0: createConfigurationValue(true), + config4: createConfigurationValue('38') } }); @@ -76,7 +90,7 @@ suite('ExtHostConfiguration', function () { test('name vs property', function () { const all = createExtHostConfiguration({ farboo: { - get: 'get-prop' + get: createConfigurationValue('get-prop') } }); const config = all.getConfiguration('farboo'); @@ -90,7 +104,7 @@ suite('ExtHostConfiguration', function () { test('udate/section to key', function () { const shape = new RecordingShape(); - const allConfig = createExtHostConfiguration({ foo: { bar: 1, far: 2 } }, shape); + const allConfig = createExtHostConfiguration({ foo: { bar: createConfigurationValue(1), far: createConfigurationValue(2) } }, shape); let config = allConfig.getConfiguration('foo'); config.update('bar', 42, true);