--- title: On TUIs date: 2023-08-19 --- I'm using a relatively modern terminal emulator. It has snappy native scrolling, with e.g. great touchpad support. I can search the scrollback with a single hotkey. The way I navigate and use it is consistent across all cli utilities. That is, except TUIs. ## scrollback Most TUI programs---IRC clients, (web/gopher/gemini) browsers, mail clients---use custom scroll widgets, as opposed to just printing all the data and letting the terminal handle scrolling. If you're using them over ssh, scrolling turns from something your terminal can do instantly, to something that requires a roundtrip for each unit scrolled. This makes them painful to use on high latency connections, which would otherwise have enough bandwidth to just transfer all scrolled text. It also breaks touch support, as you lose pixel-wise scrolling, and the lag makes it harder to predict the length of a scroll. Every program has its own bespoke set of hotkeys, so you lose out on consistency. Some programs have vi bindings, some don't. Some don't even support arrow keys. Search support will differ, too, with a slightly different implementation in each program that supports it. You already pretty much need scrollback and search for regular shell usage, though. Even if you're using a "dumb" terminal, you're probably using something like tmux to provide that. Why, then, should programs reimplement what's already provided for you by the terminal? One common reason is top/sidebars. The terminal protocol we use is too primitive to express multiple windows, so scrolling a program with a sidebar ends up looking wrong. It's a tradeoff, sure, but IMO the benefits of using the terminal's scrollback outweight the benefits of sidebars. In most cases, you don't really need them: * Some IRC clients have sidebars with the list of open channels, users, or a topbar with the topic. They're not really used too often, and they can always be checked with a single command. * Mail clients have sidebars with the inboxes. It's useful to see the amount of unread messages in each, but that could be printed on startup. After that, there's no need to keep the inboxes on screen when you're not actively switching between them. I'm not saying top/sidebars are useless. They're great in *true* GUIs, where using a sidebar doesn't mean you're no longer allowed to use native widgets. Sadly, terminals are much dumber than that. If you want to use them well, blindly copying GUI conventions isn't the way to go. ## improving the situation The tradeoff would mostly be gone if terminals were extended to allow GUI-like subwindows. I believe that was one of [notty]'s goals. [rio] *sort of* supports that, too. ## line editing Speaking of [rio], it had some good ideas. One of them was the "output point". It separated the program output from your current input. Instead of directly sending your input to the program, it let rio function as a line editor. It had autocompletion, utf8 support, and gracefully handled program output during editing. If the program sent some text, your input was moved out of the way. If the program erased some text (`\b`), your input was moved back to the available space. Going back to the status quo---this is actually pretty hard to achieve, if you're not also reimplementing a scroll widget. If you want to input and output text at the same time (think netcat, or an IRC client), your two options are: * Use Readline as a base. It doesn't support asynchronous output so you'll have to [implement that on your own][rlwrap]. * Use [linenoise]. Hopefully you didn't need utf8 support, as [until recently][yhirose] none of the utf8 forks supported the asynchronous API. Similarly, just as with custom scroll widgets, line editing over a high latency connection *hurts*, because you need to do a full roundtrip for each character typed. Also, some utf8 characters have ambiguous width. Only the terminal knows how they will be rendered, so, if you want your line editor to be fully correct, you'd have to poll the position after each such character printed. Doesn't this suggest that line editing would be better handled on the terminal's end, as in rio? ### appendix: autocompletion How would autocompletion work if line editing was implemented in the terminal? rio was limited to autocompleting paths, but here's my idea for a more general approach: When a program with support for autocomplete starts, it'd send an escape code to notify the terminal that it can use autocomplete. Similarly, it'd disable autocomplete support when launching another program, or quitting. On ``, the terminal would send the current, incomplete, line wrapped in another set of escape codes. The program would recognize that, and respond with a list of possible completions, potentially also informing the terminal that it can request more. ## tldr 1. Scrolling through TUI sucks. 2. I wish terminals handled line editing. [notty]: https://github.com/withoutboats/notty [rio]: https://man.cat-v.org/plan_9/1/rio [rlwrap]: https://github.com/hanslub42/rlwrap/blob/master/src/readline.c [linenoise]: https://github.com/antirez/linenoise [yhirose]: https://github.com/yhirose/linenoise/pull/1