Give the file name the row, and stop repeating the project

On a real project the name lost the width fight and the trailing text read
"MyGame.Runtime MyGame.Runtime" — the directory and the project were the
same string, printed twice.

- The detail text now has a shrink factor of 1000 against the name's 1, plus
  a 45% cap, so it gives up its width first and the name is the last thing to
  ellipsize.
- A directory that already begins with the project name says everything the
  project name would, so only one of the two is shown.
- verticalTabs.showDirectory became never / duplicates / always, defaulting
  to duplicates: width goes to a directory only when two open tabs share a
  file name, which is the usual reason to want it. Turning showProject off
  as well gives the name the whole row.
- The trailing text is composed in the extension now rather than the webview,
  so the deduplication is unit-testable and there is one implementation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
max
2026-09-07 18:42:14 +02:00
co-authored by Claude Opus 5
parent 95799c6353
commit 925e55d619
6 changed files with 109 additions and 40 deletions
+38 -6
View File
@@ -4,7 +4,9 @@ import * as vscode from 'vscode';
import { closeOthers, closeTab, togglePin } from '../../actions';
import { projectColorIndex, projectOf } from '../../projects';
import { TabEntry, buildEntries, describeEntry, sortEntries, tabUri } from '../../tabs';
import {
TabEntry, buildEntries, describeEntry, duplicateLabels, sortEntries, tabUri,
} from '../../tabs';
/** Files in three different projects of the test solution. */
const FILES = [
@@ -240,23 +242,53 @@ suite('Tab list', () => {
assert.ok(settings.id.startsWith('1:label:'), `unexpected id ${settings.id}`);
});
test('the description shows directory, project and only shows the group when split', () => {
test('the detail text never repeats the project inside the directory', () => {
const all = { showProject: true, showDirectory: true, showColumn: false };
// A .csproj-per-directory layout: the directory already names the project.
const same = { label: 'Profiler.cs', dir: 'Nerfed.Runtime', project: 'Nerfed.Runtime' } as TabEntry;
assert.strictEqual(describeEntry(same, all), 'Nerfed.Runtime');
// A subdirectory of the project: still no need to repeat it.
const nested = {
label: 'Transform.cs', dir: 'Nerfed.Runtime/Util', project: 'Nerfed.Runtime',
} as TabEntry;
assert.strictEqual(describeEntry(nested, all), 'Nerfed.Runtime/Util');
// A Unity-style layout, where the directory says nothing about the assembly.
const unrelated = {
label: 'ClientTranslator.cs', dir: 'Assets/Scripts/Client', project: 'Acme.Shop.Client',
} as TabEntry;
assert.strictEqual(describeEntry(unrelated, all),
'Assets/Scripts/Client Acme.Shop.Client');
});
test('the detail text can be reduced to nothing', () => {
const entry = {
label: 'Profiler.cs', dir: 'Nerfed.Runtime', project: 'Nerfed.Runtime', column: 1,
} as TabEntry;
assert.strictEqual(
describeEntry(entry, { showProject: true, showDirectory: true, showColumn: false }),
'Nerfed.Runtime [Nerfed.Runtime]');
assert.strictEqual(
describeEntry(entry, { showProject: false, showDirectory: true, showColumn: false }),
'Nerfed.Runtime');
assert.strictEqual(
describeEntry(entry, { showProject: true, showDirectory: false, showColumn: false }),
'Nerfed.Runtime');
assert.strictEqual(
describeEntry(entry, { showProject: false, showDirectory: false, showColumn: true }),
'#1');
assert.strictEqual(
describeEntry(entry, { showProject: false, showDirectory: false, showColumn: false }),
'');
'', 'the file name should be able to have the whole row');
});
test('only names shared by more than one tab count as ambiguous', () => {
const entry = (label: string) => ({ label } as TabEntry);
assert.deepStrictEqual(
[...duplicateLabels([entry('a.cs'), entry('b.cs'), entry('a.cs')])],
['a.cs']);
assert.deepStrictEqual([...duplicateLabels([entry('a.cs'), entry('b.cs')])], []);
assert.deepStrictEqual([...duplicateLabels([])], []);
});
test('sorting is stable for entries that compare equal', () => {