fix(app): stack overflow in filetree (#13667)
Co-authored-by: adamelmore <2363879+adamdottv@users.noreply.github.com>
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user