Terminal UI the Easy Way: BubbleTEA

For the past 18 months I've been tinkering with a terminal application that serves as an Algorand node frontend. I had a number of requirements in mind when choosing the technology: a full screen terminal application, works over ssh, extensible to allow it to evolve with different utilities and views of the system over time, be programmed with the Go programming language. 

In the past I've created these sorts of programs with bash, but it's not fun to manage terminal control characters and deal with re-drawing with bash. Using bash has always led my applications to be simplistic, typically never getting beyond simple spinners and progress bars. I knew about ncurses, but didn't want to lose the portability of bash -- some of these scripts were even POSIX compliant, and honestly I just didn't want to deal with low level interfaces and C bindings.

Enter bubbletea. The team at Charm has built the tools I didn't even know I needed. There is an impressive array of documentation, example programs, and supporting tools. The "getting started" story is quite nice, there are even YouTube videos if that's your thing. Overall, my experience with these tools has been transformative and I'm enabled to build advanced terminal applications. The sort that I knew were possible, but were previously too daunting to consider.

One of my favorite things about bubbletea is that it's only one part of a larger ecosystem of libraries and tools. You can use wish to serve the UI over ssh, or glamour to render markdown files in your app, and lipgloss is great for formatting text. Then there are other tools that are generally useful on the terminal, like vhs to record demos and wishlist to manage ssh targets. I have yet to get into charm, which seems to be their flagship project.

Gotchas

This isn't to say everything is perfect. These tools can be used to quickly build a simple program, but there are no training wheels. Once you stray off the established path things quickly get complicated. For example:

  • managing multiple views, and their sizes.
  • themes across multiple components.
  • customizing an existing component.
  • input wizards.
  • dialogs.

This can all be done, but the framework isn't designed to handle them. At the core of bubbletea is an event handler and not much more. The examples may lull you into a false sense of security here, because it kind of looks like an application framework. Don't be fooled.

Some examples.

If you have two views that should be displayed side by side and a third that should span the entire width of the terminal, you need to explicitly set all of their sizes. While this sounds pretty obvious, the event system really isn't conducive for it. Each view is self managed, so there's no easy way to clamp them to 50% of the width, that's something you need to build yourself. Not rocket science, but not provided by the event system.

Themes are entirely managed by the View functions. How they are passed throughout the system is up to you. This becomes especially complicated when using off-the-shelf components, where you need to merge your themes with (if you're lucky) the theming capabilities of the different libraries. In some examples a global "style" object is passed around. But what to put in that global object... and how to use it. That is for you to decide. If you don't make a decision, you're gonna have a bad time (or a funky looking app).

Until recently, a table wasn't available out of the box, which was a surprising omission. Thankfully there are several 3rd party alternatives. But every bubble library has a slightly different take on how it should be used. Sometimes this leads to some quirky issues. The list bubble is designed to be used as a single page application, and does things like handle the escape key to quit the program. In one of my programs I had a list as part of a wizard and wanted escape to go back. Fortunately there is a "DisableQuitKeybindings" option, but figuring that out involved a frustrating debugging session.

Takeaway

  1. These are not problems, they're a misunderstanding. I thought I was using a framework but that's not the case. Bubbletea is an event system with some very convenient terminal events built in. There are a number of thoughtfully designed supporting libraries that turn it into a TUI toolkit. It's a smart decision that fits well with the go ethos.

  2. The light touch used throughout also leaves the door open for some excellent community extensions like bubblezone and many others. You should use these libraries, and your own, when building applications of any size.
     
  3. The developer is responsible for knowing the exact size of each view. Take a look at how vertical and horizontal margins are passed in library code and figure out how you want to something similar in your program. My most complex program ended up having global variables that keep track of footer and header sizes to deal with the fact that I didn't have margins plumbed into every view. It's a glaring design flaw that leads to weird display issues when I modify the program. Margins should be carefully handed down to better fit with the Update function. There's room in the ecosystem for a layout-aware WindowSize event library.


# What's Next

I build that layout-aware WindowSize library and will talk about that in a future blog post.

For now take a look at the README and enjoy building terminal applications!

Comments

Popular posts from this blog

Decommissioning the cloud: A self-hosted future

Using a JFileChooser to browse AWS S3

Taking Stuff Apart: Realistic Modulette-8