Buckets:
GHHG10/CodeServer / opencode /packages /tui /src /feature-plugins /system /diff-viewer-file-tree-utils.ts
| // Paths branch softly through the screen, | |
| // A quiet tree of changed designs; | |
| // Each leaf remembers what has been, | |
| // And waits where careful light aligns. | |
| export type FileTreeItem = { | |
| readonly file: string | |
| readonly status?: "added" | "deleted" | "modified" | |
| } | |
| export type FileTreeNode = { | |
| readonly id: number | |
| readonly name: string | |
| readonly parent: number | undefined | |
| readonly children: number[] | |
| readonly depth: number | |
| readonly kind: "directory" | "file" | |
| readonly fileIndex?: number | |
| } | |
| export type FileTree = { | |
| readonly roots: number[] | |
| readonly nodes: FileTreeNode[] | |
| } | |
| export type FileTreeRow = { | |
| readonly id: number | |
| readonly depth: number | |
| readonly kind: "directory" | "file" | |
| readonly name: string | |
| readonly fileIndex?: number | |
| } | |
| export function buildFileTree(files: readonly FileTreeItem[]): FileTree { | |
| const roots: number[] = [] | |
| const nodes: FileTreeNode[] = [] | |
| const directoryByPath = new Map<string, number>() | |
| files.forEach((file, fileIndex) => { | |
| const segments = file.file.split("/").filter(Boolean) | |
| if (segments.length === 0) return | |
| const parent = segments.slice(0, -1).reduce( | |
| (state, segment) => { | |
| const directoryPath = state.path ? `${state.path}/${segment}` : segment | |
| const existing = directoryByPath.get(directoryPath) | |
| if (existing !== undefined) return { id: existing, path: directoryPath, depth: state.depth + 1 } | |
| const id = addFileTreeNode(nodes, roots, { | |
| name: segment, | |
| parent: state.id, | |
| depth: state.depth, | |
| kind: "directory", | |
| }) | |
| directoryByPath.set(directoryPath, id) | |
| return { id, path: directoryPath, depth: state.depth + 1 } | |
| }, | |
| { id: undefined as number | undefined, path: "", depth: 0 }, | |
| ) | |
| addFileTreeNode(nodes, roots, { | |
| name: segments[segments.length - 1]!, | |
| parent: parent.id, | |
| depth: parent.depth, | |
| kind: "file", | |
| fileIndex, | |
| }) | |
| }) | |
| const tree = { roots, nodes } | |
| tree.roots.sort((left, right) => compareFileTreeNodes(tree, left, right)) | |
| tree.nodes.forEach((node) => node.children.sort((left, right) => compareFileTreeNodes(tree, left, right))) | |
| return tree | |
| } | |
| export function flattenFileTree(tree: FileTree, expanded?: ReadonlySet<number>): FileTreeRow[] { | |
| const rows: FileTreeRow[] = [] | |
| const visit = (id: number, depth: number) => { | |
| const node = tree.nodes[id]! | |
| if (node.kind === "file") { | |
| rows.push({ | |
| id: node.id, | |
| depth, | |
| kind: node.kind, | |
| name: node.name, | |
| fileIndex: node.fileIndex, | |
| }) | |
| return | |
| } | |
| const chain = collapsedFileTreeDirectoryChain(tree, node.id) | |
| const last = chain[chain.length - 1]! | |
| rows.push({ | |
| id: node.id, | |
| depth, | |
| kind: node.kind, | |
| name: chain.map((item) => item.name).join("/"), | |
| fileIndex: node.fileIndex, | |
| }) | |
| if (!expanded || expanded.has(node.id)) last.children.forEach((child) => visit(child, depth + 1)) | |
| } | |
| tree.roots.forEach((root) => visit(root, 0)) | |
| return rows | |
| } | |
| function collapsedFileTreeDirectoryChain(tree: FileTree, id: number): FileTreeNode[] { | |
| const node = tree.nodes[id]! | |
| const child = node.children.length === 1 ? tree.nodes[node.children[0]!] : undefined | |
| if (child?.kind !== "directory") return [node] | |
| return [node, ...collapsedFileTreeDirectoryChain(tree, child.id)] | |
| } | |
| export function compareFileTreeNodes(tree: FileTree, left: number, right: number) { | |
| const leftNode = tree.nodes[left]! | |
| const rightNode = tree.nodes[right]! | |
| if (leftNode.kind !== rightNode.kind) return leftNode.kind === "directory" ? -1 : 1 | |
| if (leftNode.name < rightNode.name) return -1 | |
| if (leftNode.name > rightNode.name) return 1 | |
| return left - right | |
| } | |
| export function moveFileTreeSelection(rows: readonly FileTreeRow[], selected: number | undefined, offset: number) { | |
| if (rows.length === 0) return undefined | |
| const index = selected === undefined ? -1 : rows.findIndex((row) => row.id === selected) | |
| if (index === -1) return rows[0]!.id | |
| return rows[Math.max(0, Math.min(rows.length - 1, index + offset))]!.id | |
| } | |
| export function moveFileTreeSelectionToFirstChild(rows: readonly FileTreeRow[], selected: number | undefined) { | |
| const index = selected === undefined ? -1 : rows.findIndex((row) => row.id === selected) | |
| const row = index === -1 ? undefined : rows[index] | |
| if (row?.kind !== "directory") return selected | |
| const child = rows[index + 1] | |
| return child && child.depth > row.depth ? child.id : selected | |
| } | |
| export function moveFileTreeSelectionToParent(rows: readonly FileTreeRow[], selected: number | undefined) { | |
| const index = selected === undefined ? -1 : rows.findIndex((row) => row.id === selected) | |
| const row = index === -1 ? undefined : rows[index] | |
| if (!row || row.depth === 0) return selected | |
| return rows.findLast((item, itemIndex) => itemIndex < index && item.depth < row.depth)?.id ?? selected | |
| } | |
| export function moveFileTreeSelectionToFile( | |
| rows: readonly FileTreeRow[], | |
| selected: number | undefined, | |
| offset: number, | |
| ) { | |
| const fileRows = rows.filter((row) => row.fileIndex !== undefined) | |
| if (fileRows.length === 0) return undefined | |
| const selectedIndex = selected === undefined ? -1 : rows.findIndex((row) => row.id === selected) | |
| if (selectedIndex === -1) return offset < 0 ? fileRows[fileRows.length - 1]!.id : fileRows[0]!.id | |
| const next = | |
| offset < 0 | |
| ? fileRows.findLast((row) => rows.findIndex((item) => item.id === row.id) < selectedIndex) | |
| : fileRows.find((row) => rows.findIndex((item) => item.id === row.id) > selectedIndex) | |
| return next?.id ?? (offset < 0 ? fileRows[0]!.id : fileRows[fileRows.length - 1]!.id) | |
| } | |
| export function fileTreeFileSelection(tree: FileTree, fileIndex: number) { | |
| const node = tree.nodes.find((item) => item.kind === "file" && item.fileIndex === fileIndex) | |
| if (!node) return undefined | |
| return { | |
| highlightedNode: node.id, | |
| expandedNodes: fileTreeParentDirectories(tree, node.id), | |
| } | |
| } | |
| export function singlePatchFileIndex( | |
| selected: number | undefined, | |
| active: number | undefined, | |
| current: number | undefined, | |
| first: number | undefined, | |
| ) { | |
| return selected ?? active ?? current ?? first | |
| } | |
| export function orderedPatchFileIndexes(rows: readonly FileTreeRow[]) { | |
| return rows.flatMap((row) => (row.fileIndex === undefined ? [] : [row.fileIndex])) | |
| } | |
| export function showDiffViewerFileTree(showFileTree: boolean, fileCount: number) { | |
| return showFileTree && fileCount > 0 | |
| } | |
| export function movePatchFileIndex(fileIndexes: readonly number[], current: number | undefined, offset: number) { | |
| if (fileIndexes.length === 0) return undefined | |
| const index = current === undefined ? -1 : fileIndexes.indexOf(current) | |
| if (index === -1) return fileIndexes[0] | |
| return fileIndexes[Math.max(0, Math.min(fileIndexes.length - 1, index + offset))] | |
| } | |
| export function allExpandedFileTreeDirectories(tree: FileTree) { | |
| return new Set(tree.nodes.filter((node) => node.kind === "directory").map((node) => node.id)) | |
| } | |
| export function toggleFileTreeDirectory(tree: FileTree, expanded: ReadonlySet<number>, selected: number | undefined) { | |
| if (selected === undefined || tree.nodes[selected]?.kind !== "directory") return expanded | |
| const next = new Set(expanded) | |
| if (next.has(selected)) next.delete(selected) | |
| else next.add(selected) | |
| return next | |
| } | |
| export function setFileTreeDirectoryExpanded( | |
| tree: FileTree, | |
| expanded: ReadonlySet<number>, | |
| selected: number | undefined, | |
| value: boolean, | |
| ) { | |
| if (selected === undefined || tree.nodes[selected]?.kind !== "directory") return expanded | |
| const next = new Set(expanded) | |
| if (value) next.add(selected) | |
| else next.delete(selected) | |
| return next | |
| } | |
| function addFileTreeNode(nodes: FileTreeNode[], roots: number[], input: Omit<FileTreeNode, "id" | "children">) { | |
| const id = nodes.length | |
| nodes.push({ ...input, id, children: [] }) | |
| if (input.parent === undefined) roots.push(id) | |
| else nodes[input.parent]!.children.push(id) | |
| return id | |
| } | |
| function fileTreeParentDirectories(tree: FileTree, id: number) { | |
| const result = new Set<number>() | |
| for (let parent = tree.nodes[id]?.parent; parent !== undefined; parent = tree.nodes[parent]?.parent) { | |
| result.add(parent) | |
| } | |
| return result | |
| } | |
Xet Storage Details
- Size:
- 8.25 kB
- Xet hash:
- 928d5ab04036ec5f563f9779c1227e06d6592a4388effb7d4e84e2c6562e9c3b
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.