boxen/home/profiles/emacs/doom.d/config.org

31 KiB

Doom emacs configuration

Preamble

  (require 'dash) ; lists
  (require 's)    ; strings
  (require 'f)    ; paths & files

Utilities

  (defmacro defjoiner (name joiner)
    `(defun ,name (&rest lines)
       (s-join ,joiner lines)))

  (defjoiner spaces " ")
  (defjoiner lines "\n")
  (defjoiner commas ", ")

Overall configuration

  (setq user-full-name "Jez Cope"
        user-mail-address (rot13 "w.pbcr@renzoyre.pb.hx")

        confirm-kill-emacs nil
        display-line-numbers-type t
        fill-column 120

        explicit-shell-file-name (executable-find "xonsh")
        shell-file-name (executable-find "bash"))

Appearance

  (setq doom-font (font-spec :family "Iosevka"
                             :size (let ((wd (display-pixel-width)))
                                     (cond ((< wd 1600) 12.0)
                                           (t 14.0)))
                             :weight 'light)
        doom-variable-pitch-font (font-spec :family "Iosevka Aile")

        doom-theme 'doom-gruvbox
        doom-themes-treemacs-theme "doom-colors")

    (doom-themes-treemacs-config)
    (doom-themes-org-config)

Avy key tweaks

Theory: alternating left/right, 1st to 4th finger then middle column, home row -> upper row -> lower row

  (setq avy-keys (string-to-list "aoeuhtnsid',.pgcrlyf")
        lispy-avy-keys avy-keys)
  (after! ace-window
    (setq aw-keys (-slice avy-keys 0 20)))

Save all on focus-out

(add-hook 'focus-out-hook (lambda () (evil-write-all nil)))

Popup buffers

Most monitors are wide these days: prefer the right-hand side to the bottom for popups.

  (defun clear-popup-rule! (pred)
    (assoc-delete-all pred +popup--display-buffer-alist))
  (defun clear-popup-rules! (&rest preds)
    (dolist (pred preds)
      (clear-popup-rule! pred)))

  (plist-put! +popup-defaults
              :size 0.38 ;; golden ratio!
              :side 'right)

  (clear-popup-rules! "^\\*info\\*$")
  (set-popup-rules!
    '(("^\\*\\([Hh]elp\\|Apropos\\)"
       :side right :slot 2 :vslot -8 :size 0.38 :select t)
      ("^\\*info\\*$"
       :side right :size 0.38 :slot 2 :vslot 1)
      ("^\\*\\(?:doom\\)?:scratch"
       :side right size 0.38)
      ("^\\*eww\\*"
       :actions (+popup-display-buffer-stacked-side-window-fn)
       :side right :size 0.62 :slot 1 :vslot 1)
      ("^\\*Capture\\*$\\|CAPTURE-.*$"
       :side left :size 0.38 :quit nil :select t :autosave ignore)
      ("^\\*Org Src"
       ;; :actions (display-buffer-use-some-window)
       :side right :size 80
       :quit nil :select t :autosave t :modeline t :ttl nil)))

Encryption

  (use-package org-crypt
    :after (org)
    :config
    (org-crypt-use-before-save-magic)
  
    :init
    (setq org-tags-exclude-from-inheritance '("crypt")
          org-crypt-key "D9DA 3E47 E8BD 377D A317  B3D0 9E42 CE07 1C45 59D1"
          auto-save-default nil))

Spelling

  (setq ispell-dictionary "en_GB")

Formatting

Disable format-on-save for Rust, since it seems to complain…

  (setq +format-on-save-enabled-modes
        '(python-mode nix-mode))

Dashboard

  (nconc +doom-dashboard-menu-sections
         '(("RSS feeds"
            :icon
            (all-the-icons-octicon "rss" :face 'doom-dashboard-menu-title)
            :when (fboundp 'elfeed)
            :action elfeed)))

  (defun doom-dashboard-widget-simple-title ()
    (insert
     "\n\n"
     (+doom-dashboard--center
      +doom-dashboard--width
      "D O O M   E M A C S")
     "\n"))

  (when (< (display-pixel-height) 1080)
    (setq +doom-dashboard-functions
          (cons 'doom-dashboard-widget-simple-title
                (delq 'doom-dashboard-widget-banner
                      +doom-dashboard-functions))))

Scratch buffer

  (setq doom-scratch-initial-major-mode 'org-mode)

Link hints

  (map! :n "g C-l" #'link-hint-copy-link
        :n "g M-l" #'link-hint-open-link)

Projects

Projectile

(use-package! projectile
  :config
  (add-to-list 'projectile-globally-ignored-directories ".sync"))

Git

The environment variable SSH_AUTH_SOCK needs to be set for git to work within emacs.

  (setenv "SSH_AUTH_SOCK" (f-join "/run/user" (prin1-to-string (user-uid)) "gnupg/S.gpg-agent.ssh"))

Mercurial

  (setq monky-process-type 'cmdserver)

Note-taking & organisation

  (after! eww
    (require 'ol-eww))

Appearance

  (add-hook! org-mode #'org-indent-mode #'+org-pretty-mode)

  (setq org-superstar-headline-bullets-list '("◉" "○" "●" "⋄" "►")
        org-superstar-item-bullet-alist '((?- . ?•)
                                          (?+ . ?⬝)
                                          (?* . ?★)))

Bindings

  (map! :map org-mode-map
        :localleader :n "sp" #'org-paste-subtree)

Behaviour tweaks

  (general-add-hook 'org-insert-heading-hook
                    'evil-insert-state
                    t)                    ;; append instead of prepending

File locations

  (after! org
    (setq jc/notes-dir (f-expand "~/Notes")
          jc/logbook-file (f-expand "logbook.org" jc/notes-dir)

          org-directory jc/notes-dir
          org-roam-directory (f-expand "Roam/" jc/notes-dir)
          org-roam-dailies-directory "daily/"
          org-id-locations-file (f-expand ".orgids" org-directory)

          jc/agenda-file-home (f-expand "Todo/todo-home.org" jc/notes-dir)
          jc/agenda-file-work (f-expand "Todo/todo-work.org" jc/notes-dir)
          jc/agenda-files-all (list jc/agenda-file-work jc/agenda-file-home)
          org-agenda-files jc/agenda-files-all
          jc/spark-file (f-expand "spark.org" org-roam-directory)

          jc/agenda-file-sets (cons jc/agenda-files-all (mapcar 'list jc/agenda-files-all))

          org-archive-location "%s_archive::datetree/"
          jc/agenda-file-home-archive (concat jc/agenda-file-home "_archive")
          jc/agenda-file-work-archive (concat jc/agenda-file-work "_archive")))

  (add-to-list 'auto-mode-alist '("\\.org_archive\\'" . org-mode))

File sets

  (defvar jc/agenda-file-sets '() "List of agenda file sets")

  (defun list-successor (x xs)
    (if (null xs) nil
      (let ((y (car xs))
            (ys (cdr xs)))
        (or (catch 'result
              (while ys
                (if (equal x y)
                    (throw 'result (car ys))
                  (setq y (car ys)
                        ys (cdr ys)))))
            (car xs)))))

  (defun jc/agenda-file-set-next ()
    (interactive)
    (let ((next-set (list-successor org-agenda-files jc/agenda-file-sets)))
      (setq org-agenda-files next-set))
    (message (format "Current agenda files: %s" org-agenda-files)))

  (defun jc/agenda-file-set-choose ()
    (interactive)
    (let ((sets
           (--map (cons (s-join "+" it) it)
                  jc/agenda-file-sets)))
      (ivy-read "Set:" sets
                :action (lambda (result) (setq org-agenda-files (cdr result))))))

Refiling

  (after! org
    (setq org-refile-targets `((,jc/agenda-files-all . (:maxlevel . 3))
                               (,jc/spark-file . (:maxlevel . 2))
                               (nil . (:maxlevel . 3)))
          org-refile-use-outline-path t))

Capture

  (use-package! org-protocol)

  (add-hook 'org-capture-mode-hook 'evil-insert-state t)

Capture templates

  (defun jc/org-id-gen (&optional node)
    (org-id-new))

  (setq org-capture-templates
        `(("t" "Task" entry
           (file "/home/jez/Notes/Todo/inbox.org")
           "* TODO %?")
          ("n" "Note" entry
           (file "/home/jez/Notes/Todo/inbox.org")
           "* %?")
          ("l" "Link" entry
           (file "/home/jez/Notes/Todo/inbox.org")
           "* TOREAD %? %:annotation")
          ("L" "Link task" entry
           (file "/home/jez/Notes/Todo/inbox.org")
           "* TODO %? %:annotation"))
        org-protocol-default-template-key "l"

        org-roam-capture-templates
        `(("d" "default" plain
           ,(lines "categories: %?" "related: ")
           :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                              "#+title: ${title}\n")
           :empty-lines 1
           :unnarrowed t)
          ("w" "work" plain "%?"
           :if-new (file+head "work/%<%Y%m%d%H%M%S>-${slug}.org"
                              "#+title: ${title}\n")
           :empty-lines 1
           :unnarrowed t)
          ("b" "blog" entry
           ,(lines "* ${title}" "" "%?")
           :if-new (file+olp "writing/blog.org" ("Ideas"))
           :empty-lines 1
           :narrowed t))

        org-roam-capture-ref-templates
        '(("r" "ref" plain "%?"
           :if-new (file+head "${slug}.org"
                              "#+title: ${title}")
           :unnarrowed t))

        org-roam-dailies-capture-templates
        '(("d" "default" entry "* %?"
           :if-new (file+head "%<%Y/%m/%Y-%m-%d>.org"
                              "#+title: Daily notes: %<%a %d %b %Y>\n\n"))
          ("c" "clocking" entry nil
           :if-new (file+datetree "clocking.org" week)
           :clock-in))

        orb-templates
        '(("r" "ref" plain
           #'org-roam-capture--get-point
           ""
           :file-name "bibliography/${citekey}"
           :head "#+title: ${title}\n#+roam_key: ${ref}\n"
           :unnarrowed t)))

