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(examples): tree #1190

Open
wants to merge 17 commits into
base: v2-exp
Choose a base branch
from
3 changes: 0 additions & 3 deletions examples/go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
module examples

go 1.23.1

require (
Expand All @@ -15,7 +14,6 @@ require (
github.com/lucasb-eyer/go-colorful v1.2.0
github.com/mattn/go-isatty v0.0.20
)

require (
github.com/alecthomas/chroma/v2 v2.14.0 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
Expand Down Expand Up @@ -48,5 +46,4 @@ require (
golang.org/x/term v0.22.0 // indirect
golang.org/x/text v0.20.0 // indirect
)

replace github.com/charmbracelet/bubbletea/v2 => ../
25 changes: 7 additions & 18 deletions examples/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,21 @@ github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWp
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/charmbracelet/bubbles/v2 v2.0.0-alpha.2.0.20241121172047-bd415b4ebae8 h1:CRhvWh0cIainbY47znHAxzohyXDNmcmrp9ggjvn1cJk=
github.com/charmbracelet/bubbles/v2 v2.0.0-alpha.2.0.20241121172047-bd415b4ebae8/go.mod h1:fKcC1zxdgRjgg21XbRIf/bkSELpd9D9XKlHRCrhR1Tk=
github.com/charmbracelet/colorprofile v0.1.8 h1:PywDeXsiAzlPtkiiKgMEVLvb6nlEuKrMj9+FJBtj4jU=
github.com/charmbracelet/colorprofile v0.1.8/go.mod h1:+jpmObxZl1Dab3H3IMVIPSZTsKcFpjJUv97G0dLqM60=
github.com/charmbracelet/glamour v0.7.0/go.mod h1:jUMh5MeihljJPQbJ/wf4ldw2+yBP59+ctV36jASy7ps=
github.com/charmbracelet/glamour v0.8.0 h1:tPrjL3aRcQbn++7t18wOpgLyl8wrOHUEDS7IZ68QtZs=
github.com/charmbracelet/glamour v0.8.0/go.mod h1:ViRgmKkf3u5S7uakt2czJ272WSg2ZenlYEZXT2x7Bjw=
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804 h1:7CYjb9YMZA4kMhLgGdtlXvq+nu1oyENpMyMQlTvqSFw=
github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804/go.mod h1:F/6E/LGdH3eHCJf2rG8/O3CjlW8cZFL5YJCknJs1GkI=
github.com/charmbracelet/x/ansi v0.5.1 h1:+mg6abP9skvsu/JQZrIJ9Z/4O1YDnLVkpfutar3dUnc=
github.com/charmbracelet/x/ansi v0.5.1/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q=
github.com/charmbracelet/x/cellbuf v0.0.6 h1:pJUWN/G1jbt1Nj/+ILfC2/ABQoZzWu1vG73yHQEYELI=
github.com/charmbracelet/x/cellbuf v0.0.6/go.mod h1:d72o71glp8flkCz54PHLe3+nuw5u2v3UxmKqruUERWQ=
github.com/charmbracelet/x/ansi v0.3.2 h1:wsEwgAN+C9U06l9dCVMX0/L3x7ptvY1qmjMwyfE6USY=
github.com/charmbracelet/x/ansi v0.3.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/exp/teatest/v2 v2.0.0-20241016014612-3b4d04043233 h1:2bTR/MtnJuq9RrCZSPwCOO34YSDByKL6nzXQMnsKK6U=
github.com/charmbracelet/x/exp/teatest/v2 v2.0.0-20241016014612-3b4d04043233/go.mod h1:cw9df32BXdkcd0LzAHsFMmvXOsrrlDKazIW8PCq0cPM=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/charmbracelet/x/vt v0.0.0-20241121165045-a3720547cbb4 h1:EacjHxcQEEgOZ7TbkAU3b84hd1Bn5NwA8YV5uyJ9EI4=
github.com/charmbracelet/x/vt v0.0.0-20241121165045-a3720547cbb4/go.mod h1:1/jFoHl7/I4br0StC9OXXEondkK9qi3nUtKoqI35HcI=
github.com/charmbracelet/x/wcwidth v0.0.0-20241113152101-0af7d04e9f32 h1:14czE6R5CgOlvONsJYa2B1uTyLvXzGXpBqw2AyZeTh4=
github.com/charmbracelet/x/wcwidth v0.0.0-20241113152101-0af7d04e9f32/go.mod h1:hyua5CY63kyl7IfyIxv1SjVEqoKze/XmDkEglItuVjA=
github.com/charmbracelet/x/exp/teatest v0.0.0-20240521184646-23081fb03b28 h1:sOWKNRjt8uOEVgPiJVIJCse1+mUDM2F/vYY6W0Go640=
github.com/charmbracelet/x/exp/teatest v0.0.0-20240521184646-23081fb03b28/go.mod h1:l1w+LTJZCCozeGzMEWGxRw6Mo2DfcZUvupz8HGubdes=
github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
github.com/charmbracelet/x/windows v0.2.0 h1:ilXA1GJjTNkgOm94CLPeSz7rar54jtFatdmoiONPuEw=
github.com/charmbracelet/x/windows v0.2.0/go.mod h1:ZibNFR49ZFqCXgP76sYanisxRyC+EYrBE7TTknD8s1s=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
Expand Down
62 changes: 62 additions & 0 deletions examples/tree-default/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package main

import (
"fmt"
"os"

"github.com/charmbracelet/bubbles/tree"
tea "github.com/charmbracelet/bubbletea"
)

type model struct {
tree tree.Model
}

func (m model) Init() tea.Cmd {
return nil
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.KeyPressMsg:
switch msg.String() {
case "q", "ctrl+c":
return m, tea.Quit
}
}
m.tree, cmd = m.tree.Update(msg)
return m, cmd
}

func (m model) View() string {
return m.tree.View()
}

func main() {
t := tree.New(tree.Root("~/charm").
Child(
"ayman",
tree.Root("bash").
Child(
tree.Root("tools").
Child("zsh",
"doom-emacs",
),
),
tree.Root("carlos").
Child(
tree.Root("emotes").
Child(
"chefkiss.png",
"kekw.png",
),
),
"maas",
), 70, 13)

if _, err := tea.NewProgram(model{tree: t}).Run(); err != nil {
fmt.Println("Oh no:", err)
os.Exit(1)
}
}
142 changes: 142 additions & 0 deletions examples/tree-file-system/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package main

import (
"fmt"
"os"

"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/tree"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
ltree "github.com/charmbracelet/lipgloss/tree"
"github.com/charmbracelet/x/ansi"
)

type model struct {
tree tree.Model
choice *tree.Node
}

func (m model) Init() tea.Cmd {
return nil
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.KeyPressMsg:
switch msg.String() {
case "e":
m.choice = m.tree.NodeAtCurrentOffset()
return m, tea.Quit
case "q", "ctrl+c":
return m, tea.Quit
}
}
m.tree, cmd = m.tree.Update(msg)
m.updateStyles()

return m, cmd
}

