ChangesTree
The Changes view: the codebase's branches (how many names differ from this one's) and this branch's history, each entry the command that made it and, opened, the names it changed. An edited definition opens as a diff of its code before and after; anything else opens its page.
import type { BranchItem, HistoryItem, NameChange } from "#codebase/tcmts/cli/changes"; import { ago, CHANGES } from "#codebase/tcmts/vscode/changes"; import type { ChangesNode } from "#codebase/tcmts/vscode/changes"; import type { Request } from "#codebase/tcmts/vscode/releases"; import * as vscode from "vscode"; export class ChangesTree implements vscode.TreeDataProvider<ChangesNode> { request: Request; changed = new vscode.EventEmitter<ChangesNode | undefined>(); onDidChangeTreeData = this.changed.event; /** how many history entries are shown: "more" shows more */ limit = 30; branch = ""; constructor(request: Request) { this.request = request; } /** What a history entry changed, or what another branch has that this one doesn't. */ changesOf(e: ChangesNode): Promise<NameChange[]> { if (e.kind === "entry") return this.request("tcm/historyEntry", { hash: e.hash }); if (e.kind === "branch") return this.request("tcm/branchChanges", { branch: e.name }); return Promise.resolve([]); } refresh(): void { this.changed.fire(undefined); } async getChildren(e?: ChangesNode): Promise<ChangesNode[]> { if (!e) return [{ kind: "group", group: "branches" }, { kind: "group", group: "history" }]; if (e.kind === "group" && e.group === "branches") { const branches = (await this.request("tcm/branches", {})) as BranchItem[]; this.branch = branches.find((b) => b.current)?.name ?? ""; return branches.map((b) => ({ kind: "branch", ...b })); } if (e.kind === "group") { const entries = (await this.request("tcm/history", { limit: this.limit })) as HistoryItem[]; return [...entries.map((h): ChangesNode => ({ kind: "entry", ...h })), ...(entries.length === this.limit ? [{ kind: "more" } as const] : [])]; } if (e.kind === "entry" || (e.kind === "branch" && !e.current)) return (await this.changesOf(e)).map((change) => ({ kind: "change", change })); return []; } getTreeItem(e: ChangesNode): vscode.TreeItem { if (e.kind === "group") { const item = new vscode.TreeItem(e.group === "branches" ? "Branches" : "History", vscode.TreeItemCollapsibleState.Expanded); item.id = `group:${e.group}`; if (e.group === "history" && this.branch) item.description = this.branch; return item; } if (e.kind === "more") { const item = new vscode.TreeItem("Show more…"); item.command = { command: "tcm.moreHistory", title: "Show more" }; return item; } if (e.kind === "branch") { // another branch opens to the names that differ const item = new vscode.TreeItem(e.name, e.current || !e.differs ? vscode.TreeItemCollapsibleState.None : vscode.TreeItemCollapsibleState.Collapsed); item.id = `branch:${e.name}`; item.iconPath = new vscode.ThemeIcon(e.current ? "check" : "git-branch"); const when = e.ts ? ago(e.ts) : ""; item.description = e.current ? ["this branch", when].filter(Boolean).join(" · ") : [`${e.differs} name${e.differs === 1 ? "" : "s"} differ${e.differs === 1 ? "s" : ""}`, when].filter(Boolean).join(" · "); item.tooltip = e.current ? `${e.name}: the branch this codebase is on` : `${e.name}: ${e.differs} name(s) differ from this branch's`; item.contextValue = e.current ? "branch.current" : "branch"; return item; } if (e.kind === "entry") { const item = new vscode.TreeItem(e.command, vscode.TreeItemCollapsibleState.Collapsed); item.id = `entry:${e.hash}`; item.description = `${ago(e.ts)} · ${e.short}`; item.iconPath = new vscode.ThemeIcon(e.parents.length > 1 ? "git-merge" : "history"); item.tooltip = new vscode.MarkdownString(`**${e.command}**\n\n${new Date(e.ts).toLocaleString()} · \`${e.short}\` · ${e.back} back (\`tcm reset ${e.back}\` undoes it and what came after)`); item.contextValue = "historyEntry"; return item; } const c = e.change; const [what, icon, colour] = CHANGES[c.change]; const item = new vscode.TreeItem(c.path); item.iconPath = new vscode.ThemeIcon(icon, colour ? new vscode.ThemeColor(colour) : undefined); item.description = c.change === "renamed" ? `renamed from ${c.from}` : c.change === "propagated" ? `re-hashed: uses ${c.because!.join(", ")}` : c.change === "aliased" ? `another name for ${c.existing!.join(", ")}` : what; if ((c.change === "edited" || c.change === "propagated") && c.oldUri && c.uri) { item.command = { command: "vscode.diff", title: "Open changes", arguments: [vscode.Uri.parse(c.oldUri), vscode.Uri.parse(c.uri), `${c.path} (before ↔ after)`] }; } else if (c.hash) { item.command = { command: "tcm.openPage", title: "Open page", arguments: [{ hash: c.hash }] }; } return item; } }
uses
- tcmts.cli.changes.BranchItem
- tcmts.cli.changes.HistoryItem
- tcmts.cli.changes.NameChange
- tcmts.vscode.changes.CHANGES
- tcmts.vscode.changes.ChangesNode
- tcmts.vscode.changes.ago
- tcmts.vscode.releases.Request