0

I've mashed together my own form of "snippets" in Emacs with Skeletons and Abbrevs. I updated the exact macro for generating both the abbrev and skeleton function, but now abbrevs never expand.

A few things to get out of the way:

  1. abbrev-mode is defaulted to t in my config
  2. abbrev-mode is t in the mode that I've been testing this in
  3. The abbrev is shown in M-x list-abbrevs
  4. Calling the exact skeleton function with M-x my-skeleton-function insert text correctly
  5. The abbrev is shown in local-abbrev-table and in the exact mode table
  6. Calling expand-abbrev manually does not expand the abbrev
  7. Calling hippie-expand gives he-string-member: Wrong type argument: sequencep, my-skeleton-function
  8. Evaluating (abbrev-insert (abbrev-symbol "abbrv" the-mode-abbrev-table)) will return abbrv
  9. Evaluating (abbrev-expansion (abbrev-symbol "abbrv" the-mode-abbrev-table)) will return the named skeleton function
  10. I'm using Corfu + Cape for my completion, although I'm not sure if an update to either or both of those would be causing this issue
  11. It's possible this could be an issue with Orderless as I use a custom "style" for it (orderless:fast-dispatch), but maybe not
  12. I'm on the development version of Emacs (major version 30)

Now, with those out of the way, let me give a rough sample of my configuration. If you don't care to look at configurations for other packages or other things in Emacs and you just want to see what the macro is, scroll the the bottom of the following code block.

;; Abbreviations and Auto Insert
(setq-default abbrev-mode       t
              ;; 'my-local-dir` just points to "~/.emacs.d/.local"
              abbrev-file-name  (concat my-local-dir "abbrev.el")
              save-abbrevs      'silent ; Please stop.
              auto-insert-mode  t
              auto-insert-query nil)   ; Please stop asking.

;; Don't show help for completions
(setq completion-show-help nil)

;; ...
;; the standard stuff you need to install use-package
;; is in my config
;; ...

