notes from /dev/null

by Charles Choi 최민수


Tuning Emacs to Write Prose in Org and Markdown

14 Aug 2023  Charles Choi

While default (aka vanilla or un-configured) Emacs is serviceable in editing Org and Markdown files, its out-of-the-box experience feels more like I’m programming said files rather than writing them. Thankfully, Emacs has excellent features (both built-in and external packages) to help you focus on writing prose. In this post I'll go over such features. As always, these recommendations are subjective, but I don’t think they fall too far from conventional thinking on the matter.

As to the features that I think help write prose:

  • The use of proportional typefaces
  • Hiding markup
  • Automatic spell checking
  • Dictionary lookup of a selected word
  • Menu-driven
    • Styling of selected text
    • Toggling of hidden markup
  • Dynamic completion of a previously written word
  • Turn-on word-wrapping
  • Navigation and moving of text
  • Deleting whitespace
  • Word count

Of course all of the above can be keyboard-driven (this is Emacs) but I like the affordance that menus can provide for text operations, particularly for selected text. Nothing wrong with having both.

Before moving forward, I’d also clarify that I’m not defining an explicit Emacs mode for distraction-free interactions as there are other modes which offer this. This post is really about configuration. That all said, let’s configure Emacs!

Prerequisites

This post presumes you understand enough of the following:

All the configurations in this post have been tested with Emacs 28.2.

Use Proportional Typefaces

For prose, a well-chosen proportional typeface is vastly more readable than a fixed-width one. You can turn-on rendering a proportional typeface in an Emacs buffer by enabling variable-pitch-mode, typically by adding it via a mode hook in the init file. An Elisp example that does this for both Org and Markdown modes is shown below:

1
2
(add-hook 'org-mode-hook 'variable-pitch-mode)
(add-hook 'markdown-mode-hook 'variable-pitch-mode)

When variable-pitch-mode is turned-on, the typeface used to render overlay-free text in the buffer is defined in the Elisp variable variable-pitch. Customize variable-pitch to taste. A menu-driven workflow for configuring any face is conveniently done via the menu-bar flow OptionsCustomize EmacsSpecific Face… which will prompt you for the face to customize.

Another setting to improve readability is to increase the line spacing via the Elisp variable line-spacing. As both the Org and Markdown major modes inherit from the Text major mode, we can add a hook to set line-spacing for both to text-mode-hook in the init file.

1
2
(add-hook 'text-mode-hook (lambda ()
                            (setq-local line-spacing 0.1)))

More granular approaches to adjusting line height can be achieved by configuring the height of a face.

Using a different typeface helps to distinguish headers from body text. For Markdown, that face variable is named markdown-header-face. For Org, there is a set of faces all corresponding to the header level, org-level-N, where N is a value between 1 and 8 inclusive.

While the reader can always choose their own typefaces, size, and spacing, my personal preference is to use the following settings:

face typeface height slant weight width Notes
default Menlo 150 normal normal normal
variable-pitch Optima 1.4 height is a scaling value based of default height
markdown-header-face Futura 1.1
org-level-[1-8] Futura 1.1 org-level-N inherits from to outline-N face, so actual configuration configures outline-1 face.

This link shows my implementation of the above settings using customize-face. Note that the above typefaces are pre-installed on macOS and may not be available for other platforms.

For reference, much of my guidance on typographic configuration is taken from zzamboni.org | Beautifying Org Mode in Emacs. Highly recommended for readers who want to delve further into this.

Hiding Markup

Both Org and Markdown modes provide variables which support hiding markup delimiters. This makes for a more distraction-free experience.

For Org mode, setting the variable org-hide-emphasis-markers to t will hide the markup around styled text. I also like setting the variable org-hide-leading-stars to t so that headers are not so noisy.

For Markdown mode, invoking the function markdown-toggle-markup-hiding as part of the initialization Markdown hook will achieve the same.

1
(add-hook 'markdown-mode-hook 'markdown-toggle-markup-hiding)

Don't fret about getting back your hidden text. Later in this post we will show how to easily reveal hidden markup.

Automatic Spellchecking

While spell checking support has long existed in Emacs, automatically checking text as you type is not turned on by default. Using the flyspell and company modes can remedy this, the first to automatically spell check a buffer and the latter to provide candidate replacements for a misspelled word. Enabling this in the init file via the text-mode-hook turns this on for both markdown-mode and org-mode.

