XFN authoring in Emacs, part 1
In which I teach Emacs how to tab complete Atom and HTML link relations. If you like, you can follow along at home!
I’ve been a bad microformateer lately. I’ve been very lax in XFN-ifying links to people. For instance, consider my wife Erin. I should link to her like this:
<a rel="friend met co-resident spouse muse"
href="http://erin.oconnor.cx/">Erin</a>
But usually I just write
<a href="http://erin.oconnor.cx/">Erin</a>
It’s just much less effort.
Maybe some Emacs hackery is in order, to improve the likelihood
of me acually authoring with XFN. Let’s try
for XFN authoring enlightenment by making Emacs
tab-complete rel
values when in nxml-mode
(the major mode I use for authoring XML and HTML).
Tab completion of link relations
Posts in my blog backend are
represented as Atom Entry
Documents, so I write both atom:link/@rel
and
HTML’s @rel
pretty frequently.
I’d like to be able to complete for any of the relations
allowed in either place.
(It’ll be my responsibility at authoring time to remember
which is allowed where.)
The first thing to do is to collect a complete list of Atom and HTML link relations. Here’s what I’ve come up with. Note that several relations are defined in multiple places. I’ve defined these variables to hold them:
ted-html4-link-relations
ted-html5-link-relations
ted-atom-link-relations
ted-xfn-link-relations
ted-uf-link-relations
Aside: I use ted-
as a
prefix for Emacs Lisp code I write for personal use. Emacs Lisp
lacks namespaces, and it’s considered good
practice to use such a prefix. If I ever wrap up this code
into its own xfn.el
, I’ll replace
ted-
with xfn-
.
I’ve defined ted-link-relations
to contain
all of these relations, like so (You’ll have to (require
'cl)
to get
remove-duplicates
):
(defvar ted-link-relations
(remove-duplicates
(sort (append ted-html4-link-relations ted-html5-link-relations
ted-atom-link-relations ted-xfn-link-relations
ted-uf-link-relations)
'string<))
"List of Atom and HTML link relations.")
To prompt the user for a link relation with completion, you can
use completing-read
:
(defun ted-read-link-relation ()
"Read a link relation from the user, with completion."
(interactive)
(completing-read "Link relation: " ted-link-relations))
OK, that’s nice, but what I’d really like is to
prompt the user for as many relations as they’d like to
type, not just one (remember the case of Erin above).
Here’s a function which uses
ted-read-link-relation
to do this:
(defun ted-read-link-relations ()
"Read link relations from the user until they hit RET."
(interactive)
(let ((relations '())
(relation (ted-read-link-relation)))
(while (not (equal relation ""))
(push relation relations)
(setq relation (ted-read-link-relation)))
(mapconcat (lambda (x) x) (sort relations 'string<) " ")))
One optimization to make at this point is to make
ted-read-link-relations
stop right away if I enter
XFN’s me
relation. (The odds are
really good that I don’t want to enter any more
@rel
values at that point.) Let’s do this by
wrapping the while
loop in a conditional:
(defun ted-read-link-relations ()
"Read link relations from the user until they hit RET."
(interactive)
(let ((relations '())
(relation (ted-read-link-relation)))
(if (equal relation "me")
relation
(while (not (equal relation ""))
(push relation relations)
(setq relation (ted-read-link-relation)))
(mapconcat (lambda (x) x) (sort relations 'string<) " "))))
We now have the low-level bits that’ll let us augment
nxml-mode
to prompt for link relations
automatically. Let’s do that next.
Automatically asking for @rel
values while
editing
Emacs has extensive facilities for the automatic insertion of code templates while editing. The way this is usually done is to define a skeleton and bind it to an abbrev. A skeleton is a special function which does the template formatting and insertion. You can read all about skeletons in the Autotype manual, or on the EmacsWiki.
Once we have such a skeleton, we’ll bind it to an abbrev. An abbrev is a short sequence of characters that, when typed, are automatically replaced by substitution text. As with skeletons, you can read all about abbrevs in the Emacs Manual or on the EmacsWiki.
Here’s a skeleton that, when invoked, will insert
rel="whatever relations the user chose"
into the current buffer:
(define-skeleton ted-rel-expand
"Expand @rel."
nil
"rel=\"" (ted-read-link-relations) "\"")
OK, let’s make an abbrev for our new skeleton. If you
haven’t already enabled abbrev-mode
, you
should do so now. Here’s how I turn it on in my ~/.emacs
:
(setq-default abbrev-mode t)
(when (file-exists-p abbrev-file-name)
(quietly-read-abbrev-file))
Here’s the actual abbrev definition:
(define-abbrev nxml-mode-abbrev-table
"rel" t 'ted-rel-expand)
There’s a catch, though: nxml-mode
doesn’t define an abbrev table, so we’ll have to do
that ourselves. Here’s how to do that (N.B.: this
code needs to be evaluated before the call to
define-abbrev
above):
(unless (boundp 'nxml-mode-abbrev-table)
(setq nxml-mode-abbrev-table (make-abbrev-table)))
Right, so that pretty much wraps things up. We can now tab complete link relations when authoring links in Atom and HTML. This is a piece of the XFN-authoring-zen puzzle, but it’s just a piece. Next time, I’ll teach Emacs to read XFN blogrolls. Then all I'll have to do is say "link erin" and it’ll DTRT.
Would someone please DTRT and ack?
— RMS, on numerous occasions