Skip to content

Commit 1f00afe

Browse files
authored
Use platform agnostic relative paths for workspace file (fix microsoft#148492) (microsoft#157507)
* Use platform agnostic relative paths for workspace file (fix microsoft#148492) * more fixes * cleanup * fix * rename methods * always use slash * cleanup * fix tests on windows
1 parent 47cd001 commit 1f00afe

File tree

5 files changed

+65
-52
lines changed

5 files changed

+65
-52
lines changed

src/vs/platform/workspaces/common/workspaces.ts

Lines changed: 49 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { Event } from 'vs/base/common/event';
7-
import { toSlashes } from 'vs/base/common/extpath';
7+
import { isUNC, toSlashes } from 'vs/base/common/extpath';
88
import * as json from 'vs/base/common/json';
99
import * as jsonEdit from 'vs/base/common/jsonEdit';
1010
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
1111
import { normalizeDriveLetter } from 'vs/base/common/labels';
1212
import { Schemas } from 'vs/base/common/network';
13-
import { isAbsolute } from 'vs/base/common/path';
13+
import { isAbsolute, posix } from 'vs/base/common/path';
1414
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
1515
import { IExtUri, isEqualAuthority } from 'vs/base/common/resources';
1616
import { URI } from 'vs/base/common/uri';
@@ -124,55 +124,77 @@ export interface IEnterWorkspaceResult {
124124
}
125125

126126
/**
127-
* Given a folder URI and the workspace config folder, computes the IStoredWorkspaceFolder using
128-
* a relative or absolute path or a uri.
129-
* Undefined is returned if the folderURI and the targetConfigFolderURI don't have the same schema or authority
127+
* Given a folder URI and the workspace config folder, computes the `IStoredWorkspaceFolder`
128+
* using a relative or absolute path or a uri.
129+
* Undefined is returned if the `folderURI` and the `targetConfigFolderURI` don't have the
130+
* same schema or authority.
130131
*
131132
* @param folderURI a workspace folder
132133
* @param forceAbsolute if set, keep the path absolute
133134
* @param folderName a workspace name
134135
* @param targetConfigFolderURI the folder where the workspace is living in
135-
* @param useSlashForPath if set, use forward slashes for file paths on windows
136136
*/
137-
export function getStoredWorkspaceFolder(folderURI: URI, forceAbsolute: boolean, folderName: string | undefined, targetConfigFolderURI: URI, useSlashForPath = !isWindows, extUri: IExtUri): IStoredWorkspaceFolder {
137+
export function getStoredWorkspaceFolder(folderURI: URI, forceAbsolute: boolean, folderName: string | undefined, targetConfigFolderURI: URI, extUri: IExtUri): IStoredWorkspaceFolder {
138+
139+
// Scheme mismatch: use full absolute URI as `uri`
138140
if (folderURI.scheme !== targetConfigFolderURI.scheme) {
139141
return { name: folderName, uri: folderURI.toString(true) };
140142
}
141143

144+
// Always prefer a relative path if possible unless
145+
// prevented to make the workspace file shareable
146+
// with other users
142147
let folderPath = !forceAbsolute ? extUri.relativePath(targetConfigFolderURI, folderURI) : undefined;
143148
if (folderPath !== undefined) {
144149
if (folderPath.length === 0) {
145150
folderPath = '.';
146-
} else if (isWindows && folderURI.scheme === Schemas.file && !useSlashForPath) {
147-
// Windows gets special treatment:
148-
// - use backslahes unless slash is used by other existing folders
149-
folderPath = folderPath.replace(/\//g, '\\');
151+
} else {
152+
if (isWindows) {
153+
folderPath = massagePathForWindows(folderPath);
154+
}
150155
}
151-
} else {
156+
}
152157

153-
// use absolute path
158+
// We could not resolve a relative path
159+
else {
160+
161+
// Local file: use `fsPath`
154162
if (folderURI.scheme === Schemas.file) {
155163
folderPath = folderURI.fsPath;
156164
if (isWindows) {
157-
// Windows gets special treatment:
158-
// - normalize all paths to get nice casing of drive letters
159-
// - use backslahes unless slash is used by other existing folders
160-
folderPath = normalizeDriveLetter(folderPath);
161-
if (useSlashForPath) {
162-
folderPath = toSlashes(folderPath);
163-
}
164-
}
165-
} else {
166-
if (!extUri.isEqualAuthority(folderURI.authority, targetConfigFolderURI.authority)) {
167-
return { name: folderName, uri: folderURI.toString(true) };
165+
folderPath = massagePathForWindows(folderPath);
168166
}
167+
}
168+
169+
// Different authority: use full absolute URI
170+
else if (!extUri.isEqualAuthority(folderURI.authority, targetConfigFolderURI.authority)) {
171+
return { name: folderName, uri: folderURI.toString(true) };
172+
}
173+
174+
// Non-local file: use `path` of URI
175+
else {
169176
folderPath = folderURI.path;
170177
}
171178
}
172179

173180
return { name: folderName, path: folderPath };
174181
}
175182

183+
function massagePathForWindows(folderPath: string) {
184+
185+
// Drive letter should be upper case
186+
folderPath = normalizeDriveLetter(folderPath);
187+
188+
// Always prefer slash over backslash unless
189+
// we deal with UNC paths where backslash is
190+
// mandatory.
191+
if (!isUNC(folderPath)) {
192+
folderPath = toSlashes(folderPath);
193+
}
194+
195+
return folderPath;
196+
}
197+
176198
export function toWorkspaceFolders(configuredFolders: IStoredWorkspaceFolder[], workspaceConfigFile: URI, extUri: IExtUri): WorkspaceFolder[] {
177199
const result: WorkspaceFolder[] = [];
178200
const seen: Set<string> = new Set();
@@ -187,8 +209,8 @@ export function toWorkspaceFolders(configuredFolders: IStoredWorkspaceFolder[],
187209
} else if (isRawUriWorkspaceFolder(configuredFolder)) {
188210
try {
189211
uri = URI.parse(configuredFolder.uri);
190-
if (uri.path[0] !== '/') {
191-
uri = uri.with({ path: '/' + uri.path }); // this makes sure all workspace folder are absolute
212+
if (uri.path[0] !== posix.sep) {
213+
uri = uri.with({ path: posix.sep + uri.path }); // this makes sure all workspace folder are absolute
192214
}
193215
} catch (e) {
194216
console.warn(e); // ignore
@@ -222,7 +244,6 @@ export function rewriteWorkspaceFileForNewLocation(rawWorkspaceContents: string,
222244
const targetConfigFolder = extUri.dirname(targetConfigPathURI);
223245

224246
const rewrittenFolders: IStoredWorkspaceFolder[] = [];
225-
const slashForPath = useSlashForPath(storedWorkspace.folders);
226247

227248
for (const folder of storedWorkspace.folders) {
228249
const folderURI = isRawFileWorkspaceFolder(folder) ? extUri.resolvePath(sourceConfigFolder, folder.path) : URI.parse(folder.uri);
@@ -232,7 +253,7 @@ export function rewriteWorkspaceFileForNewLocation(rawWorkspaceContents: string,
232253
} else {
233254
absolute = !isRawFileWorkspaceFolder(folder) || isAbsolute(folder.path); // for existing workspaces, preserve whether a path was absolute or relative
234255
}
235-
rewrittenFolders.push(getStoredWorkspaceFolder(folderURI, absolute, folder.name, targetConfigFolder, slashForPath, extUri));
256+
rewrittenFolders.push(getStoredWorkspaceFolder(folderURI, absolute, folder.name, targetConfigFolder, extUri));
236257
}
237258

238259
// Preserve as much of the existing workspace as possible by using jsonEdit
@@ -264,14 +285,6 @@ function doParseStoredWorkspace(path: URI, contents: string): IStoredWorkspace {
264285
return storedWorkspace;
265286
}
266287

267-
export function useSlashForPath(storedFolders: IStoredWorkspaceFolder[]): boolean {
268-
if (isWindows) {
269-
return storedFolders.some(folder => isRawFileWorkspaceFolder(folder) && folder.path.indexOf('/') >= 0);
270-
}
271-
272-
return true;
273-
}
274-
275288
//#endregion
276289

277290
//#region Workspace Storage

src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { mnemonicButtonLabel } from 'vs/base/common/labels';
1111
import { Disposable } from 'vs/base/common/lifecycle';
1212
import { Schemas } from 'vs/base/common/network';
1313
import { dirname, join } from 'vs/base/common/path';
14-
import { isWindows } from 'vs/base/common/platform';
1514
import { basename, extUriBiasedIgnorePathCase, joinPath, originalFSPath } from 'vs/base/common/resources';
1615
import { withNullAsUndefined } from 'vs/base/common/types';
1716
import { URI } from 'vs/base/common/uri';
@@ -180,7 +179,7 @@ export class WorkspacesManagementMainService extends Disposable implements IWork
180179
const storedWorkspaceFolder: IStoredWorkspaceFolder[] = [];
181180

182181
for (const folder of folders) {
183-
storedWorkspaceFolder.push(getStoredWorkspaceFolder(folder.uri, true, folder.name, untitledWorkspaceConfigFolder, !isWindows, extUriBiasedIgnorePathCase));
182+
storedWorkspaceFolder.push(getStoredWorkspaceFolder(folder.uri, true, folder.name, untitledWorkspaceConfigFolder, extUriBiasedIgnorePathCase));
184183
}
185184

186185
return {

src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import * as assert from 'assert';
77
import * as fs from 'fs';
88
import * as os from 'os';
9+
import { isUNC, toSlashes } from 'vs/base/common/extpath';
910
import { normalizeDriveLetter } from 'vs/base/common/labels';
1011
import * as path from 'vs/base/common/path';
1112
import { isWindows } from 'vs/base/common/platform';
@@ -126,13 +127,16 @@ flakySuite('WorkspacesManagementMainService', () => {
126127
return pfs.Promises.rm(testDir);
127128
});
128129

129-
function assertPathEquals(p1: string, p2: string): void {
130+
function assertPathEquals(pathInWorkspaceFile: string, pathOnDisk: string): void {
130131
if (isWindows) {
131-
p1 = normalizeDriveLetter(p1);
132-
p2 = normalizeDriveLetter(p2);
132+
pathInWorkspaceFile = normalizeDriveLetter(pathInWorkspaceFile);
133+
pathOnDisk = normalizeDriveLetter(pathOnDisk);
134+
if (!isUNC(pathOnDisk)) {
135+
pathOnDisk = toSlashes(pathOnDisk); // workspace file is using slashes for all paths except where mandatory
136+
}
133137
}
134138

135-
assert.strictEqual(p1, p2);
139+
assert.strictEqual(pathInWorkspaceFile, pathOnDisk);
136140
}
137141

138142
function assertEqualURI(u1: URI, u2: URI): void {
@@ -335,16 +339,16 @@ flakySuite('WorkspacesManagementMainService', () => {
335339
assert.strictEqual(ws.folders.length, 3);
336340
assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[0]).path, folder1);
337341
assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[1]).path, 'inside');
338-
assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[2]).path, isWindows ? 'inside\\somefolder' : 'inside/somefolder');
342+
assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[2]).path, 'inside/somefolder');
339343

340344
origConfigPath = workspaceConfigPath;
341345
workspaceConfigPath = URI.file(path.join(tmpDir, 'other', 'myworkspace2.code-workspace'));
342346
newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, false, workspaceConfigPath, extUriBiasedIgnorePathCase);
343347
ws = (JSON.parse(newContent) as IStoredWorkspace);
344348
assert.strictEqual(ws.folders.length, 3);
345349
assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[0]).path, folder1);
346-
assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[1]).path, isWindows ? '..\\inside' : '../inside');
347-
assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[2]).path, isWindows ? '..\\inside\\somefolder' : '../inside/somefolder');
350+
assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[1]).path, '../inside');
351+
assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[2]).path, '../inside/somefolder');
348352

