The venerable Emacs skeleton system, while powerful, lacks documentation in some areas. For example the topic of subskeletons is only touched on with little to no examples. This post attempts to alleviate it to some degree. A basic Elisp familiarity is assumed.
Skeleton syntax reminder
As a quick reminder, this is the basic skeleton syntax:
(INTERACTOR ELEMENT...)
A trivial skeleton producing "foobar"
looks like this:
(nil "foo" "bar")
The interactor can be:
nil
(for non-interactive skeletons)- a string (an interactive prompt text)
- a Lisp expression (an arbitrary prompt, interactive or not)
The result of a non-nil interactor is bound to str
which can be used
as one of the elements:
("Name: "
"Hello, my name is " str ". Nice to meet you!")
As seen above, an element can be many things (see the documentation), but for simplicity’s sake, let’s say it can be:
- a string (which is inserted as is)
str
(invoke the interactor and insert the result)- a list (see below)
A skeleton element that’s a list is either a Lisp expression or a subskeleton. This is differentiated by its first element. If the first list element is a list or a string, the skeleton element is a subskeleton. When it’s a symbol, it’s assumed to be a Lisp function call. As of Emacs 29.4, all the other cases are invalid (technically assumed to be function calls but they are invalid as function calls).
The subskeleton syntax is almost the same as the top-level skeleton’s.
In addition to previous the possible interactor values, it can also be
a list of strings to iterate over. Subskeletons are almost always
a loop. For example a prompt-based subskeleton will get inserted as
many times, as many times we provide it with a non-empty string, each
time with the new input assigned to str
.
An element that’s a Lisp expression is evaluated and the result is interpreted as a skeleton element again.
Sounds simple but it can be confusing in practice, so let’s move on to some examples.
Examples
All the examples below will be in the following form so they can be
easily evaluated to try them out. Consider reading this post in
eww so
that C-x C-e
is readily available.
(with-temp-buffer
(skeleton-insert
'SKELETON)
(buffer-string))
Example
(with-temp-buffer
(skeleton-insert
'(nil "foo" "bar"))
(buffer-string))
The same SKELETON
can be used with define-skeleton
(just without
the outer parentheses).
A Lisp expression
(with-temp-buffer
(skeleton-insert
'(nil
(progn "foo")
"bar" "baz"
(progn '(progn "qux"))))
(buffer-string))
"foobarbazqux"
As mentioned before, each list inside a skeleton that starts with
a symbol (here: progn
), gets evaluated as Elisp and the result is
considered as a skeleton element again. Possibly recursively, as
shown with the second progn
returning a yet another
progn
expression.
Quoted Lisp expression
There is also a quoted variant of the Lisp expression skeleton element. Its result is always discarded, it’s evaluated purely for its side-effects.
(with-temp-buffer
(skeleton-insert
'(nil
'(setq v1 nil)
"foo" "bar"))
(buffer-string))
"foobar"
Note the v1
variable. It’s one of the helper variables always
available inside skeletons (no let
expressions necessary). We’ll be
using it in some examples later.
A trivial subskeleton with a nil interactor
(with-temp-buffer
(skeleton-insert
'(nil
"foo{"
(nil "aaa" "bbb")
"}bar"))
(buffer-string))
"foo{aaabbb}bar"
This is the only subskeleton form that isn’t a de facto loop and
doesn’t need to contain the str
element.
It’s also superficially identical to using concat
inside skeleton,
though this one lacks any additional skeleton-specific functionality.
a concat
example
(with-temp-buffer
(skeleton-insert
'(nil
"foo{"
(concat "aaa" "bbb")
"}bar"))
(buffer-string))
"foo{aaabbb}bar"
This syntax isn’t very useful alone, but can be used for example to return more than one skeleton element from a Lisp expression, like this:
(with-temp-buffer
(skeleton-insert
'(nil
"foo{"
(when (y-or-n-p "Hello?")
'(nil "aaa" "bbb"))
"}bar"))
(buffer-string))
Note that if a subskeleton with a nil interactor actually does contain
str
, that whole subskeleton gets discarded. This is useful if we do
intend to iterate over a list passed as an interactor, but that list
happens to be empty (i.e. nil).
A subskeleton with a string interactor (prompt)
(with-temp-buffer
(skeleton-insert
'(nil
"foo{"
("Input: " "<" str ">")
"}bar"))
(buffer-string))
"foo{<input1><input2>...}bar"
The interactor is treated as the prompt for the interactive read of
the value that gets assigned to str
. To exit the loop, enter an
empty string.
A subskeleton with a Lisp expression interactor
(with-temp-buffer
(skeleton-insert
'(nil
'(setq v1 '("aaa" "bbb" "ccc"))
"foo{"
((completing-read "Input: " v1) "<" str ">")
"}bar"))
(buffer-string))
This syntax allows to use an arbitrary Lisp expression to get the
str
value. Often a call to completing-read
or a similar function.
The loop exits when the expression returns nil or an empty string.
A subskeleton with a list of strings interactor
(with-temp-buffer
(skeleton-insert
'(nil
"foo{"
(("aaa" "bbb" "ccc") "<" str ">")
"}bar"))
(buffer-string))
"foo{<aaa><bbb><ccc>}bar"
This construct allows non-interactive looping over a list of strings,
with each one being assigned to str
in turn.
A subskeleton with a non-trivial list of strings interactor
When I started working on more complex skeletons, it quickly dawned on me that there seemingly is no syntax for a subskeleton with a list of strings interactor with the list being provided either by a variable, or some expression. The list of strings can only ever be a list literal. The following skeletons do NOT work.
This tries to call v1
as a function:
(with-temp-buffer
(skeleton-insert
'(nil
'(setq v1 '("aaa" "bbb" "ccc"))
"foo{"
(v1 "<" str ">")
"}bar"))
(buffer-string))
Symbol’s function definition is void: v1
This uses the whole return value of (reverse '("aaa" "bbb" "ccc"))
as str
which firstly isn’t a string, and secondly wouldn’t ever
become nil to end the loop:
(with-temp-buffer
(skeleton-insert
'(nil
"foo{"
((reverse '("aaa" "bbb" "ccc")) "<" str ">")
"}bar"))
(buffer-string))
Wrong type argument: stringp, ("ccc" "bbb" "aaa")
Backquotes!
It turns out there is a solution but as far as I’m aware it is not documented anywhere! The intended effect can be achieved using backquotes (aka quasiquotes).
(with-temp-buffer
(skeleton-insert
'(nil
'(setq v1 '("aaa" "bbb" "ccc"))
"foo{"
`(,v1 "<" str ">")
"}bar"))
(buffer-string))
"foo{<aaa><bbb><ccc>}bar"
(with-temp-buffer
(skeleton-insert
'(nil
"foo{"
`(,(reverse '("aaa" "bbb" "ccc")) "<" str ">")
"}bar"))
(buffer-string))
"foo{<ccc><bbb><aaa>}bar"
Post-scriptum
In some indefinite future I intend to improve the skeleton section of the actual GNU Emacs documentation. In the meanwhile this blog post is surely better than nothing.