fix(app): stack overflow in filetree (#13667)

Co-authored-by: adamelmore <2363879+adamdottv@users.noreply.github.com>
This commit is contained in:
Adam
2026-02-14 19:24:48 -06:00
committed by GitHub
parent c190f5f611
commit 460a87f359

View File

@@ -21,6 +21,8 @@ import {
import { Dynamic } from "solid-js/web" import { Dynamic } from "solid-js/web"
import type { FileNode } from "@opencode-ai/sdk/v2" import type { FileNode } from "@opencode-ai/sdk/v2"
const MAX_DEPTH = 128
function pathToFileUrl(filepath: string): string { function pathToFileUrl(filepath: string): string {
return `file://${encodeFilePath(filepath)}` return `file://${encodeFilePath(filepath)}`
} }
@@ -260,12 +262,20 @@ export default function FileTree(props: {
_marks?: Set<string> _marks?: Set<string>
_deeps?: Map<string, number> _deeps?: Map<string, number>
_kinds?: ReadonlyMap<string, Kind> _kinds?: ReadonlyMap<string, Kind>
_chain?: readonly string[]
}) { }) {
const file = useFile() const file = useFile()
const level = props.level ?? 0 const level = props.level ?? 0
const draggable = () => props.draggable ?? true const draggable = () => props.draggable ?? true
const tooltip = () => props.tooltip ?? true const tooltip = () => props.tooltip ?? true
const key = (p: string) =>
file
.normalize(p)
.replace(/[\\/]+$/, "")
.replaceAll("\\", "/")
const chain = props._chain ? [...props._chain, key(props.path)] : [key(props.path)]
const filter = createMemo(() => { const filter = createMemo(() => {
if (props._filter) return props._filter if (props._filter) return props._filter
@@ -307,23 +317,45 @@ export default function FileTree(props: {
const out = new Map<string, number>() const out = new Map<string, number>()
const visit = (dir: string, lvl: number): number => { const root = props.path
const expanded = file.tree.state(dir)?.expanded ?? false if (!(file.tree.state(root)?.expanded ?? false)) return out
if (!expanded) return -1
const nodes = file.tree.children(dir) const seen = new Set<string>()
const max = nodes.reduce((max, node) => { const stack: { dir: string; lvl: number; i: number; kids: string[]; max: number }[] = []
if (node.type !== "directory") return max
const open = file.tree.state(node.path)?.expanded ?? false
if (!open) return max
return Math.max(max, visit(node.path, lvl + 1))
}, lvl)
out.set(dir, max) const push = (dir: string, lvl: number) => {
return max const id = key(dir)
if (seen.has(id)) return
seen.add(id)
const kids = file.tree
.children(dir)
.filter((node) => node.type === "directory" && (file.tree.state(node.path)?.expanded ?? false))
.map((node) => node.path)
stack.push({ dir, lvl, i: 0, kids, max: lvl })
}
push(root, level - 1)
while (stack.length > 0) {
const top = stack[stack.length - 1]!
if (top.i < top.kids.length) {
const next = top.kids[top.i]!
top.i++
push(next, top.lvl + 1)
continue
}
out.set(top.dir, top.max)
stack.pop()
const parent = stack[stack.length - 1]
if (!parent) continue
parent.max = Math.max(parent.max, top.max)
} }
visit(props.path, level - 1)
return out return out
}) })
@@ -459,21 +491,27 @@ export default function FileTree(props: {
}} }}
style={`left: ${Math.max(0, 8 + level * 12 - 4) + 8}px`} style={`left: ${Math.max(0, 8 + level * 12 - 4) + 8}px`}
/> />
<FileTree <Show
path={node.path} when={level < MAX_DEPTH && !chain.includes(key(node.path))}
level={level + 1} fallback={<div class="px-2 py-1 text-12-regular text-text-weak">...</div>}
allowed={props.allowed} >
modified={props.modified} <FileTree
kinds={props.kinds} path={node.path}
active={props.active} level={level + 1}
draggable={props.draggable} allowed={props.allowed}
tooltip={props.tooltip} modified={props.modified}
onFileClick={props.onFileClick} kinds={props.kinds}
_filter={filter()} active={props.active}
_marks={marks()} draggable={props.draggable}
_deeps={deeps()} tooltip={props.tooltip}
_kinds={kinds()} onFileClick={props.onFileClick}
/> _filter={filter()}
_marks={marks()}
_deeps={deeps()}
_kinds={kinds()}
_chain={chain}
/>
</Show>
</Collapsible.Content> </Collapsible.Content>
</Collapsible> </Collapsible>
</Match> </Match>