(use-package orderless
  :preface
  (defun orderless:fast-dispatch (word index total)
    "Dispatch orderless on a WORD where INDEX is 0 and TOTAL is at least 1 character.
Potentially speeds up completion a little bit."
    (and (= index 0) (= total 1) (length< word 1)
         `(orderless-regexp . ,(concat "^" (regexp-quote word)))))
  :config
  (orderless-define-completion-style orderless-fast
    (orderless-style-dispatchers '(orderless:fast-dispatch))
    (orderless-matching-styles '(orderless-literal orderless-initialism orderless-prefixes)))
  (setq orderless-component-separator #'orderless-escapable-split-on-space
        completion-styles '(orderless-fast basic)
        completion-category-defaults nil
        completion-category-overrides
        '((file (styles . (orderless-fast partial-completion)))
          (buffer (styles . (orderless-fast partial-completion)))
          (info-menu (styles . (orderless-fast partial-completion)))))
  (set-face-attribute 'completions-first-difference nil :inherit nil))

(use-package minibuffer
  :ensure nil
  :config
  (unless (package-installed-p 'orderless)
    (setq completion-styles '(basic substring flex partial-completion)))
  (file-name-shadow-mode 1)
  (minibuffer-depth-indicate-mode 1)
  (minibuffer-electric-default-mode 1)
  :custom
  (tab-always-indent 'complete)
  (completions-format 'vertical)
  (completion-cycle-threshold 3)
  (completion-flex-nospace nil)
  (completion-ignore-case t)
  (completion-pcm-complete-word-inserts-delimiters t)
  (read-buffer-completion-ignore-case t)
  (read-file-name-completion-ignore-case t)
  (resize-mini-windows 'grow-only))

(use-package abbrev
  :ensure nil
  :bind ("<C-tab>" . expand-abbrev))

(use-package dabbrev
  :ensure nil
  :bind (("C-M-/" . dabbrev-completion)
         ;;("C-M-/" . dabbrev-expand)
         )
  :custom
  (dabbrev-ignored-buffer-regexps '("\\.\\(?:pdf\\|jpe?g\\|png\\)\\'")))

(use-package hippie-exp
  :bind ("M-/" . hippie-expand)
  :custom
  (hippie-expand-try-functions-list
   '(try-expand-all-abbrevs
     try-expand-dabbrev
     try-expand-dabbrev-all-buffers
     try-expand-line
     try-complete-file-name-partially
     try-complete-file-name
     try-expand-list)))

(use-package corfu
  :hook (after-init . global-corfu-mode)
  :general (:keymaps 'corfu-map
            "TAB"       #'corfu-next
            "<tab>"     #'corfu-next
            [tab]       #'corfu-next
            "C-n"       #'corfu-next
            "S-TAB"     #'corfu-previous
            "<S-tab>"   #'corfu-previous
            "<backtab>" #'corfu-previous
            [backtab]   #'corfu-previous
            "C-p"       #'corfu-previous
            "RET"       #'corfu-insert
            "<return>"  #'corfu-insert
            [return]    #'corfu-insert
            ;; "RET"       #'ignore
            ;; "<return>"  #'ignore
            ;; [return]    #'ignore
            "M-d"       #'corfu-show-documentation
            "M-l"       #'corfu-show-location)
  :preface
  (defun corfu:in-minibuffer ()
    "Allow for Corfu in the minibuffer."
    (unless (or (bound-and-true-p mct--active)
                (bound-and-true-p vertico--input))
      (setq-local corfu-auto nil)
      (corfu-mode +1)))

  (defun corfu:send-shell (&rest _)
    "Send completion candidates when inside comint/shell."
    (cond
     ((and (derived-mode-p 'eshell-mode) (fboundp 'eshell-send-input))
      (eshell-send-input))
     ((and (derived-mode-p 'comint-mode)  (fboundp 'comint-send-input))
      (comint-send-input))))

  (defun corfu:insert-shell-filter (&optional _)
    "Insert completion candidate and send when inside comint/eshell."
    (when (derived-mode-p 'eshell-mode 'comint-mode)
      (lambda ()
        (interactive)
        (corfu-insert)
        (corfu:send-shell))))
  :custom
  (corfu-cycle t)
  (corfu-auto t)
  (corfu-auto-delay 0.66)
  (corfu-auto-prefix 2)
  (corfu-preselect 'prompt)
  (corfu-quit-no-match t)
  (corfu-quit-at-boundary t)
  (corfu-scroll-margin 6)
  (corfu-echo-documentation nil)
  (corfu-popupinfo-delay (cons 0.5 0.5))
  :config
  (add-hook 'minibuffer-setup-hook #'corfu:in-minibuffer 1)
  (corfu-echo-mode +1)
  (corfu-history-mode +1)
  (corfu-popupinfo-mode +1))

(use-package cape
  :after corfu
  :preface
  (defalias 'cape:elisp-capf
    (cape-super-capf
     #'elisp-completion-at-point
     #'cape-abbrev
     #'cape-keyword
     #'cape-symbol
     #'cape-dabbrev)
    "A ‘cape-super-capf’ that’s similar to most simpler Company configurations.
The defacto cape to use for Emacs Lisp.")

  (defalias 'cape:prog-capf
    (cape-super-capf
     #'cape-abbrev
     #'cape-keyword
     #'cape-dabbrev)
    "A ‘cape-super-capf’ for when a ‘prog-mode’-esque mode doesn’t utilitize ‘lsp-mode’.")

  (defalias 'cape:mega-writing-capf
    (cape-super-capf
     #'cape-dict
     #'cape-abbrev
     #'cape-ispell
     #'cape-dabbrev)
    "A ‘cape-super-capf’ for modes for writing.
The defacto cape to use for markup languages for writing.")
  :custom
  (cape-dict-file (expand-file-name "~/.local/share/dict/words"))
  (cape-dabbrev-min-length 2)
  :config
  (setq-mode-local conf-mode
                   completion-at-point-functions (list (cape-capf-buster #'cape:prog-capf)
                                                       #'cape-file)))

;; The part where the custom snippets come in

;;;###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)))))

;; The actual macro I use more mode-based "snippets"
;;;###autoload
(defmacro def-mode-snippet (name
                            mode
                            docstring
                            &rest prompt-and-or-skeleton)
  "Create a MODE specific \"snippet\" with PROMPT-AND-OR-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))
  (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
               nil
               :system t
               :case-fixed t)
          `(define-abbrev-table ',abbrev-table
             '((,snip-name #',func-name nil :system t :case-fixed t))
             ,(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 current snippet that I've been testing with in the Emacs Lisp mode
(def-mode-snippet defun emacs-lisp-mode
  "defun snippet for Emacs Lisp"
  > "(defun " @ - " (" @ _ ")" \n
  > -2 "\"" @ _ "\"" \n
  > -1 @ _ ")")

As of note, the use of the custom macro expands 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 nil :system t :case-fixed t))

For all I know, this could be a bug in Emacs.

If you want to scrutinize my configuration more, you can view it here.

I hope this is enough information.

0 Answers0