summary refs log tree commit diff
path: root/src/tui.md
blob: 5b23336d849296dbe197b660643c8b5d407477f5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
---
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 `<Tab>`, 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