Is linewrap dead?

Kurt Hackenberg kh at panix.com
Tue Sep 6 02:53:15 UTC 2022


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.
-------------- next part --------------
(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))


More information about the Mutt-users mailing list