diff --git a/src/vs/workbench/services/label/common/labelService.ts b/src/vs/workbench/services/label/common/labelService.ts index f00e56f4499..c3a566e3c3e 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 { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceContextService, IWorkspace } from 'vs/platform/workspace/common/workspace'; -import { isEqual, basenameOrAuthority, basename, joinPath, dirname } from 'vs/base/common/resources'; +import { basenameOrAuthority, basename, joinPath, dirname } from 'vs/base/common/resources'; import { tildify, getPathLabel } from 'vs/base/common/labels'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, WORKSPACE_EXTENSION, toWorkspaceIdentifier, isWorkspaceIdentifier, isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces'; import { ILabelService, ResourceLabelFormatter, ResourceLabelFormatting, IFormatterChangeEvent } from 'vs/platform/label/common/label'; @@ -141,21 +141,18 @@ export class LabelService extends Disposable implements ILabelService { const baseResource = this.contextService?.getWorkspaceFolder(resource); if (options.relative && baseResource) { - const rootName = baseResource?.name ?? basenameOrAuthority(baseResource.uri); + const baseResourceLabel = this.formatUri(baseResource.uri, formatting, options.noPrefix); + let relativeLabel = this.formatUri(resource, formatting, options.noPrefix); - let relativeLabel: string; - if (isEqual(baseResource.uri, resource)) { - relativeLabel = ''; // no label if resources are identical - } else { - const baseResourceLabel = this.formatUri(baseResource.uri, formatting, options.noPrefix); - relativeLabel = this.formatUri(resource, formatting, options.noPrefix).substring(baseResourceLabel.lastIndexOf(formatting.separator) + 1); - if (relativeLabel.startsWith(rootName)) { - relativeLabel = relativeLabel.substring(rootName.length + (relativeLabel[rootName.length] === formatting.separator ? 1 : 0)); - } + let overlap = 0; + while (relativeLabel[overlap] && relativeLabel[overlap] === baseResourceLabel[overlap]) { overlap++; } + if (!relativeLabel[overlap] || relativeLabel[overlap] === formatting.separator) { + relativeLabel = relativeLabel.substring(1 + overlap); } const hasMultipleRoots = this.contextService.getWorkspace().folders.length > 1; if (hasMultipleRoots && !options.noPrefix) { + const rootName = baseResource?.name ?? basenameOrAuthority(baseResource.uri); relativeLabel = relativeLabel ? (rootName + ' • ' + relativeLabel) : rootName; // always show root basename if there are multiple } diff --git a/src/vs/workbench/services/label/test/browser/label.test.ts b/src/vs/workbench/services/label/test/browser/label.test.ts index 89b38e9cd1d..f5d8ff1ab39 100644 --- a/src/vs/workbench/services/label/test/browser/label.test.ts +++ b/src/vs/workbench/services/label/test/browser/label.test.ts @@ -3,14 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as resources from 'vs/base/common/resources'; import * as assert from 'assert'; import { TestEnvironmentService, TestPathService } from 'vs/workbench/test/browser/workbenchTestServices'; import { URI } from 'vs/base/common/uri'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; +import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; suite('URI Label', () => { - let labelService: LabelService; setup(() => { @@ -156,3 +157,75 @@ suite('URI Label', () => { assert.equal(labelService.getUriLabel(uri1, { relative: false }), 'LABEL: /END'); }); }); + + +suite('multi-root worksapce', () => { + let labelService: LabelService; + + setup(() => { + const sources = URI.file('folder1/src'); + const tests = URI.file('folder1/test'); + const other = URI.file('folder2'); + + labelService = new LabelService( + TestEnvironmentService, + new TestContextService( + new Workspace('test-workspaace', [ + new WorkspaceFolder({ uri: sources, index: 0, name: 'Sources' }, { uri: sources.toString() }), + new WorkspaceFolder({ uri: tests, index: 1, name: 'Tests' }, { uri: tests.toString() }), + new WorkspaceFolder({ uri: other, index: 2, name: resources.basename(other) }, { uri: other.toString() }), + ])), + new TestPathService()); + }); + + test('labels of files in multiroot workspaces are the foldername folloed by offset from the folder', () => { + labelService.registerFormatter({ + scheme: 'file', + formatting: { + label: '${authority}${path}', + separator: '/', + tildify: false, + normalizeDriveLetter: false, + authorityPrefix: '//', + workspaceSuffix: '' + } + }); + + const tests = { + 'folder1/src/file': 'Sources • file', + 'folder1/src/folder/file': 'Sources • folder/file', + 'folder1/src': 'Sources', + 'folder1/other': '/folder1/other', + 'folder2/other': 'folder2 • other', + }; + + Object.entries(tests).forEach(([path, label]) => { + const generated = labelService.getUriLabel(URI.file(path), { relative: true }); + assert.equal(generated, label); + }); + }); + + test('labels with context after path', () => { + labelService.registerFormatter({ + scheme: 'file', + formatting: { + label: '${path} (${scheme})', + separator: '/', + } + }); + + const tests = { + 'folder1/src/file': 'Sources • file (file)', + 'folder1/src/folder/file': 'Sources • folder/file (file)', + 'folder1/src': 'Sources', + 'folder1/other': '/folder1/other (file)', + 'folder2/other': 'folder2 • other (file)', + }; + + Object.entries(tests).forEach(([path, label]) => { + const generated = labelService.getUriLabel(URI.file(path), { relative: true }); + assert.equal(generated, label, path); + }); + + }); +});