Skip to content

Commit 73dad2e

Browse files
author
Benjamin Pasero
committed
multi root - stable workspace.id
1 parent a248e33 commit 73dad2e

10 files changed

Lines changed: 82 additions & 84 deletions

File tree

src/vs/platform/storage/test/storageService.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ suite('Workbench StorageSevice', () => {
9595
assert.strictEqual(s.get('wkey1', StorageScope.WORKSPACE), 'foo');
9696
assert.strictEqual(s.get('wkey2', StorageScope.WORKSPACE), 'foo2');
9797

98-
ws = new Workspace(TestWorkspace.resource, TestWorkspace.name);
98+
ws = new Workspace(TestWorkspace.resource, Date.now());
9999
ws.uid = new Date().getTime() + 100;
100100
s = new StorageService(storageImpl, null, ws);
101101

src/vs/platform/workspace/common/workspace.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ export interface IWorkspaceContextService {
6060
* Given a workspace relative path, returns the resource with the absolute path.
6161
*/
6262
toResource: (workspaceRelativePath: string) => URI;
63-
6463
}
6564

6665
export interface IWorkspace {
@@ -71,6 +70,11 @@ export interface IWorkspace {
7170
*/
7271
resource: URI;
7372

73+
/**
74+
* the creation date of the workspace if known.
75+
*/
76+
ctime: number;
77+
7478
/**
7579
* the name of the workspace
7680
*/
@@ -93,12 +97,13 @@ export interface IWorkspace2 {
9397
* Mutliple roots in this workspace. First entry is master and never changes.
9498
*/
9599
readonly roots: URI[];
96-
97100
}
98101

99102
export class Workspace implements IWorkspace {
103+
private _name: string;
100104

101-
constructor(private _resource: URI, private _name?: string) {
105+
constructor(private _resource: URI, private _ctime?: number) {
106+
this._name = paths.basename(this._resource.fsPath) || this._resource.fsPath;
102107
}
103108

104109
public get resource(): URI {
@@ -109,6 +114,10 @@ export class Workspace implements IWorkspace {
109114
return this._name;
110115
}
111116

117+
public get ctime(): number {
118+
return this._ctime;
119+
}
120+
112121
public isInsideWorkspace(resource: URI): boolean {
113122
if (resource) {
114123
return isEqualOrParent(resource.fsPath, this._resource.fsPath, !isLinux /* ignorecase */);
@@ -132,8 +141,4 @@ export class Workspace implements IWorkspace {
132141

133142
return null;
134143
}
135-
136-
public toJSON() {
137-
return { resource: this._resource, name: this._name };
138-
}
139144
}

src/vs/platform/workspace/test/common/testWorkspace.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ import URI from 'vs/base/common/uri';
88

99
export const TestWorkspace = new Workspace(
1010
URI.file('C:\\testWorkspace'),
11-
'Test Workspace'
11+
Date.now()
1212
);

src/vs/workbench/api/node/extHost.protocol.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export interface IEnvironment {
5858

5959
export interface IWorkspaceData {
6060
id: string;
61+
name: string;
6162
roots: URI[];
6263
}
6364

src/vs/workbench/api/node/extHostExtensionService.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
1616
import { createApiFactory, initializeExtensionApi } from 'vs/workbench/api/node/extHost.api.impl';
1717
import { IThreadService } from 'vs/workbench/services/thread/common/threadService';
1818
import { MainContext, MainProcessExtensionServiceShape, IWorkspaceData, IEnvironment, IInitData } from './extHost.protocol';
19-
import { createHash } from 'crypto';
2019

2120
const hasOwnProperty = Object.hasOwnProperty;
2221

@@ -130,11 +129,7 @@ class ExtensionStoragePath {
130129
return TPromise.as(undefined);
131130
}
132131
// TODO@joh what to do with multiple roots?
133-
const storageName = createHash('md5')
134-
.update(this._workspace.roots[0].fsPath)
135-
.update(this._workspace.id || '')
136-
.digest('hex');
137-
132+
const storageName = this._workspace.id;
138133
const storagePath = join(this._environment.appSettingsHome, 'workspaceStorage', storageName);
139134

140135
return dirExists(storagePath).then(exists => {

src/vs/workbench/electron-browser/main.ts

Lines changed: 50 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import paths = require('vs/base/common/paths');
1818
import uri from 'vs/base/common/uri';
1919
import strings = require('vs/base/common/strings');
2020
import { IResourceInput } from 'vs/platform/editor/common/editor';
21-
import { Workspace } from 'vs/platform/workspace/common/workspace';
21+
import { IWorkspace, Workspace } from "vs/platform/workspace/common/workspace";
2222
import { WorkspaceConfigurationService } from 'vs/workbench/services/configuration/node/configuration';
2323
import { realpath, stat } from 'vs/base/node/pfs';
2424
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
@@ -95,47 +95,45 @@ function toInputs(paths: IPath[], isUntitledFile?: boolean): IResourceInput[] {
9595
}
9696

9797
function openWorkbench(configuration: IWindowConfiguration, options: IOptions): TPromise<void> {
98-
return getWorkspace(configuration).then(workspace => {
98+
return resolveWorkspace(configuration).then(workspace => {
9999
const environmentService = new EnvironmentService(configuration, configuration.execPath);
100100
const workspaceConfigurationService = new WorkspaceConfigurationService(environmentService, workspace);
101101
const timerService = new TimerService((<any>window).MonacoEnvironment.timers as IInitData, !workspaceConfigurationService.hasWorkspace());
102-
103-
return createStorageService(configuration, environmentService).then(storageService => {
104-
105-
// Since the configuration service is one of the core services that is used in so many places, we initialize it
106-
// right before startup of the workbench shell to have its data ready for consumers
107-
return workspaceConfigurationService.initialize().then(() => {
108-
timerService.beforeDOMContentLoaded = Date.now();
109-
110-
return domContentLoaded().then(() => {
111-
timerService.afterDOMContentLoaded = Date.now();
112-
113-
// Open Shell
114-
timerService.beforeWorkbenchOpen = Date.now();
115-
const shell = new WorkbenchShell(document.body, {
116-
contextService: workspaceConfigurationService,
117-
configurationService: workspaceConfigurationService,
118-
environmentService,
119-
timerService,
120-
storageService
121-
}, configuration, options);
122-
shell.open();
123-
124-
// Inform user about loading issues from the loader
125-
(<any>self).require.config({
126-
onError: (err: any) => {
127-
if (err.errorCode === 'load') {
128-
shell.onUnexpectedError(loaderError(err));
129-
}
102+
const storageService = createStorageService(workspace, configuration, environmentService);
103+
104+
// Since the configuration service is one of the core services that is used in so many places, we initialize it
105+
// right before startup of the workbench shell to have its data ready for consumers
106+
return workspaceConfigurationService.initialize().then(() => {
107+
timerService.beforeDOMContentLoaded = Date.now();
108+
109+
return domContentLoaded().then(() => {
110+
timerService.afterDOMContentLoaded = Date.now();
111+
112+
// Open Shell
113+
timerService.beforeWorkbenchOpen = Date.now();
114+
const shell = new WorkbenchShell(document.body, {
115+
contextService: workspaceConfigurationService,
116+
configurationService: workspaceConfigurationService,
117+
environmentService,
118+
timerService,
119+
storageService
120+
}, configuration, options);
121+
shell.open();
122+
123+
// Inform user about loading issues from the loader
124+
(<any>self).require.config({
125+
onError: (err: any) => {
126+
if (err.errorCode === 'load') {
127+
shell.onUnexpectedError(loaderError(err));
130128
}
131-
});
129+
}
132130
});
133131
});
134132
});
135133
});
136134
}
137135

138-
function getWorkspace(configuration: IWindowConfiguration): TPromise<Workspace> {
136+
function resolveWorkspace(configuration: IWindowConfiguration): TPromise<Workspace> {
139137
if (!configuration.workspacePath) {
140138
return TPromise.as(null);
141139
}
@@ -153,42 +151,36 @@ function getWorkspace(configuration: IWindowConfiguration): TPromise<Workspace>
153151
// update config
154152
configuration.workspacePath = realWorkspacePath;
155153

156-
const workspaceResource = uri.file(realWorkspacePath);
157-
const folderName = path.basename(realWorkspacePath) || realWorkspacePath;
158-
159-
return new Workspace(workspaceResource, folderName);
154+
// resolve ctime of workspace
155+
return stat(realWorkspacePath).then(folderStat => new Workspace(
156+
uri.file(realWorkspacePath),
157+
platform.isLinux ? folderStat.ino : folderStat.birthtime.getTime() // On Linux, birthtime is ctime, so we cannot use it! We use the ino instead!
158+
));
160159
}, error => {
161160
errors.onUnexpectedError(error);
162161

163162
return null; // treat invalid paths as empty workspace
164163
});
165164
}
166165

167-
function createStorageService(configuration: IWindowConfiguration, environmentService: IEnvironmentService): TPromise<IStorageService> {
168-
let workspaceStatPromise: TPromise<fs.Stats> = TPromise.as(null);
169-
if (configuration.workspacePath) {
170-
workspaceStatPromise = stat(configuration.workspacePath);
166+
function createStorageService(workspace: IWorkspace, configuration: IWindowConfiguration, environmentService: IEnvironmentService): IStorageService {
167+
let id: IWorkspaceStorageIdentifier;
168+
if (workspace) {
169+
id = { resource: workspace.resource, uid: workspace.ctime };
170+
} else if (configuration.backupPath) {
171+
// if we do not have a workspace open, we need to find another identifier for the window to store
172+
// workspace UI state. if we have a backup path in the configuration we can use that because this
173+
// will be a unique identifier per window that is stable between restarts as long as there are
174+
// dirty files in the workspace.
175+
// We use basename() to produce a short identifier, we do not need the full path. We use a custom
176+
// scheme so that we can later distinguish these identifiers from the workspace one.
177+
id = { resource: uri.from({ path: path.basename(configuration.backupPath), scheme: 'empty' }) };
171178
}
172179

173-
return workspaceStatPromise.then(stat => {
174-
let id: IWorkspaceStorageIdentifier;
175-
if (stat) {
176-
id = { resource: uri.file(configuration.workspacePath), uid: platform.isLinux ? stat.ino : stat.birthtime.getTime() };
177-
} else if (configuration.backupPath) {
178-
// if we do not have a workspace open, we need to find another identifier for the window to store
179-
// workspace UI state. if we have a backup path in the configuration we can use that because this
180-
// will be a unique identifier per window that is stable between restarts as long as there are
181-
// dirty files in the workspace.
182-
// We use basename() to produce a short identifier, we do not need the full path. We use a custom
183-
// scheme so that we can later distinguish these identifiers from the workspace one.
184-
id = { resource: uri.from({ path: path.basename(configuration.backupPath), scheme: 'empty' }) };
185-
}
180+
const disableStorage = !!environmentService.extensionTestsPath; // never keep any state when running extension tests!
181+
const storage = disableStorage ? inMemoryLocalStorageInstance : window.localStorage;
186182

187-
const disableStorage = !!environmentService.extensionTestsPath; // never keep any state when running extension tests!
188-
const storage = disableStorage ? inMemoryLocalStorageInstance : window.localStorage;
189-
190-
return new StorageService(storage, storage, id);
191-
});
183+
return new StorageService(storage, storage, id);
192184
}
193185

194186
function loaderError(err: Error): Error {

src/vs/workbench/node/extensionHostMain.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ export class ExtensionHostMain {
104104
return TPromise.as(null);
105105
}
106106

107-
108107
const desiredFilesMap: {
109108
[filename: string]: boolean;
110109
} = {};

src/vs/workbench/parts/terminal/test/electron-browser/terminalInstance.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ suite('Workbench - TerminalInstance', () => {
107107
});
108108

109109
test('should use to the workspace if it exists', () => {
110-
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/foo') }), '/foo');
110+
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/foo'), ctime: Date.now() }), '/foo');
111111
});
112112

113113
test('should use an absolute custom cwd as is', () => {
@@ -117,11 +117,11 @@ suite('Workbench - TerminalInstance', () => {
117117

118118
test('should normalize a relative custom cwd against the workspace path', () => {
119119
configHelper.config.cwd = 'foo';
120-
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/bar') }), '/bar/foo');
120+
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/bar'), ctime: Date.now() }), '/bar/foo');
121121
configHelper.config.cwd = './foo';
122-
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/bar') }), '/bar/foo');
122+
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/bar'), ctime: Date.now() }), '/bar/foo');
123123
configHelper.config.cwd = '../foo';
124-
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/bar') }, ), '/foo');
124+
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/bar'), ctime: Date.now() }, ), '/foo');
125125
});
126126

127127
test('should fall back for relative a custom cwd that doesn\'t have a workspace', () => {
@@ -135,7 +135,7 @@ suite('Workbench - TerminalInstance', () => {
135135

136136
test('should ignore custom cwd when told to ignore', () => {
137137
configHelper.config.cwd = '/foo';
138-
assertPathsMatch(instance._getCwd({ executable: null, args: [], ignoreConfigurationCwd: true }, { resource: Uri.file('/bar') }), '/bar');
138+
assertPathsMatch(instance._getCwd({ executable: null, args: [], ignoreConfigurationCwd: true }, { resource: Uri.file('/bar'), ctime: Date.now() }), '/bar');
139139
});
140140
});
141141
});

src/vs/workbench/services/configuration/node/configuration.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class Workspace implements IWorkspace2 {
6969

7070
public get name(): string {
7171
if (!this._name) {
72-
this._name = this.roots.map(root => basename(root.fsPath)).join(', ');
72+
this._name = this.roots.map(root => basename(root.fsPath) || root.fsPath).join(', ');
7373
}
7474

7575
return this._name;
@@ -101,7 +101,13 @@ export class WorkspaceConfigurationService extends Disposable implements IWorksp
101101
constructor(private environmentService: IEnvironmentService, private singleRootWorkspace?: SingleRootWorkspace, private workspaceSettingsRootFolder: string = WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME) {
102102
super();
103103

104-
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
104+
if (singleRootWorkspace) {
105+
const workspaceId = createHash('md5').update(singleRootWorkspace.resource.fsPath).update(singleRootWorkspace.ctime ? String(singleRootWorkspace.ctime) : '').digest('hex');
106+
this.workspace = new Workspace(workspaceId, [singleRootWorkspace.resource]);
107+
} else {
108+
this.workspace = null;
109+
}
110+
105111
this.rootsTrieMap = new TrieMap<URI>(TrieMap.PathSplitter);
106112
if (this.workspace) {
107113
this.rootsTrieMap.insert(this.workspace.roots[0].fsPath, this.workspace.roots[0]);

src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ suite('ExtHostWorkspace', function () {
1414

1515
test('asRelativePath', function () {
1616

17-
const ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', roots: [URI.file('/Coding/Applications/NewsWoWBot')] });
17+
const ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', roots: [URI.file('/Coding/Applications/NewsWoWBot')], name: 'Test' });
1818

1919
assert.equal(ws.getRelativePath('/Coding/Applications/NewsWoWBot/bernd/das/brot'), 'bernd/das/brot');
2020
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 () {
2828
test('asRelativePath, same paths, #11402', function () {
2929
const root = '/home/aeschli/workspaces/samples/docker';
3030
const input = '/home/aeschli/workspaces/samples/docker';
31-
const ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', roots: [URI.file(root)] });
31+
const ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', roots: [URI.file(root)], name: 'Test' });
3232

3333
assert.equal(ws.getRelativePath(input), input);
3434

@@ -43,7 +43,7 @@ suite('ExtHostWorkspace', function () {
4343
});
4444

4545
test('asRelativePath, multiple folders', function () {
46-
const ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', roots: [URI.file('/Coding/One'), URI.file('/Coding/Two')] });
46+
const ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', roots: [URI.file('/Coding/One'), URI.file('/Coding/Two')], name: 'Test' });
4747
assert.equal(ws.getRelativePath('/Coding/One/file.txt'), 'file.txt');
4848
assert.equal(ws.getRelativePath('/Coding/Two/files/out.txt'), 'files/out.txt');
4949
assert.equal(ws.getRelativePath('/Coding/Two2/files/out.txt'), '/Coding/Two2/files/out.txt');

0 commit comments

Comments
 (0)