Emacs Lisp: Abbrev, Template, for Major Mode
This page shows you how to create abbrev and function templates for your own major mode.
Problem
You are writing a major mode for your own language. You want function templates for your language.
For example,
you want
fn
to expand to:
function
and
function
expand to:
function name (x) { return x; }
(We use JavaScript as example for the expanded text. But the expansion string can be any text or template.)
Solution
Here's the complete code for a major mode with abbrev.
;; clear value and all properties ;; useful when testing. ;; because this get rid of abbrev table symbol properties too (setq yy-abbrev-table nil) (define-abbrev-table 'yy-abbrev-table '(("fn" "function") ("function" "function name (x) { return x; }") ;; hundreds more ) "Abbrev table for `yy'" :case-fixed t :system t ) (define-derived-mode yy prog-mode "yy" "A major mode for yy." (abbrev-mode 1) ;; (setq local-abbrev-table yy-abbrev-table) ;; normally, use this to set the local abbrev table ;; :abbrev-table yy-abbrev-table ;; this this same as ;; (setq local-abbrev-table yy-abbrev-table) ;; we don't need to manually set local-abbrev-table ;; because our table name has a standard naming of ;; mode-activation-command-name followed by -abbrev-table ;; so define-derived-mode will find it and set for us )
- Copy and paste the code into a buffer.
- Alt+x
eval-buffer
- Open a new file, then Alt+x yy
- Now, type “fn”, then type space. It'll become “function”
Define Abbrev Table
First, define the abbrev table, usingdefine-abbrev-table
define-abbrev-table
-
(define-abbrev-table TABLENAME DEFINITIONS &optional DOCSTRING &rest PROPS)
Create a abbrev table, and define a list of abbrevs, passing each to
define-abbrev
.(setq yy-abbrev-table nil) (define-abbrev-table 'yy-abbrev-table (list (quote ("fn" "function")) (quote ("function" "function name (x) { return x; }"))) "Abbrev table for `yy'" :case-fixed t :system t ) (define-derived-mode yy prog-mode "yy" "A major mode for yy." (abbrev-mode 1))
Or, you can define each abbrev separately
make-abbrev-table
-
(make-abbrev-table &optional PROPS)
Create an abbrev table.
(setq yy-abbrev-table (make-abbrev-table ))
define-abbrev
-
(define-abbrev TABLE ABBREV EXPANSION &optional HOOK &rest PROPS)
Adds one abbrev definition to a existing abbrev table.
(defvar yy-abbrev-table nil "Abbrev table for `yy'") (setq yy-abbrev-table (make-abbrev-table )) (abbrev-table-put yy-abbrev-table :case-fixed t) (abbrev-table-put yy-abbrev-table :system t) (define-abbrev yy-abbrev-table "fn" "function") (define-abbrev yy-abbrev-table "function" "function name (x) { return x; }" ) (define-derived-mode yy prog-mode "yy" "A major mode for yy lang." (abbrev-mode 1))
Abbrev Properties
- Each abbrev may have several properties that control its behavior.
You can attach these properties for each abbrev individually
when you define an abbrev by
define-abbrev
, or you can attach any of these properties to a abbrev table for all abbrevs in the table viadefine-abbrev-table
. - An abbrev table have properties that applies to all abbrevs, and it has a few of its own properties for the whole table.
You can use
make-abbrev-table
ordefine-abbrev-table
to set them.
Note: these properties are not the normal Symbol Property List. Both the abbrev table, and each abbrev, has their own implementation of property list. You use special function to acces them, e.g.
abbrev-get
abbrev-put
abbrev-table-get
abbrev-table-put
Properties for Each Abbrev
Here are the most important properties for each abbrev.
- :system
- if t, “system abbrev”, means don't save to user's abbrev file. Major mode's abbrev should be system abbrev.
- :enable-function
- a function that determines whether it should be expanded at the time. Value should be a function.
- :case-fixed
- t or nil. Whether letter case sensitive.
Abbrev Properties (ELISP Manual)
Properties for an Abbrev Table
Here are the most important properties specific for abbrev table.
- :regexp
-
regex to determine how far back to grab as the abbrev symbol.
By default, it only get character that has word syntax (by default, word syntax means letters and 0 to 9, but not dash - or lowline _.). [see Emacs Lisp: Syntax Table]
2023-08-20 note: i cannot get this to work. also, it's probably better to limit abbrev to just letters and digits, not any punctuations. that way, we avoid complexity.
- :parents
-
A list of parent abbrev tables.
Value should be a list, each element should be a variable, not symbol. For example,
(list lisp-mode-abbrev-table)
you can set properties manually:
(abbrev-table-put yy-abbrev-table :case-fixed t) (abbrev-table-put yy-abbrev-table :system t)
Buffer Local Abbrev Table
Each buffer can have its own abbrev table.
- local-abbrev-table
-
Variable.
Local abbrev table of current buffer.
;; set local abbrev table (setq local-abbrev-table yy-abbrev-table)
If you are using
define-derived-mode
to create a major mode,
you can use the optional argument
:abbrev-table table_var_name
to set a value for local-abbrev-table.
(define-derived-mode yy prog-mode "yy" "A major mode for emacs yy" (abbrev-mode 1) :abbrev-table yy-abbrev-table )
Or, just name your abbrev table to be your mode activation command name joined with
-abbrev-table
.
The define-derived-mode
will recognize the name and automatically set it as local abbrev.
Abbrev Trigger Character
The trigger is activated if the char before cursor is word syntax and char just typed is not. ( Syntax Tables (ELISP Manual) ) Normally it's space or return, or a punctuation char.
The trigger character cannot be changed. This is low level, implemented as part of self-insert-command
.
(see its C source code by
Alt+x describe-function
)
Abbrev Enable Function
when an abbrev expansion is triggered, the :enable-function property for the abbrev is consulted, to decide whether to expand. The value should be a function that returns true or false. If it returns true, do expand, else do nothing.
example
;; adding a property to abbrev table (abbrev-table-put yy-abbrev-table :enable-function 'yy-abbrev-enable-function) (defun yy-abbrev-enable-function () "Return t if not in string or comment. Else nil." (let ((xsyntax-state (syntax-ppss))) (not (or (nth 3 xsyntax-state) (nth 4 xsyntax-state)))))
Abbrev Expand Function (Customize Abbrev Behavior)
An abbrev can expand to arbitrary dynamic text at expansion time, by writing your own function.
If you want your own expansion function, in your major mode activation function, you need to set the variable abbrev-expand-function as local var, and with value to your own expansion function.
- abbrev-expand-function
-
Variable.
Value is a function.
This function is called to expand abbrev.
- The function takes no argument.
- The return value of the expansion function should be non-nil if abbrev is considered expanded. Else, nil.
The default value is
abbrev--default-expand
.(make-local-variable 'abbrev-expand-function) (setq abbrev-expand-function 'yy-expand-abbrev)
example
(make-local-variable 'abbrev-expand-function) (setq abbrev-expand-function 'yy-expand-abbrev) (defun yy-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" (interactive) (when (yy-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) (yy--abbrev-position-cursor xp1) xabrSymbol) nil)))) (defun yy--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: 2016-10-24 2023-08-20" (interactive) (let ((xfoundQ (search-backward "▮" (if Pos Pos (max (point-min) (- (point) 100))) t))) (when xfoundQ (delete-char 1)) xfoundQ ))
Note: If you define your own expansion function, then the :enable-function property value is ignored. If you still want a function to check when should abbrev be expanded, you need to put that check inside your expansion function.
The actual expansion, of getting a abbrev string's expanded string, is by calling the function abbrev-insert
, like this:
(abbrev-insert ABBREV &optional NAME WORDSTART WORDEND)
There are several other functions that help you write the expansion, such as what's the last abbrev used. see Abbrev Expansion (ELISP Manual)
Abbrev's Hook
The last step of abbrev expansion is a hook function for the abbrev.
The hook can be added for each abbrev using
define-abbrev-table
or
define-abbrev
If this hook function returns true, and has property no-self-insert
true, then the char that triggered the abbrev will not be inserted.
;; attaching a hook when defining an abbrev (define-abbrev yy-abbrev-table "fn" "function" yy--abhook) (defun yy--abhook () "Hook function to run after abbrev expansion. Mostly used to prevent inserting the char that triggered expansion" (message "abbrev hook function ran" ) t) ;; prevent inserting the char that triggered expansion (put 'yy--abhook 'no-self-insert t)
Complete Example of Abbrev as Function Templates
Here, we'll use abbrev system to build a function template such that:
- Not expand when inside string quote or comment.
- Add parenthesis as appropriate.
- Move cursor into the proper position after expansion.
- Do not add the trigger character, such as space.
Here's the complete code.
(defun yy-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 yy-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" (interactive) (when (yy-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 ) (yy--abbrev-position-cursor xp1) xabrSymbol) nil)))) (defun yy--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: 2016-10-24 2023-08-20" (interactive) (let ((xfoundQ (search-backward "▮" (if Pos Pos (max (point-min) (- (point) 100))) t))) (when xfoundQ (delete-char 1)) xfoundQ )) (defun yy--abhook () "function to run after abbrev expansion Mostly used to prevent inserting the char that triggered expansion. Version 2016-10-24" (interactive) (message "abbrev hook function ran" ) t) (put 'yy--abhook 'no-self-insert t) ;; if still dev, it's convenient to start fresh (setq yy-abbrev-table nil) (define-abbrev-table 'yy-abbrev-table '( ("fn" "function" yy--abhook) ("function" "function ▮name (x) { return x; }" yy--abhook) ;; hundreds more ) "Abbrev table for `yy'" ) (abbrev-table-put yy-abbrev-table :case-fixed t) (abbrev-table-put yy-abbrev-table :system t) (abbrev-table-put yy-abbrev-table :enable-function 'yy-abbrev-enable-function) (define-derived-mode yy prog-mode "yy" "A major mode for emacs lisp...." (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 'yy-expand-abbrev)) (progn (add-hook 'abbrev-expand-functions 'yy-expand-abbrev nil t))) (abbrev-mode 1) ;; no need in our case. ;; :abbrev-table yy-abbrev-table )
(2016-11-05 thanks to John Kitchen ( [2016-11-05 https://twitter.com/johnkitchin ] ) for suggesting to remove the cursor position place holder char ▮ after expanding a abbrev.)