File: tree.go

package info (click to toggle)
golang-github-charmbracelet-lipgloss 0.12.1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 600 kB
  • sloc: makefile: 3
file content (320 lines) | stat: -rw-r--r-- 7,581 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
// Package tree allows you to build trees, as simple or complicated as you need.
//
// Define a tree with a root node, and children, set rendering properties (such
// as style, enumerators, etc...), and print it.
//
//	t := tree.New().
//		Child(
//			".git",
//			tree.Root("examples/").
//				Child(
//					tree.Root("list/").
//						Child("main.go").
//					tree.Root("table/").
//						Child("main.go").
//				).
//			tree.Root("list/").
//				Child("list.go", "list_test.go").
//			tree.New().
//				Root("table/").
//				Child("table.go", "table_test.go").
//			"align.go",
//			"align_test.go",
//			"join.go",
//			"join_test.go",
//		)
package tree

import (
	"fmt"
	"sync"

	"github.com/charmbracelet/lipgloss"
)

// Node defines a node in a tree.
type Node interface {
	fmt.Stringer
	Value() string
	Children() Children
	Hidden() bool
}

// Leaf is a node without children.
type Leaf struct {
	value  string
	hidden bool
}

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

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

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

// String returns the string representation of a Leaf node.
func (s Leaf) String() string {
	return s.Value()
}

// Tree implements a Node.
type Tree struct { //nolint:revive
	value    string
	hidden   bool
	offset   [2]int
	children Children

	r     *renderer
	ronce sync.Once
}

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

// Hide sets whether to hide the tree node.
func (t *Tree) Hide(hide bool) *Tree {
	t.hidden = hide
	return t
}

// Offset sets the tree children offsets.
func (t *Tree) Offset(start, end int) *Tree {
	if start > end {
		_start := start
		start = end
		end = _start
	}

	if start < 0 {
		start = 0
	}
	if end < 0 || end > t.children.Length() {
		end = t.children.Length()
	}

	t.offset[0] = start
	t.offset[1] = end
	return t
}

