Skip to content

Latest commit

 

History

History
2899 lines (2335 loc) · 115 KB

init.org

File metadata and controls

2899 lines (2335 loc) · 115 KB

My Emacs config file

This is my Emacs configuration file.

This file is written in literate programming style using org-mode. See init.el for the generated file. You can see this in a nicer format on my blog post My Emacs Configuration, With Commentary.

Note: I no longer maintain this configuration (though I frequently refer to it when I need to remember how to do something). I now use Doom Emacs, you can find my current Emacs config at https://github.com/zzamboni/dot-doom/blob/master/doom.org.

Table of Contents

References

Emacs config is an art, and I have learned a lot by reading through other people’s config files, and from many other resources. These are some of the best ones (several are also written in org mode). You will find snippets from all of these (and possibly others) throughout my config.

Performance optimization

Lately I’ve been playing with optimizing my Emacs load time. I have found a couple of useful resources, including:

Based on these, I have added the code below.

First, we wrap the whole init file in a block that sets file-name-handler-alist to nil to prevent any special-filename parsing of files loaded from the init file (e.g. remote files loaded through tramp, etc.). The let block gets closed in the Epilogue.

(let ((file-name-handler-alist nil))

Next, a hook that reports how long and how many garbage collections the startup took. We use a hook to run it at the very end, so the message doesn’t get clobbered by other messages during startup.

(add-hook 'emacs-startup-hook
          (lambda ()
            (message "Emacs ready in %s with %d garbage collections."
                     (format "%.2f seconds"
                             (float-time
                              (time-subtract after-init-time before-init-time)))
                     gcs-done)))

Optionally enable debug-on-error - I do this only when I’m trying to figure out some problem in my config.

;;  (setq debug-on-error t)

If the gcmh package is already installed, load and enable it early. If not, this gets installed a bit later in the Package Management section. This package manages the garbage collection thresholds and scheduling to improve performance. DISABLED for now because it seems to cause performance degradation when inserting text in long files.

;; (when (require 'gcmh nil t)
;;   (gcmh-mode 1))

We set gc-cons-threshold to its maximum value, to prevent any garbage collection from happening during load time. We also reset this value in the Epilogue.

(setq gc-cons-threshold most-positive-fixnum)

Customized variables

Emacs has its own Customization mechanism for easily customizing many parameters. To make it easier to manage, I keep the customized variables and faces in a separate file and load it from the main file. A lot of my custom settings are configured from this init file as well, but there are always some which I change by hand for added flexibility.

(setq custom-file "~/.emacs.d/custom.el")
(load custom-file)

My current custom.el file can be found at https://github.com/zzamboni/dot-emacs/blob/master/custom.el.

Package management

I use the wonderful use-package to manage most of the packages in my installation (one exception is org-mode, see below). As this is not bundled yet with Emacs, the first thing we do is install it by hand. All other packages are then declaratively installed and configured with use-package. This makes it possible to fully bootstrap Emacs using only this config file, everything else is downloaded, installed and configured automatically.

First, we declare the package repositories to use.

(custom-set-variables '(package-archives
                        '(("marmalade" . "https://marmalade-repo.org/packages/")
                          ("melpa"     . "https://melpa.org/packages/")
                          ("elpa"      . "https://elpa.gnu.org/packages/"))))

Then we initialize the package system, refresh the list of packages and install use-package if needed.

(package-initialize)

(when (not package-archive-contents)
  (package-refresh-contents))

