A tree view row cannot be styled: no border, no spacing, and a long file name gets middle-ellipsized in a narrow strip — which on a real project left "ClientDogSurger...nionBehaviour.cs" and hid the project text entirely. Drawing the rows means controlling all of it. - A coloured left border per project instead of a tinted icon, so the file icon slot is free and the name gets the width. The border uses var(--vscode-verticalTabs-projectN): VS Code injects every contributed theme colour into a webview, so overrides in colorCustomizations still apply and nothing is hardcoded. - The name is split into stem and extension, and only the stem ellipsizes, so ".cs" survives on a long name in a narrow view. - Configurable row spacing and border width, a rule under the pinned block, a dirty dot that the close button replaces on hover, italics for preview and for tabs that cannot be focused. - Selection, arrow/Home/End/Enter/Delete keys, middle-click to close, and an HTML context menu, since contributes.menus does not reach webview rows. - Clicking a webview row posts nothing rather than asking for an open that would silently do nothing. Actions moved to actions.ts so the wiring, the view and the tests share one implementation; extension.ts is now just registration. The per-row commands are gone — rows act through webview messages — and the palette keeps close / closeOthers / togglePin acting on the active editor. Verified in a browser harness at side-bar width: all twelve project colours resolve, rows stay a consistent 24px, and click, pin, close, middle-click, keyboard and both context-menu variants post the right intents. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
89 lines
3.2 KiB
TypeScript
89 lines
3.2 KiB
TypeScript
import * as vscode from 'vscode';
|
|
import { canActivate, tabUri } from './tabs';
|
|
|
|
/**
|
|
* What the list can do to a tab.
|
|
*
|
|
* Everything here takes a live `vscode.Tab`. The rows the view draws are snapshots, and
|
|
* acting on stale state silently does the wrong thing (or nothing at all) — an earlier
|
|
* version compared a snapshot's `isPinned` and so never unpinned anything.
|
|
*/
|
|
|
|
/**
|
|
* Brings a tab to the front by re-opening its resource in its own group. There is no API
|
|
* to activate a tab directly, which is also why webview tabs cannot be focused: they
|
|
* have no resource to re-open.
|
|
*/
|
|
export async function openTab(tab: vscode.Tab): Promise<void> {
|
|
const uri = tabUri(tab);
|
|
if (!uri) {
|
|
return;
|
|
}
|
|
const column = tab.group.viewColumn ?? vscode.ViewColumn.Active;
|
|
const input = tab.input;
|
|
|
|
if (input instanceof vscode.TabInputNotebook) {
|
|
const notebook = await vscode.workspace.openNotebookDocument(uri);
|
|
await vscode.window.showNotebookDocument(notebook, { viewColumn: column, preview: false });
|
|
return;
|
|
}
|
|
if (input instanceof vscode.TabInputTextDiff) {
|
|
await vscode.commands.executeCommand('vscode.diff', input.original, input.modified,
|
|
tab.label, { viewColumn: column, preview: false });
|
|
return;
|
|
}
|
|
if (input instanceof vscode.TabInputCustom) {
|
|
await vscode.commands.executeCommand('vscode.openWith', uri, input.viewType,
|
|
{ viewColumn: column, preview: false });
|
|
return;
|
|
}
|
|
const doc = await vscode.workspace.openTextDocument(uri);
|
|
await vscode.window.showTextDocument(doc, { viewColumn: column, preview: false });
|
|
}
|
|
|
|
export async function closeTab(tab: vscode.Tab): Promise<void> {
|
|
await vscode.window.tabGroups.close(tab, true);
|
|
}
|
|
|
|
/**
|
|
* VS Code can only pin the *active* editor, so pinning another tab means focusing it
|
|
* first. The focus lands where the user clicked anyway, which is what they expect.
|
|
*/
|
|
export async function togglePin(tab: vscode.Tab): Promise<void> {
|
|
const pinned = !tab.isPinned;
|
|
if (!tab.isActive) {
|
|
if (!canActivate(tab)) {
|
|
void vscode.window.showInformationMessage(
|
|
`Cannot pin “${tab.label}”: this kind of tab has to be focused first.`);
|
|
return;
|
|
}
|
|
await openTab(tab);
|
|
}
|
|
await vscode.commands.executeCommand(
|
|
pinned ? 'workbench.action.pinEditor' : 'workbench.action.unpinEditor');
|
|
}
|
|
|
|
/** Closes everything except `tab`, keeping pinned tabs the way a tab bar does. */
|
|
export async function closeOthers(tab: vscode.Tab): Promise<void> {
|
|
const others = vscode.window.tabGroups.all
|
|
.flatMap(group => group.tabs)
|
|
.filter(other => other !== tab && !other.isPinned);
|
|
if (others.length > 0) {
|
|
await vscode.window.tabGroups.close(others, true);
|
|
}
|
|
}
|
|
|
|
export async function copyPath(tab: vscode.Tab): Promise<void> {
|
|
const uri = tabUri(tab);
|
|
if (uri) {
|
|
await vscode.env.clipboard.writeText(uri.fsPath);
|
|
}
|
|
}
|
|
|
|
export async function revealInExplorer(tab: vscode.Tab): Promise<void> {
|
|
const uri = tabUri(tab);
|
|
if (uri) {
|
|
await vscode.commands.executeCommand('revealInExplorer', uri);
|
|
}
|
|
}
|