28 April 2016
It’s now easier than ever to write major modes in Emacs. If you haven’t written a major mode in a while, or you’re just starting out, here are my three top tips:
As of Emacs 24,
regexp-opt takes a
'symbols option. You should write your font-lock keywords like this:
(defvar js-mode-font-lock-keywords `((,(regexp-opt '("var" "for" "function" "if" "else") 'symbols) . font-lock-keyword-face)
This has two advantages. By whitelisting keywords, users can quickly spot mistakes when editing:
This also prevents a classic bug where Emacs highlights substrings that happen to be keywords:
(Don’t) use company
Company is excellent, and I highly recommend it. However, not all Emacsers use company. You don’t need to force company on your users.
Instead, you can use
completion-at-point-functions . Your completion functionality will work in stock Emacs, and company users will benefit too through
Ah, but what about all the extra annotations you can supply to company? We can have our cake and eat it too:
(defun racer-complete-at-point () "Complete the symbol at point." (unless (nth 3 (syntax-ppss)) ;; not in string (let* ((bounds (bounds-of-thing-at-point 'symbol)) (beg (or (car bounds) (point))) (end (or (cdr bounds) (point)))) (list beg end (completion-table-dynamic #'racer-complete) :annotation-function #'racer-complete--annotation :company-prefix-length (racer-complete--prefix-p beg end) :company-docsig #'racer-complete--docsig :company-location #'racer-complete--location))))
Test with assess
Historically, it’s been rather awkward to test major modes. Many authors didn’t bother.
That’s all changed with the release of assess . Assess provides great assertions with readable error messages.
For example, here’s a simple indentation test from cask-mode :
(ert-deftest cask-mode-indent-inside-development () "Ensure we correctly indent inside (development ...) blocks." (should (assess-indentation= 'cask-mode ;; before: " (development (depends-on "foo"))" ;; after: " (development (depends-on "foo"))")))
Highlighting is particularly helped by assess:
(ert-deftest cask-mode-highlight-sources () "Ensure we highlight known values for source." (should (assess-face-at= "(source melpa)" 'cask-mode "melpa" 'cask-mode-source-face)))
If this test fails, we get a helpful message describing which faces were actually used:
#("Face does not match expected value Expected: cask-mode-source-face Actual: font-lock-keyword-face Location: 9 Line Context: (source melpa) bol Position: 1"
These tips are all new things I’ve learnt writing a new major mode for Cask files . If you’re just getting started with Emacs development, check out adding a new language to Emacs . Finally, if I’ve missed your favourite tip, leave a comment on the /r/emacs discussion !