Skip to content
149 changes: 149 additions & 0 deletions tree/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package tree_test

import (
"fmt"

"github.com/charmbracelet/lipgloss/tree"
"github.com/charmbracelet/x/ansi"
)

// Leaf Examples

func ExampleLeaf_SetHidden() {
tr := tree.New().
Child(
"Foo",
tree.Root("Bar").
Child(
"Qux",
tree.Root("Quux").
Child("Hello!"),
"Quuux",
),
"Baz",
)

tr.Children().At(1).Children().At(2).SetHidden(true)
fmt.Println(tr.String())
// Output:
//
// ├── Foo
// ├── Bar
// │ ├── Qux
// │ └── Quux
// │ └── Hello!
// └── Baz
//
}

func ExampleNewLeaf() {
tr := tree.New().
Child(
"Foo",
tree.Root("Bar").
Child(
"Qux",
tree.Root("Quux").
Child(
tree.NewLeaf("This should be hidden", true),
tree.NewLeaf(
tree.Root("I am groot").Child("leaves"), false),
),
"Quuux",
),
"Baz",
)

fmt.Println(tr.String())
// Output:
// ├── Foo
// ├── Bar
// │ ├── Qux
// │ ├── Quux
// │ │ └── I am groot
// │ │ └── leaves
// │ └── Quuux
// └── Baz
//
}

func ExampleLeaf_SetValue() {
t := tree.
Root("⁜ Makeup").
Child(
"Glossier",
"Fenty Beauty",
tree.New().Child(
"Gloss Bomb Universal Lip Luminizer",
"Hot Cheeks Velour Blushlighter",
),
"Nyx",
"Mac",
"Milk",
).
Enumerator(tree.RoundedEnumerator)
glossier := t.Children().At(0)
glossier.SetValue("Il Makiage")
fmt.Println(ansi.Strip(t.String()))
// Output:
//⁜ Makeup
//├── Il Makiage
//├── Fenty Beauty
//│ ├── Gloss Bomb Universal Lip Luminizer
//│ ╰── Hot Cheeks Velour Blushlighter
//├── Nyx
//├── Mac
//╰── Milk
}

// Tree Examples

func ExampleTree_Hide() {
tr := tree.New().
Child(
"Foo",
tree.Root("Bar").
Child(
"Qux",
tree.Root("Quux").
Child("Foo", "Bar").
Hide(true),
"Quuux",
),
"Baz",
)

fmt.Println(tr.String())
// Output:
// ├── Foo
// ├── Bar
// │ ├── Qux
// │ └── Quuux
// └── Baz
}

func ExampleTree_SetHidden() {
tr := tree.New().
Child(
"Foo",
tree.Root("Bar").
Child(
"Qux",
tree.Root("Quux").
Child("Foo", "Bar"),
"Quuux",
),
"Baz",
)

// Hide a tree after its creation. We'll hide Quux.
tr.Children().At(1).Children().At(1).SetHidden(true)
// Output:
// ├── Foo
// ├── Bar
// │ ├── Qux
// │ └── Quuux
// └── Baz
//
fmt.Println(tr.String())
}
9 changes: 9 additions & 0 deletions tree/renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ func (r *renderer) render(node Node, root bool, prefix string) string {
}

for i := 0; i < children.Length(); i++ {
if i < children.Length()-1 {
if child := children.At(i + 1); child.Hidden() {
// Don't count the last child if its hidden. This renders the
// last visible element with the right prefix
//
// The only type of Children is NodeChildren.
children = children.(NodeChildren).Remove(i + 1)
}
}
prefix := enumerator(children, i)
prefix = r.style.enumeratorFunc(children, i).Render(prefix)
maxLen = max(lipgloss.Width(prefix), maxLen)
Expand Down
51 changes: 41 additions & 10 deletions tree/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ type Node interface {
Value() string
Children() Children
Hidden() bool
SetHidden(bool)
SetValue(any)
}

// Leaf is a node without children.
Expand All @@ -46,21 +48,44 @@ type Leaf struct {
hidden bool
}

// NewLeaf returns a new Leaf.
func NewLeaf(value any, hidden bool) *Leaf {
s := Leaf{}
s.SetValue(value)
s.SetHidden(hidden)
return &s
}

// Children of a Leaf node are always empty.
func (Leaf) Children() Children {
return NodeChildren(nil)
}

// Value of a leaf node returns its value.
// Value returns the value of a Leaf node.
func (s Leaf) Value() string {
return s.value
}

// SetValue sets the value of a Leaf node.
func (s *Leaf) SetValue(value any) {
switch item := value.(type) {
case Node, fmt.Stringer:
s.value = item.(fmt.Stringer).String()
case string, nil:
s.value = item.(string)
default:
s.value = fmt.Sprintf("%v", item)
}
}

// Hidden returns whether a Leaf node is hidden.
func (s Leaf) Hidden() bool {
return s.hidden
}

// SetHidden hides a Leaf node.
func (s *Leaf) SetHidden(hidden bool) { s.hidden = hidden }

// String returns the string representation of a Leaf node.
func (s Leaf) String() string {
return s.Value()
Expand All @@ -77,18 +102,22 @@ type Tree struct { //nolint:revive
ronce sync.Once
}

// Hidden returns whether this node is hidden.
// Hidden returns whether a Tree node is hidden.
func (t *Tree) Hidden() bool {
return t.hidden
}

// Hide sets whether to hide the tree node.
// Hide sets whether to hide the Tree node. Use this when creating a new
// hidden Tree.
func (t *Tree) Hide(hide bool) *Tree {
t.hidden = hide
return t
}

// Offset sets the tree children offsets.
// SetHidden hides a Tree node.
func (t *Tree) SetHidden(hidden bool) { t.Hide(hidden) }

// Offset sets the Tree children offsets.
func (t *Tree) Offset(start, end int) *Tree {
if start > end {
_start := start
Expand All @@ -113,12 +142,17 @@ func (t *Tree) Value() string {
return t.value
}

// String returns the string representation of the tree node.
// SetValue sets the value of a Tree node.
func (t *Tree) SetValue(value any) {
t.Root(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.
// 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).
Expand Down Expand Up @@ -147,7 +181,7 @@ func (t *Tree) Child(children ...any) *Tree {
t.children = t.children.(NodeChildren).Append(item)
case fmt.Stringer:
s := Leaf{value: item.String()}
t.children = t.children.(NodeChildren).Append(s)
t.children = t.children.(NodeChildren).Append(&s)
case string:
s := Leaf{value: item}
t.children = t.children.(NodeChildren).Append(&s)
Expand Down Expand Up @@ -180,9 +214,6 @@ func ensureParent(nodes Children, item *Tree) (*Tree, int) {
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
Expand Down