On Mon, Sep 05, 2022 at 08:36:54AM +1000, Cameron Simpson wrote:

But not space-stuffing, right?

I just reread https://www.rfc-editor.org/rfc/rfc3676#section-4.4 to refresh my brain. Yeah, I don't think I'd want that when writing a message.

Which I guess is why Mutt space-stuffs the format=flowed that it gets back from the editor.

Aye. I avoid lines commencing with a ">" just because they look quoted to my eye anyway, so that aside "live" space stuffing in authoring is something I'd find distracting.

Agreed, I wouldn't want space-stuffing maintained on the fly, while editing. It could be done at the end, when handing the message back to Mutt.

It seems a little conceptually cleaner to have the editor do the whole job, rather than divide it between the editor and Mutt. But another complication is that you can edit a message more than once...

I've just written something for Emacs, to use Emacs to compose format=flowed for Mutt. Well, I wrote two things, that do it two different ways.

The first one does it like vim, maintaining format=flowed on the fly as you type, by adding things to the existing "fill" functions (re-wrap). I have it working -- I've used it to post a couple things here -- but I had to put fingers into existing Emacs mechanisms (by adding "advice" to functions), which I'm leery of, and I don't think it can be done in a buffer-local way.

The second one works differently. It's a "major mode"; it has commands to convert both ways between format=flowed and one-line paragraphs, while you edit. The idea is to type with each paragraph being a single long line, and then "encode" that as format=flowed when you're done. That doesn't mess with existing mechanisms, and doesn't take much code. Emacs has something called "visual line mode" to make those long lines not ugly. I just got it working...I've tested it, but not extensively...I'm using it for this message...

That second one is attached, for anyone who's interested (Cameron, I suppose you probably don't use Emacs).

Set it up like this in .muttrc:

set text_flowed = yes           #compose text/plain format=flowed
set editor = 'emacs --load=$HOME/elisp/mutt-flowed-text-mode.el %s 
--funcall=mutt-flowed-text-mode'
set edit_headers = no           #don't want to flow the header section

Fire up Mutt, compose a message, and type C-h m to get help on the Emacs mode.
(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 could 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.

This mode sets the buffer-local variable fill-column to a very
large number, because Emacs text fill would damage both
single-line paragraphs and text encoded as format=flowed.

Don't encode/decode the message header section; that could
possibly break things.  It's best to tell Mutt

    set edit_headers = no

to avoid that possibility.

This mode does not space-stuff the message, because Mutt does that.
For that reason, it may not be usable with other mail readers."

  :syntax-table=nil :abbrev-table=nil
  (setq-local delsp nil)
  (setq fill-column most-positive-fixnum))

(defun flowed-text-quote-level ()
  (save-excursion
    (forward-line 0)
    (looking-at ">*")
    (length (match-string-no-properties 0))))

(defun flowed-text-decode-region (begin end)
  (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 (looking-at-p ".* \n")
            (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)
  (interactive "r")
  (save-excursion
    (let (endm bl el quote-level)
      (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))
        (when (> el (+ bl 78))
          (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 ()
  (interactive)
  (flowed-text-decode-region (point-min) (point-max)))

(defun flowed-text-encode-buffer ()
  (interactive)
  (flowed-text-encode-region (point-min) (point-max)))

(defun flowed-text-done ()
  (interactive)
  (flowed-text-encode-buffer)
  (save-buffer)
  (bury-buffer))

Reply via email to