lipgloss/tree/renderer.go
Maas Lalani 1afeca0d80
Tree API Changes (#307)
* refactor: clean tree_test.go

* refactor: remove subtests

* refactor: rename `Data` to `Items` for `Lists` and `Children` for `Trees`

* chore: support only NodeData

* fix: enumerations -> enumerators for consistency

* fix: item -> children

* fix: use l for List, rather than n

* fix: move list Enumerator definition to enumerator.go

* fix: list enumerator comments

* docs: add package documentation

* fix: examples

* feat: split enumerator and indenters

* docs: add comments

* refactor: package comment

* docs: use more tree terminology

* docs: refactor indentor comments

* fix: lint
2024-05-30 17:11:28 -04:00

138 lines
3.2 KiB
Go

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
}