English | Русский

Blog on Linux and Wheels

Soft Wrapping in Emacs

If you are using plain text regularly probably you prefer wrapping text with hard breaks. Hard wrapping ensures that text remains readable regardless how large the window size is. But there are cases when soft wrapping is desirable. For example, you may want it when viewing text with no hard breaks (say, in incoming emails). Other examples are services like GitHub which expect emails to be softly wrapped and web forms which usually don't work well with hard-wrapped input. The ideal solution would be if we were able to work with soft-wrapped text like if it was hard-wrapped.

To achieve this goal in Emacs we will need two modes. The first one is visual-line-mode. It's a built-in mode which redefines the commands so they apply to visual lines instead of hard lines. The second one is called visual-fill-column-mode. When active the mode softly wraps text on the specific column (fill-column by default). When both modes enabled it should feel like you're working with hard-wrapped text with enabled auto-fill-mode.

Now you would probably like to see some indication of whether paragraph is hard- or soft-wrapped. You can show fringes for visual lines as follows:

(setq visual-line-fringe-indicators '(left-curly-arrow right-curly-arrow))

Though I prefer using whitespace-mode to display hard newlines. If I were writing this blog post with no hard breaks in paragraphs it would look like this (notice the symbol at the line endings):

My whitespace-mode setup responsible for showing indicators is the following:

(setq whitespace-display-mappings
    '((newline-mark 10 [?↷ 10])))      ; newline

(eval-after-load 'whitespace
  (lambda ()
    (set-face-attribute 'whitespace-newline nil :foreground "#d3d7cf")))

I'm activating the whitespace-mode alongside with visual-fill-column-mode in visual-line-mode hook as follows:

(defvar my-visual-line-state nil)

(defun my-visual-line-mode-hook ()
  (when visual-line-mode
    (setq my-visual-line-state
          `(whitespace-style ,whitespace-style
            whitespace-mode ,whitespace-mode
            auto-fill-mode ,auto-fill-function))

    (when whitespace-mode
      whitespace-mode -1)

    ;; display newline characters with whitespace-mode
    (make-local-variable 'whitespace-style)
    (setq whitespace-style '(newline newline-mark))

    ;; disable auto-fill-mode
    (when auto-fill-function
      (auto-fill-mode -1))

    ;; visually wrap text at fill-column

(add-hook 'visual-line-mode-hook 'my-visual-line-mode-hook)

Now to exit visual-line-mode we need to disable all the modes we activated and revert the settings we modified. So I have a dedicated function to disable visual-line-mode:

(defun my-visual-line-mode-off ()
  (visual-line-mode -1)
  ;; revert the state before activating visual-line-mode
  (when my-visual-line-state
    (let ((ws-style (plist-get my-visual-line-state 'whitespace-style))
          (ws-mode (plist-get my-visual-line-state 'whitespace-mode))
          (af-mode (plist-get my-visual-line-state 'auto-fill-mode)))

      (when whitespace-mode
        (whitespace-mode -1))
      (when ws-style (setq whitespace-style ws-style))
      (when ws-mode (whitespace-mode 1))

      (when af-mode (auto-fill-mode 1)))))

If I want to toggle visual-line-mode I call the following function:

(defun my-visual-line-mode-toggle ()
  (if visual-line-mode
    (visual-line-mode 1)))

Finally, I mapped the visual line mode toggling to the key sequence qm (see key-seq):

(key-seq-define-global "qm" 'my-visual-line-mode-toggle)

If you're using email inside Emacs you may also want to activate visual-line-mode in the mode displaying emails (many promotional and transactional emails don't use hard wrap). For mu4e I'm just registering visual-line-mode on mu4e-view-mode's hook:

(add-hook 'mu4e-view-mode-hook 'visual-line-mode)

When replying to GitHub (and similar) emails I'm activating visual-line-mode for the new email via the following hook:

(add-hook 'mu4e-compose-mode-hook
  (defun my-mu4e-visual-line-reply ()
    "Activate visual-line-mode for specific services like GitHub."
    (let ((msg mu4e-compose-parent-message))
      (when (and msg (mu4e-message-contact-field-matches
                      msg :from '(".*@github.com$" ".*@upwork.com$")))

Whenever I want to toggle the visual-line-mode manually I press qm. If needed I adjust fill-column before that.

You can find my emacs.d setup on GitHub:


You can write here in Markdown