Tuesday, August 26, 2014

Using emacs to compose email

For many years, my email client of choice has been mutt, with vim used for composing messages. However, in recent years I've drifted away from vim for longer edits, succumbing to the temptation of emacs.

Email was one place where emacs had some trouble displacing vim, in part because I'm fussy and have an (apparently) odd style.  I indent the first like of each paragraph by two spaces and also leave a space between them. Emacs can handle indented paragraphs with paragraph-indent-minor-mode. However, PIMM does not play nicely with Adaptive Fill, which can be very nice for writing bulleted lists.

My current vim configuration, while allowing the paragraph spacing I want for body text, does fall down somewhat on bulleted lists. It requires manual spacing of list items, which so far I've just been living with. (There's also probably a way to fix this inside vim, but that wouldn't have the advantage of using the emacs daemon that I'm nearly always running. I'll likely investigate this later.)

At first blush, it looks like a suitable adaptive-fill-function could save the day. One perhaps like this:

(defun crh-adaptive-fill-function ()
  "Adaptive fill for indented paragraphs."
  (if (and (looking-at-p "^  [A-Z]")
         (or (bobp)
             (previous-line 1)
             (looking-at paragraph-separate))))

Sadly (and undocumentedly) adaptive-fill-function can't actually specify an empty fill prefix. The section of lisp/simple.el that calls the adaptive fill machinery discards empty prefixes, saying "Use auto-indentation rather than a guessed empty prefix.".

So, that's frustrating. On the bright side, the process of figuring out what was going on gave me a chance to improve my elisp debugging skills and learn more about the emacs codebase.

However, it does work to wrap do-adaptive-fill and delete the unwanted indentation after it was inserted:

(defun crh-do-auto-fill ()
  "Adaptive fill for indented paragraphs in mail-mode."
  (if (mail-mode-auto-fill)
      (if (looking-at-p "^  [A-Z]")
          (while (= (following-char) 32) ; space
            (delete-char 1))))

This is somewhat less elegant than never inserting it, but it has the advantage of working in practice.