Customizing ed(2): Syntax Highlighting and rlwrap Heresy

By Artyom Bologov Bright thumbnail with “custom(ed)2” in the center. The letters alternate between dark gray and maroon. The “2” floats as if it’s an exponential of the phrase before it. In the corners, attributions to “Artyom Bologov” and “aartaka.me” are written in the same alluring maroon.

Now wait, isn’t ed(1) already customizable enough? No, not enough. It’s still pretty pedestrian with all my previous customizations. I want to get real dirty with it. And you’ll watch this perversion I’m commiting:

Don’t mind the quotes, the stuff is still pretty good… for ed(1). Shall we?

A Praise to Rlwrap #

I’ve already used Rlwrap in my previous post, but only as a line editing wrapper. Rlwrap is awesome and is much more than mere line editing:

Filters and keyboard macros are particularly important for this post. You’ll see.

Paredit/Bracket Operations #

I’m a Lisper. Not ashamed of it any bit. But… ed(1) is not exactly a Lisp-aware editor. One can write Lisp in a line-effective way. And still suffer from the lack of structural editing.

No more! I implemented two Paredit operations that I use the most: Expression wrapping, and Slurping (sorry for HTTP-only links) with pure POSIX BREs:

"\C-x(": "s/\\([[:space:]]*\\)\\(\\(\\([[({]\\(\\([[({]\\(\\([[({]\\([^][(){}]\\{0,1\\}\\)*[]})]\\)\\{0,1\\}[^][(){}]\\{0,1\\}\\)*[]})]\\)\\{0,1\\}[^][(){}]\\{0,1\\}\\)*[]})]\\)\\{0,1\\}[^][(){}]\\{0,1\\}\\)*\\)/\\1(\\2)/n"
"\C-x)": "s/(\\(\\(\\([[({]\\(\\([[({]\\(\\([[({]\\([^][(){}]\\{0,1\\}\\)*[]})]\\)\\{0,1\\}[^][(){}]\\{0,1\\}\\)*[]})]\\)\\{0,1\\}[^][(){}]\\{0,1\\}\\)*[]})]\\)\\{0,1\\}[^][(){}]\\{0,1\\}\\)*\\))[[:space:]]\\{1,\\}\\(\\(\\([[({]\\(\\([[({]\\(\\([[({]\\([^][(){}]\\{0,1\\}\\)*[]})]\\)\\{0,1\\}[^][(){}]\\{0,1\\}\\)*[]})]\\)\\{0,1\\}[^][(){}]\\{0,1\\}\\)*[]})]\\)\\{0,1\\}[^][(){} ]\\{0,1\\}\\)*\\)/(\\1 \\9)/n"
# Continued for square brackets [] and curly braces {}
Paredit-like operations in pure POSIX BREs as used in aed

Yeah, I know. Unreadable. Much like my earlier experiments with matching parentheses with regex. But I guess that’s just how it is with regex 🤷 You have to trust me these work and properly wrap and devour Lisp forms.

Syntax Highlighting #

The idea is mostly the same as in my previous post on CSS-only highlighting: take delimited words matching your requirements and wrap them into styling. Except that with CSS the styling part is a separate stylesheet. But in case of ed(1) it’ll be inserted right on match:

g/\<\([[:alnum:]-]*[:\/]\{1,2\}\)\{0,1\}def[^[:space:]()]*/s//&/
g/\<\([[:alnum:]-]*[:\/]\{1,2\}\)\{0,1\}fu*nc*\(tion\)*\>/s//&/
g/\<\([[:alnum:]-]*[:\/]\{1,2\}\)\{0,1\}lambda\>/s//&/
Some of my highlighting regex (escape might be mangled, see page source or .txt version)

So it’s tokens like my-package::lambda wrapped into bright red ANSI directive. (Well, red on Linux, bold on Mac.) Some more flexibility added to support Clojure, Scheme, Lamber… Python, Lua, JavaScript. And most C-family languages, in fact—the keywords don’t really differ.

This magic works via Rlwrap filters, and outfilter in particular. outfilter allows any shell command/script/executable to act on wrapped command output. As if this output is mere standard input for the filter script.

cat ~/bin/aed_coloring
=> touch -f ~/.aed_coloring
=> cat <&0 > ~/.aed_coloring
=> cat ~/.local/share/aed/coloring.ed | ed -s ~/.aed_coloring
=> rm ~/.aed_coloring
rlwrap -z "outfilter aed_coloring" ... ed -p ...
Rlwrap command using coloring filter (simplified, see aed for full one)

There are many many many types of filters in Rlwrap And it’s possible to write more. But they require Perl, which I’m not exactly ready to touch. So existing outfilter and makefilter filter construction primitives it is.

“Visual” Editing #

Now this one might be an exaggeration. But still, I’ve configured my ed(1) to be instantaneous and visual for some operations. With these keyboard macros (mapping one sequence of keys to another one):

# Page scrolling
"\C-xp": "-15,-1n\C-m-14n\C-m"
"\C-xn": "+1,+15n\C-m"
# Quick quit 😜
"\C-xq": "Q\C-m"
# Quick (and smart) indent
"\C-x\C-i": "s/^[ ]\\{0,2\\}[	]\\{0,1\\}/&&/n"
Visual-ish editing

Scrolling covers most of the necessary cases for visual editing. The rest can be easily done with s-ubstitutions and command history. (Though I still entertain an idea of making an Rlwrap filter for line editing a-la vi.)

IDE Now? #

We have syntax highlighting. We have convenient file movement. We have structural editing. Almost an IDE we have here! What’s missing is numerous build and REPL tools. I have these, actually:

"\C-xg": "w\C-m!git fetch; git pull; git status -vv\C-m"
"\C-xc": "w\C-m!EDITOR=aed git fetch && git pull && git add -p % && git commit -e\C-m"
"\C-xa": "w\C-m!aspell -H --warn --suggest --camel-case -c %\C-m"
"\C-xi": "!indent -linux --procnames-start-lines %\C-m"
"\C-xm": "!make clean ; make all -j 4\C-m"
"\C-xl": "!lisp ecl --load *.asd ; cd .. ; lisp ecl --load *.asd ; cd .. ;  lisp ecl --load *.asd"
My IDE-like code&prose tools

So I have git client, spell checker, C auto-indentation, Lisp REPL, and Makefile builds. That covers most of my IDE needs really. What’s left is an LLM interface I guess? No, not right now, thank you.

Use ed(1) #

If this post didn’t prove that ed(1) is a good IDE… Then I don’t know what will. Use ed(1)… ahem, aed, my ed(1) wrapper incorporating all these customizations.

Leave feedback! (via email)