1. 20

So many warts, details, gotchas in terminal emulation… If we were to start over with 20/20 hindsight here (vt2020), is there any consensus on what should be added, subtracted, or done differently?

  1.  

  2. 15
    1. 7

      VT2000 is the web (HTML+CSS), and the terminal emulator is the web browser. VT2020 could be pretty much the same thing, warts removed. I would be OK with that.

      … Or just give me Plan 9’s /dev/bitblt any day.

      1. 3

        Never heard about /dev/bitblt.

        What can you do with it?

        1. 6

          I would refer you to 8½, the Plan 9 Window System, specifically the Graphical Output section.

          1. 3

            You can try it out with the devdraw in plan9port. The protocol is not really documented, but it’s concise enough to understand within one function, https://github.com/9fans/plan9port/blob/92aa0e13ad8cec37936998a66eb728bfca88d689/src/cmd/devdraw/devdraw.c#L639

            1. 2

              The protocol is muxed mouse, keyboard, and draw. They’re all documented here:

              http://man.9front.org/3/draw
              http://man.9front.org/8/kbdfs http://man.9front.org/3/mouse

              However, you don’t get the interposability or window takeover that plan 9 gives you with plan9port, so in the end this is just a less powerful X11

              1. 1

                It does not prevent you from doing that, does it? Currently it always launch a new devdraw process, but in principle you could reuse the existing one. It would need a lot of book keeping though.

                By the way, this: https://bitbucket.org/yiyus/devwsys-prev/src/default/ seems to be a nice direction.

          2. 1

            If web is part of the progression, wtf happened with vt2000?!

            1. 5

              It’s on a farm upstate, together with JPG2000…

          3. 6

            The big #1 thing would be - keep “user sessions” and “standard input and output” and “serial port drivers” as separate things, instead of mixing them all together into one messy kernel object.

            My #2 thing would probably be having separate streams for raw and formatted data. A program can be hard-coded to always spit out raw data, and if it follows certain simple conventions (like using \x09 to separate columns) the terminal can present the data in a more human-readable way (like automatically laying out the data as a table). Or, a program can handle the formatting itself if the formatted-output stream is available.

            Instead of job control, just allow multiple programs to connect to a terminal at the same time, and let the terminal switch between them with tabs, or separate windows, or whatever.

            1. 5

              Add: rich graphics, direct manipulation, some kind of object metaphor, preview/review of actions Remove: text-only manipulation, pedantic “I didn’t understand that” belittling of people who make mistakes

              1. 4

                Keep:

                • Command-response pattern which implicitly builds a log. You can always look what you did.
                • Text-based so you can copy&paste commands.
                • Copy-pasting commands into a text file to create scripts. It should be trivial to automate stuff.
                • Previous commands cannot be changed. The log is immutable. No rerun like with Jupyter notebooks because it becomes confusing what is the current state. A copy-paste-to-end-and-execute would be fine though.

                Start:

                • Rich output: Tables, images, etc.
                • Text-based beyond ASCII. Unicode is fine. Even emojies.
                • & by default. User should always be able to enter new commands.
                • Further interaction with older outputs but unable to change state. For example, reorder a table of files by size or age.

                Stop:

                • Ncurses-like TUIs which break the command-response pattern. No vi, no might-commander. Spawning other windows would be ok, e.g. gvim and nautilus.
                • Fixed-width fonts. Variable-width fonts are easier to read than fixed width and it should not be a necessary crutch for column alignment.

                Unsure:

                • Are multiple channels (stdout and stderr) a good idea? Are streams a good idea? If “no” to both, then interaction can be simplified to a REPL. TCL?
                • Undo everywhere would be great but is it feasible?
                • Object-predicate instead of Predicate-object style? Example “foo/bar list” instead of “ls foo/bar”. Discovering commands via tab-completion is much better in object-predicate style. Easier to learn. Con: Very different to today.
                • Maybe completion can be delegated to the commands/objects instead of the shell having some database? This means there is a “second main function”, a second entry point to an executable, especially to complete a command.
                • How powerful is the “further interaction with older outputs”? Fully Turing-complete apps or only builtin special cases for tables?
                1. 3

                  Previous commands cannot be changed. The log is immutable. No rerun like with Jupyter notebooks because it becomes confusing what is the current state. A copy-paste-to-end-and-execute would be fine though.

                  The issue is that rerun is so useful, since we always want to rerun things with hindsight. I’ve been thinking about this. We could keep a tree of states, in addition to call-with-current-continuation we need a call-with-current-environment extending eval, such that every time we evaluate a new expression we get a new node in the tree representing a new continuation and environment, and we can choose to continue with the saved continuation using the new environment or keep experimenting with new environments.

                  1. 2

                    While I like the idea philosophically, I don’t believe it is feasible in this case. State here includes not just the environment variables, but also the whole file system and the world at large. How do you return to the state before you did the rm -r or before the sendmail?

                    1. 2

                      Yes. There needs to be a boundary where we can no longer take snapshots of the environment. It is fair to say that within the systems that under your control it is feasible, which probably covers most of the cases. So rm -r is trivial, but sendmail probably requires support from the remote end.

                  2. 3

                    Maybe completion can be delegated to the commands/objects instead of the shell having some database? This means there is a “second main function”, a second entry point to an executable, especially to complete a command.

                    This is already being done! Haskell programs that use the optparse-applicative library have secret command-line flags to produce bash and zsh completion scripts. You can use it something like this:

                    source <(myprogram --bash-completion-script `which myprogram`)
                    

                    As a concrete example, Pandoc doesn’t use this exact library but you can get it to produce a completion script with pandoc --bash-completion.

                    It would be nice if the shells had more native support for this, maybe even to the point where the shell could have a real conversation with the running binary instead of the binary just dumping out a completion script. Of course, that might also lead to confusion as people need to adjust their mental models of exactly when and why a particular program is running… is it really running, or is zsh just interrogating it for a list of possible completions for something I typed?

                    1. 1

                      The real conversation would be great. Then completion would work for internal or remote data: Complete git branch names, ssh remote paths, make goals, or package names.

                      1. 2

                        Come to think of it, there is a little bit of a conversation already… zsh comes with built-in completion for those first three things. (And maybe the fourth, depending on which context you’re talking about.) My understanding is that the completion scripts just call the command with the appropriate flags to “list all of my branch names” or whatever. This logic is tied up in zsh-specific scripts for now; I agree that it would be better if binaries could be called directly to get a shell-agnostic and dynamic view of the possible completions.

                    2. 2

                      Most of these are good but some are kind of bizarre in my opinion.

                      Rich output: Tables, images, etc.

                      The output of a I/O stream is text. How would you output a table in a way that can be copy-pasted into vim?

                      & by default. User should always be able to enter new commands.

                      The synchronisation and ordering of a terminal is really useful to me. I would not like this.

                      Ncurses-like TUIs which break the command-response pattern. No vi, no might-commander. Spawning other windows would be ok, e.g. gvim and nautilus.

                      The whole point of the terminal is ncurses! That’s the great thing about the terminal, that it healthily mixes semi-graphical and textual UI. Ctrl-Z in vim, man!

                      Fixed-width fonts. Variable-width fonts are easier to read than fixed width and it should not be a necessary crutch for column alignment.

                      Fixed width fonts are easier to read than variable-width. As soon as you get rid of fixed-width fonts you need to find some other non-textual way of doing tables, which totally breaks the whole design of the standard I/O interfaces hich are that they’re entirely text.

                      Are multiple channels (stdout and stderr) a good idea? Are streams a good idea? If “no” to both, then interaction can be simplified to a REPL. TCL?

                      Having an unbuffered output for logging is really useful. I don’t know why we’d want to get rid of it. How are streams not a good idea?

                      Object-predicate instead of Predicate-object style? Example “foo/bar list” instead of “ls foo/bar”. Discovering commands via tab-completion is much better in object-predicate style. Easier to learn. Con: Very different to today.

                      That doesn’t make any sense. The entire a.b(c, d, e) paradigm is bizarre nonsense that makes no sense except in very very specific cases. If you have a function that has one special argument, it makes sense. If you have a function with no or two or more special arguments it’s incredibly awkward.

                      Shell commands also aren’t predicates, and their arguments aren’t objects. Their arguments are flags, or file names, or subcommands, or any other arbitrary strings. There’s nothing special about the first argument in most commands, nor can I even think of any commands with a privileged special argument.

                      I think it’d be better to think of the current directory, the current state of your shell, as the current command. Regardless, this is a shell thing and not a terminal thing.

                      Maybe completion can be delegated to the commands/objects instead of the shell having some database? This means there is a “second main function”, a second entry point to an executable, especially to complete a command.

                      Now this is an idea that I wish had caught on. Shells having to have their own autocompletion is crazy. I don’t think that it should be directly part of the executable though, because much like documentation, it should be possible to add it to commands that don’t already have it done well by packagers/distributions.

                    3. 3

                      Well, do we have hindsight doing it today, or hindsight doing it with the limitations back in the day?

                      If I was doing it today in the same paradigm, I’d easily put structured message blocks for all communications on the wire. No more picking out escape sequences and hoping you get it right.

                      But back in the day, they had limited bandwidth, and that message block framing would be significant overhead. You could still potentially make it work though by at least defining the escape sequences with some kind of consistent detection scheme… maybe something inspired by UTF-8 so you can notice if you were dropped in the middle of a scheme and bypass that to the next regular character to resync, and definitely DEFINITELY make sure the “escape” sequence is not the same character as the esc key on the keyboard! (If you want to manually input things, put more logic in the keyboard itself to send the new sequence, like an fn modifier on modern laptops)

                      I think an unholy cross of UTF-8 and base64 could present arbitrary data with fairly small overhead for the typical case while providing stream correction capabilities and potential for unlimited expansion. My big worry would be how much the terminal hardware would cost and the processing power to decode on the mainframe… would be just my luck a competitor would offer a low-cost alternative that does it worse and they’d take over the market :(

                      But still that’d be the big thing I’d like to fix that was probably doable back then and would solve a LOT of headaches we still have today.

                      if totally redoing it though… well it probably wouldn’t even maintain the paradigm. There’s some bits of terminal UI I like, and bits of pipe i/o I like… but I probably wouldn’t combine them (even my terminal library I have today doesn’t let you actually combine - you must if(interactive) { do one thing } else { do another}) and each of those I’d do totally differently separately. Make the UI, well, a UI, and make the pipe system have more structure as well. Like using XML instead of plain text. Not even kidding.

                      1. 3

                        I want something that can handle the full range of characters and chords that my keyboard is capable of typing. A while ago I went down a rabbit hole trying to make a key binding for Control-Return. Eventually I realized that as far as the terminal is concerned, Return is Control-M, so binding Control-Return doesn’t even make sense. And yet… my OS doesn’t need to reduce all of my keyboard input to ASCII’s 7-bit space; what if my terminal didn’t need to either?

                        1. 3

                          I want a physical button on my keyboard named “LOCAL”. It’s a modifier. And I want there to be no mechanism for sending any “local” codes to a remote machine. In fact, I want the specifications for any interstitial infrastructure to explicitly forbid the transmission of such codes. They’d be of extremely limited use, because of that. Perfect.

                          1. 3

                            do elaborate, why would you want this?

                            1. 9

                              I want a key that is just mine. I don’t want RET ~ . to terminate a ssh session. That strange invocation is required because all the other keys are being sent to the remote. I want LOCAL+c (like ctrl+c, but guaranteed to be received and handled locally). I bet I could find more uses for it, too.

                              edit: fix typo: terminal –> terminate

                              1. 2

                                ah I see, thought it was something in the more eccentric side where similar phrasing has been used, but there the meaning was about input modes that was used to taint tag input to prevent it from crossing certain boundaries (oops I pasted the password thing into the wrong window that happened to be networked).

                                1. 1

                                  That’s in the ballpark, I guess. (I have a passing familiarity with ‘taint mode’ from perl?)

                                  The first example I was thinking of was something like “7-bit ascii” vs “8-bit ascii”.. (See https://en.wikipedia.org/wiki/8-bit_clean I guess.) But, the control codes are all represented within the first five bits so, not the example I was thinking it was!

                                  The second example is Private Address Spaces. 192.168.*.* and friends don’t get passed between routers. Sure, some manufacturer or crazy admin could break that rule, but they’d be “wrong, wrong, wrong!”, and everybody would know it.

                                  I want a button reserved for private use. Now that I thought it through, my idea is clearly not about terminals. Sorry OhhhhYeaaaaah. :)

                          2. 2

                            Get rid of ansi escape codes and any text graphics. Add an out of band message to switch to full graphics mode.

                            Add a third mode to allow graphics in scrollback, so I can write something like a ‘graph’ or ‘img’ program that draws pictures in the terminal.

                            1. 2

                              I’ve often toyed with doing a Blit-style terminal. It’s a regular terminal but then downloads wasm apps over the in-band channel and lets them take over the window and present a little GUI.

                              Think of it as what Blit or RIPscrip would’ve evolved into if modern GUIs and the Web hadn’t happened.

                              Another road-not-taken is CLIM, the Common Lisp Interface Manager.

                              1. 2

                                Are we talking just terminal emulation, or the whole shell ecosystem? In the former case our hands are more or less tied by path dependence. In the latter:

                                1. rich (ideally self-describing) data structures ought to be part of the inter-process communication mechanism, & pipelining should support them. When not piped to some other process, they will render in the terminal in a way that defaults to sensible serialization but can be overridden. The rendering would involve arbitrary graphical elements, but in a box (so you can scroll past it, or scroll back up in order to interact). The default ‘sensible serialization’ would be a collapsed recursive listing of object members, with a color tag based on a simple hash of the pointer (such that you could identify circular structures by shared pointers). Basically, bring in all of the benefits of notebook interfaces & the browser javascript console into regular command lines, but without the overhead of an actual browser (since it’s easier & computationally cheaper just to use a regular gui toolkit). Typing for these objects ought to be structural (falling back to duck).

                                2. keep all the nice features introduced into modern shells over the years – tab completion that works with globbing, history and history search, etc. Add as-you-type syntax highlighting and make the default behavior on trying to tab complete to show documentation.

                                3. speaking of documentation, it should all support at least searching and jump links, & should be automatically generated from comments & from arg parsing code. If a command produces a particular object format, the object should inherit pointers to the documentation so that, when viewing some arbitrarily mangled object, one can see automatically-generated documentation for the portions preserved and navigate from there to complete documentation of the object’s original form. (Basically, something like a combination of python docstring help() and TempleOS’s everywhere-accessible hyperlinked documentation.)

                                4. Support the acme mouse language. Arbitrary text should become command executions based on which mouse button you use to select it.

                                5. Allow scrolling back to previous commands, editing them, and re-running them, either keeping the history as-is or appending the modified version to the history. Support exporting parts of the history as either command-only scripts or as viewable notebooks with stored context.

                                6. Piping to arbitrary numbered streams is supported in every shell but is confusing, so support an expanded graphical means of both displaying such code and writing it – probably using arrows from a pipeline fragment to other fragments on separate lines.