Emacs Lisp: How to Write Keyword Completion Command
This page shows you how to implement keyword completion in emacs.
Suppose your language xyz has the following list of keywords.
;; this is your lang's keywords (setq xyz-kwdList '("touch" "touch_start" "touch_end" "for" "foreach" "forall" ))
The following is the code that does the completion.
(defun xyz-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 xyz-kwdList)) (cond ((eq maxMatchResult t)) ((null maxMatchResult) (message "Can't find completion for “%s”" meat) (ding)) ((not (string= 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 xyz-kwdList) meat)) (message "Making completion list…%s" "done")))))
Now, to test it, just set a temp key for easy call:
(global-set-key (kbd "<f7>") 'xyz-complete-symbol)
Then, open a new buffer, type any letter, say “t”, then press F7, type some more letter, press F7 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 is
try-completionreturns the maximal match.
all-completionsreturns all possible completions.
display-completion-listtakes 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 alist or hashtable. A alist looks like this:
(setq xyz-kwdList '(("touch" . nil) ("touch_start" . nil) ("touch_end" . nil)))
The keyword list can also be a hash table. See: Emacs Lisp Tutorial: Hash Table.
(info "(elisp) Completion")
Using ido for Completion
A alternative mechanism of completion is to use ido-mode's interface.
Here's a example.
;; this is your lang's keywords (setq abc-kwdList '("touch" "touch_start" "touch_end" "for" "foreach" "forall" )) (defun abc-complete-symbol () "Perform keyword completion on current symbol. This uses `ido-mode' user interface for completion." (interactive) (let* ( (ξbds (bounds-of-thing-at-point 'symbol)) (ξp1 (car ξbds)) (ξp2 (cdr ξbds)) (ξcurrent-sym (if (or (null ξp1) (null ξp2) (equal ξp1 ξp2)) "" (buffer-substring-no-properties ξp1 ξp2))) ξresult-sym) (when (not ξcurrent-sym) (setq ξcurrent-sym "")) (setq ξresult-sym (ido-completing-read "" abc-kwdList nil nil ξcurrent-sym )) (delete-region ξp1 ξp2) (insert ξresult-sym)))
This is much more convenient.