mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-27 10:37:38 +01:00
multi root - stable workspace.id
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -8,5 +8,5 @@ import URI from 'vs/base/common/uri';
|
||||
|
||||
export const TestWorkspace = new Workspace(
|
||||
URI.file('C:\\testWorkspace'),
|
||||
'Test Workspace'
|
||||
Date.now()
|
||||
);
|
||||
|
||||
@@ -58,6 +58,7 @@ export interface IEnvironment {
|
||||
|
||||
export interface IWorkspaceData {
|
||||
id: string;
|
||||
name: string;
|
||||
roots: URI[];
|
||||
}
|
||||
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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<void> {
|
||||
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((<any>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
|
||||
(<any>self).require.config({
|
||||
onError: (err: any) => {
|
||||
if (err.errorCode === 'load') {
|
||||
shell.onUnexpectedError(loaderError(err));
|
||||
}
|
||||
// Inform user about loading issues from the loader
|
||||
(<any>self).require.config({
|
||||
onError: (err: any) => {
|
||||
if (err.errorCode === 'load') {
|
||||
shell.onUnexpectedError(loaderError(err));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getWorkspace(configuration: IWindowConfiguration): TPromise<Workspace> {
|
||||
function resolveWorkspace(configuration: IWindowConfiguration): TPromise<Workspace> {
|
||||
if (!configuration.workspacePath) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
@@ -153,10 +151,11 @@ function getWorkspace(configuration: IWindowConfiguration): TPromise<Workspace>
|
||||
// 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<Workspace>
|
||||
});
|
||||
}
|
||||
|
||||
function createStorageService(configuration: IWindowConfiguration, environmentService: IEnvironmentService): TPromise<IStorageService> {
|
||||
let workspaceStatPromise: TPromise<fs.Stats> = 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 {
|
||||
|
||||
@@ -104,7 +104,6 @@ export class ExtensionHostMain {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
|
||||
const desiredFilesMap: {
|
||||
[filename: string]: boolean;
|
||||
} = {};
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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<URI>(TrieMap.PathSplitter);
|
||||
if (this.workspace) {
|
||||
this.rootsTrieMap.insert(this.workspace.roots[0].fsPath, this.workspace.roots[0]);
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user