Skip to content

Latest commit

 

History

History
1516 lines (1303 loc) · 51.5 KB

File metadata and controls

1516 lines (1303 loc) · 51.5 KB

Preface

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

(doom!
 <<doom-modules>>
 )

Completion

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

:completion
(company +childframe)
(helm +fuzzy)

UI

I enable various UI improvements and such here

:ui
doom
modeline
nav-flash
ophints
(popup +defaults)
treemacs
vc-gutter
vi-tilde-fringe
window-select

Editor

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

:editor
(evil +everywhere)
format
snippets

Emacs

Improve dired, version-control, and parenthesis behaviour

:emacs
(dired +icons)
electric
vc
undo

Terminal

vterm is the best term

:term
vterm

Checkers

Enable syntax and spell checkers

:checkers
syntax
(spell +aspell +everywhere)

Tools

Set up a bunch of extra functionality in emacs

:tools
(eval +overlay)                         ; evaluation
docker                                  ; docker
lookup                                  ; lookup of definitions/docs
(lsp +peek)                             ; enable language server
(magit +forge)                          ; git wizardry
pdf                                     ; pdf viewing and annotation

Languages

Enable lots of programming language integrations

:lang
(cc +lsp)
emacs-lisp
data
json
latex
markdown
(org +present +hugo)
(python +pyenv)
(julia +lsp)
(sh +fish)

App

:app
calendar

Config

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

:config
literate
(default +bindings +smartparens)

Environment

By default doom blacklists SSH_AUTH_SOCK and SSH_AUTH_PID variables, which means ssh agents don’t work. This whitelists them in init.el

