lipgloss/tree/renderer.go
Carlos Alexandro Becker d21c576fdf
feat: trees and lists (#264)
* feat: implement list renderer

* feat: data model for list

* feat: add Offset, Height, Indent

* feat: tree renderer

* fix: list example

* test: last tree node is a subtree

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: tree

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: lint

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* docs: example

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* test: inc cov

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: multiline items

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* feat: one approach to sublists

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: tree improvements

* wip

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* wip

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* wip

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* wip

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: prevent having to pass renderer to all trees

* test: force linux line endings

* fix: windows

* fix: lint issues

* fix: ignore lint issue failing forever

* fix: renames

* fix: renderer

* ci: fix coveralls hopefully

* wip

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: style

* docs: update

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: renderer

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* test: cover with tests

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* feat: more list enumerators

* fix: drop renderer api

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* feat: improve api

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: prealloc

* fix: clean up

* fix: list setup

* fix: improve sublist tests

* fix: improve sublists

* refactor: simplify

* docs: more examples, readme updates

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* docs: readme

* fix: set item style

* fix(tree): do not use golden files

* test: do not use golden files

* refactor: remove unused code

* feat: hide nodes

* feat: filter

* fix: allow fmt.Stringer

* test: table within a tree

* docs: update

* fix: rename atter to data

* test: test public api only

* docs: sublist with table example

* docs: update example

* fix: lint issues

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* docs: update examples

* docs: update

* feat: offset

* feat: offsetstart && offsetend

* fix: remove last from enumerator

* refactor: make tree more explicit

* `New()` now take no args
* added a new `Root(string)` method
* added a new `Items(...any)` method

* refactor: improve list

* docs: fix examples

* test: ensure embed lists in trees

* docs: sublist example with list as tree node

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* refactor: first pass at examples cleanup

* refactor: first pass at examples cleanup

* fix: support multi-line prefixes

* test: more glow-style lists within trees

* docs: simplify examples

* fix: improve handling of different types

* chore: go mod tidy

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: code review suggestions

Co-authored-by: Ayman Bagabas <ayman.bagabas@gmail.com>
Co-authored-by: bashbunni <15822994+bashbunni@users.noreply.github.com>

* fix: apply code review suggestion

* feat(tree): rounded enumerator (#280)

* chore(examples): go mod tidy

* feat(tree): add rounded corner enumerator

* test: rounded enumerator test

* docs: readme updates

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* docs: readme updates

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* chore: fmt

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* docs: godoc

* docs: godoc

* fix: bash's issue

* fix: bad example

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* docs: improve godoc for trees and lists (#296)

* docs(godoc): add overview

* docs(godoc): include examples in godoc

* docs(godoc): fix roman numerals example

* docs(godoc): fix tree examples

* docs(godoc): attempt to fix list Enumerator examples

* docs: added some examples to godoc

* fix(list): New(items...) with sublist

* docs: update examples

* chore(tree): improve var names in rounded example

* fix: use padding instead of margin to better styles

* fix: lint

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
Co-authored-by: Maas Lalani <maas@lalani.dev>
Co-authored-by: Ayman Bagabas <ayman.bagabas@gmail.com>
Co-authored-by: bashbunni <15822994+bashbunni@users.noreply.github.com>
Co-authored-by: Christian Rocha <christian@rocha.is>
2024-05-24 12:02:18 -03:00

134 lines
3.1 KiB
Go

package tree
import (
"strings"
"github.com/charmbracelet/lipgloss"
)
// StyleFunc allows the list to be styled per item.
type StyleFunc func(data Data, 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(Data, int) lipgloss.Style {
return lipgloss.NewStyle().PaddingRight(1)
},
itemFunc: func(Data, int) lipgloss.Style {
return lipgloss.NewStyle()
},
},
enumerator: DefaultEnumerator,
}
}
type renderer struct {
style Style
enumerator Enumerator
}
// 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
// print the root node name if its not empty.
if name := node.Name(); 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, 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.Name())
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.renderer != nil {
renderer = child.renderer
}
}
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
}