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))

Reply via email to