Emacs Lisp: Implementing Name Completion

Master emacs+lisp, benefit for life. Testimonials. Thank you for support.
,

This page shows you how to implement computer language keyword completion in emacs. You should know the basics of writing a major mode. If not, see: How to Write a Emacs Major Mode for Syntax Coloring.

Problem Description

You are writing a emacs major mode for your own language. You want to have a keyword completion feature, so that user can press a key and have the word under cursor automatically expanded to the possible keywords of the language.

xlsl-keyword completion
Keyword completion in emacs.

Solution

The basic concept of keyword completion is pretty simple. You begin with a list of keywords, and you are given a string that you want to complete. You match the string against the keywords, find the maximal match, then replace the current word with that max match. However, you will also need to pop-up a list of possible completions for the user to choose, and allow user some user interface conveniences such as clicking on one of the choices.

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

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 is try-completion, all-completions, display-completion-list.

Set a keyboard shortcut for your completion function, so that you can easily test it. ⁖ (global-set-key (kbd "<f6>") 'xyz-complete-symbol). When you are ready to put the code in your major mode, make sure you assign it a key (kbd "M-TAB"). That key is emacs's convention for doing keyword completion.

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

For hash, see: Emacs Lisp Tutorial: Hash Table.

(info "(elisp) Completion")

Emacs is beautiful.

Like what you read?
Buy Xah Emacs Tutorial
or share some
blog comments powered by Disqus