Doom Emacs & Org-Mode

2049 words; 11 minute(s)

Table of Contents

Screenshots

These screenshots are showing a project opened with projectile, a treemacs side pane open with the project contents, multiple buffers tiled next to each other, and the help pane open at the bottomm.

The themes are doom-homage-white and doom-homage-black.

Doom Emacs Light
Mode

Doom Emacs Dark
Mode

Getting Started

I have been switching back and forth between markdown and org-mode recently for my personal note taking, wiki, and even this blog. As a result, I have been stumbling further into the world of Emacs and found myself at a point where I now prefer to do most of my basic editing within Emacs.

I'll leave the markdown vs. org-mode debate for another post, but I love org-mode's extensibility and interactive nature within Emacs, but it becomes very unwieldy in any other client implementation of org-mode - especially on iOS. On the flip side, markdown is limited in functionality and fractured into different standards, but it's simple and popular enough that there are a plethora of great clients to choose from that will get the job done.

For now, I want to focus on how I have been using Emacs and some of the things that would have helped me learn it faster had I known where to start.

Installation

This post focuses on Doom Emacs, which is an Emacs framework that provides an alternative experience to the vanilla GNU Emacs.

The Getting Start Guide has an extremely detailed walkthrough of installation for all systems, so please refer to that guide for up-to-date instructions.

I chose to install on macOS, using the Homebrew option with the railwaycat/emacsmacport version of Emacs.

Once the program is installed, you can run the program by typing emacs in a terminal. If you installed a version of Emacs that supports both a GUI and TUI, you will have to run emacs -nw to get the TUI instead of the default GUI.

Configuration

Once installed, you can configure Doom by editing the files within the ~/.doom.d/ directory. This directory holds four files:

  1. config.el - Personal configuration file
  2. custom.el - Custom set variables
  3. init.el - Doom modules and load order, must run doom sync after modifying
  4. packages.el - Declare packages to install in this file, then run doom sync to install

I only needed a few customizations for my configuration, so I'll list them below.

