-
Notifications
You must be signed in to change notification settings - Fork 819
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
672 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"), | ||
), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
Oops, something went wrong.