From 6a2aef2a193d5c9536801136dccd3e41313f6176 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Tue, 10 Dec 2024 13:26:36 -0300 Subject: [PATCH 1/7] feat(tree): hide children Signed-off-by: Carlos Alexandro Becker --- tree/tree.go | 12 ++++++++---- tree/tree_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/tree/tree.go b/tree/tree.go index 94f5dd08..fc8c1904 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -38,6 +38,7 @@ type Node interface { Value() string Children() Children Hidden() bool + SetHidden(bool) } // Leaf is a node without children. @@ -46,6 +47,9 @@ type Leaf struct { hidden bool } +// SetHidden implements Node. +func (s *Leaf) SetHidden(hidden bool) { s.hidden = hidden } + // Children of a Leaf node are always empty. func (Leaf) Children() Children { return NodeChildren(nil) @@ -77,6 +81,9 @@ type Tree struct { //nolint:revive ronce sync.Once } +// SetHidden implements Node. +func (t *Tree) SetHidden(hidden bool) { t.Hide(hidden) } + // Hidden returns whether this node is hidden. func (t *Tree) Hidden() bool { return t.hidden @@ -147,7 +154,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) @@ -180,9 +187,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 diff --git a/tree/tree_test.go b/tree/tree_test.go index 3ee1fd34..94fd0d9f 100644 --- a/tree/tree_test.go +++ b/tree/tree_test.go @@ -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() From 6a45d13bdfee954f6c17d63b9fadbab56b09ba29 Mon Sep 17 00:00:00 2001 From: bashbunni Date: Tue, 10 Dec 2024 09:02:11 -0800 Subject: [PATCH 2/7] feat(tree): add SetHidden option for Nodes --- tree/tree.go | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/tree/tree.go b/tree/tree.go index fc8c1904..827dac5a 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -47,15 +47,12 @@ type Leaf struct { hidden bool } -// SetHidden implements Node. -func (s *Leaf) SetHidden(hidden bool) { s.hidden = hidden } - // Children of a Leaf node are always empty. func (Leaf) Children() Children { return NodeChildren(nil) } -// Value of a leaf node returns its value. +// Value of a Leaf node returns its value. func (s Leaf) Value() string { return s.value } @@ -65,6 +62,9 @@ func (s Leaf) Hidden() bool { return s.hidden } +// SetHidden hides the Leaf. +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() @@ -81,21 +81,22 @@ type Tree struct { //nolint:revive ronce sync.Once } -// SetHidden implements Node. -func (t *Tree) SetHidden(hidden bool) { t.Hide(hidden) } - -// 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 the Tree. +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 @@ -120,12 +121,12 @@ func (t *Tree) Value() string { return t.value } -// String returns the string representation of the tree node. +// 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). From f9581d1bd1d9340bb931589ca4060a6530e2e569 Mon Sep 17 00:00:00 2001 From: bashbunni Date: Thu, 12 Dec 2024 08:26:07 -0800 Subject: [PATCH 3/7] feat(tree): add SetValue for Leaf and Tree --- tree/tree.go | 32 ++++++++++++++++++++++--- tree/tree_test.go | 59 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/tree/tree.go b/tree/tree.go index 827dac5a..fdb629b0 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -39,6 +39,7 @@ type Node interface { Children() Children Hidden() bool SetHidden(bool) + SetValue(any) } // Leaf is a node without children. @@ -47,22 +48,42 @@ 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 the Leaf. +// SetHidden hides a Leaf node. func (s *Leaf) SetHidden(hidden bool) { s.hidden = hidden } // String returns the string representation of a Leaf node. @@ -93,7 +114,7 @@ func (t *Tree) Hide(hide bool) *Tree { return t } -// SetHidden hides the Tree. +// SetHidden hides a Tree node. func (t *Tree) SetHidden(hidden bool) { t.Hide(hidden) } // Offset sets the Tree children offsets. @@ -121,6 +142,11 @@ func (t *Tree) Value() string { return t.value } +// SetValue sets the value of a Tree node. +func (t *Tree) SetValue(value any) { + t = t.Root(value) +} + // String returns the string representation of the Tree node. func (t *Tree) String() string { return t.ensureRenderer().render(t, true, "") diff --git a/tree/tree_test.go b/tree/tree_test.go index 94fd0d9f..90f622b1 100644 --- a/tree/tree_test.go +++ b/tree/tree_test.go @@ -1,6 +1,7 @@ package tree_test import ( + "fmt" "strings" "testing" "unicode" @@ -756,3 +757,61 @@ func trimSpace(s string) string { } return strings.Join(result, "\n") } + +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 + // +} From 7ea672ccba5e328a5b7d2140097df767a9d40b85 Mon Sep 17 00:00:00 2001 From: bashbunni Date: Thu, 12 Dec 2024 08:27:19 -0800 Subject: [PATCH 4/7] fix(tree): render correct prefix when items are hidden --- tree/renderer.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tree/renderer.go b/tree/renderer.go index 8fd86930..2df0f5b5 100644 --- a/tree/renderer.go +++ b/tree/renderer.go @@ -55,9 +55,18 @@ func (r *renderer) render(node Node, root bool, prefix string) string { } for i := 0; i < children.Length(); i++ { - prefix := enumerator(children, i) - prefix = r.style.enumeratorFunc(children, i).Render(prefix) - maxLen = max(lipgloss.Width(prefix), maxLen) + 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) + } } for i := 0; i < children.Length(); i++ { From 9d9e381d0e70ca76a0e871432c992e22fba307b4 Mon Sep 17 00:00:00 2001 From: bashbunni Date: Thu, 12 Dec 2024 08:33:45 -0800 Subject: [PATCH 5/7] fix(tree): render last element prefix correctly --- tree/renderer.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tree/renderer.go b/tree/renderer.go index 2df0f5b5..fea96fae 100644 --- a/tree/renderer.go +++ b/tree/renderer.go @@ -63,10 +63,10 @@ func (r *renderer) render(node Node, root bool, prefix string) string { // 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) } + prefix := enumerator(children, i) + prefix = r.style.enumeratorFunc(children, i).Render(prefix) + maxLen = max(lipgloss.Width(prefix), maxLen) } for i := 0; i < children.Length(); i++ { From 9fbb417f8db47bf2346b3f4b01e8204604e5b422 Mon Sep 17 00:00:00 2001 From: bashbunni Date: Thu, 12 Dec 2024 08:38:19 -0800 Subject: [PATCH 6/7] fix(lint): remove ineffectual assignment --- tree/tree.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tree/tree.go b/tree/tree.go index fdb629b0..4d93670a 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -144,7 +144,7 @@ func (t *Tree) Value() string { // SetValue sets the value of a Tree node. func (t *Tree) SetValue(value any) { - t = t.Root(value) + t.Root(value) } // String returns the string representation of the Tree node. From 88340e01041636b53a0a7976c90fab6107ff6b65 Mon Sep 17 00:00:00 2001 From: bashbunni Date: Thu, 12 Dec 2024 08:55:07 -0800 Subject: [PATCH 7/7] docs(examples): add testable examples for Hide and SetHidden --- tree/example_test.go | 119 +++++++++++++++++++++++++++++++++++++++++++ tree/tree_test.go | 59 --------------------- 2 files changed, 119 insertions(+), 59 deletions(-) create mode 100644 tree/example_test.go diff --git a/tree/example_test.go b/tree/example_test.go new file mode 100644 index 00000000..158a3a8b --- /dev/null +++ b/tree/example_test.go @@ -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()) +} diff --git a/tree/tree_test.go b/tree/tree_test.go index 90f622b1..94fd0d9f 100644 --- a/tree/tree_test.go +++ b/tree/tree_test.go @@ -1,7 +1,6 @@ package tree_test import ( - "fmt" "strings" "testing" "unicode" @@ -757,61 +756,3 @@ func trimSpace(s string) string { } return strings.Join(result, "\n") } - -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 - // -}