diff --git a/src/vs/editor/browser/standalone/standaloneServices.ts b/src/vs/editor/browser/standalone/standaloneServices.ts index 68e9623baf1..456290afa8d 100644 --- a/src/vs/editor/browser/standalone/standaloneServices.ts +++ b/src/vs/editor/browser/standalone/standaloneServices.ts @@ -115,14 +115,15 @@ export module StaticServices { export const instantiationService = define(IInstantiationService, () => new InstantiationService(_serviceCollection, true)); - export const contextService = define(IWorkspaceContextService, () => new WorkspaceContextService(new Workspace( + const configurationServiceImpl = new SimpleConfigurationService(); + export const configurationService = define(IConfigurationService, () => configurationServiceImpl); + + export const contextService = define(IWorkspaceContextService, () => new WorkspaceContextService(configurationServiceImpl, new Workspace( URI.from({ scheme: 'inmemory', authority: 'model', path: '/' }) ))); export const telemetryService = define(ITelemetryService, () => new StandaloneTelemetryService()); - export const configurationService = define(IConfigurationService, () => new SimpleConfigurationService()); - export const messageService = define(IMessageService, () => new SimpleMessageService()); export const markerService = define(IMarkerService, () => new MarkerService()); diff --git a/src/vs/platform/workspace/common/workspace.ts b/src/vs/platform/workspace/common/workspace.ts index a1a0480997d..8e0747fa8de 100644 --- a/src/vs/platform/workspace/common/workspace.ts +++ b/src/vs/platform/workspace/common/workspace.ts @@ -9,6 +9,11 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import paths = require('vs/base/common/paths'); import { isEqualOrParent } from 'vs/platform/files/common/files'; import { isLinux } from 'vs/base/common/platform'; +import Event, { Emitter } from 'vs/base/common/event'; +import { IConfigurationService } from "vs/platform/configuration/common/configuration"; +import { IDisposable, dispose } from "vs/base/common/lifecycle"; +import { distinct, equals } from "vs/base/common/arrays"; +import { Schemas } from "vs/base/common/network"; export const IWorkspaceContextService = createDecorator('contextService'); @@ -42,6 +47,12 @@ export interface IWorkspaceContextService { * Given a workspace relative path, returns the resource with the absolute path. */ toResource: (workspaceRelativePath: string) => URI; + + /** + * TODO@Ben multiroot + */ + getFolders(): URI[]; + onDidChangeFolders: Event; } export interface IWorkspace { @@ -111,14 +122,79 @@ export class Workspace implements IWorkspace { } } +type IWorkspaceConfiguration = { path: string; folders: string[]; }[]; + export class WorkspaceContextService implements IWorkspaceContextService { public _serviceBrand: any; - private workspace: Workspace; + private _onDidChangeFolders: Emitter; + private folders: URI[]; + private toDispose: IDisposable[]; - constructor(workspace?: Workspace) { - this.workspace = workspace; + constructor(private configurationService: IConfigurationService, private workspace?: Workspace) { + this.toDispose = []; + + this._onDidChangeFolders = new Emitter(); + this.toDispose.push(this._onDidChangeFolders); + + this.folders = workspace ? [workspace.resource] : []; + + this.resolveAdditionalFolders(); + + this.registerListeners(); + } + + private registerListeners(): void { + this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => this.onDidUpdateConfiguration())); + } + + private onDidUpdateConfiguration(): void { + this.resolveAdditionalFolders(true); + } + + private resolveAdditionalFolders(notify?: boolean): void { + if (!this.workspace) { + return; // no additional folders for empty workspaces + } + + // Resovled configured folders for workspace + let configuredFolders: URI[] = [this.workspace.resource]; + const config = this.configurationService.getConfiguration('workspace'); + if (Array.isArray(config)) { + for (let i = 0; i < config.length; i++) { + const targetWorkspace = config[i]; + if (targetWorkspace.path === this.workspace.resource.toString()) { + const additionalFolders = targetWorkspace.folders + .map(f => URI.parse(f)) + .filter(r => r.scheme === Schemas.file); // only support files for now + + configuredFolders.push(...additionalFolders); + + break; + } + } + } + + // Remove duplicates + configuredFolders = distinct(configuredFolders, r => r.toString()); + + // Find changes + const changed = !equals(this.folders, configuredFolders, (r1, r2) => r1.toString() === r2.toString()); + + this.folders = configuredFolders; + + if (notify && changed) { + this._onDidChangeFolders.fire(configuredFolders); + } + } + + public get onDidChangeFolders(): Event { + return this._onDidChangeFolders && this._onDidChangeFolders.event; //TODO@Sandeep sinon is a pita + } + + public getFolders(): URI[] { + return this.folders; } public getWorkspace(): IWorkspace { @@ -140,4 +216,8 @@ export class WorkspaceContextService implements IWorkspaceContextService { public toResource(workspaceRelativePath: string): URI { return this.workspace ? this.workspace.toResource(workspaceRelativePath) : null; } + + public dispose(): void { + dispose(this.toDispose); + } } \ No newline at end of file diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index e34a426f1ef..2cbc72f32ec 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -357,4 +357,33 @@ configurationRegistry.registerConfiguration({ 'description': nls.localize('zenMode.restore', "Controls if a window should restore to zen mode if it was exited in zen mode.") } } +}); + +// Configuration: Workspace +configurationRegistry.registerConfiguration({ + 'id': 'workspace', + 'order': 10000, + 'title': nls.localize('workspaceConfigurationTitle', "Workspace"), + 'type': 'object', + 'properties': { + 'workspace': { + 'type': 'array', + 'title': nls.localize('workspaces.title', "Folder configuration of the workspace"), + 'items': { + 'required': ['path'], + 'type': 'object', + 'defaultSnippets': [{ 'body': { 'path': '$1', 'folders': ['$2'] } }], + 'properties': { + 'path': { + 'type': 'string', + 'description': nls.localize('workspaces.path', "Path of the folder to configure folders for"), + }, + 'folders': { + 'description': nls.localize('workspaces.additionalFolders', "Folders of this workspace"), + 'type': 'array' + } + } + } + } + } }); \ No newline at end of file diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index b8bfbe51ce6..4afe4e06bf4 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -150,8 +150,8 @@ function getWorkspace(workspacePath: string): TPromise { function openWorkbench(environment: IWindowConfiguration, workspace: Workspace, options: IOptions): TPromise { const environmentService = new EnvironmentService(environment, environment.execPath); - const contextService = new WorkspaceContextService(workspace); const configurationService = new WorkspaceConfigurationService(environmentService, workspace); + const contextService = new WorkspaceContextService(configurationService, workspace); const timerService = new TimerService((window).MonacoEnvironment.timers as IInitData, !contextService.hasWorkspace()); // Since the configuration service is one of the core services that is used in so many places, we initialize it diff --git a/src/vs/workbench/node/extensionHostMain.ts b/src/vs/workbench/node/extensionHostMain.ts index abb6f76e6c8..6d38c79e651 100644 --- a/src/vs/workbench/node/extensionHostMain.ts +++ b/src/vs/workbench/node/extensionHostMain.ts @@ -18,6 +18,7 @@ import { RemoteTelemetryService } from 'vs/workbench/api/node/extHostTelemetry'; import { IWorkspaceContextService, WorkspaceContextService, Workspace } from 'vs/platform/workspace/common/workspace'; import { IInitData, IEnvironment, MainContext } from 'vs/workbench/api/node/extHost.protocol'; import * as errors from 'vs/base/common/errors'; +import { NullConfigurationService } from "vs/workbench/services/configuration/node/nullConfigurationService"; const nativeExit = process.exit.bind(process); process.exit = function () { @@ -49,7 +50,7 @@ export class ExtensionHostMain { if (workspaceRaw) { workspace = new Workspace(workspaceRaw.resource, workspaceRaw.uid, workspaceRaw.name); } - this._contextService = new WorkspaceContextService(workspace); + this._contextService = new WorkspaceContextService(new NullConfigurationService(), workspace); //TODO@Ben implement for exthost const threadService = new ExtHostThreadService(remoteCom); const telemetryService = new RemoteTelemetryService('pluginHostTelemetry', threadService); diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts index 0f50eb3a0e5..084d5a3ad71 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts @@ -31,6 +31,7 @@ import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { IWorkspaceContextService, WorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TestConfigurationService } from "vs/platform/configuration/test/common/testConfigurationService"; suite('ExtensionsActions Test', () => { @@ -52,7 +53,7 @@ suite('ExtensionsActions Test', () => { instantiationService.stub(IURLService, { onOpenURL: new Emitter().event }); instantiationService.stub(ITelemetryService, NullTelemetryService); - instantiationService.set(IWorkspaceContextService, new WorkspaceContextService(TestWorkspace)); + instantiationService.set(IWorkspaceContextService, new WorkspaceContextService(new TestConfigurationService(), TestWorkspace)); instantiationService.stub(IConfigurationService, { onDidUpdateConfiguration: () => { }, getConfiguration: () => ({}) }); instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index d42837e340e..15ba37559d7 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -32,6 +32,7 @@ import { IWorkspaceContextService, WorkspaceContextService } from 'vs/platform/w import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { IChoiceService } from 'vs/platform/message/common/message'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TestConfigurationService } from "vs/platform/configuration/test/common/testConfigurationService"; suite('ExtensionsWorkbenchService Test', () => { @@ -55,7 +56,7 @@ suite('ExtensionsWorkbenchService Test', () => { instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); - instantiationService.set(IWorkspaceContextService, new WorkspaceContextService(TestWorkspace)); + instantiationService.set(IWorkspaceContextService, new WorkspaceContextService(new TestConfigurationService(), TestWorkspace)); instantiationService.stub(IConfigurationService, { onDidUpdateConfiguration: () => { }, getConfiguration: () => ({}) }); instantiationService.stub(IExtensionManagementService, ExtensionManagementService); diff --git a/src/vs/workbench/parts/terminal/test/electron-browser/terminalLinkHandler.test.ts b/src/vs/workbench/parts/terminal/test/electron-browser/terminalLinkHandler.test.ts index d61de282fd4..3670f2a79f9 100644 --- a/src/vs/workbench/parts/terminal/test/electron-browser/terminalLinkHandler.test.ts +++ b/src/vs/workbench/parts/terminal/test/electron-browser/terminalLinkHandler.test.ts @@ -13,6 +13,7 @@ import URI from 'vs/base/common/uri'; import * as strings from 'vs/base/common/strings'; import * as path from 'path'; import * as sinon from 'sinon'; +import { TestConfigurationService } from "vs/platform/configuration/test/common/testConfigurationService"; class TestTerminalLinkHandler extends TerminalLinkHandler { public get localLinkRegex(): RegExp { @@ -175,7 +176,7 @@ suite('Workbench - TerminalLinkHandler', () => { suite('preprocessPath', () => { test('Windows', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Windows, null, null, - new WorkspaceContextService(new TestWorkspace('C:\\base'))); + new WorkspaceContextService(new TestConfigurationService(), new TestWorkspace('C:\\base'))); let stub = sinon.stub(path, 'join', function (arg1, arg2) { return arg1 + '\\' + arg2; @@ -189,7 +190,7 @@ suite('Workbench - TerminalLinkHandler', () => { test('Linux', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, null, null, - new WorkspaceContextService(new TestWorkspace('/base'))); + new WorkspaceContextService(new TestConfigurationService(), new TestWorkspace('/base'))); let stub = sinon.stub(path, 'join', function (arg1, arg2) { return arg1 + '/' + arg2; @@ -202,7 +203,7 @@ suite('Workbench - TerminalLinkHandler', () => { }); test('No Workspace', () => { - const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, null, null, new WorkspaceContextService(null)); + const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, null, null, new WorkspaceContextService(new TestConfigurationService())); assert.equal(linkHandler.preprocessPath('./src/file1'), null); assert.equal(linkHandler.preprocessPath('src/file2'), null); diff --git a/src/vs/workbench/services/configuration/node/nullConfigurationService.ts b/src/vs/workbench/services/configuration/node/nullConfigurationService.ts new file mode 100644 index 00000000000..c38b5b29b2e --- /dev/null +++ b/src/vs/workbench/services/configuration/node/nullConfigurationService.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IConfigurationService, IConfigurationServiceEvent, IConfigurationValue, getConfigurationValue, IConfigurationKeys } from "vs/platform/configuration/common/configuration"; +import Event, { Emitter } from 'vs/base/common/event'; +import { TPromise } from "vs/base/common/winjs.base"; + +export class NullConfigurationService implements IConfigurationService { + + _serviceBrand: any; + + private _onDidUpdateConfiguration = new Emitter(); + public onDidUpdateConfiguration: Event = this._onDidUpdateConfiguration.event; + + private _config: any; + + constructor() { + this._config = Object.create(null); + } + + public getConfiguration(section?: any): T { + return this._config; + } + + public reloadConfiguration(section?: string): TPromise { + return TPromise.as(this.getConfiguration(section)); + } + + public lookup(key: string): IConfigurationValue { + return { + value: getConfigurationValue(this.getConfiguration(), key), + default: getConfigurationValue(this.getConfiguration(), key), + user: getConfigurationValue(this.getConfiguration(), key) + }; + } + + public keys(): IConfigurationKeys { + return { default: [], user: [] }; + } +} \ No newline at end of file diff --git a/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts index df423a3c094..86e267938fe 100644 --- a/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts @@ -116,8 +116,8 @@ suite('ConfigurationEditingService', () => { const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile); instantiationService.stub(IEnvironmentService, environmentService); const workspace = noWorkspace ? null : new Workspace(URI.file(workspaceDir)); - instantiationService.stub(IWorkspaceContextService, new WorkspaceContextService(workspace)); const configurationService = new WorkspaceConfigurationService(environmentService, workspace); + instantiationService.stub(IWorkspaceContextService, new WorkspaceContextService(configurationService, workspace)); instantiationService.stub(IConfigurationService, configurationService); instantiationService.stub(ILifecycleService, new TestLifecycleService()); instantiationService.stub(IEditorGroupService, new TestEditorGroupService()); diff --git a/src/vs/workbench/test/browser/part.test.ts b/src/vs/workbench/test/browser/part.test.ts index a8a31790634..0d4d0f1418f 100644 --- a/src/vs/workbench/test/browser/part.test.ts +++ b/src/vs/workbench/test/browser/part.test.ts @@ -14,6 +14,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { StorageService, InMemoryLocalStorage } from 'vs/platform/storage/common/storageService'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { TestThemeService } from 'vs/workbench/test/workbenchTestServices'; +import { TestConfigurationService } from "vs/platform/configuration/test/common/testConfigurationService"; class MyPart extends Part { @@ -91,7 +92,7 @@ suite('Workbench Part', () => { fixture = document.createElement('div'); fixture.id = fixtureId; document.body.appendChild(fixture); - context = new WorkspaceContextService(TestWorkspace); + context = new WorkspaceContextService(new TestConfigurationService(), TestWorkspace); storage = new StorageService(new InMemoryLocalStorage(), null, context); }); diff --git a/src/vs/workbench/test/common/memento.test.ts b/src/vs/workbench/test/common/memento.test.ts index 796ef72d8dd..c8d988af459 100644 --- a/src/vs/workbench/test/common/memento.test.ts +++ b/src/vs/workbench/test/common/memento.test.ts @@ -11,13 +11,14 @@ import { StorageScope } from 'vs/platform/storage/common/storage'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { Memento, Scope } from 'vs/workbench/common/memento'; import { StorageService, InMemoryLocalStorage } from 'vs/platform/storage/common/storageService'; +import { TestConfigurationService } from "vs/platform/configuration/test/common/testConfigurationService"; suite('Workbench Memento', () => { let context; let storage; setup(() => { - context = new WorkspaceContextService(TestWorkspace); + context = new WorkspaceContextService(new TestConfigurationService(), TestWorkspace); storage = new StorageService(new InMemoryLocalStorage(), null, context); }); diff --git a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts index dfd2576d1d4..26a08ffd11b 100644 --- a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts @@ -71,9 +71,9 @@ suite('QuickOpen performance (integration)', () => { const configurationService = new SimpleConfigurationService(); const instantiationService = new InstantiationService(new ServiceCollection( [ITelemetryService, telemetryService], - [IConfigurationService, new SimpleConfigurationService()], + [IConfigurationService, configurationService], [IModelService, new ModelServiceImpl(null, configurationService)], - [IWorkspaceContextService, new WorkspaceContextService(new Workspace(URI.file(testWorkspacePath)))], + [IWorkspaceContextService, new WorkspaceContextService(configurationService, new Workspace(URI.file(testWorkspacePath)))], [IWorkbenchEditorService, new TestEditorService()], [IEditorGroupService, new TestEditorGroupService()], [IEnvironmentService, TestEnvironmentService], diff --git a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts index 7d1bb6b1139..64f77fcc9c9 100644 --- a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts @@ -60,9 +60,9 @@ suite('TextSearch performance (integration)', () => { const configurationService = new SimpleConfigurationService(); const instantiationService = new InstantiationService(new ServiceCollection( [ITelemetryService, telemetryService], - [IConfigurationService, new SimpleConfigurationService()], + [IConfigurationService, configurationService], [IModelService, new ModelServiceImpl(null, configurationService)], - [IWorkspaceContextService, new WorkspaceContextService(new Workspace(URI.file(testWorkspacePath)))], + [IWorkspaceContextService, new WorkspaceContextService(configurationService, new Workspace(URI.file(testWorkspacePath)))], [IWorkbenchEditorService, new TestEditorService()], [IEditorGroupService, new TestEditorGroupService()], [IEnvironmentService, TestEnvironmentService], diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 32f74fe5a2d..0928c71276d 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -66,9 +66,20 @@ export class TestContextService implements IWorkspaceContextService { private workspace: any; private options: any; + private _onDidChangeFolders: Emitter; + constructor(workspace: any = TestWorkspace, options: any = null) { this.workspace = workspace; this.options = options || Object.create(null); + this._onDidChangeFolders = new Emitter(); + } + + public get onDidChangeFolders(): Event { + return this._onDidChangeFolders.event; + } + + public getFolders(): URI[] { + return this.workspace ? [this.workspace.resource] : []; } public hasWorkspace(): boolean {