// Helpers de construction des TreeItem (icônes d'état, labels, contextValue) partagés par les deux // arbres (Repositories & Groups). Les `contextValue` pilotent les menus contextuels (cf. package.json) ; // les regex y matchent des sous-chaînes → format stable `arboretum:session:[:discovered][:waiting]`. import * as vscode from 'vscode'; import type { GroupSummary, RepoSummary, SessionSummary, WorktreeSummary } from '@arboretum/shared'; export function repoTreeItem(repo: RepoSummary): vscode.TreeItem { const item = new vscode.TreeItem(repo.label, vscode.TreeItemCollapsibleState.Expanded); item.id = `repo:${repo.id}`; item.contextValue = 'arboretum:repo'; item.iconPath = new vscode.ThemeIcon('repo'); item.description = repo.defaultBranch ?? ''; item.tooltip = repo.path; return item; } export function worktreeTreeItem(worktree: WorktreeSummary): vscode.TreeItem { const label = worktree.branch ?? (worktree.detached ? '(detached)' : worktree.head.slice(0, 8)); const item = new vscode.TreeItem(label, vscode.TreeItemCollapsibleState.Expanded); item.id = `wt:${worktree.path}`; item.contextValue = 'arboretum:worktree'; item.iconPath = new vscode.ThemeIcon(worktree.isMain ? 'star-full' : 'git-branch'); item.description = worktreeStatus(worktree); item.tooltip = worktreeTooltip(worktree); item.resourceUri = vscode.Uri.file(worktree.path); return item; } function worktreeStatus(w: WorktreeSummary): string { const parts: string[] = []; if (w.isMain) parts.push('main'); if (w.git.ahead > 0) parts.push(`↑${w.git.ahead}`); if (w.git.behind > 0) parts.push(`↓${w.git.behind}`); if (w.git.dirtyCount > 0) parts.push(`●${w.git.dirtyCount}`); // Compteurs fins (P7, additifs) : staged/conflits surfacés directement dans l'arbre. if (w.git.stagedCount && w.git.stagedCount > 0) parts.push(`+${w.git.stagedCount}`); if (w.git.conflictCount && w.git.conflictCount > 0) parts.push(`!${w.git.conflictCount}`); return parts.join(' '); } /** Tooltip détaillé : chemin + statut git fin (P7) + dernier commit. */ function worktreeTooltip(w: WorktreeSummary): vscode.MarkdownString { const md = new vscode.MarkdownString(); md.appendMarkdown(`\`${w.path}\`\n\n`); const g = w.git; const status: string[] = []; if (g.stagedCount && g.stagedCount > 0) status.push(`${g.stagedCount} staged`); if (g.unstagedCount && g.unstagedCount > 0) status.push(`${g.unstagedCount} unstaged`); if (g.conflictCount && g.conflictCount > 0) status.push(`${g.conflictCount} conflicts`); if (status.length === 0 && g.dirtyCount > 0) status.push(`${g.dirtyCount} changed`); md.appendMarkdown(status.length > 0 ? status.join(' · ') : 'clean'); if (g.ahead > 0 || g.behind > 0) md.appendMarkdown(` (↑${g.ahead} ↓${g.behind})`); if (g.lastCommitSubject) md.appendMarkdown(`\n\n_${g.lastCommitSubject}_`); return md; } export function sessionContextValue(s: SessionSummary): string { let v = `arboretum:session:${s.live ? 'live' : 'dead'}`; if (s.source === 'discovered') v += ':discovered'; if (s.live && s.activity === 'waiting') v += ':waiting'; // Session issue de « Démarrer le projet » : suffixe additif (les regex de menu existantes, // ancrées sur le préfixe, restent valides) qui gate l'action « Stop launch ». if (s.launchRunId) v += ':launch'; return v; } export function sessionTreeItem(s: SessionSummary): vscode.TreeItem { const label = s.title?.trim() || s.command; const item = new vscode.TreeItem(label, vscode.TreeItemCollapsibleState.None); item.id = `session:${s.id}`; item.contextValue = sessionContextValue(s); item.iconPath = sessionIcon(s); item.description = sessionDescription(s); item.tooltip = sessionTooltip(s); return item; } function sessionIcon(s: SessionSummary): vscode.ThemeIcon { if (!s.live) return new vscode.ThemeIcon('circle-slash'); switch (s.activity) { case 'waiting': return new vscode.ThemeIcon('bell-dot', new vscode.ThemeColor('charts.yellow')); case 'busy': return new vscode.ThemeIcon('loading~spin'); case 'idle': return new vscode.ThemeIcon('pass', new vscode.ThemeColor('charts.green')); default: return new vscode.ThemeIcon('terminal'); } } function sessionDescription(s: SessionSummary): string { const parts: string[] = []; // Affiche 'available' pour idle (cohérent avec le libellé web) ; l'enum reste 'idle' côté protocole. if (s.live && s.activity) parts.push(s.activity === 'idle' ? 'available' : s.activity); else if (!s.live) parts.push('exited'); if (s.archived) parts.push('archived'); if (s.source === 'discovered') parts.push('external'); if (s.groupId) parts.push('group'); if (s.launchRunId) parts.push('launch'); if (s.waitingFor) parts.push(`· ${s.waitingFor}`); return parts.join(' '); } function sessionTooltip(s: SessionSummary): string { const lines = [`${s.command} · ${s.cwd}`, `status: ${s.status}${s.live ? ' (live)' : ''}`]; if (s.activity) lines.push(`activity: ${s.activity}`); if (s.addedDirs && s.addedDirs.length > 0) lines.push(`added dirs: ${s.addedDirs.length}`); return lines.join('\n'); } export function groupTreeItem(group: GroupSummary): vscode.TreeItem { const item = new vscode.TreeItem(group.label, vscode.TreeItemCollapsibleState.Collapsed); item.id = `group:${group.id}`; item.contextValue = 'arboretum:group'; item.iconPath = new vscode.ThemeIcon('folder-library'); item.description = `${group.repoIds.length} repos`; if (group.description) item.tooltip = group.description; return item; }