Emacs Lisp: How to Write Keyword Completion Command
This page shows you how to implement keyword completion in emacs.
You are writing a major mode for your own language. You need keyword completion.
When user calls
complete-symbol 【Ctrl+Alt+i】 in your major mode, completion should be done with your language's keywords.
Two things you have to do:
- Write a function
xyz-completion-at-pointto the hook completion-at-point-functions in your mode command body.
xyz-completion-at-point will not take any argument. It will 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 hash table and others, but we'll just do list of strings.)
- PROPS is a property list. We can ignore it for now.
Here's the complete code:
;; sample major mode with keyword completion feature ;; this is your lang's keywords (setq xyz-keywords '("touch" "touch_start" "touch_end" "for" "foreach" "forall" )) (defun xyz-completion-at-point () "This is the function to be used for the hook `completion-at-point-functions'." (interactive) (let* ( (bds (bounds-of-thing-at-point 'symbol)) (start (car bds)) (end (cdr bds))) (list start end xyz-keywords . nil ))) (define-derived-mode xyz-mode c-mode "xyz" "Major mode for editing xyz lang code …" (add-hook 'completion-at-point-functions 'xyz-completion-at-point nil 'local))
- Copy and paste the above into a new buffer.
- Open a new buffer.
- M-x xyz-mode.
- Type f, then press 【Ctrl+Alt+i】. Emacs will complete it to become “for”. Press again to see choices.
For more detail on how this work, see (info "(elisp) Completion in Buffers")
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. The function does not dependent on major mode conventions.
;; 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.
You'll need to give it a key.
(global-set-key (kbd "TAB") 'abc-complete-symbol)
Solution 3: Write Your Own Completion Function
Here's alternative way to do completion.
Suppose your language xyz has the following list of keywords.
;; this is your lang's keywords (setq xyz-keywords '("touch" "touch_start" "touch_end" "for" "foreach" "forall" ))
The following is a standalone function that does the completion. The function does not dependent on major mode conventions.
(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-keywords)) (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-keywords) meat)) (message "Making completion list…%s" "done")))))
Now, give it key for easy call:
(global-set-key (kbd "TAB") 'xyz-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→ returns the maximal match.
all-completions→ returns 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 alist or hashtable. A alist looks like this:
(setq xyz-keywords '(("touch" . nil) ("touch_start" . nil) ("touch_end" . nil)))
The keyword list can also be a hash table. See: Emacs Lisp Tutorial: Hash Table.
(try-completion STRING COLLECTION &optional PREDICATE) → returns
or a string of longest completion.
COLLECTION is a list of strings (it can be list of cons pairs or hashtable and other. See elisp manual for detail).
(info "(elisp) Basic Completion")
tif there's a exact match.
nilif there's no match.
- Else, returns a string of longest match. That is, in COLLECTION, find all strings that starts with STRING, then, of these, find the longest string that they all share at the start of string.
;; found no match. returns nil (try-completion "c" '( "amc" "amb" )) ;; nil ;; only 1 possible match, and is same as input ;; returns t (try-completion "amb" '( "amc" "amb" )) ;; t ;; only 1 possible match, and is longer than input ;; returns the matched string (try-completion "ah" '( "amc" "amb" "ahu" )) ;; "ahu" ;; found more than 1 match. returns the longest start string shared by all matched strings (try-completion "a" '( "amc" "amb" )) ;; "am"
all-completions is like
try-completion, but it returns a list of all matches.
;; no match (all-completions "c" '( "amc" "amb" )) ;; nil ;; only only 1 possible match, same as input (all-completions "amb" '( "amc" "amb" )) ;; ("amb") ;; only only 1 possible match, longer than input (all-completions "ah" '( "amc" "amb" "ahu" )) ;; ("ahu") ;; found more than 1 match. returns them all (all-completions "a" '( "amc" "amb" )) ;; ("amc" "amb")
or, buy something from my keyboard store.