Editor-as-UI pattern
- title
- Editor-as-UI pattern
- type
- concept
- summary
- Shelling out to $EDITOR with a temp file as a lightweight middle ground between CLI flags and a full TUI
- tags
- cli, unix, shell, design-pattern
- created
- 2026-04-30
- updated
- 2026-07-22
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:
- Create a temp file (
mktemp) or open a fixed-path settings file. - Optionally seed it with defaults, comments documenting choices, or the previous run's values.
- Spawn
$EDITOR(with$VISUALas fallback, then a hardcodedvi/nanofinal fallback) on the file. - Wait for the editor process to exit. Exit status convention: zero means apply, non-zero often means abort.
- Read the file back, parse, validate. On validation failure, classic implementations re-launch the editor with an error comment prepended (this is what
crontab -edoes).
Canonical examples
Existing programs that already use this pattern:
git commitโ empty message aborts the commit;git rebase -iextends it to a structured DSL.crontab -eโ re-launches with the error inline if cron syntax fails to parse.visudoโ locks/etc/sudoersfor 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 --editorand many otherghsubcommands.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
$EDITORset 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 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