1# Bubble Tea
2
3<p>
4 <picture>
5 <source media="(prefers-color-scheme: light)" srcset="https://stuff.charm.sh/bubbletea/bubble-tea-v2-light.png" width="308">
6 <source media="(prefers-color-scheme: dark)" srcset="https://stuff.charm.sh/bubbletea/bubble-tea-v2-dark.png" width="312">
7 <img src="https://stuff.charm.sh/bubbletea/bubble-tea-v2-light.png" width="308" />
8 </picture>
9 <br>
10 <a href="https://github.com/charmbracelet/bubbletea/releases"><img src="https://img.shields.io/github/release/charmbracelet/bubbletea.svg" alt="Latest Release"></a>
11 <a href="https://pkg.go.dev/github.com/charmbracelet/bubbletea?tab=doc"><img src="https://godoc.org/github.com/charmbracelet/bubbletea?status.svg" alt="GoDoc"></a>
12 <a href="https://github.com/charmbracelet/bubbletea/actions"><img src="https://github.com/charmbracelet/bubbletea/actions/workflows/build.yml/badge.svg" alt="Build Status"></a>
13</p>
14
15The fun, functional and stateful way to build terminal apps. A Go framework
16based on [The Elm Architecture][elm]. Bubble Tea is well-suited for simple and
17complex terminal applications, either inline, full-window, or a mix of both.
18
19<p>
20 <img src="https://stuff.charm.sh/bubbletea/bubbletea-example.gif" width="100%" alt="Bubble Tea Example">
21</p>
22
23Bubble Tea is in use in production and includes a number of features and
24performance optimizations we’ve added along the way. Among those is
25a framerate-based renderer, mouse support, focus reporting and more.
26
27To get started, see the tutorial below, the [examples][examples], the
28[docs][docs], the [video tutorials][youtube] and some common [resources](#libraries-we-use-with-bubble-tea).
29
30[youtube]: https://charm.sh/yt
31
32## By the way
33
34Be sure to check out [Bubbles][bubbles], a library of common UI components for Bubble Tea.
35
36<p>
37 <a href="https://github.com/charmbracelet/bubbles"><img src="https://stuff.charm.sh/bubbles/bubbles-badge.png" width="174" alt="Bubbles Badge"></a>
38 <a href="https://github.com/charmbracelet/bubbles"><img src="https://stuff.charm.sh/bubbles-examples/textinput.gif" width="400" alt="Text Input Example from Bubbles"></a>
39</p>
40
41---
42
43## Tutorial
44
45Bubble Tea is based on the functional design paradigms of [The Elm
46Architecture][elm], which happens to work nicely with Go. It's a delightful way
47to build applications.
48
49This tutorial assumes you have a working knowledge of Go.
50
51By the way, the non-annotated source code for this program is available
52[on GitHub][tut-source].
53
54[elm]: https://guide.elm-lang.org/architecture/
55[tut-source]: https://github.com/charmbracelet/bubbletea/tree/main/tutorials/basics
56
57### Enough! Let's get to it.
58
59For this tutorial, we're making a shopping list.
60
61To start we'll define our package and import some libraries. Our only external
62import will be the Bubble Tea library, which we'll call `tea` for short.
63
64```go
65package main
66
67// These imports will be used later on the tutorial. If you save the file
68// now, Go might complain they are unused, but that's fine.
69// You may also need to run `go mod tidy` to download bubbletea and its
70// dependencies.
71import (
72 "fmt"
73 "os"
74
75 tea "github.com/charmbracelet/bubbletea/v2"
76)
77```
78
79Bubble Tea programs are comprised of a **model** that describes the application
80state and three simple methods on that model:
81
82- **Init**, a function that returns an initial command for the application to run.
83- **Update**, a function that handles incoming events and updates the model accordingly.
84- **View**, a function that renders the UI based on the data in the model.
85
86### The Model
87
88So let's start by defining our model which will store our application's state.
89It can be any type, but a `struct` usually makes the most sense.
90
91```go
92type model struct {
93 choices []string // items on the to-do list
94 cursor int // which to-do list item our cursor is pointing at
95 selected map[int]struct{} // which to-do items are selected
96}
97```
98
99## Initialization
100
101Next, we’ll define our application’s initial state. In this case, we’re defining
102a function to return our initial model, however, we could just as easily define
103the initial model as a variable elsewhere, too.
104
105```go
106func initialModel() model {
107 return model{
108 // Our to-do list is a grocery list
109 choices: []string{"Buy carrots", "Buy celery", "Buy kohlrabi"},
110
111 // A map which indicates which choices are selected. We're using
112 // the map like a mathematical set. The keys refer to the indexes
113 // of the `choices` slice, above.
114 selected: make(map[int]struct{}),
115 }
116}
117```
118
119After that, we’ll define our application’s initial state in the `Init` method. `Init`
120can return a `Cmd` that could perform some initial I/O. For now, we don't need
121to do any I/O, so for the command, we'll just return `nil`, which translates to
122"no command."
123
124```go
125func (m model) Init() (tea.Model, tea.Cmd) {
126 m = {
127 // Our to-do list is a grocery list
128 choices: []string{"Buy carrots", "Buy celery", "Buy kohlrabi"},
129
130 // A map which indicates which choices are selected. We're using
131 // the map like a mathematical set. The keys refer to the indexes
132 // of the `choices` slice, above.
133 selected: make(map[int]struct{}),
134 }
135
136 // Just return `nil`, which means "no I/O right now, please."
137 return m, nil
138}
139```
140
141### The Update Method
142
143Next up is the update method. The update function is called when ”things
144happen.” Its job is to look at what has happened and return an updated model in
145response. It can also return a `Cmd` to make more things happen, but for now
146don't worry about that part.
147
148In our case, when a user presses the down arrow, `Update`’s job is to notice
149that the down arrow was pressed and move the cursor accordingly (or not).
150
151The “something happened” comes in the form of a `Msg`, which can be any type.
152Messages are the result of some I/O that took place, such as a keypress, timer
153tick, or a response from a server.
154
155We usually figure out which type of `Msg` we received with a type switch, but
156you could also use a type assertion.
157
158For now, we'll just deal with `tea.KeyPressMsg` messages, which are
159automatically sent to the update function when keys are pressed.
160
161```go
162func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
163 switch msg := msg.(type) {
164
165 // Is it a key press?
166 case tea.KeyPressMsg:
167
168 // Cool, what was the actual key pressed?
169 switch msg.String() {
170
171 // These keys should exit the program.
172 case "ctrl+c", "q":
173 return m, tea.Quit
174
175 // The "up" and "k" keys move the cursor up
176 case "up", "k":
177 if m.cursor > 0 {
178 m.cursor--
179 }
180
181 // The "down" and "j" keys move the cursor down
182 case "down", "j":
183 if m.cursor < len(m.choices)-1 {
184 m.cursor++
185 }
186
187 // The "enter" key and the space bar (a literal space) toggle the
188 // selected state for the item that the cursor is pointing at.
189 case "enter", "space":
190 _, ok := m.selected[m.cursor]
191 if ok {
192 delete(m.selected, m.cursor)
193 } else {
194 m.selected[m.cursor] = struct{}{}
195 }
196 }
197 }
198
199 // Return the updated model to the Bubble Tea runtime for processing.
200 // Note that we're not returning a command.
201 return m, nil
202}
203```
204
205You may have noticed that <kbd>ctrl+c</kbd> and <kbd>q</kbd> above return
206a `tea.Quit` command with the model. That’s a special command which instructs
207the Bubble Tea runtime to quit, exiting the program.
208
209### The View Method
210
211At last, it’s time to render our UI. Of all the methods, the view is the
212simplest. We look at the model in its current state and use it to return
213a `string`. That string is our UI!
214
215Because the view describes the entire UI of your application, you don’t have to
216worry about redrawing logic and stuff like that. Bubble Tea takes care of it
217for you.
218
219```go
220func (m model) View() string {
221 // The header
222 s := "What should we buy at the market?\n\n"
223
224 // Iterate over our choices
225 for i, choice := range m.choices {
226
227 // Is the cursor pointing at this choice?
228 cursor := " " // no cursor
229 if m.cursor == i {
230 cursor = ">" // cursor!
231 }
232
233 // Is this choice selected?
234 checked := " " // not selected
235 if _, ok := m.selected[i]; ok {
236 checked = "x" // selected!
237 }
238
239 // Render the row
240 s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, choice)
241 }
242
243 // The footer
244 s += "\nPress q to quit.\n"
245
246 // Send the UI for rendering
247 return s
248}
249```
250
251### All Together Now
252
253The last step is to simply run our program. We pass our initial model to
254`tea.NewProgram` and let it rip:
255
256```go
257func main() {
258 p := tea.NewProgram(initialModel())
259 if _, err := p.Run(); err != nil {
260 fmt.Printf("Alas, there's been an error: %v", err)
261 os.Exit(1)
262 }
263}
264```
265
266## What’s Next?
267
268This tutorial covers the basics of building an interactive terminal UI, but
269in the real world you'll also need to perform I/O. To learn about that have a
270look at the [Command Tutorial][cmd]. It's pretty simple.
271
272There are also several [Bubble Tea examples][examples] available and, of course,
273there are [Go Docs][docs].
274
275[cmd]: https://github.com/charmbracelet/bubbletea/tree/main/tutorials/commands/
276[examples]: https://github.com/charmbracelet/bubbletea/tree/main/examples
277[docs]: https://pkg.go.dev/github.com/charmbracelet/bubbletea?tab=doc
278
279## Debugging
280
281### Debugging with Delve
282
283Since Bubble Tea apps assume control of stdin and stdout, you’ll need to run
284delve in headless mode and then connect to it:
285
286```bash
287# Start the debugger
288$ dlv debug --headless --api-version=2 --listen=127.0.0.1:43000 .
289API server listening at: 127.0.0.1:43000
290
291# Connect to it from another terminal
292$ dlv connect 127.0.0.1:43000
293```
294
295If you do not explicitly supply the `--listen` flag, the port used will vary
296per run, so passing this in makes the debugger easier to use from a script
297or your IDE of choice.
298
299Additionally, we pass in `--api-version=2` because delve defaults to version 1
300for backwards compatibility reasons. However, delve recommends using version 2
301for all new development and some clients may no longer work with version 1.
302For more information, see the [Delve documentation](https://github.com/go-delve/delve/tree/master/Documentation/api).
303
304### Logging Stuff
305
306You can’t really log to stdout with Bubble Tea because your TUI is busy
307occupying that! You can, however, log to a file by including something like
308the following prior to starting your Bubble Tea program:
309
310```go
311if len(os.Getenv("DEBUG")) > 0 {
312 f, err := tea.LogToFile("debug.log", "debug")
313 if err != nil {
314 fmt.Println("fatal:", err)
315 os.Exit(1)
316 }
317 defer f.Close()
318}
319```
320
321To see what’s being logged in real time, run `tail -f debug.log` while you run
322your program in another window.
323
324## Libraries we use with Bubble Tea
325
326- [Bubbles][bubbles]: Common Bubble Tea components such as text inputs, viewports, spinners and so on
327- [Lip Gloss][lipgloss]: Style, format and layout tools for terminal applications
328- [Harmonica][harmonica]: A spring animation library for smooth, natural motion
329- [BubbleZone][bubblezone]: Easy mouse event tracking for Bubble Tea components
330- [ntcharts][ntcharts]: A terminal charting library built for Bubble Tea and [Lip Gloss][lipgloss]
331
332[bubbles]: https://github.com/charmbracelet/bubbles
333[lipgloss]: https://github.com/charmbracelet/lipgloss
334[harmonica]: https://github.com/charmbracelet/harmonica
335[bubblezone]: https://github.com/lrstanley/bubblezone
336[ntcharts]: https://github.com/NimbleMarkets/ntcharts
337
338## Bubble Tea in the Wild
339
340There are over [10,000 applications](https://github.com/charmbracelet/bubbletea/network/dependents) built with Bubble Tea! Here are a handful of ’em.
341
342### Staff favourites
343
344- [chezmoi](https://github.com/twpayne/chezmoi): securely manage your dotfiles across multiple machines
345- [circumflex](https://github.com/bensadeh/circumflex): read Hacker News in the terminal
346- [gh-dash](https://www.github.com/dlvhdr/gh-dash): a GitHub CLI extension for PRs and issues
347- [Tetrigo](https://github.com/Broderick-Westrope/tetrigo): Tetris in the terminal
348- [Signls](https://github.com/emprcl/signls): a generative midi sequencer designed for composition and live performance
349- [Superfile](https://github.com/yorukot/superfile): a super file manager
350
351### In Industry
352
353- Microsoft Azure – [Aztify](https://github.com/Azure/aztfy): bring Microsoft Azure resources under Terraform
354- Daytona – [Daytona](https://github.com/daytonaio/daytona): open source dev environment manager
355- Cockroach Labs – [CockroachDB](https://github.com/cockroachdb/cockroach): a cloud-native, high-availability distributed SQL database
356- Truffle Security Co. – [Trufflehog](https://github.com/trufflesecurity/trufflehog): find leaked credentials
357- NVIDIA – [container-canary](https://github.com/NVIDIA/container-canary): a container validator
358- AWS – [eks-node-viewer](https://github.com/awslabs/eks-node-viewer): a tool for visualizing dynamic node usage within an EKS cluster
359- MinIO – [mc](https://github.com/minio/mc): the official [MinIO](https://min.io) client
360- Ubuntu – [Authd](https://github.com/ubuntu/authd): an authentication daemon for cloud-based identity providers
361
362### Charm stuff
363
364- [Glow](https://github.com/charmbracelet/glow): a markdown reader, browser, and online markdown stash
365- [Huh?](https://github.com/charmbracelet/huh): an interactive prompt and form toolkit
366- [Mods](https://github.com/charmbracelet/mods): AI on the CLI, built for pipelines
367- [Wishlist](https://github.com/charmbracelet/wishlist): an SSH directory (and bastion!)
368
369### There’s so much more where that came from
370
371For more applications built with Bubble Tea see [Charm & Friends][community].
372Is there something cool you made with Bubble Tea you want to share? [PRs][community] are
373welcome!
374
375## Contributing
376
377See [contributing][contribute].
378
379[contribute]: https://github.com/charmbracelet/bubbletea/contribute
380
381## Feedback
382
383We’d love to hear your thoughts on this project. Feel free to drop us a note!
384
385- [Twitter](https://twitter.com/charmcli)
386- [The Fediverse](https://mastodon.social/@charmcli)
387- [Discord](https://charm.sh/chat)
388
389## Acknowledgments
390
391Bubble Tea is based on the paradigms of [The Elm Architecture][elm] by Evan
392Czaplicki et alia and the excellent [go-tea][gotea] by TJ Holowaychuk. It’s
393inspired by the many great [_Zeichenorientierte Benutzerschnittstellen_][zb]
394of days past.
395
396[elm]: https://guide.elm-lang.org/architecture/
397[gotea]: https://github.com/tj/go-tea
398[zb]: https://de.wikipedia.org/wiki/Zeichenorientierte_Benutzerschnittstelle
399[community]: https://github.com/charm-and-friends/charm-in-the-wild
400
401## License
402
403[MIT](https://github.com/charmbracelet/bubbletea/raw/main/LICENSE)
404
405---
406
407Part of [Charm](https://charm.sh).
408
409<a href="https://charm.sh/"><img alt="The Charm logo" src="https://stuff.charm.sh/charm-badge.jpg" width="400"></a>
410
411Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة