Elisp: Example of Abbrev as Function Templates

By Xah Lee. Date: . Last updated: .

Complete Example of Abbrev as Function Templates

Here, we'll use abbrev system to build a function template such that:

  1. Not expand when inside string quote or comment.
  2. Move cursor into the proper position after expansion.
  3. Do not add the trigger character, such as space.

Here's the complete code.

(defun xx-abbrev-enable-function ()
  "Return t if not in string or comment. Else nil.
This is for abbrev table property `:enable-function'.
Version 2016-10-24"
  (let ((xsyntax-state (syntax-ppss)))
    (not (or (nth 3 xsyntax-state) (nth 4 xsyntax-state)))))

(defun xx-expand-abbrev ()
  "Expand the symbol before cursor,
if cursor is not in string or comment.
Returns the abbrev symbol if there's a expansion, else nil.
Version 2016-10-24"
  (when (xx-abbrev-enable-function) ; abbrev property :enable-function is ignored, if you define your own expansion function
    (let (
          xp1 xp2
          xabrStr
          xabrSymbol
          )
      (save-excursion
        (forward-symbol -1)
        (setq xp1 (point))
        (forward-symbol 1)
        (setq xp2 (point)))
      (setq xabrStr (buffer-substring-no-properties xp1 xp2))
      (setq xabrSymbol (abbrev-symbol xabrStr))
      (if xabrSymbol
          (progn
            (abbrev-insert xabrSymbol xabrStr xp1 xp2 )
            (xx--abbrev-position-cursor xp1)
            xabrSymbol)
        nil))))

(defun xx--abbrev-position-cursor (&optional Pos)
  "Move cursor back to ▮ if exist.
Pos is a buffer position limit of search backward. If nil, default to point minus 100.
Return point if found, else nil.
Version: 2023-08-20"
  (let ((xfoundQ (search-backward "▮" (if Pos Pos (max (point-min) (- (point) 100))) t)))
    (when xfoundQ (delete-char 1))
    xfoundQ
    ))

(defun xx--abhook ()
  "function to run after abbrev expansion
 Mostly used to prevent inserting the char that triggered expansion.
Version 2016-10-24"
  (message "abbrev hook function ran" )
  t)

(put 'xx--abhook 'no-self-insert t)

;; if still dev, it's convenient to start fresh
(setq xx-abbrev-table nil)

(define-abbrev-table 'xx-abbrev-table
  '(
    ("fn" "function" xx--abhook)
    ("function" "function ▮name (x) { return x; }" xx--abhook)
    ;; hundreds more
    )
  "Abbrev table for `xx'"
  )

(abbrev-table-put xx-abbrev-table :case-fixed t)
(abbrev-table-put xx-abbrev-table :system t)
(abbrev-table-put xx-abbrev-table :enable-function 'xx-abbrev-enable-function)

(define-derived-mode xx prog-mode "xx"
  "A major mode for xx"
  :abbrev-table xx-abbrev-table
  (make-local-variable 'abbrev-expand-function)
  (if (or
       (and (>= emacs-major-version 24)
            (>= emacs-minor-version 4))
       (>= emacs-major-version 25))
      (progn
        (setq abbrev-expand-function 'xx-expand-abbrev))
    (progn (add-hook 'abbrev-expand-functions 'xx-expand-abbrev nil t)))
  (abbrev-mode 1))

2016-11-05 thanks to John Kitchen ( https://x.com/johnkitchin ) for suggesting to remove the cursor position place holder char ▮ after expanding a abbrev.

Elisp, creating abbrev