Bubble Tea: fun, functional and stateful way to build terminal apps
github.com
github.com
[1] https://docs.rs/dialoguer/latest/dialoguer/ [2] https://github.com/console-rs/indicatif [3] https://docs.rs/colour/latest/colour/
After seeing some recent posts on HN, I've been fantasizing about a Turbo Rust project.
The achilles heel of terminal UIs is that they can't display images.
Sure, it's something you can do with SSH via X11 forwarding since forever, but it seems the ecosystem never quite stuck the landing, and now we're here trying to figure out how to make CLI more like GUI for our own sanity.
Maybe we should give it another shot? It seems Windows is trying to do something similar with WSLg to achieve seamless integration of Linux apps within Windows 11 (using RDP under the hood), what is everyone else doing?
TUI/GUI (qt,gtk,cocoa,...) & even has pas2js
JAWS might have dumped all their TUI accessibility features now that they think blind users should only use the GUI, but BRLTTY is based around the console. Orca does assume Gnome and/or a web browser, but it's no worse than usual with a console. (That is to say - I hate Orca, no matter where I'm using it.)
If you're making your own TUIs, there's a few things that curses sneaks in to make things a nicer experience. It uses the start of heading, start of text, file/group/record/unit separators from the invisible portion at the start of the ASCII table, which your sighted users can't see, but things like BRLTTY announce.
(Note: I don't use BRLTTY with an actual Braille display. I use it with espeak.)
QT and Electron are the most capable cross platform tools for building UIs in my opinion. Both have their drawbacks of course, QT is a pain to work with and electron is famous for being not responsive enough for some users
We're all up this tree where we want all the features we're used to and we're willing to put up with software bloat and insecure software and sprawling supply chains to get it. I'm not. So I start with something minimalist and do what I can within it.
One of my inspirations is http://akkartik.name/illich.pdf
Is it an artificial limit you put on rendering?
Do you have button animations at 30 FPS?
He was looking for a cross-platform GUI toolkit.
> "The achilles heel of terminal UIs is that they can't display images."
That statement is not entirely 100% accurate these days, thanks to some really creative hacks over the years. I'll list a few links where some fun examples live. ;)
[1] https://github.com/atanunq/viu
[2] https://github.com/stefanhaustein/TerminalImageViewer
[3] https://askubuntu.com/questions/97542/how-do-i-make-my-termi...
[4] https://unix.stackexchange.com/questions/35333/what-is-the-f...
The real achilles heel is they're not accessible. There is no terminal protocol for denoting actual objects being drawn in the window such that they can be exposed to accessibility APIs. Standard CLI tools are usable, but TUIs are not.
I have previously seen someone state that for this reason, every TUI program should have an optional CLI mode.
Also, if every UI is a TUI then the OS task switcher becomes useless, terminal programs don't have access to the full set of key modifiers, mouse support is spotty, etc.
Its amazing how one persons perspective can completely disagree with anothers.
What did you mean by the "OS task switcher becomes useless" ?
As for the OS task switcher, if everything is a TUI then you only have one app, which is your terminal, and so your OS task switcher cannot help you switch between apps anymore. You have to switch between windows belonging to the same process. It’s the same problem that you run into by doing everything inside a browser.
I have hyper meta super and ctrl work, sent through to my curses app. Are you referring to something like gnome or something else intercepting the keys ?
I do see on https://invisible-island.net/xterm/ctlseqs/ctlseqs.html that xterm has a resource modifyOtherKeys that is documented to turn keys like like alt-tab turn into CSI 2 7 ; 3 ; 9 ~, but this doesn't appear to work on Terminal.app, iTerm.app, or Kitty. Similarly there's a documented sequence that's supposed to make xterm set the 8th bit for meta instead of prefixing with ESC, but that also doesn't work on these three terminal emulators. I am not set up right now to try xterm itself (I don't have an X11 server).
Ultimately, the ways keys are sent to terminal processes by default disallow many modifier combinations (and may also have multiple key chords that send the same input). Some terminals may allow for customizing this on a key-by-key basis but most CLI apps won't understand the resulting input, and won't have a way to declare whether or not the terminal should do this. xterm documents a way to do this for most keys, but that isn't supported on the terminals I have access to on macOS.
It doesn't pass command through, which I don't personally consider a problem since I want the command key to do things to my terminal (like opening new tabs) but ymmv on that one.
It looks like kitty actually has a means of requesting this stuff dynamically with CSI = flags ; mode u (and a push/pop stack). It also says it will use CSI-u mode automatically for keys that don't have a "legacy" escape, but I didn't see this before because stuff like C-M-3 does not in fact echo as CSI-u but instead that inputs as just 3 instead of CSI 5 1 ; 7 u. Even using Kitty's "progressive enhancement" mode to request disambiguation of escape codes leaves C-M-3 broken which is rather confusing. Versus iTerm where its profile-wide CSI-u mode toggle does report C-M-3 correctly.
In any case, Terminal.app doesn't support this, and I'm not sure what else does. Looking at Alacritty right now, I don't see any direct support for this, the closest I've found is some issue comments suggesting setting up custom key bindings to simulate it (which I guess means defining every single combination of modifiers for every single keyboard key?).
As for Command, yes, it should be for actions _to_ my terminal. But for GUI apps they go to that app, and so replacing GUIs with TUIs means losing the ability to use Command for this sort of thing. For example, in MacVim I can press ⌘N to open a new MacVim window. In vim I can't, because that just opens a new Terminal window. Or ⌘A to select all, which doesn't even have an equivalent CLI key shortcut (e.g. in Vim I'd have to type something like ggv<C-End>, exiting insert mode first if necessary).
And of course proper usability of the terminal typically requires binding Alt to Meta, but doing that means losing the ability to type non-ASCII chars on macOS. This is another thing that GUIs don't have to worry about.
I don't see a way to split the difference between ⌘N for a new terminal window and ⌘N for a new application window. It has to do one of those things because it can't to two, and speaking personally, I want it to open the new terminal window. It's a tradeoff.
CSIu mode is new, it's pretty well supported though.
> And of course proper usability of the terminal typically requires binding Alt to Meta, but doing that means losing the ability to type non-ASCII chars on macOS. This is another thing that GUIs don't have to worry about.
This doesn't happen to be true though, right Option can be the compose key and left Option can be Meta/Alt aka prepend-with-Escape. I also use Karabiner to set pressing left Alt to send Escape for even more faithful terminal fidelity, but that's optional. Having both Compose and Alt as right and left Option is perfectly straightforward, and I suggest it, because much like with Command, it does have to do one or the other, but this time, we have two keys.
It's probably possible to split the left and right command keys but again, personally, I don't want the terminal app to see my command keys and am comfortable with the mere six(!) combinations of modifier + key I can send without it.
Right. I'm not saying CLI tools should be able to get command-keys, I'm saying using a TUI instead of a GUI means you cannot use command-keys. It's a notable limitation inherent to TUIs, and it's one I'm talking about since the context here is "the terminal is the most portable GUI platform available".
> Having both Compose and Alt as right and left Option is perfectly straightforward, and I suggest it, because much like with Command, it does have to do one or the other, but this time, we have two keys.
I like that this is an option in some terminal emulators. Unfortunately it's not an option in Terminal.app, and for me personally I'm using right-option for something else system-wide (though I may have chosen differently if Terminal.app offered this).
Unlike say, Rust. I do love Rust though.
Now with generics there's even less to complain about.
With C at least it's clear to me that it's literally (very literally) a 50 years old language, and I've gotten used to it after decades. It's far from ideal, but at least there's some deep familiarity, and that coupled with the fact that it's everywhere makes me feel more tolerant of it.
But learning a new language which is, in some ways, stuck in the same past as that established 50 years old language is not very pleasing.
When a language has some property that surprises you, consider whether it might have been designed that way on purpose, not simply because the creators were ignorant/lazy/negligent/drunk/lost a bet/etc.
This is not a well-intended comment, but then why didn't they add generics at the start? It's not like it was a surprise to anyone even remotely familiar with PLs that they will have to retrofit it sooner or later. Also, function-scoped defers instead of surrounding braces-based one? Come on...
There is zero point in postponing a feature so that you can build a community, which you will burn down later. It’s not a startup that has to be ready in X months or it will fail.
And while I can totally understand how that makes sense in the intended setting, I don't consider that pleasing at all. "Pleasing" for me implies that I like a thing because I like the thing itself, not because external constraints impose (perceived) limitations on other choices. Especially in this context, where we were talking about learning and using a programming language by choice, instead of assembling a team of programmers from an already common pool.
You are a new developer too. Everyone is. When it’s easy to learn something, it’s easy to relearn it, and it’s easy to learn things around it (because you don’t devote as much brain to the language).
All languages should target new, dumb devs, because all devs (including Rob pike) are new and dumb.
And I absolutely reject the notion that an easy to learn programming language is better. Especially so because Go was made “easier to learn” in large parts by its similarity to C, so it’s really easier for people already and only familiar with C, and that was explicitly stated by the creators.
It is simple enough to learn quickly so the investment isn't that demanding. I'd like to learn Rust but the learning curve is much steeper and it seemed to be subject to changes. I'm not shitting on it, just lazy with too many distractions to hunker down and do a real investment of time into it. Someday.
you just said you wanted to write SSH apps. is that not enough of a spark?
I use both weekly, for different things. There are numerous people who don't use Go at all that are in Go specific forums. They read Go specific threads here on HN just to make their Rust points. I poke fun at them with my statement. You just gave me the hook.
:) smiley face for you
The problem is that sometimes the zero value is valid, but not special in any way and doesn't make sense as the default. Go has arbitrarily decided that one particular value is special without your blessing or the ability to override it. This leads to bugs in which the zero value shows up in places where you don't expect it, simply because there is some code which doesn't explicitly set it. That's a very easy mistake to make, which makes writing Go an error prone activity.
I shudder to think that there's probably some Go program that deals with money, and a user's balance might be cleared to zero simply because some operation forgot to explicitly set it in some struct.
In order to implement "optional" fields in Go where you want to distinguish between zero (a valid value) and none (a missing value), the common solution is to either put the integer behind a pointer (which can be null) or use some "sentinel" value like -1 to indicate that the value is missing (as long as that sentinel value isn't actually valid in the domain of discourse). These are terrible hacks, but exactly what you'd expect from programmers who spent the majority of their careers writing C.
This design also leads to another kind of issue: sometimes you don't want people to be able to construct values of a particular type without going through a constructor which ensures the relevant invariants hold. This concept ("smart constructors") is widely used in other programming languages, but it's impossible in Go because Go allows anyone to construct an inhabitant of any type simply by declaring a variable of that type.
A simple example of that kind of issue is pointers: in (safe) Rust, references are guaranteed to be non-null, and you use sum types to implement optionality. This is great because you can always dereference a reference and not worry about handling the null case. In Go, all pointer types have that nasty zero value (null), and there's nothing you can do about it. The billion dollar mistake.
I like the fact that Go encourages simplicity, and for the most part the language is fine. But I'm convinced that having every type be pointed rather than supporting proper sum types is actually more complex in terms of the implications it has on writing and reasoning about code. They have mistaken minimality for simplicity.
As an embedded firmware guy, I'll try not to be offended by that line :) I agree with you, though. C is a really good language when you consider its original purpose and the time period in which it was created. Ignoring 50 years of progress in type theory/category theory in the name of "simplicity" is inexcusable.
Writing C is an understatement given that Go and C were created by the same person (with help, of course).
*(psql, sqlplus, isql, etc....)
https://changelog.com/podcast/481#transcript
And a couple audiogram snippets from the conversation:
Correct. A value copy is a simple shallow copy. Deep copy requires use of reflection (if you don't want to manually manage copying).
It's actually the `tea.Msg` that causes re-renders. Here's the code: https://github.com/charmbracelet/bubbletea/blob/v0.20.0/tea.....
tea.Msg is, in Redux land, an "action" that triggers some state updates and _always_ triggers a re-render, even if nothing changes.
This is elm-like, but we have to understand the limitations of what we're working with: a terminal with no way of (nicely) updating elements in place. It's essentially an immediate-mode UI, and so it always paints.
For example, say you needed an undo/redo mechanism. If the model were guaranteed to be immutable model, you could simply hang on to the previous models as a sequence of state changes; undoing/redoing is trivial. If the model is mutable, then the undo/redo system has to defensively make a copy before update is called
How does one manage dynamically sized lists of elements, if arrays are disallowed?
- Light-weight lambda syntax
- Sum types and records
- Global type inference
- Exhaustive match expressions
- Powerful generics
- Immutability by default
- Partial application / currying
- Modules
- Ecosystem of packages built around pure functions
This looks really interesting and could be a fun way to get into the Go landscape!
Ruby, Python, Java, Rails, React, Go, Rust, Elm, Dart, C, C#, Node, Next, Nest, Kafka, LaTeX, bash, fish, cat, Android, Apple, Windows…
I'd be happy to search for this with "bubble tea framework" or "bubble tea tui" or similar and it doesn't bother me.
Maybe we need explicit namespacing in English? I'll start saying stuff like "programming languages colon colon ruby" so people can be absolutely sure what I'm talking about.
/derail.
The single letter variable approach is sensible and not dogmatic -- e.g., `i` in a tight for loop makes sense.
But like, people that work for Google surely know something about search term collision. Or just don't care about making anything else that has "Go" in the query gets confusing results. Which is an odd stance for Google employees, but none of my business I guess.
Of course it's common anyway, I just don't see why you'd do this to yourself. You either bury your own project, or you are so successful with branding that no one can find what used to have that name. Google can push something hard enough with perfect SEO to make sure that Go the language shows up. A github page?
I mean, it's not Google but: https://duckduckgo.com/?t=ffab&q=go&ia=web First hit -- https://go.dev
https://duckduckgo.com/?t=ffab&q=bubble+tea&ia=places This project is not anywhere near the front page and I can't imagine it ever being. Too many different companies are selling boba tea.
I never really like it when companies use existing common words, but unless you're a huge project or a company that can push the SEO and marketing it's actively burying your work from anyone finding it.
People are also pretty good at disambiguating things. Fewer than 7% of people ordering from Amazon have ended up in Brazil on accident.