diff --git a/src/vs/platform/storage/test/storageService.test.ts b/src/vs/platform/storage/test/storageService.test.ts index 2978c54f134..2893d767a27 100644 --- a/src/vs/platform/storage/test/storageService.test.ts +++ b/src/vs/platform/storage/test/storageService.test.ts @@ -95,7 +95,7 @@ suite('Workbench StorageSevice', () => { assert.strictEqual(s.get('wkey1', StorageScope.WORKSPACE), 'foo'); assert.strictEqual(s.get('wkey2', StorageScope.WORKSPACE), 'foo2'); - ws = new Workspace(TestWorkspace.resource, TestWorkspace.name); + ws = new Workspace(TestWorkspace.resource, Date.now()); ws.uid = new Date().getTime() + 100; s = new StorageService(storageImpl, null, ws); diff --git a/src/vs/platform/workspace/common/workspace.ts b/src/vs/platform/workspace/common/workspace.ts index ab469fc3e8a..e5670d0fa6c 100644 --- a/src/vs/platform/workspace/common/workspace.ts +++ b/src/vs/platform/workspace/common/workspace.ts @@ -60,7 +60,6 @@ export interface IWorkspaceContextService { * Given a workspace relative path, returns the resource with the absolute path. */ toResource: (workspaceRelativePath: string) => URI; - } export interface IWorkspace { @@ -71,6 +70,11 @@ export interface IWorkspace { */ resource: URI; + /** + * the creation date of the workspace if known. + */ + ctime: number; + /** * the name of the workspace */ @@ -93,12 +97,13 @@ export interface IWorkspace2 { * Mutliple roots in this workspace. First entry is master and never changes. */ readonly roots: URI[]; - } export class Workspace implements IWorkspace { + private _name: string; - constructor(private _resource: URI, private _name?: string) { + constructor(private _resource: URI, private _ctime?: number) { + this._name = paths.basename(this._resource.fsPath) || this._resource.fsPath; } public get resource(): URI { @@ -109,6 +114,10 @@ export class Workspace implements IWorkspace { return this._name; } + public get ctime(): number { + return this._ctime; + } + public isInsideWorkspace(resource: URI): boolean { if (resource) { return isEqualOrParent(resource.fsPath, this._resource.fsPath, !isLinux /* ignorecase */); @@ -132,8 +141,4 @@ export class Workspace implements IWorkspace { return null; } - - public toJSON() { - return { resource: this._resource, name: this._name }; - } } \ No newline at end of file diff --git a/src/vs/platform/workspace/test/common/testWorkspace.ts b/src/vs/platform/workspace/test/common/testWorkspace.ts index 4400e7544ea..8933555a376 100644 --- a/src/vs/platform/workspace/test/common/testWorkspace.ts +++ b/src/vs/platform/workspace/test/common/testWorkspace.ts @@ -8,5 +8,5 @@ import URI from 'vs/base/common/uri'; export const TestWorkspace = new Workspace( URI.file('C:\\testWorkspace'), - 'Test Workspace' + Date.now() ); diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 20cf8960029..9cb2b395ee6 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -58,6 +58,7 @@ export interface IEnvironment { export interface IWorkspaceData { id: string; + name: string; roots: URI[]; } diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 67acd239b02..ff5a189ebb7 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -16,7 +16,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { createApiFactory, initializeExtensionApi } from 'vs/workbench/api/node/extHost.api.impl'; import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; import { MainContext, MainProcessExtensionServiceShape, IWorkspaceData, IEnvironment, IInitData } from './extHost.protocol'; -import { createHash } from 'crypto'; const hasOwnProperty = Object.hasOwnProperty; @@ -130,11 +129,7 @@ class ExtensionStoragePath { return TPromise.as(undefined); } // TODO@joh what to do with multiple roots? - const storageName = createHash('md5') - .update(this._workspace.roots[0].fsPath) - .update(this._workspace.id || '') - .digest('hex'); - + const storageName = this._workspace.id; const storagePath = join(this._environment.appSettingsHome, 'workspaceStorage', storageName); return dirExists(storagePath).then(exists => { diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index cf8ae033c6d..dd2f096321f 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -18,7 +18,7 @@ import paths = require('vs/base/common/paths'); import uri from 'vs/base/common/uri'; import strings = require('vs/base/common/strings'); import { IResourceInput } from 'vs/platform/editor/common/editor'; -import { Workspace } from 'vs/platform/workspace/common/workspace'; +import { IWorkspace, Workspace } from "vs/platform/workspace/common/workspace"; import { WorkspaceConfigurationService } from 'vs/workbench/services/configuration/node/configuration'; import { realpath, stat } from 'vs/base/node/pfs'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; @@ -95,47 +95,45 @@ function toInputs(paths: IPath[], isUntitledFile?: boolean): IResourceInput[] { } function openWorkbench(configuration: IWindowConfiguration, options: IOptions): TPromise { - return getWorkspace(configuration).then(workspace => { + return resolveWorkspace(configuration).then(workspace => { const environmentService = new EnvironmentService(configuration, configuration.execPath); const workspaceConfigurationService = new WorkspaceConfigurationService(environmentService, workspace); const timerService = new TimerService((window).MonacoEnvironment.timers as IInitData, !workspaceConfigurationService.hasWorkspace()); + const storageService = createStorageService(workspace, configuration, environmentService); - return createStorageService(configuration, environmentService).then(storageService => { + // 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 + return workspaceConfigurationService.initialize().then(() => { + timerService.beforeDOMContentLoaded = Date.now(); - // 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 - return workspaceConfigurationService.initialize().then(() => { - timerService.beforeDOMContentLoaded = Date.now(); + return domContentLoaded().then(() => { + timerService.afterDOMContentLoaded = Date.now(); - return domContentLoaded().then(() => { - timerService.afterDOMContentLoaded = Date.now(); + // Open Shell + timerService.beforeWorkbenchOpen = Date.now(); + const shell = new WorkbenchShell(document.body, { + contextService: workspaceConfigurationService, + configurationService: workspaceConfigurationService, + environmentService, + timerService, + storageService + }, configuration, options); + shell.open(); - // Open Shell - timerService.beforeWorkbenchOpen = Date.now(); - const shell = new WorkbenchShell(document.body, { - contextService: workspaceConfigurationService, - configurationService: workspaceConfigurationService, - environmentService, - timerService, - storageService - }, configuration, options); - shell.open(); - - // Inform user about loading issues from the loader - (self).require.config({ - onError: (err: any) => { - if (err.errorCode === 'load') { - shell.onUnexpectedError(loaderError(err)); - } + // Inform user about loading issues from the loader + (self).require.config({ + onError: (err: any) => { + if (err.errorCode === 'load') { + shell.onUnexpectedError(loaderError(err)); } - }); + } }); }); }); }); } -function getWorkspace(configuration: IWindowConfiguration): TPromise { +function resolveWorkspace(configuration: IWindowConfiguration): TPromise { if (!configuration.workspacePath) { return TPromise.as(null); } @@ -153,10 +151,11 @@ function getWorkspace(configuration: IWindowConfiguration): TPromise // update config configuration.workspacePath = realWorkspacePath; - const workspaceResource = uri.file(realWorkspacePath); - const folderName = path.basename(realWorkspacePath) || realWorkspacePath; - - return new Workspace(workspaceResource, folderName); + // resolve ctime of workspace + return stat(realWorkspacePath).then(folderStat => new Workspace( + uri.file(realWorkspacePath), + platform.isLinux ? folderStat.ino : folderStat.birthtime.getTime() // On Linux, birthtime is ctime, so we cannot use it! We use the ino instead! + )); }, error => { errors.onUnexpectedError(error); @@ -164,31 +163,24 @@ function getWorkspace(configuration: IWindowConfiguration): TPromise }); } -function createStorageService(configuration: IWindowConfiguration, environmentService: IEnvironmentService): TPromise { - let workspaceStatPromise: TPromise = TPromise.as(null); - if (configuration.workspacePath) { - workspaceStatPromise = stat(configuration.workspacePath); +function createStorageService(workspace: IWorkspace, configuration: IWindowConfiguration, environmentService: IEnvironmentService): IStorageService { + let id: IWorkspaceStorageIdentifier; + if (workspace) { + id = { resource: workspace.resource, uid: workspace.ctime }; + } else if (configuration.backupPath) { + // if we do not have a workspace open, we need to find another identifier for the window to store + // workspace UI state. if we have a backup path in the configuration we can use that because this + // will be a unique identifier per window that is stable between restarts as long as there are + // dirty files in the workspace. + // We use basename() to produce a short identifier, we do not need the full path. We use a custom + // scheme so that we can later distinguish these identifiers from the workspace one. + id = { resource: uri.from({ path: path.basename(configuration.backupPath), scheme: 'empty' }) }; } - return workspaceStatPromise.then(stat => { - let id: IWorkspaceStorageIdentifier; - if (stat) { - id = { resource: uri.file(configuration.workspacePath), uid: platform.isLinux ? stat.ino : stat.birthtime.getTime() }; - } else if (configuration.backupPath) { - // if we do not have a workspace open, we need to find another identifier for the window to store - // workspace UI state. if we have a backup path in the configuration we can use that because this - // will be a unique identifier per window that is stable between restarts as long as there are - // dirty files in the workspace. - // We use basename() to produce a short identifier, we do not need the full path. We use a custom - // scheme so that we can later distinguish these identifiers from the workspace one. - id = { resource: uri.from({ path: path.basename(configuration.backupPath), scheme: 'empty' }) }; - } + const disableStorage = !!environmentService.extensionTestsPath; // never keep any state when running extension tests! + const storage = disableStorage ? inMemoryLocalStorageInstance : window.localStorage; - const disableStorage = !!environmentService.extensionTestsPath; // never keep any state when running extension tests! - const storage = disableStorage ? inMemoryLocalStorageInstance : window.localStorage; - - return new StorageService(storage, storage, id); - }); + return new StorageService(storage, storage, id); } function loaderError(err: Error): Error { diff --git a/src/vs/workbench/node/extensionHostMain.ts b/src/vs/workbench/node/extensionHostMain.ts index 5c361eb9b06..3cd60b367ec 100644 --- a/src/vs/workbench/node/extensionHostMain.ts +++ b/src/vs/workbench/node/extensionHostMain.ts @@ -104,7 +104,6 @@ export class ExtensionHostMain { return TPromise.as(null); } - const desiredFilesMap: { [filename: string]: boolean; } = {}; diff --git a/src/vs/workbench/parts/terminal/test/electron-browser/terminalInstance.test.ts b/src/vs/workbench/parts/terminal/test/electron-browser/terminalInstance.test.ts index 0542ce7f902..df83db52ee2 100644 --- a/src/vs/workbench/parts/terminal/test/electron-browser/terminalInstance.test.ts +++ b/src/vs/workbench/parts/terminal/test/electron-browser/terminalInstance.test.ts @@ -107,7 +107,7 @@ suite('Workbench - TerminalInstance', () => { }); test('should use to the workspace if it exists', () => { - assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/foo') }), '/foo'); + assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/foo'), ctime: Date.now() }), '/foo'); }); test('should use an absolute custom cwd as is', () => { @@ -117,11 +117,11 @@ suite('Workbench - TerminalInstance', () => { test('should normalize a relative custom cwd against the workspace path', () => { configHelper.config.cwd = 'foo'; - assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/bar') }), '/bar/foo'); + assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/bar'), ctime: Date.now() }), '/bar/foo'); configHelper.config.cwd = './foo'; - assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/bar') }), '/bar/foo'); + assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/bar'), ctime: Date.now() }), '/bar/foo'); configHelper.config.cwd = '../foo'; - assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/bar') }, ), '/foo'); + assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/bar'), ctime: Date.now() }, ), '/foo'); }); test('should fall back for relative a custom cwd that doesn\'t have a workspace', () => { @@ -135,7 +135,7 @@ suite('Workbench - TerminalInstance', () => { test('should ignore custom cwd when told to ignore', () => { configHelper.config.cwd = '/foo'; - assertPathsMatch(instance._getCwd({ executable: null, args: [], ignoreConfigurationCwd: true }, { resource: Uri.file('/bar') }), '/bar'); + assertPathsMatch(instance._getCwd({ executable: null, args: [], ignoreConfigurationCwd: true }, { resource: Uri.file('/bar'), ctime: Date.now() }), '/bar'); }); }); }); \ No newline at end of file diff --git a/src/vs/workbench/services/configuration/node/configuration.ts b/src/vs/workbench/services/configuration/node/configuration.ts index fb935685395..a2ba71e00b7 100644 --- a/src/vs/workbench/services/configuration/node/configuration.ts +++ b/src/vs/workbench/services/configuration/node/configuration.ts @@ -69,7 +69,7 @@ class Workspace implements IWorkspace2 { public get name(): string { if (!this._name) { - this._name = this.roots.map(root => basename(root.fsPath)).join(', '); + this._name = this.roots.map(root => basename(root.fsPath) || root.fsPath).join(', '); } return this._name; @@ -101,7 +101,13 @@ export class WorkspaceConfigurationService extends Disposable implements IWorksp constructor(private environmentService: IEnvironmentService, private singleRootWorkspace?: SingleRootWorkspace, private workspaceSettingsRootFolder: string = WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME) { super(); - this.workspace = singleRootWorkspace ? new Workspace(createHash('md5').update(singleRootWorkspace.resource.toString()).digest('hex'), [singleRootWorkspace.resource]) : null; // TODO@Ben for now use the first root folder as id, but revisit this later + if (singleRootWorkspace) { + const workspaceId = createHash('md5').update(singleRootWorkspace.resource.fsPath).update(singleRootWorkspace.ctime ? String(singleRootWorkspace.ctime) : '').digest('hex'); + this.workspace = new Workspace(workspaceId, [singleRootWorkspace.resource]); + } else { + this.workspace = null; + } + this.rootsTrieMap = new TrieMap(TrieMap.PathSplitter); if (this.workspace) { this.rootsTrieMap.insert(this.workspace.roots[0].fsPath, this.workspace.roots[0]); diff --git a/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts b/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts index c66640c2bd7..da0cc1a86a6 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts @@ -14,7 +14,7 @@ suite('ExtHostWorkspace', function () { test('asRelativePath', function () { - const ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', roots: [URI.file('/Coding/Applications/NewsWoWBot')] }); + const ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', roots: [URI.file('/Coding/Applications/NewsWoWBot')], name: 'Test' }); assert.equal(ws.getRelativePath('/Coding/Applications/NewsWoWBot/bernd/das/brot'), 'bernd/das/brot'); assert.equal(ws.getRelativePath('/Apps/DartPubCache/hosted/pub.dartlang.org/convert-2.0.1/lib/src/hex.dart'), @@ -28,7 +28,7 @@ suite('ExtHostWorkspace', function () { test('asRelativePath, same paths, #11402', function () { const root = '/home/aeschli/workspaces/samples/docker'; const input = '/home/aeschli/workspaces/samples/docker'; - const ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', roots: [URI.file(root)] }); + const ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', roots: [URI.file(root)], name: 'Test' }); assert.equal(ws.getRelativePath(input), input); @@ -43,7 +43,7 @@ suite('ExtHostWorkspace', function () { }); test('asRelativePath, multiple folders', function () { - const ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', roots: [URI.file('/Coding/One'), URI.file('/Coding/Two')] }); + const ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', roots: [URI.file('/Coding/One'), URI.file('/Coding/Two')], name: 'Test' }); assert.equal(ws.getRelativePath('/Coding/One/file.txt'), 'file.txt'); assert.equal(ws.getRelativePath('/Coding/Two/files/out.txt'), 'files/out.txt'); assert.equal(ws.getRelativePath('/Coding/Two2/files/out.txt'), '/Coding/Two2/files/out.txt');