My Emacs config

Below is my emacs configuration generated from its source repo. This version is generated from commit .

Preface

This repo contains my emacs configuration in a literate org-mode file config.org. This config is based on Doom emacs.

Installation

This repo should be cloned to ~/.doom.d and Doom should be cloned to ~/.emacs.d. config.org needs to be tangled before doom installation so that init.el is produced and Doom knows what modules to use. This can be done with Doom’s included org-tangle executable, or done manually in emacs org-mode with org-babel-tangle from config.org. After this is done, Dooms usual doom [install,sync,upgrade] should work as expected.

Encrypted files

I’ve included some encrypted files for my benefit which contain personal information that I don’t want public. These are included in the repo for my convenience

License

This configuration, and any code within is licensed under GPLv3

On the “Literateness” of this document

This document tangles to multiple files, namely config.el, packages.el, and init.el, and it frequently uses noweb to insert code blocks into other code blocks. When exported these details aren’t visible. This means that the exported version should not be used as the sole source of truth, and you will need to reference the actual config.org document for full picture.

Doom modules

All my doom modules appear in the following sections

1
2
3
(doom!
 <<doom-modules>>
 )

Completion

I’m using company as my inline completion framework, and helm as my navigation/completion/UI completion framework

1
2
3
:completion
(company +childframe)
(ivy +prescient +icons)

UI

I enable various UI improvements and such here

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
:ui
doom
doom-dashboard
modeline
nav-flash
ophints
(popup +defaults)
(treemacs +lsp)
vc-gutter
vi-tilde-fringe
window-select

Editor

I use evil keybindings everywhere. I also want to enable snippets and code formatting

1
2
3
4
5
:editor
(evil +everywhere)
format
snippets
fold

Disable doom snippets (I use snippets for LSP completion snippets, not weird opinionated pregenerated ones)

1
(package! doom-snippets :ignore t)

Emacs

Improve dired, version-control, and parenthesis behaviour

1
2
3
4
5
:emacs
(dired +icons)
electric
vc
undo

Terminal

vterm is the best term

1
2
:term
vterm

Checkers

Enable syntax and spell checkers

1
2
3
:checkers
syntax
(spell +aspell +everywhere)

Tools

Set up a bunch of extra functionality in emacs

1
2
3
4
5
:tools
docker                                  ; docker
lookup                                  ; lookup of definitions/docs
(lsp +peek)                             ; enable language server
magit                                   ; git wizardry

Languages

Enable lots of programming language integrations

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
:lang
(cc +lsp)
emacs-lisp
data
json
latex
markdown
web
(org +hugo)
(python +lsp)
(julia +lsp)
(sh +fish)
(yaml +lsp)
(javascript +lsp)

Config

Enable bindings and also tell Doom that I’m using a literate config

1
2
3
:config
literate
(default +bindings +smartparens)

Personalisation

Setup name and email

1
2
(setq user-full-name "Tim Quelch"
      user-mail-address "tim@tquelch.com")

Load my secrets (API keys, email configs etc.)

1
(defvar tq/secrets-loaded (load (concat doom-private-dir "my-secrets") t))

Setup org-crypt to use my key to encrypt-decrypt

1
2
(after! org-crypt
  (setq org-crypt-key "07CFA8E6B5CA3E4243916E42CAE8E8818C4B8B84"))

The following ensure that the PATH variable is maintained on remote connections. This is important for me because the unimelb HPC (and others) paths are define by module load in the .bash_profile. This ensures that the explicitly loaded versions of git and other tools are used.

1
2
(after! tramp
  (add-to-list 'tramp-remote-path 'tramp-own-remote-path))

UI

Use some nice fonts

1
2
(setq doom-font (font-spec :family "Iosevka" :size 18)
      doom-variable-pitch-font (font-spec :family "DejaVu Sans"))

Set the theme

1
2
3
(setq doom-one-brighter-comments t
      doom-one-comment-bg nil
      doom-theme 'doom-one)

Display line numbers

1
(setq display-line-numbers-type t)

Set up fill-column to be wider by default

1
(setq-default fill-column 100)

Increase the amount of context lines when scrolling full screen-fulls (default is 2)

1
(setq next-screen-context-lines 8)

Ensure dired-omit-mode is not started with dired. It hides some files transparently and has caused lots of confusion on my part.

1
2
(after! dired
  (remove-hook 'dired-mode-hook 'dired-omit-mode))

Set the dictionary to use en_AU

1
2
(after! ispell
  (setq ispell-dictionary "en_AU"))

By default bookmarked lines are highlighted in an annoying orange background which often removes other formatting. This disables that.

1
2
(after! bookmark
  (setq bookmark-fontify nil))

Change dashboard to remove header and footer (don’t need DOOM in my editor)

1
2
(setq +doom-dashboard-functions '(doom-dashboard-widget-shortmenu
				  doom-dashboard-widget-loaded))

