Customizing ed(2): Syntax Highlighting and rlwrap Heresy
By Artyom Bologov
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:
- Syntax highlighting
- “Visual” editing
- Paredit-like structural editing
- And IDE “functionality”
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:
- It allows using custom filters on input, output, prompt, history etc.
- It supports commands and keyboard macros.
- It has Emacs and vi presets for keyboard editing.
- And it works with essentially any command-oriented program there might be.
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//[91m&[0m/
g/\<\([[:alnum:]-]*[:\/]\{1,2\}\)\{0,1\}fu*nc*\(tion\)*\>/s//[91m&[0m/
g/\<\([[:alnum:]-]*[:\/]\{1,2\}\)\{0,1\}lambda\>/s//[91m&[0m/
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 ...
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"
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"
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.