Here's an updated version of my Emacs mode for Mutt flowed text, with
tweaks: displays quoted text in a different color, handles signature
separator lines correctly, has a little more documentation.
(defvar mutt-flowed-text-mode-map
(let ((map (make-sparse-keymap)))
(set-keymap-parent map text-mode-map)
(define-key map "\C-cd" 'flowed-text-decode-region)
(define-key map "\C-ce" 'flowed-text-encode-region)
(define-key map "\C-cD" 'flowed-text-decode-buffer)
(define-key map "\C-cE" 'flowed-text-encode-buffer)
(define-key map "\C-c\C-c" 'flowed-text-done)
map)
"Keymap for Mutt flowed text mode.")
(define-derived-mode mutt-flowed-text-mode text-mode "Mutt flowed text"
"Major mode to compose text/plain format=flowed for mail reader Mutt.
Type and edit text with each paragraph being a single long line.
When you're done, use a mode command to encode it as flowed text;
that will insert soft line breaks. You can decode it (go back to
one-line paragraphs) and encode it any time, but make sure the
message body is encoded when you exit Emacs and hand the message
back to Mutt.
You can use visual-line-mode to display those long lines
nicely. Also, you might want to make trailing whitespace visible
by setting the variable show-trailing-whitespace, or by using
whitespace-mode.
Encoding and decoding work on both quoted and unquoted text.
This mode sets the buffer-local variable fill-column to a very
large number, because normally Emacs text fill would damage both
single-line paragraphs and text encoded as format=flowed. With
that large number, you could use the Emacs fill functions to turn
a multi-line paragraph into a single-line paragraph.
Don't encode/decode the message header section; that could break
things. Trailing spaces are not unusual in headers, and long
lines are possible. It's best to tell Mutt
set edit_headers = no
to avoid that possibility.
This mode assumes DelSp=no (the default), because that's what
Mutt generates.
This mode does not space-stuff the message, because Mutt does that.
For that reason, this may not be usable with other mail readers."
:syntax-table=nil :abbrev-table=nil
(setq-local delsp nil) ;buffer-local
(defface flowed-text-quoted-text '((t . (:foreground "SlateBlue"))) "")
(setq flowed-text-font-lock-keywords '(("^>+.*$" . flowed-text-quoted-text)))
(setq ft-yet-more-indirection 'flowed-text-quoted-text)
(setq flowed-text-font-lock-keywords '(("^>+.*$" . ft-yet-more-indirection)))
(setq font-lock-defaults '(flowed-text-font-lock-keywords t)) ;buffer-local
(setq fill-column most-positive-fixnum)) ;buffer-local
(defun flowed-text-quote-level ()
(save-excursion
(forward-line 0)
(looking-at ">*")
(length (match-string-no-properties 0))))
(defun ft-looking-at-flowed-line-p ()
(and
(looking-at-p ".* \n")
;;not signature separator (which can be quoted, can be space-stuffed)
(not (looking-at-p ">* ?-- \n"))))
(defun flowed-text-decode-region (begin end)
"Decode text/plain format=flowed. That is, remove its soft line
breaks. This turns a flowed paragraph into a single-line paragraph.
Works on both quoted and unquoted text."
(interactive "r")
(save-excursion
(let (quote-level endm)
(setq endm (set-marker (make-marker) end))
(set-marker-insertion-type endm t)
(goto-char begin)
(while (< (point) endm)
(setq quote-level (flowed-text-quote-level))
(if (ft-looking-at-flowed-line-p)
(progn
(forward-line)
(if (and (< (point) endm)
(equal (flowed-text-quote-level) quote-level))
(progn
(delete-char (if delsp -2 -1))
(delete-char quote-level))
(progn ;else: invalid space, remove it
(backward-char 1)
(delete-char -1)
(forward-char))))
(forward-line))))))
(defun flowed-text-encode-region (begin end)
"Encode text into text/plain format=flowed: insert soft line
breaks into lines longer than 78 characters. Works on both
quoted and unquoted text."
(interactive "r")
(save-excursion
(let (maxlen endm bl el quote-level)
(setq maxlen (if delsp 77 78))
(setq endm (set-marker (make-marker) end))
(set-marker-insertion-type endm t)
(goto-char begin)
(while (< (point) endm)
;;line-end-position stays within a field, forward-line doesn't
(setq bl (point)) ;beginning of line
(setq el (line-end-position)) ;end of line
(setq quote-level (flowed-text-quote-level))
;;If a line is a single very long word -- no spaces -- we
;;can't break it with DelSp=no, so leave it alone. I hope
;;Mutt will use quoted-printable to insert soft line breaks at
;;that level, at least if the line is longer than 998
;;characters.
;;
;;DelSp=yes could break the long word, but that's not
;;implemented here.
(when (and
(> el (+ bl maxlen))
(< quote-level 70))
(goto-char (+ bl 72))
(search-backward " " bl t)
(if (re-search-forward " +" el t)
(progn
(if delsp
(insert ?\s))
(insert ?\n)
(insert (make-string quote-level ?>))
(forward-line -1))))
(forward-line)))))
(defun flowed-text-decode-buffer ()
"Decode the whole buffer into single-line paragraphs (remove soft line
breaks)."
(interactive)
(flowed-text-decode-region (point-min) (point-max)))
(defun flowed-text-encode-buffer ()
"Encode the whole buffer as format=flowed: insert soft line breaks."
(interactive)
(flowed-text-encode-region (point-min) (point-max)))
(defun flowed-text-done ()
"Done editing message. Encode, save, and bury the buffer."
(interactive)
(flowed-text-encode-buffer)
(save-buffer)
(bury-buffer))