Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/minad/tempel
:classical_building: TempEl - Simple templates for Emacs
https://github.com/minad/tempel
Last synced: 30 days ago
JSON representation
:classical_building: TempEl - Simple templates for Emacs
- Host: GitHub
- URL: https://github.com/minad/tempel
- Owner: minad
- License: gpl-3.0
- Created: 2022-01-05T03:37:06.000Z (almost 3 years ago)
- Default Branch: main
- Last Pushed: 2024-02-16T15:43:51.000Z (9 months ago)
- Last Synced: 2024-05-02T00:32:51.423Z (6 months ago)
- Language: Emacs Lisp
- Homepage:
- Size: 902 KB
- Stars: 475
- Watchers: 11
- Forks: 22
- Open Issues: 0
-
Metadata Files:
- Readme: README.org
- Changelog: CHANGELOG.org
- License: LICENSE
Awesome Lists containing this project
README
#+title: TempEl - Simple templates for Emacs
#+author: Daniel Mendler
#+language: en
#+export_file_name: tempel.texi
#+texinfo_dir_category: Emacs misc features
#+texinfo_dir_title: Tempel: (tempel).
#+texinfo_dir_desc: Simple templates for Emacs#+html:
#+html:
#+html:
#+html:
#+html:
#+html: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 30 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 new implementation of Tempo with inline expansion and integration with
recent Emacs facilities. Tempel takes advantage of the standard
=completion-at-point-functions= mechanism which is used by Emacs for in-buffer
completion.#+toc: headlines 8
* 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~. The
Capf returns a list of templates names which are presented by the completion
UI for selection.
+ ~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~. This Capf returns only the single
exactly matching template name, such that no selection in the completion UI is
possible.
+ ~tempel-insert~ selects a template by name via ~completing-read~ and insert it
into the current buffer.For the commands ~tempel-complete~ and ~tempel-expand~, you may want to give my
[[https://github.com/minad/corfu][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~. As soon as you move before
(behind) the first (last) field, the fields are finalized. The key bindings are
defined in the ~tempel-map~ keymap. I recommend that you inspect the ~tempel-map~
and look at the provided key bindings. You can customize the key bindings there.#+html:
* 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=. For
some ready-made templates check out the package [[https://github.com/Crandel/tempel-collection][tempel-collection]]. The
collection is not comprehensive yet, but will certainly grow thanks to
contributions.#+begin_src emacs-lisp
;; 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 'conf-mode-hook 'tempel-setup-capf)
(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: Add tempel-collection.
;; The package is young and doesn't have comprehensive coverage.
(use-package tempel-collection);; Optional: Use the Corfu completion UI
(use-package corfu
:init
(global-corfu-mode))
#+end_src* Template file format
The templates are defined in a Lisp data file configured by ~tempel-path~. Lisp
data files are files containing Lisp s-expressions (see ~lisp-data-mode~). 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. I recommend to use avoid special letters for the template
names, since special letters may carry meaning during completion filtering and
as such make it harder to select the desired template. Thus the name =lett= is
better than =let*=. 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.The following examples are written on a single line, but this is is of course
not a requirement. Strings can even contain line breaks, which can be useful if
you want to write complex templates.#+begin_src emacs-lisp
;; ~/.config/emacs/templatesfundamental-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
(abstract "\\begin{abstract}\n" r> n> "\\end{abstract}")
(align "\\begin{align}\n" r> n> "\\end{align}")
(alignn "\\begin{align*}\n" r> n> "\\end{align*}")
(gather "\\begin{gather}\n" r> n> "\\end{gather}")
(gatherr "\\begin{gather*}\n" r> n> "\\end{gather*}")
(appendix "\\begin{appendix}\n" r> n> "\\end{appendix}")
(begin "\\begin{" (s env) "}" r> n> "\\end{" (s env) "}")
(center "\\begin{center}\n" r> n> "\\end{center}")
(displaymath "\\begin{displaymath}\n" r> n> "\\end{displaymath}")
(document "\\begin{document}\n" r> n> "\\end{document}")
(enumerate "\\begin{enumerate}\n\\item " r> n> "\\end{enumerate}")
(equation "\\begin{equation}" r> n> "\\end{equation}")
(flushleft "\\begin{flushleft}" r> n> "\\end{flushleft}")
(flushright "\\begin{flushright}" r> n> "\\end{flushright}")
(frac "\\frac{" p "}{" q "}")
(fussypar "\\begin{fussypar}" r> n> "\\end{fussypar}")
(itemize "\\begin{itemize}\n\\item " r> n> "\\end{itemize}")
(letter "\\begin{letter}\n" r> n> "\\end{letter}")
(math "\\begin{math}\n" r> n> "\\end{math}")
(minipage "\\begin{minipage}[t]{0.5\linewidth}\n" r> n> "\\end{minipage}")
(quotation "\\begin{quotation}\n" r> n> "\\end{quotation}")
(quote "\\begin{quote}\n" r> n> "\\end{quote}")
(sloppypar "\\begin{sloppypar}\n" r> n> "\\end{sloppypar}")
(theindex "\\begin{theindex}\n" r> n> "\\end{theindex}")
(trivlist "\\begin{trivlist}\n" r> n> "\\end{trivlist}")
(verbatim "\\begin{verbatim}\n" r> n> "\\end{verbatim}")
(verbatimm "\\begin{verbatim*}\n" r> n> "\\end{verbatim*}")texinfo-mode
(defmac "@defmac " p n> r> "@end defmac")
(defun "@defun " p n> r> "@end defun")
(defvar "@defvar " p n> r> "@end defvar")
(example "@example " p n> r> "@end example")
(lisp "@lisp " p n> r> "@end lisp")
(bullet "@itemize @bullet{}" n> r> "@end itemize")
(code "@code{" p "}")
(var "@var{" p "}")lisp-mode emacs-lisp-mode ;; Specify multiple modes
(lambda "(lambda (" p ")" n> r> ")")
emacs-lisp-mode
(autoload ";;;###autoload")
(pt "(point)")
(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> ")")
(whilelet "(while-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> ")")
(lett "(let* (" p ")" n> r> ")")
(pcaselet "(pcase-let (" p ")" n> r> ")")
(pcaselett "(pcase-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) ")")
(header ";;; " (file-name-nondirectory (or (buffer-file-name) (buffer-name)))
" -- " p " -*- lexical-binding: t -*-" n
";;; Commentary:" n ";;; Code:" n n)
(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
(box "┌─" (make-string (length str) ?─) "─┐" n
"│ " (s str) " │" n
"└─" (make-string (length str) ?─) "─┘" n)
(abox "+-" (make-string (length str) ?-) "-+" n
"| " (s str) " |" n
"+-" (make-string (length str) ?-) "-+" n)
(cut "--8<---------------cut here---------------start------------->8---" n r n
"--8<---------------cut here---------------end--------------->8---" 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
(caption "#+caption: ")
(drawer ":" p ":" n r ":end:")
(begin "#+begin_" (s name) n> r> n "#+end_" name)
(quote "#+begin_quote" n> r> n "#+end_quote")
(sidenote "#+begin_sidenote" n> r> n "#+end_sidenote")
(marginnote "#+begin_marginnote" n> r> n "#+end_marginnote")
(example "#+begin_example" n> r> n "#+end_example")
(center "#+begin_center" n> r> n "#+end_center")
(ascii "#+begin_export ascii" n> r> n "#+end_export")
(html "#+begin_export html" n> r> n "#+end_export")
(latex "#+begin_export latex" n> r> n "#+end_export")
(comment "#+begin_comment" n> r> n "#+end_comment")
(verse "#+begin_verse" n> r> n "#+end_verse")
(src "#+begin_src " q n r n "#+end_src")
(gnuplot "#+begin_src gnuplot :var data=" (p "table") " :file " (p "plot.png") 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))
(inlsrc "src_" p "{" q "}")
(title "#+title: " p n "#+author: Daniel Mendler" n "#+language: en");; Local Variables:
;; mode: lisp-data
;; outline-regexp: "[a-z]"
;; End:
#+end_src* 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 unless there is only whitespace between line start and point.
- ~%~ Insert newline unless 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 )~ 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 )~ Insert region or act like ~(p ...)~.
- ~(r> PROMPT )~ Act like ~(r ...)~, but indent region.Furthermore Tempel supports syntax extensions:
- ~(p FORM )~ 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!
* Defining custom elements
Tempel supports custom user elements via the configuration variable
=tempel-user-elements=. As a demonstration we add the element =(i template)= to
include templates by name in another template.#+begin_src emacs-lisp
(defun tempel-include (elt)
(when (eq (car-safe elt) 'i)
(if-let (template (alist-get (cadr elt) (tempel--templates)))
(cons 'l template)
(message "Template %s not found" (cadr elt))
nil)))
(add-to-list 'tempel-user-elements #'tempel-include)
#+end_srcThe following example templates uses the newly defined include element.
#+begin_src emacs-lisp
(header ";;; " (or (buffer-file-name) (buffer-name)) " -- " p
" -*- lexical-binding: t -*-" n n)
(provide "(provide '" (file-name-base (or (buffer-file-name) (buffer-name))) ")" n
";;; " (file-name-nondirectory (or (buffer-file-name) (buffer-name))) " ends here" n)
(package (i header) r n n (i provide))
#+end_src* 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:#+begin_src emacs-lisp
(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)
#+end_src* 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.#+begin_src emacs-lisp
(tempel-key "C-c t f" fun emacs-lisp-mode-map)
(tempel-key "C-c t d" ("DATE: " (format-time-string "%Y-%m-%d")))
#+end_srcInternally ~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 [[https://github.com/jwiegley/use-package][use-package]], [[https://github.com/noctuid/general][general]]
or similar keybinding packages.* Alternatives
There are plenty of alternative packages which provide abbreviation or snippet
expansion. 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 [[https://nschum.de/src/emacs/tempo-snippets/][Tempo-Snippets]]
package by Nikolaj Schumacher ([[https://github.com/nschum/tempo-snippets.el][GitHub link]]).List of alternatives (built-in or separate packages):
- abbrev.el: Abbreviation expansion, builtin
- expand.el: Abbreviation expansion, builtin
- skeleton.el: Lisp syntax for templates, builtin
- tempo.el: Lisp syntax for templates, builtin
- srecode.el: CEDET template manager and code generator, builtin
- [[https://github.com/ymarco/auto-activating-snippets][aas.el]]: Auto activating snippets
- [[https://github.com/cdominik/cdlatex][cdlatex.el]]: Fast LaTeX insertion
- [[https://github.com/tecosaur/LaTeX-auto-activating-snippets][laas.el]]: Latex auto activating snippets
- [[https://github.com/jiahaowork/muban.el][muban.el]]: Lightweight template expansion
- [[https://github.com/oantolin/placeholder][placeholder.el]]: Treat buffers as templates
- [[https://github.com/xFA25E/tempo-abbrev][tempo-abbrev.el]]: Abbrev integration for Tempo
- [[https://github.com/pkazmier/snippet.el][snippet.el]]: Original snippet mode, with inline expansion
- [[https://nschum.de/src/emacs/tempo-snippets/][tempo-snippets.el]]: Interface like snippet.el for Tempo
- [[https://github.com/joaotavora/yasnippet][yasnippet.el]]: Template system inspired by Textmate snippets* Contributions
Since this package is part of [[https://elpa.gnu.org/packages/tempel.html][GNU ELPA]] contributions require a copyright
assignment to the FSF.