More improvements to default path label (#148293)

* more fun with labels

* more label funs

* add tests

* fix tests
This commit is contained in:
Benjamin Pasero
2022-04-27 19:22:18 +02:00
committed by GitHub
parent 9d607f2176
commit 059abff975
4 changed files with 165 additions and 61 deletions

View File

@@ -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;

View File

@@ -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 &&not 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');
});
});

View File

@@ -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<IWorkspaceContextService>('contextService');
export interface IWorkspaceContextService extends IWorkspaceFolderProvider {
export interface IWorkspaceContextService {
readonly _serviceBrand: undefined;

View File

@@ -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<ResourceLabelFormatter[]>({
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,