Private
Public Access
1
0

Add pkg/util/dirtree

Update dir_tree tool to use it
Update filepicker bubble to use it
This commit is contained in:
2025-01-01 05:06:37 +00:00
parent 3cac7e8e11
commit 8e2991da1a
5 changed files with 340 additions and 346 deletions

View File

@@ -0,0 +1,82 @@
package dirtree
import "strings"
// FlattenTree returns []*Node consistent with each node's `Expanded` state
func FlattenTree(node *Node, expandAll bool) []*Node {
lines := make([]*Node, 0)
var flatten func(n *Node)
flatten = func(node *Node) {
lines = append(lines, node)
if node.isDir && (node.Expanded || expandAll) {
for _, child := range node.children {
flatten(child)
}
}
}
flatten(node)
return lines
}
type TreeFormat struct {
Space string
Span string
Node string
NodeLast string
}
var (
Standard = TreeFormat{
Space: " ",
Span: "│ ",
Node: "├── ",
NodeLast: "└── ",
}
Compact = TreeFormat{
Space: " ",
Span: "│ ",
Node: "├ ",
NodeLast: "└ ",
}
)
func RenderNodes(displayNodes []*Node, format TreeFormat, cursor int, renderNode func(*Node) string) string {
var sb strings.Builder
levelIsLast := make(map[int]bool)
for i, node := range displayNodes {
// Cursor indicator
if cursor >= 0 {
if i == cursor {
sb.WriteString("> ")
} else {
sb.WriteString(" ")
}
}
// Tree structure
if node.depth > 0 {
for d := 1; d < node.depth; d++ {
if levelIsLast[d] {
sb.WriteString(format.Space)
} else {
sb.WriteString(format.Span)
}
}
// Find if this node is the last among its siblings
isLast := node.parent.children[len(node.parent.children)-1] == node
levelIsLast[node.depth] = isLast
if isLast {
sb.WriteString(format.NodeLast)
} else {
sb.WriteString(format.Node)
}
}
// Render the node
sb.WriteString(renderNode(node))
sb.WriteString("\n")
}
return sb.String()
}

132
pkg/util/dirtree/tree.go Normal file
View File

@@ -0,0 +1,132 @@
package dirtree
import (
"os"
"path/filepath"
"strings"
)
type Tree struct {
Root *Node
ShowHidden bool
}
type Node struct {
tree *Tree
parent *Node
children []*Node
depth int
Path string
Name string
Expanded bool
isDir bool
selected bool
}
func NewTree(path string) *Tree {
tree := &Tree{
ShowHidden: false,
}
tree.Root = &Node{
tree: tree,
parent: nil,
Path: path,
Name: filepath.Base(path),
isDir: true,
Expanded: true,
}
return tree
}
func (node *Node) IsRoot() bool {
return node.tree.Root == node
}
func (node *Node) Parent() *Node {
return node.parent
}
func (node *Node) IsSelected() bool {
return node.selected
}
func (node *Node) IsDir() bool {
return node.isDir
}
func (node *Node) Select(recurse bool, maxDepth int) {
node.selected = true
if node.isDir && recurse {
node.LoadChildren(recurse, maxDepth, func (n *Node) {
n.selected = true
})
}
}
func (node *Node) Deselect(recurse bool) {
node.selected = false
if node.isDir && recurse {
for _, child := range node.children {
child.Deselect(recurse)
}
}
}
func (node *Node) LoadChildren(recurse bool, maxDepth int, visitFunc func (node *Node)) error {
startDepth := node.depth
var loadChildren func(node *Node) error
loadChildren = func(node *Node) error {
// Load new entries
entries, err := os.ReadDir(node.Path)
if err != nil {
return err
}
// Save current children before loading
oldChildren := make(map[string]*Node)
for _, child := range node.children {
oldChildren[child.Path] = child
}
node.children = make([]*Node, 0)
for _, entry := range entries {
if !node.tree.ShowHidden && strings.HasPrefix(entry.Name(), ".") {
continue
}
child := &Node{
tree: node.tree,
parent: node,
depth: node.depth + 1,
Name: entry.Name(),
isDir: entry.IsDir(),
Path: filepath.Join(node.Path, entry.Name()),
}
oldChild, exists := oldChildren[child.Path]
if child.isDir {
// Preserve child's grandchildren and expanded/selected state
if exists && (oldChild.Expanded || len(oldChild.children) > 0) {
child.Expanded = oldChild.Expanded
child.selected = oldChild.selected
child.children = oldChild.children
for i := range child.children {
child.children[i].parent = child
}
loadChildren(child)
} else if recurse && (maxDepth <= 0 || node.depth - startDepth < maxDepth) {
loadChildren(child)
}
} else if exists {
child.selected = oldChild.selected
}
node.children = append(node.children, child)
if visitFunc != nil {
visitFunc(child)
}
}
return nil
}
return loadChildren(node)
}