mirror of
https://github.com/charmbracelet/lipgloss.git
synced 2024-10-26 22:57:49 +03:00
1afeca0d80
* 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
138 lines
3.2 KiB
Go
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
|
|
}
|