jgarte / tempel

:classical_building: TempEl - Simple templates for Emacs

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

TempEl - Simple templates for Emacs

GNU Emacs GNU ELPA GNU-devel ELPA MELPA MELPA Stable

Tempel is a tiny template package for Emacs, which uses the syntax of the Emacs Tempo library. Tempo is an ancient temple of the church of Emacs. It is 27 years old, but still in good shape since it successfully resisted change over the decades. However it may look a bit dusty here and there. Therefore we present Tempel, a modernized implementation of Tempo.

Template expansion

Tempel comes with three commands for template expansion:

  • tempel-complete completes a template name at point in the buffer and subsequently expands the template. If called non-interactively the function behaves like a Capf and can be added to completion-at-point-functions.
  • tempel-expand expands an exactly matching template name at point in the buffer. If called non-interactively the function behaves like a Capf and can be added to completion-at-point-functions.
  • tempel-insert selects a template by name and insert it into the current buffer.

For the completion at point commands tempel-complete and tempel-expand, you may want to give my Corfu completion at point popup UI a try. After inserting the template you can move between the visible template fields with the keys M-{, M-} or C-up/down which are normally bound to forward/backward-paragraph. Tempel temporarily remaps these commands to tempel-next/previous. The key bindings are defined in the tempel-map keymap. You can customize them there. As soon as you move before (behind) the first (last) field, the fields are finalized.

Configuration

The package is available on GNU ELPA and MELPA and can be installed with package-install. The following example configuration relies on use-package.

