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.