Skip to content

Commit

Permalink
Add list examples
Browse files Browse the repository at this point in the history
  • Loading branch information
muesli committed Aug 23, 2021
1 parent 1b8495e commit 57fd990
Show file tree
Hide file tree
Showing 7 changed files with 672 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
examples/fullscreen/fullscreen
examples/help/help
examples/http/http
examples/list-default/list-default
examples/list-fancy/list-fancy
examples/list-simple/list-simple
examples/mouse/mouse
examples/pager/pager
examples/simple/simple
Expand Down
2 changes: 2 additions & 0 deletions examples/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55k
github.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776 h1:VRIbnDWRmAh5yBdz+J6yFMF5vso1It6vn+WmM/5l7MA=
github.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776/go.mod h1:9wvnDu3YOfxzWM9Cst40msBF1C2UdQgDv962oTxSuMs=
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
Expand Down Expand Up @@ -63,6 +64,7 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
Expand Down
87 changes: 87 additions & 0 deletions examples/list-default/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package main

import (
"fmt"
"os"

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

var docStyle = lipgloss.NewStyle().Margin(1, 2)

type item struct {
title, desc string
}

func (i item) Title() string { return i.title }
func (i item) Description() string { return i.desc }
func (i item) FilterValue() string { return i.title }

type model struct {
list list.Model
}

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

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
if msg.String() == "ctrl+c" {
return m, nil
}
case tea.WindowSizeMsg:
top, right, bottom, left := docStyle.GetMargin()
m.list.SetSize(msg.Width-left-right, msg.Height-top-bottom)
}

var cmd tea.Cmd
m.list, cmd = m.list.Update(msg)
return m, cmd
}

func (m model) View() string {
return docStyle.Render(m.list.View())
}

func main() {
items := []list.Item{
item{title: "Raspberry Pi’s", desc: "I have ’em all over my house"},
item{title: "Nutella", desc: "It's good on toast"},
item{title: "Bitter melon", desc: "It cools you down"},
item{title: "Nice socks", desc: "And by that I mean socks without holes"},
item{title: "Eight hours of sleep", desc: "I had this once"},
item{title: "Cats", desc: "Usually"},
item{title: "Plantasia, the album", desc: "My plants love it too"},
item{title: "Pour over coffee", desc: "It takes forever to make though"},
item{title: "VR", desc: "Virtual reality...what is there to say?"},
item{title: "Noguchi Lamps", desc: "Such pleasing organic forms"},
item{title: "Linux", desc: "Pretty much the best OS"},
item{title: "Business school", desc: "Just kidding"},
item{title: "Pottery", desc: "Wet clay is a great feeling"},
item{title: "Shampoo", desc: "Nothing like clean hair"},
item{title: "Table tennis", desc: "It’s surprisingly exhausting"},
item{title: "Milk crates", desc: "Great for packing in your extra stuff"},
item{title: "Afternoon tea", desc: "Especially the tea sandwich part"},
item{title: "Stickers", desc: "The thicker the vinyl the better"},
item{title: "20° Weather", desc: "Celsius, not Fahrenheit"},
item{title: "Warm light", desc: "Like around 2700 Kelvin"},
item{title: "The vernal equinox", desc: "The autumnal equinox is pretty good too"},
item{title: "Gaffer’s tape", desc: "Basically sticky fabric"},
item{title: "Terrycloth", desc: "In other words, towel fabric"},
}

m := model{list: list.NewModel(items, list.NewDefaultDelegate(), 0, 0)}
m.list.Title = "My Fave Things"

p := tea.NewProgram(m)
p.EnterAltScreen()

if err := p.Start(); err != nil {
fmt.Println("Error running program:", err)
os.Exit(1)
}
}
89 changes: 89 additions & 0 deletions examples/list-fancy/delegate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package main

import (
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
)

func newItemDelegate(keys *delegateKeyMap) list.DefaultDelegate {
d := list.NewDefaultDelegate()

d.UpdateFunc = func(msg tea.Msg, m *list.Model) tea.Cmd {
var title string

if i, ok := m.SelectedItem().(item); ok {
title = i.Title()
} else {
return nil
}

switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, keys.choose):
return m.NewStatusMessage(statusMessageStyle("You chose " + title))

case key.Matches(msg, keys.remove):
index := m.Index()
m.RemoveItem(index)
if len(m.Items()) == 0 {
keys.remove.SetEnabled(false)
}
return m.NewStatusMessage(statusMessageStyle("Deleted " + title))
}
}

return nil
}

help := []key.Binding{keys.choose, keys.remove}

d.ShortHelpFunc = func() []key.Binding {
return help
}

d.FullHelpFunc = func() [][]key.Binding {
return [][]key.Binding{help}
}

return d
}

type delegateKeyMap struct {
choose key.Binding
remove key.Binding
}

// Additional short help entries. This satisfies the help.KeyMap interface and
// is entirely optional.
func (d delegateKeyMap) ShortHelp() []key.Binding {
return []key.Binding{
d.choose,
d.remove,
}
}

// Additional full help entries. This satisfies the help.KeyMap interface and
// is entirely optional.
func (d delegateKeyMap) FullHelp() [][]key.Binding {
return [][]key.Binding{
{
d.choose,
d.remove,
},
}
}

