Emacs auto-insert

The Emacs manual has a section called Autoinserting Text in Empty Files which explains how to insert something akin to a template when opening a file. I tried it recently and document my steps and understanding below. I primaily used the Emacs manual and a post from https://howardism.org/ titled Having Emacs Type for You.

First, I needed to turn on auto-insert-mode:

(auto-insert-mode t)

Then the simplest thing I tried was to define the variable auto-insert-alist based on reading its documentation. (This variable already exists in a default Emacs installation but more on that later.)

This variable is a special list that takes either of two forms:

(CONDITION . ACTION)                    ; or
((CONDITION . DESCRIPTION) . ACTION)

The documentation describes these terms:

CONDITION
(a) a regular expression that must match the new file's name, or (b) a symbol that must match the major mode for the new file
ACTION
(a) an absolute filename, (b) a filename relative to auto-insert-directory, (c) a skeleton to insert, or (d) a function to call.
DESCRIPTION
is a string for filling auto-insert-prompt (optional)

In the next three sections I document ACTION being based on an absolute file path, a path relative to a directory, and a skeleton.

Inserting a file specified with an absolute path

The text below is the contents of a template in the form of a file called auto-insert.R which is stored in my home directory.

########################################
## An R header from my home directory ##
########################################

Next is a very simple auto-insert-alist dealing only with R files that will insert this header when I create a new R file. The regular expression is meant to match a filename that has a literal period followed by either R or r followed by the end of the string. (I believe $ denotes the end of a line in Emacs regular expression syntax so is not used here.)

(setq auto-insert-alist '(("\\.[rR]\\'" . "~/auto-insert.R")))
                                        ;; ^ absolute path

Now, when I open a new R file I am asked whether I want to perform auto-insertion. So that is working.

Setting auto-insert-query to nil means I won't be prompted before inserting the template:

(setq auto-insert-query nil)

Inserting a file specified relative to auto-insert-directory

I can also put my various auto-insert files in a common directory and tell Emacs about this directory. Here is my R template in ~/.emacs.d/auto-insert/auto-insert.R:

###########################################################
## An R header from my ~/.emacs.d/auto-insert directory ###
###########################################################

I next needed to tell Emacs which directory I used. (I am using Linux and the documentation for auto-insert-directory says I need to have the directory end in a slash so that it is explicitly an absolute path to a directory.)

(setq auto-insert-directory "~/.emacs.d/auto-insert/")
                         ;;  ^^^  absolute path  ^^^
(setq auto-insert-alist '(("\\.[rR]\\'" . "auto-insert.R")))
                                        ;; relative to dir

Here is the same as above but with the DESCRIPTION functionality.

(setq auto-insert-alist
      '((("\\.[rR]\\'" . "My auto-insert for R files") .
         "auto-insert.R")))

Insertion based on invoking a skeleton

The documentation says ACTION can be an Emacs skeleton. Skeletons have a special Skeleton syntax and are a wild mix of Emacs lisp, constants, strings, magic, etc.

To understand a moderately complicated skeleton, here is the built-in LaTeX mode element of auto-insert-alist. (This is one element of a much longer auto-insert-alist.) The CONDITION is not a filename regular expression but a symbol that matches a major mode.

The logic is as follows:

  • Prompt for document class options and handle none if applicable
  • Prompt for document class
  • Prompt for package options and handle none if applicable
  • Prompt for package name
  • Repeat prompting for package options and package name until done
  • Add the begin and end document tags
(latex-mode             ; I don't know why there is no `.'
"options, RET: "        ; first prompt for documentclass options

;; If anything entered at first prompt then add a closing char 93
;; (`]') otherwise back up one space to delete the opening square
;; bracket and then type char 123 (`{')
"\\documentclass[" str & 93 | -1 123

;; this is the 2nd prompt which asks for and reads the document class  
(read-string "class: ")

"}\n"

;; This is a subskeleton, a complete skeleton in a skeleton.
;; It prompts for package information and repeats until the
;; user has no knew packages to enter.

;; Third prompt. The `%s' is linked to `RET' I think
("package, %s: "  
 "\\usepackage["  

;; read the package options
 (read-string "options, RET: ") 

;; Similar to above different so confusing
 & 93 | -1 123 str "}\n")
         _ "
\\begin{document}
" _ "                  
\\end{document}")

I wanted to do something similar and ended up with this skeleton.

(setq auto-insert-alist 
      '((("\\.[rR]\\'" . "My Skeleton template for R files") 
nil ;; No prompt string functionality initially

"## Who:  " (progn user-full-name) "\n" ; grap user's name

;; Prompt for and enter a short description
"## What: " (read-string "short description: ") "\n"

;; Add date like 'December  5, 2025' but if two spaces between
;; month and day of month zap one space
"## When: Started " 
(replace-regexp-in-string "  " " " (format-time-string "%B %e, %Y"))
"\n"

;; Prompt for and enter a short purpose. If something is entered
;; add a char 10 (return). If nothing is entered back up 9 spaces
;; which zaps `## Why:  '
"## Why:  " (read-string "short purpose: ") & 10 | -9

;; Prompt for and enter how the program works using the same logic
"## How:  " (read-string "how it works: ") & 10 | -9

;; This is a one-line subskeleton for looping through packages to
;; add. Character 41 is close paren. The skeleton interpreter
;; handles both no packages and multiple packages.
("package: " "\nlibrary(" str & 41)
"\n"
_                                       ; Cursor here

;; Define the `compile-command' so it's "active" initially.
;; The single quote at the start of the expression tells auto-insert
;; to evaluate the expression and not insert anything
'(setq compile-command (format "Rscript --no-save --no-restore \"%s\""
                              (file-name-nondirectory buffer-file-name)))

;; Specify file local variables at bottom of file, writing out
;; the compile command.
"\n\n\n\n\n"
"## For Emacs users:\n"
"## Local Variables:\n"
"## compile-command: \"Rscript --no-save --no-restore "
(file-name-nondirectory buffer-file-name)
" \"\n"
"## End:\n")))

The above is working and it was probably faster for me to try to learn some Skeleton syntax than me writing an Emacs lisp function to do the same. But a skeleton is not easy to read and an Emacs lisp function may be a better route.

(To see the state of the art, try auto-insert on an Emacs lisp file.)

Adding functionality to the existing auto-insert-alist

The function define-auto-insert allows one to add CONDITION and ACTION pairs to an existing auto-insert-alist. This means one can keep using the built-in auto-insert-alist which handles a number of different files. Here is the signature and docstring for define-auto-insert.

(define-auto-insert CONDITION ACTION &optional AFTER)

Associate CONDITION with (additional) ACTION in ‘auto-insert-alist’. Optional AFTER means to insert action after all existing actions for CONDITION, or if CONDITION had no actions, after all other CONDITIONs.

Here is an example of using this approach. For brevity I don't use the skeleton but instead specify a file in the auto-insert-directory.

(define-auto-insert "\\.[rR]\\'" "template.R")

After evaluating the expression, this entry is at the front of the auto-insert-alist.

Conclusion

It was fun to learn the auto-insert functionality. I spent a lot of time learning and writing up what is in this document and I will need to create hundreds of new R files before I start to save any time. But with this file I should be able to make better, faster, stronger skeletons for more file types.

Author: Stephen Weigand

Created: 2023-09-01 Fri 23:14

Validate