Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tree): support width and indenter styling #446

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions examples/tree/background/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

func main() {
enumeratorStyle := lipgloss.NewStyle().
darkBg := lipgloss.NewStyle().
Background(lipgloss.Color("0")).
Padding(0, 1)

Expand All @@ -23,7 +23,8 @@ func main() {
t := tree.Root("# Table of Contents").
RootStyle(itemStyle).
ItemStyle(itemStyle).
EnumeratorStyle(enumeratorStyle).
EnumeratorStyle(darkBg).
IndenterStyle(darkBg).
Child(
tree.Root("## Chapter 1").
Child("Chapter 1.1").
Expand Down
1 change: 1 addition & 0 deletions examples/tree/files/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ func main() {
}

t := tree.Root(pwd).
IndenterStyle(enumeratorStyle).
EnumeratorStyle(enumeratorStyle).
RootStyle(itemStyle).
ItemStyle(itemStyle)
Expand Down
1 change: 1 addition & 0 deletions examples/tree/makeup/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func main() {
).
Enumerator(tree.RoundedEnumerator).
EnumeratorStyle(enumeratorStyle).
IndenterStyle(enumeratorStyle).
RootStyle(rootStyle).
ItemStyle(itemStyle)

Expand Down
5 changes: 4 additions & 1 deletion examples/tree/rounded/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ func main() {
"Leek",
"Artichoke",
),
).ItemStyle(itemStyle).EnumeratorStyle(enumeratorStyle).Enumerator(tree.RoundedEnumerator)
).ItemStyle(itemStyle).
EnumeratorStyle(enumeratorStyle).
Enumerator(tree.RoundedEnumerator).
IndenterStyle(enumeratorStyle)

fmt.Println(t)
}
143 changes: 143 additions & 0 deletions examples/tree/selection/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package main

import (
"fmt"
"path"

"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss/tree"
)

const selected = "/Users/bash/.config/doom-emacs"

type styles struct {
base,
container,
dir,
selected,
dimmed,
toggle lipgloss.Style
}

func defaultStyles() styles {
var s styles
s.base = lipgloss.NewStyle().
Background(lipgloss.Color("235"))
s.container = s.base.
Margin(1, 2).
Padding(1, 0)
s.dir = s.base.
Inline(true)
s.toggle = s.base.
Foreground(lipgloss.Color("5")).
PaddingRight(1)
s.selected = s.base.
Background(lipgloss.Color("8")).
Foreground(lipgloss.Color("207")).
Bold(true)
s.dimmed = s.base.
Foreground(lipgloss.Color("241"))
return s
}

type dir struct {
name string
open bool
styles styles
}

func (d dir) String() string {
t := d.styles.toggle.PaddingLeft(1).Render
n := d.styles.dir.Render
if d.open {
return t("▼") + n(d.name)
}
return t("▶") + n(d.name)
}

// file implements the Node interface.
type file struct {
name string
styles styles
}

func (s file) String() string {
return path.Base(s.name)
}

func (s file) Hidden() bool {
return false
}

func (s file) Children() tree.Children {
return tree.NodeChildren(nil)
}

func (s file) Value() string {
return s.String()
}

func isItemSelected(children tree.Children, index int) bool {
child := children.At(index)
if file, ok := child.(file); ok && file.name == selected {
return true
}

return false
}

func itemStyle(children tree.Children, index int) lipgloss.Style {
s := defaultStyles()
if isItemSelected(children, index) {
return s.selected
}

return s.base
}

func indenterStyle(children tree.Children, index int) lipgloss.Style {
s := defaultStyles()
if isItemSelected(children, index) {
return s.dimmed.Background(s.selected.GetBackground())
}

return s.dimmed
}

func main() {
s := defaultStyles()

t := tree.Root(dir{"~/charm", true, s}).
Child(
dir{"ayman", false, s},
tree.Root(dir{"bash", true, s}).
Child(
file{"/Users/bash/.config/doom-emacs", s},
),
tree.Root(dir{"carlos", true, s}).
Child(
tree.Root(dir{"emotes", true, s}).
Child(
file{"/home/caarlos0/Pictures/chefkiss.png", s},
file{"/home/caarlos0/Pictures/kekw.png", s},
),
),
dir{"maas", false, s},
).
Width(30).
Indenter(Indenter).
Enumerator(Enumerator).
EnumeratorStyleFunc(indenterStyle).
IndenterStyleFunc(indenterStyle).
ItemStyleFunc(itemStyle)

fmt.Println(s.container.Render(t.String()))
}

func Enumerator(children tree.Children, index int) string {
return " │ "
}