func newDelegateKeyMap() *delegateKeyMap {
return &delegateKeyMap{
choose: key.NewBinding(
key.WithKeys("enter"),
key.WithHelp("enter", "choose"),
),
remove: key.NewBinding(
key.WithKeys("x", "backspace"),
key.WithHelp("x", "delete"),
),
}
}
190 changes: 190 additions & 0 deletions examples/list-fancy/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package main

import (
"fmt"
"math/rand"
"os"
"time"

"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)

var (
appStyle = lipgloss.NewStyle().Padding(1, 2)

titleStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#FFFDF5")).
Background(lipgloss.Color("#25A065")).
Padding(0, 1)

statusMessageStyle = lipgloss.NewStyle().
Foreground(lipgloss.AdaptiveColor{Light: "#04B575", Dark: "#04B575"}).
Render
)

type item struct {
title string
description string
}

func (i item) Title() string { return i.title }
func (i item) Description() string { return i.description }
func (i item) FilterValue() string { return i.title }

type listKeyMap struct {
toggleSpinner key.Binding
toggleTitleBar key.Binding
toggleStatusBar key.Binding
togglePagination key.Binding
toggleHelpMenu key.Binding
insertItem key.Binding
}

func newListKeyMap() *listKeyMap {
return &listKeyMap{
insertItem: key.NewBinding(
key.WithKeys("a"),
key.WithHelp("a", "add item"),
),
toggleSpinner: key.NewBinding(
key.WithKeys("s"),
key.WithHelp("s", "toggle spinner"),
),
toggleTitleBar: key.NewBinding(
key.WithKeys("T"),
key.WithHelp("T", "toggle title"),
),
toggleStatusBar: key.NewBinding(
key.WithKeys("S"),
key.WithHelp("S", "toggle status"),
),
togglePagination: key.NewBinding(
key.WithKeys("P"),
key.WithHelp("P", "toggle pagination"),
),
toggleHelpMenu: key.NewBinding(
key.WithKeys("H"),
key.WithHelp("H", "toggle help"),
),
}
}

type model struct {
list list.Model
itemGenerator *randomItemGenerator
keys *listKeyMap
delegateKeys *delegateKeyMap
}

func newModel() model {
var (
itemGenerator randomItemGenerator
delegateKeys = newDelegateKeyMap()
listKeys = newListKeyMap()
)

// Make initial list of items
const numItems = 24
items := make([]list.Item, numItems)
for i := 0; i < numItems; i++ {
items[i] = itemGenerator.next()
}

// Setup list
delegate := newItemDelegate(delegateKeys)
groceryList := list.NewModel(items, delegate, 0, 0)
groceryList.Title = "Groceries"
groceryList.Styles.Title = titleStyle
groceryList.AdditionalFullHelpKeys = func() []key.Binding {
return []key.Binding{
listKeys.toggleSpinner,
listKeys.insertItem,
listKeys.toggleTitleBar,
listKeys.toggleStatusBar,
listKeys.togglePagination,
listKeys.toggleHelpMenu,
}
}

return model{
list: groceryList,
keys: listKeys,
delegateKeys: delegateKeys,
itemGenerator: &itemGenerator,
}
}

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

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd

switch msg := msg.(type) {
case tea.WindowSizeMsg:
topGap, rightGap, bottomGap, leftGap := appStyle.GetPadding()
m.list.SetSize(msg.Width-leftGap-rightGap, msg.Height-topGap-bottomGap)

case tea.KeyMsg:
// Don't match any of the keys below if we're actively filtering.
if m.list.FilterState() == list.Filtering {
break
}

switch {
case key.Matches(msg, m.keys.toggleSpinner):
cmd := m.list.ToggleSpinner()
return m, cmd

case key.Matches(msg, m.keys.toggleTitleBar):
v := !m.list.ShowTitle()
m.list.SetShowTitle(v)
m.list.SetShowFilter(v)
m.list.SetFilteringEnabled(v)
return m, nil

case key.Matches(msg, m.keys.toggleStatusBar):
m.list.SetShowStatusBar(!m.list.ShowStatusBar())
return m, nil

case key.Matches(msg, m.keys.togglePagination):
m.list.SetShowPagination(!m.list.ShowPagination())
return m, nil

case key.Matches(msg, m.keys.toggleHelpMenu):
m.list.SetShowHelp(!m.list.ShowHelp())
return m, nil

case key.Matches(msg, m.keys.insertItem):
m.delegateKeys.remove.SetEnabled(true)
newItem := m.itemGenerator.next()
insCmd := m.list.InsertItem(0, newItem)
statusCmd := m.list.NewStatusMessage(statusMessageStyle("Added " + newItem.Title()))
return m, tea.Batch(insCmd, statusCmd)
}
}

// This will also call our delegate's update function.
newListModel, cmd := m.list.Update(msg)
m.list = newListModel
cmds = append(cmds, cmd)

return m, tea.Batch(cmds...)
}

func (m model) View() string {
return appStyle.Render(m.list.View())
}

func main() {
rand.Seed(time.Now().UTC().UnixNano())

if err := tea.NewProgram(newModel()).Start(); err != nil {
fmt.Println("Error running program:", err)
os.Exit(1)
}
}
Loading

0 comments on commit 57fd990

Please sign in to comment.