🏠 About Now Archive Tags RSS

Emacs' built-in tempo mode for templates (snippets)

A reason to use LLMs that is often repeated is that they can write boilerplate which speeds up the programmer. Why not use templates or snippets instead? They use far less energy and do not contribute to climate change or nuclear waste unlike LLMs, they don't hallucinate, you don't have to give US$ 200 a month to Microsoft to get half-decent results, they don't ruin the internet with scraping, they don't ruin text based browsing, you know the output is correct since you wrote it yourself, you will never get sued for using your templates or snippets unlike LLM code whose legal status is questionable because of stolen, non-licensed and copyleft licensed training data, and you waste less time trying to "engineer" a prompt that will yield a usable result. There is some work up-front writing the templates or snippets, but you do that once and reap the benefit for as long as you use the same editor or IDE. I anticipate using GNU Emacs for the rest of my life since it has already been around for 40 years (or 52 if you count MIT Emacs) and is still developed for and by a vibrant free software community, unlike most IDEs and text editors which seldom survive more than a decade or two.

I have been thinking for a while that I would check out the snippet package Yasnippets which is recommended by a lot of Emacsers. I discovered tempo recently by stumbling upon Thomas Ingram's website. Tempo is a built-in template or snippets package in Emacs without any built-in documentation (unlike most Emacs packages). There is an info manual on David Kågedal's homepage and some examples of its use and if you follow the link to Ingram's site, there is also some information there. The documentation is very readable when you find it. I tend to prefer built-in packages unless there is functionality only available in external ones that I want or need, and tempo looked like it had many interesting features, so I tried it.

There are two main ways of using tempo. Either you insert a template into a buffer and it can ask you interactively in the minibuffer for things to fill in into the template, like for instance names of function, type annotation or variables and the like. To use it this way, set tempo-interactive to t. The default way of using it will instead insert the template into a buffer and then use the command tempo-forward-mark to go to the next place in the template where you should fill in some value. There is also a tempo-backwards-mark to go back if you change your mind about something you already filled in.

There is also a third way where you mark a region, and use the universal argument before calling the function with the name of your template. You can use this whether tempo-interactive is t or nil. This includes the region in the part of the template with the r keyword and then either prompts you in the minibuffer for things to fill in or you do it with tempo-forward-mark and tempo-backward-mark depending on your setting of tempo-interactive. This may be useful if you want to wrap some code in a function definition, a class definition, a loop or an if-statement etc.

To me, the default "non-interactive" way of using it seems the fastest, so I set tempo-interactive to nil. Since I do not anticipate that I will switch to doing it that way in the future, I wrote my templates without prompts for the minibuffer. Whenever there is a p or an r, you can wrap them in parenthesis and add a string which will become the minibuffer prompt if you use tempo with tempo-interactive set to t, but this is optional and not needed if you, like me, use it the default way.

There are also different ways to insert a template into a buffer. Every template becomes a function you can run interactively with M-x with the name "tempo-template-" and the name you have given your template. The other way of invoking it is to write a tag and then run the function tempo-complete-tag which replaces the tag with the template. Thomas Ingram also writes that you can use abbrev mode to insert a template with the help of its function name, but since I have yet to learn abbrev mode, I thought using tags and binding the function to replace them with templates would be fastest for me. It also avoids having to configure tempo templates another place in the config, so even if/when I learn abbrev mode, I may continue to use it this way.

I have my own keymap using the menu key which I have rebound my CapsLock to on my GNU/Linux machines, and which I have as a separate key next to Alt gr on my Windows 11 work laptop. I could have bound Caps to Hyper on GNU/Linux, but then I would not be able to use that keymap on Windows without a lot of setup. I have set menu-n to tempo-forward-mark, menu-p to tempo-backward-mark and menu-a to tempo-complete-tag. To get a template inserted, I write the tag and hit menu-a. Then I hit menu-n to go to where the next p or r is in the template and fill it in.

This way of using tempo seems very efficient so far, as long as the tag names are short enough. I thought it was a good idea to start tags related to Python with py, Elisp with el, JavaScript with js, C with c etc to keep them short. The challenge is probably to remember the tags, but as with keybindings, the ones I use often will become second nature. Since every template becomes a function with a name starting with "tempo-template-", finding templates if you cannot remember their tags is easy through M-x and completions.

The first template I made was a template to create tempo templates with the name "definition" so the function name would be "tempo-template-definition" and with the tag tempo. This template makes it easier for me to create future templates. So far I have only made two for function definitions in Python and Elisp, but I will make more and more complex ones over time. I think tempo will increase my tempo when I have written enough templates to speed up writing boilerplate. And since I use Emacs for a lot more than programming, I may use tempo for boilerplate in other text as well.

In the code block below, you can see how I have configured tempo thus far. To not slow down Emacs start-up I don't ensure that it is loaded, but load it on demand when the function tempo-complete-tag is run (which I do with the keypress menu-a after writing a tag for a template).

(use-package tempo
  :ensure nil
  :commands tempo-complete-tag
  :config
  (setq tempo-interactive 'nil)
  (tempo-define-template "definition"
                         '("(tempo-define-template \"" p "\" '(\"" r "\") \"" p "\" \"" p "\")")
                         "tempo" "A tempo template to define tempo templates.")
  (tempo-define-template "python-def"
                         '("def " p " (" p ": " p ") -> " p ":\n"
                           "    \"\"\"\n    " p "\n    \"\"\"\n    " r)
                         "pydef" "Create a Python function definition.")
  (tempo-define-template "elisp-defun"
                       '("(defun " p " (" p ")" n> "\"" p "\"" n> r ")")
                       "eldefun" "Create an Elisp function definition.")
  )
© Einar Mostad 2010 - 2025. Content is licensed under the terms of CC BY SA except code which is GNU GPL v3 or later.
Made with GNU Emacs, Org-Static-Blog, and Codeberg Pages on GNU/Linux.