go/pkg/cli/tree.go
Claude 228170d610
feat(cli): TUI components, Frame AppShell, command builders (#11-15)
Issues #11-13: WithAppName for variant binaries, NewPassthrough builder
for flag.FlagSet commands, RegisterCommands test coverage with resetGlobals
helper. Fix pre-existing daemon_test.go break.

Issue #14: Rich Table with box-drawing borders (Normal, Rounded, Heavy,
Double), per-column CellStyleFn, WithMaxWidth responsive truncation.
Tree renderer with box-drawing connectors and styled nodes. Parallel
TaskTracker with braille spinners, thread-safe concurrent updates, and
non-TTY fallback. Streaming text renderer with word-wrap and channel
pattern support.

Issue #15: Frame live compositional AppShell using HLCRF regions with
Model interface, Navigate/Back content swapping, alt-screen live mode,
graceful non-TTY fallback. Built-in region components: StatusLine,
KeyHints, Breadcrumb. Zero new dependencies — pure ANSI + x/term.

68 tests, all passing with -race.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 19:45:23 +00:00

98 lines
2.3 KiB
Go

package cli
import (
"fmt"
"strings"
)
// TreeNode represents a node in a displayable tree structure.
// Use NewTree to create a root, then Add children.
//
// tree := cli.NewTree("core-php")
// tree.Add("core-tenant").Add("core-bio")
// tree.Add("core-admin")
// tree.Add("core-api")
// fmt.Print(tree)
// // core-php
// // ├── core-tenant
// // │ └── core-bio
// // ├── core-admin
// // └── core-api
type TreeNode struct {
label string
style *AnsiStyle
children []*TreeNode
}
// NewTree creates a new tree with the given root label.
func NewTree(label string) *TreeNode {
return &TreeNode{label: label}
}
// Add appends a child node and returns the child for chaining.
func (n *TreeNode) Add(label string) *TreeNode {
child := &TreeNode{label: label}
n.children = append(n.children, child)
return child
}
// AddStyled appends a styled child node and returns the child for chaining.
func (n *TreeNode) AddStyled(label string, style *AnsiStyle) *TreeNode {
child := &TreeNode{label: label, style: style}
n.children = append(n.children, child)
return child
}
// AddTree appends an existing tree as a child and returns the parent for chaining.
func (n *TreeNode) AddTree(child *TreeNode) *TreeNode {
n.children = append(n.children, child)
return n
}
// WithStyle sets the style on this node and returns it for chaining.
func (n *TreeNode) WithStyle(style *AnsiStyle) *TreeNode {
n.style = style
return n
}
// String renders the tree with box-drawing characters.
// Implements fmt.Stringer.
func (n *TreeNode) String() string {
var sb strings.Builder
sb.WriteString(n.renderLabel())
sb.WriteByte('\n')
n.writeChildren(&sb, "")
return sb.String()
}
// Render prints the tree to stdout.
func (n *TreeNode) Render() {
fmt.Print(n.String())
}
func (n *TreeNode) renderLabel() string {
if n.style != nil {
return n.style.Render(n.label)
}
return n.label
}
func (n *TreeNode) writeChildren(sb *strings.Builder, prefix string) {
for i, child := range n.children {
last := i == len(n.children)-1
connector := "├── "
next := "│ "
if last {
connector = "└── "
next = " "
}
sb.WriteString(prefix)
sb.WriteString(connector)
sb.WriteString(child.renderLabel())
sb.WriteByte('\n')
child.writeChildren(sb, prefix+next)
}
}