Map

Editor-as-UI pattern

Wiki conceptcliunixshelldesign-pattern โ†ณ show in map Markdown
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:

  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 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.

  • 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
Sub-pages