|
|
@@ -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>
|