August 27, 2021

reformat paragraph

Lets say we have a paragraph1 of plain text - probably in a markup language like Markdown, Orgmode, Asciidoc or LaTeX.

Then there are 4 sensible ways this paragraph can be formated:

unfilled

Everything is in one line. This can be achieved using the function unfill-paragraph in purcells unfill package.

filled

Every line has at most x characters 2. This makes a mass of text look uniform and prevents horizontal scrolling. This can be achieved with the function fill-paragraph.

one-sentence-per-line

This works very well for version controll, especially if the text is in some markup language. Diffs of small changes look very nice and readable. This format can be achieved with the custom function fill-senteces-in-paragraph I wrote below3.

custom

If the author wants it in his very specific way then nothing should stop him from formatting his text as they wants. Obviously this can not be done by the editor.

Here is an example of how they would look like in a text:

Unfilled: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam. Quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

Filled: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed
do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam. Quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo consequat.

One sentence per line: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam.
Quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

Custom: Lorem ipsum dolor sit amet,
consectetur adipiscing elit.
Sed do eiusmod tempor
incididunt ut
labore et
dolore magna
aliqua. Ut enim ad
minim veniam. Quis nostrud
exercitation ullamco laboris nisi
ut aliquip ex ea commodo consequat.

So the first 3 formates can be done by emacs directly. Building on the results of the article "Fill and unfill paragraphs with a single key" on http://endlessparentheses.com I want a single keybinding (M-q) that cycles the paragraph through all of those fomattings.

This is great for 4 reasons:

  • I can keep my existing muscle memory where M-q is fill-paragraph, because we are basically building an extention to that command.

  • We don't loose functionality because there is no reason to call fill-paragraph multiple times in a row anyways4.

  • I only have to commit a single keybinding for reformating (keybindings are hard to remember well and short keybindings are rare for emacs users).

  • I can repeatedly press the M-q until I like the formating. This allows me to go through the formattings quickly and see what I like best.

Having one keybinding cycling the text through different states is not very common in emacs, but it is not unheared of either:

  • recenter-top-bottom (bound to C-l by default) cycles through different views of the same buffer.

  • org-cycle (bound to TAB in orgmode) does rotate through different views of subtrees.

So lets try to build this.

Get all the formating functions

First we need to have a funtion for each format that reformats the current paragraph into that format.

The function fill-paragraph is conviniently built into emacs. For unfill-paragraph we can just use this package.

(use-package unfill)

Now fill-sentences-in-paragraph we will have to write ourselfs:

(defun fill-sentences-in-paragraph ()
  "Put a newline at the end of each sentence in the current paragraph."
  (interactive)
  (save-excursion
    (mark-paragraph)
    (call-interactively 'fill-sentences-in-region)
    )
  )

(defun fill-sentences-in-region (start end)
  "Put a newline at the end of each sentence in the region maked by (start end)."
  (interactive "*r")
  (call-interactively 'unfill-region)
  (save-excursion
    (goto-char start)
    (while (< (point) end)
      (forward-sentence)
      (if (looking-at-p " ")
          (newline-and-indent)
        )
      )
    )
  )

Note that using forward sentence means that emacs needs to know how your sentences look like. Read the documentation on sentences, especially the part where sentences are/aren't followed by a double space5.

Cycling through the functions.

Now we need a function that behaves different on consequtive calls.

(defvar repetition-counter 0
  "How often cycle-on-repetition was called in a row using the same command.")

(defun cycle-on-repetition (list-of-expressions)
  "Return the first element from the list on the first call,
   the second expression on the second consecutive call etc"
  (interactive)
  (if (equal this-command last-command)
      (setq repetition-counter (+ repetition-counter 1)) ;; then
    (setq repetition-counter 0) ;; else
    )
  (nth
   (mod repetition-counter (length list-of-expressions))
   list-of-expressions) ;; implicit return of the last evaluated value
  )

We can try this out by running (cycle-on-perpitition '("a" "b" "c")) a few times. The result is (as expected): a, b, c, a, b

I especially like the thought that even though repetition-counter is a global variable and there is only one of them, we can use cycle-on-repetition in different commands and the repetition-counter will only ever count the repeats of the last envoked command. That is because the switch is detected when this-command and last-command are compared.

Putting it together

Now we can use a list of functions (functional programming, yeah!), cycle through those and call the result each time.

(defun reformat-paragraph ()
  "Cycles the paragraph between three states: filled/unfilled/fill-sentences."
  (interactive)
  (funcall (cycle-on-repetition '(fill-paragraph fill-sentences-in-paragraph unfill-paragraph)))
  )

And finally we bind that to M-q, to replace/extend emacs default behaviour.

(global-set-key
 (kbd "M-q")
 'reformat-paragraph
 )

Badabing Badaboom, our function is live. I use it very often when writing texts (like this blogpost) in emacs.

Cycling through the different formatings of a paragraph.

Some notes:

  • I am quite proud of cycle-on-repetition, it is one of the first complex behavours that I implemented in emacs-lisp all by myself.

  • my emacs config is public so you can have a look to see if this code is still part of it or not.

  • If you liked this idea, follow me on insta… then try to reimplement recenter-top-bottom using cycle-on-repetition.


1

A paragraph is one or more senteces separated from the rest of the text by two or more newlines.

2

x is controlled by the variable fill-column

3

There are many versions of similar functions around, I chose one that worked best for me.

4

The article on endlessparentheses calls that a "free feature"

5

This is a holy war I wasn't aware of.