File: renderer.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 (137 lines) | stat: -rw-r--r-- 3,260 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
package tree

import (
	"strings"

	"github.com/charmbracelet/lipgloss"
)

// StyleFunc allows the list to be styled per item.
type StyleFunc func(children Children, i int) lipgloss.Style

// Style is the styling applied to the list.
type Style struct {
	enumeratorFunc StyleFunc
	itemFunc       StyleFunc
}

// newRenderer returns the renderer used to render a tree.
func newRenderer() *renderer {
	return &renderer{
		style: Style{
			enumeratorFunc: func(Children, int) lipgloss.Style {
				return lipgloss.NewStyle().PaddingRight(1)
			},
			itemFunc: func(Children, int) lipgloss.Style {
				return lipgloss.NewStyle()
			},
		},
		enumerator: DefaultEnumerator,
		indenter:   DefaultIndenter,
	}
}

type renderer struct {
	style      Style
	enumerator Enumerator
	indenter   Indenter
}

// render is responsible for actually rendering the tree.
func (r *renderer) render(node Node, root bool, prefix string) string {
	if node.Hidden() {
		return ""
	}
	var strs []string
	var maxLen int
	children := node.Children()
	enumerator := r.enumerator
	indenter := r.indenter

	// print the root node name if its not empty.
	if name := node.Value(); name != "" && root {
		strs = append(strs, r.style.itemFunc(children, -1).Render(name))
	}

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

	for i := 0; i < children.Length(); i++ {
		child := children.At(i)
		if child.Hidden() {
			continue
		}
		indent := indenter(children, i)
		nodePrefix := enumerator(children, i)
		enumStyle := r.style.enumeratorFunc(children, i)
		itemStyle := r.style.itemFunc(children, i)

		nodePrefix = enumStyle.Render(nodePrefix)
		if l := maxLen - lipgloss.Width(nodePrefix); l > 0 {
			nodePrefix = strings.Repeat(" ", l) + nodePrefix
		}

		item := itemStyle.Render(child.Value())
		multineLinePrefix := prefix

		// This dance below is to account for multiline prefixes, e.g. "|\n|".
		// In that case, we need to make sure that both the parent prefix and
		// the current node's prefix have the same height.
		for lipgloss.Height(item) > lipgloss.Height(nodePrefix) {
			nodePrefix = lipgloss.JoinVertical(
				lipgloss.Top,
				nodePrefix,
				enumStyle.Render(indent),
			)
		}
		for lipgloss.Height(nodePrefix) > lipgloss.Height(multineLinePrefix) {
			multineLinePrefix = lipgloss.JoinVertical(
				lipgloss.Top,
				multineLinePrefix,
				prefix,
			)
		}

		strs = append(
			strs,
			lipgloss.JoinHorizontal(
				lipgloss.Left,
				multineLinePrefix,
				nodePrefix,
				item,
			),
		)

		if children.Length() > 0 {
			// here we see if the child has a custom renderer, which means the
			// user set a custom enumerator, style, etc.
			// if it has one, we'll use it to render itself.
			// otherwise, we keep using the current renderer.
			renderer := r
			switch child := child.(type) {
			case *Tree:
				if child.r != nil {
					renderer = child.r
				}
			}
			if s := renderer.render(
				child,
				false,
				prefix+enumStyle.Render(indent),
			); s != "" {
				strs = append(strs, s)
			}
		}
	}
	return lipgloss.JoinVertical(lipgloss.Top, strs...)
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}