You may want to inform yourself about human rights in China.

On Plan9's Acme

date: 2022-12-21
update: 2024-07-21

Tools shape one’s perceptions and thinking. Programming languages constitute a common example: consider for instance how regular iteration encourages a certain mindset, while recursion coupled with purity encourages different trains of thoughts.

What is true with languages remains true, for instance with text editors. And is maintained outside of the programming realm.

A side-effect is that, once we’re comfortable with a given way to achieve a certain result, it can be psychologically demanding to switch to another approach.

As an example, however great debuggers are, not having a debugger forces you to read code more carefully, and to think more. Perhaps then, it’s not much of a loss not to have a debugger in the long run (over decades). But that doesn’t mean debuggers should be absolutely avoided either.

In particular, acme(1) encourages a peculiar mindset, by comparison with mainstream editors. I’ll try in this article to explore this difference, while highlighting a similar dichotomy in woodworking.


Why?

Being more mindful of such a situation can help to overcome difficulties arising when learning a new arbitrary tools.

In the wood

As already stated, what holds true in programming also holds true for other disciplines. Woodworking is interesting because it’s a much older discipline than programming, and thus, it has had the time to be refined: at mankind’s scale, software programming is but in its infancy.

Consider woodworking planes (again): here are a bunch of Japanese planes: (their designed didn’t really evolved with the industrial revolution; Western’s did):

Takenaka Carpentry Tools Museum in Kobe, Hyogo prefecture, Japan. Archetypal Japanase plane, essentially made out of a piece of metal wedged into a single block of wood

Takenaka Carpentry Tools Museum in Kobe, Hyogo prefecture, Japan. Archetypal Japanase plane, essentially made out of a piece of metal wedged into a single block of wood by 663highland through wikimedia.orgCC-BY-SA-3.0

So, what’s a Japanese plane I ask you? You may answer, well, a piece of sharpened metal stuck in a squareish piece of wood. But you have good eyes, so you’ll notice that there there are two flat pieces of metal, held together by a little metallic cylinder.

It’s likely that even if you were given the opportunity to hold one for some time, you wouldn’t be able to find much more to say about them. If you a bit hackerish, you may observe that they would make a decent ad-hoc square.

A Japanese plane is a really a fine tool: some of its key characteristics aren’t loudly advertised, nor easily perceptible to the uninitiated. There’s not much to look at, but everything has been carefully designed, likely refined over centuries by master crafstmen:

If acme(1) is comparable to a Japanese plane, then mainstream editors are closer to industrial thickness planers. This should motivate enough the fact that learning to use an editor as dry as acme(1) (or sam(1)}) can be demanding, from the point of view of someone used to mainstream editors.

Thickness planer for woodworking

Thickness planer for woodworking by Veba Meccanica SRL through wikimedia.orgPublic domain

Good enough; reactive, stable

Acme’s not perfect. But, as Unix once was, it is, for better or for worse, good enough. Some elements could be refined, or at least experimented with.

Acme’s fast: no syntax highlighting, a small C codebase, a text-only interface, fixed features set (⇒ almost no updates), low resources usage.

Note: I’ve had a few crashes with Edit X// (a way to iterate on multiple windows), but I rarely need to use it anyway, and when I do, I’ve got a few tools for it.

Discoverability: meh

The editor was built in an isolated, research setting, a few decades ago (before Internet as we know was a even thinkable, as is evident from the design of 9P, Plan9’s backbone). You can try to toy with the editor on your own, but it hasn’t been built with a regular, modern user in mind, and so likely, you won’t get far.

Instead, it comes with a few well-written manual pages, and some research papers:

As acme(1) borrows a few things from sam(1), the two following papers are worth studying as well.

You have to read those. Because acme(1) is now more broadly use, you can find additional content online, but however useful, they will never be able to compensate for the man pages/papers. This is how the system was originally documented and used.

Mouse

I’ve never used acme(1) with a 3 buttons mouse (perhaps once or twice, at most). I used to have a clickable wheel a few years ago, and this worked just fine. I’ve been on a Thinkpad for around 8 years, so there are three clickable “mouse buttons” right above the touchpad, under my thumbs. This is just as great, perhaps even better.

That is to say, a fancy mouse isn’t mandatory.

Edition speed

Learning to use a new text-editor requires to adjust multiple habits at once. Furthermore, acme(1) often demands a considerable mindset shift.

So it’s likely that one will be slow to operate acme(1) at first. In the long run, I’m not so sure: as far as I’m concerned, what’s most time-consuming is thinking, debugging, testing. Not editing/typing code. To say it otherwise, the time gained by optimizing edition is negligible in front of the time spent thinking, designing and testing code (premature optimization then).

