mirror of
https://github.com/charmbracelet/lipgloss.git
synced 2024-09-11 22:17:12 +03:00
* refactor: clean tree_test.go * refactor: remove subtests * refactor: rename `Data` to `Items` for `Lists` and `Children` for `Trees` * chore: support only NodeData * fix: enumerations -> enumerators for consistency * fix: item -> children * fix: use l for List, rather than n * fix: move list Enumerator definition to enumerator.go * fix: list enumerator comments * docs: add package documentation * fix: examples * feat: split enumerator and indenters * docs: add comments * refactor: package comment * docs: use more tree terminology * docs: refactor indentor comments * fix: lint
321 lines
7.4 KiB
Go
321 lines
7.4 KiB
Go
// Package tree allows you to build trees, as simple or complicated as you need.
|
|
//
|
|
// Define a tree with a root node, and children, set rendering properties (such
|
|
// as style, enumerators, etc...), and print it.
|
|
//
|
|
// t := tree.New().
|
|
// Child(
|
|
// ".git",
|
|
// tree.Root("examples/").
|
|
// Child(
|
|
// tree.Root("list/").
|
|
// Child("main.go").
|
|
// tree.Root("table/").
|
|
// Child("main.go").
|
|
// ).
|
|
// tree.Root("list/").
|
|
// Child("list.go", "list_test.go").
|
|
// tree.New().
|
|
// Root("table/").
|
|
// Child("table.go", "table_test.go").
|
|
// "align.go",
|
|
// "align_test.go",
|
|
// "join.go",
|
|
// "join_test.go",
|
|
// )
|
|
package tree
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/charmbracelet/lipgloss"
|
|
)
|
|
|
|
// Node defines a node in a tree.
|
|
type Node interface {
|
|
fmt.Stringer
|
|
Value() string
|
|
Children() Children
|
|
Hidden() bool
|
|
}
|
|
|
|
// Leaf is a node without children.
|
|
type Leaf struct {
|
|
value string
|
|
hidden bool
|
|
}
|
|
|
|
// Children of a Leaf node are always empty.
|
|
func (Leaf) Children() Children {
|
|
return NodeChildren(nil)
|
|
}
|
|
|
|
// Value of a leaf node returns its value.
|
|
func (s Leaf) Value() string {
|
|
return s.value
|
|
}
|
|
|
|
// Hidden returns whether a Leaf node is hidden.
|
|
func (s Leaf) Hidden() bool {
|
|
return s.hidden
|
|
}
|
|
|
|
// String returns the string representation of a Leaf node.
|
|
func (s Leaf) String() string {
|
|
return s.Value()
|
|
}
|
|
|
|
// Tree implements a Node.
|
|
type Tree struct { //nolint:revive
|
|
value string
|
|
hidden bool
|
|
offset [2]int
|
|
children Children
|
|
|
|
r *renderer
|
|
ronce sync.Once
|
|
}
|
|
|
|
// Hidden returns whether this node is hidden.
|
|
func (t *Tree) Hidden() bool {
|
|
return t.hidden
|
|
}
|
|
|
|
// Hide sets whether to hide the tree node.
|
|
func (t *Tree) Hide(hide bool) *Tree {
|
|
t.hidden = hide
|
|
return t
|
|
}
|
|
|
|
// Offset sets the tree children offsets.
|
|
func (t *Tree) Offset(start, end int) *Tree {
|
|
if start > end {
|
|
_start := start
|
|
start = end
|
|
end = _start
|
|
}
|
|
|
|
if start < 0 {
|
|
start = 0
|
|
}
|
|
if end < 0 || end > t.children.Length() {
|
|
end = t.children.Length()
|
|
}
|
|
|
|
t.offset[0] = start
|
|
t.offset[1] = end
|
|
return t
|
|
}
|
|
|
|
// Value returns the root name of this node.
|
|
func (t *Tree) Value() string {
|
|
return t.value
|
|
}
|
|
|
|
// String returns the string representation of the tree node.
|
|
func (t *Tree) String() string {
|
|
return t.ensureRenderer().render(t, true, "")
|
|
}
|
|
|
|
// Child adds a child to this tree.
|
|
//
|
|
// If a Child Tree is passed without a root, it will be parented to it's sibling
|
|
// child (auto-nesting).
|
|
//
|
|
// tree.Root("Foo").Child("Bar", tree.New().Child("Baz"), "Qux")
|
|
// tree.Root("Foo").Child(tree.Root("Bar").Child("Baz"), "Qux")
|
|
//
|
|
// ├── Foo
|
|
// ├── Bar
|
|
// │ └── Baz
|
|
// └── Qux
|
|
func (t *Tree) Child(children ...any) *Tree {
|
|
for _, child := range children {
|
|
switch item := child.(type) {
|
|
case *Tree:
|
|
newItem, rm := ensureParent(t.children, item)
|
|
if rm >= 0 {
|
|
t.children = t.children.(NodeChildren).Remove(rm)
|
|
}
|
|
t.children = t.children.(NodeChildren).Append(newItem)
|
|
case Children:
|
|
for i := 0; i < item.Length(); i++ {
|
|
t.children = t.children.(NodeChildren).Append(item.At(i))
|
|
}
|
|
case Node:
|
|
t.children = t.children.(NodeChildren).Append(item)
|
|
case fmt.Stringer:
|
|
s := Leaf{value: item.String()}
|
|
t.children = t.children.(NodeChildren).Append(s)
|
|
case string:
|
|
s := Leaf{value: item}
|
|
t.children = t.children.(NodeChildren).Append(&s)
|
|
case []any:
|
|
return t.Child(item...)
|
|
case []string:
|
|
ss := make([]any, 0, len(item))
|
|
for _, s := range item {
|
|
ss = append(ss, s)
|
|
}
|
|
return t.Child(ss...)
|
|
case nil:
|
|
continue
|
|
default:
|
|
return t.Child(fmt.Sprintf("%v", item))
|
|
}
|
|
}
|
|
return t
|
|
}
|
|
|
|
func ensureParent(nodes Children, item *Tree) (*Tree, int) {
|
|
if item.Value() != "" || nodes.Length() == 0 {
|
|
return item, -1
|
|
}
|
|
j := nodes.Length() - 1
|
|
parent := nodes.At(j)
|
|
switch parent := parent.(type) {
|
|
case *Tree:
|
|
for i := 0; i < item.Children().Length(); i++ {
|
|
parent.Child(item.children.At(i))
|
|
}
|
|
return parent, j
|
|
case Leaf:
|
|
item.value = parent.Value()
|
|
return item, j
|
|
case *Leaf:
|
|
item.value = parent.Value()
|
|
return item, j
|
|
}
|
|
return item, -1
|
|
}
|
|
|
|
func (t *Tree) ensureRenderer() *renderer {
|
|
t.ronce.Do(func() { t.r = newRenderer() })
|
|
return t.r
|
|
}
|
|
|
|
// EnumeratorStyle sets a static style for all enumerators.
|
|
//
|
|
// Use EnumeratorStyleFunc to conditionally set styles based on the tree node.
|
|
func (t *Tree) EnumeratorStyle(style lipgloss.Style) *Tree {
|
|
t.ensureRenderer().style.enumeratorFunc = func(Children, int) lipgloss.Style {
|
|
return style
|
|
}
|
|
return t
|
|
}
|
|
|
|
// EnumeratorStyleFunc sets the enumeration style function. Use this function
|
|
// for conditional styling.
|
|
//
|
|
// t := tree.New().
|
|
// EnumeratorStyleFunc(func(_ tree.Children, i int) lipgloss.Style {
|
|
// if selected == i {
|
|
// return lipgloss.NewStyle().Foreground(hightlightColor)
|
|
// }
|
|
// return lipgloss.NewStyle().Foreground(dimColor)
|
|
// })
|
|
func (t *Tree) EnumeratorStyleFunc(fn StyleFunc) *Tree {
|
|
if fn == nil {
|
|
fn = func(Children, int) lipgloss.Style { return lipgloss.NewStyle() }
|
|
}
|
|
t.ensureRenderer().style.enumeratorFunc = fn
|
|
return t
|
|
}
|
|
|
|
// ItemStyle sets a static style for all items.
|
|
//
|
|
// Use ItemStyleFunc to conditionally set styles based on the tree node.
|
|
func (t *Tree) ItemStyle(style lipgloss.Style) *Tree {
|
|
t.ensureRenderer().style.itemFunc = func(Children, int) lipgloss.Style { return style }
|
|
return t
|
|
}
|
|
|
|
// ItemStyleFunc sets the item style function. Use this for conditional styling.
|
|
// For example:
|
|
//
|
|
// t := tree.New().
|
|
// ItemStyleFunc(func(_ tree.Data, i int) lipgloss.Style {
|
|
// if selected == i {
|
|
// return lipgloss.NewStyle().Foreground(hightlightColor)
|
|
// }
|
|
// return lipgloss.NewStyle().Foreground(dimColor)
|
|
// })
|
|
func (t *Tree) ItemStyleFunc(fn StyleFunc) *Tree {
|
|
if fn == nil {
|
|
fn = func(Children, int) lipgloss.Style { return lipgloss.NewStyle() }
|
|
}
|
|
t.ensureRenderer().style.itemFunc = fn
|
|
return t
|
|
}
|
|
|
|
// Enumerator sets the enumerator implementation. This can be used to change the way the branches indicators look.
|
|
// Lipgloss includes predefined enumerators including bullets, roman numerals, and more. For
|
|
// example, you can have a numbered list:
|
|
//
|
|
// tree.New().
|
|
// Enumerator(Arabic)
|
|
func (t *Tree) Enumerator(enum Enumerator) *Tree {
|
|
t.ensureRenderer().enumerator = enum
|
|
return t
|
|
}
|
|
|
|
// Indenter sets the indenter implementation. This is used to change the way
|
|
// the tree is indented. The default indentor places a border connecting sibling
|
|
// elements and no border for the last child.
|
|
//
|
|
// └── Foo
|
|
// └── Bar
|
|
// └── Baz
|
|
// └── Qux
|
|
// └── Quux
|
|
//
|
|
// You can define your own indenter.
|
|
//
|
|
// func ArrowIndenter(children tree.Children, index int) string {
|
|
// return "→ "
|
|
// }
|
|
//
|
|
// → Foo
|
|
// → → Bar
|
|
// → → → Baz
|
|
// → → → → Qux
|
|
// → → → → → Quux
|
|
func (t *Tree) Indenter(indenter Indenter) *Tree {
|
|
t.ensureRenderer().indenter = indenter
|
|
return t
|
|
}
|
|
|
|
// Children returns the children of a node.
|
|
func (t *Tree) Children() Children {
|
|
var data []Node
|
|
for i := t.offset[0]; i < t.children.Length()-t.offset[1]; i++ {
|
|
data = append(data, t.children.At(i))
|
|
}
|
|
return NodeChildren(data)
|
|
}
|
|
|
|
// Root returns a new tree with the root set.
|
|
//
|
|
// tree.Root(root)
|
|
//
|
|
// It is a shorthand for:
|
|
//
|
|
// tree.New().Root(root)
|
|
func Root(root string) *Tree {
|
|
return New().Root(root)
|
|
}
|
|
|
|
// Root sets the root value of this tree.
|
|
func (t *Tree) Root(root string) *Tree {
|
|
t.value = root
|
|
return t
|
|
}
|
|
|
|
// New returns a new tree.
|
|
func New() *Tree {
|
|
return &Tree{
|
|
children: NodeChildren(nil),
|
|
}
|
|
}
|