Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tree): hide children #454

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
119 changes: 119 additions & 0 deletions tree/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package tree_test

import (
"fmt"

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

// 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
//
}

// 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
27 changes: 27 additions & 0 deletions tree/tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,33 @@ func TestTypes(t *testing.T) {
assertEqual(t, want, tree.String())
}

func TestLeafHidden(t *testing.T) {
tr := tree.New().
Child(
"Foo",
tree.Root("Bar").
Child(
"Qux",
tree.Root("Quux").
Child("This should be hidden").
Hide(true),
"Quuux",
),
"Baz",
)

// Hide Qux.
tr.Children().At(1).Children().At(0).SetHidden(true)

want := `
├── Foo
├── Bar
│ └── Quuux
└── Baz
`
assertEqual(t, want, tr.String())
}

// assertEqual verifies the strings are equal, assuming its terminal output.
func assertEqual(tb testing.TB, want, got string) {
tb.Helper()
Expand Down
Loading