Ed is customizable, actually.\ It can have syntax highlighting, interactive commands, and build/REPL setup.\ Enter rlwrap. assets/customize-ed-2.png 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. IMAGE_ALT

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 {}

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//&/

So it’s tokens like 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 in particular. 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 ...

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 and 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"

Scrolling covers most of the necessary cases for visual editing. The rest can be easily done with -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"

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.