/** * Groups a flat list of file paths into a directory tree, the way GitLab's * MR "Changes" file list (and most file explorers) present them, instead of * one flat line per full path. */ export interface TreeRow { depth: number; label: string; isDir: boolean; /** Index into the original `paths` array. Present only for file rows. */ fileIndex?: number; } interface Node { children: Map; fileIndex?: number; } export function buildTreeRows(paths: readonly string[]): TreeRow[] { const root: Node = { children: new Map() }; paths.forEach((path, index) => { const parts = path.split("/").filter(Boolean); let node = root; parts.forEach((part, i) => { let child = node.children.get(part); if (!child) { child = { children: new Map() }; node.children.set(part, child); } node = child; if (i === parts.length - 1) node.fileIndex = index; }); }); const rows: TreeRow[] = []; function walk(node: Node, depth: number): void { for (const [name, child] of node.children) { const isDir = child.children.size > 0; if (isDir) { rows.push({ depth, label: `${name}/`, isDir: true }); walk(child, depth + 1); } else { rows.push({ depth, label: name, isDir: false, fileIndex: child.fileIndex }); } } } walk(root, 0); return rows; }