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