I built a terminal because I could never remember the keybindings

rust dev.to

I could never remember the keybindings.

I know WezTerm and NeoVim are good. I installed both more than once, and backed
out both more than once, always at the same place. It isn't that I refused to
learn them — I stalled before I got far enough to learn anything.

So I built the terminal I wanted instead: one where you can do everything
without knowing a single key, and where knowing the keys only makes you faster.

https://github.com/hyuga611/tsumugi

Rust, MIT / Apache-2.0. Developed on Windows.

One idea: the screen is a document

In a normal terminal, past output is over. You can scroll back and look at it,
but you can't really use it — you end up dragging with the mouse and hoping
you grabbed the right lines.

But scrollback isn't a log. It's what you just did, and it's meant to be read
back and reused. So tsumugi treats it as a document:

  • walk past output with vim motions (j k w [[ ]])
  • select a command and its output as one thing (ac / io text objects)
  • open a file in the same pane (:e)

The screen and a file are both "documents", so the same keys work on both.
There is no separate editor to launch.

Selecting "a command and its output" honestly

Guessing at this always breaks. Decide that "lines starting with $ are
prompts" and the first $ inside some output ruins it.

So tsumugi uses OSC 133 — shell integration. The shell tells the terminal
where the prompt starts, where the command starts, where the output starts, and
what the exit code was.

tsg --install-shell-integration
Enter fullscreen mode Exit fullscreen mode

After that you get ✓ / ✗ marks in the left gutter, [[ ]] to jump between
prompts, and ]e to walk only the failed commands. Nothing is matched with a
regex against your output, so no mark is ever a lie.

Every command has a mouse path — enforced by a test

Since the whole point is "you don't have to know the keys", this isn't left to
good intentions:

#[test]
fn every_command_is_mouse_reachable() { ... }
Enter fullscreen mode Exit fullscreen mode

Add a command without a mouse path and CI fails. There are 78 of them right now.
Right-click gives you "what you can do here". Space t labels every path and URL
on screen with one or two letters, and that letter opens it.

Running several AI agents turned me into a search function

This part came out of using it, not planning it.

I had three Claude Code sessions going on different jobs, and noticed that most
of my actual work had become hunting for which one was waiting on me. Look
at one pane, look at the next, find the stalled one, answer it, start over.

So I made the agents say so themselves:

tsg --install-agent-hooks
Enter fullscreen mode Exit fullscreen mode

That wires one line into Claude Code's and Codex's hooks. Tabs get a ● mark, the
status bar counts how many are waiting, and Space a jumps to the next one.

If the window isn't focused, the taskbar flashes. Anything running inside can
also call for you with OSC 9 when a long job finishes.

Losing the machine doesn't lose the session

Closing the window was already fine — the multiplexer is a separate process, so
shells and agents keep running. Losing power was not.

Now the layout and each pane's directory come back on the next launch. But
the screen contents are never written to disk. SECURITY.md promises that
scrollback never reaches the filesystem, and I didn't want to quietly break it
for a convenience feature.

So what comes back is the shape, the directories, and the argv. And the
conversation? The agent owns its own transcript, so tsumugi only needs to
know how to say "continue". A restored pane gets claude --continue typed at
the prompt but not run
— pressing it is your call.

Reviewing what the agent wrote, where you are

Space g opens git diff in colour. Space G stages the hunk under the
cursor, Space R reverts it (twice — it destroys work).

A diff you only look at means making the same decision again somewhere else
later. And if an agent rewrites a file you have open, tsumugi reloads it (unless
you have unsaved edits, in which case it tells you instead). Without that you
read stale text, edit it, and delete the agent's work the moment you save.

Also in the box

  • language servers: squiggles for errors, gd to definition, Ctrl+Space to complete
  • :grep searches the whole project and opens straight from the results
  • :123, s/old/new/, g/ for a regular expression
  • SSH domains — the far side keeps running when the link drops
  • Markdown rendered in place, images (Kitty graphics / Sixel)
  • narrowing the window re-wraps scrollback instead of truncating it

What building it actually taught me

The lesson that stuck: you cannot test your own work with your own habits.

Seven keys listed in the command palette did nothing when pressed — Space g
for the diff, Space a to jump to a waiting agent, and five more. I had been
verifying everything through the CLI (tsg --run <id>) and had never once gone
through the key path. A test that mechanically cross-checks the list against the
implementation found all seven in one run.

Opening a tab-indented file deleted every tab — the worst kind, because
saving then destroys someone else's indentation. That fell out of writing a test
for auto-indent, not from using the editor.

Wiring up LSP, I sent initialized without waiting for the initialize
response (a spec violation) and rust-analyzer simply went quiet: running fine,
zero diagnostics. Then a second one — language servers return file:///c:/...
with a lowercase drive letter, and I was comparing paths as strings, so every
diagnostic was arriving and being thrown away.

All three looked like they were working.

Being straight about the state

Developed on Windows. macOS and Linux build and pass CI, but nobody has run
the window layer
(decorations, IME, the installer). The README says untested
rather than supported, and I'd rather keep it that way until someone has.

It isn't finished so much as mine — I use it daily and can fix it myself. I
started out unable to remember keybindings and now drive about half of it from
the keyboard. Not because I finally memorised them, but because I could keep
using it while I didn't.

Source: dev.to

arrow_back Back to Tutorials