As one of the two primary authors of idris-mode, I’ve had to learn a fair bit about implementing Emacs modes. While I’m far from an expert on the subject, I do think that I’ve picked up a few ideas that are useful. There are already a number of good tutorials about the very basics, such as
font-lock, and Emacs packages. What I haven’t been able to find is an answer to the question “what’s next?”. You can view this post as a kind of checklist for useful things to add to your mode.
I won’t go into great detail about how to do all of these things, because the documentation is typically quite good. The real problem is learning that there is documentation to be read!
While many Emacs users turn off the menu bar entirely, making Imenu not particularly attractive, it pays to support it anyway. This is because lots of other features in Emacs use Imenu as a data source. For example, speedbar will use it to show an index of your file and
which-func-mode uses it to determine what to show in your mode line.
These days, it is very straightforward to implement completion for your mode. Many older modes contain complicated code to show completion buffers, restoring window configurations afterwards. In Emacs 24, we have
completion-at-point. Simply ensure that this is bound to something (often
M-TAB), and then arrange for it to have enough information to present good completions
completion-at-point-functions provides the raw data that completion-at-point will use to construct a completions buffer. The variable contains a list of 0-argument functions, each of which should return either nil or a collection of completion candidates. Emacs takes care of all the tedious organization of buffers. In
idris-mode, we simply delegate to the compiler to provide completion candidates.
Eldoc is a minor mode for showing documentation strings in the echo area. By default, it supports Emacs Lisp, but it’s quite straightfoward to make it work for your language. All you need is a function that returns either a docstring for the symbol at point or nil. In the case of Idris, we already had a command to ask the compiler for a type signature, so it was especially easy. Once you have this function, just set
eldoc-documentation-function to its name and place
turn-on-eldoc-mode in your default mode hook.
Here’s the function from idris-mode:
(defun idris-eldoc-lookup () "Support for showing type signatures in the modeline when there's a running Idris" (let ((signature (ignore-errors (idris-eval (list :type-of (idris-name-at-point)))))) (when signature (with-temp-buffer (idris-propertize-spans (idris-repl-semantic-text-props (cdr signature)) (insert (car signature))) (buffer-string)))))
All of the
idris-propertize-spans stuff is to apply font information from the compiler.
Flycheck is a minor mode that runs quick external programs on the contents of your buffer, annotating the buffer with any warnings produced, without you needing to save the buffer. It is fairly straightforward to define a checker for Flycheck, and it can be added to your mode without disturbing users who don’t use Flycheck by wrapping it up in an
eval-after-load. Here is the entire checker from
idris-mode, written by Brian McKenna:
;;;###autoload (eval-after-load 'flycheck '(progn (flycheck-define-checker idris "An Idris syntax and type checker." :command ("idris" "--check" "--nocolor" "--warnpartial" source) :error-patterns ((warning line-start (file-name) ":" line ":" column ":Warning - " (message (and (* nonl) (* "\n" (not (any "/" "~")) (* nonl))))) (error line-start (file-name) ":" line ":" column ":" (message (and (* nonl) (* "\n" (not (any "/" "~")) (* nonl)))))) :modes idris-mode) (add-to-list 'flycheck-checkers 'idris)))
In my opinion, it’s important to have variables that control settings for a mode be separate from other variables, so that users can see what they’re intended to change and what they are not. A good way to indicate that a variable is intended for user customization is to make it available through customize. When thinking about how to customize features of your mode, consider how to organize them into groups so that they are easy to find.
If you define your mode as a derive mode (and you should), remember to pass the
:group option to
define-derived-mode. This makes the standard command
Additionally, your mode should really avoid using built-in faces. Instead, define your own faces with
defface and set them to inherit from the built-in faces that you were thinking of using. This lets your users decide how they want to see your mode. Good defaults are also important, to the extent that you can have them, so pay attention to built-in faces that you can inherit from, which will make your mode automatically conform to the user’s color theme. Perhaps the biggest source of complaints about idris-mode has come from default colors that don’t look good for users, where I was unable to find a built-in face to inherit from that makes sense. Surprisingly many Emacs users don’t know that they can easily customize a face.
Remember to derive your mode from
prog-mode. If you don’t do that, at least make sure that you call
prog-mode-hook. This allows Emacs users to set things up for every programming language mode, while not using these settings in other places. This is useful for things like showing trailing whitespace or turning on line numbering.
Common Lisp features
If you have experience writing Common Lisp code, you’re in for a disappointment when you write Emacs Lisp. There are many, many useful features, such as
destructuring-bind, that Emacs Lisp just doesn’t support. Furthermore, when these features are available, you either have to use a compile-time-only macro package (
cl) or an ugly prefixed version (
cl-lib). I recommend the latter, as you avoid having to worry about which features are macros and which are functions. Unfortunately, you will probably end up with
cl loaded by something in your initialization file, so Emacs will probably not complain if you accidentally use
cl names instead of
cl-lib names, which can lead to problems for your users. Consider using
cl-lib-highlight to get warnings in your Lisp buffers when you use un-prefixed
All the rest
After you’ve got these things done, then it’s time to start attacking all the little things, like getting the parsing working for motion commands such as forward-sexp, implementing fill-paragraph for comments and docstrings, and so forth. I haven’t done all these things yet, but I’ll write it up if I find a nice strategy.