If you’re unfamiliar with Acme, Russ Cox has recorded an excellent demonstration. This is not an introduction to Acme, nor is it a setup guide. But if you’ve tried or seen or read about Acme and were wondering how people could possibly be productive with it, this might help point you in some direction. Or it might not, but it is how I use it these days.

Since my main computer doesn’t run Plan 9, I use Acme from Plan 9 from User Space, a port of large portions of the Plan 9 user space to Unix, with a handful of customization. You can find some of these customizations in the branches of my fork, and a merged version of the ones I currently use in my “current” branch.

When working on a project I usually start by opening its guide file, which is a collection of shell and Acme commands that are useful for this project. The original Acme paper contains a few examples. From there I use xplor, t and g to navigate.

T is a fuzzy finder; it generates a list of files and filters that using some fuzzy search (currently fzf). Where in other environments one would use ⌘T to open a dialog, enter “sometexttofilterwith”, then select and open the results, I 2 (middle-click) “t sometexttofilterwith”, then 3 any of the results to open them.

G is a content search tool like grep (currently ripgrep), configured to search recursively. It replaces the “Find in Project”-style search found in other environments.

The most straightforward way to navigate using external programs is by making sure they write positions in the traditional format $filename:$line or $filename:$line:$column . This lets Acme users 3 (right-click) on a position to open that file and select that line or column, essentially turning all of those positions into hyperlinks. Of course, I configured both t and g to do this.

Acme exposes its state as a filesystem. In this filesystem there is a directory corresponding to each open window, and external programs can use the files in this directory to manipulate things like the window contents or selection. It also contains a log file that emits events generated in that window by the user.

When an external program opens this log, Acme assumes that it will take over handling any interaction in the window. Programs that do this are called “external client programs” in the documentation, and xplor is an example of one.

Xplor is a tree-style file explorer for Acme that was originally written by Mathieu Lonjaret and that I massively rewrote. Taking over event handling for the window lets us override 3 on directories, say, to toggle whether xplor displays the contents of that directory.

This makes xplor useful for drilling down into familiar or exploring unfamiliar directories, and given the deep directory hierarchies that are common these days it feels less cumbersome to use than Acme’s native directory editing, which opens every subdirectory in its own window.

External programs don’t need to handle events though, as both acmepipe and acmeeval demonstrate.

Acmepipe filters the window contents through some command. It works like | inside Acme, but it doesn’t clear the window contents when the command fails, it can apply the changes in separate chunks, and we can call it from outside of Acme.

Acmeeval programmatically runs commands in Acme’s interactive language, possibly from outside of Acme. It works by adding the commands to the tag (the “menu” above each window) and generating an execution event for their region in the tag.

When I start Acme, I also run

autoacme ~/bin/acme-editorconfig autoacme ~/bin/acme-autoformat

(except in the background) so Acme respects editorconfig settings and automatically runs formatters and linters.

Autoacme is similar to an external client program, but instead of a window log it reads events from the global Acme log and runs a command for each one it receives.

Acme-editorconfig handles new events, which happen when Acme opens a new window. It runs editorconfig on the window’s file, translates the output into Acme commands, and uses acmeeval to execute them. This requires the “soft-tabs” patch described below.

Acme-autoformat handles put events, which Acme generating when saving files. It determines the type of the saved file, runs the configured formatter on it, and updates the window content with the result using acmepipe. For types without a fitting formatter, it runs a linter instead.

Ideally I’d like to run both a linter and a formatter, but I’m not sure how to make that work: The formatter updates the window contents, so running it after the linter means that the line numbers in the lint report could be off, but running it before the linter means the linter would have to read if from stdin so its report would be missing the file name.

The acme/soft-tabs branch adds “soft tabs” support to Acme.

When this mode is active, Acme emulates soft tabs by having the ⇥ key insert $tabwidth space characters, and the ⌫ key delete as many space characters. Without it, Acme only ever inserts single tab or space characters.

The change originates in 9front, a modern fork of Plan 9, and was ported by neutralinsomniac. It has been rejected upstream but is very useful when working on codebases where the authors expect indentation using spaces.

There are still more tools for working with Acme, some of which I haven’t had a chance to use yet, including a language server protocol implementation!

If you are excited about trying Acme, or if you are already using it, or if you know other Acme-related tools I should try, or if I got anything wrong, or for any other reason at all, please don’t hesitate to get in touch!

Header photo by Ruud Luijten on Unsplash.