Elisp: Keyword Completion
Problem
You want to write a keyword completion command for your own Major Mode.
When user calls
complete-symbol
in your major mode, completion should be done with your language's keywords.
〔see Emacs: Name Completion〕
Solution 1: completion-at-point
Two things you have to do:
- Write a function
xx-completion
- Add
xx-completion
to the hook completion-at-point-functions in your mode command body.
The function xx-completion
should
take no argument,
and return a list of this form:
(START END COLLECTION . PROPS)
- START END is the start and end positions of the word to be completed. Cursor is between them.
- COLLECTION is a list of keywords. (it can be List, Association List, Vector, Hash Table )
- PROPS is a property list. We can ignore it for now. Use nil.
Here's the complete code of a major mode with keyword completion feature.
;; sample major mode with keyword completion feature ;; this is your lang's keywords (setq xx-keywords '("touch" "touch_start" "touch_end" "for" "foreach" "forall" )) (defun xx-completion () "This is the function to be used for the hook `completion-at-point-functions'." (interactive) (let ((bds (bounds-of-thing-at-point 'symbol)) start end) (setq start (car bds)) (setq end (cdr bds)) (list start end xx-keywords . nil))) (define-derived-mode xx-mode c-mode "xx" "Major mode for editing xx lang code." (add-hook 'completion-at-point-functions 'xx-completion nil 'local))
- Copy and paste the above into a new buffer.
- Alt+x
eval-buffer
. - Open a new buffer.
- Alt+x xx-mode.
- Type f, then press Ctrl+Alt+i. Emacs will complete it to become “for”. Press again to see choices.
How does this work?
The command
complete-symbol
is emacs standard command to complete word at point.
The default key is
Ctrl+Alt+i.
complete-symbol
eventually just call functions in the variable list
completion-at-point-functions
So, all you have to do is add your own completion function to the list completion-at-point-functions.
Our completion function is xx-completion
, and we added to the
hook completion-at-point-functions by this line:
Solution 2: Using ido for Completion
Here's another way to do completion, using ido mode 's interface.
Here is the function that does the completion, using ido interface.
(require 'ido) ; part of emacs ;; this is your lang's keywords (setq yy-keywords '("touch" "touch_start" "touch_end" "for" "foreach" "forall" )) (defun yy-complete-symbol () "Perform keyword completion on current symbol. This uses `ido-mode' user interface for completion." (interactive) (let (xbds xp1 xp2 xcurrent-sym xresult-sym) (let ((xbds (bounds-of-thing-at-point 'symbol))) (setq xp1 (car xbds) xp2 (cdr xbds))) (setq xcurrent-sym (if (and xp1 xp2 (not (equal xp1 xp2))) (buffer-substring-no-properties xp1 xp2) "" )) (setq xresult-sym (ido-completing-read "" yy-keywords nil nil xcurrent-sym)) (delete-region xp1 xp2) (insert xresult-sym)))
You'll need to give it a key in your major mode.
〔see Elisp: Create Keymap (keybinding)〕
For temp testing, you can do:
(global-set-key (kbd "TAB") 'yy-complete-symbol)
💡 TIP:
Emacs: ido mode
is becoming obsolete in
Emacs 28 (Released 2022-04)
, because of new features in
〔see icomplete〕
In the above code,
you may
replace
ido-completing-read
by
completing-read
and turn on
fido-vertical-mode
Solution 3: Write Your Own Completion Function
Here's alternative way to do completion.
;; this is your lang's keywords (setq zz-keywords '("touch" "touch_start" "touch_end" "for" "foreach" "forall" )) ;; The following is a standalone function that does the completion. (defun zz-complete-symbol () "Perform keyword completion on word before cursor." (interactive) (let ((posEnd (point)) (meat (thing-at-point 'symbol)) maxMatchResult) ;; when nil, set it to empty string, so user can see all lang's keywords. ;; if not done, try-completion on nil result lisp error. (when (not meat) (setq meat "")) (setq maxMatchResult (try-completion meat zz-keywords)) (cond ((eq maxMatchResult t)) ((null maxMatchResult) (message "Can't find completion for “%s”" meat) (ding)) ((not (string-equal meat maxMatchResult)) (delete-region (- posEnd (length meat)) posEnd) (insert maxMatchResult)) (t (message "Making completion list…") (with-output-to-temp-buffer "*Completions*" (display-completion-list (all-completions meat zz-keywords) meat)) (message "Making completion list…%s" "done")))))
You'll need to give it a key in your major mode.
〔see Elisp: Create Keymap (keybinding)〕
For temp testing, you can just do:
(global-set-key (kbd "TAB") 'zz-complete-symbol)
Then, open a new buffer, type any letter, say “t”, then press Tab, type some more letter, press Tab again.
The above code is very easy to understand. First, you grab the word before cursor, save it as “meat”. Then, you find the maximal match, save it as maxMatchResult. Then, we have a few cases:
- If the max match is the same as the word under cursor, then do nothing, because the word is already complete.
- If the max match is empty, then tell user there is no completion.
- If not the above two cases, then expand the current word to max match.
- Otherwise, pop up a dialog to list possible completions.
Lucky for us, emacs does most of the tedious job. The core functions that do the job are:
try-completion
- Return the maximal match.
all-completions
- Return all possible completions.
display-completion-list
- Takes care of the user interface for displaying the possible completions, and making them clickable.
In the above, we used a simple list for our keywords, and fed them to emacs's completion functions. Emacs's completion functions can also take keyword argument in the form of a Association List or Elisp: Hash Table hashtable.
Reference
Elisp, keyword completion
Emacs lisp, writing a major mode. Essentials
- Elisp: Write a Major Mode for Syntax Coloring
- Elisp: Font Lock Mode
- Elisp: Syntax Color Comments
- Elisp: Write Comment/Uncomment Command
- Elisp: Keyword Completion
- Elisp: Create Keymap (keybinding)
- Elisp: Create Function Templates
- Elisp: Command to Lookup Doc
- Elisp: Create a Hook
- Elisp: Major Mode Names
- Elisp: provide, require, features
- Elisp: load, load-file, autoload
- Elisp: Syntax Table