;; ~/.doom.d/config.el
(setq doom-theme 'doom-homage-black)
(setq display-line-numbers-type t)
(setq org-directory "~/Documents/Notes/")

;; lengthy org-publish directives at the bottom of the file
;; ~/.doom.d/init.el
(doom! :input
       :completion
       company           ; the ultimate code completion backend
       vertico           ; the search engine of the future

       :ui
       doom              ; what makes DOOM look the way it does
       doom-dashboard    ; a nifty splash screen for Emacs
       (emoji +unicode)  ; 🙂
       hl-todo           ; highlight TODO/FIXME/NOTE/DEPRECATED/HACK/REVIEW
       minimap           ; show a map of the code on the side
       modeline          ; snazzy, Atom-inspired modeline, plus API
       ophints           ; highlight the region an operation acts on
       (popup +defaults)   ; tame sudden yet inevitable temporary windows
       tabs              ; a tab bar for Emacs
       treemacs          ; a project drawer, like neotree but cooler
       (vc-gutter +pretty) ; vcs diff in the fringe
       vi-tilde-fringe   ; fringe tildes to mark beyond EOB
       workspaces        ; tab emulation, persistence & separate workspaces

       :editor
       (evil +everywhere); come to the dark side, we have cookies
       file-templates    ; auto-snippets for empty files
       fold              ; (nigh) universal code folding
       snippets          ; my elves. They type so I don't have to

       :emacs
       dired             ; making dired pretty [functional]
       electric          ; smarter, keyword-based electric-indent
       undo              ; persistent, smarter undo for your inevitable mistakes
       vc                ; version-control and Emacs, sitting in a tree

       :term
       term              ; basic terminal emulator for Emacs

       :checkers
       syntax              ; tasing you for every semicolon you forget

       :tools
       (eval +overlay)     ; run code, run (also, repls)
       lookup              ; navigate your code and its documentation
       magit             ; a git porcelain for Emacs

       :os
       (:if (featurep :system 'macos) macos)  ; improve compatibility with macOS

       :lang
       common-lisp       ; if you've seen one lisp, you've seen them all
       emacs-lisp        ; drown in parentheses
       markdown          ; writing docs for people to ignore
       org               ; organize your plain life in plain text
       python            ; beautiful is better than ugly
       sh                  ; she sells {ba,z,fi}sh shells on the C xor

       :app
       irc               ; how neckbeards socialize
       (rss +org)        ; emacs as an RSS reader

       (default +bindings +smartparens))

If you're editing these files within Doom directly, remember to run SPC h r r to reload the configuration. Also remember to run doom sync for any changes to the init.el or packages.el files.

Basic Functionality

I kept a cheat sheet note open at first with all of the basic functions typed out, copied as I went through the tutorial. After a little while, I no longer needed it. I highly recommend writing down the most applicable shortcuts for your preferred functionality and refer back to it until you've memorized it.

Memorizing the shortcuts will differ based on the type of Emacs framework being used. Personally, migrating from vanilla Emacs to Doom Emacs simplified everything by a large factor and instantly enabled me to start working on my projects, eliminating most of the hurdles I was running into. The vanilla emacs hotkeys became obnoxious and I actually stopped using Emacs entirely for about a month before trying Doom.

For me, the first logical step is to interact with the local filesystem. To do this, I needed to know how to open directories, open files, save files, discard changes, close files, and switch between open files. Here are some example shortcuts I've written down in order to accomplish file-based actions.

Doom HotkeyEmacs HotkeyDescription
SPC :C-xRun functions
SPC f fC-x fOpen file in buffer
SPC f dC-x dOpen directory with dired
iC-x C-qEdit current buffer (insert mode)
qC-x C-qQuit out of insert mode
SPC f sC-x sSave current buffer
SPC b kC-x kKill current buffer
SPC w h/j/k/lC-x o1Move left/down/up/right to next buffer
1

Doom's evil-window functionality is a bit different from GNU Emacs, but you can always switch to the "other" buffer with C-x o or C-x b to get a list of buffers to select.

In general, when in Doom, you can press SPC and wait a second for the help pane to appear with all available hotkey options. For example, you can press SPC, wait for the help pane, and then select a key such as g to enter the git help pane and explore further command options.

Editing

Next in my process is to dive into editing for any languages I'm currently using. In this post, I will just cover Markdown and Org-Mode but I have also been slowly adoping some Python and general web dev tools as well.

Markdown

Markdown
Preview

Markdown is fairly simple as the syntax is limited, so just make sure the ~/.doom.d/init.el includes the markdown declaration in the :lang section.

This package includes the following hotkey menus. The insert and toggle menu expands further, allowing you to insert various markdown elements and toggle things like link hiding.

Doom HotkeyFunction
SPC m 'markdown-edit-code-block
SPC m emarkdown-export
SPC m i+insert
SPC m omarkdown-open
SPC m pmarkdown-preview
SPC m t+toggle
SPC : markdown-table-alignmarkdown-table-align

Org-Mode

Org-Mode Preview

Similar to the markdown section above, ensure that the ~/.doom.d/init.el includes the org declaration in the :lang section.

There are a few hot keys, but a quick search with SPC : org shows that there are 865 possible org-related functions you can run. I won't possibly be able to list them all, so I will simply cover a few of the basic commands I use myself.

Doom HotkeyFunction
SPC m torg-todo
SPC n torg-todo-list
SPC o Aorg-agenda
SPC Xorg-capture
SPC m p porg-priority
SPC m d sorg-schedule
TABorg-cycle
SHIFT TABCollapse/open all headings in buffer
M-qFormat/wrap current section
M-Left/RightDemote/promote current heading
M-Down/UpShift current heading section down/up
  1. Org-Publish

    Org includes a publishing management system by default that allows you to export org files to Org, iCalendar, HTML, LaTex, Markdown, ODT, and Plain Text. Most of these can be exported into another buffer and opened, or simply to an external file.

    While inside an org file, simply run SPC m e or M-x org-export-dispatch to open the export menu. This menu will show all options and ask you to select an option. If you want to export to HTML, simply press h and then H (As HTML buffer), h (As HTML file), or o (As HTML file and open).

  2. Projects

    Some publishing options are easier with a defined project in Emacs. To create a project within Emacs, I use two methods:

    1. Add the project via the projectile command SPC p a. Does not always work for me.
    2. Add an empty .projectile file in the project root.

    Once a project has been created, you can create custom publishing actions within your ~/.doom.d/config.el file. For example, here's a test project I created to try and convert this blog to org-mode recently.

    ;; org-publish
    (require 'ox-publish)
    
    (defun my/org-sitemap-date-entry-format (entry style project) "Format ENTRY in
      org-publish PROJECT Sitemap format ENTRY ENTRY STYLE format that includes
      date." (let ((filename (org-publish-find-title entry project))) (if (= (length
      filename) 0) (format "*%s*" entry) (format "{{{timestamp(%s)}}}
      [[file:%s][%s]]" (format-time-string "%Y-%m-%d" (org-publish-find-date entry
      project)) entry filename))))
    
    (setq org-export-global-macros '(("timestamp" . "@@html:<time datetime='[$1]'
          class='timestamp'>[$1]</time>@@")))
    
    (setq org-publish-project-alist
          `(("blog"
             :base-directory "~/Source/cleberg.net/"
             :base-extension "org"
             :recursive t
             :publishing-directory "~/Source/cleberg.net/public/"
             :publishing-function org-html-publish-to-html
             ;; HTML5
             :html-doctype "html5"
             :html-html5-fancy t
             ;; Disable some Org's HTML defaults
             :html-head-include-scripts nil
             :html-head-include-default-style nil
             :section-numbers nil
             :with-title nil
             ;; Sitemap
             :auto-sitemap t
             :sitemap-title: "Sitemap"
             :sitemap-sort-files anti-chronologically
             ; :sitemap-function my/org-sitemap-date-entry-format
             ;; Customize HTML output
             :html-divs ((preamble "header" "preamble")
                         (content "main" "content")
                         (postamble "footer" "postamble"))
             :html-head "<meta name='theme-color' content='#111' media='(prefers-color-scheme: dark)'>
                         <meta name='theme-color' content='#fff' media='(prefers-color-scheme: light)'>
                         <link rel='stylesheet' href='/syntax-theme-dark.css' media='(prefers-color-scheme: dark)'>
                         <link rel='stylesheet' href='/syntax-theme-light.css' media='(prefers-color-scheme: light)'>
                         <link rel='stylesheet' href='/styles.css' type='text/css'>"
             :html-preamble "<nav class='site-nav' aria-label='site-nav' role='navigation'>
                    <ul>
                            <li><a href='/'>Home</a></li>
                            <li><a href='/blog/'>Blog</a></li>
                            <li><a href='/services/'>Services</a></li>
                            <li><a href='/wiki/'>Wiki</a></li>
                    </ul></nav>
                    <h1>%t</h1>
                    <time datetime='%d'>%d</time>"
             :html-postamble "
                    <p>Last build: %T</p>
                    <p>Created with %c</p>"
            )
    
            ("static"
             :base-directory "~/Source/cleberg.net/static/"
             :base-extension "css\\|txt\\|jpg\\|gif\\|png"
             :recursive t
             :publishing-directory  "~/Source/cleberg.net/public/"
             :publishing-function org-publish-attachment)
    
            ("cleberg.net" :components ("blog" "static"))))
    

General Thoughts

I have enjoyed Doom Emacs (far more than GNU Emacs) and will likely continue to use it as my main editor for the time being. Org-Mode is certainly the largest factor here, as I far prefer it over Markdown due to its inherent features and detailed markup options. However, working with org-mode on iOS has been a pain and I will have to see if there's an easier way to resolve those issues or if going back to separate Markdown, Reminders, and Calendar apps is easier to work with than an all-in-one org solution.