(when (not (package-installed-p 'use-package))
  (package-install 'use-package))

Finally, we load use-package.

(require 'use-package)

We set some configuration for use-package:

  • The use-package-always-ensure variable indicates that use-package should always try to install missing packages. For some libraries this is not appropriate, and in those cases you see the :ensure nil declaration as part of the use-package statement. This applies mainly to libraries which are installed as part of some other package (happens mostly with some libraries that come with org-mode).
    (custom-set-variables '(use-package-always-ensure t))
        
  • The use-package-always-defer sets :defer true as the default for all package declarations. This makes Emacs startup much faster by preventing packages from being loaded when Emacs starts, and only doing so when they are needed. Some packages don’t work well with this, so you’ll see some declarations when I explicitly set :defer nil to force the package to be loaded at startup, or :defer n to load the package, but only n seconds after startup.
    (custom-set-variables '(use-package-always-defer t))
        
  • The use-package-verbose variable enables verbose loading of packages, useful for debugging. I set/unset this according to need.
    (custom-set-variables '(use-package-verbose nil))
        

Testing =quelpa= and to install packages directly from their github repositories (and other places). I install quelpa using use-package first, and then install =quelpa-use-package= to allow using quelpa from within use-package declarations. Very recursive.

(use-package quelpa
  :defer nil
  :config
  (quelpa
   '(quelpa-use-package
     :fetcher git
     :url "https://github.com/quelpa/quelpa-use-package.git"))
  (require 'quelpa-use-package))
(require 'quelpa)
(quelpa-use-package-activate-advice)

This variable tells Emacs to prefer the .el file if it’s newer, even if there is a corresponding .elc file. Also, use auto-compile to autocompile files as needed.

(custom-set-variables '(load-prefer-newer t))
(use-package auto-compile
  :defer nil
  :config (auto-compile-on-load-mode))

Set the load path to the directories from where I sometimes load things outside the package system. Note that the path for specific packages like org-mode (which I load from a checkout of its git repository) is set as part of their use-package declarations, so they don’t appear here.

(add-to-list 'load-path "~/.emacs.d/lisp")

Giving a try to Paradox for an enhanced package management interface. We set paradox-github-token to t to disable GitHub integration (I don’t want to star repos).

(use-package paradox
  :defer nil
  :custom
  (paradox-github-token t)
  :config
  (paradox-enable))

Password management

Password management using auth-sources and pass (I normally use 1Password, but I have not found a good command-line/Emacs interface for it, so I am using pass for now for some items I need to add to my Emacs config file).

(require 'auth-source)
(require 'auth-source-pass)
(auth-source-pass-enable)

Miscellaneous settings

General settings

Prevent asking for confirmation to kill processes when exiting.

(custom-set-variables '(confirm-kill-processes nil))

Proxy settings

These are two short functions I wrote to be able to set/unset proxy settings within Emacs. I haven’t bothered to improve or automate this, as I pretty much only need it to be able to install packages sometimes when I’m at work. For now I just call them manually with M-x zz/(un)set-proxy when I need to.

(defun zz/set-proxy ()
  (interactive)
  (customize-set-variable 'url-proxy-services
                          '(("http"  . "proxy.corproot.net:8079")
                            ("https" . "proxy.corproot.net:8079"))))
(defun zz/unset-proxy ()
  (interactive)
  (customize-set-variable 'url-proxy-services nil))
  • Set default encoding.
    (set-language-environment "UTF-8")
    (prefer-coding-system       'utf-8)
    (set-default-coding-systems 'utf-8)
    (set-terminal-coding-system 'utf-8)
    (set-keyboard-coding-system 'utf-8)
    (setq default-buffer-file-coding-system 'utf-8)
        
  • Load the cl library to enable some additional macros (e.g. lexical-let).
    (require 'cl)
        
  • Install and load the async package to enable asynchronous operations (this gets loaded by some other packages, but I use it explicitly in zz/org-babel-async-tangle below, so I load it explicitly).
    (use-package async)
        
  • Start the Emacs server
    (server-start)
        
  • This is probably one of my oldest settings - I remember adding it around 1993 when I started learning Emacs, and it has been in my config ever since. When time-stamp is run before every save, the string Time-stamp: <> in the first 8 lines of the file will be updated with the current timestamp.
    (add-hook 'before-save-hook 'time-stamp)
        
  • When at the beginning of the line, make Ctrl-K remove the whole line, instead of just emptying it.
    (custom-set-variables '(kill-whole-line t))
        
  • Paste text where the cursor is, not where the mouse is.
    (custom-set-variables '(mouse-yank-at-point t))
        
  • Make completion case-insensitive.
    (setq completion-ignore-case t)
    (custom-set-variables
     '(read-buffer-completion-ignore-case t)
     '(read-file-name-completion-ignore-case t))
        
  • Show line numbers. I used linum-mode before, but it caused severe performance issues on large files. Emacs 26 introduces display-line-numbers-mode, which has no perceivable performance impact even on very large files. Disabled for now.
    (when (>= emacs-major-version 26)
      (use-package display-line-numbers
        :defer nil
        :ensure nil
        :config
        (global-display-line-numbers-mode)))
        
  • Highlight trailing whitespace in red, so it’s easily visible (disabled for now as it created a lot of noise in some modes, e.g. the org-mode export screen)
    (custom-set-variables '(show-trailing-whitespace nil))
        
  • Highlight matching parenthesis
    (show-paren-mode)
        
  • Don’t use hard tabs
    (custom-set-variables '(indent-tabs-mode nil))
        
  • Emacs automatically creates backup files, by default in the same folder as the original file, which often leaves backup files behind. This tells Emacs to put all backups in ~/.emacs.d/backups.
    (custom-set-variables
     '(backup-directory-alist
       `(("." . ,(concat user-emacs-directory "backups")))))
        
  • WinnerMode makes it possible to cycle and undo window configuration changes (i.e. arrangement of panels, etc.)
    (when (fboundp 'winner-mode) (winner-mode))
        
  • Add “unfill” commands to parallel the “fill” ones, bind A-q to unfill-paragraph and rebind M-q to the unfill-toggle command, which fills/unfills paragraphs alternatively.
    (use-package unfill
      :bind
      ("M-q" . unfill-toggle)
      ("A-q" . unfill-paragraph))
        
  • Save the place of the cursor in each file, and restore it upon opening it again.
    (use-package saveplace
      :defer nil
      :config
      (save-place-mode))
        
  • Provide mode-specific “bookmarks” - press M-i and you will be presented with a list of elements to which you can navigate - they can be headers in org-mode, function names in emacs-lisp, etc.
    (use-package imenu-anywhere
      :bind
      ("M-i" . helm-imenu-anywhere))
        
  • Smooth scrolling (line by line) instead of jumping by half-screens.
    (use-package smooth-scrolling
      :config
      (smooth-scrolling-mode 1))
        
  • Delete trailing whitespace before saving a file.
    (add-hook 'before-save-hook 'delete-trailing-whitespace)
        
  • Suppress “ad-handle-definition: .. redefined” warnings during Emacs startup.
    (custom-set-variables '(ad-redefinition-action (quote accept)))
        

System-specific configuration

Some settings maybe OS-specific, and this is where we set them. For now I only use Emacs on my Mac, so only the Mac section is filled out, but there are sections for Linux and Windows as well.

(cond ((eq system-type 'darwin)
       <<Mac settings>>
       )
      ((eq system-type 'windows-nt)
       <<Windows settings>>
       )
      ((eq system-type 'gnu/linux)
       <<Linux settings>>
       ))

Mac

First, we set the key modifiers correctly to my preferences: Make Command (⌘) act as Meta, Option as Alt, right-Option as Super

(custom-set-variables
 '(mac-command-modifier 'meta)
 '(mac-option-modifier 'alt)
 '(mac-right-option-modifier 'super))

We also make it possible to use the familiar ⌘-+ and ⌘-- to increase and decrease the font size. ⌘-= is also bound to “increase” because it’s on the same key in an English keyboard.

(bind-key "M-+" 'text-scale-increase)
(bind-key "M-=" 'text-scale-increase)
(bind-key "M--" 'text-scale-decrease)

Somewhat surprisingly, there seems to be no “reset” function, so I define my own and bind it to ⌘-0.

(defun zz/text-scale-reset ()
  (interactive)
  (text-scale-set 0))
(bind-key "M-0" 'zz/text-scale-reset)

We also use the exec-path-from-shell to make sure the path settings from the shell are loaded into Emacs (usually it starts up with the default system-wide path).

(use-package exec-path-from-shell
  :defer nil
  :config
  (exec-path-from-shell-initialize))

I enable mac-auto-operator-composition-mode to get ligatures. Note that this requires emacs-mac to be installed.

(if (fboundp 'mac-auto-operator-composition-mode)
    (mac-auto-operator-composition-mode))

The emacs-mac port I use binds C-M-SPC to the Mac “insert symbol” dialog, so I map A-M-SPC and M-s-SPC (which is generated by my external keyboard) to mark-sexp to select parenthesized expressions.

(bind-key "A-M-SPC" #'mark-sexp)
(bind-key "M-s-SPC" #'mark-sexp)

Linux

There are no Linux-specific settings for now.

Windows

There are no Windows-specific settings for now.

Keybindings

The which-key package makes Emacs functionality much easier to discover and explore: in short, after you start the input of a command and stop, pondering what key must follow, it will automatically open a non-intrusive buffer at the bottom of the screen offering you suggestions for completing the command. Extremely useful.

(use-package which-key
  :defer nil
  :diminish which-key-mode
  :config
  (which-key-mode))

I use the bind-key package to more easily keep track and manage user keybindings. bind-key comes with use-package so we just load it.

The main advantage of using this over define-key or global-set-key is that you can use M-x describe-personal-keybindings to see a list of all the customized keybindings you have defined.

(require 'bind-key)

Miscellaneous keybindings

  • M-g interactively asks for a line number and jump to it (goto-line).
    (bind-key "M-g" 'goto-line)
        
  • M-` focuses the next frame, if multiple ones are active (emulate the Mac “next app window” keybinding)
    (bind-key "M-`" 'other-frame)
        
  • Interactive search key bindings - visual-regexp-steroids provides sane regular expressions and visual incremental search. We make C-s and C-r run the visual-regexp functions. We leave C-M-s and C-M-r to run the default isearch-forward/backward functions, as a fallback. I use the pcre2el package to support PCRE-style regular expressions.
    (use-package pcre2el)
    (use-package visual-regexp-steroids
      :custom
      (vr/engine 'pcre2el "Use PCRE regular expressions")
      :bind
      ("C-c r" . vr/replace)
      ("C-c q" . vr/query-replace)
      ("C-r"   . vr/isearch-backward)
      ("C-S-s" . vr/isearch-forward)
      ("C-M-s" . isearch-forward)
      ("C-M-r" . isearch-backward))
        
  • Key binding to use ”hippie expand” for text autocompletion
    (bind-key "M-/" 'hippie-expand)
        

Emulating vi’s % key

One of the few things I missed in Emacs from vi was the % key, which jumps to the parenthesis, bracket or brace which matches the one below the cursor. This function implements the functionality. Inspired by http://www.emacswiki.org/emacs/NavigatingParentheses, but modified to use smartparens instead of the default commands, and to work on brackets and braces.

(defun zz/goto-match-paren (arg)
  "Go to the matching paren/bracket, otherwise (or if ARG is not
    nil) insert %.  vi style of % jumping to matching brace."
  (interactive "p")
  (if (not (memq last-command '(set-mark
                                cua-set-mark
                                zz/goto-match-paren
                                down-list
                                up-list
                                end-of-defun
                                beginning-of-defun
                                backward-sexp
                                forward-sexp
                                backward-up-list
                                forward-paragraph
                                backward-paragraph
                                end-of-buffer
                                beginning-of-buffer
                                backward-word
                                forward-word
                                mwheel-scroll
                                backward-word
                                forward-word
                                mouse-start-secondary
                                mouse-yank-secondary
                                mouse-secondary-save-then-kill
                                move-end-of-line
                                move-beginning-of-line
                                backward-char
                                forward-char
                                scroll-up
                                scroll-down
                                scroll-left
                                scroll-right
                                mouse-set-point
                                next-buffer
                                previous-buffer
                                previous-line
                                next-line
                                back-to-indentation
                                )))
      (self-insert-command (or arg 1))
    (cond ((looking-at "\\s\(") (sp-forward-sexp) (backward-char 1))
          ((looking-at "\\s\)") (forward-char 1) (sp-backward-sexp))
          (t (self-insert-command (or arg 1))))))

We bind this function to the % key.

(bind-key "%" 'zz/goto-match-paren)

Org mode

I have started using org-mode to writing, blogging, coding, presentations and more, thanks to the hearty recommendations and information from Nick and many others. I am duly impressed. I have been a fan of the idea of literate programming for many years, and I have tried other tools before (most notably noweb, which I used during grad school for many of my homeworks and projects), but org-mode is the first tool I have encountered which seems to make it practical. Here are some of the resources I have found useful in learning it:

This is the newest and most-in-flux section of my Emacs config, since I’m still learning org-mode myself.

I use use-package to load the org package, and put its configuration inside the corresponding sections for keybindings (:bind), custom variables (:custom), custom faces (:custom-face), hooks (:hook) and general configuration code (:config), respectively. The contents of each section is populated with the corresponding snippets that follow. See the sections below for the details on what goes into each configuration section, and some other configuration code that ends up outside this declaration.

(use-package org
  ;;    :pin manual
  :load-path ("lisp/org-mode/lisp" "lisp/org-mode/lisp/contrib/lisp")
  :bind
  (:map org-mode-map
   <<org-mode-keybindings>>)
  :custom
  <<org-mode-custom-vars>>
  :custom-face
  <<org-mode-faces>>
  :hook
  <<org-mode-hooks>>
  :config
  <<org-mode-config>>)

General Org Configuration

Note that mode-specific configuration variables are defined under their corresponding packages, this section defines only global org-mode configuration variables, which are inserted in the main use-package declaration for org-mode.

  • Default directory for org files (not all are stored here).
    (org-directory "~/org")
        
  • Automatically log done times in todo items.
    (org-log-done t)
        
  • Keep the indentation well structured by setting org-startup-indented to t. This is a must have. Makes it feel less like editing a big text file and more like a purpose built editor for org-mode that forces the indentation. Thanks Nick for the tip!
    (org-startup-indented t)
        

    By default, org-indent produces an indicator =”Ind”= in the modeline. We use diminish to hide it. I also like to increase the indentation a bit so that the levels are more visible.

    (use-package org-indent
      :ensure nil
      :diminish
      :custom
      (org-indent-indentation-per-level 4))
        
  • Log stuff into the LOGBOOK drawer by default
    (org-log-into-drawer t)
        

General Org Keybindings

Note that other keybindings are configured under their corresponding packages, this section defines only global org-mode keybindings, which are inserted in the main use-package declaration for org-mode.

  • Use the special C-a, C-e and C-k definitions for Org, which enable some special behavior in headings.
    (org-special-ctrl-a/e t)
    (org-special-ctrl-k t)
        
  • Set up C-c l to store a link to the current org object, in counterpart to the default C-c C-l to insert a link.
    ("C-c l" . org-store-link)
        
  • The default keybinding for org-mark-element is M-h, which in macOS hides the current application, so I bind it to A-h.
    ("A-h" . org-mark-element)
        

Enable Speed Keys, which allows quick single-key commands when the cursor is placed on a heading. Usually the cursor needs to be at the beginning of a headline line, but defining it with this function makes them active on any of the asterisks at the beginning of the line (useful with the font highlighting I use, as all but the last asterisk are sometimes not visible).

(org-use-speed-commands
 (lambda ()
   (and (looking-at org-outline-regexp)
        (looking-back "^\**"))))

Capturing stuff

First, I define some global keybindings to open my frequently-used org files (original tip from Learn how to take notes more efficiently in Org Mode).

I define a helper function to define keybindings that open files. Since I use the which-key package, it also defines the description of the key that will appear in the which-key menu. Note the use of lexical-let so that the lambda creates a closure, otherwise the keybindings don’t work.

(defun zz/add-file-keybinding (key file &optional desc)
  (lexical-let ((key key)
                (file file)
                (desc desc))
    (global-set-key (kbd key) (lambda () (interactive) (find-file file)))
    (which-key-add-key-based-replacements key (or desc file))))

Now I define keybindings to access my commonly-used org files, and add them to org-agenda-files

(custom-set-variables '(org-agenda-files
                        '("~/gtd" "~/Work/work.org.gpg" "~/org/ideas.org" "~/org/projects.org" "~/org/diary.org")))
(zz/add-file-keybinding "C-c f w" "~/Work/work.org.gpg" "work.org")
(zz/add-file-keybinding "C-c f i" "~/org/ideas.org" "ideas.org")
(zz/add-file-keybinding "C-c f p" "~/org/projects.org" "projects.org")
(zz/add-file-keybinding "C-c f d" "~/org/diary.org" "diary.org")

org-capture provides a generic and extensible interface to capturing things into org-mode in different formats. I set up C-c c as the default keybinding for triggering org-capture. Usually setting up a new capture template requires some custom code, which gets defined in the corresponding package config sections and included in the :config section below.

(use-package org-capture
  :ensure nil
  :after org
  :defer 1
  :bind
  ("C-c c" . org-capture)
  :config
  <<org-capture-config>>
  )

Define all my org agenda files as targets for refiling.

(setq org-refile-targets '((nil :maxlevel . 9)
                           (org-agenda-files :maxlevel . 9)))
(setq org-outline-path-complete-in-steps nil)         ; Refile in a single go
(setq org-refile-use-outline-path t)                  ; Show full paths for refiling

Task tracking

Org-Agenda is the umbrella for all todo, journal, calendar, and other views. I set up C-c a to call up agenda mode.

(use-package org-agenda
  :ensure nil
  :after org
  :bind
  ("C-c a" . org-agenda)
  :custom
  (org-agenda-include-diary t)
  (org-agenda-prefix-format '((agenda . " %i %-12:c%?-12t% s")
                              ;; Indent todo items by level to show nesting
                              (todo . " %i %-12:c%l")
                              (tags . " %i %-12:c")
                              (search . " %i %-12:c")))
  (org-agenda-start-on-weekday nil))

I also provide some customization for the holidays package, since its entries are included in the Org Agenda through the org-agenda-include-diary integration.

(use-package mexican-holidays
  :defer nil)
(quelpa '(swiss-holidays :fetcher github :repo "egli/swiss-holidays"))
(require 'swiss-holidays)
(setq swiss-holidays-zh-city-holidays
      '((holiday-float 4 1 3 "Sechseläuten") ;; meistens dritter Montag im April
        (holiday-float 9 1 3 "Knabenschiessen"))) ;; zweites Wochenende im September
(use-package holidays
  :defer nil
  :ensure nil
  :init
  (require 'mexican-holidays)
  :config
  (setq calendar-holidays
        (append '((holiday-fixed 1 1 "New Year's Day")
                  (holiday-fixed 2 14 "Valentine's Day")
                  (holiday-fixed 4 1 "April Fools' Day")
                  (holiday-fixed 10 31 "Halloween")
                  (holiday-easter-etc)
                  (holiday-fixed 12 25 "Christmas")
                  (solar-equinoxes-solstices))
                swiss-holidays
                swiss-holidays-labour-day
                swiss-holidays-catholic
                swiss-holidays-zh-city-holidays
                holiday-mexican-holidays)))

org-super-agenda provides great grouping and customization features to make agenda mode easier to use.

(require 'org-habit)
(use-package org-super-agenda
  :defer nil
  :custom
  (org-super-agenda-groups '((:auto-dir-name t)))
  :config
  (org-super-agenda-mode))

I configure org-archive to archive completed TODOs by default to the archive.org file in the same directory as the source file, under the “date tree” corresponding to the task’s CLOSED date - this allows me to easily separate work from non-work stuff. Note that this can be overridden for specific files by specifying the desired value of org-archive-location in the #+archive: property at the top of the file.

(use-package org-archive
  :ensure nil
  :custom
  (org-archive-location "archive.org::datetree/"))

Trying out org-gtd:

(use-package org-edna
  :defer nil)
(use-package org-gtd
  :defer 3
  :after org
  :config
  (require 'org-gtd)
  ;; these are the interactive functions you're likely to want to use as you go about GTD.
  (global-set-key (kbd "C-c d c") 'org-gtd-capture) ;; add item to inbox
  (global-set-key (kbd "C-c d p") 'org-gtd-process-inbox) ;; process entire inbox
  (global-set-key (kbd "C-c d a") 'org-agenda-list) ;; see what's on your plate today
  (global-set-key (kbd "C-c d n") 'org-gtd-show-all-next) ;; see all NEXT items
  (global-set-key (kbd "C-c d s") 'org-gtd-show-stuck-projects) ;; see projects that don't have a NEXT item

  ;; package: https://www.nongnu.org/org-edna-el/
  ;; org-edna is used to make sure that when a project task gets DONE,
  ;; the next TODO is automatically changed to NEXT.
  (setq org-edna-use-inheritance t)
  (org-edna-load)

  ;; package: https://github.com/Malabarba/org-agenda-property
  ;; this is so you can see who an item was delegated to in the agenda
  (setq org-agenda-property-list '("DELEGATED_TO"))
  ;; I think this makes the agenda easier to read
  (setq org-agenda-property-position 'next-line))
(add-to-list 'org-capture-templates
             '("i" "GTD item"
               entry (file (lambda () (org-gtd--path org-gtd-inbox-file-basename)))
               "* %?\n%U\n\n  %i"
               :kill-buffer t))
(add-to-list 'org-capture-templates
             '("l" "GTD item with link to where you are in emacs now"
               entry (file (lambda () (org-gtd--path org-gtd-inbox-file-basename)))
               "* %?\n%U\n\n  %i\n  %a"
               :kill-buffer t))

Note taking

I’m testing a new library called org-roam for non-hierarchical note taking.

Install and load dependencies first.

(use-package emacsql
  :defer nil)
(use-package emacsql-sqlite
  :after emacsql
  :defer nil)

Then we load org-roam itself.

(use-package org-roam
  :after (org emacsql emacsql-sqlite)
  :load-path "lisp/org-roam"
  :diminish
  :hook
  ((org-mode . org-roam-mode)
   (after-init . org-roam--build-cache-async))
  :custom
  (org-roam-directory "~/org")
  :bind
  ("C-c n l" . org-roam)
  ("C-c n t" . org-roam-today)
  ("C-c n f" . org-roam-find-file)
  ("C-c n i" . org-roam-insert)
  ("C-c n g" . org-roam-show-graph))

org-roam integrates nicely with =deft=:

(use-package deft
  :after org
  :bind
  ("C-c n d" . deft)
  :custom
  (deft-directory org-directory)
  (deft-recursive t)
  (deft-use-filename-as-title nil)
  (deft-use-filter-string-for-filename t)
  (deft-file-naming-rules '((noslash . "-")
                            (nospace . "-")
                            (case-fn . downcase)))
  (deft-org-mode-title-prefix t)
  (deft-extensions '("org" "txt" "text" "md" "markdown" "org.gpg"))
  (deft-default-extension "org"))

Using org-download to make it easier to insert images into my org notes.

(use-package org-download
  :after org
  :defer nil
  :custom
  (org-download-method 'directory)
  (org-download-image-dir "images")
  (org-download-heading-lvl nil)
  (org-download-timestamp "%Y%m%d-%H%M%S_")
  (org-image-actual-width 300)
  :bind
  ("C-M-y" .
   (lambda (&optional noask)
     (interactive "P")
     (let ((file
            (if (not noask)
                (read-string (format "Filename [%s]: " org-download-screenshot-basename)
                             nil nil org-download-screenshot-basename)
              nil)))
       (org-download-clipboard file))))
  :config
  (require 'org-download))

Building presentations

org-reveal is an awesome package for building presentations with org-mode. The MELPA version of the package gives me a conflict with my hand-installed version of org-mode, so I also install it by hand and load it directly from its checked-out repository.

(use-package ox-reveal
  :load-path ("lisp/org-reveal")
  :defer 3
  :after org
  :custom
  (org-reveal-note-key-char nil)
  (org-reveal-root "file:///Users/taazadi1/.emacs.d/lisp/reveal.js"))
(use-package htmlize
  :defer 3
  :after ox-reveal)

Various exporters

One of the big strengths of org-mode is the ability to export a document in many different formats. Here I load some of the exporters I have found useful.

  • HTML
    (use-package ox-html
      :ensure nil
      :defer 3
      :after org
      :custom
      (org-html-checkbox-type 'unicode))
        
  • Markdown
    (use-package ox-md
      :ensure nil
      :defer 3
      :after org)
        
  • Jira markup. I also load org-jira, which provides a full interface to Jira through org-mode.
    (use-package ox-jira
      :defer 3
      :after org)
        
    (use-package org-jira
      :defer 3
      :after org
      :custom
      (jiralib-url "https://jira.work.com"))
        
  • Confluence markup.
    (use-package ox-confluence
      :defer 3
      :ensure nil
      :after org)
        
  • AsciiDoc
    (use-package ox-asciidoc
      :defer 3
      :after org)
        
  • TexInfo. I have found that the best way to produce a PDF from an org file is to export it to a .texi file, and then use texi2pdf to produce the PDF.
    (use-package ox-texinfo
      :load-path "lisp/org-mode/lisp"
      :defer 3
      :ensure nil
      :after org)
        
  • Some customizations for the LaTeX exporter. ox-latex gets loaded automatically, but we use use-package anyway so that the config code is only executed after the package is loaded. I add a pseudo-class which uses the document class book but without parts (only chapters at the top level).
    (use-package ox-latex
      :load-path "lisp/org-mode/lisp"
      :ensure nil
      :demand
      :after org
      :custom
      (org-latex-compiler "xelatex")
      ;; (org-latex-pdf-process
      ;;  '("%latex -shell-escape -interaction nonstopmode -output-directory %o %f"
      ;;    "%latex -interaction nonstopmode -output-directory %o %f"
      ;;    "%latex -interaction nonstopmode -output-directory %o %f"))
      :config
      ;; (setq org-latex-listings 'minted)
      ;; (add-to-list 'org-latex-packages-alist '("newfloat" "minted"))
      ;; (add-to-list 'org-latex-minted-langs '(lua "lua"))
      ;; (add-to-list 'org-latex-minted-langs '(shell "shell"))
      (add-to-list 'org-latex-classes
                   '("book-no-parts" "\\documentclass[11pt,letterpaper]{book}"
                     ("\\chapter{%s}" . "\\chapter*{%s}")
                     ("\\section{%s}" . "\\section*{%s}")
                     ("\\subsection{%s}" . "\\subsection*{%s}")
                     ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
                     ("\\paragraph{%s}" . "\\paragraph*{%s}")))
      ;; Necessary for LuaLaTeX to work - see
      ;; https://tex.stackexchange.com/a/374391/10680
      (setenv "LANG" "en_US.UTF-8"))
        
  • ox-clip to export HTML-formatted snippets.
    (use-package ox-clip
      :bind
      ("A-C-M-k" . ox-clip-formatted-copy))
        
  • I use ox-awesomecv and ox-hugocv from Org-CV, to export my Curriculum Vitæ.
    (use-package ox-awesomecv
      :load-path "~/.emacs.d/lisp/org-cv"
      :init
      (require 'ox-awesomecv))
    (use-package ox-hugocv
      :disabled
      :load-path "~/.emacs.d/lisp/org-cv"
      :init
      (require 'ox-hugocv))
        
  • I use ox-org to generate an org file from another. For example, the README.org file for my elvish-modules package is generated by exporting from README-src.org, to automatically extract summaries from the different module files.
    (use-package ox-org
      :ensure nil
      :defer 3
      :after org)
        

Blogging with Hugo

ox-hugo is an awesome way to blog from org-mode. It makes it possible for posts in org-mode format to be kept separate, and it generates the Markdown files for Hugo. Hugo supports org files, but using ox-hugo has multiple advantages:

  • Parsing is done by org-mode natively, not by an external library. Although goorgeous (used by Hugo) is very good, it still lacks in many areas, which leads to text being interpreted differently as by org-mode.
  • Hugo is left to parse a native Markdown file, which means that many of its features such as shortcodes, TOC generation, etc., can still be used on the generated file.
  • I am intrigued by ox-hugo’s “one post per org subtree” proposed structure. So far I’ve always had one file per post, but with org-mode’s structuring features, it might make sense to give it a try.
(use-package ox-hugo
  :defer 3
  :after org
  ;; Testing hooks to automatically set the filename on an ox-hugo
  ;; blog entry when it gets marked as DONE
  ;; :hook
  ;; (org-mode . (lambda ()
  ;;               (add-hook 'org-after-todo-state-change-hook
  ;;                         (lambda ()
  ;;                           (org-set-property
  ;;                            "testprop"
  ;;                            (concat "org-state: " org-state
  ;;                                    " prev-state: " (org-get-todo-state))))
  ;;                         'run-at-end 'only-in-org-mode)))
  :custom
  (org-hugo-use-code-for-kbd t))

Configure a capture template for creating new ox-hugo blog posts, from ox-hugo’s Org Capture Setup.

(defun org-hugo-new-subtree-post-capture-template ()
  "Returns `org-capture' template string for new Hugo post.
  See `org-capture-templates' for more information."
  (let* ((title (read-from-minibuffer "Post Title: "))
         (fname (org-hugo-slug title)))
    (mapconcat #'identity
               `(,(concat "* TODO " title)
                 ":PROPERTIES:"
                 ,(concat ":EXPORT_HUGO_BUNDLE: " fname)
                 ":EXPORT_FILE_NAME: index"
                 ":END:"
                 "%?\n") ; Place the cursor here finally
               "\n")))
(add-to-list 'org-capture-templates
             '("z"       ;`org-capture' binding + z
               "zzamboni.org post"
               entry
               (file+olp "~/Personal/websites/zzamboni.org/content-org/zzamboni.org" "Ideas")
               (function org-hugo-new-subtree-post-capture-template)))

Encryption

First, load the built-in EasyPG support. By calling (epa-file-enable), Emacs automatically encrypts/decrypts files with a .gpg extension. By default it asks about the key to use, but I configure it to always use my own GPG key.

(use-package epa-file
  :ensure nil ;; included with Emacs
  :config
  (setq epa-file-encrypt-to '("[email protected]"))
  :custom
  (epa-file-select-keys 'silent))

Then, load org-crypt to enable selective encryption/decryption using GPG within org-mode.

(use-package org-crypt
  :ensure nil  ;; included with org-mode
  :after org
  :config
  (org-crypt-use-before-save-magic)
  (setq org-tags-exclude-from-inheritance (quote ("crypt")))
  :custom
  (org-crypt-key "[email protected]"))

Keeping a Journal

I use 750words for my personal Journal, and I used to write my entries locally using Scrivener. Now I am using org-journal for this, works quite well together with wc-mode to keep a count of how many words I have written.

In order to keep my journal entries encrypted there are two separate but confusingly named mechanisms:

  • org-journal-encrypt-journal, if set to t has the effect of transparently encrypting/decrypting the journal files as they are written to disk. This is what I use.
  • org-journal-enable-encryption, if set to t, enables integration with org-crypt (see above), so it automatically adds a :crypt: tag to new journal entries. This has the effect of automatically encrypting those entries upon save, replacing them with a blob of gpg-encrypted text which has to be further decrypted with org-decrypt-entry in order to read or edit them again. I have disabled it for now to make it more transparent to work with my journal entries while I am editing them.
(use-package org-journal
  :after org
  :custom
  (org-journal-dir (concat (file-name-as-directory org-directory) "journal"))
  (org-journal-file-format "%Y/%m/%Y%m%d")
  (org-journal-date-format "%A, %Y-%m-%d")
  (org-journal-encrypt-journal t)
  (org-journal-enable-encryption nil)
  (org-journal-enable-agenda-integration t)
  :bind
  ("C-c j" . org-journal-new-entry))

Literate programming

Org-mode is the first literate programming tool that seems practical and useful, since it’s easy to edit, execute and document code from within the same tool (Emacs) using all of its existing capabilities (i.e. each code block can be edited in its native Emacs mode, taking full advantage of indentation, completion, etc.)

First, we load the necessary programming language support. The base features and literate programming for Emacs LISP is built-in, but the ob-* packages provide the ability to execute code in different languages directly from within the Org buffer, beyond those included with org-mode. I load the modules for some of the languages I use frequently:

  • CFEngine, used extensively for my book /Learning CFEngine/.
    (use-package ob-cfengine3
      :after org)
        
  • Elvish, my favorite shell.
    (use-package ob-elvish
      :after org)
        
  • The PlantUML graph language.

    We determine the location of the PlantUML jar file automatically from the installed Homebrew formula.

    brew list plantuml | grep jar
        

Which in my current setup results in the following:

/usr/local/Cellar/plantuml/1.2020.15/libexec/plantuml.jar

The command defined above is used to define the value of the homebrew-plantuml-jar-path variable. If you don’t use Homebrew of have installed PlantUML some other way, you need to modify this command, or hard-code the path.

(require 'subr-x)
(setq homebrew-plantuml-jar-path
      (expand-file-name
       (string-trim
        (shell-command-to-string "<<plantuml-jar-command>>"))))

Finally, we use this value to configure both plantuml-mode (for syntax highlighting) and ob-plantuml (for evaluating PlantUML code and inserting the results in exported Org documents).

(use-package plantuml-mode
  :custom
  (plantuml-jar-path homebrew-plantuml-jar-path))

(use-package ob-plantuml
  :ensure nil
  :after org
  :custom
  (org-plantuml-jar-path homebrew-plantuml-jar-path))
  • Define shell-script-mode as an alias for console-mode, so that console src blocks can be edited and are fontified correctly.
    (defalias 'console-mode 'shell-script-mode)
        
  • Finally, from all the available languages, we configure the ones for which to load org-babel support.
    (org-babel-do-load-languages
     'org-babel-load-languages
     '((cfengine3 . t)
       (ruby      . t)
       (latex     . t)
       (plantuml  . t)
       (python    . t)
       (shell     . t)
       (elvish    . t)
       (calc      . t)
       (dot       . t)
       (ditaa     . t)
       (org       . t)))
        

Now, we configure some other org-babel settings:

  • Tangle-on-save has revolutionized my literate programming workflow. It automatically runs org-babel-tangle upon saving any org-mode buffer, which means the resulting files will be automatically kept up to date. For a long time I simply had the following hook:
    (org-mode . (lambda () (add-hook 'after-save-hook 'org-babel-tangle :append :local)))
        

    This is simple and it works, the only disadvantage is that it runs the tangle process synchronously, so Emacs freezes until the org-babel-tangle command is done. For large files (such as this one), the delay is noticeable, so I also had some hooks to measure and report the tangle time:

    (defun zz/report-tangle-time (start-time)
      (message "org-babel-tangle took %s"
               (format "%.2f seconds"
                       (float-time (time-since start-time)))))
        
    (org-babel-pre-tangle  . (lambda ()
                               (setq zz/pre-tangle-time (current-time))))
    (org-babel-post-tangle . (lambda ()
                               (zz/report-tangle-time zz/pre-tangle-time)))
        

    Thanks to the kind help of Ihor in the emacs-orgmode mailing list, I now have an asynchronous version of this, which dispatches the tangle function to a subprocess, so that the main Emacs is not blocked while it runs. The zz/org-babel-tangle-async function uses the emacs-async package to start the tangle operation in a child process. Note that the child Emacs started by async-start is empty, without any configuration, so we need to load org before tangling. Depending on your setup, you may need to load more configuration.

    (defun zz/org-babel-tangle-async (file)
      "Invoke `org-babel-tangle-file' asynchronously."
      (message "Tangling %s..." (buffer-file-name))
      (async-start
       (let ((args (list file)))
         `(lambda ()
            (require 'org)
            ;;(load "~/.emacs.d/init.el")
            (let ((start-time (current-time)))
              (apply #'org-babel-tangle-file ',args)
              (format "%.2f" (float-time (time-since start-time))))))
       (let ((message-string (format "Tangling %S completed after " file)))
         `(lambda (tangle-time)
            (message (concat ,message-string
                             (format "%s seconds" tangle-time)))))))
    
    (defun zz/org-babel-tangle-current-buffer-async ()
      "Tangle current buffer asynchronously."
      (zz/org-babel-tangle-async (buffer-file-name)))
        

    Finally, we set up an org-mode hook which adds the async tangle function to the after-save-hook, so that it happens automatically after every save. Disabled for now because the tangle is getting interrupted sometimes when I move the cursor before the async tangle finishes, leaving files incomplete.

    (org-mode . (lambda () (add-hook 'after-save-hook
                                     'zz/org-babel-tangle-current-buffer-async
                                     'run-at-end 'only-in-org-mode)))
        
  • This is potentially dangerous: it suppresses the query before executing code from within org-mode. I use it because I am very careful and only press C-c C-c on blocks I absolutely understand.
    (org-confirm-babel-evaluate nil)
        
  • This makes it so that code within src blocks is fontified according to their corresponding Emacs mode, making the file much more readable.
    (org-src-fontify-natively t)
        
  • In principle this makes it so that indentation in src blocks works as in their native mode, but in my experience it does not always work reliably. For full proper indentation, always edit the code in a native buffer by pressing =C-c ‘=.
    (org-src-tab-acts-natively t)
        
  • Automatically show inline images, useful when executing code that produces them, such as PlantUML or Graphviz.
    (org-babel-after-execute . org-redisplay-inline-images)
        

Beautifying org-mode

Emphasis, lists and bullets

These settings make org-mode much more readable by using different fonts for headings, hiding some of the markup, etc. This was taken originally from Howard Abrams’ Org as a Word Processor, and subsequently tweaked and broken up in the different parts of the use-package declaration by me.

First, we set org-hid-emphasis-markers so that the markup indicators are not shown.

(org-hide-emphasis-markers t)

We add an entry to the org-mode font-lock table so that list markers are shown with a middle dot instead of the original character.

(font-lock-add-keywords
 'org-mode
 '(("^ *\\([-]\\) "
    (0 (prog1 () (compose-region (match-beginning 1) (match-end 1) ""))))))

We use the org-bullets package to display the titles with nice unicode bullets instead of the text ones.

(use-package org-bullets
  :after org
  :hook
  (org-mode . (lambda () (org-bullets-mode 1))))

Prettify checkbox lists and other symbols - courtesy of https://blog.jft.rocks/emacs/unicode-for-orgmode-checkboxes.html. First, we add special characters for checkboxes:

(org-mode . (lambda ()
              "Beautify Org Checkbox Symbol"
              (push '("[ ]" . "" ) prettify-symbols-alist)
              (push '("[X]" . "" ) prettify-symbols-alist)
              (push '("[-]" . "" ) prettify-symbols-alist)
              (prettify-symbols-mode)))

Show symbols when the cursor is over of right after them.

(prettify-symbols-unprettify-at-point 'right-edge)

Second, we define a special face for checked items.

(defface org-checkbox-done-text
  '((t (:foreground "#71696A" :strike-through t)))
  "Face for the text part of a checked org-mode checkbox.")

(font-lock-add-keywords
 'org-mode
 `(("^[ \t]*\\(?:[-+*]\\|[0-9]+[).]\\)[ \t]+\\(\\(?:\\[@\\(?:start:\\)?[0-9]+\\][ \t]*\\)?\\[\\(?:X\\|\\([0-9]+\\)/\\2\\)\\][^\n]*\n\\)"
    1 'org-checkbox-done-text prepend))
 'append)

Headings

We choose a nice font for the document title and the section headings. The first one found in the system from the list below is used, and the same font is used for the different levels, in varying sizes.

(let* ((variable-tuple
        (cond ((x-list-fonts   "ETBembo")         '(:font   "ETBembo"))
              ((x-list-fonts   "Source Sans Pro") '(:font   "Source Sans Pro"))
              ((x-list-fonts   "Lucida Grande")   '(:font   "Lucida Grande"))
              ((x-list-fonts   "Verdana")         '(:font   "Verdana"))
              ((x-family-fonts "Sans Serif")      '(:family "Sans Serif"))
              (nil (warn "Cannot find a Sans Serif Font."))))
       (base-font-color (face-foreground 'default nil 'default))
       (headline `(:inherit default :weight bold
                   :foreground ,base-font-color)))

  (custom-theme-set-faces
   'user
   `(org-level-8        ((t (,@headline ,@variable-tuple))))
   `(org-level-7        ((t (,@headline ,@variable-tuple))))
   `(org-level-6        ((t (,@headline ,@variable-tuple))))
   `(org-level-5        ((t (,@headline ,@variable-tuple))))
   `(org-level-4        ((t (,@headline ,@variable-tuple :height 1.1))))
   `(org-level-3        ((t (,@headline ,@variable-tuple :height 1.25))))
   `(org-level-2        ((t (,@headline ,@variable-tuple :height 1.5))))
   `(org-level-1        ((t (,@headline ,@variable-tuple :height 1.75))))
   `(org-headline-done  ((t (,@headline ,@variable-tuple :strike-through t))))
   `(org-document-title ((t (,@headline ,@variable-tuple
                                        :height 2.0 :underline nil))))))

Fonts and wrapping

I use proportional fonts in org-mode for the text, while keeping fixed-width fonts for blocks, so that source code, tables, etc. are shown correctly. These settings include:

  • Setting up the variable-pitch face to the proportional font I like to use. My current favorite is ET Book, in the past I have used Source Sans Pro and Avenir Next.
    (variable-pitch ((t (:family "ETBembo" :height 180 :weight thin))))
    ;;(variable-pitch ((t (:family "Avenir Next" :height 160 :weight light))))
        
  • Setting up the fixed-pitch face to be the same as my usual default face. My current one is Inconsolata Fira Code
    ;;    (fixed-pitch ((t (:family "Inconsolata Nerd Font"))))
    (fixed-pitch ((t (:family "Fira Code Retina" :height 160))))
        
  • Configure org-indent to inherit from fixed-pitch to fix the vertical spacing in code blocks. Thanks to Ben for the tip!
    (org-indent ((t (:inherit (org-hide fixed-pitch)))))
        
  • Configure org-fontify-done-headline to apply a special face to DONE items in org-mode, and configure the org-done face to be used. Note that org-done only applies to the “DONE” keyword itself, the face for the rest of a “done” headline is defined above as the org-headline-done face.
    (org-fontify-done-headline t)
        
    (org-done ((t (:foreground "PaleGreen"
                   :strike-through t))))
        
  • Configuring the corresponding org-mode faces for blocks, verbatim code, and maybe a couple of other things. As these change more frequently, I do them directly from the customize-face interface, you can see their current settings in the Customized variables section.
  • Setting up visual-line-mode and making all my paragraphs one single line, so that the lines wrap around nicely in the window according to their proportional-font size, instead of at a fixed character count, which does not work so nicely when characters have varying widths. I set up a hook that automatically enables visual-line-mode and variable-pitch-mode when entering org-mode.
    (org-mode . visual-line-mode)
    (org-mode . variable-pitch-mode)
        

    Turns out visual-line-mode also remaps the C-a and C-e keybindings (of course, which breaks the behavior enabled by the org-special-ctrl-a/e/k variables. To counter this, I also add some bindings that set those keys to their Org functions. These functions know how to deal with visual mode anyway.

    ("C-a" . org-beginning-of-line)
    ("C-e" . org-end-of-line)
    ("C-k" . org-kill-line)
        
  • In variable-pitch mode, the default right-alignment for headline tags doesn’t work, and results in the tags being misaligned (as it uses character positions to do the alignment). This setting positions the tags right after the last character of the headline, so at least they are more consistent.
    (org-tags-column 0)
        
  • I also set org-todo-keyword-faces to highlight different types of org-mode TODO items with different colors.
    (org-todo-keyword-faces
     '(("AREA"         . "DarkOrchid1")
       ("[AREA]"       . "DarkOrchid1")
       ("PROJECT"      . "DarkOrchid1")
       ("[PROJECT]"    . "DarkOrchid1")
       ("INBOX"        . "cyan")
       ("[INBOX]"      . "cyan")
       ("PROPOSAL"     . "orange")
       ("[PROPOSAL]"   . "orange")
       ("DRAFT"        . "yellow3")
       ("[DRAFT]"      . "yellow3")
       ("INPROGRESS"   . "yellow4")
       ("[INPROGRESS]" . "yellow4")
       ("MEETING"      . "purple")
       ("[MEETING]"    . "purple")
       ("CANCELED"     . "blue")
       ("[CANCELED]"   . "blue")))
        

    These two modes produce modeline indicators, which I disable using diminish.

    (eval-after-load 'face-remap '(diminish 'buffer-face-mode))
    (eval-after-load 'simple '(diminish 'visual-line-mode))
        

“Focused Writing” mode

I’m experimenting with some settings based on Ricing up Org Mode, particularly for using when writing, to avoid distractions. For now these are contained within a function that I can call to enable them, to give me a chance to experiment.

(defun zz/write ()
  (interactive)
  ;; Line spacing
  (setq line-spacing 0.1)
  ;; Top padding
  (setq header-line-format " ")
  ;; Hide modeline
  (hide-mode-line-mode)
  ;;(setq mode-line-format nil)
  ;; Side padding
  (setq left-margin-width 2)
  (setq right-margin-width 2)
  (set-window-buffer nil (current-buffer)))

The function above uses hide-mode-line mode.

(use-package hide-mode-line)

Source code blocks

The following code (by Rasmus) prettifies org-mode’s source blocks by replacing the #+begin/end_src keywords and the header arguments with symbols. In my config, the following code:

(defvar zzamboni/test-symbol ?✎
  "This is a test symbol")

Looks like this:

images/my-emacs-configuration-with-commentary/2020-03-17_09-54-28_screenshot.png

When the cursor is over or next to one of the symbols, it gets expanded into its text representation to make editing easier. This is enabled by setting prettify-symbols-unprettify-at-point to =’right-edge=:

images/my-emacs-configuration-with-commentary/2020-03-17_10-22-49_screenshot.png

(with-eval-after-load 'org
  (defvar-local rasmus/org-at-src-begin -1
    "Variable that holds whether last position was a ")

  (defvar rasmus/ob-header-symbol ?☰
    "Symbol used for babel headers")

  (defun rasmus/org-prettify-src--update ()
    (let ((case-fold-search t)
          (re "^[ \t]*#\\+begin_src[ \t]+[^ \f\t\n\r\v]+[ \t]*")
          found)
      (save-excursion
        (goto-char (point-min))
        (while (re-search-forward re nil t)
          (goto-char (match-end 0))
          (let ((args (org-trim
                       (buffer-substring-no-properties (point)
                                                       (line-end-position)))))
            (when (org-string-nw-p args)
              (let ((new-cell (cons args rasmus/ob-header-symbol)))
                (cl-pushnew new-cell prettify-symbols-alist :test #'equal)
                (cl-pushnew new-cell found :test #'equal)))))
        (setq prettify-symbols-alist
              (cl-set-difference prettify-symbols-alist
                                 (cl-set-difference
                                  (cl-remove-if-not
                                   (lambda (elm)
                                     (eq (cdr elm) rasmus/ob-header-symbol))
                                   prettify-symbols-alist)
                                  found :test #'equal)))
        ;; Clean up old font-lock-keywords.
        (font-lock-remove-keywords nil prettify-symbols--keywords)
        (setq prettify-symbols--keywords (prettify-symbols--make-keywords))
        (font-lock-add-keywords nil prettify-symbols--keywords)
        (while (re-search-forward re nil t)
          (font-lock-flush (line-beginning-position) (line-end-position))))))

  (defun rasmus/org-prettify-src ()
    "Hide src options via `prettify-symbols-mode'.

        `prettify-symbols-mode' is used because it has uncollpasing. It's
        may not be efficient."
    (let* ((case-fold-search t)
           (at-src-block (save-excursion
                           (beginning-of-line)
                           (looking-at "^[ \t]*#\\+begin_src[ \t]+[^ \f\t\n\r\v]+[ \t]*"))))
      ;; Test if we moved out of a block.
      (when (or (and rasmus/org-at-src-begin
                     (not at-src-block))
                ;; File was just opened.
                (eq rasmus/org-at-src-begin -1))
        (rasmus/org-prettify-src--update))
      ;; Remove composition if at line; doesn't work properly.
      ;; (when at-src-block
      ;;   (with-silent-modifications
      ;;     (remove-text-properties (match-end 0)
      ;;                             (1+ (line-end-position))
      ;;                             '(composition))))
      (setq rasmus/org-at-src-begin at-src-block)))

  ;; This function helps to produce a single glyph out of a
  ;; string. The glyph can then be used in prettify-symbols-alist.
  ;; This function was provided by Ihor in the org-mode mailing list.
  (defun yant/str-to-glyph (str)
    "Transform string into glyph, displayed correctly."
    (let ((composition nil))
      (dolist (char (string-to-list str)
                    (nreverse (cdr composition)))
        (push char composition)
        (push '(Br . Bl) composition))))

  (defun rasmus/org-prettify-symbols ()
    (mapc (apply-partially 'add-to-list 'prettify-symbols-alist)
          (cl-reduce 'append
                     (mapcar (lambda (x) (list x (cons (upcase (car x)) (cdr x))))
                             `(("#+begin_src" . ?⎡) ;; ⎡ ➤ 🖝 ➟ ➤ ✎
                               ;; multi-character strings can be used with something like this:
                               ;; ("#+begin_src" . ,(yant/str-to-glyph "```"))
                               ("#+end_src"   . ?⎣) ;; ⎣ ✐
                               ("#+header:" . ,rasmus/ob-header-symbol)
                               ("#+begin_quote" . )
                               ("#+end_quote" . )))))
    (turn-on-prettify-symbols-mode)
    (add-hook 'post-command-hook 'rasmus/org-prettify-src t t))
  (add-hook 'org-mode-hook #'rasmus/org-prettify-symbols))

Auto-generated table of contents

The toc-org package allows us to insert a table of contents in headings marked with :TOC:. This is useful for org files that are to be viewed directly on GitHub, which renders org files correctly, but does not generate a table of contents at the top. For an example, see this file on GitHub.

Note that this breaks HTML export by default, as the links generated by toc-org cannot be parsed properly by the html exporter. The workaround is to use :TOC:noexport: as the marker, which removed the generated TOC from the export, but still allows ox-html to insert its own TOC at the top.

(use-package toc-org
  :after org
  :hook
  (org-mode . toc-org-enable))

Grabbing links from different Mac applications

org-mac-link (included in contrib) implements the ability to grab links from different Mac apps and insert them in the file. Bind C-c g to call org-mac-grab-link to choose an application and insert a link.

(use-package org-mac-link
  :ensure nil
  :load-path "lisp/org-mode/contrib/lisp"
  :after org
  :custom
  (org-mac-grab-Acrobat-app-p nil "Disable grabbing from Adobe Acrobat")
  (org-mac-grab-devonthink-app-p nil "Disable grabbinb from DevonThink")
  :bind
  (:map org-mode-map
   ("C-c g" . org-mac-grab-link)))

Reformatting an Org buffer

I picked up this little gem in the org mailing list. A function that reformats the current buffer by regenerating the text from its internal parsed representation. Quite amazing.

(defun zz/org-reformat-buffer ()
  (interactive)
  (when (y-or-n-p "Really format current buffer? ")
    (let ((document (org-element-interpret-data (org-element-parse-buffer))))
      (erase-buffer)
      (insert document)
      (goto-char (point-min)))))

Remove a link. For some reason this is not part of org-mode. From https://emacs.stackexchange.com/a/10714/11843, I bind it to C-c C-M-u.

(defun afs/org-remove-link ()
  "Replace an org link by its description or if empty its address"
  (interactive)
  (if (org-in-regexp org-bracket-link-regexp 1)
      (let ((remove (list (match-beginning 0) (match-end 0)))
            (description (if (match-end 3)
                             (org-match-string-no-properties 3)
                           (org-match-string-no-properties 1))))
        (apply 'delete-region remove)
        (insert description))))
(bind-key "C-c C-M-u" 'afs/org-remove-link)

Code for org-mode macros

Here I define functions which get used in some of my org-mode macros

The first is a support function which gets used in some of the following, to return a string (or an optional custom string) only if it is a non-zero, non-whitespace string, and nil otherwise.

(defun zz/org-if-str (str &optional desc)
  (when (org-string-nw-p str)
    (or (org-string-nw-p desc) str)))

This function receives three arguments, and returns the org-mode code for a link to the Hammerspoon API documentation for the link module, optionally to a specific function. If desc is passed, it is used as the display text, otherwise section.function is used.

(defun zz/org-macro-hsapi-code (module &optional func desc)
  (org-link-make-string
   (concat "https://www.hammerspoon.org/docs/"
           (concat module (zz/org-if-str func (concat "#" func))))
   (or (org-string-nw-p desc)
       (format "=%s="
               (concat module
                       (zz/org-if-str func (concat "." func)))))))

Split STR at spaces and wrap each element with the ~ char, separated by +. Zero-width spaces are inserted around the plus signs so that they get formatted correctly. Envisioned use is for formatting keybinding descriptions. There are two versions of this function: “outer” wraps each element in ~, the “inner” wraps the whole sequence in them.

(defun zz/org-macro-keys-code-outer (str)
  (mapconcat (lambda (s)
               (concat "~" s "~"))
             (split-string str)
             (concat (string ?\u200B) "+" (string ?\u200B))))
(defun zz/org-macro-keys-code-inner (str)
  (concat "~" (mapconcat (lambda (s)
                           (concat s))
                         (split-string str)
                         (concat (string ?\u200B) "-" (string ?\u200B)))
          "~"))
(defun zz/org-macro-keys-code (str)
  (zz/org-macro-keys-code-inner str))

Links to a specific section/function of the Lua manual.

(defun zz/org-macro-luadoc-code (func &optional section desc)
  (org-link-make-string
   (concat "https://www.lua.org/manual/5.3/manual.html#"
           (zz/org-if-str func section))
   (zz/org-if-str func desc)))
(defun zz/org-macro-luafun-code (func &optional desc)
  (org-link-make-string
   (concat "https://www.lua.org/manual/5.3/manual.html#"
           (concat "pdf-" func))
   (zz/org-if-str (concat "=" func "()=") desc)))

Publishing project configuration

Define a publishing function based on org-latex-publish-to-pdf but which opens the resulting file at the end.

(defun org-latex-publish-to-latex-and-open (plist file pub-dir)
  (org-open-file (org-latex-publish-to-pdf plist file pub-dir)))

Sample project configuration - disabled for now because this configuration has been incorporated into the structure.tex file and in the general ox-latex configuration, but kept here as a sample.

(org-publish-project-alist
 '(("mac-automation"
    :base-directory "~/Personal/writing/mac-automation/"
    :publishing-directory "~/Personal/writing/mac-automation/build/"
    :base-extension "org"
    :publishing-function org-latex-publish-to-latex-and-open
    :latex-compiler "xelatex"
    :latex-classes '("book-no-parts" "\\documentclass[11pt]{book}"
                     ("\\chapter{%s}" . "\\chapter*{%s}")
                     ("\\section{%s}" . "\\section*{%s}")
                     ("\\subsection{%s}" . "\\subsection*{%s}")
                     ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
                     ("\\paragraph{%s}" . "\\paragraph*{%s}"))
    :latex-class "book-no-parts"
    :latex-title-command "\\makeatletter\\begingroup
    \\thispagestyle{empty}
    \\begin{tikzpicture}[remember picture,overlay]
    \\node[inner sep=0pt] (background) at (current page.center) {\\includegraphics[width=\\paperwidth]{background}};
    \\draw (current page.center) node [fill=ocre!30!white,fill opacity=0.6,text opacity=1,inner sep=1cm]{\\Huge\\centering\\bfseries\\sffamily\\parbox[c][][t]{\\paperwidth}{\\centering \\@title \\\\[15pt]
    {\\Large \\@subtitle }\\\\[20pt]
    {\\huge \\@author }}};
    \\end{tikzpicture}
    \\vfill
    \\endgroup\\makeatother
    \\chapterimage{chapter_head_1.pdf}"
    :latex-toc-command "\\pagestyle{empty}
  \\tableofcontents
  \\cleardoublepage
  \\pagestyle{fancy}"
    )))

Publishing to LeanPub

I use LeanPub for self-publishing my books. Fortunately, it is possible to export from org-mode to both LeanPub-flavored Markdown and Markua, the new preferred Leanpub markup format, so I can use Org for writing the text and simply export it in the correct format and structure needed by Leanpub.

When I decided to use org-mode to write my books, I looked around for existing modules and code. Here are some of the resources I found:

Building upon these, I have developed a new ox-leanpub package which you can find in MELPA (source at https://github.com/zzamboni/ox-leanpub), and which I load and configure below.

The ox-leanpub module sets up Markua export automatically, and I add the code for setting up the Markdown exporter too (I don’t use it, but just to keep an eye on any breakage):

(use-package ox-leanpub
  :defer 1
  :after org
  :config
  (require 'ox-leanpub-markdown)
  (org-leanpub-book-setup-menu-markdown))

I highly recommend using Markua rather than Markdown, as it is the future that Leanpub is guaranteed to support in the future, and where most of the new features are being developed.

With this setup, I can write my book in org-mode (I usually keep a single book.org file at the top of my repository), and then call the corresponding “Book” export commands. The manuscript directory, as well as the corresponding Book.txt and other necessary files are created and populated automatically.

Miscellaneous org functions and configuration

Utility org-get-keyword function (from the org-mode mailing list) to get the value of file-level properties.

(defun org-get-keyword (key)
  (org-element-map (org-element-parse-buffer 'element) 'keyword
    (lambda (k)
      (when (string= key (org-element-property :key k))
        (org-element-property :value k)))
    nil t))

org-sidebar provides a configurable sidebar to org buffers, showing the agenda, headlines, etc.

(use-package org-sidebar)

Appearance, buffer/file management and theming

Here we take care of all the visual, UX and desktop-management settings.

You’ll notice that many of the packages in this section have :defer nil. This is because some of these package are never called explicitly because they operate in the background, but I want them loaded when Emacs starts so they can perform their necessary customization.

Emacs 26 (which I am trying now) introduces pixel-level scrolling.

(when (>= emacs-major-version 26)
  (pixel-scroll-mode))

The diminish package makes it possible to remove clutter from the modeline. Here we just load it, it gets enabled for individual packages in their corresponding declarations.

(use-package diminish
  :defer 1)

I have been playing with different themes, and I have settled for now with spacemacs-light. Some of my other favorites are also here so I don’t forget about them.

;;(use-package solarized-theme)
;;(use-package darktooth-theme)
;;(use-package kaolin-themes)
;;(use-package gruvbox-theme)
(use-package spacemacs-theme)
(load-theme 'spacemacs-light)

Install smart-mode-line for modeline goodness, including configurable abbreviation of directories, and other things.

(use-package smart-mode-line
  :defer 2
  :config
  (sml/setup)
  :custom
  (sml/theme 'respectful)
  (sml/replacer-regexp-list
   '(("^~/\\.emacs\\.d/elpa/"                            ":ELPA:")
     ("^~/\\.emacs\\.d/"                                 ":ED:")
     ("^/sudo:.*:"                                       ":SU:")
     ("^~/Documents/"                                    ":Doc:")
     ("^:\\([^:]*\\):Documento?s/"                       ":\\1/Doc:")
     ("^~/Dropbox/"                                      ":DB:")
     ("^:DB:org"                                         ":Org:")
     ("^:DB:Personal/"                                   ":P:")
     ("^:DB:Personal/writing/"                           ":Write:")
     ("^:P:devel/"                                       ":Dev:")
     ("^:Write:learning-cfengine-3/learning-cfengine-3/" ":cf-learn:")
     ("^:Dev:go/src/github.com/elves/elvish/"            ":elvish:")
     ("^:Dev:zzamboni.org/zzamboni.org/"                 ":zz.org:"))))

Enable desktop-save mode, which saves the current buffer configuration on exit and reloads it on restart.

Desktop mode also includes the desktop-clear function, which can be used to kill all open buffers. I bind it to Control-Meta-super-k.

(use-package desktop
  :defer nil
  :custom
  (desktop-restore-eager   1 "Restore the first buffer right away")
  (desktop-lazy-idle-delay 1 "Restore the other buffers 1 second later")
  (desktop-lazy-verbose  nil "Be silent about lazily opening buffers")
  :bind
  ("C-M-s-k" . desktop-clear)
  :config
  (desktop-save-mode))

The uniquify package makes it much easier to identify different open files with the same name by prepending/appending their directory or some other information to them. I configure it to add the directory name after the filename. uniquify is included with Emacs, so I specify :ensure nil so that use-package doesn’t try to install it, and just loads and configures it.

(use-package uniquify
  :defer 1
  :ensure nil
  :custom
  (uniquify-after-kill-buffer-p t)
  (uniquify-buffer-name-style 'post-forward)
  (uniquify-strip-common-suffix t))

I like to highlight the current line. For this I use the built-in hl-line.

(use-package hl-line
  :disabled
  :defer nil
  :config
  <<hl-line custom line-range function>>
  (global-hl-line-mode))

I also provide a custom value for hl-line-range-function (thanks to Eric on the org-mode mailing list for the tip) which highlights only the current visual line in visual-line-mode, which I use for Org-mode files (see Beautifying org-mode).

(defun zz/get-visual-line-range ()
  (let (b e)
    (save-excursion
      (beginning-of-visual-line)
      (setq b (point))
      (end-of-visual-line)
      (setq e (+ 1 (point)))
      )
    (cons b e)))
(setq hl-line-range-function #'zz/get-visual-line-range)

I have also experimented with highlighting the current column. At the moment the code below is all disabled because I find it too distracting, but I’m leaving it here for reference. I found two options to achieve this:

  • The col-highlight package, which highlights the column only after a defined interval has passed
  • The crosshairs package, which always highlights both the column and the line. It also has a “highlight crosshairs when idle” mode, but I prefer to have the current line always highlighted.
(use-package col-highlight
  :disabled
  :defer nil
  :config
  (col-highlight-toggle-when-idle)
  (col-highlight-set-interval 2))
(use-package crosshairs
  :disabled
  :defer nil
  :config
  (crosshairs-mode))

I also use recentf to keep a list of recently open buffers. These are visible in helm’s open-file mode.

(use-package recentf
  :defer 1
  :custom
  (recentf-max-menu-items 100)
  (recentf-max-saved-items 100)
  :init
  (recentf-mode))

The ibuffer package allows all sort of useful operations on the list of open buffers. I haven’t customized it yet, but I have a keybinding to open it. (Disabled for now as I am using helm’s helm-buffer-list).

(use-package ibuffer
  :disabled
  :bind
  ("C-x C-b" . ibuffer))

The smex package is incredibly useful, adding IDO integration and some other very nice features to M-x, which make it easier to discover and use Emacs commands. Highly recommended. (Disabled for now as I’m using helm’s helm-M-x).

(use-package smex
  :disabled
  :bind (("M-x" . smex))
  :config (smex-initialize))

midnight-mode purges buffers which haven’t been displayed in 3 days. We configure the period so that the cleanup happens every 2 hours (7200 seconds).

(use-package midnight
  :defer 3
  :config
  (setq midnight-period 7200)
  (midnight-mode 1))

For distraction-free writing, I’m testing out writeroom-mode.

(use-package writeroom-mode)

NeoTree shows a navigation tree on a sidebar, and allows a number of operations on the files and directories. I’m not much of a fan of this type of interface in Emacs, but I have set it up to check it out.

(use-package neotree
  :custom
  (neo-theme (if (display-graphic-p) 'icons 'arrow))
  (neo-smart-open t)
  (projectile-switch-project-action 'neotree-projectile-action)
  :config
  (defun neotree-project-dir ()
    "Open NeoTree using the git root."
    (interactive)
    (let ((project-dir (projectile-project-root))
          (file-name (buffer-file-name)))
      (neotree-toggle)
      (if project-dir
          (if (neo-global--window-exists-p)
              (progn
                (neotree-dir project-dir)
                (neotree-find file-name)))
        (message "Could not find git project root."))))
  :bind
  ([f8] . neotree-project-dir))

wc-mode allows counting characters and words, both on demand and continuously. It also allows setting up a word/character goal.

(use-package wc-mode
  :defer 3
  :hook
  (org-journal-mode . wc-mode))

The all-the-icons package provides a number of useful icons.

(use-package all-the-icons
  :defer 3)

Completion: IDO or Helm?

The battle rages on - helm or IDO? Both are nice completion frameworks for Emacs, and both integrate nicely with most main Emacs functions, including file opening, command and buffer selection, etc. I was using IDO for some time but are now giving helm a try. Both my configs are shown below, but only Helm is enabled at the moment.

Should I also look at ivy?

IDO

I use IDO mode to get better matching capabilities everywhere in Emacs (disabled while I give helm a try, see below).

(use-package ido
  :disabled
  :config
  (ido-mode t)
  (ido-everywhere 1)
  (setq ido-use-virtual-buffers t)
  (setq ido-enable-flex-matching t)
  (setq ido-use-filename-at-point nil)
  (setq ido-auto-merge-work-directories-length -1))

(use-package ido-completing-read+
  :disabled
  :config
  (ido-ubiquitous-mode 1))

Helm

This config came originally from Uncle Dave’s Emacs config, though I have tweaked it a bit.

(use-package helm
  :defer 1
  :diminish helm-mode
  :bind
  (("C-x C-f"       . helm-find-files)
   ("C-x C-b"       . helm-buffers-list)
   ("C-x b"         . helm-multi-files)
   ("M-x"           . helm-M-x)
   :map helm-find-files-map
   ("C-<backspace>" . helm-find-files-up-one-level)
   ("C-f"           . helm-execute-persistent-action)
   ([tab]           . helm-ff-RET))
  :init
  (defun daedreth/helm-hide-minibuffer ()
    (when (with-helm-buffer helm-echo-input-in-header-line)
      (let ((ov (make-overlay (point-min) (point-max) nil nil t)))
        (overlay-put ov 'window (selected-window))
        (overlay-put ov 'face
                     (let ((bg-color (face-background 'default nil)))
                       `(:background ,bg-color :foreground ,bg-color)))
        (setq-local cursor-type nil))))
  :custom
  (helm-autoresize-max-height 0)
  (helm-autoresize-min-height 40)
  (helm-buffers-fuzzy-matching t)
  (helm-recentf-fuzzy-match t)
  (helm-semantic-fuzzy-match t)
  (helm-imenu-fuzzy-match t)
  (helm-split-window-in-side-p nil)
  (helm-move-to-line-cycle-in-source nil)
  (helm-ff-search-library-in-sexp t)
  (helm-scroll-amount 8)
  (helm-echo-input-in-header-line nil)
  :config
  (require 'helm-config)
  (helm-mode 1)
  (helm-autoresize-mode 1)
  :hook
  (helm-mode .
             (lambda ()
               (setq completion-styles
                     (cond ((assq 'helm-flex completion-styles-alist)
                            '(helm-flex)) ;; emacs-26
                           ((assq 'flex completion-styles-alist)
                            '(flex))))))  ;; emacs-27+
  (helm-minibuffer-set-up . daedreth/helm-hide-minibuffer))

(use-package helm-flx
  :custom
  (helm-flx-for-helm-find-files t)
  (helm-flx-for-helm-locate t)
  :config
  (helm-flx-mode +1))

(use-package swiper-helm
  :bind
  ("C-s" . swiper))

Coding

Coding is one of my primary uses for Emacs, although lately it has shifted toward more general writing. This used to be the largest section in my config until Org mode overtook it :)

General settings and modules

When enabled, subword allows navigating “sub words” individually in CamelCaseIdentifiers. For now I only enable it in clojure-mode.

(use-package subword
  :hook
  (clojure-mode . subword-mode))

With aggressive-indent, indentation is always kept up to date in the whole buffer. Sometimes it gets in the way, but in general it’s nice and saves a lot of work, so I enable it for all programming modes except for Python mode, where I explicitly disable as it often gets the indentation wrong and messes up existing code.

Disabled for now while I test how much I miss it (I often find it gets in the way, but I’m not sure how often it helps and I don’t even notice it)

(use-package aggressive-indent
  :disabled
  :diminish aggressive-indent-mode
  :hook
  (prog-mode . aggressive-indent-mode)
  (python-mode . (lambda () (aggressive-indent-mode -1))))

With company-mode, we get automatic completion - when there are completions available, a popup menu will appear when you stop typing for a moment, and you can either continue typing or accept the completion using the Enter key. I enable it globally.

(use-package company
  :diminish company-mode
  :hook
  (after-init . global-company-mode))

projectile-mode allows us to perform project-relative operations such as searches, navigation, etc.

(use-package projectile
  :defer 2
  :diminish projectile-mode
  :config
  (projectile-global-mode))

I find iedit absolutely indispensable when coding. In short: when you hit Ctrl-:, all occurrences of the symbol under the cursor (or the current selection) are highlighted, and any changes you make on one of them will be automatically applied to all others. It’s great for renaming variables in code, but it needs to be used with care, as it has no idea of semantics, it’s a plain string replacement, so it can inadvertently modify unintended parts of the code.

(use-package iedit
  :config
  (set-face-background 'iedit-occurrence "Magenta")
  :bind
  ("C-;" . iedit-mode))

Turn on the online documentation mode for all programming modes (not all of them support it) and for the Clojure REPL cider mode.

(use-package eldoc
  :diminish
  :hook
  (prog-mode       . turn-on-eldoc-mode)
  (cider-repl-mode . turn-on-eldoc-mode))

On-the-fly spell checking. I enable it for all text modes.

(use-package flyspell
  :defer 1
  :diminish)

Clojure and LISP coding

I dabble in Clojure and Emacs LISP, and Emacs has some fantastic support for them. There’s a number of packages and configuration related to this, so I have a whole section for it.

The centerpiece is of course clojure-mode. In addition to files ending in .clj, I bind it automatically to .boot files (both by extension and by shebang line) and to the Riemann config files.

(use-package clojure-mode
  :mode "\\.clj.*$"
  :mode "riemann.config"
  :mode "\\.boot"
  :config
  (add-to-list 'magic-mode-alist '(".* boot" . clojure-mode)))

Enable some additional fontification for Clojure code.

(use-package clojure-mode-extra-font-locking)

The cider package provides a fantastic REPL built into Emacs. We configure a few aspects, including pretty printing, fontification, history size and others.

(use-package cider
  :custom
  ;; nice pretty printing
  (cider-repl-use-pretty-printing nil)
  ;; nicer font lock in REPL
  (cider-repl-use-clojure-font-lock t)
  ;; result prefix for the REPL
  (cider-repl-result-prefix "; => ")
  ;; never ending REPL history
  (cider-repl-wrap-history t)
  ;; looong history
  (cider-repl-history-size 5000)
  ;; persistent history
  (cider-repl-history-file "~/.emacs.d/cider-history")
  ;; error buffer not popping up
  (cider-show-error-buffer nil)
  ;; go right to the REPL buffer when it's finished connecting
  (cider-repl-pop-to-buffer-on-connect t))

We use clj-refactor for supporting advanced code refactoring in Clojure.

(use-package clj-refactor
  :config
  (defun my-clojure-mode-hook ()
    (clj-refactor-mode 1)
    (yas-minor-mode 1) ; for adding require/use/import statements
    ;; This choice of keybinding leaves cider-macroexpand-1 unbound
    (cljr-add-keybindings-with-prefix "C-c C-m"))
  :hook
  (clojure-mode . my-clojure-mode-hook))

Use emr for supporting refactoring in Emacs LISP and some other languages.

(use-package emr
  :config
  (bind-key "A-RET" 'emr-show-refactor-menu prog-mode-map))

When coding in LISP-like languages, rainbow-delimiters is a must-have - it marks each concentric pair of parenthesis with different colors, which makes it much easier to understand expressions and spot mistakes.

(use-package rainbow-delimiters
  :hook
  ((prog-mode cider-repl-mode) . rainbow-delimiters-mode))

Another useful addition for LISP coding - smartparens enforces parenthesis to match, and adds a number of useful operations for manipulating parenthesized expressions. I map M-( to enclose the next expression as in paredit using a custom function. Prefix argument can be used to indicate how many expressions to enclose instead of just 1. E.g. C-u 3 M-( will enclose the next 3 sexps.

(defun zz/sp-enclose-next-sexp (num)
  (interactive "p")
  (insert-parentheses (or num 1)))

(use-package smartparens
  :diminish smartparens-mode
  :config
  (require 'smartparens-config)
  :custom
  (sp-base-key-bindings 'paredit)
  :hook
  ((clojure-mode
    emacs-lisp-mode
    lisp-mode
    cider-repl-mode
    racket-mode
    racket-repl-mode) . smartparens-strict-mode)
  (smartparens-mode  . sp-use-paredit-bindings)
  (smartparens-mode  . (lambda ()
                         (local-set-key (kbd "M-(")
                                        'zz/sp-enclose-next-sexp))))

Minor mode for highlighting the current sexp in LISP modes.

(use-package hl-sexp
  :hook
  ((clojure-mode lisp-mode emacs-lisp-mode) . hl-sexp-mode))

Trying out lispy for LISP code editing (disabled for now).

(use-package lispy
  :disabled
  :config
  (defun enable-lispy-mode () (lispy-mode 1))
  :hook
  ((clojure-mode
    emacs-lisp-mode
    common-lisp-mode
    scheme-mode
    lisp-mode) . enable-lispy-mode))

Some tools for developing Emacs LISP modules and for contributing to MELPA:

Other programming languages

Many other programming languages are well served by a single mode, without so much setup as Clojure/LISP.

  • CFEngine policy files.
    (use-package cfengine
      :commands cfengine3-mode
      :mode ("\\.cf\\'" . cfengine3-mode))
        
  • Perl.
    (use-package cperl-mode
      :mode "\\.p[lm]\\'"
      :interpreter "perl"
      :config
      (setq cperl-hairy t))
        
  • Fish shell.
    (use-package fish-mode
      :mode "\\.fish\\'"
      :interpreter "fish")
        
  • Lua, which I use for Hammerspoon configuration.
    (use-package lua-mode)
        
  • YAML, generally useful
    (use-package yaml-mode)
        
  • AppleScript
    ;;    (use-package applescript-mode)
        
  • Go
    (use-package go-mode)
        
  • Build and check MELPA package definitions
    (use-package package-build)
    (use-package package-lint)
        
  • Elvish shell
    (use-package elvish-mode)
        
  • Racket
    (use-package racket-mode)
        
  • Nix package files
    (use-package nix-mode)
        
  • Dockerfile files
    (use-package dockerfile-mode)
        
  • The Dhall configuration language
    (use-package dhall-mode
      :ensure t
      :mode "\\.dhall\\'")
        

Other tools

  • The rx library makes it easier to express regular expressions (I know regex syntax, but in Emacs the quoting makes them very hard to read). The =xr= library is the inverse - can be used to convert regex strings to rx syntax, which makes it easier to learn by example.
    (require 'rx)
    (use-package xr
      :defer nil)
        
  • Use helm-pass as an interface to pass.
    (use-package helm-pass)
        
  • git interface with some simple configuration I picked up somewhere. When you press C-c C-g, magit-status runs full-screen, but when you press q, it restores your previous window setup. Very handy.
    (use-package magit
      :diminish auto-revert-mode
      :bind
      (("C-c C-g" . magit-status)
       :map magit-status-mode-map
       ("q"       . magit-quit-session))
      :config
      (defadvice magit-status (around magit-fullscreen activate)
        "Make magit-status run alone in a frame."
        (window-configuration-to-register :magit-fullscreen)
        ad-do-it
        (delete-other-windows))
    
      (defun magit-quit-session ()
        "Restore the previous window configuration and kill the magit buffer."
        (interactive)
        (kill-buffer)
        (jump-to-register :magit-fullscreen)))
        
  • Interface to use the silver-searcher
    (use-package ag)
        
  • Publishing with Hugo. I don’t use this anymore since I started blogging with ox-hugo. I keep it loaded, but without its keybinding, because it makes it easy sometimes to see the history of my Markdown posts.
    (use-package easy-hugo
      :custom
      (easy-hugo-basedir "~/Personal/devel/zzamboni.org/zzamboni.org/")
      (easy-hugo-url "http://zzamboni.org/")
      (easy-hugo-previewtime "300")
      ;;(define-key global-map (kbd "C-c C-e") 'easy-hugo)
      )
        
  • Function to randomize the order of lines in a region, from https://www.emacswiki.org/emacs/RandomizeBuffer.
    (defun my-randomize-region (beg end)
      "Randomize lines in region from BEG to END."
      (interactive "*r")
      (let ((lines (split-string
                    (delete-and-extract-region beg end) "\n")))
        (when (string-equal "" (car (last lines 1)))
          (setq lines (butlast lines 1)))
        (apply 'insert
               (mapcar 'cdr
                       (sort (mapcar
                              (lambda (x)
                                (cons (random) (concat x "\n")))
                              lines)
                             (lambda (a b) (< (car a) (car b))))))))
        
  • auto-insert mode for automatically inserting user-defined templates for certain file types. It’s included with Emacs, so I just configure its directory to one inside my Dropbox, and set the hook to run it automatically when opening a file.
    (use-package autoinsert
      :ensure nil
      :custom
      (auto-insert-directory (concat user-emacs-directory "auto-insert/"))
      :hook
      (find-file . auto-insert))
        
  • Create and manage GitHub gists. Setting gist-view-gist to t makes it open new gists in the web browser automatically after creating them.
    (use-package gist
      :custom
      (gist-view-gist t "Automatically open new gists in browser"))
        
  • Emacs Startup Profiler, to get detailed stats of what’s taking time during initialization.
    (use-package esup)
        
  • Macro to measure how long a command takes, from https://stackoverflow.com/questions/23622296/emacs-timing-execution-of-function-calls-in-emacs-lisp
(defmacro measure-time (&rest body)
  "Measure the time it takes to evaluate BODY."
  `(let ((time (current-time)))
     ,@body
     (message "%.06f" (float-time (time-since time)))))
  • Ability to restart Emacs from within Emacs:
    (use-package restart-emacs)
        
  • Multiple cursors
    (use-package multiple-cursors
      :bind
      ("C-c m c"   . mc/edit-lines)
      ("C-c m <"   . mc/mark-next-like-this)
      ("C-c m >"   . mc/mark-previous-like-this)
      ("C-c m C-<" . mc/mark-all-like-this))
        
  • Lorem Ipsum
    (use-package lorem-ipsum)
        
  • Emacs support for Keybase:
    (use-package keybase
      :disabled
      :ensure nil
      :load-path ("lisp/keybase-chat")
      :config (require 'keybase))
        
  • erc configuration for IRC. Based on https://www.reddit.com/r/emacs/comments/8ml6na/tip_how_to_make_erc_fun_to_use/
    (use-package erc
      :custom
      (erc-autojoin-channels-alist '(("freenode.net" "#elvish" "#hammerspoon"
                                      "#org-mode")))
      (erc-autojoin-timing 'ident)
      (erc-fill-function 'erc-fill-static)
      (erc-fill-static-center 22)
      (erc-hide-list '("JOIN" "PART" "QUIT"))
      (erc-lurker-hide-list '("JOIN" "PART" "QUIT"))
      (erc-lurker-threshold-time 43200)
      (erc-nick "zzamboni")
      (erc-prompt-for-nickserv-password nil)
      (erc-server-reconnect-attempts 5)
      (erc-server-reconnect-timeout 3)
      (erc-track-exclude-types '("JOIN" "MODE" "NICK" "PART" "QUIT"
                                 "324" "329" "332" "333" "353" "477"))
      :config
      (add-to-list 'erc-modules 'notifications)
      (add-to-list 'erc-modules 'spelling)
      (erc-services-mode 1)
      (erc-update-modules))
        

General text editing

In addition to coding, I configure some modes that can be used for text editing.

  • AsciiDoc, which I use for my book and some other text. I also set up visual-line-mode and variable-pitch-mode here. adoc-mode is not so granular as org-mode with respect to face assignments, so the variable/fixed distinction does not always work, but it’s still pretty good for long-text editing.
    (use-package adoc-mode
      :mode "\\.asciidoc\\'"
      :hook
      (adoc-mode . visual-line-mode)
      (adoc-mode . variable-pitch-mode))
        
  • Markdown, generally useful. I also set up variable pitch and visual line mode.
    (use-package markdown-mode
      :hook
      (markdown-mode . visual-line-mode)
      (markdown-mode . variable-pitch-mode))
        
  • When typopunct is enabled (needs to be enabled by hand), automatically inserts “pretty” quotes of the appropriate type.
    (use-package typopunct
      :config
      (typopunct-change-language 'english t))
        
  • undo-tree visualises undo history as a tree for easy navigation (found about this from Jamie’s config)
    (use-package undo-tree
      :ensure t
      :diminish undo-tree-mode
      :config
      (global-undo-tree-mode 1))
        

Cheatsheet and experiments

Playground and how to do different things, not necessarily used in my Emacs config but useful sometimes.

Export an org file to separate per-top-header markdown files. Based on https://medium.com/@lakshminp/publishing-a-book-using-org-mode-9e817a56d144. This code is kept separately at https://gist.github.com/zzamboni/2e6ac3c4f577249d98efb224d9d34488.

;; Call this function with "M-x org-multi-file-md-export"
(defun org-multi-file-md-export ()
  "Export current buffer to multiple Markdown files."
  (interactive)
  ;; Loop over all entries in the file
  (org-map-entries
   (lambda ()
     (let* ((level (nth 1 (org-heading-components)))
            (title (or (nth 4 (org-heading-components)) ""))
            ;; Export filename is the EXPORT_FILE_NAME property, or the
            ;; lower-cased section title if it's not set.
            (filename
             (or (org-entry-get (point) "EXPORT_FILE_NAME")
                 (concat (replace-regexp-in-string " " "-" (downcase title)) ".md"))))
       (when (= level 1) ;; export only first level entries
         ;; Mark the subtree so that the title also gets exported
         (org-mark-subtree)
         ;; Call the export function. This is one of the base org
         ;; functions, the 'md defines the backend to use for the
         ;; conversion. For exporting to other formats, simply use the
         ;; correct backend name, and also change the file extension
         ;; above.
         (org-export-to-file 'md filename nil t nil))))
   ;; skip headlines tagged with "noexport" (this is an argument to
   ;; org-map-entries above)
   "-noexport")
  nil nil)

This is how we get a global header property in org-mode

(alist-get :tangle
           (org-babel-parse-header-arguments
            (org-entry-get-with-inheritance "header-args")))

Testing formatting org snippets to look like noweb-rendered output (disabled for now).

(eval-after-load 'ob
  (customize-set-variable
   'org-entities-user
   '(("llangle" "\\llangle" t "&lang;&lang;" "<<" "<<" "«")
     ("rrangle" "\\rrangle" t "&rang;&rang;" ">>" ">>" "»")))
  (setq org-babel-exp-code-template
        (concat "\n@@latex:\\noindent@@\\llangle​/%name/​\\rrangle\\equiv\n"
                org-babel-exp-code-template)))

An experiment to reduce file tangle time, from https://www.wisdomandwonder.com/article/10630/how-fast-can-you-tangle-in-org-mode. In my tests it doesn’t have a noticeable impact.

(setq help/default-gc-cons-threshold gc-cons-threshold)
(defun help/set-gc-cons-threshold (&optional multiplier notify)
  "Set `gc-cons-threshold' either to its default value or a
     `multiplier' thereof."
  (let* ((new-multiplier (or multiplier 1))
         (new-threshold (* help/default-gc-cons-threshold
                           new-multiplier)))
    (setq gc-cons-threshold new-threshold)
    (when notify (message "Setting `gc-cons-threshold' to %s"
                          new-threshold))))
(defun help/double-gc-cons-threshold () "Double `gc-cons-threshold'." (help/set-gc-cons-threshold 10))
(add-hook 'org-babel-pre-tangle-hook #'help/double-gc-cons-threshold)
(add-hook 'org-babel-post-tangle-hook #'help/set-gc-cons-threshold)

A work-in-progress Hammerspoon shell for Emacs, posted on the Hammerspoon mailing list.

;;===> hammerspoon-shell
;; Quick and dirty shell with interactive history search and persistence
;; Just drop into your ~/.emacs file.
;;
;; A hammerspoon buffer is any lua buffer visiting a pathname like
;;    **/*hammerspoon**/*.lua
;; Usage: M-x hammerspoon-shell, or Hyper-s in a hammerspoon buffer.
;; In any hammerspoon buffer, Hyper-c runs dofile(file) on the visited file.
;;
;; Tip: to reload a Spoon "MySpoon" without hs.reload:
;; package.loaded.MySpoon=false hs.spoons.use("MySpoon",{config={debug=true})
(add-hook 'lua-mode-hook
          (lambda ()
            (when (string-match "hammerspoon" buffer-file-name)
              (local-set-key (kbd "H-s") #'hammerspoon-shell)
              (local-set-key
               (kbd "H-c")
               (lambda ()
                 (interactive)
                 (save-buffer)
                 (let ((name buffer-file-name))
                   (unless (and (boundp 'hammerspoon-buffer)
                                (buffer-live-p hammerspoon-buffer))
                     (hammerspoon-shell))
                   (with-current-buffer hammerspoon-buffer
                     (goto-char (point-max))
                     (insert (concat "dofile(\"" name "\")"))
                     (comint-send-input))))))))

(defvar hammerspoon-buffer nil)
(defun hammerspoon-shell ()
  (interactive)
  (if (and hammerspoon-buffer (comint-check-proc hammerspoon-buffer))
      (pop-to-buffer hammerspoon-buffer)
    (setq hammerspoon-buffer (make-comint "hammerspoon"
                                          "/usr/local/bin/hs" nil "-C"))
    (let* ((process (get-buffer-process hammerspoon-buffer))
           (history-file "~/.hammerspoon/.hs-history"))
      (pop-to-buffer hammerspoon-buffer)
      (turn-on-comint-history history-file)
      (local-set-key (kbd "<down>") (lambda() (interactive)
                                      (comint-move-or-history nil)))
      (local-set-key (kbd "<up>") (lambda() (interactive)
                                    (comint-move-or-history 'up))))))

;; Comint configs and extensions
(setq comint-input-ring-size 1024
      comint-history-isearch 'dwim)
(defun comint-move-or-history (up &optional arg)
  "History if at process mark, move otherwise"
  (interactive)
  (let* ((proc (get-buffer-process (current-buffer)))
         (proc-pos (if proc (marker-position (process-mark proc))))
         (arg (or arg 1))
         (arg (if up arg (- arg))))
    (if (and proc
             (if up
                 (= (line-number-at-pos) (line-number-at-pos proc-pos))
               (= (line-number-at-pos) (line-number-at-pos (point-max)))))
        (comint-previous-input arg)
      (forward-line (- arg)))))

(defun comint-write-history-on-exit (process event)
  (comint-write-input-ring)
  (let ((buf (process-buffer process)))
    (when (buffer-live-p buf)
      (with-current-buffer buf
        (insert (format "\nProcess %s %s" process event))))))

(defun turn-on-comint-history (&optional file)
  (let ((process (get-buffer-process (current-buffer))))
    (when process
      (setq comint-input-ring-file-name
            (or file
                (format "~/.emacs.d/inferior-%s-history"
                        (process-name process))))
      (comint-read-input-ring)
      ;; Ensure input ring gets written
      (add-hook 'kill-buffer-hook 'comint-write-input-ring nil t)
      (set-process-sentinel process
                            #'comint-write-history-on-exit))))

;; Ensure all input rings get written on exit
(defun comint-write-input-ring-all-buffers ()
  (mapc (lambda (buffer)
          (with-current-buffer buffer
            (comint-write-input-ring)))
        (buffer-list)))
(add-hook 'kill-emacs-hook 'comint-write-input-ring-all-buffers)

Epilogue

Here we close the let expression from the preface.

(setq gc-cons-threshold (* 2 1000 1000))
)