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)) (whitespace-mode) ;; disable auto-fill-mode (when auto-fill-function (auto-fill-mode -1)) ;; visually wrap text at fill-column (visual-fill-column-mode))) (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 () (interactive) (visual-fill-column-mode--disable) (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 () (interactive) (if visual-line-mode (my-visual-line-mode-off) (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$"))) (visual-line-mode)))))
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:
Comments