Elisp: How to Write Keyword Completion Command

By Xah Lee. Date: . Last updated: .

This page shows you how to implement keyword completion in emacs.

xlsl-keyword completion
Keyword completion in emacs.

Problem

You are writing a major mode for your own language. You want keyword completion feature.

When user calls complete-symbol or completion-at-pointCtrl+Alt+i】 in your major mode, completion should be done with your language's keywords.

Solution

Two things you have to do:

  1. Write a function xyz-completion-at-point
  2. Add xyz-completion-at-point to the hook completion-at-point-functions in your mode command body.

The function xyz-completion-at-point will not take any argument. It will return a list of this form:

(START END COLLECTION . PROPS)

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 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))
  1. Copy and paste the above into a new buffer.
  2. Alt+x eval-buffer.
  3. Open a new buffer.
  4. M-x xyz-mode.
  5. 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 or completion-at-point is emacs standard command to complete word at point. The default key is 【Ctrl+Alt+i】.

complete-symbol is a wrapper to. completion-at-point. You can see in the source code.

;; emacs 25.1.1 source code for complete-symbol. 2016-10-25
(defun complete-symbol (arg)
  "Perform completion on the text around point.
The completion method is determined by `completion-at-point-functions'.

With a prefix argument, this command does completion within
the collection of symbols listed in the index of the manual for the
language you are using."
  (interactive "P")
  (if arg (info-complete-symbol) (completion-at-point)))

The command completion-at-point will 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 xyz-completion-at-point, and we added to the hook completion-at-point-functions by this line:

(add-hook 'completion-at-point-functions 'xyz-completion-at-point nil 'local)

(info "(elisp) Completion in Buffers")

Solution 2: Using ido for Completion

Here's another way to do completion, using ido-mode's interface.

emacs keyword completion ido
emacs keyword completion with ido

ido mode's User Interface is much more convenient. User does not need to press Tab. Completion is done automatically as one types. 〔►see Emacs: icomplete vs ido mode

Here is the function that does the completion, using ido interface.

(require 'ido) ; part of emacs

;; this is your lang's keywords
(setq abc-keywords
      '("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-keywords nil nil $current-sym ))
    (delete-region $p1 $p2)
    (insert $result-sym)))

You'll need to give it a key in your major mode.

〔►see Elisp: How to Create Keymap for Major Mode

For temp testing, you can do:

(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.

(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")))))

You'll need to give it a key in your major mode.

〔►see Elisp: How to Create Keymap for Major Mode

For temp testing, you can just do:

(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:

Lucky for us, emacs does most of the tedious job. The core functions that do the job are:

  1. try-completion → returns the maximal match.
  2. all-completions → returns all possible completions.
  3. 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.

(info "(elisp) Completion")


Here's detailed comparison of the two most fundamental functions try-completion and all-completions.

try-completion

(try-completion STRING COLLECTION &optional PREDICATE) → returns t, nil 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")

;; 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

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")

Writing Major Mode

  1. How to Write a Emacs Major Mode for Syntax Coloring
  2. Elisp: html6-mode
  3. Elisp: Font Lock Mode Basics
  4. Elisp: How to Define Face
  5. Elisp: How to Color Comment in Major Mode
  6. Elisp: How to Write Comment Command in Major Mode
  7. Elisp: How to Write Your Own Comment Command from Scratch
  8. Elisp: How to Write Keyword Completion Command
  9. Elisp: How to Create Keymap for Major Mode
  10. Elisp: Create Abbrev and Templates for Major Mode
  11. Elisp: Text Properties
  12. Elisp: Overlay Highlighting
  13. Emacs: Lookup Google, Dictionary, Documentation
  14. Elisp: Syntax Table Tutorial

  1. Elisp: How to Name Your Major Mode
  2. Elisp: What's “feature”?
  3. Elisp: require, load, load-file, autoload, feature, Explained

Syntax Table

  1. Elisp: Syntax Table Tutorial
  2. Elisp: How to Find Syntax of a Character?
  3. Elisp: How to Modify Syntax Table Temporarily
  4. Elisp: How to Determine If Cursor is Inside String or Comment
  5. Elisp: Regex Patterns and Syntax Table
  6. Elisp: Find Matching Bracket Character
Liket it? Put $5 at patreon. Or Buy Xah Emacs Tutorial. Thanks.