An Emacs Minor Mode

January 28, 2009 by John Jenkins · 1 Comment
Filed under: Emacs 

(aka the Article Mode conversion)

Emacs has a convenience macro for defining minor modes called (appropriately enough) (define-minor-mode ...). It takes a few handy keywords, e.g. :lighter to define that identifier in the mode line and :keymap to define the keymap and the body is executed every time article mode is activated or deactivated.

The new core of my article mode now looks like this:

(define-minor-mode article-mode "Minor mode to assist writing articles."
  :lighter " Article"
  :keymap article-mode-keymap
  (if article-mode
      (article-mode-start)
    (article-mode-stop)))

What else has changed?

  • Keyword highlights are only removed after a change, rather than after any keystroke (e.g. scrolling)
  • The info window is deleted on exiting article mode

And that is about it. I’ve also made a few tweaks to make the updating of the info window a little more reliable.

Let me show you the -start and -stop functions.

(defun article-mode-start ()
  (am-add-info-window)
  (am-update-info)
  (add-hook 'after-change-functions 'am-after-change-functions-hook nil t)
  (add-hook 'am-timer-hook 'am-update-info nil t)
  nil)

(defun article-mode-stop ()
  (remove-hook 'after-change-functions 'am-after-change-functions-hook)
  (remove-hook 'am-timer-hook 'am-update-info)
  (delete-window am-info-window)
  (setq am-info-window nil))

As you can see, they are both pretty simple. The -start function adds the info window and immediately updates it so that it is current. Then it adds a buffer-local1 hook to after-change-functions. This hook kicks off a timer that runs the hooks in am-run-timer-hook when emacs is idle for two seconds2. The reasoning behind this is that (am-update-info) will eventually be a fairly expensive function and we don’t want to run it after every change in the buffer.

(defun am-run-timer-hook ()
  (run-hooks 'am-timer-hook))

(defun am-after-change-functions-hook (begin end length)
  (when article-mode
    (when (timerp am-timer) (cancel-timer am-timer))
    (setq am-timer
          (run-with-timer am-update-time nil 'am-run-timer-hook))))

Unfortunately, the conversion to a minor mode has highlighted (if you’ll pardon the pun) a shortcoming of the original implementation. We used (add-text-properties ...) to highlight the keywords but this doesn’t generally work if font-lock is activated in the buffer. We’ll discuss how to fix this in the next post.

Here is the updated article-mode.el.


1. The fourth parameter to (add-hook ...) makes a hook buffer-local if it is non-nil.

2. Why not use idle timer? I only want the hook to run after activity in the article mode buffer.