// The Diffusion view (oxygenhub-vscode#5, oxygenhub docs/diffusion.md): for // each repository, the subrepos it takes from and the repositories it // diffuses to, each with the hub's pill in words and its open bump. import * as vscode from "vscode"; import type * as api from "./hubapi"; import { HubAuth } from "./auth"; import { hubUrl } from "./hub"; import type { HubRepo } from "./proposals"; import { look, pinDescription, pinLabel, pinTooltip, tone } from "./diffusionview"; type Side = "takes" | "gives"; type Node = | { kind: "repo"; repo: HubRepo } | { kind: "side"; repo: HubRepo; side: Side; pins: api.DiffusionPin[] } | { kind: "pin"; repo: HubRepo; side: Side; pin: api.DiffusionPin } | { kind: "none"; repo: HubRepo }; export type DiffusionPinNode = Extract; export class DiffusionProvider implements vscode.TreeDataProvider { private readonly emitter = new vscode.EventEmitter(); readonly onDidChangeTreeData = this.emitter.event; constructor( private readonly repos: () => Promise, private readonly auth: HubAuth, ) {} refresh(): void { this.emitter.fire(); } getTreeItem(node: Node): vscode.TreeItem { switch (node.kind) { case "repo": { const item = new vscode.TreeItem(`${node.repo.owner}/${node.repo.name}`, vscode.TreeItemCollapsibleState.Expanded); item.iconPath = new vscode.ThemeIcon("repo"); item.contextValue = "diffusionRepo"; return item; } case "side": { const item = new vscode.TreeItem(node.side === "takes" ? "Takes from" : "Diffuses to", vscode.TreeItemCollapsibleState.Expanded); item.description = String(node.pins.length); item.iconPath = new vscode.ThemeIcon(node.side === "takes" ? "arrow-down" : "arrow-up"); return item; } case "pin": { const t = tone(node.pin); const l = look(t); const item = new vscode.TreeItem(pinLabel(node.pin, node.side), vscode.TreeItemCollapsibleState.None); item.description = pinDescription(node.pin); item.iconPath = new vscode.ThemeIcon(l.icon, new vscode.ThemeColor(l.color)); item.tooltip = new vscode.MarkdownString(pinTooltip(node.pin)); item.contextValue = node.pin.bump !== null && node.pin.bump !== undefined ? "diffusionPinBump" : "diffusionPin"; item.command = { title: "Open", command: "oxygenhub.openDiffusionPin", arguments: [node] }; return item; } case "none": { const item = new vscode.TreeItem("Neither pins nor is pinned by a repository on the hub", vscode.TreeItemCollapsibleState.None); item.iconPath = new vscode.ThemeIcon("info"); return item; } } } async getChildren(node?: Node): Promise { if (!node) { return (await this.repos()).map((repo) => ({ kind: "repo", repo })); } if (node.kind === "repo") { try { const d = await this.auth.clientFor(node.repo.base).diffusion(node.repo.owner, node.repo.name); const out: Node[] = []; if (d.takes_from.length > 0) { out.push({ kind: "side", repo: node.repo, side: "takes", pins: d.takes_from }); } if (d.diffuses_to.length > 0) { out.push({ kind: "side", repo: node.repo, side: "gives", pins: d.diffuses_to }); } return out.length > 0 ? out : [{ kind: "none", repo: node.repo }]; } catch (e) { void vscode.window.showWarningMessage(`OxygenHub: ${(e as Error).message}`); return []; } } if (node.kind === "side") { return node.pins.map((pin) => ({ kind: "pin", repo: node.repo, side: node.side, pin })); } return []; } } /** Where a row leads on the hub: its open bump, else the other repository. */ export function pinUrl(node: DiffusionPinNode): string { const [owner, name] = (node.side === "takes" ? node.pin.upstream : node.pin.consumer).split("/"); const [cOwner, cName] = node.pin.consumer.split("/"); if (node.pin.bump !== null && node.pin.bump !== undefined) { return hubUrl(node.repo.base, cOwner, cName, { kind: "proposal", number: node.pin.bump }); } return hubUrl(node.repo.base, owner, name, { kind: "repository" }); } /** The repository's Diffusion tab on the hub. */ export function diffusionTab(repo: HubRepo): string { return hubUrl(repo.base, repo.owner, repo.name, { kind: "diffusion" }); }