Skip to content

Commit bafb8fd

Browse files
authored
feat(tree): hide children (#460)
* feat(tree): add SetHidden option for Nodes * feat(tree): add SetValue for Leaf and Tree * fix(tree): render correct prefix when items are hidden * fixup! feat(tree): add SetHidden option for Nodes * fix(tree): render last element prefix correctly * fix(lint): remove ineffectual assignment * docs(examples): add testable examples for Hide and SetHidden * test(tree): add ExampleSetValue * refactor(tree): clarify SetValue usage in test * chore(tree): remove unused styles from ExampleLeaf_SetValue
1 parent 8af45f8 commit bafb8fd

File tree

3 files changed

+199
-10
lines changed

3 files changed

+199
-10
lines changed

tree/example_test.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package tree_test
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/charmbracelet/lipgloss/tree"
7+
"github.com/charmbracelet/x/ansi"
8+
)
9+
10+
// Leaf Examples
11+
12+
func ExampleLeaf_SetHidden() {
13+
tr := tree.New().
14+
Child(
15+
"Foo",
16+
tree.Root("Bar").
17+
Child(
18+
"Qux",
19+
tree.Root("Quux").
20+
Child("Hello!"),
21+
"Quuux",
22+
),
23+
"Baz",
24+
)
25+
26+
tr.Children().At(1).Children().At(2).SetHidden(true)
27+
fmt.Println(tr.String())
28+
// Output:
29+
//
30+
// ├── Foo
31+
// ├── Bar
32+
// │ ├── Qux
33+
// │ └── Quux
34+
// │ └── Hello!
35+
// └── Baz
36+
//
37+
}
38+
39+
func ExampleNewLeaf() {
40+
tr := tree.New().
41+
Child(
42+
"Foo",
43+
tree.Root("Bar").
44+
Child(
45+
"Qux",
46+
tree.Root("Quux").
47+
Child(
48+
tree.NewLeaf("This should be hidden", true),
49+
tree.NewLeaf(
50+
tree.Root("I am groot").Child("leaves"), false),
51+
),
52+
"Quuux",
53+
),
54+
"Baz",
55+
)
56+
57+
fmt.Println(tr.String())
58+
// Output:
59+
// ├── Foo
60+
// ├── Bar
61+
// │ ├── Qux
62+
// │ ├── Quux
63+
// │ │ └── I am groot
64+
// │ │ └── leaves
65+
// │ └── Quuux
66+
// └── Baz
67+
//
68+
}
69+
70+
func ExampleLeaf_SetValue() {
71+
t := tree.
72+
Root("⁜ Makeup").
73+
Child(
74+
"Glossier",
75+
"Fenty Beauty",
76+
tree.New().Child(
77+
"Gloss Bomb Universal Lip Luminizer",
78+
"Hot Cheeks Velour Blushlighter",
79+
),
80+
"Nyx",
81+
"Mac",
82+
"Milk",
83+
).
84+
Enumerator(tree.RoundedEnumerator)
85+
glossier := t.Children().At(0)
86+
glossier.SetValue("Il Makiage")
87+
fmt.Println(ansi.Strip(t.String()))
88+
// Output:
89+
//⁜ Makeup
90+
//├── Il Makiage
91+
//├── Fenty Beauty
92+
//│ ├── Gloss Bomb Universal Lip Luminizer
93+
//│ ╰── Hot Cheeks Velour Blushlighter
94+
//├── Nyx
95+
//├── Mac
96+
//╰── Milk
97+
}
98+
99+
// Tree Examples
100+
101+
func ExampleTree_Hide() {
102+
tr := tree.New().
103+
Child(
104+
"Foo",
105+
tree.Root("Bar").
106+
Child(
107+
"Qux",
108+
tree.Root("Quux").
109+
Child("Foo", "Bar").
110+
Hide(true),
111+
"Quuux",
112+
),
113+
"Baz",
114+
)
115+
116+
fmt.Println(tr.String())
117+
// Output:
118+
// ├── Foo
119+
// ├── Bar
120+
// │ ├── Qux
121+
// │ └── Quuux
122+
// └── Baz
123+
}
124+
125+
func ExampleTree_SetHidden() {
126+
tr := tree.New().
127+
Child(
128+
"Foo",
129+
tree.Root("Bar").
130+
Child(
131+
"Qux",
132+
tree.Root("Quux").
133+
Child("Foo", "Bar"),
134+
"Quuux",
135+
),
136+
"Baz",
137+
)
138+
139+
// Hide a tree after its creation. We'll hide Quux.
140+
tr.Children().At(1).Children().At(1).SetHidden(true)
141+
// Output:
142+
// ├── Foo
143+
// ├── Bar
144+
// │ ├── Qux
145+
// │ └── Quuux
146+
// └── Baz
147+
//
148+
fmt.Println(tr.String())
149+
}

tree/renderer.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,15 @@ func (r *renderer) render(node Node, root bool, prefix string) string {
5555
}
5656

