Hey gang,
So initially I had something pretty simple to have “snippets” in my Emacs config, using Skeletons and Abbrevs. I’ve expanded on it a little bit since then, but I’m running into an issue where the abbrev no longer expands to call the skeleton function.
Here’s what I have:
;;;###autoload
(defun abbrev::abbrev-table-add-props (abbrev-table props)
"Add one or more PROPS to an existing ABBREV-TABLE.
PROPS should be a plist of (PROP VALUE).
Example:
(:enable-function (lambda (&rest _) (do-something)))"
(if (cddr props)
(cl-loop for (k v) on props by #'cddr
unless (not (abbrev-table-p abbrev-table))
unless (abbrev-table-get abbrev-table k)
do (abbrev-table-put abbrev-table k v))
(unless (and (not (abbrev-table-p abbrev-table))
(abbrev-table-get abbrev-table (car props)))
(abbrev-table-put abbrev-table (car props) (cadr props)))))
;;;###autoload
(defmacro def-mode-snippet (name
mode
docstring
&rest prompt-and-or-skeleton)
"Create a MODES specific \"snippet\" with NAME and SKELETON.
NAME must be valid in the Emacs Lisp naming convention.
MODE must be a valid major or minor mode that is known to Emacs.
Example: ‘org-mode’, ‘emacs-lisp-mode’, etc.
DOCSTRING is used in the abbrev rather than the skeleton.
PROMPT-AND-OR-SKELETON can be any of the following:
1. A valid Skeleton that uses the internal `Skeleton' langauge
2. A key/value \"pair\" that’s :prompt STRING, followed by
a valid Skeleton that uses the internal `Skeleton' language.
The prompt given is used by the Skeleton to prompt the user for an input.
This macro makes use of `define-skeleton' and `define-abbrev' in order to
create something similar to a code/writing snippet system, like that of
`YASnippet'. Keep in mind that all abbreviations created are put in the abbrev
table of MODE you passed to this macro.
Example: passing ‘org-mode’ will add the abbrev to the ‘org-mode-abbrev-table’.
That may or may not be something you want depending on your uses.
If you're looking to only define an abbrev globally, see `def-global-snippet'."
(declare (debug t)
(doc-string 3)
(indent defun))
;; TODO need to figure out how to work with lists better
(let* ((snip-name (symbol-name `,name))
(func-name (intern (concat (symbol-name mode) "-" snip-name "-skel")))
(var-str (concat (symbol-name mode) "-abbrev-table"))
(abbrev-table (intern-soft var-str))
(has-prompt (keywordp (car prompt-and-or-skeleton)))
(prompt (if has-prompt (cadr prompt-and-or-skeleton) nil))
(skeleton (if (not has-prompt) prompt-and-or-skeleton (cddr prompt-and-or-skeleton)))
;; Not using this for now until the issue with abbrevs not expanding is solved.
;;(enable-fn (lambda (&rest _) (or (eq major-mode 'mode) (numberp (cl-position 'mode minor-mode-list)))))
)
(macroexp-progn
`((define-skeleton ,func-name
,(format "%s %s %s %s." snip-name "skeleton. Defined in" var-str "abbreviaton table")
,prompt
,@skeleton)
,(if (not (abbrev-table-p abbrev-table))
`(define-abbrev ,abbrev-table
,snip-name
',func-name)
`(define-abbrev-table ',abbrev-table
'((,snip-name ',func-name))
,(format "An abbrev table for %s" mode)
:system t
:case-fixed t)
(abbrev::abbrev-table-add-props ,abbrev-table
'(:system t
:case-fixed t)))))))
The example I can give is the following:
(def-mode-snippet defun emacs-lisp-mode
"defun snippet for Emacs Lisp"
> "(defun " @ - " (" @ _ ")" \n
> -2 "\"" @ _ "\"" \n
> -1 @ _ ")")
And that will macro-expand to:
(progn
(define-skeleton emacs-lisp-mode-defun-skel "defun skeleton. Defined in emacs-lisp-mode-abbrev-table abbreviaton table." nil > "(defun " @ - " (" @ _ ")" n > -2 "\"" @ _ "\"" n > -1 @ _ ")")
(define-abbrev emacs-lisp-mode-abbrev-table "defun" 'emacs-lisp-mode-defun-skel))
Which evals fine, even though I think the n
would not be valid to Skeleton, but I’m not sure.
EDIT 1: I’ve put up a question on The Emacs StackExchange with more details if anyone is interested. Also, if it helps looking at my full config you can view it here.
EDIT 2: Good god, I nearly lost my mind by missing this:
(define-abbrev emacs-lisp-mode-abbrev-table "defun" 'emacs-lisp-mode-defun-skel)
When defining an abbrev using a skeleton, the skeleton function should go in an define-abbrev
’s hook argument. As the documentation for define-abbrev
says:
If HOOK is a non-nil symbol with a non-nil no-self-insert property, it can control whether the character that triggered abbrev expansion is inserted. If such a HOOK returns non-nil, the character is not inserted. If such a HOOK returns nil, then so does abbrev-insert (and expand-abbrev), as if no abbrev expansion had taken place.
~~If it helps, I’m on the developer version of Emacs (30). Another weird thing is that I get no errors and the abbrev is listed when running
M-x list-abbrevs
. The only error I see is when trying to dohippie-expand
on the abbrev when it’s typed out gives me:he-string-member: Wrong type argument: sequencep, emacs-lisp-mode-defun-skel
So maybe this is an issue with the commit of the developer version I’m on? I really don’t know at this point. And before anyone asks, yes,
abbrev-mode
is on. 😆 ~~Read my edits. 😑