Emacs: Reformat Lines for Source Code

By Xah Lee. Date: . Last updated: .

Here's a command to reformat current paragraph into 1 long line or multiple short lines.

When there's a text selection, work on that region. Else, work on current paragraph (between blank lines.).

This command is designed to work with programing language source code. It strictly exchange whitespaces and newline.

Code also available on github at https://github.com/xahlee/xah-reformat-code and https://melpa.org/#/xah-reformat-code

The main command is xah-reformat-lines.

These commands does not depends on fill-column or any of emacs fill commands.

(defun xah-reformat-lines ( &optional @length)
  "Reformat current text block into 1 long line or multiple short lines.
When there is a text selection, act on the selection, else, act on a text block separated by blank lines.

When the command is called for the first time, it checks the current line's length to decide to go into 1 line or multiple lines. If current line is short, it'll reformat to 1 long lines. And vice versa.

Repeated call toggles between formatting to 1 long line and multiple lines.

If `universal-argument' is called first, use the number value for min length of line. By default, it's 70.

URL `http://ergoemacs.org/emacs/emacs_reformat_lines.html'
Version 2017-07-15"
  (interactive)
  ;; This command symbol has a property “'is-longline-p”, the possible values are t and nil. This property is used to easily determine whether to compact or uncompact, when this command is called again
  (let* (
         (@length (if @length
                      @length
                    (if current-prefix-arg (prefix-numeric-value current-prefix-arg) 70 )))
         (is-longline-p
          (if (eq last-command this-command)
              (get this-command 'is-longline-p)
            (> (- (line-end-position) (line-beginning-position)) @length)))
         ($blanks-regex "\n[ \t]*\n")
         $p1 $p2
         )
    (if (use-region-p)
        (progn (setq $p1 (region-beginning))
               (setq $p2 (region-end)))
      (save-excursion
        (if (re-search-backward $blanks-regex nil "move")
            (progn (re-search-forward $blanks-regex)
                   (setq $p1 (point)))
          (setq $p1 (point)))
        (if (re-search-forward $blanks-regex nil "move")
            (progn (re-search-backward $blanks-regex)
                   (setq $p2 (point)))
          (setq $p2 (point)))))
    (progn
      (if current-prefix-arg
          (xah-reformat-to-multi-lines $p1 $p2 @length)
        (if is-longline-p
            (xah-reformat-to-multi-lines $p1 $p2 @length)
          (xah-reformat-whitespaces-to-one-space $p1 $p2)))
      (put this-command 'is-longline-p (not is-longline-p)))))

(defun xah-reformat-whitespaces-to-one-space (@begin @end)
  "Replace whitespaces by one space.

URL `http://ergoemacs.org/emacs/emacs_reformat_lines.html'
Version 2017-01-11"
  (interactive "r")
  (save-excursion
    (save-restriction
      (narrow-to-region @begin @end)
      (goto-char (point-min))
      (while
          (search-forward "\n" nil "move")
        (replace-match " "))
      (goto-char (point-min))
      (while
          (search-forward "\t" nil "move")
        (replace-match " "))
      (goto-char (point-min))
      (while
          (re-search-forward "  +" nil "move")
        (replace-match " ")))))

(defun xah-reformat-to-multi-lines ( &optional @begin @end @min-length)
  "Replace spaces by a newline at places so lines are not long.
When there is a text selection, act on the selection, else, act on a text block separated by blank lines.

If `universal-argument' is called first, use the number value for min length of line. By default, it's 70.

URL `http://ergoemacs.org/emacs/emacs_reformat_lines.html'
Version 2017-07-06"
  (interactive )
  (let (
        $p1 $p2
        ($blanks-regex "\n[ \t]*\n")
        ($minlen (if @min-length
                     @min-length
                   (if current-prefix-arg (prefix-numeric-value current-prefix-arg) 70 ))))
    (if (and  @begin @end)
        (setq $p1 @begin $p2 @end)
      (if (region-active-p)
          (progn (setq $p1 (region-beginning) $p2 (region-end)))
        (save-excursion
          (if (re-search-backward $blanks-regex nil "move")
              (progn (re-search-forward $blanks-regex)
                     (setq $p1 (point)))
            (setq $p1 (point)))
          (if (re-search-forward $blanks-regex nil "move")
              (progn (re-search-backward $blanks-regex)
                     (setq $p2 (point)))
            (setq $p2 (point))))))
    (save-restriction
      (narrow-to-region $p1 $p2)
      (goto-char (point-min))
      (while
          (re-search-forward " +" nil "move")
        (when (> (- (point) (line-beginning-position)) $minlen)
          (replace-match "\n" ))))))

When working on programing language code, we want to reformat lines by a exchange of whitespaces.

  1. A whitespace here is defined to be one of {space, tab, newline character}.
  2. A whitespace sequence (or, whitespaces), is 1 or more consecutive whitespace.
  3. Any whitespace sequence (WS) is considered equivalent to any other whitespace sequence.
  4. The only change allowed is swapping one WS by another.
  5. A WS is never created if it didn't exist before.
  6. A WS is never deleted.

The advantage of the code above is that they do not call emacs fill-region commands.

fill-region sometimes delete non-whitespace characters.

fill-region sometimes remove whitespaces to none, or create whitespaces.

Here's examples of fill-region issues.

whitespaces added

- Xxxx xx xxxxxxxxx xxxxxxx xxxxxxx xxxx xxxxxxx xx xxxxxxxxxx xxxxxxxx xxxx, xxx xxx'x xxxx xxxxxxx xxxx xxx xxxxx xxxx xxxxxxxxxxxx xxxxx xxxx xxxx xxxxxx. Xxx xxxx-xxxxxx xxxxxxxx xxxxxxxxx xxxx xxx xxxxxx xx xxxxx xxxx, xx xxxxxx xxxxxx xxxxxxx xxxxxx x xxxxx xx xxxxx.

after fill-paragraph.

adaptive-fill-mode is t

becomes

- Xxxx xx xxxxxxxxx xxxxxxx xxxxxxx xxxx xxxxxxx xx xxxxxxxxxx
  xxxxxxxx xxxx, xxx xxx'x xxxx xxxxxxx xxxx xxx xxxxx xxxx
  xxxxxxxxxxxx xxxxx xxxx xxxx xxxxxx. Xxx xxxx-xxxxxx xxxxxxxx
  xxxxxxxxx xxxx xxx xxxxxx xx xxxxx xxxx, xx xxxxxx xxxxxx xxxxxxx
  xxxxxx x xxxxx xx xxxxx.

Notice the extra whitespaces.

if adaptive-fill-mode is nil, then this won't happen.

adaptive-fill-mode is t by default.

elisp code here for convenience.

(setq adaptive-fill-mode nil)
(setq adaptive-fill-mode t)

Asterisks Deleted

* Xxxx xx xxxxxxxxx xxxxxxx xxxxxxx xxxx xxxxxxx xx xxxxxxxxxx
* xxxxxxxx xxxx, xxx xxx'x xxxx xxxxxxx xxxx xxx xxxxx xxxx
* xxxxxxxxxxxx xxxxx xxxx xxxx xxxxxx. Xxx xxxx-xxxxxx xxxxxxxx
* xxxxxxxxx xxxx xxx xxxxxx xx xxxxx xxxx, xx xxxxxx xxxxxx xxxxxxx
* xxxxxx x xxxxx xx xxxxx.

after fill-paragraph with big fill-column value.

adaptive-fill-mode is t

becomes

* Xxxx xx xxxxxxxxx xxxxxxx xxxxxxx xxxx xxxxxxx xx xxxxxxxxxx xxxxxxxx xxxx, xxx xxx'x xxxx xxxxxxx xxxx xxx xxxxx xxxx xxxxxxxxxxxx xxxxx xxxx xxxx xxxxxx. Xxx xxxx-xxxxxx xxxxxxxx xxxxxxxxx xxxx xxx xxxxxx xx xxxxx xxxx, xx xxxxxx xxxxxx xxxxxxx xxxxxx x xxxxx xx xxxxx.

The asterisks are gone.

if adaptive-fill-mode is nil, then this won't happen.

elisp code here for convenience.

(setq fill-column 3045708747)
(setq fill-column 70) ; default

Whitespace Created When There Was None

混沌未分天地乱,茫茫渺渺无人见。自从盘古破鸿蒙,开辟从兹清浊辨。覆载群生仰至仁,发明万物皆成善。欲知造化会元功,须看西游释厄传。一派白虹起,千寻雪浪飞;海风吹不断,江月照还依。冷气分青嶂,馀流润翠微;潺湲名瀑布,真似挂帘帷。

after fill-paragraph.

becomes

混沌未分天地乱,茫茫渺渺无人见。自从盘古破鸿蒙,开辟从兹清浊辨。覆载群
生仰至仁,发明万物皆成善。欲知造化会元功,须看西游释厄传。一派白虹起,
千寻雪浪飞;海风吹不断,江月照还依。冷气分青嶂,馀流润翠微;潺湲名瀑布,
真似挂帘帷。

whitespaces are created at positions when there was none.

This is a problem when displayed in browser. Because, the newline characters will create a gap, and gaps in Chinese can change meaning.

Version Calling Emacs Fill

For a version that calls emacs fill-region, see Emacs: fill-region, unfill-region Toggle.

Liket it? Put $5 at patreon. Or Buy Xah Emacs Tutorial. Thanks.