From ca7c1feaa1d2ab14320d583e77618ee448440d80 Mon Sep 17 00:00:00 2001 From: bashbunni Date: Tue, 10 Dec 2024 09:02:11 -0800 Subject: [PATCH 01/10] feat(tree): add SetHidden option for Nodes --- tree/tree.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tree/tree.go b/tree/tree.go index 94f5dd08..272fceb5 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -51,7 +51,7 @@ 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 } @@ -61,6 +61,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() @@ -77,18 +80,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 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 @@ -113,12 +120,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 719750ebe027175a9152edfa3d1d1d2e73f5ec1e Mon Sep 17 00:00:00 2001 From: bashbunni Date: Thu, 12 Dec 2024 08:26:07 -0800 Subject: [PATCH 02/10] feat(tree): add SetValue for Leaf and Tree --- tree/tree.go | 33 +++++++++++++++++++++++--- tree/tree_test.go | 59 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/tree/tree.go b/tree/tree.go index 272fceb5..c2893c3a 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -38,6 +38,8 @@ type Node interface { Value() string Children() Children Hidden() bool + SetHidden(bool) + SetValue(any) } // Leaf is a node without children. @@ -46,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. @@ -92,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. @@ -120,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 3ee1fd34..beb27ea8 100644 --- a/tree/tree_test.go +++ b/tree/tree_test.go @@ -1,6 +1,7 @@ package tree_test import ( + "fmt" "strings" "testing" "unicode" @@ -729,3 +730,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 2d2c409917ba92a67c9eb79b1e71c81b8b5488fc Mon Sep 17 00:00:00 2001 From: bashbunni Date: Thu, 12 Dec 2024 08:27:19 -0800 Subject: [PATCH 03/10] 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 a778aa16971f310cf79339440e5b26e7c78dc950 Mon Sep 17 00:00:00 2001 From: bashbunni Date: Fri, 10 Jan 2025 10:50:26 -0800 Subject: [PATCH 04/10] fixup! feat(tree): add SetHidden option for Nodes --- tree/tree.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tree/tree.go b/tree/tree.go index c2893c3a..fdb629b0 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -181,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) @@ -214,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 From b18a974e75be4e1d386b0f704e0dabc8b1d737ba Mon Sep 17 00:00:00 2001 From: bashbunni Date: Thu, 12 Dec 2024 08:33:45 -0800 Subject: [PATCH 05/10] 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 3e227dea3b7e178b3e7d5d180b709836efb38032 Mon Sep 17 00:00:00 2001 From: bashbunni Date: Thu, 12 Dec 2024 08:38:19 -0800 Subject: [PATCH 06/10] 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 1c1eb7abd6dcd1acc8fb9dbe9492d82ea28977de Mon Sep 17 00:00:00 2001 From: bashbunni Date: Thu, 12 Dec 2024 08:55:07 -0800 Subject: [PATCH 07/10] 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 beb27ea8..3ee1fd34 100644 --- a/tree/tree_test.go +++ b/tree/tree_test.go @@ -1,7 +1,6 @@ package tree_test import ( - "fmt" "strings" "testing" "unicode" @@ -730,61 +729,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 - // -} From 0cdf8758449c143b7894e09e24004bddb2e105f4 Mon Sep 17 00:00:00 2001 From: bashbunni Date: Thu, 9 Jan 2025 09:45:21 -0800 Subject: [PATCH 08/10] test(tree): add ExampleSetValue --- tree/example_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tree/example_test.go b/tree/example_test.go index 158a3a8b..ad3c9b02 100644 --- a/tree/example_test.go +++ b/tree/example_test.go @@ -3,6 +3,7 @@ package tree_test import ( "fmt" + "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss/tree" ) @@ -66,6 +67,35 @@ func ExampleNewLeaf() { // } +func ExampleSetValue() { + enumeratorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("63")).MarginRight(1) + rootStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("35")) + itemStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("212")) + + 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). + EnumeratorStyle(enumeratorStyle). + RootStyle(rootStyle). + ItemStyle(itemStyle) + glossier := t.Children().At(0) + glossier.SetValue(tree.Root(glossier.Value()).Child(tree.Root("Apparel").Child("Pink Hoodie", "Baseball Cap"))) + fmt.Println(t.String()) + // Output: + // hello +} + // Tree Examples func ExampleTree_Hide() { From 8cd37f1e66a0b1a9139aa13030fea53d321f9a44 Mon Sep 17 00:00:00 2001 From: bashbunni Date: Fri, 10 Jan 2025 10:53:23 -0800 Subject: [PATCH 09/10] refactor(tree): clarify SetValue usage in test --- tree/example_test.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tree/example_test.go b/tree/example_test.go index ad3c9b02..796d3d0d 100644 --- a/tree/example_test.go +++ b/tree/example_test.go @@ -5,6 +5,7 @@ import ( "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss/tree" + "github.com/charmbracelet/x/ansi" ) // Leaf Examples @@ -67,7 +68,7 @@ func ExampleNewLeaf() { // } -func ExampleSetValue() { +func ExampleLeaf_SetValue() { enumeratorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("63")).MarginRight(1) rootStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("35")) itemStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("212")) @@ -90,10 +91,17 @@ func ExampleSetValue() { RootStyle(rootStyle). ItemStyle(itemStyle) glossier := t.Children().At(0) - glossier.SetValue(tree.Root(glossier.Value()).Child(tree.Root("Apparel").Child("Pink Hoodie", "Baseball Cap"))) - fmt.Println(t.String()) + glossier.SetValue("Il Makiage") + fmt.Println(ansi.Strip(t.String())) // Output: - // hello + //⁜ Makeup + //├── Il Makiage + //├── Fenty Beauty + //│ ├── Gloss Bomb Universal Lip Luminizer + //│ ╰── Hot Cheeks Velour Blushlighter + //├── Nyx + //├── Mac + //╰── Milk } // Tree Examples From 78bd2da3de1b6239711349041ce7375e81275213 Mon Sep 17 00:00:00 2001 From: bashbunni Date: Fri, 10 Jan 2025 11:21:08 -0800 Subject: [PATCH 10/10] chore(tree): remove unused styles from ExampleLeaf_SetValue --- tree/example_test.go | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/tree/example_test.go b/tree/example_test.go index 796d3d0d..56a8f9bf 100644 --- a/tree/example_test.go +++ b/tree/example_test.go @@ -3,7 +3,6 @@ package tree_test import ( "fmt" - "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss/tree" "github.com/charmbracelet/x/ansi" ) @@ -69,10 +68,6 @@ func ExampleNewLeaf() { } func ExampleLeaf_SetValue() { - enumeratorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("63")).MarginRight(1) - rootStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("35")) - itemStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("212")) - t := tree. Root("⁜ Makeup"). Child( @@ -86,10 +81,7 @@ func ExampleLeaf_SetValue() { "Mac", "Milk", ). - Enumerator(tree.RoundedEnumerator). - EnumeratorStyle(enumeratorStyle). - RootStyle(rootStyle). - ItemStyle(itemStyle) + Enumerator(tree.RoundedEnumerator) glossier := t.Children().At(0) glossier.SetValue("Il Makiage") fmt.Println(ansi.Strip(t.String()))