349353
origConfigPath = workspaceConfigPath;
350354
workspaceConfigPath = URI.parse('foo://foo/bar/myworkspace2.code-workspace');
@@ -396,7 +400,7 @@ flakySuite('WorkspacesManagementMainService', () => {
396400
const ws = (JSON.parse(newContent) as IStoredWorkspace);
397401
assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[0]).path, folder1Location);
398402
assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[1]).path, folder2Location);
399-
assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[2]).path, 'inner\\more');
403+
assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[2]).path, 'inner/more');
400404

401405
service.deleteUntitledWorkspaceSync(workspace);
402406
});

src/vs/workbench/services/configuration/browser/configurationService.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { Configuration } from 'vs/workbench/services/configuration/common/config
1818
import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, RestrictedSettings, PROFILE_SCOPES, LOCAL_MACHINE_PROFILE_SCOPES, profileSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration';
1919
import { Registry } from 'vs/platform/registry/common/platform';
2020
import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings, ConfigurationScope, IConfigurationPropertySchema, keyFromOverrideIdentifiers, OVERRIDE_PROPERTY_PATTERN, resourceLanguageSettingsSchemaId, configurationDefaultsSchemaId } from 'vs/platform/configuration/common/configurationRegistry';
21-
import { IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, useSlashForPath, getStoredWorkspaceFolder, toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces';
21+
import { IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, getStoredWorkspaceFolder, toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces';
2222
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
2323
import { ConfigurationEditingService, EditableConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditingService';
2424
import { WorkspaceConfiguration, FolderConfiguration, RemoteUserConfiguration, UserConfiguration, DefaultConfiguration } from 'vs/workbench/services/configuration/browser/configuration';
@@ -254,8 +254,6 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
254254
return !this.contains(foldersToRemove, currentWorkspaceFolders[index].uri); // keep entries which are unrelated
255255
});
256256

257-
const slashForPath = useSlashForPath(newStoredFolders);
258-
259257
foldersHaveChanged = currentWorkspaceFolders.length !== newStoredFolders.length;
260258

261259
// Add afterwards (if any)
@@ -280,7 +278,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
280278
continue;
281279
}
282280
} catch (e) { /* Ignore */ }
283-
storedFoldersToAdd.push(getStoredWorkspaceFolder(folderURI, false, folderToAdd.name, workspaceConfigFolder, slashForPath, this.uriIdentityService.extUri));
281+
storedFoldersToAdd.push(getStoredWorkspaceFolder(folderURI, false, folderToAdd.name, workspaceConfigFolder, this.uriIdentityService.extUri));
284282
}
285283

