Emacs
![]() |
Earlier releases of this book included my previous, hand-maintained Emacs configuration. I have replaced it with my Doom config because it is what I use now, and also because it is a good example of generating multiple config files from the same Org file. My old Emacs config is still available at https://github.com/zzamboni/dot-emacs/blob/master/init.org. |
![]() |
The contents of this section is included directly from my real Doom Emacs config file at https://github.com/zzamboni/dot-doom/blob/master/doom.org, using the following Org directive (the first 15 lines are skipped because they contain some global document directives which do not apply in this context): 1 #+include: "~/.doom.d/doom.org" :lines "14-"
|
This is my Doom Emacs configuration. From this org file, all the necessary Doom Emacs config files are generated.
This file is written in literate programming style using org-mode. See init.el, packages.el and config.el for the generated files. You can see this in a nicer format on my blog post My Doom Emacs configuration, with commentary.
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.
Note: a lot of manual configuration has been rendered moot by using Emacs Doom, which aggregates a well-maintained and organized collection of common configuration settings for performance optimization, package management, commonly used packages (e.g. Org) and much more.
Doom config file overview
Doom Emacs uses three config files:
init.eldefines which of the existing Doom modules are loaded. A Doom module is a bundle of packages, configuration and commands, organized into a unit that can be toggled easily from this file.packages.eldefines which packages should be installed, beyond those that are installed and loaded as part of the enabled modules.config.elcontains all custom configuration and code.
There are other files that can be loaded, but theses are the main ones. The load order of different files is defined depending on the type of session being started.
All the config files are generated from this Org file, to try and make its meaning as clear as possible. All package! declarations are written to packages.el, all other LISP code is written to config.el.
Config file headers
We start by simply defining the standard headers used by the three files. These headers come from the initial files generated by doom install, and contain either some Emacs-LISP relevant indicators like lexical-binding, or instructions about the contents of the file.
1 ;;; init.el -*- lexical-binding: t; -*-
2
3 ;; DO NOT EDIT THIS FILE DIRECTLY
4 ;; This is a file generated from a literate programing source file located at
5 ;; https://gitlab.com/zzamboni/dot-doom/-/blob/master/doom.org
6 ;; You should make any changes there and regenerate it from Emacs org-mode
7 ;; using org-babel-tangle (C-c C-v t)
8
9 ;; This file controls what Doom modules are enabled and what order they load
10 ;; in. Remember to run 'doom sync' after modifying it!
11
12 ;; NOTE Press 'SPC h d h' (or 'C-h d h' for non-vim users) to access Doom's
13 ;; documentation. There you'll find a "Module Index" link where you'll find
14 ;; a comprehensive list of Doom's modules and what flags they support.
15
16 ;; NOTE Move your cursor over a module's name (or its flags) and press 'K' (or
17 ;; 'C-c c k' for non-vim users) to view its documentation. This works on
18 ;; flags as well (those symbols that start with a plus).
19 ;;
20 ;; Alternatively, press 'gd' (or 'C-c c d') on a module to browse its
21 ;; directory (for easy access to its source code).
1 ;; -*- no-byte-compile: t; -*-
2 ;;; $DOOMDIR/packages.el
3
4 ;; DO NOT EDIT THIS FILE DIRECTLY
5 ;; This is a file generated from a literate programing source file located at
6 ;; https://gitlab.com/zzamboni/dot-doom/-/blob/master/doom.org
7 ;; You should make any changes there and regenerate it from Emacs org-mode
8 ;; using org-babel-tangle (C-c C-v t)
9
10 ;; To install a package with Doom you must declare them here and run 'doom sync'
11 ;; on the command line, then restart Emacs for the changes to take effect -- or
12 ;; use 'M-x doom/reload'.
13
14 ;; To install SOME-PACKAGE from MELPA, ELPA or emacsmirror:
15 ;;(package! some-package)
16
17 ;; To install a package directly from a remote git repo, you must specify a
18 ;; `:recipe'. You'll find documentation on what `:recipe' accepts here:
19 ;; https://github.com/raxod502/straight.el#the-recipe-format
20 ;;(package! another-package
21 ;; :recipe (:host github :repo "username/repo"))
22
23 ;; If the package you are trying to install does not contain a PACKAGENAME.el
24 ;; file, or is located in a subdirectory of the repo, you'll need to specify
25 ;; `:files' in the `:recipe':
26 ;;(package! this-package
27 ;; :recipe (:host github :repo "username/repo"
28 ;; :files ("some-file.el" "src/lisp/*.el")))
29
30 ;; If you'd like to disable a package included with Doom, you can do so here
31 ;; with the `:disable' property:
32 ;;(package! builtin-package :disable t)
33
34 ;; You can override the recipe of a built in package without having to specify
35 ;; all the properties for `:recipe'. These will inherit the rest of its recipe
36 ;; from Doom or MELPA/ELPA/Emacsmirror:
37 ;;(package! builtin-package :recipe (:nonrecursive t))
38 ;;(package! builtin-package-2 :recipe (:repo "myfork/package"))
39
40 ;; Specify a `:branch' to install a package from a particular branch or tag.
41 ;; This is required for some packages whose default branch isn't 'master' (which
42 ;; our package manager can't deal with; see raxod502/straight.el#279)
43 ;;(package! builtin-package :recipe (:branch "develop"))
44
45 ;; Use `:pin' to specify a particular commit to install.
46 ;;(package! builtin-package :pin "1a2b3c4d5e")
47
48 ;; Doom's packages are pinned to a specific commit and updated from release to
49 ;; release. The `unpin!' macro allows you to unpin single packages...
50 ;;(unpin! pinned-package)
51 ;; ...or multiple packages
52 ;;(unpin! pinned-package another-pinned-package)
53 ;; ...Or *all* packages (NOT RECOMMENDED; will likely break things)
54 ;;(unpin! t)
1 ;;; $DOOMDIR/config.el -*- lexical-binding: t; -*-
2
3 ;; DO NOT EDIT THIS FILE DIRECTLY
4 ;; This is a file generated from a literate programing source file located at
5 ;; https://gitlab.com/zzamboni/dot-doom/-/blob/master/doom.org
6 ;; You should make any changes there and regenerate it from Emacs org-mode
7 ;; using org-babel-tangle (C-c C-v t)
8
9 ;; Place your private configuration here! Remember, you do not need to run 'doom
10 ;; sync' after modifying this file!
11
12 ;; Some functionality uses this to identify you, e.g. GPG configuration, email
13 ;; clients, file templates and snippets.
14 ;; (setq user-full-name "John Doe"
15 ;; user-mail-address "john@doe.com")
16
17 ;; Doom exposes five (optional) variables for controlling fonts in Doom. Here
18 ;; are the three important ones:
19 ;;
20 ;; + `doom-font'
21 ;; + `doom-variable-pitch-font'
22 ;; + `doom-big-font' -- used for `doom-big-font-mode'; use this for
23 ;; presentations or streaming.
24 ;;
25 ;; They all accept either a font-spec, font string ("Input Mono-12"), or xlfd
26 ;; font string. You generally only need these two:
27 ;; (setq doom-font (font-spec :family "monospace" :size 12 :weight 'semi-light)
28 ;; doom-variable-pitch-font (font-spec :family "sans" :size 13))
29
30 ;; There are two ways to load a theme. Both assume the theme is installed and
31 ;; available. You can either set `doom-theme' or manually load a theme with the
32 ;; `load-theme' function. This is the default:
33 ;; (setq doom-theme 'doom-one)
34
35 ;; If you use `org' and don't want your org files in the default location below,
36 ;; change `org-directory'. It must be set before org loads!
37 ;; (setq org-directory "~/org/")
38
39 ;; This determines the style of line numbers in effect. If set to `nil', line
40 ;; numbers are disabled. For relative line numbers, set this to `relative'.
41 ;; (setq display-line-numbers-type t)
42
43 ;; Here are some additional functions/macros that could help you configure Doom:
44 ;;
45 ;; - `load!' for loading external *.el files relative to this one
46 ;; - `use-package!' for configuring packages
47 ;; - `after!' for running code after a package has loaded
48 ;; - `add-load-path!' for adding directories to the `load-path', relative to
49 ;; this file. Emacs searches the `load-path' when you load packages with
50 ;; `require' or `use-package'.
51 ;; - `map!' for binding new keys
52 ;;
53 ;; To get information about any of these functions/macros, move the cursor over
54 ;; the highlighted symbol at press 'K' (non-evil users must press 'C-c c k').
55 ;; This will open documentation for it, including demos of how they are used.
56 ;;
57 ;; You can also try 'gd' (or 'C-c c d') to jump to their definition and see how
58 ;; they are implemented.
Customized variables
Note: do not use M-x customize or the customize API in general. Doom is designed to be configured programmatically from your config.el, which can conflict with Customize’s way of modifying variables.
All necessary settings are therefore set by hand as part of this configuration file. The only exceptions are “safe variable” and “safe theme” settings, which are automatically saved by Emacs in custom.el, but this is OK as they don’t conflict with anything else from the config.
Doom modules
This code is written to the init.el to select which modules to load. Written here as-is for now, as it is quite well structured and clear.
1 (doom!
2 :input
3 ;;chinese
4 ;;japanese
5 ;;layout ; auie,ctsrnm is the superior home row
6
7 :completion
8 (company +childframe) ; the ultimate code completion backend
9 ;;helm ; the *other* search engine for love and life
10 ;;ido ; the other *other* search engine...
11 (ivy +prescient -childframe
12 -fuzzy +icons) ; a search engine for love and life
13
14 :ui
15 ;;deft ; notational velocity for Emacs
16 doom ; what makes DOOM look the way it does
17 doom-dashboard ; a nifty splash screen for Emacs
18 ;;doom-quit ; DOOM quit-message prompts when you quit Emacs
19 ;;fill-column ; a `fill-column' indicator
20 hl-todo ; highlight TODO/FIXME/NOTE/DEPRECATED/HACK/REVIEW
21 ;;hydra
22 ;;indent-guides ; highlighted indent columns
23 (ligatures +extra) ; ligatures or substitute text with pretty symbols
24 ;;minimap ; show a map of the code on the side
25 modeline ; snazzy, Atom-inspired modeline, plus API
26 nav-flash ; blink cursor line after big motions
27 ;;neotree ; a project drawer, like NERDTree for vim
28 ophints ; highlight the region an operation acts on
29 (popup +defaults) ; tame sudden yet inevitable temporary windows
30 ;;tabs ; a tab bar for Emacs
31 ;;treemacs ; a project drawer, like neotree but cooler
32 ;;unicode ; extended unicode support for various languages
33 ;;vc-gutter ; vcs diff in the fringe
34 vi-tilde-fringe ; fringe tildes to mark beyond EOB
35 window-select ; visually switch windows
36 workspaces ; tab emulation, persistence & separate workspaces
37 zen ; distraction-free coding or writing
38
39 :editor
40 ;;(evil +everywhere) ; come to the dark side, we have cookies
41 file-templates ; auto-snippets for empty files
42 ;;fold ; (nigh) universal code folding
43 ;;(format +onsave) ; automated prettiness
44 ;;god ; run Emacs commands without modifier keys
45 ;;lispy ; vim for lisp, for people who don't like vim
46 ;;multiple-cursors ; editing in many places at once
47 ;;objed ; text object editing for the innocent
48 ;;parinfer ; turn lisp into python, sort of
49 ;;rotate-text ; cycle region at point between text candidates
50 snippets ; my elves. They type so I don't have to
51 ;;word-wrap ; soft wrapping with language-aware indent
52
53 :emacs
54 dired ; making dired pretty [functional]
55 electric ; smarter, keyword-based electric-indent
56 ;;ibuffer ; interactive buffer management
57 undo ; persistent, smarter undo for your inevitable mistakes
58 vc ; version-control and Emacs, sitting in a tree
59
60 :term
61 ;;eshell ; the elisp shell that works everywhere
62 ;;shell ; simple shell REPL for Emacs
63 ;;term ; basic terminal emulator for Emacs
64 vterm ; the best terminal emulation in Emacs
65
66 :checkers
67 (syntax +childframe) ; tasing you for every semicolon you forget
68 spell ; tasing you for misspelling mispelling
69 ;;grammar ; tasing grammar mistake every you make
70
71 :tools
72 ansible
73 debugger ; FIXME stepping through code, to help you add bugs
74 ;;direnv
75 ;;docker
76 ;;editorconfig ; let someone else argue about tabs vs spaces
77 ;;ein ; tame Jupyter notebooks with emacs
78 (eval +overlay) ; run code, run (also, repls)
79 gist ; interacting with github gists
80 lookup ; navigate your code and its documentation
81 lsp
82 (magit +forge) ; a git porcelain for Emacs
83 ;;make ; run make tasks from Emacs
84 pass ; password manager for nerds
85 ;;pdf ; pdf enhancements
86 ;;prodigy ; FIXME managing external services & code builders
87 ;;rgb ; creating color strings
88 ;;taskrunner ; taskrunner for all your projects
89 ;;terraform ; infrastructure as code
90 ;;tmux ; an API for interacting with tmux
91 ;;upload ; map local to remote projects via ssh/ftp
92
93 :os
94 (:if IS-MAC macos) ; improve compatibility with macOS
95 ;;tty ; improve the terminal Emacs experience
96
97 :lang
98 ;;agda ; types of types of types of types...
99 ;;cc ; C/C++/Obj-C madness
100 ;;clojure ; java with a lisp
101 common-lisp ; if you've seen one lisp, you've seen them all
102 ;;coq ; proofs-as-programs
103 ;;crystal ; ruby at the speed of c
104 ;;csharp ; unity, .NET, and mono shenanigans
105 ;;data ; config/data formats
106 ;;(dart +flutter) ; paint ui and not much else
107 ;;elixir ; erlang done right
108 ;;elm ; care for a cup of TEA?
109 emacs-lisp ; drown in parentheses
110 ;;erlang ; an elegant language for a more civilized age
111 (ess +lsp) ; emacs speaks statistics
112 ;;faust ; dsp, but you get to keep your soul
113 ;;fsharp ; ML stands for Microsoft's Language
114 ;;fstar ; (dependent) types and (monadic) effects and Z3
115 ;;gdscript ; the language you waited for
116 (go +lsp) ; the hipster dialect
117 ;;(haskell +dante) ; a language that's lazier than I am
118 ;;hy ; readability of scheme w/ speed of python
119 ;;idris ; a language you can depend on
120 json ; At least it ain't XML
121 ;;(java +meghanada) ; the poster child for carpal tunnel syndrome
122 ;;javascript ; all(hope(abandon(ye(who(enter(here))))))
123 ;;julia ; a better, faster MATLAB
124 ;;kotlin ; a better, slicker Java(Script)
125 (latex +latexmk) ; writing papers in Emacs has never been so fun
126 ;;lean
127 ;;factor
128 ;;ledger ; an accounting system in Emacs
129 lua ; one-based indices? one-based indices
130 markdown ; writing docs for people to ignore
131 ;;nim ; python + lisp at the speed of c
132 ;;nix ; I hereby declare "nix geht mehr!"
133 ;;ocaml ; an objective camel
134 (org +pretty +journal ;-dragndrop
135 +hugo +roam +pandoc
136 +present) ; organize your plain life in plain text
137 ;;php ; perl's insecure younger brother
138 plantuml ; diagrams for confusing people more
139 ;;purescript ; javascript, but functional
140 python ; beautiful is better than ugly
141 ;;qt ; the 'cutest' gui framework ever
142 racket ; a DSL for DSLs
143 ;;raku ; the artist formerly known as perl6
144 ;;rest ; Emacs as a REST client
145 rst ; ReST in peace
146 ;;(ruby +rails) ; 1.step {|i| p "Ruby is #{i.even? ? 'love' : 'life'}"}
147 rust ; Fe2O3.unwrap().unwrap().unwrap().unwrap()
148 ;;scala ; java, but good
149 ;;scheme ; a fully conniving family of lisps
150 (sh +lsp) ; she sells {ba,z,fi}sh shells on the C xor
151 ;;sml
152 ;;solidity ; do you need a blockchain? No.
153 ;;swift ; who asked for emoji variables?
154 ;;terra ; Earth and Moon in alignment for performance.
155 ;;web ; the tubes
156 (yaml +lsp) ; JSON, but readable
157
158 :email
159 ;;(mu4e +gmail)
160 ;;notmuch
161 ;;(wanderlust +gmail)
162
163 :app
164 ;;calendar
165 everywhere ; *leave* Emacs!? You must be joking
166 irc ; how neckbeards socialize
167 ;;(rss +org) ; emacs as an RSS reader
168 ;;twitter ; twitter client https://twitter.com/vnought
169
170 :config
171 ;;literate
172 (default +bindings +smartparens))
General configuration
My user information.
1 (setq user-full-name "Diego Zamboni"
2 user-mail-address "diego@zzamboni.org")
Change the Mac and Linux modifiers to my liking. I also disable passing Control characters to the system, to avoid that C-M-space launches the Character viewer instead of running mark-sexp.
1 (cond (IS-MAC
2 (setq mac-command-modifier 'meta
3 mac-option-modifier 'alt
4 mac-right-option-modifier 'alt
5 mac-pass-control-to-system nil))
6 (IS-LINUX
7 (setq x-meta-keysym 'super
8 x-super-keysym 'meta)))
When at the beginning of the line, make Ctrl-K remove the whole line, instead of just emptying it.
1 (setq kill-whole-line t)
Disable line numbers.
1 ;; This determines the style of line numbers in effect. If set to `nil', line
2 ;; numbers are disabled. For relative line numbers, set this to `relative'.
3 (setq display-line-numbers-type nil)
For some reason Doom disables auto-save and backup files by default. Let’s reenable them.
1 (setq auto-save-default t
2 make-backup-files t)
Disable exit confirmation.
1 (setq confirm-kill-emacs nil)
Doom configures auth-sources by default to include the Keychain on macOS, but it puts it at the beginning of the list. This causes creation of auth items to fail because the macOS Keychain sources do not support creation yet. I reverse it to leave ~/.authinfo.gpg at the beginning.
1 (after! auth-source
2 (setq auth-sources (nreverse auth-sources)))
Auto-save the desktop setup (open buffers, etc.)
1 (desktop-save-mode 1)
2 (setq! desktop-load-locked-desktop t)
Visual, session and window settings
I made a super simple set of Doom-Emacs custom splash screens by combining a Doom logo with the word “Emacs” rendered in the Doom Font. You can see them at https://gitlab.com/zzamboni/dot-doom/-/tree/master/splash (you can also see one of them at the top of this file). I configure it to be used instead of the default splash screen. It took me all of 5 minutes to make, so improvements are welcome!
If you want to choose at random among a few different splash images, you can list them in alternatives.
You can find other splash images at the jeetelongname/doom-banners GitHub repository.
1 (let ((alternatives '("doom-emacs-bw-light.svg"
2 "doom-emacs-flugo-slant_out_purple-small.png"
3 "doom-emacs-flugo-slant_out_bw-small.png")))
4 (setq fancy-splash-image
5 (concat doom-private-dir "splash/"
6 (nth (random (length alternatives)) alternatives))))
I eliminate all but the first two items in the dashboard menu, since those are the only ones I still use sometimes.
1 (setq +doom-dashboard-menu-sections (cl-subseq +doom-dashboard-menu-sections 0 2))
Set base and variable-pitch fonts. I currently like Fira Code and Alegreya (another favorite and my previous choice: ET Book).
1 (setq doom-font (font-spec :family "Fira Code Nerd Font" :size 16)
2 ;;doom-variable-pitch-font (font-spec :family "ETBembo" :size 18)
3 doom-variable-pitch-font (font-spec :family "Alegreya" :size 16))
Allow mixed fonts in a buffer. This is particularly useful for Org mode, so I can mix source and prose blocks in the same document. I also manually enable solaire-mode in Org mode as a workaround for font scaling not working properly.
1 (add-hook! 'org-mode-hook #'mixed-pitch-mode)
2 ;;(add-hook! 'org-mode-hook #'solaire-mode)
3 (setq mixed-pitch-variable-pitch-cursor nil)
Keybindings to increase/decrease font size.
1 (map! "C-=" #'doom/increase-font-size
2 "C--" #'doom/decrease-font-size
3 "C-0" #'doom/reset-font-size)
Set the theme to use. I like the Spacemacs-Light, which does not come with Doom, so we need to install it from package.el:
1 (package! spacemacs-theme)
2 ;; Trying https://github.com/agraul/doom-alabaster-theme
3 ;;(package! doom-alabaster-theme :recipe (:host github :repo "agraul/doom-alabaster-theme"))
And then from config.el we specify the theme to use.
1 ;;(setq doom-theme 'doom-alabaster)
2 (setq doom-theme 'spacemacs-light)
3 ;;(setq doom-theme 'doom-nord-light) ;;OK
4 ;;NO (setq doom-theme 'doom-solarized-light)
5 ;;(setq doom-theme 'doom-one-light) ;;MAYBE
6 ;;NO (setq doom-theme 'doom-opera-light)
7 ;;NO (setq doom-theme 'doom-tomorrow-day)
8 ;;NO (setq doom-theme 'doom-acario-light)
I love the spacemacs-light theme, but for some reason, the transparent dashboard images showed up with a light tint, which I eventually tracked to the fact that Doom by default uses the font-lock-comment-face for the dashboard banner image, and this this face has a background color in Spacemacs-light. I redefine the doom-dashboard-banner face to use the default face, which fixes the problem. Another way to fix it (commented out below) is to disable the background tint color in the theme. While we are at it, I also fix doom-dashboard-loaded, which suffers from the same problem.
1 (custom-set-faces!
2 '(doom-dashboard-banner :inherit default)
3 '(doom-dashboard-loaded :inherit default))
4 ;;(setq spacemacs-theme-comment-bg nil)
In my previous configuration, I used to automatically restore the previous session upon startup. Doom Emacs starts up so fast that it does not feel right to do it automatically. In any case, from the Doom dashboard I can simply press Enter to invoke the first item, which is “Reload Last Session”. So this code is commented out now.
1 ;;(add-hook 'window-setup-hook #'doom/quickload-session)
Maximize the window upon startup.
1 ;;(setq initial-frame-alist '((top . 1) (left . 1) (width . 114) (height . 32)))
2 ;;(add-to-list 'initial-frame-alist '(maximized))
Truncate lines in ivy childframes. Thanks Henrik! (disabled for now)
1 (setq posframe-arghandler
2 (lambda (buffer-or-name key value)
3 (or (and (eq key :lines-truncate)
4 (equal ivy-posframe-buffer
5 (if (stringp buffer-or-name)
6 buffer-or-name
7 (buffer-name buffer-or-name)))
8 t)
9 value)))
I like ligatures, but some of the ones that get enabled by the (ligatures +extra) module don’t work in the font I use, or I don’t like them, so I disable them.
1 (plist-put! +ligatures-extra-symbols
2 :and nil
3 :or nil
4 :for nil
5 :not nil
6 :true nil
7 :false nil
8 :int nil
9 :float nil
10 :str nil
11 :bool nil
12 :list nil
13 )
1 (let ((ligatures-to-disable '(:true :false :int :float :str :bool :list :and :or :for :not)))
2 (dolist (sym ligatures-to-disable)
3 (plist-put! +ligatures-extra-symbols sym nil)))
Enable showing a word count in the modeline. This is only shown for the modes listed in doom-modeline-continuous-word-count-modes (Markdown, GFM and Org by default).
1 (setq doom-modeline-enable-word-count t)
Enable pixel scrolling
1 ;;(pixel-scroll-precision-mode 1)
Key bindings
Doom Emacs has an extensive keybinding system, and most module functions are already bound. I modify some keybindings for simplicity of to match the muscle memory I have from my previous Emacs configuration.
Note: I do not use VI-style keybindings (which are the default for Doom) because I have decades of muscle memory with Emacs-style keybindings. You may need to adjust these if you want to use them.
Miscellaneous keybindings
Use counsel-buffer-or-recentf for C-x b. I like being able to see all recently opened files, instead of just the current ones. This makes it possible to use C-x b almost as a replacement for C-c C-f, for files that I edit often. Similarly, for switching between non-file buffers I use counsel-switch-buffer, mapped to C-x C-b.
1 (map! "C-x b" #'counsel-buffer-or-recentf
2 "C-x C-b" #'counsel-switch-buffer)
The counsel-buffer-or-recentf function by default shows duplicated entries because it does not abbreviate the paths of the open buffers. The function below fixes this, I have submitted this change to the counsel library (https://github.com/abo-abo/swiper/pull/2687), in the meantime I define it here and integrate it via advice-add.
1 (defun zz/counsel-buffer-or-recentf-candidates ()
2 "Return candidates for `counsel-buffer-or-recentf'."
3 (require 'recentf)
4 (recentf-mode)
5 (let ((buffers
6 (delq nil
7 (mapcar (lambda (b)
8 (when (buffer-file-name b)
9 (abbreviate-file-name (buffer-file-name b))))
10 (delq (current-buffer) (buffer-list))))))
11 (append
12 buffers
13 (cl-remove-if (lambda (f) (member f buffers))
14 (counsel-recentf-candidates)))))
15
16 (advice-add #'counsel-buffer-or-recentf-candidates
17 :override #'zz/counsel-buffer-or-recentf-candidates)
The switch-buffer-functions package allows us to update the recentf buffer list as we switch between them, so that the list produced by counsel-buffer-or-recentf is shown in the order the buffers have been visited, rather than in the order they were opened. Thanks to @tau3000 for the tip.
1 (package! switch-buffer-functions)
1 (use-package! switch-buffer-functions
2 :after recentf
3 :preface
4 (defun my-recentf-track-visited-file (_prev _curr)
5 (and buffer-file-name
6 (recentf-add-file buffer-file-name)))
7 :init
8 (add-hook 'switch-buffer-functions #'my-recentf-track-visited-file))
Use +default/search-buffer for searching by default, I like the Swiper interface.
1 ;;(map! "C-s" #'counsel-grep-or-swiper)
2 (map! "C-s" #'+default/search-buffer)
Map C-c C-g to magit-status - I have too ingrained muscle memory for this keybinding.
1 (map! :after magit "C-c C-g" #'magit-status)
Interactive search key bindings - visual-regexp-steroids provides sane regular expressions and visual incremental search. I use the pcre2el package to support PCRE-style regular expressions.
1 (package! pcre2el)
2 (package! visual-regexp-steroids)
1 (use-package! visual-regexp-steroids
2 :defer 3
3 :config
4 (require 'pcre2el)
5 (setq vr/engine 'pcre2el)
6 (map! "C-c s r" #'vr/replace)
7 (map! "C-c s q" #'vr/query-replace))
The Doom undo package introduces the use of undo-fu, which makes undo/redo more “lineal”. I normally use C-/ for undo and Emacs doesn’t have a separate “redo” action, so I map C-? (in my keyboard, the same combination + Shift) for redo.
1 (after! undo-fu
2 (map! :map undo-fu-mode-map "C-?" #'undo-fu-only-redo))
Replace the default goto-line keybindings with avy-goto-line, which is more flexible and also falls back to goto-line if a number is typed.
1 (map! "M-g g" #'avy-goto-line)
2 (map! "M-g M-g" #'avy-goto-line)
Map a keybindings for counsel-outline, which allows easily navigating documents (it works best with Org documents, but it also tries to extract navigation information from other file types).
1 (map! "M-g o" #'counsel-outline)
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 this functionality, bound to the same key. Inspired by NavigatingParentheses, but modified to use smartparens instead of the default commands, and to work on brackets and braces.
1 (after! smartparens
2 (defun zz/goto-match-paren (arg)
3 "Go to the matching paren/bracket, otherwise (or if ARG is not
4 nil) insert %. vi style of % jumping to matching brace."
5 (interactive "p")
6 (if (not (memq last-command '(set-mark
7 cua-set-mark
8 zz/goto-match-paren
9 down-list
10 up-list
11 end-of-defun
12 beginning-of-defun
13 backward-sexp
14 forward-sexp
15 backward-up-list
16 forward-paragraph
17 backward-paragraph
18 end-of-buffer
19 beginning-of-buffer
20 backward-word
21 forward-word
22 mwheel-scroll
23 backward-word
24 forward-word
25 mouse-start-secondary
26 mouse-yank-secondary
27 mouse-secondary-save-then-kill
28 move-end-of-line
29 move-beginning-of-line
30 backward-char
31 forward-char
32 scroll-up
33 scroll-down
34 scroll-left
35 scroll-right
36 mouse-set-point
37 next-buffer
38 previous-buffer
39 previous-line
40 next-line
41 back-to-indentation
42 doom/backward-to-bol-or-indent
43 doom/forward-to-last-non-comment-or-eol
44 )))
45 (self-insert-command (or arg 1))
46 (cond ((looking-at "\\s\(") (sp-forward-sexp) (backward-char 1))
47 ((looking-at "\\s\)") (forward-char 1) (sp-backward-sexp))
48 (t (self-insert-command (or arg 1))))))
49 (map! "%" 'zz/goto-match-paren))
Org mode
Org mode has become my primary tool for writing, blogging, coding, presentations and more. 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 homeworks and projects), but Org is the first tool I have encountered which makes it practical. Here are some of the resources I have found useful in learning it:
- Howard Abrams’ Introduction to Literate Programming, which got me jumpstarted into writing code documented with org-mode.
- Nick Anderson’s Level up your notes with Org, which contains many useful tips and configuration tricks. Nick’s recommendation also got me to start looking into Org-mode in the first place!
- Sacha Chua’s Some tips for learning Org Mode for Emacs, her Emacs configuration and many of her other articles.
- Rainer König’s OrgMode Tutorial video series.
Doom’s Org module provides a lot of sane configuration settings, so I don’t have to configure so much as in my previous hand-crafted config.
General Org Configuration
Unpin Org to get around a current bug.
1 ;;(unpin! org-mode)
Default directory for Org files.
1 (setq org-directory "~/org/")
Hide Org markup indicators.
1 (after! org (setq org-hide-emphasis-markers t))
Insert Org headings at point, not after the current subtree (this is enabled by default by Doom).
1 (after! org (setq org-insert-heading-respect-content nil))
Enable logging of done tasks, and log stuff into the LOGBOOK drawer by default
1 (after! org
2 (setq org-log-done t)
3 (setq org-log-into-drawer t))
Use the special C-a, C-e and C-k definitions for Org, which enable some special behavior in headings.
1 (after! org
2 (setq org-special-ctrl-a/e t)
3 (setq org-special-ctrl-k t))
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.
1 (after! org
2 (setq org-use-speed-commands
3 (lambda ()
4 (and (looking-at org-outline-regexp)
5 (looking-back "^\**")))))
Disable electric-mode, which is now respected by Org and which creates some confusing indentation sometimes.
1 (add-hook! org-mode (electric-indent-local-mode -1))
I really dislike completion of words as I type prose (in code it’s OK), so I disable it in Org and Markdown modes.
1 (defun zz/adjust-org-company-backends ()
2 (remove-hook 'after-change-major-mode-hook '+company-init-backends-h)
3 (setq-local company-backends nil))
4 (add-hook! org-mode (zz/adjust-org-company-backends))
5 (add-hook! markdown-mode (zz/adjust-org-company-backends))
Org visual settings
Enable variable and visual line mode in Org mode by default.
1 (add-hook! org-mode :append
2 #'visual-line-mode
3 #'variable-pitch-mode)
Use org-appear to reveal emphasis markers when moving the cursor over them.
1 (package! org-appear
2 :recipe (:host github
3 :repo "awth13/org-appear"))
1 (add-hook! org-mode :append #'org-appear-mode)
Capturing and note taking
First, I define where all my Org-captured things can be found.
1 (after! org
2 (setq org-agenda-files
3 '("~/gtd" "~/Work/work.org.gpg" "~/org/")))
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).
First, I define a helper function to define keybindings that open files. Note that this requires lexical binding to be enabled, so that the lambda creates a closure, otherwise the keybindings don’t work.
1 (defun zz/add-file-keybinding (key file &optional desc)
2 (let ((key key)
3 (file file)
4 (desc desc))
5 (map! :desc (or desc file)
6 key
7 (lambda () (interactive) (find-file file)))))
Now I define keybindings to access my commonly-used org files.
1 (zz/add-file-keybinding "C-c z w" "~/Work/work.org.gpg" "work.org")
2 (zz/add-file-keybinding "C-c z i" "~/org/ideas.org" "ideas.org")
3 (zz/add-file-keybinding "C-c z p" "~/org/projects.org" "projects.org")
4 (zz/add-file-keybinding "C-c z d" "~/org/diary.org" "diary.org")
I’m still trying out org-roam, although I have not figured out very well how it works for my setup.
1 (setq org-roam-directory "~/Dropbox/Personal/org-roam/")
2 (setq +org-roam-open-buffer-on-find-file t)
Configure attachments to be stored together with their Org document.
1 (setq org-attach-id-dir "attachments/")
Capturing images
Using org-download to make it easier to insert images into my org notes and blog posts. I don’t like the configuration provided by Doom as part of the (org +dragndrop) module, so I install the package by hand and configure it to my liking.
1 (package! org-download)
1 (after! org-download
2 (setq org-download-method 'directory)
3 (setq org-download-image-dir "images")
4 (setq org-download-heading-lvl nil)
5 (setq org-download-timestamp "%Y%m%d-%H%M%S_")
6 (setq org-image-actual-width 300))
7 (require 'org-download)
org-download implements all the basic machinery for downloading/copying and inserting an image in an org-mode file. However the user experience can be improved. I implemented two wrappers to fit my main use cases:
- Pasting images from the clipboard;
- Inserting images already in my laptop;
- Images must be inserted as links to themselves.
The first function zz/org-paste-clipboard inserts an image from the clipboard using org-download-clipboard, but asking for the filename to use for storing it.
1 (defun zz/org-paste-clipboard (&optional use-default-filename)
2 (interactive "P")
3 (require 'org-download)
4 (let ((file
5 (if (not use-default-filename)
6 (read-string (format "Filename [%s]: "
7 org-download-screenshot-basename)
8 nil nil org-download-screenshot-basename)
9 nil)))
10 (org-download-clipboard file)))
The second function zz/org-attach-file allows me to choose a file to attach. The file is copied with the same name to the org-download-image-dir directory. If the chosen image is already from that directory, it only inserts the link without copying the file again.
1 (defun zz/org-attach-file (&optional file)
2 (interactive (list (read-file-name "File to insert: "
3 (or (progn
4 (require 'dired-aux)
5 (dired-dwim-target-directory))
6 default-directory))))
7 (require 'org-download)
8 (if (file-in-directory-p file org-download-image-dir)
9 (org-download-insert-link (concat "file:" (file-relative-name file (org-attach-dir))) file)
10 (let ((file-url (concat "file://" file)))
11 (org-download-image file-url))))
I create keybindings for the two functions above.
1 (map! :map org-mode-map
2 "C-c l a y" #'zz/org-paste-clipboard
3 "C-M-y" #'zz/org-paste-clipboard
4 "C-c l a z" #'zz/org-attach-file
5 "C-M-z" #'zz/org-attach-file)
Finally, I like the images to be links to the image itself, so that in blog posts, for example, a thumbnail is shown, and clicking on it takes you to the full image. To this effect I define a variant of org-download-link-format-function which does this, and assign it to org-download-link-format-function.
1 (defun zz/org-download-link-format-function-link-to-file (filename)
2 "Insert the file as a link to itself."
3 (if (and (>= (string-to-number org-version) 9.3)
4 (eq org-download-method 'attach))
5 ;; Respect the default behavior if org-download-method is 'attach
6 (format "[[attachment:%s]]\n"
7 (org-link-escape
8 (file-relative-name filename (org-attach-dir))))
9 (let ((formatted-filename (org-link-escape
10 (funcall org-download-abbreviate-filename-function filename))))
11 ;; Here we use the correct link format so that the image is a link to itself
12 (format "[[file:%s][file:%s]]\n"
13 formatted-filename formatted-filename))))
14
15 (setq! org-download-link-format-function #'zz/org-download-link-format-function-link-to-file)
Capturing links
Capturing and creating internal Org links
I normally use counsel-org-link for linking between headings in an Org document. It shows me a searchable list of all the headings in the current document, and allows selecting one, automatically creating a link to it. Since it doesn’t have a keybinding by default, I give it one.
1 (map! :after counsel :map org-mode-map
2 "C-c l l h" #'counsel-org-link)
I also configure counsel-outline-display-style so that only the headline title is inserted into the link, instead of its full path within the document.
1 (after! counsel
2 (setq counsel-outline-display-style 'title))
counsel-org-link uses org-id as its backend which generates IDs using UUIDs, and it uses the ID property to store them. I prefer using human-readable IDs stored in the CUSTOM_ID property of each heading, so we need to make some changes.
First, configure org-id to use CUSTOM_ID if it exists. This affects the links generated by the org-store-link function.
1 (after! org-id
2 ;; Do not create ID if a CUSTOM_ID exists
3 (setq org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id))
Second, I override counsel-org-link-action, which is the function that actually generates and inserts the link, with a custom function that computes and inserts human-readable CUSTOM_ID links. This is supported by a few auxiliary functions for generating and storing the CUSTOM_ID.
1 (defun zz/make-id-for-title (title)
2 "Return an ID based on TITLE."
3 (let* ((new-id (replace-regexp-in-string "[^[:alnum:]]" "-" (downcase title))))
4 new-id))
5
6 (defun zz/org-custom-id-create ()
7 "Create and store CUSTOM_ID for current heading."
8 (let* ((title (or (nth 4 (org-heading-components)) ""))
9 (new-id (zz/make-id-for-title title)))
10 (org-entry-put nil "CUSTOM_ID" new-id)
11 (org-id-add-location new-id (buffer-file-name (buffer-base-buffer)))
12 new-id))
13
14 (defun zz/org-custom-id-get-create (&optional where force)
15 "Get or create CUSTOM_ID for heading at WHERE.
16
17 If FORCE is t, always recreate the property."
18 (org-with-point-at where
19 (let ((old-id (org-entry-get nil "CUSTOM_ID")))
20 ;; If CUSTOM_ID exists and FORCE is false, return it
21 (if (and (not force) old-id (stringp old-id))
22 old-id
23 ;; otherwise, create it
24 (zz/org-custom-id-create)))))
25
26 ;; Now override counsel-org-link-action
27 (after! counsel
28 (defun counsel-org-link-action (x)
29 "Insert a link to X.
30
31 X is expected to be a cons of the form (title . point), as passed
32 by `counsel-org-link'.
33
34 If X does not have a CUSTOM_ID, create it based on the headline
35 title."
36 (let* ((id (zz/org-custom-id-get-create (cdr x))))
37 (org-insert-link nil (concat "#" id) (car x)))))
Ta-da! Now using counsel-org-link inserts nice, human-readable links.
Capturing links to external applications
org-mac-link 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.
1 (when IS-MAC
2 (package! org-mac-link))
1 (when IS-MAC
2 (use-package! org-mac-link
3 :after org
4 :config
5 (setq org-mac-grab-Acrobat-app-p nil) ; Disable grabbing from Adobe Acrobat
6 (setq org-mac-grab-devonthink-app-p nil) ; Disable grabbinb from DevonThink
7 (map! :map org-mode-map
8 "C-c g" #'org-mac-grab-link)))
Tasks and agenda
Customize the agenda display to indent todo items by level to show nesting, and enable showing holidays in the Org agenda display.
1 (after! org-agenda
2 ;; (setq org-agenda-prefix-format
3 ;; '((agenda . " %i %-12:c%?-12t% s")
4 ;; ;; Indent todo items by level to show nesting
5 ;; (todo . " %i %-12:c%l")
6 ;; (tags . " %i %-12:c")
7 ;; (search . " %i %-12:c")))
8 (setq org-agenda-include-diary t))
Install and load some custom local holiday lists I’m interested in.
1 (package! mexican-holidays)
2 (package! swiss-holidays)
1 (use-package! holidays
2 :after org-agenda
3 :config
4 (require 'mexican-holidays)
5 (require 'swiss-holidays)
6 (setq swiss-holidays-zh-city-holidays
7 '((holiday-float 4 1 3 "Sechseläuten")
8 (holiday-float 9 1 3 "Knabenschiessen")))
9 (setq calendar-holidays
10 (append '((holiday-fixed 1 1 "New Year's Day")
11 (holiday-fixed 2 14 "Valentine's Day")
12 (holiday-fixed 4 1 "April Fools' Day")
13 (holiday-fixed 10 31 "Halloween")
14 (holiday-easter-etc)
15 (holiday-fixed 12 25 "Christmas")
16 (solar-equinoxes-solstices))
17 swiss-holidays
18 swiss-holidays-labour-day
19 swiss-holidays-catholic
20 swiss-holidays-zh-city-holidays
21 holiday-mexican-holidays)))
org-super-agenda provides great grouping and customization features to make agenda mode easier to use.
1 (package! org-super-agenda)
1 (use-package! org-super-agenda
2 :after org-agenda
3 :config
4 (setq org-super-agenda-groups '((:auto-dir-name t)))
5 (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.
1 (use-package! org-archive
2 :after org
3 :config
4 (setq org-archive-location "archive.org::datetree/"))
I have started using org-clock to track time I spend on tasks. Often I restart Emacs for different reasons in the middle of a session, so I want to persist all the running clocks and their history.
1 (after! org-clock
2 (setq org-clock-persist t)
3 (org-clock-persistence-insinuate))
GTD
I am trying out Trevoke’s org-gtd. I haven’t figured out my perfect workflow for tracking GTD with Org yet, but this looks like a very promising approach.
1 (package! org-gtd)
1 ;; Supress org-gtd update warning
2 (setq org-gtd-update-ack "2.1.0")
3 (setq org-gtd-update-ack "4.0.0")
4 (use-package! org-gtd
5 :after org
6 :config
7 ;; where org-gtd will put its files. This value is also the default one.
8 (setq org-gtd-directory "~/gtd/")
9 ;; package: https://github.com/Malabarba/org-agenda-property
10 ;; this is so you can see who an item was delegated to in the agenda
11 (setq org-agenda-property-list '("DELEGATED_TO"))
12 ;; I think this makes the agenda easier to read
13 (setq org-agenda-property-position 'next-line)
14 ;; package: https://www.nongnu.org/org-edna-el/
15 ;; org-edna is used to make sure that when a project task gets DONE,
16 ;; the next TODO is automatically changed to NEXT.
17 (setq org-edna-use-inheritance t)
18 (org-edna-load)
19 :bind
20 (("C-c d c" . org-gtd-capture) ;; add item to inbox
21 ("C-c d a" . org-agenda-list) ;; see what's on your plate today
22 ("C-c d p" . org-gtd-process-inbox) ;; process entire inbox
23 ("C-c d n" . org-gtd-show-all-next) ;; see all NEXT items
24 ;; see projects that don't have a NEXT item
25 ("C-c d s" . org-gtd-show-stuck-projects)
26 ;; the keybinding to hit when you're done editing an item in the
27 ;; processing phase
28 ("C-c d f" . org-gtd-clarify-finalize)))
Capture templates
We define the corresponding Org-GTD capture templates.
1 (after! (org-gtd org-capture)
2 (add-to-list 'org-capture-templates
3 '("i" "GTD item"
4 entry
5 (file (lambda () (org-gtd--path org-gtd-inbox-file-basename)))
6 "* %?\n%U\n\n %i"
7 :kill-buffer t))
8 (add-to-list 'org-capture-templates
9 '("l" "GTD item with link to where you are in emacs now"
10 entry
11 (file (lambda () (org-gtd--path org-gtd-inbox-file-basename)))
12 "* %?\n%U\n\n %i\n %a"
13 :kill-buffer t))
14 (add-to-list 'org-capture-templates
15 '("m" "GTD item with link to current Outlook mail message"
16 entry
17 (file (lambda () (org-gtd--path org-gtd-inbox-file-basename)))
18 "* %?\n%U\n\n %i\n %(org-mac-outlook-message-get-links)"
19 :kill-buffer t)))
I set up an advice before org-capture to make sure org-gtd and org-capture are loaded, which triggers the setup of the templates above.
1 (defadvice! +zz/load-org-gtd-before-capture (&optional goto keys)
2 :before #'org-capture
3 (require 'org-capture)
4 (require 'org-gtd))
Exporting a Curriculum Vitae
I use ox-awesomecv from Org-CV, to export my Curriculum Vitæ.
Org-CV is not yet in MELPA, so I install from its repository.
1 (package! org-cv
2 :recipe (:host gitlab
3 :repo "Titan-C/org-cv"))
For when I do development on it (I wrote the ox-awesomecv exporter), I check it out from my local repo - this is normally disabled.
1 (package! org-cv
2 :recipe (:local-repo "~/Dropbox/Personal/devel/emacs/org-cv"))
1 (use-package! ox-awesomecv
2 :after org
3 :config
4 (defun org-awesomecv--cventry-right-img-code (file)
5 (if file
6 (format "\\begin{wrapfigure}{r}{0.15\\textwidth}
7 \\raggedleft\\vspace{-10.0mm}
8 \\includegraphics[width=0.1\\textwidth]{%s}
9 \\end{wrapfigure}" file) "")))
10 (use-package! ox-moderncv
11 :after org)
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, 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:
- Publishing a Book with Leanpub and Org Mode by Jon Snader (from where I found the links to the above).
Building upon these, I 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. 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):
1 (package! ox-leanpub
2 :recipe (:local-repo "~/Dropbox/Personal/devel/emacs/ox-leanpub"))
1 (use-package! ox-leanpub
2 :after org
3 :config
4 (require 'ox-leanpub-markdown)
5 (org-leanpub-book-setup-menu-markdown))
I highly recommend using Markua rather than Markdown, as it is the format 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.
If you are interested in learning more about publishing to Leanpub with Org-mode, check out my book Publishing with Emacs, Org-mode and Leanpub.
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.
Doom Emacs includes and configures ox-hugo as part of its (:lang org +hugo) module, so all that’s left is to configure some parameters to my liking.
I set org-hugo-use-code-for-kbd so that I can apply a custom style to keyboard bindings in my blog.
1 (after! ox-hugo
2 (setq org-hugo-use-code-for-kbd t))
Define an org-capture template to create a new blog post skeleton automatically. Based on https://ox-hugo.scripter.co/doc/org-capture-setup/, with some customizations for my blog’s setup (like a default value for :featured_image). I also add :jump-to-captured t in the org-capture-templates definition so that when I create a new blog post snippet, I get automatically taken to that location so I can continue writing.
1 (after! org-capture
2 (defun org-hugo-new-subtree-post-capture-template ()
3 "Returns `org-capture' template string for new Hugo post.
4 See `org-capture-templates' for more information."
5 (let* ((title (read-from-minibuffer "Post Title: ")) ;Prompt to enter the post title
6 (fname (org-hugo-slug title)))
7 (mapconcat #'identity
8 `(
9 ,(concat "* TODO " title)
10 ":PROPERTIES:"
11 ,(concat ":export_hugo_bundle: " (format-time-string "%Y-%m-%d-") fname)
12 ":export_file_name: index"
13 ,(concat ":custom_id: " fname)
14 ":export_hugo_custom_front_matter: :featured_image /images/tram-zurich.jpg :toc false"
15 ":END:"
16 "#+BEGIN_DESCRIPTION\n%?\n#+END_DESCRIPTION" ;Place the cursor here at the end
17 "\n<write here>")
18 "\n")))
19
20 (add-to-list 'org-capture-templates
21 '("h" ;`org-capture' binding + h
22 "Hugo post"
23 entry
24 ;; It is assumed that below file is present in `org-directory'
25 ;; and that it has an "Ideas" heading. It can even be a
26 ;; symlink pointing to the actual location of all-posts.org!
27 (file+olp "~/Personal/websites/zzamboni.org/content-org/zzamboni.org" "Ideas")
28 (function org-hugo-new-subtree-post-capture-template)
29 :jump-to-captured t)))
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.
1 (defun zz/org-if-str (str &optional desc)
2 (when (org-string-nw-p str)
3 (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.
1 (defun zz/org-macro-hsapi-code (module &optional func desc)
2 (org-link-make-string
3 (concat "https://www.hammerspoon.org/docs/"
4 (concat module (zz/org-if-str func (concat "#" func))))
5 (or (org-string-nw-p desc)
6 (format "=%s="
7 (concat module
8 (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.
1 (defun zz/org-macro-keys-code-outer (str)
2 (mapconcat (lambda (s)
3 (concat "~" s "~"))
4 (split-string str)
5 (concat (string ?\u200B) "+" (string ?\u200B))))
6 (defun zz/org-macro-keys-code-inner (str)
7 (concat "~" (mapconcat (lambda (s)
8 (concat s))
9 (split-string str)
10 (concat (string ?\u200B) "-" (string ?\u200B)))
11 "~"))
12 (defun zz/org-macro-keys-code (str)
13 (zz/org-macro-keys-code-inner str))
Links to a specific section/function of the Lua manual.
1 (defun zz/org-macro-luadoc-code (func &optional section desc)
2 (org-link-make-string
3 (concat "https://www.lua.org/manual/5.3/manual.html#"
4 (zz/org-if-str func section))
5 (zz/org-if-str func desc)))
1 (defun zz/org-macro-luafun-code (func &optional desc)
2 (org-link-make-string
3 (concat "https://www.lua.org/manual/5.3/manual.html#"
4 (concat "pdf-" func))
5 (zz/org-if-str (concat "=" func "()=") desc)))
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.
1 (defun zz/org-reformat-buffer ()
2 (interactive)
3 (when (y-or-n-p "Really format current buffer? ")
4 (let ((document (org-element-interpret-data (org-element-parse-buffer))))
5 (erase-buffer)
6 (insert document)
7 (goto-char (point-min)))))
Avoiding non-Org mode files
org-pandoc-import is a mode that automates conversions to/from Org mode as much as possible.
1 (package! org-pandoc-import
2 :recipe (:host github
3 :repo "tecosaur/org-pandoc-import"
4 :files ("*.el" "filters" "preprocessors")))
1 (use-package org-pandoc-import)
Reveal.js presentations
I use org-re-reveal to make presentations. The functions below help me improve my workflow by automatically exporting the slides whenever I save the file, refreshing the presentation in my browser, and moving it to the slide where the cursor was when I saved the file. This helps keeping a “live” rendering of the presentation next to my Emacs window.
The first function is a modified version of the org-num--number-region function of the org-num package, but modified to only return the numbering of the innermost headline in which the cursor is currently placed.
1 (defun zz/org-current-headline-number ()
2 "Get the numbering of the innermost headline which contains the
3 cursor. Returns nil if the cursor is above the first level-1
4 headline, or at the very end of the file. Does not count
5 headlines tagged with :noexport:"
6 (require 'org-num)
7 (let ((org-num--numbering nil)
8 (original-point (point)))
9 (save-mark-and-excursion
10 (let ((new nil))
11 (org-map-entries
12 (lambda ()
13 (when (org-at-heading-p)
14 (let* ((level (nth 1 (org-heading-components)))
15 (numbering (org-num--current-numbering level nil)))
16 (let* ((current-subtree (save-excursion (org-element-at-point)))
17 (point-in-subtree
18 (<= (org-element-property :begin current-subtree)
19 original-point
20 (1- (org-element-property :end current-subtree)))))
21 ;; Get numbering to current headline if the cursor is in it.
22 (when point-in-subtree (push numbering
23 new))))))
24 "-noexport")
25 ;; New contains all the trees that contain the cursor (i.e. the
26 ;; innermost and all its parents), so we only return the innermost one.
27 ;; We reverse its order to make it more readable.
28 (reverse (car new))))))
The zz/refresh-reveal-prez function makes use of the above to perform the presentation export, refresh and update. You can use it by adding an after-save hook like this (add at the end of the file):
1 * Local variables :ARCHIVE:noexport:
2 # Local variables:
3 # eval: (add-hook! after-save :append :local (zz/refresh-reveal-prez))
4 # end:
Note #1: This is specific to my OS (macOS) and the browser I use (Brave). I will make it more generic in the future, but for now feel free to change it to your needs.
Note #2: the presentation must be already open in the browser, so you must run “Export to reveal.js -> To file and browse” (C-c C-e v b) once by hand.
1 (defun zz/refresh-reveal-prez ()
2 ;; Export the file
3 (org-re-reveal-export-to-html)
4 (let* ((slide-list (zz/org-current-headline-number))
5 (slide-str (string-join (mapcar #'number-to-string slide-list) "-"))
6 ;; Determine the filename to use
7 (file (concat (file-name-directory (buffer-file-name))
8 (org-export-output-file-name ".html" nil)))
9 ;; Final URL including the slide number
10 (uri (concat "file://" file "#/slide-" slide-str))
11 ;; Get the document title
12 (title (cadar (org-collect-keywords '("TITLE"))))
13 ;; Command to reload the browser and move to the correct slide
14 (cmd (concat
15 "osascript -e \"tell application \\\"Brave\\\" to repeat with W in windows
16 set i to 0
17 repeat with T in (tabs in W)
18 set i to i + 1
19 if title of T is \\\"" title "\\\" then
20 reload T
21 delay 0.1
22 set URL of T to \\\"" uri "\\\"
23 set (active tab index of W) to i
24 end if
25 end repeat
26 end repeat\"")))
27 ;; Short sleep seems necessary for the file changes to be noticed
28 (sleep-for 0.2)
29 (call-process-shell-command cmd)))
Other exporters
ox-jira to export in Jira markup format.
1 (package! ox-jira)
1 (use-package! ox-jira
2 :after org)
org-jira for full Jira integration - manage issues from Org mode.
1 (package! org-jira)
1 (make-directory "~/.org-jira" 'ignore-if-exists)
2 (setq jiralib-url "https://jira.example.com/")
org-special-block-extras to enable additional special block types and their corresponding exports (disabled for now).
1 (package! org-special-block-extras)
1 (use-package! org-special-block-extras
2 :after org
3 :hook (org-mode . org-special-block-extras-mode))
Other Org stuff
Programming Org
Trying out org-ml for easier access to Org objects.
1 (package! org-ml)
1 (use-package! org-ml
2 :after org)
I’m also testing org-ql for structured queries on Org documents.
1 (package! org-ql)
1 (use-package! org-ql
2 :after org)
This function returns a list of all the headings in the given file which have the given tags.
1 (defun zz/headings-with-tags (file tags)
2 (string-join
3 (org-ql-select file
4 `(tags-local ,@tags)
5 :action '(let ((title (org-get-heading 'no-tags 'no-todo)))
6 (concat "- "
7 (org-link-make-string
8 (format "file:%s::*%s" file title)
9 title))))
10 "\n"))
This function returns a list of all the headings in the given file which match the tags of the current heading.
1 (defun zz/headings-with-current-tags (file)
2 (let ((tags (s-split ":" (cl-sixth (org-heading-components)) t)))
3 (zz/headings-with-tags file tags)))
Coding
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 while I did this by manually adding org-babel-tangle to the after-save hook in Org mode, but now I use the org-auto-tangle package, which does this asynchronously and selectively for each Org file where it is desired.
1 (package! org-auto-tangle)
1 (use-package! org-auto-tangle
2 :defer t
3 :hook (org-mode . org-auto-tangle-mode)
4 :config
5 (setq org-auto-tangle-default t))
Some useful settings for LISP coding - smartparens-strict-mode to enforce parenthesis to match. 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.
1 (defun zz/sp-enclose-next-sexp (num)
2 (interactive "p")
3 (insert-parentheses (or num 1)))
4
5 (after! smartparens
6 (add-hook! (clojure-mode
7 emacs-lisp-mode
8 lisp-mode
9 cider-repl-mode
10 racket-mode
11 racket-repl-mode) :append #'smartparens-strict-mode)
12 (add-hook! smartparens-mode :append #'sp-use-paredit-bindings)
13 (map! :map (smartparens-mode-map smartparens-strict-mode-map)
14 "M-(" #'zz/sp-enclose-next-sexp))
Adding keybindings for some useful functions:
-
find-function-at-pointgets bound toC-c l g p(grouped together with other “go to” functions bound by Doom) and toC-c C-f(analog to the existingC-c f) for faster access.1 (after! prog-mode 2 (map! :map prog-mode-map "C-h C-f" #'find-function-at-point) 3 (map! :map prog-mode-map 4 :localleader 5 :desc "Find function at point" 6 "g p" #'find-function-at-point))
Some other languages I use.
-
Elvish shell, with support for org-babel.
Figure 27. →packages.el 1 (package! elvish-mode) 2 (package! ob-elvish)
-
Fish shell.
Figure 28. →packages.el 1 (package! fish-mode)
-
CFEngine policy files. The
cfengine3-modepackage is included with Emacs, but I also install org-babel support.Figure 29. →packages.el 1 (package! ob-cfengine3)
1 (use-package! cfengine 2 :defer t 3 :commands cfengine3-mode 4 :mode ("\\.cf\\'" . cfengine3-mode))
-
Graphviz for graph generation.
Figure 30. →packages.el 1 ;(package! graphviz-dot-mode)
1 ;(use-package! graphviz-dot-mode)
-
I am learning Common LISP, which is well supported through the
common-lispDoom module, but I need to configure this in the~/.slynkrcfile for I/O in the Sly REPL to work fine (source).Figure 31. →~/.slynkrc 1 (setf slynk:*use-dedicated-output-stream* nil)
-
package-lint for checking MELPA packages.
Figure 32. →packages.el 1 (package! package-lint)
-
Playing with Zig:
Figure 33. →packages.el 1 (package! zig-mode)
Other tools
Miscellaneous packages
-
Figure 34. →packages.el 1 (package! typst-ts-mode :recipe (:host codeberg :repo "meow_king/typst-ts-mode"))
-
Figure 35. →packages.el 1 (package! dockerfile-mode)
1 (add-to-list 'auto-mode-alist '("Dockerfile\\'" . dockerfile-mode)) 2 (put 'dockerfile-image-name 'safe-local-variable #'stringp)
This prevents the docker command from producing ANSI sequences during the image build process, which results in a more readable output in the compilation buffer. From https://emacs.stackexchange.com/a/55340/11843:
1 (defun plain-pipe-for-process () (setq-local process-connection-type nil))
2 (add-hook 'compilation-mode-hook 'plain-pipe-for-process)
-
Use Emacs Everywhere!
Figure 36. →packages.el 1 (package! emacs-everywhere :pin nil)
1 (use-package! emacs-everywhere 2 :config 3 (setq emacs-everywhere-major-mode-function #'org-mode))
-
Trying out Magit’s multi-repository abilities. This stays in sync with the git repo list used by my chain:summary-status Elvish shell function by reading the file every time
magit-list-repositoriesis called, usingdefadvice!. I also customize the display to add theStatuscolumn.1 (after! magit 2 (setq zz/repolist 3 "~/.elvish/package-data/elvish-themes/chain-summary-repos.json") 4 (defadvice! +zz/load-magit-repositories () 5 :before #'magit-list-repositories 6 (setq magit-repository-directories 7 (seq-map (lambda (e) (cons e 0)) (json-read-file zz/repolist)))) 8 (setq magit-repolist-columns 9 '(("Name" 25 magit-repolist-column-ident nil) 10 ("Status" 7 magit-repolist-column-flag nil) 11 ("B<U" 3 magit-repolist-column-unpulled-from-upstream 12 ((:right-align t) 13 (:help-echo "Upstream changes not in branch"))) 14 ("B>U" 3 magit-repolist-column-unpushed-to-upstream 15 ((:right-align t) 16 (:help-echo "Local changes not in upstream"))) 17 ("Path" 99 magit-repolist-column-path nil))))
-
I prefer to use the GPG graphical PIN entry utility. This is achieved by setting
epg-pinentry-mode(epa-pinentry-modebefore Emacs 27) tonilinstead of the default'loopback.1 (after! epa 2 (set 'epg-pinentry-mode nil) 3 (setq epa-file-encrypt-to '("diego@zzamboni.org")))
-
I find
ieditabsolutely indispensable when coding. In short: when you hitCtrl-;, 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.Figure 37. →packages.el 1 (package! iedit)
1 (use-package! iedit 2 :defer 3 :config 4 (set-face-background 'iedit-occurrence "Magenta") 5 :bind 6 ("C-;" . iedit-mode))
-
A useful macro (sometimes) for timing the execution of things. From StackOverflow.
1 (defmacro zz/measure-time (&rest body) 2 "Measure the time it takes to evaluate BODY." 3 `(let ((time (current-time))) 4 ,@body 5 (float-time (time-since time))))
-
I’m still not fully convinced of running a terminal inside Emacs, but
vtermis much nicer than any of the previous terminal emulators, so I’m giving it a try. I configure it so that it runs my favorite shell. Vterm runs Elvish flawlessly!1 (setq vterm-shell "/usr/local/bin/elvish")
-
Add “unfill” commands to parallel the “fill” ones, bind
A-qtounfill-paragraphand rebindM-qto theunfill-togglecommand, which fills/unfills paragraphs alternatively.Figure 38. →packages.el 1 (package! unfill)
1 (use-package! unfill 2 :defer t 3 :bind 4 ("M-q" . unfill-toggle) 5 ("A-q" . unfill-paragraph))
-
The annotate package is nice - allows adding annotations to files without modifying the file itself.
Figure 39. →packages.el 1 (package! annotate)
-
gift-mode for editing quizzes in GIFT format.
Figure 40. →packages.el 1 (package! gift-mode)
-
Figure 41. →packages.el 1 (package! just-mode)
-
chezmoi.el for interacting with chezmoi.
Figure 42. →packages.el 1 (package! chezmoi)
1 (use-package! chezmoi 2 :config 3 (load-file "~/.emacs.d/.local/straight/repos/chezmoi.el/extensions/chezmoi-magit.el"))
Posting to 750words.com
I use 750words.com for recording some writing every day (1464-day streak as of this writing!). I wrote 750words-client to allow posting my words from the command line, and the code below integrates this into Emacs, so I can post text directly from the current buffer.
1 (package! 750words
2 :recipe (:host github
3 :repo "zzamboni/750words-client"
4 :files ("*.el")))
5 ;;(package! 750words
6 ;; :recipe (:local-repo ;;"~/Dropbox/Personal/devel/750words-client"))
1 (use-package! 750words :defer t)
2 (use-package! ox-750words :defer t)
Experiments
Some experimental code to list functions which are not native-compiled. Sort of works but its very slow. This does not get tangled to my config.el, I just keep it here for reference.
1 (with-current-buffer (get-buffer-create "*Non-native functions*")
2 (mapatoms
3 (lambda (s)
4 (when (and (functionp s)
5 (not (helpful--native-compiled-p s))
6 (not (helpful--primitive-p s t)))
7 (insert (symbol-name s))
8 (insert " --- ")
9 (insert (or (cdr (find-function-library s)) "<no file>"))
10 (insert "\n"))
11 ))
12 )
Make ox-md export src blocks with backticks and the language name.
1 (defun org-md-example-block (example-block _contents info)
2 "Transcode EXAMPLE-BLOCK element into Markdown format.
3 CONTENTS is nil. INFO is a plist used as a communication
4 channel."
5 (let ((lang (or (org-element-property :language example-block) "")))
6 (format "```%s\n%s```\n"
7 lang
8 (org-remove-indentation
9 (org-export-format-code-default example-block info)))))