A text editor shift will almost always result in an alteration of the workflow. While I barely have that feeling anymore (I only do when I’m rushing), I still don’t think it’s a bad thing that your editor forces you to slow down.

It’s often a good idea actually.

Syntax highlighting

Just say no” / “Plan nein”.

The choice is pragmatic: the tangible benefits of syntax highlighting are meager, compared to its cost:

This is furthermore true if we highlight the context: a text-editor, developed in a research setting, a few decades ago, in a team involving what should come close to the most advanced Unix users (its literal creators).

Dennis Ritchie at his desk, with an acme session

Dennis Ritchie at his desk, with an acme session

I don’t mean to say that syntax highlighting is useless: besides providing a more colorful experience, it can be semantically useful, for instance to automatically detect visually missing opening/closing parenthesis/brace/brackets. But again, as far as I’m concerned, I don’t mind being a bit more cautious. That last use-case could also be resolved with a small little parser.

win(1)

win(1) (documented in acme(1)) is an example of a sophisticated C program interacting with the editor via the 9P API (documented in acme(4)). It allows to open shells (thus REPL, ssh(1) sessions, etc.) within an acme(1) buffer. Nothing outstanding, but definitely useful.

Moving around

The standard UNIX shortcuts (^A, ^E, ^U, ^W ^H; I almost ever use ^H), plus arrows, all allow for decently smooth “local movements”. For global movements, or increased precision, the mouse/touchpad (and the following tips!) do just fine. The scrollbar has some refined features in this regard too, but I barely ever use it.

Tip: One thing I learnt when trying to use ed(1) for a while (out of curiosity, but it’s proven to be a valuable experience in many ways), is that to move around in a big document, you have “natural anchors” formed by essentially unique text patterns (rare words/sequences of characters/regexp). They constitute a very convenient, cheap way to move around quickly, editor-agnostic.

Tip: I don’t think it’s clearly documented, but the home/end keys will by default send you to the previous edition location. Meaning, I can edit text at some spot, scroll around to look at something, click here or there, and then use home/end to get back at the previous editing location.

Or, you can avoid clicking around, and simply use the arrows to refocus the windows to the cursor’s position.
Or, write/comment/observe unique pieces of text to anchor yourself.
(You may want to try those)

Vim’s marks on a budget.


Here’s the two videos highlighting some differences in blade adjustment, between a modern Western plane and Japanese. Essentially, especially on high-end planes, there are knobs to adjust every setting of a Western plane; mind the simplicity of the traditional Japanese approach.

Sam’s language

sam(1) is an earlier Plan9 editor, also written by Pike, best described as an “ed(1) on steroid”: it

Most of the time, you can get away without using sam(1)’s language. I’d say, 95% of automated edition tasks I have to do can be performed via sed(1), awk(1), or sh(1), which are convenient to call from acme.

There are two main exceptions. One of them is the variable renaming idiom (that I’ve actually wrapped in a shell script, as I’m too lazy to count the dots). The other is when I need to edit arrays of (dozens/hundreds of) hashes of indented JSON-like formats (e.g. Data::Dumper).

The former idiom is covered in those papers:

Here’s an example of the latter use-case:

Example: The goal of the following command is to columnize (via qcol, which understands quoted strings and is able to preserve indentation) a series of lines located between two specific pattern in a file. A key concept in sam(1)’s language is the notion of dot, which can be understood as “the currently selected text”. The novelty in comparison to other Unix tools is that this dot can spawn multiple lines.

