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