func (m *model) updateStyles() {
dimmed := lipgloss.Color("239")
base := lipgloss.NewStyle().Background(lipgloss.Color("234"))
m.tree.SetStyles(tree.Styles{
TreeStyle: base.
Padding(1).
Border(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("236")).
BorderBackground(base.GetBackground()),
RootNodeStyle: base,
NodeStyle: base,
ParentNodeStyle: base,
OpenIndicatorStyle: base,
SelectedNodeStyle: base.Bold(true).Background(lipgloss.Color("8")),
HelpStyle: base.MarginTop(1),
EnumeratorStyle: base.Foreground(dimmed),
IndenterStyle: base.Foreground(dimmed),
})
}

func (m model) View() string {
return m.tree.View()
}

type file struct {
name string
color string
}

func (f file) String() string {
return "⌯ " + lipgloss.NewStyle().Foreground(lipgloss.Color(f.color)).Render(f.name)
}

type dir struct {
name string
}

func (d dir) String() string {
return lipgloss.NewStyle().Foreground(lipgloss.Color("4")).Render(d.name)
}

const (
width = 50
height = 21
enumeratorWidth = 3
)

func main() {
t := tree.New(
tree.Root(dir{"charmbracelet/lipgloss"}).
Indenter(func(_ ltree.Children, _ int) string {
return "│ "
}).
Enumerator(func(_ ltree.Children, _ int) string {
return "│ "
}).
Child(
tree.Root(dir{"tree"}).
Child(file{"tree.go", "6"}).
Child(file{"renderer.go", "6"}),
).
Child(
tree.Root(dir{"table"}).
Child(
tree.Root(dir{"utils"}).
Child(file{"utils.go", "6"}),
),
).
Child(tree.Root(dir{"list"}).Child(lipgloss.NewStyle().Faint(true).Render("(empty)"))).
Child(file{"README.md", "3"}).
Child(file{"go.mod", "255"}).
Child(file{"go.sum", "255"}).
Child(file{".gitignore", "255"}),
width,
height,
)
t.CursorCharacter = ""
t.OpenCharacter = "📂"
t.ClosedCharacter = "📁"
kb := []key.Binding{
key.NewBinding(key.WithKeys("e"), key.WithHelp("e", "select")),
}
t.AdditionalShortHelpKeys = func() []key.Binding {
return kb
}
t.AdditionalFullHelpKeys = func() []key.Binding {
return kb
}

p := tea.NewProgram(model{tree: t})
m, err := p.Run()
if err != nil {
fmt.Println("Oh no:", err)
os.Exit(1)
}

// Assert the final tea.Model to our local model and print the choice.
if m, ok := m.(model); ok && m.choice != nil {
fmt.Printf("---\nYou chose %s!\n", ansi.Strip(m.choice.Value()))
}
}
64 changes: 64 additions & 0 deletions examples/tree-long/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package main

import (
"fmt"
"os"
"time"

"github.com/charmbracelet/bubbles/tree"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)

type model struct {
tree tree.Model
}

func (m model) Init() tea.Cmd {
return nil
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.KeyPressMsg:
switch msg.String() {
case "q", "ctrl+c":
return m, tea.Quit
}
}
m.tree, cmd = m.tree.Update(msg)
return m, cmd
}

func (m model) View() string {
return m.tree.View()
}

func main() {
root := tree.Root("🛂 Passport expiration date")
thisYear := time.Now().Year()
for year := thisYear; year < thisYear+10; year++ {
yRoot := tree.Root(fmt.Sprintf("%d", year)).Close()
for month := 1; month <= 12; month++ {
// TODO: fix styles (RootStyle in this case)
mRoot := tree.Root(time.Month(month).String()).Close().RootStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("1")))
for day := 1; day < daysIn(time.Month(month), year); day++ {
mRoot.Child(fmt.Sprintf("%d", day))
}
yRoot.Child(mRoot)
}
root.Child(yRoot)
}

t := tree.New(root, 80, 30)

if _, err := tea.NewProgram(model{tree: t}).Run(); err != nil {
fmt.Println("Oh no:", err)
os.Exit(1)
}
}

func daysIn(m time.Month, year int) int {
return time.Date(year, m+1, 0, 0, 0, 0, 0, time.UTC).Day()
}
Loading
Loading