
\documentclass[12pt]{article}
\usepackage[T2A,OT1]{fontenc}
\usepackage[default]{cantarell}
\usepackage[a4paper, top=20mm, bottom=20mm, left=20mm, right=20mm]{geometry}
\usepackage[utf8]{inputenc}
\usepackage[russian, english]{babel}
\usepackage{tabu}
\usepackage{hyperref}
\usepackage{parskip}
\usepackage{graphicx}
\usepackage{tabularx}
\usepackage{float}
\floatstyle{boxed}
\restylefloat{figure}
\usepackage{setspace}
\onehalfspacing
\author{Artyom Bologov}
\date{\today}
\title{Customizing ed(2): Syntax Highlighting and rlwrap Heresy}
\makeatletter
\def\endenv{\expandafter\end\expandafter{\@currenvir}}
\makeatother
\begin{document}
\maketitle

\includegraphics[width=\textwidth,height=\textheight,keepaspectratio]{./assets/customize-ed-2.png}

\href{run:customize-ed}{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:

\begin{itemize}\item Syntax highlighting
\item “Visual” editing
\item Paredit-like structural editing
\item And IDE “functionality”
\end{itemize}

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

\section*{A Praise to Rlwrap} \label{rlwrap}

I’ve already used Rlwrap in my previous post, but only as a line editing wrapper.
\href{https://github.com/hanslub42/rlwrap}{Rlwrap is awesome and is much more than mere line editing}:

\begin{itemize}\item It allows using custom filters on input, output, prompt, history etc.
\item It supports commands and keyboard macros.
\item It has Emacs and vi presets for keyboard editing.
\item And it works with essentially any command-oriented program there might be.
\end{itemize}

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

\section*{Paredit/Bracket Operations} \label{paredit}

I’m a Lisper.
Not ashamed of it any bit.
But… ed(1) is not exactly a Lisp-aware editor.
\href{run:lisp-lines}{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:
\href{"https://danmidwood.com/content/2014/11/21/animated-paredit.html#:~:text=Wrapping an S-expression"}{Expression wrapping}, and
\href{"https://danmidwood.com/content/2014/11/21/animated-paredit.html#:~:text=Slurping and Barfing"}{Slurping} (sorry for HTTP-only links) with pure POSIX BREs:

\begin{figure}[h!]\begin{verbatim}
"\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 {}
\end{verbatim}\caption{Paredit-like operations in pure POSIX BREs as used in aed}\end{figure}

Yeah, I know.
Unreadable.
\href{run:paren-regex}{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.

\section*{Syntax Highlighting} \label{syntax}

The idea is mostly the same as in
\href{run:css-only-highlight}{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:

\begin{figure}[h!]\begin{verbatim}
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/
\end{verbatim}\caption{Some of my highlighting regex (escape might be mangled, see page source or .txt version)}\end{figure}

So it’s tokens like \verb|my-package::lambda| wrapped into bright red ANSI directive.
\href{https://codeberg.org/aartaka/aed/commit/007310b}{(Well, red on Linux, bold on Mac.)}
Some more flexibility added to support Clojure, Scheme,
\href{https://github.com/aartaka/lamber}{Lamber}…
Python, Lua, JavaScript.
And most C-family languages, in fact—the keywords don’t really differ.

This magic works via Rlwrap filters, and \verb|outfilter| in particular.
\verb|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.

\begin{figure}[h!]\begin{verbatim}
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 ...
\end{verbatim}\caption{Rlwrap command using coloring filter (simplified, see aed for full one)}\end{figure}

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 \verb|outfilter|
\href{https://github.com/hanslub42/rlwrap/blob/master/filters/makefilter}{and \verb|makefilter|}
filter construction primitives it is.

\section*{“Visual” Editing} \label{visual}

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):

\begin{figure}[h!]\begin{verbatim}
# 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"
\end{verbatim}\caption{Visual-ish editing}\end{figure}

Scrolling covers most of the necessary cases for visual editing.
The rest can be easily done with \verb|s|-ubstitutions and command history.
\href{https://merveilles.town/@aartaka/115372995573279396}{(Though I still entertain an idea of making an Rlwrap filter for line editing a-la vi.)}

\section*{IDE Now?} \label{ide}

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:

\begin{figure}[h!]\begin{verbatim}
"\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"
\end{verbatim}\caption{My IDE-like code\&prose tools}\end{figure}

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.

\section*{Use ed(1)} \label{use}

If this post didn’t prove that ed(1) is a good IDE…
Then I don’t know what will.
Use ed(1)…
\href{https://codeberg.org/aartaka/aed}{ahem, aed}, my ed(1) wrapper incorporating all these customizations.

\par\noindent\rule{\textwidth}{0.4pt}
Copyright 2022-2025 Artyom Bologov (aartaka).
Any and all opinions listed here are my own and not representative of my employers; future, past and present.

\end{document}