5757
for i := 0; i < children.Length(); i++ {
58+
if i < children.Length()-1 {
59+
if child := children.At(i + 1); child.Hidden() {
60+
// Don't count the last child if its hidden. This renders the
61+
// last visible element with the right prefix
62+
//
63+
// The only type of Children is NodeChildren.
64+
children = children.(NodeChildren).Remove(i + 1)
65+
}
66+
}
5867
prefix := enumerator(children, i)
5968
prefix = r.style.enumeratorFunc(children, i).Render(prefix)
6069
maxLen = max(lipgloss.Width(prefix), maxLen)

tree/tree.go

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ type Node interface {
3838
Value() string
3939
Children() Children
4040
Hidden() bool
41+
SetHidden(bool)
42+
SetValue(any)
4143
}
4244

4345
// Leaf is a node without children.
@@ -46,21 +48,44 @@ type Leaf struct {
4648
hidden bool
4749
}
4850

51+
// NewLeaf returns a new Leaf.
52+
func NewLeaf(value any, hidden bool) *Leaf {
53+
s := Leaf{}
54+
s.SetValue(value)
55+
s.SetHidden(hidden)
56+
return &s
57+
}
58+
4959
// Children of a Leaf node are always empty.
5060
func (Leaf) Children() Children {
5161
return NodeChildren(nil)
5262
}
5363

54-
// Value of a leaf node returns its value.
64+
// Value returns the value of a Leaf node.
5565
func (s Leaf) Value() string {
5666
return s.value
5767
}
5868

69+
// SetValue sets the value of a Leaf node.
70+
func (s *Leaf) SetValue(value any) {
71+
switch item := value.(type) {
72+
case Node, fmt.Stringer:
73+
s.value = item.(fmt.Stringer).String()
74+
case string, nil:
75+
s.value = item.(string)
76+
default:
77+
s.value = fmt.Sprintf("%v", item)
78+
}
79+
}
80+
5981
// Hidden returns whether a Leaf node is hidden.
6082
func (s Leaf) Hidden() bool {
6183
return s.hidden
6284
}
6385

86+
// SetHidden hides a Leaf node.
87+
func (s *Leaf) SetHidden(hidden bool) { s.hidden = hidden }
88+
6489
// String returns the string representation of a Leaf node.
6590
func (s Leaf) String() string {
6691
return s.Value()
@@ -77,18 +102,22 @@ type Tree struct { //nolint:revive
77102
ronce sync.Once
78103
}
79104

80-
// Hidden returns whether this node is hidden.
105+
// Hidden returns whether a Tree node is hidden.
81106
func (t *Tree) Hidden() bool {
82107
return t.hidden
83108
}
84109

85-
// Hide sets whether to hide the tree node.
110+
// Hide sets whether to hide the Tree node. Use this when creating a new
111+
// hidden Tree.
86112
func (t *Tree) Hide(hide bool) *Tree {
87113
t.hidden = hide
88114
return t
89115
}
90116

91-
// Offset sets the tree children offsets.
117+
// SetHidden hides a Tree node.
118+
func (t *Tree) SetHidden(hidden bool) { t.Hide(hidden) }
119+
120+
// Offset sets the Tree children offsets.
92121
func (t *Tree) Offset(start, end int) *Tree {
93122
if start > end {
94123
_start := start
@@ -113,12 +142,17 @@ func (t *Tree) Value() string {
113142
return t.value
114143
}
115144

116-
// String returns the string representation of the tree node.
145+
// SetValue sets the value of a Tree node.
146+
func (t *Tree) SetValue(value any) {
147+
t.Root(value)
148+
}
149+
150+
// String returns the string representation of the Tree node.
117151
func (t *Tree) String() string {
118152
return t.ensureRenderer().render(t, true, "")
119153
}
120154

121-
// Child adds a child to this tree.
155+
// Child adds a child to this Tree.
122156
//
123157
// If a Child Tree is passed without a root, it will be parented to it's sibling
124158
// child (auto-nesting).
@@ -147,7 +181,7 @@ func (t *Tree) Child(children ...any) *Tree {
147181
t.children = t.children.(NodeChildren).Append(item)
148182
case fmt.Stringer:
149183
s := Leaf{value: item.String()}
150-
t.children = t.children.(NodeChildren).Append(s)
184+
t.children = t.children.(NodeChildren).Append(&s)
151185
case string:
152186
s := Leaf{value: item}
153187
t.children = t.children.(NodeChildren).Append(&s)
@@ -180,9 +214,6 @@ func ensureParent(nodes Children, item *Tree) (*Tree, int) {
180214
parent.Child(item.children.At(i))
181215
}
182216
return parent, j
183-
case Leaf:
184-
item.value = parent.Value()
185-
return item, j
186217
case *Leaf:
187218
item.value = parent.Value()
188219
return item, j

0 commit comments

Comments
 (0)