1
2
(add-hook 'text-mode-hook 'flyspell-mode)
(add-hook 'text-mode-hook 'company-mode)

Don't like company mode? There are plenty of other completion packages to choose from.

Dictionary Lookup of a Selected Word

Mickey Peterson has an informative post on using the dictionary lookup feature in Emacs 28. However, since macOS is my daily driver, I actually prefer to use the native macOS dictionary via the osx-dictionary package. Regardless of what package you use, having immediate access to a dictionary while writing is extraordinarily useful. While both the native dictionary lookup and ox-dictionary are built to support keyboard-driven user interfaces, they can also be mouse-driven as well. Detailed below is how I’ve implemented a common GUI UX workflow where invoking a context-menu on a selected word will offer the option to look up that word’s definition.

First off, turn on context-menu-mode in your init file. Here I’ve turned it on via the text-mode-hook.

1
(add-hook 'text-mode-hook 'context-menu-mode)

Next up is defining your custom context menu. I’ve written an earlier post on customizing the context menu which I highly recommend reading if you are new to context menus. Adding a menu item to invoke the function osx-dictionary-search-word-at-point from the osx-dictionary package is shown in the code fragment below:

1
2
3
4
5
6
(when (use-region-p)
  (cc/context-menu-item-separator menu dictionary-operations-separator)
  (easy-menu-add-item menu nil ["Look Up" 
                                osx-dictionary-search-word-at-point
                                :label (cc/context-menu-last-word-in-region "Look Up")                                    
                                :help "Look up selected region in macOS dictionary"]))

The above code fragment is in the custom function cc/context-menu-addons which itself is added to the hook context-menu-functions.

To support context sensitivity, the predicate function use-region-p is used to determine if text is selected; if not then this menu item is not added to the context menu. Note that another custom function cc/context-menu-last-word-in-region is used to dynamically populate the menu item label with the last word in the selected region.

1
2
3
4
5
6
7
(defun cc/context-menu-last-word-in-region (prefix)
  "Generate context menu label with last word in region prepended by PREFIX."
  (let*  ((start (region-beginning))
         (end (region-end))
         (buf (buffer-substring start end))
         (last-word (car (last (split-string buf " ")))))
    (concat prefix " “" last-word "”")))

Menu-Driven Styling of Selected Text

Another common GUI UX workflow is to select text and style it (for example bold, italic, code, underline). To implement this behavior in Emacs, I defined a custom keymap cc/emphasize-menu whose source is shown below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
(easy-menu-define cc/emphasize-menu nil
  "Keymap for Emphasize Menu"
  '("Style"
    :visible (region-active-p)
    ["Bold" cc/emphasize-bold
     :enable (region-active-p)
     :visible (or (derived-mode-p 'org-mode) (derived-mode-p 'markdown-mode))
     :help "Bold selected region"]
    ["Italic" cc/emphasize-italic
     :enable (region-active-p)
     :visible (or (derived-mode-p 'org-mode) (derived-mode-p 'markdown-mode))     
     :help "Italic selected region"]
    ["Code" cc/emphasize-code
     :enable (region-active-p)
     :visible (or (derived-mode-p 'org-mode) (derived-mode-p 'markdown-mode))     
     :help "Code selected region"]
    ["Underline" cc/emphasize-underline
     :enable (region-active-p)
     :visible (derived-mode-p 'org-mode)     
     :help "Underline selected region"]
    ["Verbatim" cc/emphasize-verbatim
     :enable (region-active-p)
     :visible (derived-mode-p 'org-mode)
     :help "Verbatim selected region"]
    ["Strike Through" cc/emphasize-strike-through
     :enable (region-active-p)
     :visible (or (derived-mode-p 'org-mode) (derived-mode-p 'markdown-mode))     
     :help "Strike-through selected region"]))

Note the use of predicates for the :visible and :enable properties to support context-sensitive behavior. In the source code above, the functions that actually style the selected text each do the following: 1) test for the current major mode (in this case either Markdown or Org), and 2) invokes the mode-specific function to style the text. For example, to “bold” a text, the following function cc/emphasize-bold is called:

