summaryrefslogtreecommitdiffhomepage
path: root/packages/app
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-02-14 19:24:48 -0600
committerGitHub <[email protected]>2026-02-14 19:24:48 -0600
commit460a87f359cef2cdcd4638ba49b1d7d652ddedd5 (patch)
tree707f27abb3abb8a292fab0f2f541a0c70beade5a /packages/app
parentc190f5f611c1520a553facc362749f8aefaa5005 (diff)
downloadopencode-460a87f359cef2cdcd4638ba49b1d7d652ddedd5.tar.gz
opencode-460a87f359cef2cdcd4638ba49b1d7d652ddedd5.zip
fix(app): stack overflow in filetree (#13667)
Co-authored-by: adamelmore <[email protected]>
Diffstat (limited to 'packages/app')
-rw-r--r--packages/app/src/components/file-tree.tsx94
1 files changed, 66 insertions, 28 deletions
diff --git a/packages/app/src/components/file-tree.tsx b/packages/app/src/components/file-tree.tsx
index 5552cc90b..758f5a83f 100644
--- a/packages/app/src/components/file-tree.tsx
+++ b/packages/app/src/components/file-tree.tsx
@@ -21,6 +21,8 @@ import {
import { Dynamic } from "solid-js/web"
import type { FileNode } from "@opencode-ai/sdk/v2"
+const MAX_DEPTH = 128
+
function pathToFileUrl(filepath: string): string {
return `file://${encodeFilePath(filepath)}`
}
@@ -260,12 +262,20 @@ export default function FileTree(props: {
_marks?: Set<string>
_deeps?: Map<string, number>
_kinds?: ReadonlyMap<string, Kind>
+ _chain?: readonly string[]
}) {
const file = useFile()
const level = props.level ?? 0
const draggable = () => props.draggable ?? 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(() => {
if (props._filter) return props._filter
@@ -307,23 +317,45 @@ export default function FileTree(props: {
const out = new Map<string, number>()
- const visit = (dir: string, lvl: number): number => {
- const expanded = file.tree.state(dir)?.expanded ?? false
- if (!expanded) return -1
+ const root = props.path
+ if (!(file.tree.state(root)?.expanded ?? false)) return out
+
+ const seen = new Set<string>()
+ const stack: { dir: string; lvl: number; i: number; kids: string[]; max: number }[] = []
+
+ const push = (dir: string, lvl: number) => {
+ const id = key(dir)
+ if (seen.has(id)) return
+ seen.add(id)
- const nodes = file.tree.children(dir)
- const max = nodes.reduce((max, node) => {
- 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)
+ const kids = file.tree
+ .children(dir)
+ .filter((node) => node.type === "directory" && (file.tree.state(node.path)?.expanded ?? false))
+ .map((node) => node.path)
- out.set(dir, max)
- return max
+ 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
})
@@ -459,21 +491,27 @@ export default function FileTree(props: {
}}
style={`left: ${Math.max(0, 8 + level * 12 - 4) + 8}px`}
/>
- <FileTree
- path={node.path}
- level={level + 1}
- allowed={props.allowed}
- modified={props.modified}
- kinds={props.kinds}
- active={props.active}
- draggable={props.draggable}
- tooltip={props.tooltip}
- onFileClick={props.onFileClick}
- _filter={filter()}
- _marks={marks()}
- _deeps={deeps()}
- _kinds={kinds()}
- />
+ <Show
+ when={level < MAX_DEPTH && !chain.includes(key(node.path))}
+ fallback={<div class="px-2 py-1 text-12-regular text-text-weak">...</div>}
+ >
+ <FileTree
+ path={node.path}
+ level={level + 1}
+ allowed={props.allowed}
+ modified={props.modified}
+ kinds={props.kinds}
+ active={props.active}
+ draggable={props.draggable}
+ tooltip={props.tooltip}
+ onFileClick={props.onFileClick}
+ _filter={filter()}
+ _marks={marks()}
+ _deeps={deeps()}
+ _kinds={kinds()}
+ _chain={chain}
+ />
+ </Show>
</Collapsible.Content>
</Collapsible>
</Match>