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.
;; 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> ")")
(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))
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.
- 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.
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"))
You might have problems with expansion on non word characters like *
or -
in
lisp modes. For example let*
might trigger let
expansion. There are some
possible solutions with trade-offs.
(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.
(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
.