1
2
3
4
5
6
7
(defun cc/emphasize-bold ()
  (interactive)
  (cond ((derived-mode-p 'org-mode)
         (org-emphasize ?*))
        ((derived-mode-p 'markdown-mode)
         (markdown-insert-bold))
        (t nil)))

Definitions for all the style (emphasize) functions are at this link.

Menu-Driven Toggling of Hiding Markup (aka “Reveal Codes”)

For those users familiar with WordPerfect, it had a feature called Reveal Codes which enabled the user to explicitly see the codes used to format a document. This same behavior can be emulated for Org and Markdown buffers, via the visible-mode and markdown-toggle-markup-hiding functions, respectively.

The following code fragment from cc/context-menu-addons shows how visible-mode is invoked for Org mode.

1
2
3
4
(easy-menu-add-item menu nil
                    ["Toggle Reveal Markup"
                     visible-mode
                     :help "Toggle making all invisible text temporarily visible (Visible mode)"])

The following code fragment from cc/context-menu-addons shows how markdown-toggle-markup-hiding is invoked for Markdown mode.

1
2
3
4
(easy-menu-add-item menu nil
                    ["Toggle Reveal Markup"
                     markdown-toggle-markup-hiding
                     :help "Toggle the display or hiding of markup"])))

Context-sensitivity of how the above menu items are displayed is handled by the control flow of cc/context-menu-addons, which tests for current major mode.

Dynamic Completion of a Previously Written Word

Ever have to type a word in a document that you were going to use multiple times? The function dabbrev-expand (keyboard shortcut M-/) is your friend. There’s nothing to turn on, you get this behavior for free. Coupled with company-mode and you will wonder how you lived without it.

Turn-on Word Wrapping

While this is very much a personal preference, over the years I’ve found that treating a text file newline as a paragraph delimiter to be the least-surprising convention to use. With that, turning-on word-wrapping is done via visual-line-mode. I turn this on for all text mode buffers via the text-mode-hook.

1
(add-hook 'text-mode-hook 'visual-line-mode)

Source link for the above code in my init file.

Navigation and Moving of Text

Mastering both navigation and moving text in Emacs can go a long way towards helping you write better prose as you can focus more on expressing your thoughts instead of just typing.

If you’re going to spend a considerable time with either Markdown or Org, I strongly recommend that you learn the heading/block navigation commands provided by the Org and Markdown major modes. In addition, if you haven’t tried Avy yet, stop here and go make it a part of your life. Emacs Elements provides a video demo of Avy at work, and Irreal posts here on how they use it.

My previous post Moving Text Elegantly in Emacs described how to move around a text object such as a word, sentence, or balanced expression (sexp) forward or backwards, among other operations involving the transpose family of functions.

A fairly recent post on sorting text in Emacs by Susam Pal shows how you can make short work of sorting lines. Highly recommended.

Deleting Whitespace

Also useful when reviewing text is to delete whitespace, either vertically in the form of blank lines or horizontally in the form of multiple spaces. Emacs provides a number of functions to address these concerns. While those functions can always be invoked via keyboard, I find it convenient to be access them via menu as well. Shown below is a custom keymap cc/delete-space-menu invoking some functions that I find useful in this regard.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
(easy-menu-define cc/delete-space-menu nil
  "Keymap for Deleting Space submenu"
  '("Delete Space"
    :visible (not buffer-read-only)
    ["Join Line" join-line
     :help "Join this line to previous and fix up whitespace at join"]
    ["Just One Space" just-one-space
     :help "Delete all spaces and tabs around point, leaving one space."]
    ["Delete Horizontal Space" delete-horizontal-space
     :help "Delete all spaces and tabs around point."]
    ["Delete Blank Lines" delete-blank-lines
     :help "On blank line, delete all surrounding blank lines, leaving just one."]))

The keymap cc/delete-space-menu can be incorporated into a custom context menu as shown here and into the menu bar here.

Word Count

A common metric is the count of words in a document. The function count-words can determine this, which can be called from a context menu with the code fragment below:

1
2
3
4
5
6
7
8
9
(when (derived-mode-p 'text-mode)
      (if (use-region-p)
          (easy-menu-add-item menu nil ["Count Words in Region"
                                        count-words
                                        :help "Count words in region"])

        (easy-menu-add-item menu nil ["Count Words in Buffer"
                                      count-words
                                      :help "Count words in buffer"])))

Closing

This post suggests a number of configurations to tune Emacs for writing prose, with an emphasis on support for menu-driven operations. Hope that you find them useful!

References

A lot of this post was sourced from other posts over the years. For further reading, I encourage you follow the links below.

emacs   org mode   markdown

 

AboutMastodonInstagramGitHub

Feeds & TagsGet Captee for macOS

Powered by Pelican