I have used text editors for more than 20 years; in all that time, I havenever used a templating system to generate content because copy pasting andreplacing always felt good enough. I recently decided to give it a try.
In Emacs it is hard to talk about templating without mentionningYASnippet. Developped by theprolific João Távora, who also developpedEglot, YASnippet lets you writetemplates as text documents.
But while I was researching the subject, I found out that Emacs already hadtwo builtin templating modules:SkeletonandTempo.
Tempo looked simpler, so I decided to give it a chance.
Tempo templates are functions defined with tempo-define-template. Thecontent to be inserted is a S-expression containing various kinds of elementswhich control the insertion process.
As an example, let us define a template to insert a HTML figure.
First we define a variable to store a list of templates. When usinghtml-mode, we instruct Tempo to use it.
(defvar g-html-tempo-tags nil)(defun g-init-html-tempo-templates () (tempo-use-tag-list 'g-html-tempo-tags))(add-hook 'html-mode-hook 'g-init-html-tempo-templates)
Then we define the template itself:
(tempo-define-template "g-html-figure" '("<figure>" n > "<img src=\"" (p "URI: ") "\">" n > "<figcaption>" (p "Caption: ") "</figcaption>" n "</figure>") "figure" "a figure containing an image and caption" 'g-html-tempo-tags)
This form creates a function named tempo-template-g-html-figure. When it iscalled, Tempo processes elements of the template:
n symbol causes the insertion of a new line character> symbol tells Emacs to indent the current line according to the rulesof the major mode of the buffer.p forms cause Tempo to ask the user for values to insert. Note thatyou need to set tempo-interative to t.Note that Tempo supports more elements; refer to the documentation string oftempo-define-template for more information.
The template is associated to the figure text tag which will be used forcompletion. We also add a description, and finally add the template to theg-html-tempo-tags list. Note that these three last arguments are optional.
Of course we do not have to call the template manually. When we calltempo-complete-tag, Tempo uses the string before the cursor to decide whichtemplate to insert. Open a HTML buffer, type figure and executetempo-complete-tag (I bind it to M-S-<tab>): Tempo will automaticallyinsert our template.
Tempo will handle the case where there is partial match for multipletemplates and will spawn a completion buffer.
Now it would make sense to use <figure> as tag. To do so, update theg-init-html-tempo-templates function to set the local variable that Tempouses to detect a tag:
(setq tempo-match-finder "\\(<[a-z]+>\\)\\=")
Doing so will use HTML tags as Tempo tags. We can then alter the call totempo-define-template to use <figure> as tag, and from now on use it fortemplate insertion.
Of course this means we can customize it to allow different formats of Tempotags depending on the major mode we are in. Handy.
Calling template functions manually is unpractical and tag completion requiresremembering which tags have been defined. My interface of choice would be asimple key spawning an incremental completion buffer(Helm in my case) to let me select atemplate to insert.
As it turns out, it is not that hard:
(defun g-insert-tempo-template () (interactive) (let* ((tags-data (mapcar (lambda (entry) (let ((function (cdr entry))) (list function (documentation function)))) (tempo-build-collection))) (completion-extra-properties `(:annotation-function (lambda (string) (let* ((data (alist-get string minibuffer-completion-table nil nil #'string=)) (description (car data))) (format " %s" description))))) (function-name (completing-read "Template: " tags-data)) (function (intern function-name))) (funcall function)))
As we have already seen in a previouspost,completing-read is quite limited in terms of presentation. I will probablyspend some time switching from Helm to a mix ofVertico andMarginalia which apparently offers moreoptions.
This will do the job in the mean time.
Tempo is reasonably satisfying. While it is a very simple module, it lets medefine templates as Emacs Lisp expressions, associate them to tags and storethem in tag lists which can be used in the major modes of my choice.
I do not expect to start using dozens of tiny templates for the simplestconstructions, but Tempo is going to help with recurrent complex constructionssuch as Emacs moduleskeletonsor Common Lisp systemdefinitions.