Emacs: Cycle through different paragraph formats: all on one line, wrapped, max one sentence per line, one sentence per line
| emacs
I came across Schauderbasis - reformat paragraph via @EFLS@mastodon.social. Now I want M-q
to cycle through different ways of wrapping text:
- all on one line
- according to
fill-column
- at most one sentence per line (although still wrapping at
fill-column
) - at most one sentence per line (don't even try to keep it within
fill-column
).
Now that semantic linefeeds are part of core Emacs (as of 2025-06-14), the code for cycling through different paragraph formats can be pretty short. Most of it is actually just the logic for cycling through different commands. That might come in handy elsewhere. There's an unfill package as well, but since the code for unfilling a paragraph is very simple, I'll just include that part.
Note that fill-paragraph-semlf
pays attention to sentence-end-double-space
, and it doesn't handle comments yet. I also have some code to check if I'm in a comment and skip those filling methods if needed.
This might encourage me to write shorter sentences.
I can move sentences around with M-Shift-up
and M-Shift-down
in Org Mode, which is pretty handy.
Also, one sentence per line makes diffs easier to read.
But wrapped text is annoying to edit in Orgzly Revived on my phone, because the wrapping makes a very ragged edge on a narrow screen.
I might unwrap things that I want to edit there.
With a little bit of tweaking to skip source blocks, I can narrow to the subtree, select my whole buffer, and cycle the formatting however I like.
(defvar my-repeat-counter '()
"How often `my-repeat-next' was called in a row using the same command.
This is an alist of (cat count list) so we can use it for different functions.")
(defun my-unfill-paragraph ()
"Replace newline chars in current paragraph by single spaces.
This command does the inverse of `fill-paragraph'."
(interactive)
(let ((fill-column most-positive-fixnum))
(fill-paragraph)))
(defun my-fill-paragraph-semlf-long ()
(interactive)
(let ((fill-column most-positive-fixnum))
(fill-paragraph-semlf)))
(defun my-repeat-next (category &optional element-list reset)
"Return the next element for CATEGORY.
Initialize with ELEMENT-LIST if this is the first time."
(let* ((counter
(or (assoc category my-repeat-counter)
(progn
(push (list category -1 element-list)
my-repeat-counter)
(assoc category my-repeat-counter)))))
(setf (elt (cdr counter) 0)
(mod
(if reset 0 (1+ (elt (cdr counter) 0)))
(length (elt (cdr counter) 1))))
(elt (elt (cdr counter) 1) (elt (cdr counter) 0))))
(defun my-in-prefixed-comment-p ()
(or (member 'font-lock-comment-delimiter-face (face-at-point nil t))
(member 'font-lock-comment-face (face-at-point nil t))
(save-excursion
(beginning-of-line)
(comment-search-forward (line-end-position) t))))
;; It might be nice to figure out what state we're
;; in and then cycle to the next one if we're just
;; working with a single paragraph. In the
;; meantime, just going by repeats is fine.
(defun my-reformat-paragraph-or-region ()
"Cycles the paragraph between three states: filled/unfilled/fill-sentences.
If a region is selected, handle all paragraphs within that region."
(interactive)
(let ((func (my-repeat-next 'my-reformat-paragraph
'(fill-paragraph my-unfill-paragraph fill-paragraph-semlf
my-fill-paragraph-semlf-long)
(not (eq this-command last-command))))
(deactivate-mark nil))
(if (region-active-p)
(save-restriction
(save-excursion
(narrow-to-region (region-beginning) (region-end))
(goto-char (point-min))
(while (not (eobp))
(skip-syntax-forward " ")
(let ((elem (and (derived-mode-p 'org-mode)
(org-element-context))))
(cond
((eq (org-element-type elem) 'headline)
(org-forward-paragraph))
((member (org-element-type elem)
'(src-block export-block headline property-drawer))
(goto-char
(org-element-end (org-element-context))))
(t
(funcall func)
(if fill-forward-paragraph-function
(funcall fill-forward-paragraph-function)
(forward-paragraph)))))
)))
(funcall func))))
(keymap-global-set "M-q" #'my-reformat-paragraph-or-region)
Sometimes I use writeroom-mode to make the lines look even narrower, with lots of margin on the side.
Related:
- Text Editor Judo: One Sentence Per Line - The Daily Macro - in Org Mode, one sentence pe line lets you drag sentences around with M-Shift-up and M-Shift-down
- Writing one sentence per line | Derek Sivers (HN)
- Semantic Linefeeds
- Fill paragraph using semantic linefeeds