matklad

A Better Shell

I want a better shell.

There are exciting projects to improve data-processing capabilities of shells, like nushell. However, I personally don’t use this capability of shell a lot: 90% of commands I enter are simpler than some cmd | rg pattern.

I primarily use shell as a way to use my system, and it is these interactive capabilities that I find lacking. So I want something closer in spirit to notty.

Things I Need

The most commands I type are cd, exa, rm, git ..., cargo .... I also type mg, which launches a GUI version of Emacs with Magit:

magit

These tools make me productive. Keyboard-only input is fast and “composable” (I can press up to see previous commands, I can copy-paste paths, etc). Colored character-box based presentation is very clear and predictable, I can scan it very quickly.

Take a second to appreciate how Magit interface manages to be both faster then command line flags (you don’t have to type spaces and dashes) and infinitely mode discoverable. It was Magit who taught me about --force-with-lease.

However, there are serious gaps in the UX:

  • ctrl+c doesn’t work as it works in every other application.

  • I launch GUI version of Emacs: the terminal one changes some keybindings, which is confusing to me. For example, I have splits inside emacs, and inside my terminal as well, and I just get confused as two which shortcut I should use.

  • The output of programs is colored with escaped codes, which are horrible, and not flexible enough. When my Rust program panics and prints that it failed in my_crate::foo::bar function, I want this to be a hyperlink to the source code of the function. I want to cat images and PDFs in my terminal (and html, obviously).

  • My workflow after I’ve done a bunch of changes is:

    1. type cargo test to launch tests

    2. type ctrl+shift+Enter to split the terminal

    3. type git status or mg in the split to start making a commit in parallel to testing

The last step is crazy!

Like, cargo test is being run by my shell (fish), the split is handled by the terminal emulator (kitty), which launches a fresh instance of fish and arranges the working directory to be saved.

Hm, aren’t cats supposed to eat fishes?

As a user, I don’t care about this terminal/terminal emulator/shell split. I want to launch a program, and just type commands. Why cargo test blocks my input? Why can’t I type cargo test, Enter, exa -l, Enter and have this program to automatically create the split?

$ cargo test
...
tons of output in progress
...

--- split (which is automatically healed once `cargo test` finishes)
$ ls
foo.txt
bar.rs

prompt> git ...

Additionally, while magit awesome, I want an option to use such interface for all my utilities. Like, for tar? And, when I type cargo test --package, I really want completion for the set of packages which are available in the current directory.

New Shell

What I really want is an extensible application container, a-la Emacs or Eclipse, but focused for a shell use-case. It could look like this:

  • A GUI application (which draws using raw OpenGL: we won’t be using native OS GUI widgets).

  • A UI framework for text-based UIs, using magit as a model. ctrl+c, ctrl+v and friends should work as expected.

  • A tilling frame management, again, like the one in Emacs (and golden-ratio should be default).

  • Some concept of process-let, which can occupy a frame

  • A prompt, which is alway available, and smartly (without blocking, splitting screen if necessary) spawns new processlets.

  • An API to let processlets interact with text UI.

  • A plugin system for in-process processlets (obviously, plugins should be implemented in WASM).

  • A plugin marketplace (versions, dependencies, lockfile, backwards compatibility).

  • A plugin system for out-of-process processlets (JSON over stdio?).

  • A backwards compatibility wrapper to treat usual Unix utilities as processlets.

Emacs?

Isn’t it Emacs that I am trying to describe? Well, sort-of. Emacs is definitely in the same class of “application containers”, but it has some severe problems, in my opinion:

  • LISP is far from the best possible language for writing extensions

  • Plugin ecosystem is not really dependable

  • It doesn’t define out-of-process plugin API (things like hyperlinking output)

  • Async support is somewhere between non-existent and awkward

  • Its main focus is text editing

  • Its defaults are not really great (fish shell is a great project to learn from here)

  • ctrl+c, ctrl+v do not work by default, M-x is not really remappable

Random Closing Thoughts

This post contains the best plugin diagram ever:

This talk echoes similar sentiments:

If you build some like this, please sign me up!