;; Configure Tempel
(use-package tempel
  ;; Require trigger prefix before template name when completing.
  ;; :custom
  ;; (tempel-trigger-prefix "<")

  :bind (("M-+" . tempel-complete) ;; Alternative tempel-expand
         ("M-*" . tempel-insert))

  :init

  ;; Setup completion at point
  (defun tempel-setup-capf ()
    ;; Add the Tempel Capf to `completion-at-point-functions'.
    ;; `tempel-expand' only triggers on exact matches. Alternatively use
    ;; `tempel-complete' if you want to see all matches, but then you
    ;; should also configure `tempel-trigger-prefix', such that Tempel
    ;; does not trigger too often when you don't expect it. NOTE: We add
    ;; `tempel-expand' *before* the main programming mode Capf, such
    ;; that it will be tried first.
    (setq-local completion-at-point-functions
                (cons #'tempel-expand
                      completion-at-point-functions)))

  (add-hook 'prog-mode-hook 'tempel-setup-capf)
  (add-hook 'text-mode-hook 'tempel-setup-capf)

  ;; Optionally make the Tempel templates available to Abbrev,
  ;; either locally or globally. `expand-abbrev' is bound to C-x '.
  ;; (add-hook 'prog-mode-hook #'tempel-abbrev-mode)
  ;; (global-tempel-abbrev-mode)
)

;; Optional: Use the Corfu completion UI
(use-package corfu
  :init
  (global-corfu-mode))

Template file format

The templates are defined in a Lisp data file configured by tempel-path. By default the file ~/.config/emacs/templates is used. The templates are grouped by major mode with an optional :when condition. Each template is a list in the concise form of the Emacs Tempo syntax. The first element of each list is the name of the template. Behind the name, the Tempo syntax elements follow.

In addition, each template may specify a :pre and/or :post key with a FORM that is evaluated before the template is expanded or after it is finalized, respectively. The :post form is evaluated in the lexical scope of the template, which means that it can access the template’s named fields.

fundamental-mode ;; Available everywhere

(today (format-time-string "%Y-%m-%d"))

prog-mode

(fixme (if (derived-mode-p 'emacs-lisp-mode) ";; " comment-start) "FIXME ")
(todo (if (derived-mode-p 'emacs-lisp-mode) ";; " comment-start) "TODO ")
(bug (if (derived-mode-p 'emacs-lisp-mode) ";; " comment-start) "BUG ")
(hack (if (derived-mode-p 'emacs-lisp-mode) ";; " comment-start) "HACK ")

latex-mode

(begin "\\begin{" (s env) "}" r> n> "\\end{" (s env) "}")
(frac "\\frac{" p "}{" q "}")
(enumerate "\\begin{enumerate}\n\\item " r> n> "\\end{enumerate}")
(itemize "\\begin{itemize}\n\\item " r> n> "\\end{itemize}")

lisp-mode emacs-lisp-mode ;; Specify multiple modes

(lambda "(lambda (" p ")" n> r> ")")

emacs-lisp-mode

(autoload ";;;###autoload")
(pt "(point)")
(lambda "(lambda (" p ")" n> r> ")")
(var "(defvar " p "\n  \"" p "\")")
(local "(defvar-local " p "\n  \"" p "\")")
(const "(defconst " p "\n  \"" p "\")")
(custom "(defcustom " p "\n  \"" p "\"" n> ":type '" p ")")
(face "(defface " p " '((t :inherit " p "))\n  \"" p "\")")
(group "(defgroup " p " nil\n  \"" p "\"" n> ":group '" p n> ":prefix \"" p "-\")")
(macro "(defmacro " p " (" p ")\n  \"" p "\"" n> r> ")")
(alias "(defalias '" p " '" p ")")
(fun "(defun " p " (" p ")\n  \"" p "\"" n> r> ")")
(iflet "(if-let (" p ")" n> r> ")")
(whenlet "(when-let (" p ")" n> r> ")")
(iflet* "(if-let* (" p ")" n> r> ")")
(whenlet* "(when-let* (" p ")" n> r> ")")
(andlet* "(and-let* (" p ")" n> r> ")")
(cond "(cond" n "(" q "))" >)
(pcase "(pcase " (p "scrutinee") n "(" q "))" >)
(let "(let (" p ")" n> r> ")")
(let* "(let* (" p ")" n> r> ")")
(rec "(letrec (" p ")" n> r> ")")
(dotimes "(dotimes (" p ")" n> r> ")")
(dolist "(dolist (" p ")" n> r> ")")
(loop "(cl-loop for " p " in " p " do" n> r> ")")
(command "(defun " p " (" p ")\n  \"" p "\"" n> "(interactive" p ")" n> r> ")")
(advice "(defun " (p "adv" name) " (&rest app)" n> p n> "(apply app))" n>
        "(advice-add #'" (p "fun") " " (p ":around") " #'" (s name) ")")
(provide "(provide '" (file-name-base (or (buffer-file-name) (buffer-name))) ")" n
         ";;; " (file-name-nondirectory (or (buffer-file-name) (buffer-name))) " ends here" n)

eshell-mode

(for "for " (p "i") " in " p " { " q " }")
(while "while { " p " } { " q " }")
(until "until { " p " } { " q " }")
(if "if { " p " } { " q " }")
(ife "if { " p " } { " p " } { " q " }")
(unl "unless { " p " } { " q " }")
(unle "unless { " p " } { " p " } { " q " }")

text-mode

(cut "--8<---------------cut here---------------start------------->8---" n r n
     "--8<---------------cut here---------------end--------------->8---" n)
(asciibox "+-" (make-string (length str) ?-) "-+" n
          "| " (s str)                       " |" n
          "+-" (make-string (length str) ?-) "-+" n)
(rot13 (p "plain text" text) n "----" n (rot13 text))
(calc (p "taylor(sin(x),x=0,3)" formula) n "----" n (format "%s" (calc-eval formula)))

rst-mode

(title (make-string (length title) ?=) n (p "Title: " title) n (make-string (length title) ?=) n)

java-mode

(class "public class " (p (file-name-base (or (buffer-file-name) (buffer-name)))) " {" n> r> n "}")

c-mode :when (re-search-backward "^\\S-*$" (line-beginning-position) 'noerror)

(inc "#include <" (p (concat (file-name-base (or (buffer-file-name) (buffer-name))) ".h")) ">")
(incc "#include \"" (p (concat (file-name-base (or (buffer-file-name) (buffer-name))) ".h")) "\"")

org-mode

(title "#+title: " p n "#+author: Daniel Mendler" n "#+language: en" n n)
(quote "#+begin_quote" n> r> n> "#+end_quote")
(example "#+begin_example" n> r> n> "#+end_example")
(center "#+begin_center" n> r> n> "#+end_center")
(comment "#+begin_comment" n> r> n> "#+end_comment")
(verse "#+begin_verse" n> r> n> "#+end_verse")
(src "#+begin_src " p n> r> n> "#+end_src" :post (org-edit-src-code))
(elisp "#+begin_src emacs-lisp" n> r> n "#+end_src" :post (org-edit-src-code))

;; Local Variables:
;; mode: lisp-data
;; outline-regexp: "[a-z]"
;; End:

Template syntax

All the Tempo syntax elements are fully supported. The syntax elements are described in detail in the docstring of tempo-define-template in tempo.el. We document the important ones here:

  • “string” Inserts a string literal.
  • p Inserts an unnamed placeholder field.
  • n Inserts a newline.
  • > Indents with indent-according-to-mode.
  • r Inserts the current region. If no region is active, quits the containing template when jumped to.
  • r> Acts like r, but indent region.
  • n> Inserts a newline and indents.
  • & Insert newline if there is only whitespace between line start and point.
  • % Insert newline if there is only whitespace between point and line end.
  • o Like % but leaves the point before newline.
  • (s NAME) Inserts a named field.
  • (p PROMPT <NAME> <NONINS>) Insert an optionally named field with a prompt. The PROMPT is displayed directly in the buffer as default value. If NOINSERT is non-nil, no field is inserted. Then the minibuffer is used for prompting and the value is bound to NAME.
  • (r PROMPT <NAME> <NOINSERT>) Insert region or act like (p ...).
  • (r> PROMPT <NAME> <NOINSERT>) Act like (r ...), but indent region.

Furthermore Tempel supports syntax extensions:

  • (p FORM <NAME> <NONINS>) Like p described above, but FORM is evaluated.
  • (FORM ...) Other Lisp forms are evaluated. Named fields are lexically bound.
  • q Quits the containing template when jumped to.

Use caution with templates which execute arbitrary code!

Adding template sources

Tempel offers a flexible mechanism for providing the templates, which are applicable to the current context. The variable tempel-template-sources specifies a list of sources or a single source. A source can either be a function, which should return a list of applicable templates, or the symbol of a variable, which holds a list of templates, which apply to the current context. By default, Tempel configures only the source tempel-path-templates. You may want to add global or local template variables to your user configuration:

(defvar my-global-templates
  '((example "Global example template"))
  "My global templates.")
(defvar-local my-local-templates nil
  "Buffer-local templates.")
(add-to-list 'tempel-template-sources 'my-global-templates)
(add-to-list 'tempel-template-sources 'my-local-templates)

Hooking into the Abbrev mechanism

Tempel can hook into Abbrev by enabling the tempel-abbrev-mode in a buffer or by enabling the global-tempel-abbrev-mode. Then the Tempel templates will be available via expand-abbrev which is usually bound to ~C-x ‘~.

Binding important templates to a key

Important templates can be bound to a key with the small utility macro tempel-key which accepts three arguments, a key, a template or name and optionally a map.

(tempel-key "C-c t f" fun emacs-lisp-mode-map)
(tempel-key "C-c t d" (format-time-string "%Y-%m-%d"))

Internally tempel-key uses tempel-insert to trigger the insertion. Depending on the style of your user configuration you may want to write your own helper macros, which allow you to conveniently bind templates via use-package, general or similar keybinding packages.

Alternatives

Tempel does not come with readily available snippet collections, unlike the YASnippet library. Try Tempel if you like small and simple packages. With Tempel you write your templates in Lisp syntax, which from my perspective fits well to the hackable nature of Emacs. Tempel took inspiration from the Tempo-Snippets package by Nikolaj Schumacher (GitHub link).

There are plenty of alternative packages which provide abbreviation or snippet expansion.

  • abbrev.el: Abbreviation expansion, builtin
  • skeleton.el: Lisp syntax for templates, builtin
  • tempo.el: Lisp syntax for templates, builtin
  • aas.el: Auto activating snippets
  • cdlatex.el: Fast LaTeX insertion
  • laas.el: Latex auto activating snippets
  • muban.el: Lightweight template expansion
  • placeholder.el: Treat buffers as templates
  • skempo.el: Unifies the Skeleton and Tempo configuration
  • snippet.el: Original snippet mode
  • tempo-snippets.el: snippet.el-like interface for Tempo
  • yasnippet.el: The most popular template system

Contributions

Since this package is part of GNU ELPA contributions require a copyright assignment to the FSF.

About

:classical_building: TempEl - Simple templates for Emacs

License:GNU General Public License v3.0


Languages

Language:Emacs Lisp 100.0%