# Editor-as-UI pattern

Editor-as-UI is the Unix pattern of treating the user's text editor as the input widget for a program. The program writes a temporary file, calls `$EDITOR` (or `$VISUAL`) on it, and reads the result back when the editor exits.

It sits between two more obvious choices. Pure command-line flags are cheap to write but degrade fast as parameter count or input length grows. A full TUI (curses, Bubble Tea, ncurses) gives you a real interactive surface but takes serious engineering effort and produces yet another interface the user has to learn. Editor-as-UI is the gap in the middle: the user keeps their own editor — with their muscle memory, plugins, undo history, search, paste — and the program gets a free input stage worth thousands of lines of UI code.

## How it works

The mechanics are uniform across implementations:

1. Create a temp file (`mktemp`) or open a fixed-path settings file.
2. Optionally seed it with defaults, comments documenting choices, or the previous run's values.
3. Spawn `$EDITOR` (with `$VISUAL` as fallback, then a hardcoded `vi`/`nano` final fallback) on the file.
4. Wait for the editor process to exit. Exit status convention: zero means apply, non-zero often means abort.
5. Read the file back, parse, validate. On validation failure, classic implementations re-launch the editor with an error comment prepended (this is what `crontab -e` does).

## Canonical examples

Existing programs that already use this pattern:

- **`git commit`** — empty message aborts the commit; `git rebase -i` extends it to a structured DSL.
- **`crontab -e`** — re-launches with the error inline if cron syntax fails to parse.
- **`visudo`** — locks `/etc/sudoers` for editing, validates the result before installing.
- **`vipw`** — the same for `/etc/passwd`. The man page literally markets the validation as the feature.
- **`gh pr create --editor`** and many other `gh` subcommands.
- **`kubectl edit`** — applies the diff between original and edited YAML.

## When it fits

Strong fits:

- The input is text-shaped (config, query, message, list).
- The parameter set is small enough to scan in a single editor screen, but too large or too unstructured to sit on a command line comfortably.
- The user benefits from comments, documentation, and previous values being visible while they edit.
- The user is technical enough to already have an editor preference set in `$EDITOR`.

Poor fits:

- True interactive workflows that need live feedback (a file watcher, a query-as-you-type search).
- Inputs that aren't text — image regions, audio scrubbing, drag targets.
- Non-developer end users who don't have `$EDITOR` set or don't know what vi is.
- Anything that needs to be embedded inside another program's TUI.

## Idioms

A few patterns recur in well-built editor-as-UI scripts:

- **Comments as documentation.** The temp file's first line is often a `#` comment listing valid choices for the field below — `git rebase -i`'s entire instruction block, the available subdirs in [[text-editor-as-ui|Gauer's yt-dlp wrapper]].
- **Persistence over `mktemp`.** A fixed-path settings file lets the second invocation start from the first invocation's values. The cost is concurrency safety; for personal tools that's usually fine.
- **Validate then re-edit.** On parse failure, prepend the error as a comment and re-launch — don't just bail out and lose the user's work.
- **Diff the result.** If the user exits without changing anything, treat that as an abort.

## Why this is hard to replace

A custom TUI text-input widget that approaches editor parity would need: cursor movement (word, line, paragraph), search, search-and-replace, undo/redo with branching, multi-cursor, syntax highlighting, paste from system clipboard, configurable keybindings, multi-buffer support. That's years of work. Editor-as-UI gets all of this for the cost of a `system()` call.

The closest modern equivalent is **opening a file in VS Code via a URL handler** or running a Bubble Tea form, but neither matches the universality — every Unix system already has `$EDITOR`. It's load-bearing infrastructure that nobody had to build.

## Related

- [[text-editor-as-ui]] — Dave Gauer's post that named this pattern for me
- [[clean-code-coding-agents]] — same "lean on existing context, don't reinvent" instinct, applied to LLM tooling
- [[good-tools-are-invisible]] — Ginger Bill's version of the same idea from the toolmaker's side: a good tool disappears into the background instead of demanding attention
- [[sheets]] — refuses the choice: a full vim-keybinding TUI for browsing a CSV, plus a flag-free CLI mode (`sheets budget.csv B7=10`) for edits that don't deserve an interface at all