// Value returns the root name of this node.
func (t *Tree) Value() string {
	return t.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.
//
// If a Child Tree is passed without a root, it will be parented to it's sibling
// child (auto-nesting).
//
//	tree.Root("Foo").Child("Bar", tree.New().Child("Baz"), "Qux")
//	tree.Root("Foo").Child(tree.Root("Bar").Child("Baz"), "Qux")
//
//	├── Foo
//	├── Bar
//	│   └── Baz
//	└── Qux
func (t *Tree) Child(children ...any) *Tree {
	for _, child := range children {
		switch item := child.(type) {
		case *Tree:
			newItem, rm := ensureParent(t.children, item)
			if rm >= 0 {
				t.children = t.children.(NodeChildren).Remove(rm)
			}
			t.children = t.children.(NodeChildren).Append(newItem)
		case Children:
			for i := 0; i < item.Length(); i++ {
				t.children = t.children.(NodeChildren).Append(item.At(i))
			}
		case Node:
			t.children = t.children.(NodeChildren).Append(item)
		case fmt.Stringer:
			s := Leaf{value: item.String()}
			t.children = t.children.(NodeChildren).Append(s)
		case string:
			s := Leaf{value: item}
			t.children = t.children.(NodeChildren).Append(&s)
		case []any:
			return t.Child(item...)
		case []string:
			ss := make([]any, 0, len(item))
			for _, s := range item {
				ss = append(ss, s)
			}
			return t.Child(ss...)
		case nil:
			continue
		default:
			return t.Child(fmt.Sprintf("%v", item))
		}
	}
	return t
}

func ensureParent(nodes Children, item *Tree) (*Tree, int) {
	if item.Value() != "" || nodes.Length() == 0 {
		return item, -1
	}
	j := nodes.Length() - 1
	parent := nodes.At(j)
	switch parent := parent.(type) {
	case *Tree:
		for i := 0; i < item.Children().Length(); i++ {
			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
	}
	return item, -1
}

func (t *Tree) ensureRenderer() *renderer {
	t.ronce.Do(func() { t.r = newRenderer() })
	return t.r
}

// EnumeratorStyle sets a static style for all enumerators.
//
// Use EnumeratorStyleFunc to conditionally set styles based on the tree node.
func (t *Tree) EnumeratorStyle(style lipgloss.Style) *Tree {
	t.ensureRenderer().style.enumeratorFunc = func(Children, int) lipgloss.Style {
		return style
	}
	return t
}

// EnumeratorStyleFunc sets the enumeration style function. Use this function
// for conditional styling.
//
//	t := tree.New().
//		EnumeratorStyleFunc(func(_ tree.Children, i int) lipgloss.Style {
//		    if selected == i {
//		        return lipgloss.NewStyle().Foreground(hightlightColor)
//		    }
//		    return lipgloss.NewStyle().Foreground(dimColor)
//		})
func (t *Tree) EnumeratorStyleFunc(fn StyleFunc) *Tree {
	if fn == nil {
		fn = func(Children, int) lipgloss.Style { return lipgloss.NewStyle() }
	}
	t.ensureRenderer().style.enumeratorFunc = fn
	return t
}

// ItemStyle sets a static style for all items.
//
// Use ItemStyleFunc to conditionally set styles based on the tree node.
func (t *Tree) ItemStyle(style lipgloss.Style) *Tree {
	t.ensureRenderer().style.itemFunc = func(Children, int) lipgloss.Style { return style }
	return t
}

// ItemStyleFunc sets the item style function. Use this for conditional styling.
// For example:
//
//	t := tree.New().
//		ItemStyleFunc(func(_ tree.Data, i int) lipgloss.Style {
//			if selected == i {
//				return lipgloss.NewStyle().Foreground(hightlightColor)
//			}
//			return lipgloss.NewStyle().Foreground(dimColor)
//		})
func (t *Tree) ItemStyleFunc(fn StyleFunc) *Tree {
	if fn == nil {
		fn = func(Children, int) lipgloss.Style { return lipgloss.NewStyle() }
	}
	t.ensureRenderer().style.itemFunc = fn
	return t
}

// Enumerator sets the enumerator implementation. This can be used to change the way the branches indicators look.
// Lipgloss includes predefined enumerators including bullets, roman numerals, and more. For
// example, you can have a numbered list:
//
//	tree.New().
//		Enumerator(Arabic)
func (t *Tree) Enumerator(enum Enumerator) *Tree {
	t.ensureRenderer().enumerator = enum
	return t
}

// Indenter sets the indenter implementation. This is used to change the way
// the tree is indented. The default indentor places a border connecting sibling
// elements and no border for the last child.
//
//	└── Foo
//	    └── Bar
//	        └── Baz
//	            └── Qux
//	                └── Quux
//
// You can define your own indenter.
//
//	func ArrowIndenter(children tree.Children, index int) string {
//		return "→ "
//	}
//
//	→ Foo
//	→ → Bar
//	→ → → Baz
//	→ → → → Qux
//	→ → → → → Quux
func (t *Tree) Indenter(indenter Indenter) *Tree {
	t.ensureRenderer().indenter = indenter
	return t
}

// Children returns the children of a node.
func (t *Tree) Children() Children {
	var data []Node
	for i := t.offset[0]; i < t.children.Length()-t.offset[1]; i++ {
		data = append(data, t.children.At(i))
	}
	return NodeChildren(data)
}

// Root returns a new tree with the root set.
//
//	tree.Root(root)
//
// It is a shorthand for:
//
//	tree.New().Root(root)
func Root(root string) *Tree {
	return New().Root(root)
}

// Root sets the root value of this tree.
func (t *Tree) Root(root string) *Tree {
	t.value = root
	return t
}

// New returns a new tree.
func New() *Tree {
	return &Tree{
		children: NodeChildren(nil),
	}
}