Helm

I now use Ivy as my main search/completion engine, but I still use helm for some things (notmuch, bibtex) so I need to do some setup here.

Use heading of helm as input line.

1
2
3
(after! helm
  (setq helm-echo-input-in-header-line t)
  (add-hook 'helm-minibuffer-set-up-hook 'helm-hide-minibuffer-maybe))

Company

Reduce prefix length and delay. I want completion fast. THis may cause performance issues

1
2
3
(after! company
  (setq company-idle-delay 0.1
	company-minimum-prefix-length 1))

Setup the default backends. By default doom includes company-dabbrev which adds too much noise. Yasnippet backend is also annoying and not included

1
(set-company-backend! '(text-mode prog-mode conf-mode) 'company-capf)

Editing

Enable the use of C-u as the universal argument again

1
2
3
(after! evil
  (setq! evil-want-C-u-delete nil
	 evil-want-C-u-scroll nil))

Instead map C-s to scroll up.

1
2
(global-unset-key (kbd "C-s"))
(map! :m "C-s" #'evil-scroll-up)

Unbind evil jumping keys. I don’t use these and I’ve found that they interfere with other uses of TAB (for example, in notmuch modes)

1
2
(map! :m [tab] nil
      :m [C-i] nil)

Enable easy use of avy

1
(map! "C-'" #'avy-goto-char-timer)

Use better comment-diwm

1
(package! comment-dwim-2 :pin "7cdafd6d98234a7402865b8abdae54a2f2551c94")
1
2
3
(use-package! comment-dwim-2
  :bind ([remap comment-dwim] . comment-dwim-2)
  :config (setq cd2/region-command 'cd2/comment-or-uncomment-region))

Disable some extra packages that I don’t really use

1
(disable-packages! evil-snipe evil-lion)

Org and friends

Base

1
(setq org-directory "~/documents/org/")

Set the org-agenda files to be the org directory. This includes all the files in the base directory, but no sub-directories.

1
2
(defvar org-agenda-files nil)
(add-to-list 'org-agenda-files org-directory)
1
2
3
(after! org
  <<org-configuration>>
  )

Setting up TODO states. I’m trying not to use the EMAIL state, but keeping it here for archive purposes.

1
2
3
4
(setq org-todo-keywords '((sequence "TODO(t)" "NEXT(n)" "WAITING(w)" "ONHOLD(h)" "|" "DONE(d)")
			  (sequence "EMAIL(e)" "|" "SENT(s)")
			  (sequence "|" "CANCELLED(c)")
			  (sequence "|" "MOVED(m)")))

Ensure that sub-tasks must be completed before the parent task can be marked done

1
(setq org-enforce-todo-dependencies t)

Log the time when tasks are completed

1
(setq org-log-done 'time)

Use the outline path as the refile target. This can be completed in steps to work well with helm etc.

1
2
(setq org-refile-use-outline-path t)
(setq org-outline-path-complete-in-steps nil)

Don’t log when changing state with shift-arrows

1
(setq org-treat-S-cursor-todo-selection-as-state-change nil)

Log state changes into drawers rather than under the items itself. This is also important for habits

1
(setq org-log-into-drawer t)

Pressing return over links will follow the link

1
(setq org-return-follows-link t)

Archive to subdirectory and use datetree

1
2
(after! org-archive
  (setq org-archive-location "archive/%s_archive::datetree/"))

Highlight \LaTeX within org

1
(setq org-highlight-latex-and-related '(native script entities))

No longer start with latex or inline images. This is often quite slow.

1
2
(setq org-startup-with-latex-preview nil
      org-startup-with-inline-images nil)

Enable the use of org-ids for links to headlines. org-id-track-globally is on by default in doom, however this only updates the org id file when emacs exits, so I’m not sure if it will work very well for me using a daemoned emacs.

1
2
3
4
(use-package! org-id
  :after org
  :config
  (setq org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id))

Remove empty clock lines, they achieve nothing.

1
2
(after! org-clock
  (setq org-clock-out-remove-zero-time-clocks t))

Turn on auto-revert mode in org mode files so that they automatically update when changed (e.g. by syncthing, dropbox etc.). Doom does not do this automatically, instead only auto-reverting the current buffers, which is fine for most cases except background buffers used for agendas and capture.

1
(add-hook 'org-mode-hook 'auto-revert-mode)

Only use company-capf for org mode. Again: I hate dabbrev

1
(set-company-backend! 'org-mode 'company-capf)

Unmap keybind that I use for avy

1
(map! :map org-mode-map "C-'" nil)

Editing around links is a real pain. Often you are typing thinking you are outsid ethe link but it ends up adding to the description. Below are some simple functions to quickly exit the link

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
(defun tq/org-exit-link-forward ()
  "Jump just outside a link forward"
  (interactive)
  (when (org-in-regexp org-link-any-re)
    (goto-char (match-end 0))
    (insert " ")))

(defun tq/org-exit-link-backward ()
  "Jump just outside a link backward"
  (interactive)
  (when (org-in-regexp org-link-any-re)
    (goto-char (match-beginning 0))
    (save-excursion (insert " "))))

(map! :map (evil-org-mode-map org-mode-map)
      :ni "C-k" #'tq/org-exit-link-forward
      :ni "C-j" #'tq/org-exit-link-backward)

Add simple keybinding to toggle latex fragments in org mode

1
2
(map! :map evil-org-mode-map
      :n "zf" #'org-toggle-latex-fragment)

Disable some of the extra things that Doom enables

1
(disable-packages! org-superstar)

Capture

Bind capture to something more convenient

1
(map! :leader "j" #'org-capture)

Configure my capture templates. These need to go in this advice because doom loads these on a hook.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
(defadvice! tq/setup-capture-templates ()
  :after #'+org-init-capture-defaults-h
  (setq org-default-notes-file (expand-file-name "inbox.org" org-directory))

  (setq org-capture-templates
	`(("t" "todo" entry (file org-default-notes-file)
	   "* TODO %?")
	  ("a" "appointment" entry (file org-default-notes-file)
	   "* %?")
	  ("j" "journal" plain (file+olp+datetree ,(concat org-directory "journal.org"))
	   (file ,(concat org-directory "templates/journal.org"))
	   :immediate-finish t :jump-to-captured t :tree-type 'week)
	  ("w" "workout" plain
	   (file+olp+datetree ,(concat org-directory "exercise.org") "Workouts")
	   (file ,(concat org-directory "templates/workout.org"))
	   :immediate-finish t :jump-to-captured t :tree-type 'week))))

Referencing

Define my default bibliography file (generated and maintained by Zotero/BBL)

1
(defvar tq/bibliography-file "~/documents/library.bib")

I’m using org-ref to manage citations within org-mode. This might soon be replaced by native citation support though :o

1
(package! org-ref :pin "6a759a969d92dd1c69f540129ebaa8e47ef70cf3")
1
2
3
4
5
6
7
(use-package! org-ref
  :after org
  :defer-incrementally t
  :init
  (setq! org-ref-default-bibliography (list tq/bibliography-file)
	 org-ref-default-citation-link "autocite"
	 org-ref-get-pdf-filename-function (lambda (key) (car (bibtex-completion-find-pdf key)))))

For non-LaTeX exports, I use citeproc to format citations

1
(package! citeproc-org :pin "20cd7e817420a3f6e7b82faea901a3c67c6d4d9f")
1
2
3
4
5
(use-package! citeproc-org
  :after ox
  :config
  (citeproc-org-setup)
  (setq citeproc-org-org-bib-header "* References\n"))

By default citeproc-org-org-bib-header will insert a level 1 heading. This is not desirable if the minimum headline level in the exported document is not level 1. The following advice determine what the minimum headline level is in the exported document, and adjusts citeproc-org-org-bib-header to be the correct level.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
(after! citeproc-org
  (defun tq/min-headline-level ()
    (--> (org-element-parse-buffer)
	 (org-element-map it 'headline (apply-partially #'org-element-property :level))
	 (or it '(0))
	 (-min it)))

  (defadvice! tq/citeproc-org-render-references (orig &rest args)
    :around 'citeproc-org-render-references
    (let* ((minlevel (tq/min-headline-level))
	   (totallevel (max 1 minlevel))
	   (citeproc-org-org-bib-header (concat (make-string totallevel ?*)
						(string-trim-left citeproc-org-org-bib-header "\\**"))))
      (apply orig args))))

Use helm-bibtex as the main way of dealing with bibliographies

1
(package! helm-bibtex :pin "ce8c17690ddad73d01531084b282f221f8eb6669")
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
(use-package! helm-bibtex
  :after org-ref
  :config
  (setq! bibtex-completion-pdf-field "file"
	 bibtex-completion-pdf-open-function #'helm-open-file-with-default-tool
	 bibtex-completion-bibliography tq/bibliography-file
	 helm-bibtex-full-frame nil)

  (setq! bibtex-completion-display-formats
	 '((t . "${author:36} ${title:*} ${year:4} ${=has-pdf=:1}${=has-note=:1} ${=type=:20}")))

  (defadvice! tq/helm-bibtex-window-width ()
    "Override the window width getter to manually reduce the width"
    :override
    #'helm-bibtex-window-width
    (- (window-body-width) 8))

  (map! :leader :prefix "s"
	"c" #'helm-bibtex))

Exporting

1
2
3
4
(use-package ox-extra
  :after org
  :config
  (ox-extras-activate '(ignore-headlines)))
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
(use-package ox-latex
  :after org
  :config
  (add-to-list 'org-latex-classes '("a4article"
				    "\\documentclass[11pt,a4paper]{article}"
				    ("\\section{%s}" . "\\section*{%s}")
				    ("\\subsection{%s}" . "\\subsection*{%s}")
				    ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
				    ("\\paragraph{%s}" . "\\paragraph*{%s}")
				    ("\\subparagraph{%s}" . "\\subparagraph*{%s}")))
  (setq org-latex-default-class "a4article")
  (setq org-latex-packages-alist '(("titletoc, title" "appendix" nil) ; Setup appendices
				   ("margin=25mm" "geometry")         ; Setup margins
				   ("" "tocbibind" nil)  ; Put bibliography in TOC
				   ("" "pdflscape" nil)  ; Allow landscape pages
				   ("" "pdfpages" nil)   ; Allow inclusion of pdfs
				   ("" "svg" nil)        ; Allow SVG images (req. inkscape?)
				   ("" "subcaption" nil) ; Allow subcaptions
				   ("" "listings" nil)   ; Source code listings
				   ("" "color" nil)      ; Color in source code listings
				   ("binary-units" "siunitx" t)))     ; SI units

  (setq org-latex-pdf-process (list "latexmk -shell-escape -bibtex -f -pdf %f"))

  (setq org-latex-listings t)                                         ; Turn on source code inclusion
  (setq org-latex-listings-options '(("basicstyle" "\\linespread{0.85}\\ttfamily")
				     ("numbers" "left")
				     ("numberstyle" "\\tiny")
				     ("frame" "tb")
				     ("tabsize" "4")
				     ("columns" "fixed")
				     ("showstringspaces" "false")
				     ("showtabs" "false")
				     ("keepspaces" "true")
				     ("commentstyle" "\\color{red}")
				     ("keywordstyle" "\\color{blue}")
				     ("breaklines" "true"))))

Ensure that attachment links are properly expanded before export

1
2
(after! org-attach
  (add-hook 'org-export-before-parsing-hook #'org-attach-expand-links))

Notetaking

Roam

Setup org-roam, org-roam-bibtex, and org-roam-ui to track source

1
2
3
4
5
6
7
8
9
(package! org-roam
  :recipe (:host github :repo "org-roam/org-roam")
  :pin "69116a4da49448e79ac03aedceeecd9f5ae9b2d4")
(package! org-roam-bibtex
  :recipe (:host github :repo "org-roam/org-roam-bibtex")
  :pin "efdac6fe4134c33f50b06a0a6d192003d0e5094c")
(package! org-roam-ui
  :recipe (:host github :repo "org-roam/org-roam-ui" :files ("*.el" "out"))
  :pin "9474a254390b1e42488a1801fed5826b32a8030b")

I want to roll my own org-roam config rather than use doom’s module.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
(use-package! org-roam
  :commands (org-roam-node-find
	     org-roam-node-insert
	     org-roam-show-graph
	     org-roam-buffer-toggle
	     org-roam-dailies-goto-date
	     org-roam-dailies-goto-today
	     org-roam-dailies-goto-tomorrow
	     org-roam-dailies-goto-yesterday)
  :init
  <<org-roam-init>>
  :config
  <<org-roam-config>>
  )

Acknowledge that I have upgraded to v2 to squash warning

1
(setq org-roam-v2-ack t)

Set up useful keybindings to use and access org-roam

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
(map! :after org
      :leader
      :prefix "n"
      "f" #'org-roam-node-find
      "i" #'org-roam-node-insert
      "g" #'org-roam-show-graph
      "r" #'org-roam-buffer-toggle
      "n" #'org-roam-dailies-capture-today
      "t" #'org-roam-dailies-goto-today
      "d" nil
      (:prefix ("d" . "by date")
	 :desc "Goto previous note" "b" #'org-roam-dailies-goto-previous-note
	 :desc "Goto date"          "d" #'org-roam-dailies-goto-date
	 :desc "Goto next note"     "f" #'org-roam-dailies-goto-next-note
	 :desc "Goto tomorrow"      "m" #'org-roam-dailies-goto-tomorrow
	 :desc "Capture today"      "n" #'org-roam-dailies-capture-today
	 :desc "Goto today"         "t" #'org-roam-dailies-goto-today
	 :desc "Capture Date"       "v" #'org-roam-dailies-capture-date
	 :desc "Goto yesterday"     "y" #'org-roam-dailies-goto-yesterday
	 :desc "Goto directory"     "." #'org-roam-dailies-find-directory)
      "m" nil
      (:prefix ("m" . "metadata")
       "t" #'org-roam-tag-add
       "T" #'org-roam-tag-delete
       "a" #'org-roam-alias-add
       "A" #'org-roam-alias-delete))

Set directory for my org-roam notes

1
(setq org-roam-directory (concat (file-name-as-directory org-directory) "notes/"))

Put the database in the doom cache directory, rather than stored with the notes

1
(setq org-roam-db-location (concat doom-cache-dir "org-roam.db"))

Setup org-roam

1
(org-roam-setup)

Set up org roam buffer sections

1
2
3
(setq org-roam-mode-sections (list #'org-roam-backlinks-insert-section
				   #'org-roam-reflinks-insert-section
				   #'org-roam-unlinked-references-insert-section))

Set up roam buffer to be a side window

1
2
3
4
5
6
(add-to-list 'display-buffer-alist
	     '(("\\*org-roam\\*"
		(display-buffer-in-direction)
		(direction . right)
		(window-width . 0.33)
		(window-height . fit-window-to-buffer))))

Turn off verbosity. I don’t like the messages

1
(setq org-roam-verbose nil)

Set up capture template. It includes a TODO item to write about the note. I have it set to finish immediately, as I don’t really like editing them instantly.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
(setq org-roam-capture-templates
      `(("d" "default" plain "%?"
	 :if-new
	 (file+head
	  "${slug}.org"
	  "#+title: ${title}\n#+setupfile: setup.org\n\n\n\n* TODO Write about '${title}' :towrite:")
	 :unnarrowed t
	 :immediate-finish t)
	("e" "empty" plain "%?"
	 :if-new
	 (file+head
	  "${slug}.org"
	  "#+title: ${title}")
	 :unnarrowed t
	 :immediate-finish t)
	("r" "ref" plain "%?"
	 :if-new
	 (file+head ,(concat (file-name-as-directory "lit") "${citekey}.org")
		    "#+title: Notes on: ${title}\n#+setupfile: ../setup.org\n\n")
	 :unnarrowed t
	 :immediate-finish t)))

Ensure tags come from both the directory and the roam_tag file property. The default is just the property

1
(setq org-roam-tag-sources '(prop all-directories))

Exclude daily notes from the graph

1
(setq org-roam-graph-exclude-matcher '("daily/"))

Update the database immediately on file changes. The alternative is to do it on an idle timer, but I’ve found that to be buggy and I haven’t noticed the immediate updates to be very noticeable.

1
(setq org-roam-db-update-method 'immediate)

Set up an agenda view for nearby notes

1
2
3
4
5
6
7
8
(defun tq/org-agenda-nearby-notes (&optional distance)
  (interactive "P")
  (let ((org-agenda-files (org-roam-db--links-with-max-distance
			   buffer-file-name (or distance 3)))
	(org-agenda-custom-commands '(("e" "" ((alltodo ""))))))
    (org-agenda nil "e")))

(map! :leader :prefix "n" :desc "Agenda nearby" "a" #'tq/org-agenda-nearby-notes)

Set up a graph view where citation links are excluded

1
2
3
4
5
6
(defun tq/org-roam-graph-without-cites (&optional arg)
  (interactive "P")
  (let ((org-roam-graph-exclude-matcher (cons "lit/" org-roam-graph-exclude-matcher)))
    (org-roam-graph-show arg)))

(map! :leader :prefix "n" "G" #'tq/org-roam-graph-without-cites)

Setup case-insensitive completion in org-roam files

1
2
(add-hook! 'org-roam-file-setup-hook
  (setq-local completion-ignore-case t))

Also set up completion to trigger everywhere, not just on link start. Disable completion anywhere, it isn’t working as I would like right now.

1
(setq org-roam-completion-everywhere nil)

Rename files when title is changed

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
(add-hook! 'after-save-hook
	   (defun org-rename-to-new-title ()
	     (when-let*
		 ((old-file (buffer-file-name))
		  (is-roam-file (org-roam-file-p old-file))
		  (in-roam-base-directory? (string-equal
					    (expand-file-name org-roam-directory)
					    (file-name-directory old-file)))
		  (file-node (save-excursion
			       (goto-char 1)
			       (org-roam-node-at-point)))
		  (slug (org-roam-node-slug file-node))
		  (new-file (expand-file-name (concat slug ".org")))
		  (different-name? (not (string-equal old-file new-file))))
	       (rename-buffer new-file)
	       (rename-file old-file new-file)
	       (set-visited-file-name new-file)
	       (set-buffer-modified-p nil))))

Set the dailies directory to be a subdirectory in my base org-roam directory

1
(setq org-roam-dailies-directory "daily/")

Set the capture template for my daily notes

1
2
3
4
5
6
7
8
(setq org-roam-dailies-capture-templates
      '(("d" "default" entry "* %?"
	 :if-new (file+head
		  "%<%Y-%m-%d>.org"
		  "#+title: %<%Y-%m-%d>\n")
	 :unnarrowed t
	 :immediate-finish t
	 :jump-to-captured t)))

I often want to refile TODO items from journal or other org files into my inbox. This function copies the headline into my inbox, and creates bi-directional links on both headlines. It also marks the original headlines as the MOVED todo keyword.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
(defun tq/refile-to-inbox ()
  (interactive)
  (let ((id (org-id-get-create)))
    (org-refile 3 nil (list org-default-notes-file org-default-notes-file nil nil))
    (org-edit-headline (concat "[[id:" id "][HERE]] " (nth 4 (org-heading-components))))
    (let ((new-id (org-id-get-create t)))
      (save-window-excursion
	(org-id-goto id)
	(org-set-property "ORIGIN" (concat "[[id:" new-id "]]")))))
  (let ((org-enforce-todo-dependencies nil))
   (org-map-entries (lambda () (org-todo "MOVED")) nil 'tree)))

(after! org
  (map! :map org-mode-map :localleader :prefix "r" "i" #'tq/refile-to-inbox))

Configure web ui

1
2
3
(use-package org-roam-ui
  :after org-roam
  :hook (after-init . org-roam-ui-mode))

BibTeX

Enable org-roam-bibtex and setup capture template

1
2
3
4
5
6
(use-package org-roam-bibtex
  :commands (org-roam-bibtex-insert-non-ref org-roam-bibtex-find-non-ref)
  :hook (org-mode . org-roam-bibtex-mode)
  :config
  <<orb-configuration>>
  )

Set up literature notes template

1
2
3
4
5
6
7
8
(setq orb-templates
      `(("r" "ref" plain
	 (function org-roam-capture--get-point)
	 ""
	 :file-name ,(concat (file-name-as-directory "lit") "${citekey}")
	 :head "#+title: Notes on: ${title}\n#+roam_key: ${ref}\n#+setupfile: ../setup.org\n\n"
	 :unnarrowed t
	 :immediate-finish t)))

Set up orb note actions. I remove some of the options that I don’t use or want here.

1
2
3
4
5
6
7
(setq orb-note-actions-frontend 'helm)
(setq orb-note-actions-default (--remove
				(eq (cdr it) #'bibtex-completion-add-pdf-to-library)
				orb-note-actions-default))
(setq orb-note-actions-extra (--remove
			      (eq (cdr it) #'orb-note-actions-scrap-pdf)
			      orb-note-actions-extra))

Add convenient keybinding for accessing note actions

1
2
(map! :leader :prefix "n"
      "b" #'orb-note-actions)

Email

I don’t like the inbuilt notmuch Doom module, so I’m effectively implementing it myself

1
(package! notmuch :pin "a9b5f8959a20bbce774dec8a65a8b207555e52bd")
1
2
3
4
5
6
7
8
(use-package! notmuch
  :defer t
  :commands (notmuch notmuch-mua-new-mail)
  :init
  <<notmuch-init>>
  :config
  <<notmuch-config>>
  )

Ensure that linking to notmuch emails is enabled in org

1
2
(after! org
  (add-to-list 'org-modules 'ol-notmuch))

Add a nice keymap for accessing email

1
2
3
4
5
6
7
8
9
(map! :leader
      (:prefix ("e" . "email")
       :desc "Browse"         "e" (cmd! (notmuch) (widget-forward 4))
       :desc "New email"      "n" #'notmuch-mua-new-mail
       :desc "Saved searches" "j" #'notmuch-jump-search
       :desc "Search"         "s" #'helm-notmuch))

(map! :map doom-leader-search-map
      :desc "Search emails" "e" #'helm-notmuch)

Ensure that notmuch buffers are treated as real buffers

1
2
3
4
5
6
(defun tq/notmuch-buffer-p (buffer)
  (or (string-match-p "^\\*notmuch" (buffer-name buffer))
      (with-current-buffer buffer
	(equal major-mode 'notmuch-show-mode))))

(add-to-list 'doom-real-buffer-functions #'tq/notmuch-buffer-p)

Hide the notmuch logo

1
(setq notmuch-show-logo nil)

Show headers by default

1
(setq notmuch-message-headers-visible t)

Kill message buffers when sent

1
(setq message-kill-buffer-on-exit t)

Send mail with sendmail

1
2
(setq message-send-mail-function 'message-send-mail-with-sendmail)
(setq send-mail-function 'sendmail-send-it)

Sort by new

1
(setq-default notmuch-search-oldest-first nil)

Fix width of columns in search results

1
2
3
4
5
6
(setq notmuch-search-result-format
      '(("date" . "%12s ")
	("count" . "%-7s ")
	("authors" . "%-30s ")
	("subject" . "%-72s ")
	("tags" . "(%s)")))

Make unread emails specially

1
2
(setq notmuch-tag-formats
      '(("unread" (propertize tag 'face 'notmuch-tag-unread))))

Set up the sections in the main hello window

1
2
3
4
5
(setq notmuch-hello-sections
      '(notmuch-hello-insert-header
	notmuch-hello-insert-saved-searches
	notmuch-hello-insert-alltags))
(setq notmuch-show-all-tags-list t)

Setup saved searches. I have a bunch of saved searches in my secret files. If for some reason they aren’t loaded I specify some sane defaults. I generally don’t use the unread search because it is irrelevant for me (and broken).

1
2
3
4
5
6
7
(setq notmuch-saved-searches
      (if tq/secrets-loaded
	  secret/notmuch-saved-searches
	'((:name "inbox"   :query "tag:inbox" :key "i")
	  (:name "sent"    :query "tag:sent"  :key "s")
	  (:name "drafts"  :query "tag:draft" :key "d")
	  (:name "all"     :query "*"         :key "a"))))

Ensure that send mail goes into the correct folder.

1
2
(setq notmuch-maildir-use-notmuch-insert nil)
(setq notmuch-fcc-dirs (when tq/secrets-loaded secret/notmuch-fcc-dirs))

Ensure that sent mail is sent from the correct address. i.e. the one in the header of the message

1
2
3
(setq mail-envelope-from 'header
      mail-specify-envelope-from 'header
      message-sendmail-envelope-from 'header)

I want to use helm to choose which email to send email from. The notmuch default uses ido which I do not like. I also want to prompt for a sender whenever I create an email from scratch

1
2
3
4
5
6
7
(defadvice! tq/notmuch-prompt-for-sender ()
  :override #'notmuch-mua-prompt-for-sender
  (let ((name (notmuch-user-name))
	(address (completing-read "From: " (notmuch-user-emails))))
    (message-make-from name address)))

(setq notmuch-always-prompt-for-sender t)

Change the viewer for HTML email to GNUS w3m. It seems to be the best, but idk

1
(setq mm-text-html-renderer 'gnus-w3m)

Allow capturing of email in notmuch

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
(defun tq/org-capture-email ()
  (interactive)
  (let ((org-capture-templates '(("e" "email"
				  entry (file org-default-notes-file)
				  "* TODO Reply: %a :email:"
				  :immediate-finish t))))
    (org-capture nil "e")))

(map! :map notmuch-show-mode-map
      :nv "C" #'tq/org-capture-email)

Setup company completion for notmuch

1
2
3
(set-company-backend!
  '(org-msg-edit-mode notmuch-message-mode)
  'notmuch-company)

Use helm-notmuch for searching email from helm.

1
(package! helm-notmuch :pin "97a01497e079a7b6505987e9feba6b603bbec288")
1
2
3
(use-package! helm-notmuch
  :commands helm-notmuch
  :after notmuch)

Disable visual-line-mode s from message modes

1
2
3
4
(after! message
  (add-hook! 'message-mode-hook
    (visual-line-mode -1)
    (visual-fill-column-mode -1)))

Set up some face configurations. The default message summary at the top of a message is in grey, which is very low contrast. Here I change it to yellow.

1
2
(custom-theme-set-faces! 'doom-one
  `(notmuch-message-summary-face :foreground ,(doom-color 'yellow)))

Languages

Some extra packages and languages that are not included by doom modules by default

Systemd unit files

1
(package! systemd :pin "b6ae63a236605b1c5e1069f7d3afe06ae32a7bae")
1
2
(use-package systemd
  :defer t)

Docker compose

1
(package! docker-compose-mode :pin "abaa4f3aeb5c62d7d16e186dd7d77f4e846e126a")
1
2
(use-package docker-compose-mode
  :defer t)

Python

Set up LSP to turn off some python warnings

1
2
(after! python
  (setq! lsp-pylsp-plugins-pydocstyle-ignore t))

Julia

According to the help for the julia module in doom (SPC h d m "julia", *Language Server) I need to manually install lsp-julia for some reason,

1
(package! lsp-julia :recipe (:host github :repo "non-jedi/lsp-julia"))
1
2
3
(after! lsp-julia
  (setq lsp-enable-folding t)
  (setq lsp-julia-default-environment "~/.julia/environments/v1.5"))

Use vterm for backend for repl,

1
2
(after! julia-repl
  (julia-repl-set-terminal-backend 'vterm))

Add mapping in org mode to execute julia code. This is really specific and not the best solution, but I find it is far more useful than executing the julia code in the session provided by ob-julia or by running it in the non-session environment. Issues with this include:

  • Replaces default org mapping (I don’t use it though)
  • Works on any code block, not just julia. I guess I could add a simple check to check if it is a julia block, but I haven’t made this mistake yet
  • C-c C-c still does the default behaviour. I need to actually remember that I have this new binding set up
1
2
3
4
5
6
7
(after! org
  (defun tq/send-block-to-julia-repl ()
      (interactive)
      (save-mark-and-excursion
	(org-babel-mark-block)
	(julia-repl-send-region-or-line)))
  (map! :map org-mode-map "C-c C-v C-c" #'tq/send-block-to-julia-repl))

Define a minor mode to enable sending to julia-repl

1
2
3
4
5
(define-minor-mode julia-repl-interaction-mode
  "Toggle keybinds to send lines to the julia-repl"
  :keymap (let ((map (make-sparse-keymap)))
	    (define-key map (kbd "C-s") #'julia-repl-send-region-or-line)
	    map))

MATLAB/Octave

1
2
(use-package octave-mode
  :mode "\\.m\\'")

Todoist

1
(package! todoist :pin "f6906be346073f082a6d1f9ae14932ec2bfd99f5")
1
2
3
4
(use-package todoist
  :commands (todoist--query)
  :config
  (setq! todoist-token secret/todoist-token))
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
(defun tq/todoist-new-task (content &optional due description)
 (todoist--query "POST" "/tasks"
		  (append (list (cons "content" content))
			  (when due
			    (list (cons "due_date" due)))
			  (when description
			    (list (cons "description" description))))))

(defun tq/org-task-to-todoist ()
  (interactive)
  (when (org-current-level)             ; Under a headline
    (let ((content (nth 4 (org-heading-components)))
	  (due (when-let ((time (org-get-scheduled-time (point))))
		 (format-time-string "%Y-%m-%d" time))))
      (message content)
      (message due)
      (tq/todoist-new-task content due))))