Adaptive fill mode

Volume 4, Issue 11; 31 Jan 2020

To fill or not to fill, that is the question. Well. No. It’s to fill or unfill, really. Which is just…oh, nevermind.

I’ve been using Emacs for a quarter of a century or something. Lots of my habits were inculcated in the previous millennium. As a consequence, I don’t tend to treat text the way users of word processors do. I don’t just keep typing and let the editor wrap the lines where it wants. I behave a lot more like I’m using a manual typewriter.I realize with some emotion, let’s call it amusement, that many readers will never have encountered a typewriter. A typewriter was a mechanical device that allowed you to make ink marks on sheets of pressed, dead trees by banging on big mechanical levers with your fingers. It worked (mostly), but you had to physically “return the carriage” at the end of each line. On the web and in XML (and in TeX, and in most rational systems), this is fine. Those environments have markup for paragraphs and they know that text in those paragraphs can be reformatted (or “filled”) to fit the display width.

But recently, I’ve encountered a few places where my “manually filled” paragraphs produce odd looking results. One place is in other people’s email readers, where I guess the expectation is that paragraphs will be one long unbroken line that’s refilled. Another is in comments on web pages.

I’m going to keep using Emacs until you pry it out of my cold, dead fingers, but that doesn’t mean I can’t adapt and blend in. So I wondered if I could learn to stop filling paragraphs by hand.

Baby steps: let’s see if I can learn to use it in Org-mode where what I’m typing is usually, mostly prose. It’s also how I take minutes, which I send to people in email, so if it looks nicer in their email readers, that’s a win for me.

First step, enable visual-line-mode. Ugh. That works, but I have a wide Emacs window and I don’t really want lines that are the whole width of the screen. Enter visual-fill-column. So far so good.

My next problem is that I reflexively hit M-j to refill paragraphs as I’m typing. Sure, I’m used to hitting return where I want line breaks in my paragraphs, but as I edit them and rephrase things, the lines get ragged. M-j calls fill-paragraph which reformats all the lines so that they’re “filled” to the right width.

Unfortunately, that inserts hard newlines and I’m back to where I started. Well, I thought, I can fix that:

(defun ndw/fill-paragraph (arg &optional region)
  "Fill the current paragraph with sensitivity to `visual-line-mode`.
In `visual-line-mode`, 'fill' the paragraph by effectively
unfilling it. That let's `visual-line-mode` do the wrapping. In the
absence of `visual-line-mode`, just fill the paragraph as usual. If
ARG is true, do the reverse. If REGION is provided, do the
filling to the paragraphs in the region."
  ;; Parts gleefully stolen from
  (interactive "P")
  (let ((unfill (and visual-line-mode (not arg))))
    (if unfill
        (let ((fill-column (point-max))
              ;; This would override `fill-column' if it's an integer.
              (emacs-lisp-docstring-fill-column t))
          (fill-paragraph nil region))
      (fill-paragraph nil region))))

Sweet. With that bound to M-j, paragraphs are “filled” or “unfilled” according to whether or not the current buffer has visual-line-mode enabled.

Except in Org-mode, M-j is bound to org-fill-paragraph and that’s…complicated.

Rolling up my sleeves and digging into the source, I eventually work out what big function I’d have to redefine to insert my adaptive code into Org. And I just don’t wanna. It’s too complicated and I don’t like redefining big hunks of code from other packages.

That’s when I remembered “advice”. I’ve never used it, but I wondered if it could come to the rescue here. Advice let’s you wrap your own code around every call to a particular function. You can “advise” the behavior of the function without redefining every place where it’s used.

If you dig your way through the Org source code, you will eventually find the business end of M-j (for paragraphs):

		 (dolist (c (delq end cuts))
		   (fill-region-as-paragraph c end justify)
		   (setq end c))))

Right. So. In theory, I should be able to use advice to insinuate myself into the call to fill-region-as-paragraph.

(defun ndw/adaptive-fill-paragraph (orig-fun &rest args)
  (if visual-line-mode
      (let ((fill-column (point-max))
            ;; This would override `fill-column' if it's an integer.
            (emacs-lisp-docstring-fill-column t))
        (apply orig-fun args))
    (apply orig-fun args)))

(advice-add 'fill-region-as-paragraph :around #'ndw/adaptive-fill-paragraph)

And it works! Magical!

Except there’s no way to not do it. There’s no way to not “unfill” in visual-line-mode. It’s not like you’ve bound a key to ndw/fill-paragraph and you can simply run M-x fill-paragraph instead. No, fill-paragraph eventually calls fill-region-as-paragraph (apparently) and you’ve put advice around that so calling fill-paragraph has exactly the same effect as M-j.

Enter a new minor mode:

(define-minor-mode ndw/adaptive-fill-paragraph-mode
  "Toggle adaptive fill paragraph mode.
When enabled, and when `visual-line-mode` is enabled,
`ndw/adaptive-fill-paragraph` fills a paragraph by 'unfilling'
  :init-value nil)

This does nothing except control ndw/adaptive-fill-paragraph, which is redefined thus:

(defun ndw/adaptive-fill-paragraph (orig-fun &rest args)
  (if (and visual-line-mode ndw/adaptive-fill-paragraph-mode)
      (let ((fill-column (point-max))
            ;; This would override `fill-column' if it's an integer.
            (emacs-lisp-docstring-fill-column t))
        (apply orig-fun args))
    (apply orig-fun args)))

(I suppose I could have left this part out and simply toggled visual-line-mode instead, but nevermind.)

All that’s left is to enable these things in Org-mode and see how I get on.

(setq visual-line-fringe-indicators '(left-curly-arrow right-curly-arrow))
(use-package visual-fill-column)
(add-hook 'visual-line-mode-hook #'visual-fill-column-mode)
(add-hook 'visual-line-mode-hook #'ndw/adaptive-fill-paragraph-mode)
(add-hook 'org-mode-hook 'visual-line-mode)

I’d be a little surprised if I was the first person to do something like this, but I enjoyed the exercise.