(when noninteractive
  (add-to-list 'doom-env-whitelist "^SSH_"))

Personalisation

Setup name and email

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

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

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

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

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

UI

Use some nice fonts

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

Set the theme

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

Display line numbers

(setq display-line-numbers-type t)

Set up fill-column to be wider by default

(setq-default fill-column 100)

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

(setq next-screen-context-lines 8)

Wrap lines at fill column when using visual-line-mode

(package! visual-fill-column :pin "68784162d758fbe6a91d04e9caa8f05683fb6ba9")
(use-package visual-fill-column
  :hook (visual-line-mode . visual-fill-column-mode)
  :config
  (setq-default split-window-preferred-function
                'visual-fill-column-split-window-sensibly))

Set up mixed-pitch-mode in text modes. Proportional fonts are much nicer to read, but I still want fixed pitch in code blocks

(package! mixed-pitch :pin "beb22e85f6073a930f7338a78bd186e3090abdd7")
(use-package mixed-pitch
  :hook (text-mode . mixed-pitch-mode)
  :config
  (pushnew! mixed-pitch-fixed-pitch-faces
            'org-date
            'org-special-keyword
            'org-property-value
            'org-drawer
            'org-ref-cite-face
            'org-tag
            'org-todo-keyword-todo
            'org-todo-keyword-habt
            'org-todo-keyword-done
            'org-todo-keyword-wait
            'org-todo-keyword-kill
            'org-todo-keyword-outd
            'org-todo
            'org-done
            'font-lock-comment-face
            'line-number
            'line-number-current-line))

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

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

Set the dictionary to use en_AU

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

Helm

Use heading of helm as input line

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

Increase size of helm buffers

(after! helm
  (set-popup-rule! "^\\*helm" :size 0.3))

Increase width of buffer name field in buffer list

(after! helm
  (setq helm-buffer-max-length nil))

Override Doom’s search buffer with swiper-helm. See this PR.

(defadvice! tq/default-search-buffer ()
  :override #'+default/search-buffer
  (interactive)
  (call-interactively
   (if (featurep! :completion helm)
       #'swiper-helm
     (if (region-active-p)
         #'swiper-isearch-thing-at-point
       #'swiper-isearch))))

Company

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

(after! company
  (setq company-idle-delay 0.3
        company-minimum-prefix-length 1))

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

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

Editing

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

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

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)

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

Enable easy use of avy

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

Use better comment-diwm

(package! comment-dwim-2 :pin "7cdafd6d98234a7402865b8abdae54a2f2551c94")
(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

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

Org and friends

Base

(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.

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

Setting up TODO states. WAITING and CANCELLED require messages when entering these states. I’m trying not to use the EMAIL state, but keeping it here for archive purposes.

(setq org-todo-keywords '((sequence "TODO(t)" "NEXT(n)" "WAITING(w)" "|" "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

(setq org-enforce-todo-dependencies t)

Log the time when tasks are completed

(setq org-log-done 'time)

Setup refile targets. Targets include the current file and all agenda files (files in the org directory) up to 9 levels deep in the hierarchy. Only in-progress tasks are allowed as refile targets

(setq org-refile-allow-creating-parent-nodes 'confirm)
(setq org-refile-targets '((nil :maxlevel . 9)
                           (org-agenda-files :maxlevel . 9)))

(defun tq/verify-refile-target ()
  "Exclude done todo states from refile targets"
  (not (member (nth 2 (org-heading-components)) org-done-keywords)))
(setq org-refile-target-verify-function 'tq/verify-refile-target)

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

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

Don’t log when changing state with shift-arrows

(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

(setq org-log-into-drawer t)

Pressing return over links will follow the link

(setq org-return-follows-link t)

Archive to subdirectory and use datetree

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

Highlight \LaTeX within org

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

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

(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.

(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.

(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.

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

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

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

Unmap keybind that I use for avy

(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

(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

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

Disable some of the extra things that Doom enables

(disable-packages! org-superstar)

Agenda

I use org-super-agenda to group tasks in agenda

(package! org-super-agenda :pin "857783ecd3dbe35c72b4eca046e0a5dc64041fdf")
(use-package! org-super-agenda
  :hook (org-agenda-mode . org-super-agenda-mode))

(after! (org-agenda org-super-agenda)
  (setq! org-super-agenda-header-map (make-sparse-keymap)))

Map agenda to a more convenient binding

(map! :leader "a" #'org-agenda)

Next I want to configure the agenda behaviour

(after! org-agenda
  <<org-agenda-configuration>>
  <<org-agenda-filters>>
  <<org-agenda-views>>
  )

When opening an item from the agenda, ensure the whole tree (parents and siblings) is visible

(add-hook 'org-agenda-after-show-hook 'org-reveal)

Don’t dim blocked tasks (i.e. projects)

(setq org-agenda-dim-blocked-tasks nil)

Remove the ‘category’ header from the agenda. Because I only really use one main agenda file, this was the same for all the values in the agenda. Removing it gives me some more horizontal space in the agenda view.

(setq org-agenda-prefix-format '((agenda . " %i %?-12t% s")
                                 (todo . " %i ")
                                 (tags . " %i ")
                                 (search . " %i ")))

I also remap movement keys to move between agenda items rather than between lines, as this is what you want to do the vast majority of the time.

(map! :map org-agenda-mode-map
      [remap org-agenda-next-line] #'org-agenda-next-item
      [remap org-agenda-previous-line] #'org-agenda-previous-item)

Set up some other useful bindings

(map! :map org-agenda-mode-map
      :m "w" #'org-save-all-org-buffers
      :m "f" #'org-agenda-follow-mode
      :m "o" #'org-agenda-log-mode)

Enable habits

(add-to-list 'org-modules 'org-habit)

Agenda views

Setup the groups that appear in the agenda views.

(setq org-super-agenda-groups
      '((:discard
         (:todo "WAITING"))
        (:name "Emails"
         :tag "email"
         :order 3)
        (:name "Housework"
         :tag "home"
         :order 100)
        (:name "To read"
         :tag "toread"
         :order 4)
        (:name "To Write"
         :tag "towrite"
         :order 5)
        (:name "Work"
         :tag ("work" "phd")
         :order 6)
        (:name "Habits"
         :habit t
         :order 101)
        (:name "Configuration"
         :tag "config"
         :order 102)))

I want to see emails high up, because usually they are pretty quick to do. Housework and habits I don’t want to see until lower in the agenda, because they are usually low priority. Configuration to do in emacs or my OS is incredibly low priority so I want to see that last.

Any items that do not fall in one of these filter categories goes in an automatic ‘Other Items’ section which has an order of 99 (so will appear before anything with an order >99)

In my PhD view, I want do discard anything hobby or housework related. I strip out these items in the super agenda groups.

(defun tq/phd-org-super-agenda-groups ()
  (cons '(:discard (:tag ("home" "hobby"))) org-super-agenda-groups))

Below I’m setting up my main agenda view as well as a projects view

(setq org-agenda-custom-commands
      '(("j" "Super agenda" (
                             <<agenda-custom-commands>>
                             ))
        ("p" "Projects" (
                         <<agenda-projects-custom-commands>>
                         ))
        ("w" "PhD" (
                    <<agenda-phd-custom-commands>>
                    ))))

Super agenda

The first view is today’s agenda, for tasks scheduled today (or in the past) or with deadlines coming up. I include at schedule at the top
(agenda "" ((org-super-agenda-groups
             (cons '(:name "Schedule" :time-grid t) org-super-agenda-groups))
            (org-agenda-span 'day)
            (org-agenda-start-day)))

The next section is the Inbox. These are items that I have captured quickly and need to be refiled into my main agenda file.

(tags "inbox"
      ((org-agenda-overriding-header "Inbox")
       (orgs-tag-match-list-sublevels nil)))

This section shows projects which are stuck. I define projects as todo items with sub todo items. A stuck project is a project where none of the sub-todos is scheduled. These are projects where I don’t have a task to go onto next. I want to see these because I need to go into these projects and evaluate what tasks I can begin next

(todo "" ((org-agenda-overriding-header "Stuck projects")
          (org-agenda-skip-function 'tq/skip-all-but-stuck-projects)))

This section shows tasks and projects which are available to be completed (e.g. they are standalone tasks or sub-tasks) but are unscheduled. I want to see these because I need to schedule them to complete sometime.

(todo "" ((org-agenda-overriding-header "Unscheduled available todos")
          (org-agenda-skip-function 'tq/skip-all-but-available-unscheduled-todos)))

This section contains waiting projects and tasks

(todo "WAITING" ((org-super-agenda-groups
                  (--remove (equal '(:discard (:todo "WAITING")) it)
                            org-super-agenda-groups))
                 (org-agenda-overriding-header "Waiting")))

Projects view

The project view is for viewing all of my current projects (including sub-projects for now)
(todo "" ((org-agenda-skip-function #'tq/skip-all-but-projects)))

PhD view

This view is the one that I look at during my ‘work’ day. The first view is the time grid which includes everything scheduled for a specific time during the day. I need this to include everything as I might sometimes have a personal or home task scheduled during the day.
(agenda "" ((org-super-agenda-groups '((:name "Schedule" :time-grid t)
                                       (:discard (:anything t))))
            (org-agenda-span 'day)
            (org-agenda-start-day)))

Next It will include the agenda for the current day, discarding any ‘home’ tasks.

(agenda "" ((org-super-agenda-groups (cons '(:discard (:time-grid t))
                                           (tq/phd-org-super-agenda-groups) ))
            (org-agenda-span 'day)
            (org-agenda-start-day)))

The following sections are pretty much the same as the super agenda, but again ignoring the home tasks

(tags "inbox"
      ((org-super-agenda-groups (tq/phd-org-super-agenda-groups))
       (org-agenda-overriding-header "Inbox")
       (orgs-tag-match-list-sublevels nil)))
(todo "" ((org-super-agenda-groups (tq/phd-org-super-agenda-groups))
          (org-agenda-overriding-header "Stuck projects")
          (org-agenda-skip-function 'tq/skip-all-but-stuck-projects)))
(todo "" ((org-super-agenda-groups (tq/phd-org-super-agenda-groups))
          (org-agenda-overriding-header "Unscheduled available todos")
          (org-agenda-skip-function 'tq/skip-all-but-available-unscheduled-todos)))
(todo "WAITING" ((org-super-agenda-groups
                  (--remove (equal '(:discard (:todo "WAITING")) it)
                            (tq/phd-org-super-agenda-groups)))
                 (org-agenda-overriding-header "Waiting")))

Agenda filters

Functions that I use to filter the agenda

(defun tq/is-todo-p ()
  (member (nth 2 (org-heading-components)) org-todo-keywords-1))

(defun tq/is-done-todo-p ()
  (member (nth 2 (org-heading-components)) org-done-keywords))

(defun tq/is-not-done-todo-p ()
  (member (nth 2 (org-heading-components)) org-not-done-keywords))

(defun tq/has-subtodo-p ()
  (save-restriction
    (widen)
    (let ((has-subtodo)
          (subtree-end (save-excursion (org-end-of-subtree t))))
      (save-excursion
        (forward-line 1)
        (while (and (not has-subtodo)
                    (< (point) subtree-end)
                    (re-search-forward org-heading-regexp subtree-end t))
          (when (tq/is-todo-p)
            (setq has-subtodo t))))
      has-subtodo)))

(defun tq/is-project-p ()
  "Is a project. i.e. A todo (with a todo keyword) that has at least one subtodo (with a todo keyword)"
  (and (tq/is-todo-p) (tq/has-subtodo-p)))

(defun tq/is-task-p ()
  "Is a task. i.e. A todo (with a todo keyword) that has no subtodos (with a todo keyword)"
  (and (tq/is-todo-p) (not (tq/has-subtodo-p))))

(defun tq/is-subtodo-p ()
  "Is todo (either a task or a project) that is part of a project"
  (save-restriction
    (widen)
    (let ((is-subtodo)
          (is-a-todo (tq/is-todo-p)))
      (when is-a-todo
        (save-excursion
          (while (and (not is-subtodo)
                      (org-up-heading-safe))
            (when (tq/is-todo-p)
              (setq is-subtodo t)))))
      (and is-a-todo is-subtodo))))

(defun tq/is-subproject-p ()
  "Is task that is part of a project"
  (and (tq/is-subtodo-p) (tq/is-project-p)))

(defun tq/is-subtask-p ()
  "Is task that is part of a project"
  (and (tq/is-subtodo-p) (tq/is-task-p)))

(defun tq/is-standalone-project-p ()
  "Is project that is not part of a project"
  (and (not (tq/is-subtodo-p)) (tq/is-project-p)))

(defun tq/is-standalone-task-p ()
  "Is task that is not part of a project"
  (and (not (tq/is-subtodo-p)) (tq/is-task-p)))

(defun tq/is-scheduled-p ()
  "Checks whether a task is scheduled"
  (org-get-scheduled-time (point)))

(defun tq/is-stuck-project-p ()
  "Is a project that is stuck"
  (when (tq/is-project-p)
    (let ((subtree-end (save-excursion (org-end-of-subtree t)))
          (has-scheduled-subtodo))
      (save-excursion
        (forward-line 1)
        (while (and (not has-scheduled-subtodo)
                    (< (point) subtree-end)
                    (re-search-forward org-heading-regexp subtree-end t))
          (when (and (tq/is-task-p) (tq/is-not-done-todo-p) (tq/is-scheduled-p))
            (setq has-scheduled-subtodo t)))
        (not has-scheduled-subtodo)))))

(defun tq/skip-all-but-stuck-projects ()
  "Skip trees that are not stuck projects"
  (save-restriction
    (widen)
    (unless (tq/is-stuck-project-p)
      (save-excursion (or (outline-next-heading) (point-max))))))

(defun tq/skip-all-but-available-unscheduled-todos ()
  "Skip todos that are unavailable or available but already scheduled. Available todos are standalone tasks or subtasks"
  (save-restriction
    (widen)
    (unless (and (tq/is-task-p)
                 (not (tq/is-scheduled-p)))
      (save-excursion (or (outline-next-heading) (point-max))))))

(defun tq/skip-all-but-projects ()
  "Skip trees that are not projects"
  (save-restriction
    (widen)
    (unless (tq/is-project-p)
      (save-excursion (or (outline-next-heading) (point-max))))))

Capture

Bind capture to something more convenient

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

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

(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))))

Referencing

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

(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

(package! org-ref :pin "052a176b4cc1c080376c183e5e02ab37cb1f0f0a")
(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

(package! citeproc-org :pin "22a759c4f0ec80075014dcc594baa4d1b470d995")
(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.

(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

(package! helm-bibtex :pin "94807a3d3419f90b505eddc3272e244475eeb4f2")
(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

(use-package ox-extra
  :after org
  :config
  (ox-extras-activate '(ignore-headlines)))
(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

(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-server to track source

(package! org-roam
  :recipe (:host github :repo "org-roam/org-roam")
  :pin "fde40dc1c43fcb34cde3253127c3cc27abe7f1a7")
(package! org-roam-bibtex
  :recipe (:host github :repo "org-roam/org-roam-bibtex")
  :pin "81b6fedf99996a78199067e61935964dea9389ee")
;; (package! org-roam-server :recipe (:host github :repo "org-roam/org-roam-server"))

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

(use-package! org-roam
  :hook (org-load . org-roam-mode)
  :hook (org-roam-backlinks-mode . turn-on-visual-line-mode)
  :commands (org-roam
             org-roam-find-file
             org-roam-buffer-toggle-display
             org-roam-dailies-find-date
             org-roam-dailies-find-today
             org-roam-dailies-find-tomorrow
             org-roam-dailies-find-yesterday)
  :init
  <<org-roam-init>>
  :config
  <<org-roam-config>>
  )

Set up useful keybindings to use and access org-roam

(map! :after org
      :leader
      :prefix "n"
      "f" #'org-roam-find-file
      "i" #'org-roam-insert
      "g" #'org-roam-graph
      "r" #'org-roam
      "n" #'org-roam-dailies-capture-today
      "t" #'org-roam-dailies-find-today
      "d" nil
      (:prefix ("d" . "by date")
         :desc "Find previous note" "b" #'org-roam-dailies-find-previous-note
         :desc "Find date"          "d" #'org-roam-dailies-find-date
         :desc "Find next note"     "f" #'org-roam-dailies-find-next-note
         :desc "Find tomorrow"      "m" #'org-roam-dailies-find-tomorrow
         :desc "Capture today"      "n" #'org-roam-dailies-capture-today
         :desc "Find today"         "t" #'org-roam-dailies-find-today
         :desc "Capture Date"       "v" #'org-roam-dailies-capture-date
         :desc "Find yesterday"     "y" #'org-roam-dailies-find-yesterday
         :desc "Find 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

(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

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

Turn off verbosity. I don’t like the messages

(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.

(setq org-roam-capture-templates
      '(("d" "default" plain (function org-roam-capture--get-point)
         "%?"
         :file-name "${slug}"
         :head "#+title: ${title}\n#+setupfile: setup.org\n\n\n\n* TODO Write about '${title}' :towrite:"
         :unnarrowed t
         :immediate-finish t)))

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

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

Exclude daily notes from the graph

(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.

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

Set up an agenda view for nearby notes

(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

(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

(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.

(setq org-roam-completion-everywhere nil)

Do not update links in other files when changing titles. Links are often context dependant and it doesn’t make sense to change the it in other files.

(remove-hook 'org-roam-title-change-hook
             'org-roam--update-links-on-title-change)

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

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

Set the capture template for my daily notes

(setq org-roam-dailies-capture-templates
      '(("d" "default" entry (function org-roam-capture--get-point)
         "* %?"
         :file-name "daily/%<%Y-%m-%d>"
         :head "#+title: %<%Y-%m-%d>\n")))

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.

(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))

BibTeX

Enable org-roam-bibtex and setup capture template

(use-package org-roam-bibtex
  :commands (org-roam-bibtex-insert-non-ref org-roam-bibtex-find-non-ref)
  :hook (org-roam-mode . org-roam-bibtex-mode)
  :config
  <<orb-configuration>>
  )

Set up literature notes template

(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.

(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

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

Noter

I don’t like the configuration in Doom’s org-noter module so I do it myself

(package! org-noter :pin "9ead81d42dd4dd5074782d239b2efddf9b8b7b3d")
(use-package org-noter
  :defer t
  :config
  (map! :map org-noter-doc-mode-map
        :leader :n "i" #'org-noter-insert-note))

Disable org-pdftools because it breaks org-noter in nov mode

(disable-packages! org-pdftools)

Calendar

Define directory for calendars to go in

(defvar tq/cal-dir (concat org-directory "calendars/"))

I also want to add this to the list of org-agenda files that are used

(add-to-list 'org-agenda-files tq/cal-dir)

Configure calendars. These use some secret values that I do not commit to git (for obvious reasons). gcal-file-alist is an alist of the form '(("calendar-id" . "filename") ("id2" . "file2")). This will then put calendar entries in to calendar/filename.org and calendar/file2.org.

(after! org-gcal
  (when tq/secrets-loaded
    (setq org-gcal-client-id secret/gcal-client-id
          org-gcal-client-secret secret/gcal-client-secret
          org-gcal-fetch-file-alist
          (-map (lambda (entry)
                  (cons (car entry) (concat tq/cal-dir (cdr entry) ".org")))
                secret/gcal-file-alist))))

Run sync on idle timer

(after! org-gcal
  (run-with-idle-timer 600 t #'org-gcal-sync))

For the date picker calendar, I want weeks to start on monday

(after! calendar
  (setq calendar-week-start-day 1)
  (calendar-redraw))

Email

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

(package! notmuch :pin "45193bab16c728ba892a5d45fc62ef59e2a6ef85")
(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

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

Add a nice keymap for accessing email

(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

(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

(setq notmuch-show-logo nil)

Show headers by default

(setq notmuch-message-headers-visible t)

Kill message buffers when sent

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

Send mail with sendmail

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

Sort by new

(setq notmuch-search-oldest-first nil)

Fix width of columns in search results

(setq notmuch-search-result-format
      '(("date" . "%12s ")
        ("count" . "%-7s ")
        ("authors" . "%-30s ")
        ("subject" . "%-72s ")
        ("tags" . "(%s)")))

Make unread emails specially

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

Set up the sections in the main hello window

(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).

(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.

(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

(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

(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

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

Allow capturing of email in notmuch

(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)

Use org-msg to write mixed-mime email in a sane way. I’ve pinned it to a commit that is currently working. It has given me issues in the past and I can’t have it breaking and sending broken email to people.

(package! org-msg
  :recipe (:host github :repo "jeremy-compostella/org-msg")
  ;; :recipe (:host github :repo "TimQuelch/org-msg")
  :pin "557d490ecbd80522a42f7b1fb6aaacca504d4512")
(use-package! org-msg
  :after notmuch
  :config
  (org-msg-mode)
  <<org-msg-config>>
  )

Send email with both HTML and plain text (like a good well adjusted human)

(setq org-msg-default-alternatives '(text html))

Set up email signature

(setq org-msg-signature "\n\nThanks,\n\nTim Quelch\n")

Hack notmuch-company to allow it to work with org-msg-edit-mode and enable it.

(defadvice! tq/org-msg-notmuch-company (orig-fn &rest args)
  :around #'notmuch-company
  (letf! (((symbol-function 'derived-mode-p) (lambda (mode)
                                               (if (eq mode 'message-mode)
                                                   t
                                                 (derived-mode-p mode)))))
    (apply orig-fn args)))

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

Use helm-notmuch for searching email from helm.

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

Disable visual-line-mode s from message modes

(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.

(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

(package! systemd :pin "51c148e09a129ddf33d95276aa0e89d4ef6f8dd2")
(use-package systemd
  :defer t)

Docker compose

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

Python

Set up LSP to turn off some python warnings

(after! lsp-pyls
  (setq! lsp-pyls-plugins-pycodestyle-enabled nil))

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,

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

Use vterm for backend for repl,

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

Define a minor mode to enable sending to julia-repl

(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))

Documents

Use nov.el for reading ebooks.

(package! nov :pin "23e5d9c4c63e3a7dc08110a8dfcbb97a1186a37f")
(use-package nov
  :mode ("\\.epub\\'" . nov-mode))

MATLAB/Octave

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