286284
// Apply to array of newStoredFolders

src/vs/workbench/services/workspaces/browser/workspacesService.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import { IFileService, FileOperationError, FileOperationResult } from 'vs/platfo
1616
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
1717
import { joinPath } from 'vs/base/common/resources';
1818
import { VSBuffer } from 'vs/base/common/buffer';
19-
import { isWindows } from 'vs/base/common/platform';
2019
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
2120
import { IWorkspaceBackupInfo, IFolderBackupInfo } from 'vs/platform/backup/common/backup';
2221
import { Schemas } from 'vs/base/common/network';
@@ -179,7 +178,7 @@ export class BrowserWorkspacesService extends Disposable implements IWorkspacesS
179178
const storedWorkspaceFolder: IStoredWorkspaceFolder[] = [];
180179
if (folders) {
181180
for (const folder of folders) {
182-
storedWorkspaceFolder.push(getStoredWorkspaceFolder(folder.uri, true, folder.name, this.environmentService.untitledWorkspacesHome, !isWindows, this.uriIdentityService.extUri));
181+
storedWorkspaceFolder.push(getStoredWorkspaceFolder(folder.uri, true, folder.name, this.environmentService.untitledWorkspacesHome, this.uriIdentityService.extUri));
183182
}
184183
}
185184

0 commit comments

Comments
 (0)