The modern programmer’s editor is a platform. VSCode ships with a marketplace of over 50,000 extensions. Neovim has plugin managers like lazy.nvim and an entire ecosystem of Lua plugins. Even Helix, which markets itself as minimal, ships built-in LSP support, Tree-sitter parsers, and a configuration language. The assumption baked into all of these is that the editor should grow to accommodate whatever the programmer needs, and the mechanism for that growth is an internal extension API.
Plan 9’s Acme makes the opposite bet. It ships with no extension API, no scripting language, no plugin system, and no configuration file. Rob Pike designed it this way, and the design is not a compromise or an omission — it is the point. Daniel Moch’s recent writeup touches on this, framing Acme as an alternative to terminal emulators, but the deeper story is about what happens when you expose your editor’s state as a filesystem and let the rest follow from that.
A Brief Lineage
Acme did not appear from nowhere. Its ancestor is sam, an editor Rob Pike wrote in the mid-1980s for the Blit terminal at Bell Labs. sam introduced structural regular expressions, an extension of Thompson’s regex model that lets you compose operations over text regions rather than individual lines. More importantly for what came later, sam split into two processes: samterm, which ran on the user’s display, and sam itself, which ran wherever the file lived and communicated with the terminal over a simple protocol. You could edit remote files as naturally as local ones.
Acme, which shipped with Plan 9 Second Edition around 1993-94, absorbed sam’s editing model and extended it into a full working environment. Pike described it in his 1994 USENIX paper: “Acme is not a text editor. It is a user interface for programs.” The distinction matters. Acme is a tiling window manager for text, where each window is simultaneously a document, a command prompt, and a program output buffer.
The Filesystem Is the Extension API
Plan 9’s central idea is that everything is a file accessible over the 9P protocol. Not just devices and pipes as in Unix, but network connections, process state, window system state, and editor state. Acme participates in this model by exposing its entire internal state as a mounted filesystem.
When you run Acme on Plan 9, it mounts at /mnt/acme. On modern Linux or macOS via plan9port — Russ Cox’s port of the Plan 9 userland — it appears via a Unix domain socket. Every open window gets a directory:
/mnt/acme/
1/
body -- the full text of the window
tag -- the tag line text
addr -- current address (line/character range)
data -- read/write text at addr
ctl -- write commands to this window
event -- stream of mouse and keyboard events
errors -- write here to display error output
This is the complete extension surface. There is no Acme SDK, no JavaScript engine embedded in the process, no Lua interpreter. Extension happens the same way everything happens in Unix: you read and write files. A Go formatter integrates with Acme by reading body, piping through gofmt, and writing back. A linter watches the event file for Put actions, runs its check, and writes results to errors. Any program, written in any language, debugged with standard tools, has full access to every window.
The practical consequence is visible in acmego by Russ Cox, a small tool that watches Acme events and automatically runs gofmt on Go source files after each save. It is a standalone binary, roughly 100 lines of Go. In VSCode, equivalent functionality is a JSON configuration key. In Neovim, it is a few lines of Lua calling an autocmd. In both cases, you are working within the editor’s framework, using its types and its lifecycle hooks. In Acme, you are just writing a program.
acme-lsp adds full Language Server Protocol support — hover documentation, go-to-definition, completion, diagnostics — without modifying Acme at all. It runs alongside Acme, communicates with language servers in the background, and surfaces results by writing to Acme window files. LSP support appeared in Neovim only after it was built into the editor’s core. In Acme, it was retrofitted entirely externally, years after Acme’s design was fixed, and that design required no changes to accommodate it.
The Interaction Model
Acme requires a 3-button mouse, and the interaction model is built entirely around it. Plan 9 numbers buttons 1 (left), 2 (middle), and 3 (right), each with a distinct semantic:
- Button 1: Select text. Drag to extend a selection, click to place the cursor.
- Button 2: Execute. Middle-click any text to run it as a command. If it matches a built-in (
Del,Put,Get,Undo), that action fires. Otherwise, Acme searches$PATHfor a program with that name and runs it, passing the selection or window contents as context. - Button 3: Search or open. Right-click text to search for it in the current window. Right-click a filename or address to open it, routed through the plumber.
The system also supports chording: hold button 1 to start a selection, then press button 2 to cut or button 3 to paste. This means cut and paste never require the keyboard. The tag line at the top of every window — the pale-blue strip containing the filename and built-in commands — is itself editable text. You can type any command directly into the tag line and middle-click it to execute.
There are no keyboard shortcuts to memorize beyond standard text entry. Pike’s argument was that complex key bindings are a workaround for inadequate mouse support, not a fundamental feature of editing.
The Plumber
The plumber is a system-wide message router that handles button 3 clicks. When you right-click text in Acme, a plumb message is sent describing the text and its context. Rules in $HOME/lib/plumbing determine what happens: a path opens in Acme, a URL opens in a browser, a Go error message like file.go:34: jumps to that line, a man page reference opens the man page. The rules are simple pattern matches:
type is text
data matches '[a-zA-Z0-9_\-./]+'
data matches '([a-zA-Z0-9_\-./]+):([0-9]+):'
arg isfile $1
plumb to edit
plumb client window $editor
data set $file
attr add addr=$2
This is not editor-specific. Any Plan 9 or plan9port program can send and receive plumb messages. The “open file under cursor” feature that every editor implements separately — often differently, often with edge cases around path resolution — is a single system service that works the same everywhere.
Running Acme in 2025
plan9port is the main path to Acme on modern systems. On macOS it uses Quartz directly; on Linux and BSD it uses X11. The edwood project is a Go reimplementation that is more actively maintained and has better support for modern display environments, including higher-DPI screens, without requiring X11 on macOS.
Neither option is frictionless. The 3-button mouse requirement is a real barrier on modern laptops. The absence of syntax highlighting takes adjustment. The plumbing rules require a few hours to set up well. The learning curve is not the key bindings — there are almost none — but the mental model of an editor where every window is also a program’s stdin and stdout.
What the Design Argues
Acme’s design makes a specific claim about where complexity should live. Modern editors accumulate complexity internally: the extension host process in VSCode, the Lua runtime in Neovim, the language-specific parsers in Helix. Acme externalizes that complexity into programs, which are the thing programmers are already most equipped to write and debug.
The 9P filesystem interface also has a property that plugin APIs rarely achieve: stability. The file layout of Acme windows has not changed materially since the 1990s. acme-lsp, acmego, and every tool in the ecosystem works against the same interface that existed when Pike designed the original. The VSCode extension API has gone through multiple breaking changes. Neovim’s Lua plugin ecosystem rewrote itself substantially between 0.7 and 0.10. Acme’s interface predates both and has outlasted several rounds of their churn.
The tradeoff is discoverability. There is no marketplace, no package manager, no curated extension list in a sidebar. You have to know what tools exist and how to wire them together. For someone who finds that wiring natural rather than tedious, Acme remains, thirty years after its design was fixed, a coherent environment for programming work — and a useful reminder that an extension API is not the only way to make a program extensible.