Files
arboretum/packages/vscode/src/views/groups-tree.ts
Johan LEROY cf7eb05aca feat(vscode): extension VS Code native (intégration native, pas un webview)
Nouveau workspace packages/vscode (git-arboretum, privé, non publié sur npm), client REST/WS
réutilisant @arboretum/shared. Auth Authorization: Bearer sur REST et l'upgrade WS (via `ws`) ;
un client Node sans en-tête Origin passe le check Origin strict du serveur.

- Arbres temps réel : Repositories (repos → worktrees → sessions) et Groups, via le WebSocket.
- Terminaux natifs (vscode.Pseudoterminal) pour attacher/observer une session — rendu et
  scrollback de VS Code ; décodage UTF-8 streaming + comptabilité ACK dans des modules purs.
- Status bar (compteur waiting) + notifications natives sur passage en waiting, réponses Yes/No
  via la commande WS answer.
- Mutations git (create worktree, commit, push, promote), start/kill/hide/resume/fork, session
  de groupe ; conscience du workspace (reveal + start/create here).
- Bundle esbuild (format cjs, external vscode) inlinant @arboretum/shared → VSIX autonome.
  Logique réutilisable sans import vscode → testée par vitest (19 tests).
- CI : .gitea/workflows/vscode-release.yml package le VSIX sur tag vscode-vX.Y.Z (artefact +
  asset de release best-effort). build:vscode hors du build principal (comme le site).
- spikes/s5-vscode/STUDY.md : décision de conception (GO phasé A→D), marquée implémentée.
2026-06-23 17:43:03 +02:00

65 lines
2.4 KiB
TypeScript

// Arbre Groups → repos membres → worktrees/sessions, plus les sessions de groupe (multi-repo, --add-dir)
// rattachées directement au groupe. Rafraîchi en temps réel (store.onDidChange).
import * as vscode from 'vscode';
import type { Store } from '../state/store.js';
import type { GroupsNode } from './nodes.js';
import { groupTreeItem, repoTreeItem, sessionTreeItem, worktreeTreeItem } from './tree-items.js';
export class GroupsTreeProvider implements vscode.TreeDataProvider<GroupsNode> {
private readonly changeEmitter = new vscode.EventEmitter<GroupsNode | undefined>();
readonly onDidChangeTreeData = this.changeEmitter.event;
constructor(private readonly store: Store) {
store.onDidChange(() => this.changeEmitter.fire(undefined));
}
getTreeItem(node: GroupsNode): vscode.TreeItem {
switch (node.kind) {
case 'group':
return groupTreeItem(node.group);
case 'repo':
return repoTreeItem(node.repo);
case 'worktree':
return worktreeTreeItem(node.worktree);
case 'group-session':
case 'session': {
const item = sessionTreeItem(node.session);
if (node.session.live) {
item.command = { command: 'arboretum.attachSession', title: 'Attach', arguments: [node] };
}
return item;
}
}
}
getChildren(node?: GroupsNode): GroupsNode[] {
if (!node) {
return this.store.listGroups().map((group) => ({ kind: 'group', group }));
}
if (node.kind === 'group') {
const children: GroupsNode[] = [];
// sessions de groupe (couvrant plusieurs repos) en tête
for (const session of this.store.sessionsForGroup(node.group.id)) {
children.push({ kind: 'group-session', session, groupId: node.group.id });
}
// puis les repos membres
for (const repoId of node.group.repoIds) {
const repo = this.store.getRepo(repoId);
if (repo) children.push({ kind: 'repo', repo });
}
return children;
}
if (node.kind === 'repo') {
return this.store
.listWorktrees(node.repo.id)
.map((worktree) => ({ kind: 'worktree', repoId: node.repo.id, worktree }));
}
if (node.kind === 'worktree') {
return this.store
.sessionsForWorktree(node.worktree.path)
.map((session) => ({ kind: 'session', session, repoId: node.repoId, worktreePath: node.worktree.path }));
}
return [];
}
}