Edit ,x/\[\]token{\n/+,/^[	 ]+}, nil},/- | qcol -k -n
	...

		{
			"single byte tokens",
			scanAll,
			[]interface{}{strings.NewReader("  \t\t\r\n().  :<× >"), ""},
			[]interface{}{[]token{
				token{tokenLParen,   2,  1, "(", 0, 0., nil},
				token{tokenRParen,   2,  2, ")", 0, 0., nil},
				token{tokenDot,      2,  3, ".", 0, 0., nil},
				token{tokenColon,    2,  6, ":", 0, 0., nil},
				token{tokenLChevron, 2,  7, "<", 0, 0., nil},
				token{tokenProduct, 2,  8, "×", 0, 0., nil},
				token{tokenRChevron, 2, 10, ">", 0, 0., nil},
				token{tokenEOF,      2, 11, "",  0, 0., nil},
			}, nil},
		},
		{
			"multi-bytes words",
			scanAll,
			[]interface{}{strings.NewReader("hello world"), ""},
			[]interface{}{[]token{
				token{tokenName,   1,  1, "hello", 0, 0., nil},
				token{tokenName,   1,  7, "world", 0, 0., nil},
				token{tokenEOF,    1, 12, "", 0, 0., nil},
			}, nil},
		},

	...
	...

		{
			"single byte tokens",
			scanAll,
			[]interface{}{strings.NewReader("  \t\t\r\n().  :<× >"), ""},
			[]interface{}{[]token{
				token{tokenLParen,   2, 1,  "(", 0, 0., nil},
				token{tokenRParen,   2, 2,  ")", 0, 0., nil},
				token{tokenDot,      2, 3,  ".", 0, 0., nil},
				token{tokenColon,    2, 6,  ":", 0, 0., nil},
				token{tokenLChevron, 2, 7,  "<", 0, 0., nil},
				token{tokenProduct,  2, 8,  "×", 0, 0., nil},
				token{tokenRChevron, 2, 10, ">", 0, 0., nil},
				token{tokenEOF,      2, 11, "",  0, 0., nil},
			}, nil},
		},
		{
			"multi-bytes words",
			scanAll,
			[]interface{}{strings.NewReader("hello world"), ""},
			[]interface{}{[]token{
				token{tokenName, 1, 1,  "hello", 0, 0., nil},
				token{tokenName, 1, 7,  "world", 0, 0., nil},
				token{tokenEOF,  1, 12, "",      0, 0., nil},
			}, nil},
		},

	...

Here’s how it works:

Indentation is a rather basic task; note that we could also have used awk(1), but this would have been more verbose to write. We could have performed more sophisticated edition tasks, such as conditionally adding/removing lines/fields, either via sam(1)’s language or with any random shell command.


Workbenches are a key tool in woodworking. Here’s two videos highlighting some differences between a Japanese floor workbench and a regular Western bench (note the absence of a vise in the Japanese case, and ponder on the inherent difficulty of building a vise from scratch).

Typing unicode characters

A somewhat hidden feature: keyboard(7) describes a way to input “arbitrary” unicode characters (emphasis is mine)

Characters in Plan 9 are runes (see utf(7))). Any rune can be typed using a compose key followed by several other keys. The compose key is also generally near the lower right of the main key area: the Option key on the Mac and the Alt key on Unix systems. To type a single rune with the value specified by a given four-digit hexadecimal number, type the compose key, then a capital X, and then the four hexadecimal digits (decimal digits and a to f). For a longer rune, type X twice followed by five digits, or type X three times followed by six digits. There are shorthands for many characters, comprising the compose key followed by a two- or three-character sequence. The full list is too long to repeat here, but is contained in the file /usr/local/plan9/lib/keyboard […]

Here’s an excerpt from that last file:

08D  b(          ₍	subscript opening parenthesis
208E  b)          ₎	subscript closing parenthesis
20AC  e$          €	euro symbol
2102  CC          ℂ	double-struck capital c
210A  $g          ℊ	script small g
210B  $H          ℋ	script capital h
210D  HH          ℍ	double-struck capital h
210F  -h h-       ℏ	planck constant over 2 pi

For example, if you want to type ℏ, you have, in that order to:

  1. press/release ALT (the compose key)
  2. press/release “-”
  3. press/release “h”

Or:

  1. press/release ALT (the compose key)
  2. press/release “h”
  3. press/release “-”

Improvements ideas

Here’s a rough set of ideas to “improve” the editor. Bear in mind that this is from the point of a view of someone using acme(1) on Unix; this wouldn’t make as much sense on Plan9.

  1. 9P is close to useless on an Unix system (plumber(1) aside, maybe; sometimes, I wonder is 80% of what I’m using/could use the plumber(1) for, again on Unix, not on Plan9, isn’t better performed by a boring sh(1) script with a series of if/else): some RPCs over a named pipe/socket would be easier to work with;
  2. The editor could then be fully programmatically controllable;
  3. The tagline could be fully editable (I’ve found the restriction almost always useless);
  4. Building on the previous points, we could then have all commands (Del, Snarf, etc.) be external executables;
  5. Perhaps delegating the window management to, well, a window manager. This would require some more thinking, especially given the tagline/buffer interaction;

In many ways, the result would start to look like a sam(1) with chording patches & other goodies. Which means, another approach could perhaps be to start from sam(1)’s codebase instead of from acme(1)’s.


Comments

By email, at mathieu.bivert chez:

email