提交 a3364322 编写于 作者: M Martin Aeschlimann

Relative paths to folders below code-workspace not preserved on save. Fixes #83156

上级 e2a9394a
......@@ -8,7 +8,7 @@ import { localize } from 'vs/nls';
import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace';
import { URI, UriComponents } from 'vs/base/common/uri';
import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform';
import { extname } from 'vs/base/common/path';
import { extname, isAbsolute } from 'vs/base/common/path';
import { dirname, resolvePath, isEqualAuthority, isEqualOrParent, relativePath, extname as resourceExtname } from 'vs/base/common/resources';
import * as jsonEdit from 'vs/base/common/jsonEdit';
import * as json from 'vs/base/common/json';
......@@ -203,21 +203,22 @@ const SLASH = '/';
* Undefined is returned if the folderURI and the targetConfigFolderURI don't have the same schema or authority
*
* @param folderURI a workspace folder
* @param forceAbsolute if set, keep the path absolute
* @param folderName a workspace name
* @param targetConfigFolderURI the folder where the workspace is living in
* @param useSlashForPath if set, use forward slashes for file paths on windows
*/
export function getStoredWorkspaceFolder(folderURI: URI, folderName: string | undefined, targetConfigFolderURI: URI, useSlashForPath = !isWindows): IStoredWorkspaceFolder {
export function getStoredWorkspaceFolder(folderURI: URI, forceAbsolute: boolean, folderName: string | undefined, targetConfigFolderURI: URI, useSlashForPath = !isWindows): IStoredWorkspaceFolder {
if (folderURI.scheme !== targetConfigFolderURI.scheme) {
return { name: folderName, uri: folderURI.toString(true) };
}
let folderPath: string | undefined;
if (isEqualOrParent(folderURI, targetConfigFolderURI)) {
// use relative path
folderPath = relativePath(targetConfigFolderURI, folderURI) || '.'; // always uses forward slashes
if (isWindows && folderURI.scheme === Schemas.file && !useSlashForPath) {
let folderPath = !forceAbsolute ? relativePath(targetConfigFolderURI, folderURI) : undefined;
if (folderPath !== undefined) {
if (folderPath.length === 0) {
folderPath = '.';
} else if (isWindows && folderURI.scheme === Schemas.file && !useSlashForPath) {
// Windows gets special treatment:
// - use backslahes unless slash is used by other existing folders
folderPath = folderPath.replace(/\//g, '\\');
......@@ -249,7 +250,7 @@ export function getStoredWorkspaceFolder(folderURI: URI, folderName: string | un
* Rewrites the content of a workspace file to be saved at a new location.
* Throws an exception if file is not a valid workspace file
*/
export function rewriteWorkspaceFileForNewLocation(rawWorkspaceContents: string, configPathURI: URI, targetConfigPathURI: URI) {
export function rewriteWorkspaceFileForNewLocation(rawWorkspaceContents: string, configPathURI: URI, isFromUntitledWorkspace: boolean, targetConfigPathURI: URI) {
let storedWorkspace = doParseStoredWorkspace(configPathURI, rawWorkspaceContents);
const sourceConfigFolder = dirname(configPathURI);
......@@ -258,12 +259,17 @@ export function rewriteWorkspaceFileForNewLocation(rawWorkspaceContents: string,
const rewrittenFolders: IStoredWorkspaceFolder[] = [];
const slashForPath = useSlashForPath(storedWorkspace.folders);
// Rewrite absolute paths to relative paths if the target workspace folder
// is a parent of the location of the workspace file itself. Otherwise keep
// using absolute paths.
for (const folder of storedWorkspace.folders) {
let folderURI = isRawFileWorkspaceFolder(folder) ? resolvePath(sourceConfigFolder, folder.path) : URI.parse(folder.uri);
rewrittenFolders.push(getStoredWorkspaceFolder(folderURI, folder.name, targetConfigFolder, slashForPath));
const folderURI = isRawFileWorkspaceFolder(folder) ? resolvePath(sourceConfigFolder, folder.path) : URI.parse(folder.uri);
let absolute;
if (isFromUntitledWorkspace) {
// if it was an untitled workspace, try to make paths relative
absolute = false;
} else {
// for existing workspaces, preserve whether a path was absolute or relative
absolute = !isRawFileWorkspaceFolder(folder) || isAbsolute(folder.path);
}
rewrittenFolders.push(getStoredWorkspaceFolder(folderURI, absolute, folder.name, targetConfigFolder, slashForPath));
}
// Preserve as much of the existing workspace as possible by using jsonEdit
......
......@@ -175,7 +175,7 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain
const storedWorkspaceFolder: IStoredWorkspaceFolder[] = [];
for (const folder of folders) {
storedWorkspaceFolder.push(getStoredWorkspaceFolder(folder.uri, folder.name, untitledWorkspaceConfigFolder));
storedWorkspaceFolder.push(getStoredWorkspaceFolder(folder.uri, true, folder.name, untitledWorkspaceConfigFolder));
}
return {
......
......@@ -11,7 +11,7 @@ import * as pfs from 'vs/base/node/pfs';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv';
import { WorkspacesMainService, IStoredWorkspace } from 'vs/platform/workspaces/electron-main/workspacesMainService';
import { WORKSPACE_EXTENSION, IRawFileWorkspaceFolder, IWorkspaceFolderCreationData, IRawUriWorkspaceFolder, rewriteWorkspaceFileForNewLocation, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { WORKSPACE_EXTENSION, IRawFileWorkspaceFolder, IWorkspaceFolderCreationData, IRawUriWorkspaceFolder, rewriteWorkspaceFileForNewLocation, IWorkspaceIdentifier, IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
import { NullLogService } from 'vs/platform/log/common/log';
import { URI } from 'vs/base/common/uri';
import { getRandomTestPath } from 'vs/base/test/node/testUtils';
......@@ -109,11 +109,27 @@ suite('WorkspacesMainService', () => {
}
}
function createWorkspace(folders: string[], names?: string[]) {
function createUntitledWorkspace(folders: string[], names?: string[]) {
return service.createUntitledWorkspace(folders.map((folder, index) => ({ uri: URI.file(folder), name: names ? names[index] : undefined } as IWorkspaceFolderCreationData)));
}
function createWorkspaceSync(folders: string[], names?: string[]) {
function createWorkspace(workspaceConfigPath: string, folders: (string | URI)[], names?: string[]): void {
const ws: IStoredWorkspace = {
folders: []
};
for (let i = 0; i < folders.length; i++) {
const f = folders[i];
const s: IStoredWorkspaceFolder = f instanceof URI ? { uri: f.toString() } : { path: f };
if (names) {
s.name = names[i];
}
ws.folders.push(s);
}
fs.writeFileSync(workspaceConfigPath, JSON.stringify(ws));
}
function createUntitledWorkspaceSync(folders: string[], names?: string[]) {
return service.createUntitledWorkspaceSync(folders.map((folder, index) => ({ uri: URI.file(folder), name: names ? names[index] : undefined } as IWorkspaceFolderCreationData)));
}
......@@ -149,7 +165,7 @@ suite('WorkspacesMainService', () => {
}
test('createWorkspace (folders)', async () => {
const workspace = await createWorkspace([process.cwd(), os.tmpdir()]);
const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()]);
assert.ok(workspace);
assert.ok(fs.existsSync(workspace.configPath.fsPath));
assert.ok(service.isUntitledWorkspace(workspace));
......@@ -163,7 +179,7 @@ suite('WorkspacesMainService', () => {
});
test('createWorkspace (folders with name)', async () => {
const workspace = await createWorkspace([process.cwd(), os.tmpdir()], ['currentworkingdirectory', 'tempdir']);
const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()], ['currentworkingdirectory', 'tempdir']);
assert.ok(workspace);
assert.ok(fs.existsSync(workspace.configPath.fsPath));
assert.ok(service.isUntitledWorkspace(workspace));
......@@ -195,7 +211,7 @@ suite('WorkspacesMainService', () => {
});
test('createWorkspaceSync (folders)', () => {
const workspace = createWorkspaceSync([process.cwd(), os.tmpdir()]);
const workspace = createUntitledWorkspaceSync([process.cwd(), os.tmpdir()]);
assert.ok(workspace);
assert.ok(fs.existsSync(workspace.configPath.fsPath));
assert.ok(service.isUntitledWorkspace(workspace));
......@@ -210,7 +226,7 @@ suite('WorkspacesMainService', () => {
});
test('createWorkspaceSync (folders with names)', () => {
const workspace = createWorkspaceSync([process.cwd(), os.tmpdir()], ['currentworkingdirectory', 'tempdir']);
const workspace = createUntitledWorkspaceSync([process.cwd(), os.tmpdir()], ['currentworkingdirectory', 'tempdir']);
assert.ok(workspace);
assert.ok(fs.existsSync(workspace.configPath.fsPath));
assert.ok(service.isUntitledWorkspace(workspace));
......@@ -243,7 +259,7 @@ suite('WorkspacesMainService', () => {
});
test('resolveWorkspaceSync', async () => {
const workspace = await createWorkspace([process.cwd(), os.tmpdir()]);
const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()]);
assert.ok(service.resolveLocalWorkspaceSync(workspace.configPath));
// make it a valid workspace path
......@@ -262,7 +278,7 @@ suite('WorkspacesMainService', () => {
});
test('resolveWorkspaceSync (support relative paths)', async () => {
const workspace = await createWorkspace([process.cwd(), os.tmpdir()]);
const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()]);
fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib' }] }));
const resolved = service.resolveLocalWorkspaceSync(workspace.configPath);
......@@ -270,7 +286,7 @@ suite('WorkspacesMainService', () => {
});
test('resolveWorkspaceSync (support relative paths #2)', async () => {
const workspace = await createWorkspace([process.cwd(), os.tmpdir()]);
const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()]);
fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib/../other' }] }));
const resolved = service.resolveLocalWorkspaceSync(workspace.configPath);
......@@ -278,7 +294,7 @@ suite('WorkspacesMainService', () => {
});
test('resolveWorkspaceSync (support relative paths #3)', async () => {
const workspace = await createWorkspace([process.cwd(), os.tmpdir()]);
const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()]);
fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: 'ticino-playground/lib' }] }));
const resolved = service.resolveLocalWorkspaceSync(workspace.configPath);
......@@ -286,7 +302,7 @@ suite('WorkspacesMainService', () => {
});
test('resolveWorkspaceSync (support invalid JSON via fault tolerant parsing)', async () => {
const workspace = await createWorkspace([process.cwd(), os.tmpdir()]);
const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()]);
fs.writeFileSync(workspace.configPath.fsPath, '{ "folders": [ { "path": "./ticino-playground/lib" } , ] }'); // trailing comma
const resolved = service.resolveLocalWorkspaceSync(workspace.configPath);
......@@ -296,14 +312,15 @@ suite('WorkspacesMainService', () => {
test('rewriteWorkspaceFileForNewLocation', async () => {
const folder1 = process.cwd(); // absolute path because outside of tmpDir
const tmpDir = os.tmpdir();
const tmpInsideDir = path.join(os.tmpdir(), 'inside');
const tmpInsideDir = path.join(tmpDir, 'inside');
const workspace = await createWorkspace([folder1, tmpInsideDir, path.join(tmpInsideDir, 'somefolder')]);
const origContent = fs.readFileSync(workspace.configPath.fsPath).toString();
const firstConfigPath = path.join(tmpDir, 'myworkspace0.code-workspace');
createWorkspace(firstConfigPath, [folder1, 'inside', path.join('inside', 'somefolder')]);
const origContent = fs.readFileSync(firstConfigPath).toString();
let origConfigPath = workspace.configPath;
let origConfigPath = URI.file(firstConfigPath);
let workspaceConfigPath = URI.file(path.join(tmpDir, 'inside', 'myworkspace1.code-workspace'));
let newContent = rewriteWorkspaceFileForNewLocation(origContent, origConfigPath, workspaceConfigPath);
let newContent = rewriteWorkspaceFileForNewLocation(origContent, origConfigPath, false, workspaceConfigPath);
let ws = (JSON.parse(newContent) as IStoredWorkspace);
assert.equal(ws.folders.length, 3);
assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[0]).path, folder1); // absolute path because outside of tmpdir
......@@ -312,7 +329,7 @@ suite('WorkspacesMainService', () => {
origConfigPath = workspaceConfigPath;
workspaceConfigPath = URI.file(path.join(tmpDir, 'myworkspace2.code-workspace'));
newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, workspaceConfigPath);
newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, false, workspaceConfigPath);
ws = (JSON.parse(newContent) as IStoredWorkspace);
assert.equal(ws.folders.length, 3);
assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[0]).path, folder1);
......@@ -321,45 +338,45 @@ suite('WorkspacesMainService', () => {
origConfigPath = workspaceConfigPath;
workspaceConfigPath = URI.file(path.join(tmpDir, 'other', 'myworkspace2.code-workspace'));
newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, workspaceConfigPath);
newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, false, workspaceConfigPath);
ws = (JSON.parse(newContent) as IStoredWorkspace);
assert.equal(ws.folders.length, 3);
assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[0]).path, folder1);
assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[1]).path, tmpInsideDir);
assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[2]).path, path.join(tmpInsideDir, 'somefolder'));
assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[1]).path, isWindows ? '..\\inside' : '../inside');
assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[2]).path, isWindows ? '..\\inside\\somefolder' : '../inside/somefolder');
origConfigPath = workspaceConfigPath;
workspaceConfigPath = URI.parse('foo://foo/bar/myworkspace2.code-workspace');
newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, workspaceConfigPath);
newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, false, workspaceConfigPath);
ws = (JSON.parse(newContent) as IStoredWorkspace);
assert.equal(ws.folders.length, 3);
assert.equal((<IRawUriWorkspaceFolder>ws.folders[0]).uri, URI.file(folder1).toString(true));
assert.equal((<IRawUriWorkspaceFolder>ws.folders[1]).uri, URI.file(tmpInsideDir).toString(true));
assert.equal((<IRawUriWorkspaceFolder>ws.folders[2]).uri, URI.file(path.join(tmpInsideDir, 'somefolder')).toString(true));
service.deleteUntitledWorkspaceSync(workspace);
fs.unlinkSync(firstConfigPath);
});
test('rewriteWorkspaceFileForNewLocation (preserves comments)', async () => {
const workspace = await createWorkspace([process.cwd(), os.tmpdir(), path.join(os.tmpdir(), 'somefolder')]);
const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir(), path.join(os.tmpdir(), 'somefolder')]);
const workspaceConfigPath = URI.file(path.join(os.tmpdir(), `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`));
let origContent = fs.readFileSync(workspace.configPath.fsPath).toString();
origContent = `// this is a comment\n${origContent}`;
let newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, workspaceConfigPath);
let newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, false, workspaceConfigPath);
assert.equal(0, newContent.indexOf('// this is a comment'));
service.deleteUntitledWorkspaceSync(workspace);
});
test('rewriteWorkspaceFileForNewLocation (preserves forward slashes)', async () => {
const workspace = await createWorkspace([process.cwd(), os.tmpdir(), path.join(os.tmpdir(), 'somefolder')]);
const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir(), path.join(os.tmpdir(), 'somefolder')]);
const workspaceConfigPath = URI.file(path.join(os.tmpdir(), `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`));
let origContent = fs.readFileSync(workspace.configPath.fsPath).toString();
origContent = origContent.replace(/[\\]/g, '/'); // convert backslash to slash
const newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, workspaceConfigPath);
const newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, false, workspaceConfigPath);
const ws = (JSON.parse(newContent) as IStoredWorkspace);
assert.ok(ws.folders.every(f => (<IRawFileWorkspaceFolder>f).path.indexOf('\\') < 0));
service.deleteUntitledWorkspaceSync(workspace);
......@@ -375,10 +392,10 @@ suite('WorkspacesMainService', () => {
const folder2Location = '\\\\server\\share2\\some\\path';
const folder3Location = path.join(os.tmpdir(), 'wsloc', 'inner', 'more');
const workspace = await createWorkspace([folder1Location, folder2Location, folder3Location]);
const workspace = await createUntitledWorkspace([folder1Location, folder2Location, folder3Location]);
const workspaceConfigPath = URI.file(path.join(workspaceLocation, `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`));
let origContent = fs.readFileSync(workspace.configPath.fsPath).toString();
const newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, workspaceConfigPath);
const newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, false, workspaceConfigPath);
const ws = (JSON.parse(newContent) as IStoredWorkspace);
assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[0]).path, folder1Location);
assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[1]).path, folder2Location);
......@@ -388,14 +405,14 @@ suite('WorkspacesMainService', () => {
});
test('deleteUntitledWorkspaceSync (untitled)', async () => {
const workspace = await createWorkspace([process.cwd(), os.tmpdir()]);
const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()]);
assert.ok(fs.existsSync(workspace.configPath.fsPath));
service.deleteUntitledWorkspaceSync(workspace);
assert.ok(!fs.existsSync(workspace.configPath.fsPath));
});
test('deleteUntitledWorkspaceSync (saved)', async () => {
const workspace = await createWorkspace([process.cwd(), os.tmpdir()]);
const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()]);
service.deleteUntitledWorkspaceSync(workspace);
});
......@@ -405,14 +422,14 @@ suite('WorkspacesMainService', () => {
let untitled = service.getUntitledWorkspacesSync();
assert.equal(untitled.length, 0);
const untitledOne = await createWorkspace([process.cwd(), os.tmpdir()]);
const untitledOne = await createUntitledWorkspace([process.cwd(), os.tmpdir()]);
assert.ok(fs.existsSync(untitledOne.configPath.fsPath));
untitled = service.getUntitledWorkspacesSync();
assert.equal(1, untitled.length);
assert.equal(untitledOne.id, untitled[0].workspace.id);
const untitledTwo = await createWorkspace([os.tmpdir(), process.cwd()]);
const untitledTwo = await createUntitledWorkspace([os.tmpdir(), process.cwd()]);
assert.ok(fs.existsSync(untitledTwo.configPath.fsPath));
assert.ok(fs.existsSync(untitledOne.configPath.fsPath), `Unexpected workspaces count of 1 (expected 2): ${untitledOne.configPath.fsPath} does not exist anymore?`);
const untitledHome = dirname(dirname(untitledTwo.configPath));
......
......@@ -203,7 +203,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
return;
}
} catch (e) { /* Ignore */ }
storedFoldersToAdd.push(getStoredWorkspaceFolder(folderURI, folderToAdd.name, workspaceConfigFolder, slashForPath));
storedFoldersToAdd.push(getStoredWorkspaceFolder(folderURI, false, folderToAdd.name, workspaceConfigFolder, slashForPath));
}));
// Apply to array of newStoredFolders
......
......@@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri';
import * as nls from 'vs/nls';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IJSONEditingService, JSONEditingError, JSONEditingErrorCode } from 'vs/workbench/services/configuration/common/jsonEditing';
import { IWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesService, rewriteWorkspaceFileForNewLocation, WORKSPACE_FILTER, IEnterWorkspaceResult, hasWorkspaceFileExtension, WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesService, rewriteWorkspaceFileForNewLocation, WORKSPACE_FILTER, IEnterWorkspaceResult, hasWorkspaceFileExtension, WORKSPACE_EXTENSION, isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces';
import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService';
import { ConfigurationScope, IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
......@@ -234,9 +234,11 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi
return;
}
const isFromUntitledWorkspace = isUntitledWorkspace(configPathURI, this.environmentService);
// Read the contents of the workspace file, update it to new location and save it.
const raw = await this.fileService.readFile(configPathURI);
const newRawWorkspaceContents = rewriteWorkspaceFileForNewLocation(raw.value.toString(), configPathURI, targetConfigPathURI);
const newRawWorkspaceContents = rewriteWorkspaceFileForNewLocation(raw.value.toString(), configPathURI, isFromUntitledWorkspace, targetConfigPathURI);
await this.textFileService.create(targetConfigPathURI, newRawWorkspaceContents, { overwrite: true });
}
......
......@@ -139,7 +139,7 @@ export class BrowserWorkspacesService extends Disposable implements IWorkspacesS
const storedWorkspaceFolder: IStoredWorkspaceFolder[] = [];
if (folders) {
for (const folder of folders) {
storedWorkspaceFolder.push(getStoredWorkspaceFolder(folder.uri, folder.name, this.environmentService.untitledWorkspacesHome));
storedWorkspaceFolder.push(getStoredWorkspaceFolder(folder.uri, true, folder.name, this.environmentService.untitledWorkspacesHome));
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册