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:
abbrev-mode
is defaulted tot
in my configabbrev-mode
ist
in the mode that I've been testing this in- The abbrev is shown in
M-x list-abbrevs
- Calling the exact skeleton function with
M-x my-skeleton-function
insert text correctly - The abbrev is shown in
local-abbrev-table
and in the exact mode table - Calling
expand-abbrev
manually does not expand the abbrev - Calling
hippie-expand
giveshe-string-member: Wrong type argument: sequencep, my-skeleton-function
- Evaluating
(abbrev-insert (abbrev-symbol "abbrv" the-mode-abbrev-table))
will returnabbrv
- Evaluating
(abbrev-expansion (abbrev-symbol "abbrv" the-mode-abbrev-table))
will return the named skeleton function - 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
- It's possible this could be an issue with Orderless as I use a custom "style" for it (
orderless:fast-dispatch
), but maybe not - 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.