func Indenter(children tree.Children, index int) string {
return " │ "
}
6 changes: 4 additions & 2 deletions examples/tree/styles/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ func main() {
"Claire’s Boutique",
tree.Root("Nyx").
Child("Lip Gloss", "Foundation").
EnumeratorStyle(pink),
EnumeratorStyle(pink).
IndenterStyle(purple),
"Mac",
"Milk",
).
EnumeratorStyle(purple)
EnumeratorStyle(purple).
IndenterStyle(purple)
fmt.Println(t)
}
7 changes: 4 additions & 3 deletions examples/tree/toggle/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
type styles struct {
base,
block,
enumerator,
pink,
dir,
toggle,
file lipgloss.Style
Expand All @@ -25,7 +25,7 @@ func defaultStyles() styles {
Padding(1, 3).
Margin(1, 3).
Width(40)
s.enumerator = s.base.
s.pink = s.base.
Foreground(lipgloss.Color("212")).
PaddingRight(1)
s.dir = s.base.
Expand Down Expand Up @@ -66,7 +66,8 @@ func main() {

t := tree.Root(dir{"~/charm", true, s}).
Enumerator(tree.RoundedEnumerator).
EnumeratorStyle(s.enumerator).
IndenterStyle(s.pink).
EnumeratorStyle(s.pink).
Child(
dir{"ayman", false, s},
tree.Root(dir{"bash", true, s}).
Expand Down
47 changes: 34 additions & 13 deletions tree/renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type StyleFunc func(children Children, i int) lipgloss.Style
// Style is the styling applied to the tree.
type Style struct {
enumeratorFunc StyleFunc
indenterFunc StyleFunc
itemFunc StyleFunc
root lipgloss.Style
}
Expand All @@ -23,6 +24,9 @@ func newRenderer() *renderer {
enumeratorFunc: func(Children, int) lipgloss.Style {
return lipgloss.NewStyle().PaddingRight(1)
},
indenterFunc: func(Children, int) lipgloss.Style {
return lipgloss.NewStyle()
},
itemFunc: func(Children, int) lipgloss.Style {
return lipgloss.NewStyle()
},
Expand All @@ -36,6 +40,7 @@ type renderer struct {
style Style
enumerator Enumerator
indenter Indenter
width int
}

// render is responsible for actually rendering the tree.
Expand All @@ -51,6 +56,10 @@ func (r *renderer) render(node Node, root bool, prefix string) string {

// print the root node name if its not empty.
if name := node.Value(); name != "" && root {
// fmt.Println("name: ", name)
if lipgloss.Width(name) < r.width {
name = name + strings.Repeat(" ", r.width-lipgloss.Width(name))
caarlos0 marked this conversation as resolved.
Show resolved Hide resolved
}
strs = append(strs, r.style.root.Render(name))
}

Expand All @@ -65,18 +74,22 @@ func (r *renderer) render(node Node, root bool, prefix string) string {
if child.Hidden() {
continue
}
indent := indenter(children, i)
nodePrefix := enumerator(children, i)
indentStyle := r.style.indenterFunc(children, i)
enumStyle := r.style.enumeratorFunc(children, i)
itemStyle := r.style.itemFunc(children, i)

nodePrefix = enumStyle.Render(nodePrefix)
indent := indenter(children, i)
nodeIndent := indentStyle.Render(indent)
nodePrefix := enumStyle.Render(enumerator(children, i))
if l := maxLen - lipgloss.Width(nodePrefix); l > 0 {
nodePrefix = strings.Repeat(" ", l) + nodePrefix
nodePrefix = enumStyle.Render(strings.Repeat(" ", l)) + nodePrefix
}

item := itemStyle.Render(child.Value())
multineLinePrefix := prefix
if multineLinePrefix != "" {
multineLinePrefix = indentStyle.Render(multineLinePrefix)
}

// 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
Expand All @@ -85,32 +98,40 @@ func (r *renderer) render(node Node, root bool, prefix string) string {
nodePrefix = lipgloss.JoinVertical(
lipgloss.Left,
nodePrefix,
enumStyle.Render(indent),
nodeIndent,
)
}
for lipgloss.Height(nodePrefix) > lipgloss.Height(multineLinePrefix) {
multineLinePrefix = lipgloss.JoinVertical(
lipgloss.Left,
multineLinePrefix,
prefix,
indentStyle.Render(prefix),
)
}

strs = append(
strs,
line :=
lipgloss.JoinHorizontal(
lipgloss.Top,
multineLinePrefix,
nodePrefix,
item,
),
)

// If the line is shorter than the desired width, we pad it with spaces.
if lipgloss.Width(line) < r.width {
line = line + itemStyle.Render(strings.Repeat(" ", r.width-lipgloss.Width(line)))
dlvhdr marked this conversation as resolved.
Show resolved Hide resolved
}
strs = append(
strs,
line,
)

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.
// Here we see if the child has a custom renderer, which means the
// user set a custom enumerator/indenter/item style, etc.
// If it has one, we'll use it to render itself.
// otherwise, we keep using the current renderer.
// Note that the renderer doesn't inherit its parent's styles.
renderer := r
switch child := child.(type) {
case *Tree:
Expand All @@ -121,7 +142,7 @@ func (r *renderer) render(node Node, root bool, prefix string) string {
if s := renderer.render(
child,
false,
prefix+enumStyle.Render(indent),
prefix+indent,
); s != "" {
strs = append(strs, s)
}
Expand Down
Loading
Loading