This is an attempt to improve Emacs built-in /skeleton/ and /tempo/ templates. It tries to make a unified syntax for template definitions. Adds tags and marks support for /skeleton/ and abbrev support for /tempo/.
Every symbol is well documented. See Commentary and documentation strings.
** Define templates
;; Multiple modes! (skempo-define-tempo let (:tag t :abbrev t :mode (emacs-lisp-mode lisp-mode)) "(let ((" p "))" n> r> ")")
;; Skeletons too! With mark jumping! (skempo-define-skeleton defun (:tag t :abbrev t :mode emacs-lisp-mode) "Name: " "(defun " str " (" @ - ")" \n @ _ ")" \n)
;; Clever tempo templates! (skempo-define-tempo defvar (:tag t :abbrev t :mode emacs-lisp-mode) "(defvar " (string-trim-right (buffer-name) (rx ".el" eos)) "-" p n> r> ")")
;; Define tags and abbrevs for existing skeletons and tempo templates! (skempo-define-function shcase (:tag t :abbrev t :mode sh-mode) sh-case)
;; This will override emacs-lisp's "defvar", but you can always call it by ;; function name (or by tag/abbrev if they were defined). (skempo-define-tempo defvar (:tag t :mode lisp-interaction-mode) "(defvar var" p n> r> ")")
** Call templates
(add-hook 'emacs-lisp-mode 'skempo-mode) (add-hook 'lisp-interaction-mode 'skempo-mode)
(with-eval-after-load 'skempo (easy-mmode-define-keymap '(("\C-z" . skempo-complete-tag-or-call-on-region) ("\M-g\M-e" . skempo-forward-mark) ("\M-g\M-a" . skempo-backward-mark)) nil skempo-mode-map))
(custom-set-variables '(skempo-completing-read t) '(skempo-delete-duplicate-marks t) '(skempo-update-identical-tags t) '(skempo-skeleton-marks-support t) '(skempo-mode-lighter " Sk") '(skempo-enable-tempo-elements t))
What is ~:tag~ anyway? Tempo has a concept of a tag. It is a word that triggers the expansion of a template with ~tempo-complete-tag~. It is similar to the expansion name in Yasnippet.
Possible reasons to use it over Yasnippet?
Define templates in e-lisp, as opposed to some external file.
Emacs has an excellent reader, there is no need for new language.
You like built-in packages.
Additional tempo elements This package provides ~skempo-tempo-user-elements~ function which can be enabled with ~skempo-enable-tempo-elements~ option. It adds conditional and looping constructs similar to /skeleton/ ones, making skeleton pretty much obsolete. For example, the following snippets are equivalent:
(skempo-define-skeleton someskel () nil "(:option" ;; insert until empty string is given ("Insert symbol: " " #:" str) ")")
(skempo-define-tempo sometemp () "(:option" ;; insert until C-M-g key is pressed (:while ("Insert symbol: " sym) " #:" (s sym)) ")")
The main difference is in the abortion. Skeleton aborts on empty string. I think that empty input is necessary, sometimes, so I choose the approach of binding a dedicate key for abortion. This key is displayed in the prompt and can be customized with ~skempo-tempo-else-key~.
Some more complicated behavior:
(skempo-define-skeleton someskel () nil "(:option" ;; insert until empty string is given ("Insert symbol: " " #:" str) & ")" ;; or don't insert at all. It is a hack that physically removes "(:option" ;; string by deleting 8 characters if previous statements didn't move the ;; point | -8)
(skempo-define-tempo sometemp () ;; If input was not aborted, start inserting (:when ("Insert symbol: " sym) "(:option #:" (s sym) ;; Continue inserting until C-M-g is pressed (:while ("Insert symbol: " sym) " #:" (s sym)) ")"))
There is also an ~:if~ element, that can execute else branch if input was aborted.
(skempo-define-tempo sometemp () (:if ("Insert symbol: " sym) ;; Use l element to group elements together (l "insert " (s sym)) "something else"))
** Disable expansion on non space characters for lisp modes
(let ((enable-fn (lambda () (or (eq this-command 'expand-abbrev) (eql ?\s last-command-event))))) (dolist (mode '(lisp-mode emacs-lisp-mode)) (let ((table (symbol-value (skempo--abbrev-table mode)))) (abbrev-table-put table :enable-function enable-fn))))
This solution will disable automatic expansion on, for example, ~let*~ template.
** Modify syntax for these characters to make them words
(defun modify-lisp-syntax-tables () (modify-syntax-entry ?* "w" (syntax-table)) (modify-syntax-entry ?- "w" (syntax-table)))
(dolist (hook '(lisp-mode-hook emacs-lisp-mode-hook)) (add-hook hook #'modify-lisp-syntax-tables))
This solution will treat ~some-long-symbol~ as a single word. You can change only ~~ character, but in that case ~let-~ will trigger ~let~.
** Bind ~expand-abbrev~ on some key https://www.emacswiki.org/emacs/AbbrevMode#h5o-11