diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index e4461fc0ca6..2e75d866672 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -11,8 +11,7 @@ import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlighte import { IMatch } from 'vs/base/common/filters'; import uri from 'vs/base/common/uri'; import paths = require('vs/base/common/paths'); -import types = require('vs/base/common/types'); -import { IWorkspaceProvider, getPathLabel, IUserHomeProvider } from 'vs/base/common/labels'; +import { IRootProvider, getPathLabel, IUserHomeProvider } from 'vs/base/common/labels'; export interface IIconLabelCreationOptions { supportHighlights?: boolean; @@ -99,30 +98,15 @@ export class IconLabel { export class FileLabel extends IconLabel { - constructor(container: HTMLElement, file: uri, provider: IWorkspaceProvider, userHome?: IUserHomeProvider) { + constructor(container: HTMLElement, file: uri, provider: IRootProvider, userHome?: IUserHomeProvider) { super(container); this.setFile(file, provider, userHome); } - public setFile(file: uri, provider: IWorkspaceProvider, userHome: IUserHomeProvider): void { - const path = getPath(file); - const parent = paths.dirname(path); + public setFile(file: uri, provider: IRootProvider, userHome: IUserHomeProvider): void { + const parent = paths.dirname(file.fsPath); - this.setValue(paths.basename(path), parent && parent !== '.' ? getPathLabel(parent, provider, userHome) : '', { title: path }); + this.setValue(paths.basename(file.fsPath), parent && parent !== '.' ? getPathLabel(parent, provider, userHome) : '', { title: file.fsPath }); } -} - -function getPath(arg1: uri | IWorkspaceProvider): string { - if (!arg1) { - return null; - } - - if (types.isFunction((arg1).getWorkspace)) { - const ws = (arg1).getWorkspace(); - - return ws ? ws.resource.fsPath : void 0; - } - - return (arg1).fsPath; } \ No newline at end of file diff --git a/src/vs/base/common/labels.ts b/src/vs/base/common/labels.ts index e7b950da577..172700a0811 100644 --- a/src/vs/base/common/labels.ts +++ b/src/vs/base/common/labels.ts @@ -6,8 +6,7 @@ import URI from 'vs/base/common/uri'; import platform = require('vs/base/common/platform'); -import types = require('vs/base/common/types'); -import { nativeSep, normalize, isEqualOrParent, isEqual } from 'vs/base/common/paths'; +import { nativeSep, normalize, isEqualOrParent, isEqual, basename, join } from 'vs/base/common/paths'; import { endsWith, ltrim } from 'vs/base/common/strings'; export interface ILabelProvider { @@ -18,9 +17,10 @@ export interface ILabelProvider { getLabel(element: any): string; } -export interface IWorkspaceProvider { - getWorkspace(): { - resource: URI; +export interface IRootProvider { + getRoot(resource: URI): URI; + getWorkspace2(): { + roots: URI[]; }; } @@ -28,27 +28,42 @@ export interface IUserHomeProvider { userHome: string; } -export function getPathLabel(resource: URI | string, basePathProvider?: URI | string | IWorkspaceProvider, userHomeProvider?: IUserHomeProvider): string { - const absolutePath = getPath(resource); - if (!absolutePath) { +export function getPathLabel(resource: URI | string, rootProvider?: IRootProvider, userHomeProvider?: IUserHomeProvider): string { + if (!resource) { return null; } - const basepath = basePathProvider && getPath(basePathProvider); + if (typeof resource === 'string') { + resource = URI.file(resource); + } - if (basepath && isEqualOrParent(absolutePath, basepath, !platform.isLinux /* ignorecase */)) { - if (isEqual(basepath, absolutePath, !platform.isLinux /* ignorecase */)) { - return ''; // no label if pathes are identical + // return early if we can resolve a relative path label from the root + const baseResource = rootProvider ? rootProvider.getRoot(resource) : null; + if (baseResource) { + const hasMultipleRoots = rootProvider.getWorkspace2().roots.length > 1; + + let pathLabel: string; + if (isEqual(baseResource.fsPath, resource.fsPath, !platform.isLinux /* ignorecase */)) { + pathLabel = ''; // no label if pathes are identical + } else { + pathLabel = normalize(ltrim(resource.fsPath.substr(baseResource.fsPath.length), nativeSep), true); } - return normalize(ltrim(absolutePath.substr(basepath.length), nativeSep), true); + if (hasMultipleRoots) { + const rootName = basename(baseResource.fsPath); + pathLabel = pathLabel ? join(rootName, pathLabel) : rootName; // always show root basename if there are multiple + } + + return pathLabel; } - if (platform.isWindows && absolutePath && absolutePath[1] === ':') { - return normalize(absolutePath.charAt(0).toUpperCase() + absolutePath.slice(1), true); // convert c:\something => C:\something + // convert c:\something => C:\something + if (platform.isWindows && resource.fsPath && resource.fsPath[1] === ':') { + return normalize(resource.fsPath.charAt(0).toUpperCase() + resource.fsPath.slice(1), true); } - let res = normalize(absolutePath, true); + // normalize and tildify (macOS, Linux only) + let res = normalize(resource.fsPath, true); if (!platform.isWindows && userHomeProvider) { res = tildify(res, userHomeProvider.userHome); } @@ -56,23 +71,6 @@ export function getPathLabel(resource: URI | string, basePathProvider?: URI | st return res; } -function getPath(arg1: URI | string | IWorkspaceProvider): string { - if (!arg1) { - return null; - } - - if (typeof arg1 === 'string') { - return arg1; - } - - if (types.isFunction((arg1).getWorkspace)) { - const ws = (arg1).getWorkspace(); - return ws ? ws.resource.fsPath : void 0; - } - - return (arg1).fsPath; -} - export function tildify(path: string, userHome: string): string { if (path && (platform.isMacintosh || platform.isLinux) && isEqualOrParent(path, userHome, !platform.isLinux /* ignorecase */)) { path = `~${path.substr(userHome.length)}`; diff --git a/src/vs/platform/extensionManagement/common/extensionEnablementService.ts b/src/vs/platform/extensionManagement/common/extensionEnablementService.ts index 786e1be59b7..ca319bf47b6 100644 --- a/src/vs/platform/extensionManagement/common/extensionEnablementService.ts +++ b/src/vs/platform/extensionManagement/common/extensionEnablementService.ts @@ -10,7 +10,7 @@ import Event, { Emitter } from 'vs/base/common/event'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { adoptToGalleryExtensionId, getIdAndVersionFromLocalExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { IWorkspaceContextService, ILegacyWorkspace } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -34,8 +34,8 @@ export class ExtensionEnablementService implements IExtensionEnablementService { extensionManagementService.onDidUninstallExtension(this.onDidUninstallExtension, this, this.disposables); } - private get workspace(): ILegacyWorkspace { - return this.contextService.getWorkspace(); + private get hasWorkspace(): boolean { + return this.contextService.hasWorkspace(); } public getGloballyDisabledExtensions(): string[] { @@ -60,7 +60,7 @@ export class ExtensionEnablementService implements IExtensionEnablementService { } public setEnablement(identifier: string, enable: boolean, workspace: boolean = false): TPromise { - if (workspace && !this.workspace) { + if (workspace && !this.hasWorkspace) { return TPromise.wrapError(localize('noWorkspace', "No workspace.")); } @@ -106,7 +106,7 @@ export class ExtensionEnablementService implements IExtensionEnablementService { } private getDisabledExtensions(scope: StorageScope): string[] { - if (scope === StorageScope.WORKSPACE && !this.workspace) { + if (scope === StorageScope.WORKSPACE && !this.hasWorkspace) { return []; } const value = this.storageService.get(DISABLED_EXTENSIONS_STORAGE_PATH, scope, ''); diff --git a/src/vs/platform/extensionManagement/test/common/extensionEnablementService.test.ts b/src/vs/platform/extensionManagement/test/common/extensionEnablementService.test.ts index 01c5ab208cd..209d6b052c8 100644 --- a/src/vs/platform/extensionManagement/test/common/extensionEnablementService.test.ts +++ b/src/vs/platform/extensionManagement/test/common/extensionEnablementService.test.ts @@ -69,7 +69,7 @@ suite('ExtensionEnablementService Test', () => { test('test when no extensions are disabled for workspace when there is no workspace', (done) => { testObject.setEnablement('pub.a', false, true) .then(() => { - instantiationService.stub(IWorkspaceContextService, 'getWorkspace', null); + instantiationService.stub(IWorkspaceContextService, 'hasWorkspace', false); assert.deepEqual([], testObject.getWorkspaceDisabledExtensions()); }) .then(done, done); @@ -174,7 +174,7 @@ suite('ExtensionEnablementService Test', () => { }); test('test disable an extension for workspace when there is no workspace throws error', (done) => { - instantiationService.stub(IWorkspaceContextService, 'getWorkspace', null); + instantiationService.stub(IWorkspaceContextService, 'hasWorkspace', false); testObject.setEnablement('pub.a', false, true) .then(() => assert.fail('should throw an error'), error => assert.ok(error)) .then(done, done); diff --git a/src/vs/platform/storage/common/storageService.ts b/src/vs/platform/storage/common/storageService.ts index 83049e9736f..da38c935523 100644 --- a/src/vs/platform/storage/common/storageService.ts +++ b/src/vs/platform/storage/common/storageService.ts @@ -8,7 +8,7 @@ import types = require('vs/base/common/types'); import errors = require('vs/base/common/errors'); import strings = require('vs/base/common/strings'); import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import URI from "vs/base/common/uri"; +import { IWorkspace } from "vs/platform/workspace/common/workspace"; // Browser localStorage interface export interface IStorage { @@ -20,11 +20,6 @@ export interface IStorage { removeItem(key: string): void; } -export interface IWorkspaceStorageIdentifier { - resource: URI; - uid?: number; -} - export class StorageService implements IStorageService { public _serviceBrand: any; @@ -43,36 +38,35 @@ export class StorageService implements IStorageService { constructor( globalStorage: IStorage, workspaceStorage: IStorage, - workspaceIdentifier?: IWorkspaceStorageIdentifier + workspace?: IWorkspace, + legacyWorkspaceId?: number ) { this.globalStorage = globalStorage; this.workspaceStorage = workspaceStorage || globalStorage; // Calculate workspace storage key - this.workspaceKey = this.getWorkspaceKey(workspaceIdentifier ? workspaceIdentifier.resource : void 0); + this.workspaceKey = this.getWorkspaceKey(workspace ? workspace.id : void 0); // Make sure to delete all workspace storage if the workspace has been recreated meanwhile - // which is only possible if a UID property is provided that we can check on - if (workspaceIdentifier && types.isNumber(workspaceIdentifier.uid)) { - this.cleanupWorkspaceScope(workspaceIdentifier.uid); + // which is only possible if a id property is provided that we can check on + if (workspace && types.isNumber(legacyWorkspaceId)) { + this.cleanupWorkspaceScope(legacyWorkspaceId); } } - private getWorkspaceKey(workspaceId?: URI): string { - if (!workspaceId) { + private getWorkspaceKey(id?: string): string { + if (!id) { return StorageService.NO_WORKSPACE_IDENTIFIER; } - let workspaceIdStr = workspaceId.toString(); - // Special case file:// URIs: strip protocol from key to produce shorter key const fileProtocol = 'file:///'; - if (workspaceIdStr.indexOf(fileProtocol) === 0) { - workspaceIdStr = workspaceIdStr.substr(fileProtocol.length); + if (id.indexOf(fileProtocol) === 0) { + id = id.substr(fileProtocol.length); } // Always end with "/" - return `${strings.rtrim(workspaceIdStr, '/')}/`; + return `${strings.rtrim(id, '/')}/`; } private cleanupWorkspaceScope(workspaceUid: number): void { diff --git a/src/vs/platform/storage/test/storageService.test.ts b/src/vs/platform/storage/test/storageService.test.ts index a32ef2f398f..19caaf99e98 100644 --- a/src/vs/platform/storage/test/storageService.test.ts +++ b/src/vs/platform/storage/test/storageService.test.ts @@ -8,14 +8,13 @@ import * as assert from 'assert'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { StorageScope } from 'vs/platform/storage/common/storage'; -import { IWorkspaceContextService, LegacyWorkspace } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { StorageService, InMemoryLocalStorage } from 'vs/platform/storage/common/storageService'; import { TestContextService } from 'vs/workbench/test/workbenchTestServices'; -import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; suite('Workbench StorageSevice', () => { - - let contextService, instantiationService; + let contextService: IWorkspaceContextService; + let instantiationService: TestInstantiationService; setup(() => { instantiationService = new TestInstantiationService(); @@ -23,7 +22,7 @@ suite('Workbench StorageSevice', () => { }); test('Swap Data with undefined default value', () => { - let s = new StorageService(new InMemoryLocalStorage(), null, contextService.getWorkspace()); + let s = new StorageService(new InMemoryLocalStorage(), null, contextService.getWorkspace2()); s.swap('Monaco.IDE.Core.Storage.Test.swap', 'foobar', 'barfoo'); assert.strictEqual('foobar', s.get('Monaco.IDE.Core.Storage.Test.swap')); @@ -34,7 +33,7 @@ suite('Workbench StorageSevice', () => { }); test('Remove Data', () => { - let s = new StorageService(new InMemoryLocalStorage(), null, contextService.getWorkspace()); + let s = new StorageService(new InMemoryLocalStorage(), null, contextService.getWorkspace2()); s.store('Monaco.IDE.Core.Storage.Test.remove', 'foobar'); assert.strictEqual('foobar', s.get('Monaco.IDE.Core.Storage.Test.remove')); @@ -43,7 +42,7 @@ suite('Workbench StorageSevice', () => { }); test('Get Data, Integer, Boolean', () => { - let s = new StorageService(new InMemoryLocalStorage(), null, contextService.getWorkspace()); + let s = new StorageService(new InMemoryLocalStorage(), null, contextService.getWorkspace2()); assert.strictEqual(s.get('Monaco.IDE.Core.Storage.Test.get', StorageScope.GLOBAL, 'foobar'), 'foobar'); assert.strictEqual(s.get('Monaco.IDE.Core.Storage.Test.get', StorageScope.GLOBAL, ''), ''); @@ -77,16 +76,15 @@ suite('Workbench StorageSevice', () => { test('StorageSevice cleans up when workspace changes', () => { let storageImpl = new InMemoryLocalStorage(); - let ws = contextService.getWorkspace(); - ws.uid = new Date().getTime(); - let s = new StorageService(storageImpl, null, ws); + let time = new Date().getTime(); + let s = new StorageService(storageImpl, null, contextService.getWorkspace2(), time); s.store('key1', 'foobar'); s.store('key2', 'something'); s.store('wkey1', 'foo', StorageScope.WORKSPACE); s.store('wkey2', 'foo2', StorageScope.WORKSPACE); - s = new StorageService(storageImpl, null, contextService.getWorkspace()); + s = new StorageService(storageImpl, null, contextService.getWorkspace2(), time); assert.strictEqual(s.get('key1', StorageScope.GLOBAL), 'foobar'); assert.strictEqual(s.get('key1', StorageScope.WORKSPACE, null), null); @@ -95,9 +93,7 @@ suite('Workbench StorageSevice', () => { assert.strictEqual(s.get('wkey1', StorageScope.WORKSPACE), 'foo'); assert.strictEqual(s.get('wkey2', StorageScope.WORKSPACE), 'foo2'); - ws = new LegacyWorkspace(TestWorkspace.resource, Date.now()); - ws.uid = new Date().getTime() + 100; - s = new StorageService(storageImpl, null, ws); + s = new StorageService(storageImpl, null, contextService.getWorkspace2(), time + 100); assert.strictEqual(s.get('key1', StorageScope.GLOBAL), 'foobar'); assert.strictEqual(s.get('key1', StorageScope.WORKSPACE, null), null); diff --git a/src/vs/platform/telemetry/test/electron-browser/commonProperties.test.ts b/src/vs/platform/telemetry/test/electron-browser/commonProperties.test.ts index a5c00cac8c8..a504489d833 100644 --- a/src/vs/platform/telemetry/test/electron-browser/commonProperties.test.ts +++ b/src/vs/platform/telemetry/test/electron-browser/commonProperties.test.ts @@ -10,7 +10,6 @@ import { resolveWorkbenchCommonProperties } from 'vs/platform/telemetry/node/wor import { StorageService, InMemoryLocalStorage } from 'vs/platform/storage/common/storageService'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; - suite('Telemetry - common properties', function () { const commit = void 0; diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 9c5a90e9c0c..976243684bf 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -56,7 +56,7 @@ export interface IWindowsService { getWindowCount(): TPromise; log(severity: string, ...messages: string[]): TPromise; // TODO@joao: what? - closeExtensionHostWindow(extensionDevelopmentPath: string): TPromise; + closeExtensionHostWindow(extensionDevelopmentPaths: string[]): TPromise; showItemInFolder(path: string): TPromise; // This needs to be handled from browser process to prevent diff --git a/src/vs/platform/windows/common/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts index 01840d84f1e..4edb0401da5 100644 --- a/src/vs/platform/windows/common/windowsIpc.ts +++ b/src/vs/platform/windows/common/windowsIpc.ts @@ -43,7 +43,7 @@ export interface IWindowsChannel extends IChannel { call(command: 'whenSharedProcessReady'): TPromise; call(command: 'toggleSharedProcess'): TPromise; call(command: 'log', arg: [string, string[]]): TPromise; - call(command: 'closeExtensionHostWindow', arg: string): TPromise; + call(command: 'closeExtensionHostWindow', arg: string[]): TPromise; call(command: 'showItemInFolder', arg: string): TPromise; call(command: 'openExternal', arg: string): TPromise; call(command: 'startCrashReporter', arg: Electron.CrashReporterStartOptions): TPromise; @@ -235,8 +235,8 @@ export class WindowsChannelClient implements IWindowsService { return this.channel.call('log', [severity, messages]); } - closeExtensionHostWindow(extensionDevelopmentPath: string): TPromise { - return this.channel.call('closeExtensionHostWindow', extensionDevelopmentPath); + closeExtensionHostWindow(extensionDevelopmentPaths: string[]): TPromise { + return this.channel.call('closeExtensionHostWindow', extensionDevelopmentPaths); } showItemInFolder(path: string): TPromise { diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index f1c49b83fea..82bdf27e4c9 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -263,12 +263,12 @@ export class WindowsService implements IWindowsService, IDisposable { return TPromise.as(null); } - closeExtensionHostWindow(extensionDevelopmentPath: string): TPromise { - const windowOnExtension = this.windowsMainService.findWindow(null, null, extensionDevelopmentPath); - - if (windowOnExtension) { - windowOnExtension.win.close(); - } + closeExtensionHostWindow(extensionDevelopmentPaths: string[]): TPromise { + extensionDevelopmentPaths.map(p => this.windowsMainService.findWindow(null, null, p)).forEach(windowOnExtension => { + if (windowOnExtension) { + windowOnExtension.win.close(); + } + }); return TPromise.as(null); } diff --git a/src/vs/platform/workspace/test/common/testWorkspace.ts b/src/vs/platform/workspace/test/common/testWorkspace.ts index ed097d9c90b..e5453ed88c3 100644 --- a/src/vs/platform/workspace/test/common/testWorkspace.ts +++ b/src/vs/platform/workspace/test/common/testWorkspace.ts @@ -3,10 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { LegacyWorkspace } from 'vs/platform/workspace/common/workspace'; import URI from 'vs/base/common/uri'; +import { Workspace } from "vs/platform/workspace/common/workspace"; -export const TestWorkspace = new LegacyWorkspace( - URI.file('C:\\testWorkspace'), - Date.now() +const wsUri = URI.file('C:\\testWorkspace'); +export const TestWorkspace = new Workspace( + wsUri.toString(), + wsUri.fsPath, + [wsUri] ); diff --git a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts index 1cde185a17d..9e6799edbf1 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts @@ -49,13 +49,13 @@ export class MainThreadWorkspace extends MainThreadWorkspaceShape { // --- search --- $startSearch(include: string, exclude: string, maxResults: number, requestId: number): Thenable { - const workspace = this._contextService.getWorkspace(); + const workspace = this._contextService.getWorkspace2(); if (!workspace) { return undefined; } const search = this._searchService.search({ - folderResources: [workspace.resource], + folderResources: workspace.roots, type: QueryType.File, maxResults, includePattern: { [include]: true }, diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 7f698036e98..c82b28a40a3 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -57,6 +57,7 @@ export class ResourceLabel extends IconLabel { private registerListeners(): void { this.extensionService.onReady().then(() => this.render(true /* clear cache */)); // update when extensions are loaded with potentially new languages this.toDispose.push(this.configurationService.onDidUpdateConfiguration(() => this.render(true /* clear cache */))); // update when file.associations change + this.toDispose.push(this.contextService.onDidChangeWorkspaceRoots(() => this.render(true /* clear cache */))); // update when roots change } public setLabel(label: IEditorLabel, options?: IResourceLabelOptions): void { diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 386e3a35f88..2298b18f4b1 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -71,7 +71,7 @@ export class TitlebarPart extends Part implements ITitleService { this.isPure = true; this.activeEditorListeners = []; - this.workspacePath = contextService.hasWorkspace() ? labels.getPathLabel(contextService.getWorkspace().resource, void 0, environmentService) : ''; + this.computeWorkspacePath(); this.init(); @@ -100,6 +100,7 @@ export class TitlebarPart extends Part implements ITitleService { this.toUnbind.push(DOM.addDisposableListener(window, DOM.EventType.FOCUS, () => this.onFocus())); this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(() => this.onConfigurationChanged(true))); this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged())); + this.toUnbind.push(this.contextService.onDidChangeWorkspaceRoots(() => this.onDidChangeWorkspaceRoots())); } private onBlur(): void { @@ -112,6 +113,20 @@ export class TitlebarPart extends Part implements ITitleService { this.updateStyles(); } + private onDidChangeWorkspaceRoots(): void { + this.computeWorkspacePath(); + this.setTitle(this.getWindowTitle()); + } + + private computeWorkspacePath(): void { + const workspace = this.contextService.getWorkspace2(); + if (workspace && workspace.roots.length === 1) { + this.workspacePath = labels.getPathLabel(workspace.roots[0], void 0, this.environmentService); + } else { + this.workspacePath = ''; + } + } + private onConfigurationChanged(update?: boolean): void { const currentTitleTemplate = this.titleTemplate; this.titleTemplate = this.configurationService.lookup('window.title').value; diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index 4598ee7a6fd..6fd5754cbe1 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -30,7 +30,7 @@ import { KeyboardMapperFactory } from "vs/workbench/services/keybinding/electron import { IWindowConfiguration, IPath } from "vs/platform/windows/common/windows"; import { IStorageService } from "vs/platform/storage/common/storage"; import { IEnvironmentService } from "vs/platform/environment/common/environment"; -import { StorageService, inMemoryLocalStorageInstance, IWorkspaceStorageIdentifier } from "vs/platform/storage/common/storageService"; +import { StorageService, inMemoryLocalStorageInstance } from "vs/platform/storage/common/storageService"; import { webFrame } from 'electron'; @@ -97,10 +97,11 @@ function toInputs(paths: IPath[], isUntitledFile?: boolean): IResourceInput[] { function openWorkbench(configuration: IWindowConfiguration, options: IOptions): TPromise { return resolveLegacyWorkspace(configuration).then(legacyWorkspace => { + const workspace = legacyWorkspaceToMultiRootWorkspace(legacyWorkspace); const environmentService = new EnvironmentService(configuration, configuration.execPath); - const workspaceConfigurationService = new WorkspaceConfigurationService(environmentService, legacyWorkspaceToMultiRootWorkspace(legacyWorkspace)); - const timerService = new TimerService((window).MonacoEnvironment.timers as IInitData, !!legacyWorkspace); - const storageService = createStorageService(legacyWorkspace, configuration, environmentService); + const workspaceConfigurationService = new WorkspaceConfigurationService(environmentService, workspace); + const timerService = new TimerService((window).MonacoEnvironment.timers as IInitData, !!workspace); + const storageService = createStorageService(legacyWorkspace, workspace, configuration, environmentService); // Since the configuration service is one of the core services that is used in so many places, we initialize it // right before startup of the workbench shell to have its data ready for consumers @@ -135,11 +136,15 @@ function openWorkbench(configuration: IWindowConfiguration, options: IOptions): } function legacyWorkspaceToMultiRootWorkspace(legacyWorkspace: LegacyWorkspace): Workspace { - return legacyWorkspace ? new Workspace( + if (!legacyWorkspace) { + return null; + } + + return new Workspace( createHash('md5').update(legacyWorkspace.resource.fsPath).update(legacyWorkspace.ctime ? String(legacyWorkspace.ctime) : '').digest('hex'), path.basename(legacyWorkspace.resource.fsPath), [legacyWorkspace.resource] - ) : null; + ); } function resolveLegacyWorkspace(configuration: IWindowConfiguration): TPromise { @@ -172,10 +177,10 @@ function resolveLegacyWorkspace(configuration: IWindowConfiguration): TPromise r.fsPath)); } if (session && session.getId() === event.body.sessionId) { this.onSessionEnd(session); diff --git a/src/vs/workbench/parts/emmet/electron-browser/emmetActions.ts b/src/vs/workbench/parts/emmet/electron-browser/emmetActions.ts index adef8ed3893..cea4cfb76a3 100644 --- a/src/vs/workbench/parts/emmet/electron-browser/emmetActions.ts +++ b/src/vs/workbench/parts/emmet/electron-browser/emmetActions.ts @@ -285,7 +285,7 @@ export abstract class EmmetEditorAction extends EditorAction { const modeService = accessor.get(IModeService); const messageService = accessor.get(IMessageService); const contextService = accessor.get(IWorkspaceContextService); - const workspaceRoot = contextService.getWorkspace() ? contextService.getWorkspace().resource.fsPath : ''; + const workspaceRoot = contextService.hasWorkspace() ? contextService.getWorkspace().resource.fsPath : ''; const telemetryService = accessor.get(ITelemetryService); const commandService = accessor.get(ICommandService); diff --git a/src/vs/workbench/parts/search/browser/openFileHandler.ts b/src/vs/workbench/parts/search/browser/openFileHandler.ts index 1ff6554fa37..20baf4efc19 100644 --- a/src/vs/workbench/parts/search/browser/openFileHandler.ts +++ b/src/vs/workbench/parts/search/browser/openFileHandler.ts @@ -147,7 +147,7 @@ export class OpenFileHandler extends QuickOpenHandler { private doFindResults(searchValue: string, cacheKey?: string, maxSortedResults?: number): TPromise { const query: IQueryOptions = { - folderResources: this.contextService.hasWorkspace() ? [this.contextService.getWorkspace().resource] : [], + folderResources: this.contextService.hasWorkspace() ? this.contextService.getWorkspace2().roots : [], extraFileResources: getOutOfWorkspaceEditorResources(this.editorGroupService, this.contextService), filePattern: searchValue, cacheKey: cacheKey @@ -189,7 +189,7 @@ export class OpenFileHandler extends QuickOpenHandler { private cacheQuery(cacheKey: string): ISearchQuery { const options: IQueryOptions = { - folderResources: this.contextService.hasWorkspace() ? [this.contextService.getWorkspace().resource] : [], + folderResources: this.contextService.hasWorkspace() ? this.contextService.getWorkspace2().roots : [], extraFileResources: getOutOfWorkspaceEditorResources(this.editorGroupService, this.contextService), filePattern: '', cacheKey: cacheKey, diff --git a/src/vs/workbench/parts/search/browser/searchActions.ts b/src/vs/workbench/parts/search/browser/searchActions.ts index 256f1ec1a2d..3c6d6a79993 100644 --- a/src/vs/workbench/parts/search/browser/searchActions.ts +++ b/src/vs/workbench/parts/search/browser/searchActions.ts @@ -28,7 +28,6 @@ import { toResource } from 'vs/workbench/common/editor'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IListService } from 'vs/platform/list/browser/listService'; import { explorerItemToFileResource } from 'vs/workbench/parts/files/common/files'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { OS } from 'vs/base/common/platform'; export function isSearchViewletFocussed(viewletService: IViewletService): boolean { @@ -262,7 +261,6 @@ export class FindInFolderAction extends Action { export const findInFolderCommand = (accessor: ServicesAccessor, resource?: URI) => { const listService = accessor.get(IListService); const viewletService = accessor.get(IViewletService); - const contextService = accessor.get(IWorkspaceContextService); if (!URI.isUri(resource)) { const focused = listService.getFocused() ? listService.getFocused().getFocus() : void 0; @@ -274,15 +272,9 @@ export const findInFolderCommand = (accessor: ServicesAccessor, resource?: URI) } } - if (!URI.isUri(resource) && contextService.hasWorkspace()) { - resource = contextService.getWorkspace().resource; - } - - if (URI.isUri(resource)) { - viewletService.openViewlet(Constants.VIEWLET_ID, true).then((viewlet: SearchViewlet) => { - viewlet.searchInFolder(resource); - }).done(null, errors.onUnexpectedError); - } + viewletService.openViewlet(Constants.VIEWLET_ID, true).then((viewlet: SearchViewlet) => { + viewlet.searchInFolder(resource); + }).done(null, errors.onUnexpectedError); }; export class RefreshAction extends Action { diff --git a/src/vs/workbench/parts/search/browser/searchViewlet.ts b/src/vs/workbench/parts/search/browser/searchViewlet.ts index 2f6f1b58471..a20fae02809 100644 --- a/src/vs/workbench/parts/search/browser/searchViewlet.ts +++ b/src/vs/workbench/parts/search/browser/searchViewlet.ts @@ -889,13 +889,9 @@ export class SearchViewlet extends Viewlet { } } - public searchInFolder(resource: URI): void { - const workspace = this.contextService.getWorkspace(); - if (!workspace) { - return; - } - - if (workspace.resource.toString() === resource.toString()) { + public searchInFolder(resource?: URI): void { + const workspaceRelativePath = this.contextService.toWorkspaceRelativePath(resource); + if (!workspaceRelativePath || workspaceRelativePath === '.') { this.inputPatternIncludes.setValue(''); this.searchWidget.focus(); return; @@ -904,12 +900,10 @@ export class SearchViewlet extends Viewlet { if (!this.showsFileTypes()) { this.toggleQueryDetails(true, true); } - const workspaceRelativePath = this.contextService.toWorkspaceRelativePath(resource); - if (workspaceRelativePath) { - this.inputPatternIncludes.setIsGlobPattern(false); - this.inputPatternIncludes.setValue('./' + workspaceRelativePath); - this.searchWidget.focus(false); - } + + this.inputPatternIncludes.setIsGlobPattern(false); + this.inputPatternIncludes.setValue('./' + workspaceRelativePath); + this.searchWidget.focus(false); } public onQueryChanged(rerunQuery: boolean, preserveFocus?: boolean): void { @@ -956,7 +950,7 @@ export class SearchViewlet extends Viewlet { const { expression: includePattern, searchPaths } = this.inputPatternIncludes.getGlob(); const options: IQueryOptions = { - folderResources: this.contextService.hasWorkspace() ? [this.contextService.getWorkspace().resource] : [], + folderResources: this.contextService.hasWorkspace() ? this.contextService.getWorkspace2().roots : [], extraFileResources: getOutOfWorkspaceEditorResources(this.editorGroupService, this.contextService), excludePattern, includePattern, 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 220f2adccfd..81cd2677aa3 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 @@ -8,7 +8,7 @@ import * as assert from 'assert'; import { Platform } from 'vs/base/common/platform'; import { TerminalLinkHandler, LineColumnInfo } from 'vs/workbench/parts/terminal/electron-browser/terminalLinkHandler'; -import { LegacyWorkspace } from 'vs/platform/workspace/common/workspace'; +import { Workspace } from 'vs/platform/workspace/common/workspace'; import { TestContextService } from 'vs/workbench/test/workbenchTestServices'; import URI from 'vs/base/common/uri'; import * as strings from 'vs/base/common/strings'; @@ -45,9 +45,9 @@ interface LinkFormatInfo { column?: string; } -class TestWorkspace extends LegacyWorkspace { +class TestWorkspace extends Workspace { constructor(private basePath: string) { - super(new TestURI(basePath)); + super(basePath, basePath, [new TestURI(basePath)]); } } diff --git a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts b/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts index 56c00efa87e..850a40565b0 100644 --- a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts +++ b/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts @@ -227,8 +227,8 @@ class WelcomePage { recentlyOpened.then(({ folders }) => { if (this.contextService.hasWorkspace()) { - const current = this.contextService.getWorkspace().resource.fsPath; - folders = folders.filter(folder => !this.pathEquals(folder, current)); + const currents = this.contextService.getWorkspace2().roots; + folders = folders.filter(folder => !currents.some(current => this.pathEquals(folder, current.fsPath))); } if (!folders.length) { const recent = container.querySelector('.welcomePage') as HTMLElement; diff --git a/src/vs/workbench/services/backup/test/node/backupFileService.test.ts b/src/vs/workbench/services/backup/test/node/backupFileService.test.ts index a6eb9b517d6..34006542bc8 100644 --- a/src/vs/workbench/services/backup/test/node/backupFileService.test.ts +++ b/src/vs/workbench/services/backup/test/node/backupFileService.test.ts @@ -20,6 +20,7 @@ import { EnvironmentService } from 'vs/platform/environment/node/environmentServ import { parseArgs } from 'vs/platform/environment/node/argv'; import { RawTextSource } from 'vs/editor/common/model/textSource'; import { TestContextService } from 'vs/workbench/test/workbenchTestServices'; +import { Workspace } from "vs/platform/workspace/common/workspace"; class TestEnvironmentService extends EnvironmentService { @@ -48,7 +49,7 @@ const untitledBackupPath = path.join(workspaceBackupPath, 'untitled', crypto.cre class TestBackupFileService extends BackupFileService { constructor(workspace: Uri, backupHome: string, workspacesJsonPath: string) { - const fileService = new FileService(workspace.fsPath, { disableWatcher: true }, new TestContextService()); + const fileService = new FileService(new TestContextService(new Workspace(workspace.fsPath, workspace.fsPath, [workspace])), { disableWatcher: true }); super(workspaceBackupPath, fileService); } 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 89653f30eb3..85e6b5df3a6 100644 --- a/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts @@ -124,7 +124,7 @@ suite('ConfigurationEditingService', () => { instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(IModeService, ModeServiceImpl); instantiationService.stub(IModelService, instantiationService.createInstance(ModelServiceImpl)); - instantiationService.stub(IFileService, instantiationService.createInstance(FileService, workspaceDir, { disableWatcher: true })); + instantiationService.stub(IFileService, new FileService(workspaceService, { disableWatcher: true })); instantiationService.stub(IUntitledEditorService, instantiationService.createInstance(UntitledEditorService)); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index b717a758fe2..b9e496cb4f9 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -19,7 +19,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import nls = require('vs/nls'); -import { getPathLabel, IWorkspaceProvider } from 'vs/base/common/labels'; +import { getPathLabel } from 'vs/base/common/labels'; import { ResourceMap } from 'vs/base/common/map'; import { once } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -287,7 +287,7 @@ export class WorkbenchEditorService implements IWorkbenchEditorService { return input; } - private toDiffLabel(res1: URI, res2: URI, context: IWorkspaceProvider, environment: IEnvironmentService): string { + private toDiffLabel(res1: URI, res2: URI, context: IWorkspaceContextService, environment: IEnvironmentService): string { const leftName = getPathLabel(res1.fsPath, context, environment); const rightName = getPathLabel(res2.fsPath, context, environment); diff --git a/src/vs/workbench/services/files/electron-browser/fileService.ts b/src/vs/workbench/services/files/electron-browser/fileService.ts index aad50b0289f..8f011de77bd 100644 --- a/src/vs/workbench/services/files/electron-browser/fileService.ts +++ b/src/vs/workbench/services/files/electron-browser/fileService.ts @@ -48,7 +48,7 @@ export class FileService implements IFileService { @IConfigurationService private configurationService: IConfigurationService, @IWorkspaceContextService private contextService: IWorkspaceContextService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @IEnvironmentService environmentService: IEnvironmentService, + @IEnvironmentService private environmentService: IEnvironmentService, @IEditorGroupService private editorGroupService: IEditorGroupService, @ILifecycleService private lifecycleService: ILifecycleService, @IMessageService private messageService: IMessageService, @@ -65,13 +65,6 @@ export class FileService implements IFileService { const configuration = this.configurationService.getConfiguration(); - // adjust encodings - const encodingOverride: IEncodingOverride[] = []; - encodingOverride.push({ resource: uri.file(environmentService.appSettingsHome), encoding: encoding.UTF8 }); - if (this.contextService.hasWorkspace()) { - encodingOverride.push({ resource: uri.file(paths.join(this.contextService.getWorkspace().resource.fsPath, '.vscode')), encoding: encoding.UTF8 }); - } - let watcherIgnoredPatterns: string[] = []; if (configuration.files && configuration.files.watcherExclude) { watcherIgnoredPatterns = Object.keys(configuration.files.watcherExclude).filter(k => !!configuration.files.watcherExclude[k]); @@ -82,15 +75,14 @@ export class FileService implements IFileService { errorLogger: (msg: string) => this.onFileServiceError(msg), encoding: configuration.files && configuration.files.encoding, autoGuessEncoding: configuration.files && configuration.files.autoGuessEncoding, - encodingOverride, + encodingOverride: this.getEncodingOverrides(), watcherIgnoredPatterns, verboseLogging: environmentService.verbose, useExperimentalFileWatcher: configuration.files.useExperimentalFileWatcher }; // create service - const workspace = this.contextService.getWorkspace(); - this.raw = new NodeFileService(workspace ? workspace.resource.fsPath : void 0, fileServiceConfig, contextService); + this.raw = new NodeFileService(contextService, fileServiceConfig); // Listeners this.registerListeners(); @@ -140,10 +132,29 @@ export class FileService implements IFileService { // Editor changing this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged())); + // Root changes + this.toUnbind.push(this.contextService.onDidChangeWorkspaceRoots(() => this.onDidChangeWorkspaceRoots())); + // Lifecycle this.lifecycleService.onShutdown(this.dispose, this); } + private onDidChangeWorkspaceRoots(): void { + this.updateOptions({ encodingOverride: this.getEncodingOverrides() }); + } + + private getEncodingOverrides(): IEncodingOverride[] { + const encodingOverride: IEncodingOverride[] = []; + encodingOverride.push({ resource: uri.file(this.environmentService.appSettingsHome), encoding: encoding.UTF8 }); + if (this.contextService.hasWorkspace()) { + this.contextService.getWorkspace2().roots.forEach(root => { + encodingOverride.push({ resource: uri.file(paths.join(root.fsPath, '.vscode')), encoding: encoding.UTF8 }); + }); + } + + return encodingOverride; + } + private onEditorsChanged(): void { this.handleOutOfWorkspaceWatchers(); } @@ -240,11 +251,6 @@ export class FileService implements IFileService { } private doMoveItemToTrash(resource: uri): TPromise { - const workspace = this.contextService.getWorkspace(); - if (!workspace) { - return TPromise.wrapError('Need a workspace to use this'); - } - const absolutePath = resource.fsPath; const result = shell.moveItemToTrash(absolutePath); if (!result) { diff --git a/src/vs/workbench/services/files/node/fileService.ts b/src/vs/workbench/services/files/node/fileService.ts index 2c1291608a2..23222a1ac43 100644 --- a/src/vs/workbench/services/files/node/fileService.ts +++ b/src/vs/workbench/services/files/node/fileService.ts @@ -12,7 +12,6 @@ import crypto = require('crypto'); import assert = require('assert'); import { isParent, FileOperation, FileOperationEvent, IContent, IFileService, IResolveFileOptions, IResolveContentOptions, IFileStat, IStreamContent, IFileOperationResult, FileOperationResult, IUpdateContentOptions, FileChangeType, IImportResult, MAX_FILE_SIZE, FileChangesEvent } from 'vs/platform/files/common/files'; -import strings = require('vs/base/common/strings'); import { isEqualOrParent } from 'vs/base/common/paths'; import { ResourceMap } from 'vs/base/common/map'; import arrays = require('vs/base/common/arrays'); @@ -80,7 +79,6 @@ export class FileService implements IFileService { private static FS_REWATCH_DELAY = 300; // delay to rewatch a file that was renamed or deleted (in ms) private static MAX_DEGREE_OF_PARALLEL_FS_OPS = 10; // degree of parallel fs calls that we accept at the same time - private basePath: string; private tmpPath: string; private options: IFileServiceOptions; @@ -94,25 +92,10 @@ export class FileService implements IFileService { private undeliveredRawFileChangesEvents: IRawFileChange[]; constructor( - basePath: string, + private contextService: IWorkspaceContextService, options: IFileServiceOptions, - private contextService: IWorkspaceContextService ) { this.toDispose = []; - this.basePath = basePath ? paths.normalize(basePath) : void 0; - - if (this.basePath && this.basePath.indexOf('\\\\') === 0 && strings.endsWith(this.basePath, paths.sep)) { - // for some weird reason, node adds a trailing slash to UNC paths - // we never ever want trailing slashes as our base path unless - // someone opens root ("/"). - // See also https://github.com/nodejs/io.js/issues/1765 - this.basePath = strings.rtrim(this.basePath, paths.sep); - } - - if (this.basePath && !paths.isAbsolute(basePath)) { - throw new Error('basePath has to be an absolute path'); - } - this.options = options || Object.create(null); this.tmpPath = this.options.tmpDir || os.tmpdir(); @@ -126,7 +109,7 @@ export class FileService implements IFileService { this.options.errorLogger = console.error; } - if (this.basePath && !this.options.disableWatcher) { + if (contextService.hasWorkspace() && !this.options.disableWatcher) { if (this.options.useExperimentalFileWatcher) { this.setupNsfwWorkspceWatching(); } else { @@ -158,15 +141,15 @@ export class FileService implements IFileService { } private setupWin32WorkspaceWatching(): void { - this.toDispose.push(toDisposable(new WindowsWatcherService(this.basePath, this.options.watcherIgnoredPatterns, e => this._onFileChanges.fire(e), this.options.errorLogger, this.options.verboseLogging).startWatching())); + this.toDispose.push(toDisposable(new WindowsWatcherService(this.contextService, this.options.watcherIgnoredPatterns, e => this._onFileChanges.fire(e), this.options.errorLogger, this.options.verboseLogging).startWatching())); } private setupUnixWorkspaceWatching(): void { - this.toDispose.push(toDisposable(new UnixWatcherService(this.basePath, this.options.watcherIgnoredPatterns, e => this._onFileChanges.fire(e), this.options.errorLogger, this.options.verboseLogging).startWatching())); + this.toDispose.push(toDisposable(new UnixWatcherService(this.contextService, this.options.watcherIgnoredPatterns, e => this._onFileChanges.fire(e), this.options.errorLogger, this.options.verboseLogging).startWatching())); } private setupNsfwWorkspceWatching(): void { - this.toDispose.push(toDisposable(new NsfwWatcherService(this.basePath, this.options.watcherIgnoredPatterns, e => this._onFileChanges.fire(e), this.options.errorLogger, this.options.verboseLogging, this.contextService).startWatching())); + this.toDispose.push(toDisposable(new NsfwWatcherService(this.contextService, this.options.watcherIgnoredPatterns, e => this._onFileChanges.fire(e), this.options.errorLogger, this.options.verboseLogging).startWatching())); } public resolveFile(resource: uri, options?: IResolveFileOptions): TPromise { diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts index cc5bb1f690d..853020a5f44 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts @@ -13,6 +13,7 @@ import { toFileChangesEvent, IRawFileChange } from 'vs/workbench/services/files/ import { IWatcherChannel, WatcherChannelClient } from 'vs/workbench/services/files/node/watcher/nsfw/watcherIpc'; import { FileChangesEvent } from 'vs/platform/files/common/files'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { normalize } from "path"; export class FileWatcher { private static MAX_RESTARTS = 5; @@ -21,12 +22,11 @@ export class FileWatcher { private restartCounter: number; constructor( - private basePath: string, + private contextService: IWorkspaceContextService, private ignored: string[], private onFileChanges: (changes: FileChangesEvent) => void, private errorLogger: (msg: string) => void, private verboseLogging: boolean, - private contextService: IWorkspaceContextService ) { this.isDisposed = false; this.restartCounter = 0; @@ -52,7 +52,8 @@ export class FileWatcher { const service = new WatcherChannelClient(channel); // Start watching - service.watch({ basePath: this.basePath, ignored: this.ignored, verboseLogging: this.verboseLogging }).then(null, (err) => { + const basePath: string = normalize(this.contextService.getWorkspace2().roots[0].fsPath); + service.watch({ basePath, ignored: this.ignored, verboseLogging: this.verboseLogging }).then(null, (err) => { if (!(err instanceof Error && err.name === 'Canceled' && err.message === 'Canceled')) { return TPromise.wrapError(err); // the service lib uses the promise cancel error to indicate the process died, we do not want to bubble this up } diff --git a/src/vs/workbench/services/files/node/watcher/unix/watcherService.ts b/src/vs/workbench/services/files/node/watcher/unix/watcherService.ts index 241ffa853ca..bee660a8777 100644 --- a/src/vs/workbench/services/files/node/watcher/unix/watcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/watcherService.ts @@ -12,6 +12,8 @@ import uri from 'vs/base/common/uri'; import { toFileChangesEvent, IRawFileChange } from 'vs/workbench/services/files/node/watcher/common'; import { IWatcherChannel, WatcherChannelClient } from 'vs/workbench/services/files/node/watcher/unix/watcherIpc'; import { FileChangesEvent } from 'vs/platform/files/common/files'; +import { IWorkspaceContextService } from "vs/platform/workspace/common/workspace"; +import { normalize } from "path"; export class FileWatcher { private static MAX_RESTARTS = 5; @@ -20,7 +22,7 @@ export class FileWatcher { private restartCounter: number; constructor( - private basePath: string, + private contextService: IWorkspaceContextService, private ignored: string[], private onFileChanges: (changes: FileChangesEvent) => void, private errorLogger: (msg: string) => void, @@ -50,7 +52,8 @@ export class FileWatcher { const service = new WatcherChannelClient(channel); // Start watching - service.watch({ basePath: this.basePath, ignored: this.ignored, verboseLogging: this.verboseLogging }).then(null, (err) => { + const basePath: string = normalize(this.contextService.getWorkspace2().roots[0].fsPath); + service.watch({ basePath: basePath, ignored: this.ignored, verboseLogging: this.verboseLogging }).then(null, (err) => { if (!(err instanceof Error && err.name === 'Canceled' && err.message === 'Canceled')) { return TPromise.wrapError(err); // the service lib uses the promise cancel error to indicate the process died, we do not want to bubble this up } @@ -63,7 +66,6 @@ export class FileWatcher { if (this.restartCounter <= FileWatcher.MAX_RESTARTS) { this.errorLogger('[FileWatcher] terminated unexpectedly and is restarted again...'); this.restartCounter++; - // TODO: What do we do for multi-root here? this.startWatching(); } else { this.errorLogger('[FileWatcher] failed to start after retrying for some time, giving up. Please report this as a bug report!'); diff --git a/src/vs/workbench/services/files/node/watcher/win32/watcherService.ts b/src/vs/workbench/services/files/node/watcher/win32/watcherService.ts index 50cbf234661..379a08b06d4 100644 --- a/src/vs/workbench/services/files/node/watcher/win32/watcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/win32/watcherService.ts @@ -8,11 +8,15 @@ import { IRawFileChange, toFileChangesEvent } from 'vs/workbench/services/files/node/watcher/common'; import { OutOfProcessWin32FolderWatcher } from 'vs/workbench/services/files/node/watcher/win32/csharpWatcherService'; import { FileChangesEvent } from 'vs/platform/files/common/files'; +import { IWorkspaceContextService } from "vs/platform/workspace/common/workspace"; +import { normalize } from "path"; +import { rtrim, endsWith } from "vs/base/common/strings"; +import { sep } from "vs/base/common/paths"; export class FileWatcher { constructor( - private basePath: string, + private contextService: IWorkspaceContextService, private ignored: string[], private onFileChanges: (changes: FileChangesEvent) => void, private errorLogger: (msg: string) => void, @@ -21,8 +25,18 @@ export class FileWatcher { } public startWatching(): () => void { - let watcher = new OutOfProcessWin32FolderWatcher( - this.basePath, + let basePath: string = normalize(this.contextService.getWorkspace2().roots[0].fsPath); + + if (basePath && basePath.indexOf('\\\\') === 0 && endsWith(basePath, sep)) { + // for some weird reason, node adds a trailing slash to UNC paths + // we never ever want trailing slashes as our base path unless + // someone opens root ("/"). + // See also https://github.com/nodejs/io.js/issues/1765 + basePath = rtrim(basePath, sep); + } + + const watcher = new OutOfProcessWin32FolderWatcher( + basePath, this.ignored, (events) => this.onRawFileEvents(events), (error) => this.onError(error), diff --git a/src/vs/workbench/services/files/test/node/fileService.test.ts b/src/vs/workbench/services/files/test/node/fileService.test.ts index 4622af59cda..f970ed1a92b 100644 --- a/src/vs/workbench/services/files/test/node/fileService.test.ts +++ b/src/vs/workbench/services/files/test/node/fileService.test.ts @@ -20,6 +20,7 @@ import encodingLib = require('vs/base/node/encoding'); import utils = require('vs/workbench/services/files/test/node/utils'); import { onError } from 'vs/base/test/common/utils'; import { TestContextService } from "vs/workbench/test/workbenchTestServices"; +import { Workspace } from "vs/platform/workspace/common/workspace"; suite('FileService', () => { let service: FileService; @@ -36,7 +37,7 @@ suite('FileService', () => { return onError(error, done); } - service = new FileService(testDir, { disableWatcher: true }, new TestContextService()); + service = new FileService(new TestContextService(new Workspace(testDir, testDir, [uri.file(testDir)])), { disableWatcher: true }); done(); }); }); @@ -732,11 +733,11 @@ suite('FileService', () => { encoding: 'utf16le' }); - let _service = new FileService(_testDir, { + let _service = new FileService(new TestContextService(new Workspace(_testDir, _testDir, [uri.file(_testDir)])), { encoding: 'windows1252', - encodingOverride: encodingOverride, + encodingOverride, disableWatcher: true - }, new TestContextService()); + }); _service.resolveContent(uri.file(path.join(testDir, 'index.html'))).done(c => { assert.equal(c.encoding, 'windows1252'); @@ -760,9 +761,9 @@ suite('FileService', () => { let _sourceDir = require.toUrl('./fixtures/service'); let resource = uri.file(path.join(testDir, 'index.html')); - let _service = new FileService(_testDir, { + let _service = new FileService(new TestContextService(new Workspace(_testDir, _testDir, [uri.file(_testDir)])), { disableWatcher: true - }, new TestContextService()); + }); extfs.copy(_sourceDir, _testDir, () => { fs.readFile(resource.fsPath, (error, data) => { diff --git a/src/vs/workbench/services/keybinding/test/node/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/node/keybindingEditing.test.ts index 6c72dc9a758..1971b2d3b6d 100644 --- a/src/vs/workbench/services/keybinding/test/node/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/node/keybindingEditing.test.ts @@ -9,6 +9,7 @@ import assert = require('assert'); import os = require('os'); import path = require('path'); import fs = require('fs'); +import uri from 'vs/base/common/uri'; import * as json from 'vs/base/common/json'; import { OS } from 'vs/base/common/platform'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; @@ -17,7 +18,7 @@ import { KeyCode, SimpleKeybinding, ChordKeybinding } from 'vs/base/common/keyCo import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import extfs = require('vs/base/node/extfs'); import { TestTextFileService, TestEditorGroupService, TestLifecycleService, TestBackupFileService, TestContextService } from 'vs/workbench/test/workbenchTestServices'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, Workspace } from 'vs/platform/workspace/common/workspace'; import uuid = require('vs/base/common/uuid'); import { ConfigurationService } from 'vs/platform/configuration/node/configurationService'; import { FileService } from 'vs/workbench/services/files/node/fileService'; @@ -53,7 +54,7 @@ suite('Keybindings Editing', () => { let instantiationService: TestInstantiationService; let testObject: KeybindingsEditingService; - let testDir; + let testDir: string; let keybindingsFile; setup(() => { @@ -73,7 +74,7 @@ suite('Keybindings Editing', () => { instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(IModeService, ModeServiceImpl); instantiationService.stub(IModelService, instantiationService.createInstance(ModelServiceImpl)); - instantiationService.stub(IFileService, new FileService(testDir, { disableWatcher: true }, new TestContextService())); + instantiationService.stub(IFileService, new FileService(new TestContextService(new Workspace(testDir, testDir, [uri.file(testDir)])), { disableWatcher: true })); instantiationService.stub(IUntitledEditorService, instantiationService.createInstance(UntitledEditorService)); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index a6c96fcfe95..37078f3b9a5 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -64,7 +64,7 @@ export const TestEnvironmentService = new EnvironmentService(parseArgs(process.a export class TestContextService implements IWorkspaceContextService { public _serviceBrand: any; - private workspace: ILegacyWorkspace; + private workspace: IWorkspace; private id: string; private options: any; @@ -82,7 +82,7 @@ export class TestContextService implements IWorkspaceContextService { } public getFolders(): URI[] { - return this.workspace ? [this.workspace.resource] : []; + return this.workspace ? this.workspace.roots : []; } public hasWorkspace(): boolean { @@ -90,15 +90,15 @@ export class TestContextService implements IWorkspaceContextService { } public getWorkspace(): ILegacyWorkspace { - return this.workspace; + return this.workspace ? { resource: this.workspace.roots[0] } : void 0; } public getWorkspace2(): IWorkspace { - return this.workspace ? { id: this.id, roots: [this.workspace.resource], name: this.workspace.resource.fsPath } : void 0; + return this.workspace; } public getRoot(resource: URI): URI { - return this.isInsideWorkspace(resource) ? this.workspace.resource : null; + return this.isInsideWorkspace(resource) ? this.workspace.roots[0] : null; } public setWorkspace(workspace: any): void { @@ -115,7 +115,7 @@ export class TestContextService implements IWorkspaceContextService { public isInsideWorkspace(resource: URI): boolean { if (resource && this.workspace) { - return paths.isEqualOrParent(resource.fsPath, this.workspace.resource.fsPath, !isLinux /* ignorecase */); + return paths.isEqualOrParent(resource.fsPath, this.workspace.roots[0].fsPath, !isLinux /* ignorecase */); } return false; @@ -363,7 +363,7 @@ export class TestStorageService extends EventEmitter implements IStorageService super(); let context = new TestContextService(); - this.storage = new StorageService(new InMemoryLocalStorage(), null, context.getWorkspace()); + this.storage = new StorageService(new InMemoryLocalStorage(), null, context.getWorkspace2()); } store(key: string, value: any, scope: StorageScope = StorageScope.GLOBAL): void { @@ -1012,7 +1012,7 @@ export class TestWindowsService implements IWindowsService { return TPromise.as(void 0); } // TODO@joao: what? - closeExtensionHostWindow(extensionDevelopmentPath: string): TPromise { + closeExtensionHostWindow(extensionDevelopmentPaths: string[]): TPromise { return TPromise.as(void 0); } showItemInFolder(path: string): TPromise {