diff --git a/src/vs/base/common/labels.ts b/src/vs/base/common/labels.ts index cb3c967fa9d..6df31423244 100644 --- a/src/vs/base/common/labels.ts +++ b/src/vs/base/common/labels.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { hasDriveLetter, isRootOrDriveLetter } from 'vs/base/common/extpath'; +import { firstOrDefault } from 'vs/base/common/arrays'; +import { hasDriveLetter, isRootOrDriveLetter, toSlashes } from 'vs/base/common/extpath'; import { Schemas } from 'vs/base/common/network'; import { posix, sep, win32 } from 'vs/base/common/path'; import { isMacintosh, isWindows, OperatingSystem, OS } from 'vs/base/common/platform'; import { basename, extUri, extUriIgnorePathCase } from 'vs/base/common/resources'; -import { rtrim, startsWithIgnoreCase } from 'vs/base/common/strings'; +import { ltrim, rtrim, startsWithIgnoreCase } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; export interface IPathLabelFormatting { @@ -32,10 +33,10 @@ export interface IPathLabelFormatting { * Whether to convert to a relative path if the path * is within any of the opened workspace folders. */ - readonly relative?: IWorkspaceFolderProvider; + readonly relative?: IRelativePathProvider; } -export interface IWorkspaceFolderProvider { +export interface IRelativePathProvider { getWorkspaceFolder(resource: URI): { uri: URI; name?: string } | null; getWorkspace(): { folders: { uri: URI; name?: string }[]; @@ -47,58 +48,99 @@ export interface IUserHomeProvider { } export function getPathLabel(resource: URI, formatting: IPathLabelFormatting): string { - const { os, tildify: userHomeProvider, relative: rootProvider } = formatting; - const pathLib = os === OperatingSystem.Windows ? win32 : posix; - const extUriLib = os === OperatingSystem.Linux ? extUri : extUriIgnorePathCase; + const { os, tildify: tildifier, relative: relatifier } = formatting; - let pathLabel: string | undefined = undefined; - - // figure out relative path if we can by using root provider - if (rootProvider) { - const folder = rootProvider.getWorkspaceFolder(resource); - if (folder) { - if (extUriLib.isEqual(folder.uri, resource)) { - pathLabel = ''; // no label if paths are identical - } else { - pathLabel = extUriLib.relativePath(folder.uri, resource) ?? ''; - } - - // normalize - if (pathLabel) { - pathLabel = pathLib.normalize(pathLabel); - } - - if (rootProvider.getWorkspace().folders.length > 1) { - const rootName = folder.name ? folder.name : extUriLib.basename(folder.uri); - pathLabel = pathLabel ? `${rootName} • ${pathLabel}` : rootName; // always show root basename if there are multiple - } + // return early with a relative path if we can resolve one + if (relatifier) { + const relativePath = getRelativePathLabel(resource, relatifier, os); + if (typeof relativePath === 'string') { + return relativePath; } } - // return early if we can resolve a relative path label from the root - if (typeof pathLabel === 'string') { - return pathLabel; - } - - // otherwise we start with the absolute path and apply some normalization - else { - pathLabel = resource.fsPath; + // otherwise try to resolve a absolute path label and + // apply target OS standard path separators if target + // OS differs from actual OS we are running in + let absolutePath = resource.fsPath; + if (os === OperatingSystem.Windows && !isWindows) { + absolutePath = absolutePath.replace(/\//g, '\\'); + } else if (os !== OperatingSystem.Windows && isWindows) { + absolutePath = absolutePath.replace(/\\/g, '/'); } // macOS/Linux: tildify with provided user home directory - if (os !== OperatingSystem.Windows && userHomeProvider?.userHome) { - pathLabel = tildify(pathLabel, userHomeProvider.userHome.fsPath, os); - } + if (os !== OperatingSystem.Windows && tildifier?.userHome) { + let userHome = tildifier.userHome.fsPath; - // apply target OS standard path separators - if (os === OperatingSystem.Windows) { - pathLabel = pathLabel.replace(/\//g, '\\'); - } else { - pathLabel = pathLabel.replace(/\\/g, '/'); + // This is a bit of a hack, but in order to figure out if the + // resource is in the user home, we need to make sure to convert it + // to a user home resource. We cannot assume that the resource is + // already a user home resource. + let userHomeCandidate: string; + if (resource.scheme !== tildifier.userHome.scheme) { + userHomeCandidate = tildifier.userHome.with({ path: `/${ltrim(resource.path, '/')}` }).fsPath; + } else { + userHomeCandidate = resource.fsPath; + } + + // In addition, if we are on windows platform, we need to make + // sure to convert to POSIX path, because `tildify` only works + // with POSIX paths. + if (isWindows) { + userHomeCandidate = toSlashes(userHomeCandidate); + userHome = toSlashes(userHome); + } + + absolutePath = tildify(userHomeCandidate, userHome, os); } // normalize - return pathLib.normalize(normalizeDriveLetter(pathLabel, os === OperatingSystem.Windows)); + const pathLib = os === OperatingSystem.Windows ? win32 : posix; + return pathLib.normalize(normalizeDriveLetter(absolutePath, os === OperatingSystem.Windows)); +} + +function getRelativePathLabel(resource: URI, relativePathProvider: IRelativePathProvider, os: OperatingSystem): string | undefined { + const pathLib = os === OperatingSystem.Windows ? win32 : posix; + const extUriLib = os === OperatingSystem.Linux ? extUri : extUriIgnorePathCase; + + const workspace = relativePathProvider.getWorkspace(); + const firstFolder = firstOrDefault(workspace.folders); + if (!firstFolder) { + return undefined; + } + + // This is a bit of a hack, but in order to figure out the folder + // the resource belongs to, we need to make sure to convert it + // to a workspace resource. We cannot assume that the resource is + // already matching the workspace. + if (resource.scheme !== firstFolder.uri.scheme) { + resource = firstFolder.uri.with({ path: `/${ltrim(resource.path, '/')}` }); + } + + const folder = relativePathProvider.getWorkspaceFolder(resource); + if (!folder) { + return undefined; + } + + let relativePathLabel: string | undefined = undefined; + if (extUriLib.isEqual(folder.uri, resource)) { + relativePathLabel = ''; // no label if paths are identical + } else { + relativePathLabel = extUriLib.relativePath(folder.uri, resource) ?? ''; + } + + // normalize + if (relativePathLabel) { + relativePathLabel = pathLib.normalize(relativePathLabel); + } + + // always show root basename if there are multiple + if (workspace.folders.length > 1) { + const rootName = folder.name ? folder.name : extUriLib.basename(folder.uri); + relativePathLabel = relativePathLabel ? `${rootName} • ${relativePathLabel}` : rootName; + } + + return relativePathLabel; } export function getBaseLabel(resource: URI | string): string; diff --git a/src/vs/base/test/common/labels.test.ts b/src/vs/base/test/common/labels.test.ts index b01853ba03c..ee8ed0a2197 100644 --- a/src/vs/base/test/common/labels.test.ts +++ b/src/vs/base/test/common/labels.test.ts @@ -5,7 +5,8 @@ import * as assert from 'assert'; import * as labels from 'vs/base/common/labels'; -import { isMacintosh, isWindows } from 'vs/base/common/platform'; +import { isMacintosh, isWindows, OperatingSystem } from 'vs/base/common/platform'; +import { URI } from 'vs/base/common/uri'; suite('Labels', () => { (!isWindows ? test.skip : test)('shorten - windows', () => { @@ -162,4 +163,78 @@ suite('Labels', () => { assert.strictEqual(labels.mnemonicButtonLabel('Do &¬ Save & Continue'), 'Do _not Save & Continue'); } }); + + test('getPathLabel', () => { + const winFileUri = URI.file('c:/some/folder/file.txt'); + const nixFileUri = URI.file('/some/folder/file.txt'); + const uncFileUri = URI.file('c:/some/folder/file.txt').with({ authority: 'auth' }); + const remoteFileUri = URI.file('/some/folder/file.txt').with({ scheme: 'vscode-test', authority: 'auth' }); + + // Basics + + assert.strictEqual(labels.getPathLabel(winFileUri, { os: OperatingSystem.Windows }), 'C:\\some\\folder\\file.txt'); + assert.strictEqual(labels.getPathLabel(winFileUri, { os: OperatingSystem.Macintosh }), 'c:/some/folder/file.txt'); + assert.strictEqual(labels.getPathLabel(winFileUri, { os: OperatingSystem.Linux }), 'c:/some/folder/file.txt'); + + assert.strictEqual(labels.getPathLabel(nixFileUri, { os: OperatingSystem.Windows }), '\\some\\folder\\file.txt'); + assert.strictEqual(labels.getPathLabel(nixFileUri, { os: OperatingSystem.Macintosh }), '/some/folder/file.txt'); + assert.strictEqual(labels.getPathLabel(nixFileUri, { os: OperatingSystem.Linux }), '/some/folder/file.txt'); + + assert.strictEqual(labels.getPathLabel(uncFileUri, { os: OperatingSystem.Windows }), '\\\\auth\\c:\\some\\folder\\file.txt'); + assert.strictEqual(labels.getPathLabel(uncFileUri, { os: OperatingSystem.Macintosh }), '/auth/c:/some/folder/file.txt'); + assert.strictEqual(labels.getPathLabel(uncFileUri, { os: OperatingSystem.Linux }), '/auth/c:/some/folder/file.txt'); + + assert.strictEqual(labels.getPathLabel(remoteFileUri, { os: OperatingSystem.Windows }), '\\some\\folder\\file.txt'); + assert.strictEqual(labels.getPathLabel(remoteFileUri, { os: OperatingSystem.Macintosh }), '/some/folder/file.txt'); + assert.strictEqual(labels.getPathLabel(remoteFileUri, { os: OperatingSystem.Linux }), '/some/folder/file.txt'); + + // Tildify + + const nixUserHome = URI.file('/some'); + const remoteUserHome = URI.file('/some').with({ scheme: 'vscode-test', authority: 'auth' }); + + assert.strictEqual(labels.getPathLabel(nixFileUri, { os: OperatingSystem.Windows, tildify: { userHome: nixUserHome } }), '\\some\\folder\\file.txt'); + assert.strictEqual(labels.getPathLabel(nixFileUri, { os: OperatingSystem.Macintosh, tildify: { userHome: nixUserHome } }), '~/folder/file.txt'); + assert.strictEqual(labels.getPathLabel(nixFileUri, { os: OperatingSystem.Linux, tildify: { userHome: nixUserHome } }), '~/folder/file.txt'); + + assert.strictEqual(labels.getPathLabel(nixFileUri, { os: OperatingSystem.Windows, tildify: { userHome: remoteUserHome } }), '\\some\\folder\\file.txt'); + assert.strictEqual(labels.getPathLabel(nixFileUri, { os: OperatingSystem.Macintosh, tildify: { userHome: remoteUserHome } }), '~/folder/file.txt'); + assert.strictEqual(labels.getPathLabel(nixFileUri, { os: OperatingSystem.Linux, tildify: { userHome: remoteUserHome } }), '~/folder/file.txt'); + + const nixUntitledUri = URI.file('/some/folder/file.txt').with({ scheme: 'untitled' }); + + assert.strictEqual(labels.getPathLabel(nixUntitledUri, { os: OperatingSystem.Windows, tildify: { userHome: nixUserHome } }), '\\some\\folder\\file.txt'); + assert.strictEqual(labels.getPathLabel(nixUntitledUri, { os: OperatingSystem.Macintosh, tildify: { userHome: nixUserHome } }), '~/folder/file.txt'); + assert.strictEqual(labels.getPathLabel(nixUntitledUri, { os: OperatingSystem.Linux, tildify: { userHome: nixUserHome } }), '~/folder/file.txt'); + + assert.strictEqual(labels.getPathLabel(nixUntitledUri, { os: OperatingSystem.Windows, tildify: { userHome: remoteUserHome } }), '\\some\\folder\\file.txt'); + assert.strictEqual(labels.getPathLabel(nixUntitledUri, { os: OperatingSystem.Macintosh, tildify: { userHome: remoteUserHome } }), '~/folder/file.txt'); + assert.strictEqual(labels.getPathLabel(nixUntitledUri, { os: OperatingSystem.Linux, tildify: { userHome: remoteUserHome } }), '~/folder/file.txt'); + + // Relative + + const winFolder = URI.file('c:/some'); + const winRelativePathProvider: labels.IRelativePathProvider = { + getWorkspace() { return { folders: [{ uri: winFolder }] }; }, + getWorkspaceFolder(resource) { return { uri: winFolder }; } + }; + + assert.strictEqual(labels.getPathLabel(winFileUri, { os: OperatingSystem.Windows, relative: winRelativePathProvider }), 'folder\\file.txt'); + assert.strictEqual(labels.getPathLabel(winFileUri, { os: OperatingSystem.Macintosh, relative: winRelativePathProvider }), 'folder/file.txt'); + assert.strictEqual(labels.getPathLabel(winFileUri, { os: OperatingSystem.Linux, relative: winRelativePathProvider }), 'folder/file.txt'); + + const nixFolder = URI.file('/some'); + const nixRelativePathProvider: labels.IRelativePathProvider = { + getWorkspace() { return { folders: [{ uri: nixFolder }] }; }, + getWorkspaceFolder(resource) { return { uri: nixFolder }; } + }; + + assert.strictEqual(labels.getPathLabel(nixFileUri, { os: OperatingSystem.Windows, relative: nixRelativePathProvider }), 'folder\\file.txt'); + assert.strictEqual(labels.getPathLabel(nixFileUri, { os: OperatingSystem.Macintosh, relative: nixRelativePathProvider }), 'folder/file.txt'); + assert.strictEqual(labels.getPathLabel(nixFileUri, { os: OperatingSystem.Linux, relative: nixRelativePathProvider }), 'folder/file.txt'); + + assert.strictEqual(labels.getPathLabel(nixUntitledUri, { os: OperatingSystem.Windows, relative: nixRelativePathProvider }), 'folder\\file.txt'); + assert.strictEqual(labels.getPathLabel(nixUntitledUri, { os: OperatingSystem.Macintosh, relative: nixRelativePathProvider }), 'folder/file.txt'); + assert.strictEqual(labels.getPathLabel(nixUntitledUri, { os: OperatingSystem.Linux, relative: nixRelativePathProvider }), 'folder/file.txt'); + }); }); diff --git a/src/vs/platform/workspace/common/workspace.ts b/src/vs/platform/workspace/common/workspace.ts index b3e99e34b22..63c53ede8f6 100644 --- a/src/vs/platform/workspace/common/workspace.ts +++ b/src/vs/platform/workspace/common/workspace.ts @@ -6,7 +6,6 @@ import { localize } from 'vs/nls'; import { Event } from 'vs/base/common/event'; import { extname } from 'vs/base/common/path'; -import { IWorkspaceFolderProvider } from 'vs/base/common/labels'; import { TernarySearchTree } from 'vs/base/common/map'; import { extname as resourceExtname, basenameOrAuthority, joinPath, extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import { URI, UriComponents } from 'vs/base/common/uri'; @@ -16,7 +15,7 @@ import { Schemas } from 'vs/base/common/network'; export const IWorkspaceContextService = createDecorator('contextService'); -export interface IWorkspaceContextService extends IWorkspaceFolderProvider { +export interface IWorkspaceContextService { readonly _serviceBrand: undefined; diff --git a/src/vs/workbench/services/label/common/labelService.ts b/src/vs/workbench/services/label/common/labelService.ts index 74434b28f32..d508bed2693 100644 --- a/src/vs/workbench/services/label/common/labelService.ts +++ b/src/vs/workbench/services/label/common/labelService.ts @@ -12,7 +12,7 @@ import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWo import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkspaceContextService, IWorkspace, isWorkspace, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier, toWorkspaceIdentifier, WORKSPACE_EXTENSION, isUntitledWorkspace, isTemporaryWorkspace } from 'vs/platform/workspace/common/workspace'; -import { basenameOrAuthority, basename, joinPath, dirname, toLocalResource } from 'vs/base/common/resources'; +import { basenameOrAuthority, basename, joinPath, dirname } from 'vs/base/common/resources'; import { tildify, getPathLabel } from 'vs/base/common/labels'; import { ILabelService, ResourceLabelFormatter, ResourceLabelFormatting, IFormatterChangeEvent } from 'vs/platform/label/common/label'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; @@ -23,7 +23,6 @@ import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { OS } from 'vs/base/common/platform'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { Schemas } from 'vs/base/common/network'; const resourceLabelFormattersExtPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'resourceLabelFormatters', @@ -176,17 +175,6 @@ export class LabelService extends Disposable implements ILabelService { private doGetUriLabel(resource: URI, formatting?: ResourceLabelFormatting, options: { relative?: boolean; noPrefix?: boolean; endWithSeparator?: boolean } = {}): string { if (!formatting) { - - // Without a formatter we have to fallback to figuring out what the - // label could be that best matches the environment and workspace - // the user is in. - // As such, if the resource is with unfamiliar scheme, we convert it - // to the default scheme and remote authority. - - if (resource.scheme !== this.pathService.defaultUriScheme && resource.scheme !== Schemas.untitled) { - resource = toLocalResource(resource, this.environmentService.remoteAuthority, this.pathService.defaultUriScheme); - } - return getPathLabel(resource, { os: this.os, tildify: this.userHome ? { userHome: this.userHome } : undefined,