Todo

  (setq org-todo-keywords
        '((sequence "TODO(t)" "COOL(c)" "WARM(w)" "HOT(h)" "DOING(o!)" "|" "DONE(d!)" "DROPPED(x@)")
          (sequence "WAITING(a@)" "DELEGATED(l@)" "DISCUSS(s)" "MONITOR(m)" "ONHOLD(H)" "|")
          (sequence "TOREAD" "READING" "|" "READ"))

        org-enforce-todo-dependencies t)

Properties

  (setq org-tags-exclude-from-inheritance '("track" "project")

        org-id-track-globally t
        org-id-link-to-org-use-id t)

Source blocks

  (setq org-src-preserve-indentation nil)

HTTP

  (use-package! ob-http
    :after org-babel
    :commands ob-http-mode)

Org Roam

See also Capture templates

  (setq org-roam-db-update-method 'immediate
        org-roam-tag-sources '(prop first-directory)

        org-roam-graph-exclude-matcher "daily/"
        org-roam-graph-extra-config '(("rankdir" . "LR")
                                      ("concentrate" . "true"))
        org-roam-graph-edge-extra-config '(("dir" . "none"))

        org-roam-node-display-template
        "${doom-hierarchy:*} ${doom-subdirs:20} ${tags:20}"

        +org-roam-open-buffer-on-find-file nil)

  (unless (f-exists? org-roam-directory)
    (f-mkdir org-roam-directory))

  (defun jc/search-org-roam () "search org-roam with ripgrep"
         (interactive)
         (counsel-rg nil org-roam-directory
                     "" "org-roam search: "))
  (map! :leader :desc "org-roam search" :n "sR" #'jc/search-org-roam)
  (map! :localleader :mode org-mode :n "mx" #'org-roam-extract-subtree)

  (require 'org-roam-protocol)

Bibliographic notes

See also Capture templates

  (use-package! citar
    :when (featurep! :completion vertico)
    :after embark
    :defer t
    :config
    (add-to-list 'embark-keymap-alist '(bib-reference . citar-map)))

  (setq bibtex-autokey-names 1
        bibtex-autokey-additional-names ".ea"
        bibtex-autokey-name-separator "."
        bibtex-autokey-name-case-convert 'identity

        bibtex-autokey-name-year-separator "_"
        bibtex-autokey-year-length 4

        bibtex-autokey-year-title-separator "_"
        bibtex-autokey-titleword-length 10
        bibtex-autokey-titlewords 1
        bibtex-autokey-titlewords-stretch 1
        bibtex-autokey-titleword-case-convert 'capitalize

        bibtex-entry-format '(opts-or-alts
                              required-fields
                              numerical-fields
                              whitespace
                              sort-fields)

        bibtex-completion-notes-path
        (f-expand "bibliography" org-roam-directory)
        bibtex-completion-bibliography
        (f-expand "bibliography.bib" bibtex-completion-notes-path)
        bibtex-completion-library-path
        (f-expand "files" bibtex-completion-notes-path)

        org-ref-default-bibliography (list bibtex-completion-bibliography)
        org-ref-pdf-directory bibtex-completion-library-path)

  (use-package! bibtex
    :defer
    :config
    (bibtex-set-dialect 'BibTeX))

  (use-package! org-ref-ivy
    :after (org-ref))

reMarkable bibtex-completion integration

  (defvar bibtex-rmapi-destination "/Reading"
    "The folder on tablet to send PDFs to (starts with a '/')")

  (defun bibtex-completion-rmapi-function (file)
    (message "Sending %s to %s on reMarkable" file bibtex-rmapi-destination)
    (start-process
     "rmapi" "*rmapi*"
     "rmapi" "put" file bibtex-rmapi-destination))

  (defun bibtex-completion-send-to-rmapi (keys &optional fallback-action)
    "Send the PDFs associated with the marked entries to reMarkable tablet
  If multiple PDFs are found for an entry, ask for the one to open
  using `completion-read'."
    (let ((bibtex-completion-pdf-open-function 'bibtex-completion-rmapi-function))
      (bibtex-completion-open-pdf keys)))

  (with-eval-after-load 'ivy-bibtex
    (ivy-bibtex-ivify-action bibtex-completion-send-to-rmapi ivy-bibtex-send-to-rmapi)

    (ivy-add-actions
     'ivy-bibtex
     '(("R" ivy-bibtex-send-to-rmapi "Send to reMarkable tablet"))))

Agenda

Super agenda

  (defun jc/agenda-line-blocked-p (item)
    (get-text-property 0 'org-todo-blocked item))
  (setq org-agenda-dim-blocked-tasks t)

  (use-package! org-super-agenda
    :after (org-agenda)
    :config
    (setq org-super-agenda-groups
          '((:name "Future"
             :and (:todo t :scheduled future)
             :order 6)
            (:name "Active projects"
             :and (:todo t :children ("TODO" "COOL" "WARM" "HOT" "DOING"))
             :order 15)
            (:name "Waiting projects"
             :and (:todo t :children ("WAITING" "DELEGATED"))
             :order 14)
            (:name "On hold"
             :and (:children nil :todo "ONHOLD")
             :order 4)
            (:name "On-hold projcts"
             :and (:todo t :children "ONHOLD")
             :and (:tag "project" :todo "ONHOLD")
             :order 14)
            (:name "Stuck projects"
             :tag "project"
             :and (:todo t :children t)
             :order 13)
            (:name "Overdue"
             :and (:todo t :deadline past)
             :face (:foreground "red"))
            (:name "Due today"
             :and (:todo t :deadline today)
             :face (:foreground "orange"))
            (:name "Due tomorrow"
             :and (:todo t :deadline +2))
            (:name "Blocked"
             :and (:todo t :pred jc/agenda-line-blocked-p)
             :order 8)
            (:name "Doing (3)"
             :todo "DOING")
            (:name "Hot (3)"
             :todo "HOT")
            (:name "Warm (5)"
             :todo "WARM")
            (:name "Cool (8)"
             :todo "COOL")
            (:name "Backlog"
             :todo "TODO"
             :order 1)
            (:name "Waiting"
             :todo "WAITING"
             :order 2)
            (:name "Delegated"
             :todo "DELEGATED"
             :order 4)))
    (org-super-agenda-mode 1))

Org query language

  (setq org-ql-views
        (let ((kanban-query '(and (todo "DOING" "HOT" "WARM" "COOL" "TODO" "WAITING")
                                  (not (or (scheduled :from tomorrow)
                                           (property "Project" "t")
                                           (children))))))
          `(("Working: Contexts"
             :title "Tasks by context"
             :buffers-files org-agenda-files
             :query (and (not (or (done) (todo "ONHOLD" "WAITING" "DELEGATED")) )
                         (not (scheduled :from tomorrow))
                         (tags "email" "phone" "online"))
             :sort todo
             :super-groups ((:auto-tags t)))

            ("Kanban: work"
             :title "Kanban: work"
             :buffers-files (,jc/agenda-file-work)
             :query ,kanban-query
             :super-groups org-super-agenda-groups)
            ("Kanban: home"
             :title "Kanban: home"
             :buffers-files (,jc/agenda-file-home)
             :query ,kanban-query
             :super-groups org-super-agenda-groups)

            ("Focus: work"
             :title "Focus"
             :buffers-files (,jc/agenda-file-work)
             :query (and (todo "DOING" "HOT")
                         (not (or (scheduled :from tomorrow)
                                  (property "Project" "t")
                                  (children))))
             :super-groups org-super-agenda-groups)
            ("Working: People agendas"
             :title "Agendas"
             :buffers-files (,jc/agenda-file-work)
             :query (and (todo "DELEGATED" "WAITING" "DISCUSS")
                         (tags "BlaM" "BluM" "RK"))
             :super-groups ((:auto-tags t)))

            ("Process: Inbox"
             :buffers-files ,(f-expand "Todo/inbox.org" jc/notes-dir))

            ("Review: Recently closed"
             :title "Closed in the last 2 weeks"
             :buffers-files ,(list jc/agenda-file-work jc/agenda-file-work-archive)
             :query (closed :from -14)
             :super-groups ((:auto-ts t)))

            ("Review: Projects"
             :buffers-files org-agenda-files
             :query (and (todo)
                         (or (tags "project")
                             (children)))
             :super-groups ((:name "Progressing"
                             :order 15
                             :children ("TODO" "COOL" "WARM" "HOT" "DOING"))
                            (:name "Discussion needed"
                             :order 4
                             :children ("DISCUSS"))
                            (:name "Waiting"
                             :order 10
                             :children ("WAITING" "DELEGATED"))
                            (:name "On hold"
                             :order 10
                             :children ("ONHOLD"))
                            (:name "Stalled"
                             :order 0
                             :anything t)))
            ("Review: Strategic areas"
             :buffers-files org-agenda-files
             :query (property "Track")
             :super-groups ((:name "Progressing"
                             :order 5
                             :children ("TODO" "COOL" "WARM" "HOT" "DOING"))
                            (:name "Discussion needed"
                             :order 4
                             :children ("DISCUSS"))
                            (:name "Waiting"
                             :order 10
                             :children ("WAITING" "DELEGATED"))
                            (:name "On hold"
                             :order 10
                             :children ("ONHOLD"))
                            (:name "Stalled"
                             :order 0
                             :anything t))))))

  (map! :leader :desc "Org-ql views" :n "oq" #'org-ql-view)

Handy agenda views

  (defhydra jc/agenda-views ()
    "agendas"
    ("f" jc/agenda-file-set-choose
     "choose agenda file set")
    ("ht"
     (org-ql-search (list jc/agenda-file-home) '(todo)
       :super-groups org-super-agenda-groups)
     "home tasks")
    ("hk"
     (org-ql-view "Kanban: home")
     "home kanban")
    ("wt"
     (org-ql-search (list jc/agenda-file-work) '(todo)
       :super-groups org-super-agenda-groups)
     "work tasks")
    ("wk"
     (org-ql-view "Kanban: work")
     "work kanban"))

  (map! :leader :n "oa" #'jc/agenda-views/body)

Clocking

  (setq org-duration-format 'h:mm)

  (defun jc/org-clock-choose-time (&optional select)
    (interactive "P")
    (let ((at-time (encode-time (parse-time-string (org-read-date t))))
          (org-clock-continuously t))
      (org-clock-out nil nil at-time)
      (org-clock-in select at-time)))

Deft

  (use-package! deft
    :custom
    (deft-extensions '("md" "org" "txt"))
    (deft-default-extension "md")
    (deft-directory "~/Scratch/ZettelTest")
    (deft-use-filename-as-title t))

  (use-package! zetteldeft
    :after deft
    :config
    (setq zetteldeft-link-indicator "[["
          zetteldeft-link-suffix "]]"

          zetteldeft-title-prefix "# "
          zetteldeft-list-prefix "- ")
    (font-lock-add-keywords 'markdown-mode
                            `((,zetteldeft-id-regex
                               . font-lock-warning-face))))

Reading

Web

  (setq browse-url-browser-function #'eww-browse-url
        browse-url-generic-program "xdg-open")

  (map! :leader :desc "Web search" :n "sw" #'eww-search-words)

RSS feeds

  (after! elfeed
    (setq elfeed-search-filter "@6-month-ago +unread")

    (clear-popup-rules! "^\\*elfeed-entry")
    (set-popup-rule!
      "^\\*elfeed-entry"
      :actions '(+popup-display-buffer-stacked-side-window-fn)
      :side 'right :size 0.62 :slot 1 :vslot 1))

  (setq elfeed-db-directory (f-join jc/notes-dir "Data" "elfeed")
        rmh-elfeed-org-files
        (list (f-expand "feeds.org" org-roam-directory)))

  (map! :map elfeed-show-mode-map
        :n "gy" #'link-hint-copy-link
        :n "gl" #'link-hint-open-link)

Gopher & gemini

  (use-package! elpher
    :commands (elpher)
    :config
    (setq elpher-ipv4-always t))

  (setq gnutls-verify-error nil)

Activities

Blogging

  (defun jc/fix-python-date (str)
    (replace-regexp-in-string "\\([+-][0-9][0-9]\\)\\([0-9][0-9]\\)"
                              "\\1:\\2"
                              str))

  (defun jc/update-post-date ()
    (interactive)
    (save-mark-and-excursion
      (goto-char (point-min))
      (re-search-forward "^date:")
      (move-beginning-of-line 1)
      (kill-line)
      (insert (jc/fix-python-date (format-time-string "date: %FT%T%z")))))

Shell

  (map! :leader :desc "Run shell command" "!" #'shell-command)

Mail

Accounts

  (set-email-account!
   "main"
   `((user-full-name         . "Jez Cope")
     (user-mail-address      . "j.cope@erambler.co.uk")
     (mu4e-compose-signature . "---\nJez Cope")

     (mu4e-drafts-folder     . "/main/Drafts")
     (mu4e-trash-folder      . "/main/Trash")
     (mu4e-sent-folder       . "/main/Sent")
     (mu4e-refile-folder     . jc/make-refile-folder))
   t)

Reading

  (defun jc/make-refile-folder (msg)
    (format-time-string "/main/Archives.%Y"
                        (mu4e-msg-field msg :date)))

  (setq mu4e-split-view 'vertical
        mu4e-headers-visible-columns 80

        mu4e-headers-sort-direction 'ascending
        mu4e-headers-sort-field ':date
        mu4e-headers-include-related nil

        mu4e-bookmarks
        '((:name "Unified Inbox"
           :query "m:\"/main/Inbox\" OR m:\"/main/Triage.*\""
           :key ?I)
          (:name "Inbox"
           :query "m:\"/main/Inbox\""
           :key ?i)
          (:name "Unread messages"
           :query "flag:unread AND NOT flag:trashed AND NOT m:/main/Junk"
           :key ?u)
          (:name "Today's messages"
           :query "date:today..now AND NOT m:/main/Junk"
           :key ?t)
          (:name "Last 7 days"
           :query "date:7d..now AND NOT m:/main/Junk"
           :hide-unread t
           :key ?w)
          (:name "Flagged messages"
           :query "flag:flagged AND NOT m:/main/Junk"
           :key ?f))

        mu4e-headers-actions
        '(("capture message" . mu4e-action-capture-message)
          ("show this thread" . mu4e-action-show-thread)))

  (after! (mu4e-mark mu4e-headers evil-collection-mu4e)
    (setq mu4e-junk-folder "/main/Junk"

          mu4e-marks
          (append
           '((archive
              :char ("a" . "▶")
              :prompt "archive"
              :dyn-target (lambda (target msg)
                            (mu4e-get-refile-folder msg))
              :action (lambda (docid msg target)
                        (mu4e~proc-move docid
                                        (mu4e~mark-check-target target)
                                        "+S-u-N")))
             (junk
              :char ("J" . "🗑")
              :prompt "Junk"
              :show-target (lambda (target) "junk")
              :action (lambda (docid msg target)
                        (mu4e~proc-move docid
                                        (mu4e~mark-check-target mu4e-junk-folder)
                                        "+S-u-N"))))
           mu4e-marks))

    (mu4e~headers-defun-mark-for junk)
    (mu4e~headers-defun-mark-for archive)
    (map! :mode mu4e-headers-mode
          :n "zJ" #'mu4e-headers-mark-for-junk
          :n "r"  #'mu4e-headers-mark-for-archive))

Sending

  (after! mu4e
    (setq sendmail-program (executable-find "msmtp")
          send-mail-function #'smtpmail-send-it
          message-sendmail-f-is-evil t
          message-sendmail-extra-arguments '("--read-envelope-from")
          message-send-mail-function #'message-send-mail-with-sendmail
          mu4e-compose--org-msg-toggle-next nil))

Languages/modes

  (setq-default flycheck-disabled-checkers '())

Web

Templating (web-mode)

  (add-to-list 'auto-mode-alist '("\\.tmpl\\'" . web-mode))
  (setq web-mode-engines-alist
        '(("go" . "/layouts/.*\\.html\\'"))

        web-mode-enable-engine-detection t)

Python

  (add-to-list 'auto-mode-alist '("Pipfile\\'" . toml-mode))

Lisp (and related)

Syntax highlighting

  (use-package! prism
    :custom
    (prism-parens t)
    (prism-lightens '(0))
    (prism-desaturations '(0)))

  (after! elisp-mode
    (remove-hook! emacs-lisp-mode #'rainbow-delimiters-mode))
  (after! tex
    (remove-hook! TeX-update-style #'rainbow-delimiters-mode))

Formatting

  (use-package! aggressive-indent
    :hook (emacs-lisp-mode . aggressive-indent-mode))

Syntax checking (flycheck)

  (cl-pushnew 'emacs-lisp-checkdoc
               flycheck-disabled-checkers)

Navigation (symex)

  (use-package! symex
    :custom
    (symex-modal-backend 'evil)
    (symex-highlight-p t)
    :init
    (setq symex--user-evil-keyspec
          '(("j"   . symex-go-up)
            ("k"   . symex-go-down)
            ("C-j" . symex-climb-branch)
            ("C-k" . symex-descend-branch)
            ("M-j" . symex-goto-highest)
            ("M-k" . symex-goto-lowest)))
    :config
    (symex-initialize)
    :bind (:map evil-normal-state-map
           ("s-;" . symex-mode-interface)))

Machine-specific config

Work laptop

  (when (equal (system-name) "WXLT203936")
    (setq url-proxy-services '(("no_proxy" . "bl\\.uk")
                               ("http" . "public-cache.bl.uk:3128")
                               ("https" . "public-cache.bl.uk:3128"))

          spell-checking-enable-by-default nil

          jc/notes-dir "f:/Notes"))

Notes

Here are some additional functions/macros that could help you configure Doom:

  • load! for loading external *.el files relative to this one
  • use-package! for configuring packages
  • after! for running code after a package has loaded
  • add-load-path! for adding directories to the load-path, relative to this file. Emacs searches the load-path when you load packages with require or use-package.
  • map! for binding new keys

To get information about any of these functions/macros, move the cursor over the highlighted symbol at press K (non-evil users must press C-c c k). This will open documentation for it, including demos of how they are used.

You can also try gd (or C-c c d) to jump to their definition and see how they are implemented.

Disabled stuff

Fix git-gutter+ errors with tramp incompatibility

  (with-eval-after-load 'git-gutter+
    (defun git-gutter+-remote-default-directory (dir file)
      (let* ((vec (tramp-dissect-file-name file))
             (method (tramp-file-name-method vec))
             (user (tramp-file-name-user vec))
             (domain (tramp-file-name-domain vec))
             (host (tramp-file-name-host vec))
             (port (tramp-file-name-port vec)))
        (tramp-make-tramp-file-name method user domain host port dir)))

    (defun git-gutter+-remote-file-path (dir file)
      (let ((file (tramp-file-name-localname (tramp-dissect-file-name file))))
        (replace-regexp-in-string (concat "\\`" dir) "" file))))

Former spacemacs key bindings (mostly for org agendas)

  (spacemacs/declare-prefix "ot" "Todo lists")
  (spacemacs/declare-prefix "oc" "Capture")
  (spacemacs/set-leader-keys
   "occ" #'org-capture
   "oct" #'org-roam-dailies-capture-today
   "ocy" #'org-roam-dailies-capture-yesterday
   "ocd" #'org-roam-dailies-capture-date

   "ott" #'org-todo-list
   "otw" (jc/org-todo-list-with-file-command  jc/agenda-file-work)
   "oth" (jc/org-todo-list-with-file-command  jc/agenda-file-home)
   "otq" #'org-ql-view
   "otk" (jc/org-ql-view-command              "Working: Kanban")
   "otf" (jc/org-ql-view-command              "Working: Focus")

   "&"  'async-shell-command)