All Projects → elken → .doom.d

elken / .doom.d

Licence: MIT license
Doom Emacs config

Programming Languages

Org
3 projects
emacs lisp
2029 projects
shell
77523 projects

Config

images/banner.png

(icon courtesy of https://github.com/eccentric-j/doom-icon)

Below is my doom-emacs config. Most of it isn’t particularly original; snippets from stackoverflow, modernemacs, David Wilson and Tecosaur. Everything else will be commented to the best of my ability.

Most of this cobbled mess is now mine, and if you do intend to use any of it it would be nice if you mentioned me when doing so. It’s just polite :)

Table of Contents

Setup

Workflow

images/overview.png

EXWM Setup

One of the many usecases for this config is exwm which is pulled from a number of files in the current environment. Loading exwm is a simple case of enabling the exwm module, although it’s wildly untested and is basically hardcoded to just work for me. Any input is welcome, although unlikely to make it into this module (until I stop being lazy and attempt to make it better for upstream….)

modules/ui/exwm

The primary lisp file where the bulk of the configuration is held, with everything from my process manager to a now-playing segment. Below are some usage screenshots. Standard doom module layout, nothing fishy going on. For those unfamiliar,

  • init.el is loaded before anything else really, which is important to properly check if the flag exists to load the exwm code as early as possible
  • config.el is the main bread and butter, all the config lives here (surprisingly)
  • doctor.el is currently just used for detecting missing exe’s, by plugging into doom doctor
  • packages.el is a list of extra packages to be installed by doom’s package manager

images/kill-process.png

images/tray.png

Transparency is handled both through Doom and via picom.

Session

For the sake of simplicity, I use a slightly modified version of GNOME Flashback to run the startup scripts. It also gives me ootb access to things like pinentry, the various password stores, gnome-screensaver lock screen and the useful screenshot tool.

As such, everything is themed around Nord.

Over time and due to various issues, I have been migrating to a plain exwm session but I haven’t yet settled on the best approach.

start.sh / start-debug.sh

The scripts responsible for starting up exwm in the right way, including env variables and picom.

FAQ

None yet because luckily nobody else has seen this spaghetti junction

Globals

Constants and Variables

I could make a Bioshock Infinite joke here but I can’t think of one. Wouldn’t think of one? Would have thought of one.

Lexical binding

;;; -*- lexical-binding: t; -*-

Default projectile path

I stick to the same convention for projects on every OS, so it makes sense to tell projectile about it.

(setq projectile-project-search-path '("~/build"))

Lookup provider URLs

Majority of the default lookup providers are useless (to me) so let’s trim the fat, adjust the order and add in some of our own!

(setq +lookup-provider-url-alist
      '(("Doom Emacs issues" "https://github.com/hlissner/doom-emacs/issues?q=is%%3Aissue+%s")
        ("DuckDuckGo"        +lookup--online-backend-duckduckgo "https://duckduckgo.com/?q=%s")
        ("StackOverflow"     "https://stackoverflow.com/search?q=%s")
        ("Github"            "https://github.com/search?ref=simplesearch&q=%s")
        ("Youtube"           "https://youtube.com/results?aq=f&oq=&search_query=%s")
        ("MDN"               "https://developer.mozilla.org/en-US/search?q=%s")
        ("Nixpkgs"           "https://search.nixos.org/packages?query=%s")))

Subword-mode

Subword mode is a good start because PHP uses a lot of CamelCase and it makes refactoring slightly easier

(global-subword-mode 1)

Auto-revert-mode

Testing having auto-revert-mode on for text-mode buffers (should just be log files mostly)

(add-hook! 'text-mode (lambda () (auto-revert-mode 1)))

Prevent flickering

Noticed some odd flickering here and there, apparently this should resolve it

(add-to-list 'default-frame-alist '(inhibit-double-buffering . t))

Clear snippets before loading

Some attempt to make them reproducible.

(add-hook! 'org-babel-pre-tangle-hook
  (when (file-directory-p "snippets")
    (require 'async)
    (async-start
     (lambda ()
       (delete-directory "snippets" t (not (null delete-by-moving-to-trash))))
     (lambda (result)
       (print! "Delete snippets dir got: " result)))))

Load env after reload

Most of the time, reloading breaks. So, let’s not break.

(add-hook! 'doom-after-reload-hook (doom-load-envvars-file (expand-file-name "env" doom-local-dir) t))

Evil

Splits

I make a lot of splits, and it finally got annoying having to swap to them all the time. So, let’s change that

(setq evil-split-window-below t
      evil-vsplit-window-right t)

Fine undo

I don’t need this because I, like all programmers, make 0 mistaeks.

(setq evil-want-fine-undo t)

Global substitute

More often than not, I’d argue always, I want s/ on my ex commands, so let’s sort that out.

(setq evil-ex-substitute-global t)

Ignore visual text in the kill ring

When we overwrite text in visual mode, say vip, don’t add to the kill ring.

(setq evil-kill-on-visual-paste nil)

Lispyville

This structured-editing thing is apparently really neat, so let’s see how we go

(after! lispy
  (setq lispyville-key-theme
        '((operators normal)
          c-w
          (prettify insert)
          (atom-movement normal visual)
          (additional-movement normal)
          slurp/barf-lispy
          additional)))

Default scratch mode

Make the scratch buffer start in lisp mode

(setq doom-scratch-initial-major-mode 'lisp-interaction-mode)

Auth info

Add plaintext authinfo file to the list of sources. I know I should use a GPG file but I’ll get around to it damn it.

(add-to-list 'auth-sources "~/.authinfo")

fetch-auth-source

Useful function to retrieve passwords from auth-sources

(defun fetch-auth-source (&rest params)
(require 'auth-source)
  (let ((match (car (apply #'auth-source-search params))))
    (if match
        (let ((secret (plist-get match :secret)))
          (if (functionp secret)
              (funcall secret)
            secret))
      (error "Password not found for %S" params))))

Magit

Forge

Allow forge to create repos under my name

(setq forge-owned-accounts '(("elken")))

EShell

Prompt

Eshell is a beautiful thing but ootb experience is a tad dated. Custom prompt based on a combination of the famous p10k and eshell-git-prompt. I only really need the minimum out of a prompt:

  • cwd; almost impossible to work without knowing the current working directory
  • git info; current branch, dirty/clean status, etc
  • prompt number: useful for jumping up and down for fast history in a given session

Can’t get enough out of the default powerline theme, and removing a dependancy we’re rolling our own prompt called eshell-p10kline

(package! eshell-p10k
  :recipe (:host github :repo "elken/eshell-p10k"))
(use-package! eshell-p10k
  :after eshell
  :config
  (setq eshell-prompt-function #'eshell-p10k-prompt-function
        eshell-prompt-regexp eshell-p10k-prompt-string))

Settings

We use eshell in a cross platform world, so we should prefer the lisp version of things to ensure a more consistent experience.

(setq eshell-prefer-lisp-functions t)

User setup

Use my name and emails for things like GPG, snippets, mail, magit, etc. Differs based on which OS I’m on.

(setq user-full-name "Ellis Kenyő"
      user-mail-address "[email protected]")

Server

Start a server (if not running already)

(require 'server)
(when (not (server-running-p))
  (server-start))

(defun greedily-do-daemon-setup ()
  (require 'org)
  (when (require 'mu4e nil t)
    (setq mu4e-confirm-quit t)
    (setq +mu4e-lock-greedy t)
    (setq +mu4e-lock-relaxed t)
    (+mu4e-lock-add-watcher)
    (when (+mu4e-lock-available t)
      (mu4e~start))))

(when (daemonp)
  (add-hook 'emacs-startup-hook #'greedily-do-daemon-setup)
  (add-hook! 'server-after-make-frame-hook (switch-to-buffer +doom-dashboard-name)))

vterm

Vterm clearly wins the terminal war. Also doesn’t need much configuration out of the box, although the shell integration does. That currently exists in my dotfiles

Always compile

Fixes a weird bug with native-comp, and I don’t use guix anymore.

(setq vterm-always-compile-module t)

Kill buffer

If the process exits, kill the vterm buffer

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

Fix c-backspace

I’ve picked this up in muscle memory now and I’m fed up with it not working. Not anymore!

(after! vterm
  (define-key vterm-mode-map (kbd "<C-backspace>") (lambda () (interactive) (vterm-send-key (kbd "C-w")))))

Functions

Useful functions for the shell-side integration provided by vterm.

(after! vterm
  (setf (alist-get "magit-status" vterm-eval-cmds nil nil #'equal)
        '((lambda (path)
            (magit-status path)))))

Keybindings

It’s not a custom config without some fancy keybinds

Save

Back to a simpler time…

(map! :g "C-s" #'save-buffer)

Search

Swiper Consult is much better than isearch

(map! :after evil :gnvi "C-f" #'consult-line)

Dired

Dired should behave better with evil mappings

(map! :map dired-mode-map
      :n "h" #'dired-up-directory
      :n "l" #'dired-find-alternate-file)

Graphical setup

which-key

Remove some of the useless evil- prefixes from which-key commands.

(setq which-key-allow-multiple-replacements t)
(after! which-key
  (pushnew!
   which-key-replacement-alist
   '(("" . "\\`+?evil[-:]?\\(?:a-\\)?\\(.*\\)") . (nil . "\\1"))
   '(("\\`g s" . "\\`evilem--?motion-\\(.*\\)") . (nil . "\\1"))))

Marginalia

Marginalia is part of the Vertico stack, and is responsible for all the fancy faces and extra information.

Files

The doom module out of the box includes a number of customizations, but the below from Teco gives a much better experience for files.

(after! marginalia
  (setq marginalia-censor-variables nil)

  (defadvice! +marginalia--anotate-local-file-colorful (cand)
    "Just a more colourful version of `marginalia--anotate-local-file'."
    :override #'marginalia--annotate-local-file
    (when-let (attrs (file-attributes (substitute-in-file-name
                                       (marginalia--full-candidate cand))
                                      'integer))
      (marginalia--fields
       ((marginalia--file-owner attrs)
        :width 12 :face 'marginalia-file-owner)
       ((marginalia--file-modes attrs))
       ((+marginalia-file-size-colorful (file-attribute-size attrs))
        :width 7)
       ((+marginalia--time-colorful (file-attribute-modification-time attrs))
        :width 12))))

  (defun +marginalia--time-colorful (time)
    (let* ((seconds (float-time (time-subtract (current-time) time)))
           (color (doom-blend
                   (face-attribute 'marginalia-date :foreground nil t)
                   (face-attribute 'marginalia-documentation :foreground nil t)
                   (/ 1.0 (log (+ 3 (/ (+ 1 seconds) 345600.0)))))))
      ;; 1 - log(3 + 1/(days + 1)) % grey
      (propertize (marginalia--time time) 'face (list :foreground color))))

  (defun +marginalia-file-size-colorful (size)
    (let* ((size-index (/ (log10 (+ 1 size)) 7.0))
           (color (if (< size-index 10000000) ; 10m
                      (doom-blend 'orange 'green size-index)
                    (doom-blend 'red 'orange (- size-index 1)))))
      (propertize (file-size-human-readable size) 'face (list :foreground color)))))

Info pages

Slightly improve the look and feel of Info pages, might actually encourage me to read them.

(package! info-colors)
(use-package! info-colors
  :after info
  :commands (info-colors-fontify-node)
  :hook (Info-selection . info-colors-fontify-node))

Dashboard

Inhibit the menu to improve things slightly

(remove-hook '+doom-dashboard-functions #'doom-dashboard-widget-shortmenu)
(remove-hook '+doom-dashboard-functions #'doom-dashboard-widget-footer)

Tab-bar-mode

Acting as a global modeline (of sorts), this could end up being quite useful but for now this is just testing.

(defun reaper-get-running-timer-duration ()
  "Return duration of current running timer."
  (reaper-with-buffer
   (when reaper-running-timer
     (reaper--hours-to-time
      (let ((entry (assoc reaper-running-timer reaper-timeentries)))
        (cdr (assoc :hours entry)))))))

(defun reaper-modeline-vanilla ()
  "Return modeline string for vanilla emacs."
  reaper-modeline--output)

(defvar reaper-modeline--timer nil)
(defun reaper-modeline-timer ()
  "Start/stop the time for updating reaper doom-modeline segment."
  (if (timerp reaper-modeline--timer)
      (cancel-timer reaper-modeline--timer))
  (setq reaper-modeline--timer
        (run-with-timer
         1
         60
         #'reaper-modeline--update)))

(after! reaper
  (reaper-modeline-timer))

(defun reaper-modeline--update ()
  "Update the status for the reaper modeline segment."
  (async-start
   `(lambda ()
      ,(async-inject-variables "\\`\\(load-path\\|reaper-api-key\\|reaper-account-id\\)\\'")
      (defalias 'reaper-get-running-timer-duration ,(symbol-function 'reaper-get-running-timer-duration))
      (require 'reaper)
      (reaper-with-buffer
       (setq reaper-timeentries nil)
       (reaper-refresh-entries))
      (when-let ((note (reaper-get-running-timer-note))
                 (timer (reaper-get-running-timer-duration)))
        (format " [ %s | %s] " (string-trim note) timer)))
   (lambda (result)
     (setq reaper-modeline--output result))))

(add-to-list 'global-mode-string '(:eval (reaper-modeline-vanilla)))
(customize-set-variable 'tab-bar-format '(tab-bar-format-global))
(customize-set-variable 'tab-bar-mode t)
(when tab-bar-mode
  (setq display-time-format " [ %H:%M %d/%m/%y]"
        display-time-default-load-average nil)
  (display-time-mode 1))

Modeline

Default modeline is a tad cluttered, and because I don’t use exwm anymore the modeline from that module isn’t in use. So, it’s duplicated here and tweaked.

;; (package! doom-modeline-now-playing)
(after! doom-modeline
  ;; (use-package! doom-modeline-now-playing
  ;;   :config
  ;;   (doom-modeline-now-playing-timer))
  (setq all-the-icons-scale-factor 1.1
        auto-revert-check-vc-info t
        doom-modeline-major-mode-icon (display-graphic-p)
        doom-modeline-major-mode-color-icon (display-graphic-p)
        doom-modeline-buffer-file-name-style 'relative-to-project
        doom-modeline-vcs-max-length 60)
  (remove-hook 'doom-modeline-mode-hook #'size-indication-mode)
  (doom-modeline-def-modeline 'main
    '(bar modals workspace-name window-number persp-name buffer-info matches remote-host github debug)
    '(vcs github mu4e grip gnus checker misc-info repl lsp " ")))

Fonts

Configure the fonts across all used platforms (slightly different names).

(setq  doom-font (font-spec :family "Iosevka Nerd Font Mono" :size 13)
       doom-variable-pitch-font (font-spec :family "Overpass" :size 13)
       doom-emoji-fallback-font-families nil
       doom-symbol-fallback-font-families nil)

Theme

Load my current flavour-of-the-month colour scheme.

(setq doom-theme 'doom-nord)

Along with a few face overrides (thought about merging upstream but it would have sparked a discussion, maybe later)

(custom-theme-set-faces! 'doom-nord
  `(php-class :foreground ,(doom-color 'blue))
  `(php-php-tag :foreground ,(doom-color 'blue))
  `(php-constant :foreground ,(doom-color 'violet))
  `(php-magical-constant :foreground ,(doom-color 'orange))
  `(php-operator :foreground ,(doom-color 'blue))
  `(php-doc-$this :foreground ,(doom-color 'cyan))
  `(php-object-op :foreground ,(doom-color 'cyan))
  `(php-string-op :foreground ,(doom-color 'blue))
  `(php-static-method-call :foreground ,(doom-color 'magenta))
  `(php-method-call :foreground ,(doom-color 'magenta))
  `(php-function-name :foreground ,(doom-lighten 'magenta 0.3)))

Change the default banner (need to add the ASCII banner at some point)

(setq +doom-dashboard-banner-file (expand-file-name "images/banner.png" doom-private-dir))

Line Numbers

Set the default line number format to be relative and disable line numbers for specific modes

(setq display-line-numbers-type 'relative)

(dolist (mode '(org-mode-hook
                term-mode-hook
                shell-mode-hook
                eshell-mode-hook))
  (add-hook mode (lambda () (display-line-numbers-mode 0))))

GUI/Frame

Maximise emacs on startup

(add-to-list 'default-frame-alist '(fullscreen . maximized))

Add some transparency

(after! exwm
  (set-frame-parameter (selected-frame) 'alpha 90)
  (add-to-list 'default-frame-alist '(alpha . 90)))

Org Mode

Hook setup

org-mode is a wonderful thing, and far too complex to bury in another section. The more I use it, the more I will add to this area but for now it’s mostly used for documentation and organisation.

(defun elken/org-setup-hook ()
  "Modes to enable on org-mode start"
  (org-indent-mode)
  (visual-line-mode 1)
  (org-appear-mode)
  (elken/org-font-setup))

(add-hook! org-mode #'elken/org-setup-hook)

org-directory

Let’s set a sane default directory based on where I am

(setq org-directory "~/Nextcloud/org")

Font setup

Font setup to prettify the fonts. Uses Overpass in most places except where it makes sense to use the defined fixed width font.

(defun elken/org-font-setup ()
  ;; Set faces for heading levels
  (font-lock-add-keywords 'org-mode
                          '((":\\(@[^\:]+\\):" (1 'doom-modeline-bar-inactive))))
  (dolist (face '((org-level-1 . 1.2)
                  (org-level-2 . 1.1)
                  (org-level-3 . 1.05)
                  (org-level-4 . 1.0)
                  (org-level-5 . 1.1)
                  (org-level-6 . 1.1)
                  (org-level-7 . 1.1)
                  (org-level-8 . 1.1)))
    (set-face-attribute (car face) nil :font "Overpass" :weight 'regular :height (cdr face)))

  ;; Ensure that anything that should be fixed-pitch in Org files appears that way
  (set-face-attribute 'org-tag nil :foreground nil :inherit '(shadow fixed-pitch) :weight 'bold)
  (set-face-attribute 'org-block nil :foreground nil :inherit 'fixed-pitch)
  (set-face-attribute 'org-code nil   :inherit '(shadow fixed-pitch))
  (set-face-attribute 'org-table nil   :inherit '(shadow fixed-pitch))
  (set-face-attribute 'org-verbatim nil :inherit '(shadow fixed-pitch))
  (set-face-attribute 'org-special-keyword nil :inherit '(font-lock-comment-face fixed-pitch))
  (set-face-attribute 'org-meta-line nil :inherit '(font-lock-comment-face fixed-pitch))
  (set-face-attribute 'org-checkbox nil :inherit 'fixed-pitch))

Heading minimap

Outline structure of the org documents, apparently it has cool things for promoting from it.

(package! org-ol-tree
  :recipe (:host github :repo "Townk/org-ol-tree"))
(use-package! org-ol-tree
  :commands org-ol-tree)

(map! :map org-mode-map
      :after org
      :localleader
      :desc "Outline" "O" #'org-ol-tree)

Tables

Improve tables by using unicode box characters intead of boring ascii.

(package! org-pretty-table
  :recipe (:host github :repo "Fuco1/org-pretty-table"))
(use-package! org-pretty-table
  :after org
  :hook (org-mode . org-pretty-table-mode))

Properties

Allow property inheritance

This may be the solution to so many weird issues with src blocks.

(setq org-use-property-inheritance t)

Characters

Headline bullets

(setq org-superstar-headline-bullets-list '(""))

Item bullets

(setq org-superstar-item-bullet-alist '((?* . ?⋆)
                                        (?+ . ?‣)
                                        (?- . ?•)))

Dropdown icon

(setq org-ellipsis "")

Remove excess emphasis markers

(setq org-hide-emphasis-markers t)

Show real entities rather than UTF8

(setq org-pretty-entities t)

Keywords

Default keywords are far too minimal. This will need further tweaking as I start using org mode for organisation more.

(after! org
  (setq org-todo-keywords
        '((sequence "TODO(t)" "NEXT(n)" "PROJ(p)" "STORY(s)" "WAIT(w)" "HOLD(h)" "|" "DONE(d)" "KILL(k)")
          (sequence "[ ](T)" "[-](S)" "[?](W)" "|" "[X](D)"))))

Agenda/Log

Show DONE tasks in agenda

(setq org-agenda-start-with-log-mode t)

Timestamp done items

(setq org-log-done 'time)

Log items in the drawer

(setq org-log-into-drawer t)

Cycle

Cycle by default (no idea why this isn’t default)

(setq org-cycle-emulate-tab nil)

Folding

Default folding is very noisy, I rarely need to see everything expanded

(setq org-startup-folded 'content)

Org-appear

Defines a minor mode to allow special forms such as italics, bold, underline and literal to be editable when the cursor is over them, otherwise display the proper value.

(package! org-appear
  :recipe (:host github :repo "awth13/org-appear"))
(use-package! org-appear
  :after org
  :hook (org-mode . org-appear-mode)
  :config
  (setq org-appear-autoemphasis t
        org-appear-autolinks t
        org-appear-autosubmarkers t))

Mixed pitch

Enable mixed-pitch-mode to enable the more readable fonts where it makes sense.

(defvar elken/mixed-pitch-modes '(org-mode LaTeX-mode markdown-mode gfm-mode Info-mode)
  "Only use `mixed-pitch-mode' for given modes.")

(defun init-mixed-pitch-h ()
  "Hook `mixed-pitch-mode' into each mode of `elken/mixed-pitch-modes'"
  (when (memq major-mode elken/mixed-pitch-modes)
    (mixed-pitch-mode 1))
  (dolist (hook elken/mixed-pitch-modes)
    (add-hook (intern (concat (symbol-name hook) "-hook")) #'mixed-pitch-mode)))

(add-hook 'doom-init-ui-hook #'init-mixed-pitch-h)

Archive/Cleanup

Adjust the format of archived org files (so they don’t show up in orgzly)

(setq org-archive-location "archive/Archive_%s::")

Archive DONE tasks

Enables archiving of tasks. Replaces the in-built version which only works for single tasks.

(defun elken/org-archive-done-tasks ()
  "Attempt to archive all done tasks in file"
  (interactive)
  (org-map-entries
   (lambda ()
     (org-archive-subtree)
     (setq org-map-continue-from (org-element-property :begin (org-element-at-point))))
   "/DONE" 'file))

(map! :map org-mode-map :desc "Archive tasks marked DONE" "C-c DEL a" #'elken/org-archive-done-tasks)

Remove KILL tasks

Enables removal of killed tasks. I’m not yet interested in tracking this long-term.

(defun elken/org-remove-kill-tasks ()
  (interactive)
  (org-map-entries
   (lambda ()
     (org-cut-subtree)
     (pop kill-ring)
     (setq org-map-continue-from (org-element-property :begin (org-element-at-point))))
   "/KILL" 'file))

(map! :map org-mode-map :desc "Remove tasks marked as KILL" "C-c DEL k" #'elken/org-remove-kill-tasks)

Show images

Show images inline by default

(setq org-startup-with-inline-images t)

But also, adjust them to an appropriate size. This should be adjusted to handle better resolutions.

(setq org-image-actual-width 600)

Autoexecute tangled shell files

Make tangled shell files executable (I trust myself, ish…)

(defun elken/make-tangled-shell-executable ()
  "Ensure that tangled shell files are executable"
  (set-file-modes (buffer-file-name) #o755))

(add-hook 'org-babel-post-tangle-hook 'elken/make-tangled-shell-executable)

Weblog setup

Useful settings and functions for firn and other blogging tools

Testing out weblorg, might encourage me to finally finish my site revamp…

(package! weblorg)
(use-package! weblorg
  :after org)
(setq enable-dir-local-variables t)
(defun elken/find-time-property (property)
  "Find the PROPETY in the current buffer."
  (save-excursion
    (goto-char (point-min))
    (let ((first-heading
           (save-excursion
             (re-search-forward org-outline-regexp-bol nil t))))
      (when (re-search-forward (format "^#\\+%s:" property) nil t)
        (point)))))

(defun elken/has-time-property-p (property)
  "Gets the position of PROPETY if it exists, nil if not and empty string if it's undefined."
  (when-let ((pos (elken/find-time-property property)))
    (save-excursion
      (goto-char pos)
      (if (and (looking-at-p " ")
               (progn (forward-char)
                      (org-at-timestamp-p 'lax)))
          pos
        ""))))

(defun elken/set-time-property (property &optional pos)
  "Set the PROPERTY in the current buffer.
Can pass the position as POS if already computed."
  (when-let ((pos (or pos (elken/find-time-property property))))
    (save-excursion
      (goto-char pos)
      (if (looking-at-p " ")
          (forward-char)
        (insert " "))
      (delete-region (point) (line-end-position))
      (let* ((now (format-time-string "<%Y-%m-%d %H:%M>")))
        (insert now)))))

(add-hook! 'before-save-hook (when (derived-mode-p 'org-mode)
                               (elken/set-time-property "LAST_MODIFIED")
                               (elken/set-time-property "DATE_UPDATED")))

Better snippets

Programmers are, by design, lazy

(use-package! org-tempo
  :after org
  :init
  (add-to-list 'org-structure-template-alist '("sh" . "src shell"))
  (add-to-list 'org-structure-template-alist '("el" . "src emacs-lisp")))

Roam

Let’s jump on the bandwagon and start taking useful notes.

(setq org-roam-directory (expand-file-name "roam" org-directory))

Templates

(after! org-roam
  (setq org-roam-capture-templates
        `(("d" "default" plain
           (file ,(expand-file-name "templates/roam-default.org" doom-private-dir))
           :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "")
           :unnarrowed t))))

Capture

It’s about time I start using org-capture, but because I’m a developer I’m inhernetly lazy so time to steal from other people.

Useful wrapper package for creating more declarative templates

(package! doct)
(use-package! doct
  :defer t
  :commands (doct))

Prettify

Improve the look of the capture dialog (idea borrowed from tecosaur)

(defun org-capture-select-template-prettier (&optional keys)
  "Select a capture template, in a prettier way than default
Lisp programs can force the template by setting KEYS to a string."
  (let ((org-capture-templates
         (or (org-contextualize-keys
              (org-capture-upgrade-templates org-capture-templates)
              org-capture-templates-contexts)
             '(("t" "Task" entry (file+headline "" "Tasks")
                "* TODO %?\n  %u\n  %a")))))
    (if keys
        (or (assoc keys org-capture-templates)
            (error "No capture template referred to by \"%s\" keys" keys))
      (org-mks org-capture-templates
               "Select a capture template\n━━━━━━━━━━━━━━━━━━━━━━━━━"
               "Template key: "
               `(("q" ,(concat (all-the-icons-octicon "stop" :face 'all-the-icons-red :v-adjust 0.01) "\tAbort")))))))
(advice-add 'org-capture-select-template :override #'org-capture-select-template-prettier)

(defun org-mks-pretty (table title &optional prompt specials)
  "Select a member of an alist with multiple keys. Prettified.

TABLE is the alist which should contain entries where the car is a string.
There should be two types of entries.

1. prefix descriptions like (\"a\" \"Description\")
   This indicates that `a' is a prefix key for multi-letter selection, and
   that there are entries following with keys like \"ab\", \"ax\"

2. Select-able members must have more than two elements, with the first
   being the string of keys that lead to selecting it, and the second a
   short description string of the item.

The command will then make a temporary buffer listing all entries
that can be selected with a single key, and all the single key
prefixes.  When you press the key for a single-letter entry, it is selected.
When you press a prefix key, the commands (and maybe further prefixes)
under this key will be shown and offered for selection.

TITLE will be placed over the selection in the temporary buffer,
PROMPT will be used when prompting for a key.  SPECIALS is an
alist with (\"key\" \"description\") entries.  When one of these
is selected, only the bare key is returned."
  (save-window-excursion
    (let ((inhibit-quit t)
          (buffer (org-switch-to-buffer-other-window "*Org Select*"))
          (prompt (or prompt "Select: "))
          case-fold-search
          current)
      (unwind-protect
          (catch 'exit
            (while t
              (setq-local evil-normal-state-cursor (list nil))
              (erase-buffer)
              (insert title "\n\n")
              (let ((des-keys nil)
                    (allowed-keys '("\C-g"))
                    (tab-alternatives '("\s" "\t" "\r"))
                    (cursor-type nil))
                ;; Populate allowed keys and descriptions keys
                ;; available with CURRENT selector.
                (let ((re (format "\\`%s\\(.\\)\\'"
                                  (if current (regexp-quote current) "")))
                      (prefix (if current (concat current " ") "")))
                  (dolist (entry table)
                    (pcase entry
                      ;; Description.
                      (`(,(and key (pred (string-match re))) ,desc)
                       (let ((k (match-string 1 key)))
                         (push k des-keys)
                         ;; Keys ending in tab, space or RET are equivalent.
                         (if (member k tab-alternatives)
                             (push "\t" allowed-keys)
                           (push k allowed-keys))
                         (insert (propertize prefix 'face 'font-lock-comment-face) (propertize k 'face 'bold) (propertize "" 'face 'font-lock-comment-face) "  " desc "" "\n")))
                      ;; Usable entry.
                      (`(,(and key (pred (string-match re))) ,desc . ,_)
                       (let ((k (match-string 1 key)))
                         (insert (propertize prefix 'face 'font-lock-comment-face) (propertize k 'face 'bold) "   " desc "\n")
                         (push k allowed-keys)))
                      (_ nil))))
                ;; Insert special entries, if any.
                (when specials
                  (insert "─────────────────────────\n")
                  (pcase-dolist (`(,key ,description) specials)
                    (insert (format "%s   %s\n" (propertize key 'face '(bold all-the-icons-red)) description))
                    (push key allowed-keys)))
                ;; Display UI and let user select an entry or
                ;; a sub-level prefix.
                (goto-char (point-min))
                (unless (pos-visible-in-window-p (point-max))
                  (org-fit-window-to-buffer))
                (let ((pressed (org--mks-read-key allowed-keys prompt nil)))
                  (setq current (concat current pressed))
                  (cond
                   ((equal pressed "\C-g") (user-error "Abort"))
                   ((equal pressed "ESC") (user-error "Abort"))
                   ;; Selection is a prefix: open a new menu.
                   ((member pressed des-keys))
                   ;; Selection matches an association: return it.
                   ((let ((entry (assoc current table)))
                      (and entry (throw 'exit entry))))
                   ;; Selection matches a special entry: return the
                   ;; selection prefix.
                   ((assoc current specials) (throw 'exit current))
                   (t (error "No entry available")))))))
        (when buffer (kill-buffer buffer))))))
(advice-add 'org-mks :override #'org-mks-pretty)

The org-capture bin is rather nice, but I’d be nicer with a smaller frame, and no modeline.

(setf (alist-get 'height +org-capture-frame-parameters) 15)
;; (alist-get 'name +org-capture-frame-parameters) "❖ Capture") ;; ATM hardcoded in other places, so changing breaks stuff
(setq +org-capture-fn
      (lambda ()
        (interactive)
        (set-window-parameter nil 'mode-line-format 'none)
        (org-capture)))

Sprinkle in some doct utility functions

(defun +doct-icon-declaration-to-icon (declaration)
  "Convert :icon declaration to icon"
  (let ((name (pop declaration))
        (set  (intern (concat "all-the-icons-" (plist-get declaration :set))))
        (face (intern (concat "all-the-icons-" (plist-get declaration :color))))
        (v-adjust (or (plist-get declaration :v-adjust) 0.01)))
    (apply set `(,name :face ,face :v-adjust ,v-adjust))))

(defun +doct-iconify-capture-templates (groups)
  "Add declaration's :icon to each template group in GROUPS."
  (let ((templates (doct-flatten-lists-in groups)))
    (setq doct-templates (mapcar (lambda (template)
                                   (when-let* ((props (nthcdr (if (= (length template) 4) 2 5) template))
                                               (spec (plist-get (plist-get props :doct) :icon)))
                                     (setf (nth 1 template) (concat (+doct-icon-declaration-to-icon spec)
                                                                    "\t"
                                                                    (nth 1 template))))
                                   template)
                                 templates))))

(setq doct-after-conversion-functions '(+doct-iconify-capture-templates))

Templates

And we can now add some templates! This isn’t even remotely set in stone, I wouldn’t even describe them as set in jelly really.

(after! org-capture
  (setq org-capture-templates
        (doct `(("Home" :keys "h"
                 :icon ("home" :set "octicon" :color "cyan")
                 :file "Home.org"
                 :prepend t
                 :headline "Inbox"
                 :template ("* TODO %?"
                            "%i %a"))
                ("Work" :keys "w"
                 :icon ("business" :set "material" :color "yellow")
                 :file "Work.org"
                 :prepend t
                 :headline "Inbox"
                 :template ("* TODO %?"
                            "SCHEDULED: %^{Schedule:}t"
                            "DEADLINE: %^{Deadline:}t"
                            "%i %a"))
                ("Note" :keys "n"
                 :icon ("sticky-note" :set "faicon" :color "yellow")
                 :file "Notes.org"
                 :template ("* *?"
                            "%i %a"))
                ("Project" :keys "p"
                 :icon ("repo" :set "octicon" :color "silver")
                 :prepend t
                 :type entry
                 :headline "Inbox"
                 :template ("* %{keyword} %?"
                            "%i"
                            "%a")
                 :file ""
                 :custom (:keyword "")
                 :children (("Task" :keys "t"
                             :icon ("checklist" :set "octicon" :color "green")
                             :keyword "TODO"
                             :file +org-capture-project-todo-file)
                            ("Note" :keys "n"
                             :icon ("sticky-note" :set "faicon" :color "yellow")
                             :keyword "%U"
                             :file +org-capture-project-notes-file)))
                ))))

Export

LaTeX

A necessary evil. I hate it, it hates me, but it makes my PDF documents look nice.

Preambles

Various preamble setups to improve the overall look of several items

(defvar org-latex-caption-preamble "
\\usepackage{subcaption}
\\usepackage[hypcap=true]{caption}
\\setkomafont{caption}{\\sffamily\\small}
\\setkomafont{captionlabel}{\\upshape\\bfseries}
\\captionsetup{justification=raggedright,singlelinecheck=true}
\\usepackage{capt-of} % required by Org
"
  "Preamble that improves captions.")

(defvar org-latex-checkbox-preamble "
\\newcommand{\\checkboxUnchecked}{$\\square$}
\\newcommand{\\checkboxTransitive}{\\rlap{\\raisebox{-0.1ex}{\\hspace{0.35ex}\\Large\\textbf -}}$\\square$}
\\newcommand{\\checkboxChecked}{\\rlap{\\raisebox{0.2ex}{\\hspace{0.35ex}\\scriptsize \\ding{52}}}$\\square$}
"
  "Preamble that improves checkboxes.")

(defvar org-latex-box-preamble "
% args = #1 Name, #2 Colour, #3 Ding, #4 Label
\\newcommand{\\defsimplebox}[4]{%
  \\definecolor{#1}{HTML}{#2}
  \\newenvironment{#1}[1][]
  {%
    \\par\\vspace{-0.7\\baselineskip}%
    \\textcolor{#1}{#3} \\textcolor{#1}{\\textbf{\\def\\temp{##1}\\ifx\\temp\\empty#4\\else##1\\fi}}%
    \\vspace{-0.8\\baselineskip}
    \\begin{addmargin}[1em]{1em}
  }{%
    \\end{addmargin}
    \\vspace{-0.5\\baselineskip}
  }%
}
"
  "Preamble that provides a macro for custom boxes.")

Conditional features

Don’t always need everything in LaTeX, so only add it what we need when we need it.

(defvar org-latex-italic-quotes t
  "Make \"quote\" environments italic.")
(defvar org-latex-par-sep t
  "Vertically seperate paragraphs, and remove indentation.")

(defvar org-latex-conditional-features
  '(("\\[\\[\\(?:file\\|https?\\):\\(?:[^]]\\|\\\\\\]\\)+?\\.\\(?:eps\\|pdf\\|png\\|jpeg\\|jpg\\|jbig2\\)\\]\\]" . image)
    ("\\[\\[\\(?:file\\|https?\\):\\(?:[^]]+?\\|\\\\\\]\\)\\.svg\\]\\]\\|\\\\includesvg" . svg)
    ("^[ \t]*|" . table)
    ("cref:\\|\\cref{\\|\\[\\[[^\\]]+\\]\\]" . cleveref)
    ("[;\\\\]?\\b[A-Z][A-Z]+s?[^A-Za-z]" . acronym)
    ("\\+[^ ].*[^ ]\\+\\|_[^ ].*[^ ]_\\|\\\\uu?line\\|\\\\uwave\\|\\\\sout\\|\\\\xout\\|\\\\dashuline\\|\\dotuline\\|\\markoverwith" . underline)
    (":float wrap" . float-wrap)
    (":float sideways" . rotate)
    ("^[ \t]*#\\+caption:\\|\\\\caption" . caption)
    ("\\[\\[xkcd:" . (image caption))
    ((and org-latex-italic-quotes "^[ \t]*#\\+begin_quote\\|\\\\begin{quote}") . italic-quotes)
    (org-latex-par-sep . par-sep)
    ("^[ \t]*\\(?:[-+*]\\|[0-9]+[.)]\\|[A-Za-z]+[.)]\\) \\[[ -X]\\]" . checkbox)
    ("^[ \t]*#\\+begin_warning\\|\\\\begin{warning}" . box-warning)
    ("^[ \t]*#\\+begin_info\\|\\\\begin{info}"       . box-info)
    ("^[ \t]*#\\+begin_success\\|\\\\begin{success}" . box-success)
    ("^[ \t]*#\\+begin_error\\|\\\\begin{error}"     . box-error))
  "Org feature tests and associated LaTeX feature flags.

Alist where the car is a test for the presense of the feature,
and the cdr is either a single feature symbol or list of feature symbols.

When a string, it is used as a regex search in the buffer.
The feature is registered as present when there is a match.

The car can also be a
- symbol, the value of which is fetched
- function, which is called with info as an argument
- list, which is `eval'uated

If the symbol, function, or list produces a string: that is used as a regex
search in the buffer. Otherwise any non-nil return value will indicate the
existance of the feature.")

(defvar org-latex-feature-implementations
  '((image         :snippet "\\usepackage{graphicx}" :order 2)
    (svg           :snippet "\\usepackage{svg}" :order 2)
    (table         :snippet "\\usepackage{longtable}\n\\usepackage{booktabs}" :order 2)
    (cleveref      :snippet "\\usepackage[capitalize]{cleveref}" :order 1)
    (underline     :snippet "\\usepackage[normalem]{ulem}" :order 0.5)
    (float-wrap    :snippet "\\usepackage{wrapfig}" :order 2)
    (rotate        :snippet "\\usepackage{rotating}" :order 2)
    (caption       :snippet org-latex-caption-preamble :order 2.1)
    (acronym       :snippet "\\newcommand{\\acr}[1]{\\protect\\textls*[110]{\\scshape #1}}\n\\newcommand{\\acrs}{\\protect\\scalebox{.91}[.84]{\\hspace{0.15ex}s}}" :order 0.4)
    (italic-quotes :snippet "\\renewcommand{\\quote}{\\list{}{\\rightmargin\\leftmargin}\\item\\relax\\em}\n" :order 0.5)
    (par-sep       :snippet "\\setlength{\\parskip}{\\baselineskip}\n\\setlength{\\parindent}{0pt}\n" :order 0.5)
    (.pifont       :snippet "\\usepackage{pifont}")
    (checkbox      :requires .pifont :order 3
                   :snippet (concat (unless (memq 'maths features)
                                      "\\usepackage{amssymb} % provides \\square")
                                    org-latex-checkbox-preamble))
    (.fancy-box    :requires .pifont    :snippet org-latex-box-preamble :order 3.9)
    (box-warning   :requires .fancy-box :snippet "\\defsimplebox{warning}{e66100}{\\ding{68}}{Warning}" :order 4)
    (box-info      :requires .fancy-box :snippet "\\defsimplebox{info}{3584e4}{\\ding{68}}{Information}" :order 4)
    (box-success   :requires .fancy-box :snippet "\\defsimplebox{success}{26a269}{\\ding{68}}{\\vspace{-\\baselineskip}}" :order 4)
    (box-error     :requires .fancy-box :snippet "\\defsimplebox{error}{c01c28}{\\ding{68}}{Important}" :order 4))
  "LaTeX features and details required to implement them.

List where the car is the feature symbol, and the rest forms a plist with the
following keys:
- :snippet, which may be either
  - a string which should be included in the preamble
  - a symbol, the value of which is included in the preamble
  - a function, which is evaluated with the list of feature flags as its
    single argument. The result of which is included in the preamble
  - a list, which is passed to `eval', with a list of feature flags available
    as \"features\"

- :requires, a feature or list of features that must be available
- :when, a feature or list of features that when all available should cause this
    to be automatically enabled.
- :prevents, a feature or list of features that should be masked
- :order, for when ordering is important. Lower values appear first.
    The default is 0.

Features that start with ! will be eagerly loaded, i.e. without being detected.")

First, we need to detect which features we actually need

(defun org-latex-detect-features (&optional buffer info)
  "List features from `org-latex-conditional-features' detected in BUFFER."
  (let ((case-fold-search nil))
    (with-current-buffer (or buffer (current-buffer))
      (delete-dups
       (mapcan (lambda (construct-feature)
                 (when (let ((out (pcase (car construct-feature)
                                    ((pred stringp) (car construct-feature))
                                    ((pred functionp) (funcall (car construct-feature) info))
                                    ((pred listp) (eval (car construct-feature)))
                                    ((pred symbolp) (symbol-value (car construct-feature)))
                                    (_ (user-error "org-latex-conditional-features key %s unable to be used" (car construct-feature))))))
                         (if (stringp out)
                             (save-excursion
                               (goto-char (point-min))
                               (re-search-forward out nil t))
                           out))
                   (if (listp (cdr construct-feature)) (cdr construct-feature) (list (cdr construct-feature)))))
               org-latex-conditional-features)))))

Then we need to expand them and sort them according to the above definitions

(defun org-latex-expand-features (features)
  "For each feature in FEATURES process :requires, :when, and :prevents keywords and sort according to :order."
  (dolist (feature features)
    (unless (assoc feature org-latex-feature-implementations)
      (error "Feature %s not provided in org-latex-feature-implementations" feature)))
  (setq current features)
  (while current
    (when-let ((requirements (plist-get (cdr (assq (car current) org-latex-feature-implementations)) :requires)))
      (setcdr current (if (listp requirements)
                          (append requirements (cdr current))
                        (cons requirements (cdr current)))))
    (setq current (cdr current)))
  (dolist (potential-feature
           (append features (delq nil (mapcar (lambda (feat)
                                                (when (plist-get (cdr feat) :eager)
                                                  (car feat)))
                                              org-latex-feature-implementations))))
    (when-let ((prerequisites (plist-get (cdr (assoc potential-feature org-latex-feature-implementations)) :when)))
      (setf features (if (if (listp prerequisites)
                             (cl-every (lambda (preq) (memq preq features)) prerequisites)
                           (memq prerequisites features))
                         (append (list potential-feature) features)
                       (delq potential-feature features)))))
  (dolist (feature features)
    (when-let ((prevents (plist-get (cdr (assoc feature org-latex-feature-implementations)) :prevents)))
      (setf features (cl-set-difference features (if (listp prevents) prevents (list prevents))))))
  (sort (delete-dups features)
        (lambda (feat1 feat2)
          (if (< (or (plist-get (cdr (assoc feat1 org-latex-feature-implementations)) :order) 1)
                 (or (plist-get (cdr (assoc feat2 org-latex-feature-implementations)) :order) 1))
              t nil))))

Finally, we can create the preamble to be inserted

(defun org-latex-generate-features-preamble (features)
  "Generate the LaTeX preamble content required to provide FEATURES.
This is done according to `org-latex-feature-implementations'"
  (let ((expanded-features (org-latex-expand-features features)))
    (concat
     (format "\n%% features: %s\n" expanded-features)
     (mapconcat (lambda (feature)
                  (when-let ((snippet (plist-get (cdr (assoc feature org-latex-feature-implementations)) :snippet)))
                    (concat
                     (pcase snippet
                       ((pred stringp) snippet)
                       ((pred functionp) (funcall snippet features))
                       ((pred listp) (eval `(let ((features ',features)) (,@snippet))))
                       ((pred symbolp) (symbol-value snippet))
                       (_ (user-error "org-latex-feature-implementations :snippet value %s unable to be used" snippet)))
                     "\n")))
                expanded-features
                "")
     "% end features\n")))

Last step, some advice to hook in all of the above to work

(defvar info--tmp nil)

(defadvice! org-latex-save-info (info &optional t_ s_)
  :before #'org-latex-make-preamble
  (setq info--tmp info))

(defadvice! org-splice-latex-header-and-generated-preamble-a (orig-fn tpl def-pkg pkg snippets-p &optional extra)
  "Dynamically insert preamble content based on `org-latex-conditional-preambles'."
  :around #'org-splice-latex-header
  (let ((header (funcall orig-fn tpl def-pkg pkg snippets-p extra)))
    (if snippets-p header
      (concat header
              (org-latex-generate-features-preamble (org-latex-detect-features nil info--tmp))
              "\n"))))

Tectonic

Tectonic is the hot new thing, which also means I can get rid of my tex installation.

(setq-default org-latex-pdf-process '("tectonic -Z shell-escape --outdir=%o %f"))

Classes

Base

Simple base header shared by all defines classes

\\documentclass{scrartcl}
[PACKAGES]
[DEFAULT-PACKAGES]
[EXTRA]
\\setmainfont[Ligatures=TeX]{Overpass}
\\setmonofont[Ligatures=TeX]{Iosevka Nerd Font Mono}
chameleon

Template for chameleon documents.

% Using chameleon
<<base-template>>

Now for some class setup (likely to change over time)

(after! ox-latex
  (add-to-list 'org-latex-classes
               '("chameleon" "
<<chameleon-template>>
"
                 ("\\section{%s}" . "\\section*{%s}")
                 ("\\subsection{%s}" . "\\subsection*{%s}")
                 ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
                 ("\\paragraph{%s}" . "\\paragraph*{%s}")
                 ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))))

And some saner default for them

(after! ox-latex
  (setq org-latex-default-class "chameleon"
        org-latex-tables-booktabs t
        org-latex-hyperref-template "\\colorlet{greenyblue}{blue!70!green}
\\colorlet{blueygreen}{blue!40!green}
\\providecolor{link}{named}{greenyblue}
\\providecolor{cite}{named}{blueygreen}
\\hypersetup{
  pdfauthor={%a},
  pdftitle={%t},
  pdfkeywords={%k},
  pdfsubject={%d},
  pdfcreator={%c},
  pdflang={%L},
  breaklinks=true,
  colorlinks=true,
  linkcolor=,
  urlcolor=link,
  citecolor=cite\n}
\\urlstyle{same}
"
        org-latex-reference-command "\\cref{%s}"))

Packages

Add some packages (also very likely to change)

(setq org-latex-default-packages-alist
      `(("AUTO" "inputenc" t ("pdflatex"))
        ("T1" "fontenc" t ("pdflatex"))
        ("" "fontspec" t)
        ("" "xcolor" nil)
        ("" "hyperref" nil)))

Pretty code blocks

Teco is the goto for this, so basically just ripping off him.

(package! engrave-faces
  :recipe (:host github :repo "tecosaur/engrave-faces"))
(use-package! engrave-faces-latex
  :after ox-latex
  :config
  (setq org-latex-listings 'engraved))
(defadvice! org-latex-src-block-engraved (orig-fn src-block contents info)
  "Like `org-latex-src-block', but supporting an engraved backend"
  :around #'org-latex-src-block
  (if (eq 'engraved (plist-get info :latex-listings))
      (org-latex-scr-block--engraved src-block contents info)
    (funcall orig-fn src-block contents info)))

(defadvice! org-latex-inline-src-block-engraved (orig-fn inline-src-block contents info)
  "Like `org-latex-inline-src-block', but supporting an engraved backend"
  :around #'org-latex-inline-src-block
  (if (eq 'engraved (plist-get info :latex-listings))
      (org-latex-inline-scr-block--engraved inline-src-block contents info)
    (funcall orig-fn src-block contents info)))

(defvar-local org-export-has-code-p nil)

(defadvice! org-export-expect-no-code (&rest _)
  :before #'org-export-as
  (setq org-export-has-code-p nil))

(defadvice! org-export-register-code (&rest _)
  :after #'org-latex-src-block-engraved
  :after #'org-latex-inline-src-block-engraved
  (setq org-export-has-code-p t))

(setq org-latex-engraved-code-preamble "
\\usepackage{fvextra}
\\fvset{
  commandchars=\\\\\\{\\},
  highlightcolor=white!95!black!80!blue,
  breaklines=true,
  breaksymbol=\\color{white!60!black}\\tiny\\ensuremath{\\hookrightarrow}}
\\renewcommand\\theFancyVerbLine{\\footnotesize\\color{black!40!white}\\arabic{FancyVerbLine}}

\\definecolor{codebackground}{HTML}{f7f7f7}
\\definecolor{codeborder}{HTML}{f0f0f0}

% TODO have code boxes keep line vertical alignment
\\usepackage[breakable,xparse]{tcolorbox}
\\DeclareTColorBox[]{Code}{o}%
{colback=codebackground, colframe=codeborder,
  fontupper=\\footnotesize,
  colupper=EFD,
  IfNoValueTF={#1}%
  {boxsep=2pt, arc=2.5pt, outer arc=2.5pt,
    boxrule=0.5pt, left=2pt}%
  {boxsep=2.5pt, arc=0pt, outer arc=0pt,
    boxrule=0pt, leftrule=1.5pt, left=0.5pt},
  right=2pt, top=1pt, bottom=0.5pt,
  breakable}
")

(add-to-list 'org-latex-conditional-features '((and org-export-has-code-p "^[ \t]*#\\+begin_src\\|^[ \t]*#\\+BEGIN_SRC\\|src_[A-Za-z]") . engraved-code) t)
(add-to-list 'org-latex-conditional-features '("^[ \t]*#\\+begin_example\\|^[ \t]*#\\+BEGIN_EXAMPLE" . engraved-code-setup) t)
(add-to-list 'org-latex-feature-implementations '(engraved-code :requires engraved-code-setup :snippet (engrave-faces-latex-gen-preamble) :order 99) t)
(add-to-list 'org-latex-feature-implementations '(engraved-code-setup :snippet org-latex-engraved-code-preamble :order 98) t)

(defun org-latex-scr-block--engraved (src-block contents info)
  (let* ((lang (org-element-property :language src-block))
         (attributes (org-export-read-attribute :attr_latex src-block))
         (float (plist-get attributes :float))
         (num-start (org-export-get-loc src-block info))
         (retain-labels (org-element-property :retain-labels src-block))
         (caption (org-element-property :caption src-block))
         (caption-above-p (org-latex--caption-above-p src-block info))
         (caption-str (org-latex--caption/label-string src-block info))
         (placement (or (org-unbracket-string "[" "]" (plist-get attributes :placement))
                        (plist-get info :latex-default-figure-position)))
         (float-env
          (cond
           ((string= "multicolumn" float)
            (format "\\begin{listing*}[%s]\n%s%%s\n%s\\end{listing*}"
                    placement
                    (if caption-above-p caption-str "")
                    (if caption-above-p "" caption-str)))
           (caption
            (format "\\begin{listing}[%s]\n%s%%s\n%s\\end{listing}"
                    placement
                    (if caption-above-p caption-str "")
                    (if caption-above-p "" caption-str)))
           ((string= "t" float)
            (concat (format "\\begin{listing}[%s]\n"
                            placement)
                    "%s\n\\end{listing}"))
           (t "%s")))
         (options (plist-get info :latex-minted-options))
         (content-buffer
          (with-temp-buffer
            (insert
             (let* ((code-info (org-export-unravel-code src-block))
                    (max-width
                     (apply 'max
                            (mapcar 'length
                                    (org-split-string (car code-info)
                                                      "\n")))))
               (org-export-format-code
                (car code-info)
                (lambda (loc _num ref)
                  (concat
                   loc
                   (when ref
                     ;; Ensure references are flushed to the right,
                     ;; separated with 6 spaces from the widest line
                     ;; of code.
                     (concat (make-string (+ (- max-width (length loc)) 6)
                                          ?\s)
                             (format "(%s)" ref)))))
                nil (and retain-labels (cdr code-info)))))
            (funcall (org-src-get-lang-mode lang))
            (engrave-faces-latex-buffer)))
         (content
          (with-current-buffer content-buffer
            (buffer-string)))
         (body
          (format
           "\\begin{Code}\n\\begin{Verbatim}[%s]\n%s\\end{Verbatim}\n\\end{Code}"
           ;; Options.
           (concat
            (org-latex--make-option-string
             (if (or (not num-start) (assoc "linenos" options))
                 options
               (append
                `(("linenos")
                  ("firstnumber" ,(number-to-string (1+ num-start))))
                options)))
            (let ((local-options (plist-get attributes :options)))
              (and local-options (concat "," local-options))))
           content)))
    (kill-buffer content-buffer)
    ;; Return value.
    (format float-env body)))

(defun org-latex-inline-scr-block--engraved (inline-src-block _contents info)
  (let ((options (org-latex--make-option-string
                  (plist-get info :latex-minted-options)))
        code-buffer code)
    (setq code-buffer
          (with-temp-buffer
            (insert (org-element-property :value inline-src-block))
            (funcall (org-src-get-lang-mode
                      (org-element-property :language inline-src-block)))
            (engrave-faces-latex-buffer)))
    (setq code (with-current-buffer code-buffer
                 (buffer-string)))
    (kill-buffer code-buffer)
    (format "\\Verb%s{%s}"
            (if (string= options "") ""
              (format "[%s]" options))
            code)))

(defadvice! org-latex-example-block-engraved (orig-fn example-block contents info)
  "Like `org-latex-example-block', but supporting an engraved backend"
  :around #'org-latex-example-block
  (let ((output-block (funcall orig-fn example-block contents info)))
    (if (eq 'engraved (plist-get info :latex-listings))
        (format "\\begin{Code}[alt]\n%s\n\\end{Code}" output-block)
      output-block)))

ox-chameleon

Chameleons are cool, not having to touches faces is cooler (not the COVID kind)

(package! ox-chameleon
  :recipe (:host github :repo "tecosaur/ox-chameleon"))
(use-package! ox-chameleon
  :after ox)

Async

We can do better. Override the built-in tangling for something more async-y (borrowed from Tecosaur)

(defvar +literate-tangle--proc nil)
(defvar +literate-tangle--proc-start-time nil)

(defadvice! +literate-tangle-async-h ()
  "A very simplified version of `+literate-tangle-h', but async."
  :override #'+literate-tangle-h
  (unless (getenv "__NOTANGLE")
    (let ((default-directory doom-private-dir))
      (when +literate-tangle--proc
        (message "Killing outdated tangle process...")
        (set-process-sentinel +literate-tangle--proc #'ignore)
        (kill-process +literate-tangle--proc)
        (sit-for 0.3)) ; ensure the message is seen for a bit
      (setq +literate-tangle--proc-start-time (float-time)
            +literate-tangle--proc
            (start-process "tangle-config"
                           (get-buffer-create " *tangle config*")
                           "emacs" "--batch" "--eval"
                           (format "(progn \
(require 'ox) \
(require 'ob-tangle) \
(setq org-confirm-babel-evaluate nil \
      org-inhibit-startup t \
      org-mode-hook nil \
      write-file-functions nil \
      before-save-hook nil \
      after-save-hook nil \
      vc-handled-backends nil \
      org-startup-folded nil \
      org-startup-indented nil) \
(org-babel-tangle-file \"%s\" \"%s\"))"
                                   +literate-config-file
                                   (expand-file-name (concat doom-module-config-file ".el")))))
      (set-process-sentinel +literate-tangle--proc #'+literate-tangle--sentinel)
      (run-at-time nil nil (lambda () (message "Tangling config.org"))) ; ensure shown after a save message
      "Tangling config.org...")))

(defun +literate-tangle--sentinel (process signal)
  (cond
   ((and (eq 'exit (process-status process))
         (= 0 (process-exit-status process)))
    (message "Tangled config.org sucessfully (took %.1fs)"
             (- (float-time) +literate-tangle--proc-start-time))
    (setq +literate-tangle--proc nil))
   ((memq (process-status process) (list 'exit 'signal))
    (+popup-buffer (get-buffer " *tangle config*"))
    (message "Failed to tangle config.org (after %.1fs)"
             (- (float-time) +literate-tangle--proc-start-time))
    (setq +literate-tangle--proc nil))))

(defun +literate-tangle-check-finished ()
  (when (and (process-live-p +literate-tangle--proc)
             (yes-or-no-p "Config is currently retangling, would you please wait a few seconds?"))
    (switch-to-buffer " *tangle config*")
    (signal 'quit nil)))
(add-hook! 'kill-emacs-hook #'+literate-tangle-check-finished)

Run export processes in a background … process

(setq org-export-in-background t)

(sub|super)script characters

Annoying having to gate these, so let’s fix that

(setq org-export-with-sub-superscripts '{})

Languages

Configuration for various programming languages.

Eglot

Resolve an issue between flymake version (#5645)

Should be removed after it’s merged, but for now it’s needed.

(after! (flymake)
  (when (and
         (not (fboundp 'flymake--diag-buffer))
         (fboundp 'flymake--diag-locus))
    (defalias 'flymake--diag-buffer 'flymake--diag-locus)))

F#

Should probably become a package at some point, for now it’s fine.

(load! "lisp/ob-fsharp.el")

C#

Projectile doesn’t recognise these projects properly, so we have to fix that

(after! projectile
  (defun projectile-dotnet-project-p ()
    "Check if a project contains a *.sln file at the project root, or either
a .csproj or .fsproj file at either the project root or within src/*/."
    (or (projectile-verify-file-wildcard "?*.sln")
        (projectile-verify-file-wildcard "?*.csproj")
        (projectile-verify-file-wildcard "src/*/?*.csproj")
        (projectile-verify-file-wildcard "?*.fsproj")
        (projectile-verify-file-wildcard "src/*/?*.fsproj"))))

ELISP

Finally get the completion in elisp working (not sure why it’s been so bad for so long…)

(set-company-backend! 'emacs-lisp-mode
  'company-capf 'company-yasnippet)

LSP/DAP

Increase variable line length

By default this is way too short.

(setq dap-ui-variable-length 200)

Ignore files in xref

PHP is a dumb language (don’t get me started…), as such we need extra files to get decent completion & documentation. 0% of the time will we want to use them as references, so we won’t.

(defvar xref-ignored-files '("_ide_helper_models.php" "_ide_helper.php")
  "List of files to be ignored by `xref'.")

(defun xref-ignored-file-p (item)
  "Return t if `item' should be ignored."
  (seq-some
   (lambda (cand)
     (string-suffix-p cand (oref (xref-item-location item) file))) xref-ignored-files))

(defadvice! +lsp--ignored-locations-to-xref-items-a (items)
  "Remove ignored files from list of xref-items."
  :filter-return #'lsp--locations-to-xref-items
  (cl-remove-if #'xref-ignored-file-p items))

(defadvice! +lsp-ui-peek--ignored-locations-a (items)
  "Remove ignored files from list of xref-items."
  :filter-return #'lsp-ui-peek--get-references
  (cl-remove-if #'xref-ignored-file-p items))

Improve completions

The default completions are quite bad

(after! lsp-mode
  (setq +lsp-company-backends
        '(:separate company-capf company-yasnippet company-dabbrev)))

Ignore directories

Add some extra ignored directories for +lsp.

(after! lsp-mode
  (add-to-list 'lsp-file-watch-ignored-directories "[/\\\\]\\vendor"))

And some more for projectile

(after! projectile
  (add-to-list 'projectile-globally-ignored-directories "vendor"))

PHP

Web-mode setup

(after! web-mode
  (pushnew! web-mode-engines-alist '(("blade"  . "\\.blade\\."))))

Intelephense

Because I’m a massive sellout who likes features

(after! eglot
  (setq lsp-intelephense-licence-key (fetch-auth-source :user "intelephense")))

Eglot

Trying out this eglot thing for a bit, let’s see how it goes.

Make sure it’s loaded in php-mode

(after! eglot
  (add-hook 'php-mode-hook 'eglot-ensure))

Set some config needed for the server

(when (featurep! :tools lsp +eglot)
  (defvar php-intelephense-storage-path (expand-file-name "lsp-intelephense" doom-etc-dir))
  (defvar php-intelephense-command (expand-file-name "lsp/npm/intelephense/bin/intelephense" doom-etc-dir)))

And set the server to be loaded

(after! eglot
  (defclass eglot-php (eglot-lsp-server) () :documentation "PHP's Intelephense")
  (cl-defmethod eglot-initialization-options ((server eglot-php))
    "Passes through required intelephense options"
    `(:storagePath ,php-intelephense-storage-path
      :licenceKey ,lsp-intelephense-licence-key
      :clearCache t))
  (add-to-list 'eglot-server-programs `((php-mode phps-mode) . (eglot-php . (,php-intelephense-command "--stdio")))))

Snippets

I constantly find myself complaining I don’t have snippets setup, and yet I always forget to set snippets up. My own worst enemy? Probably. But who’s keeping score…

First let’s define a useful helper for creating snippets based on the current heading.

Org-mode

__

# -*- mode: snippet -*-
# name: Org template
# --
#+title: ${1:`(s-titleized-words (replace-regexp-in-string "^[0-9]\\{4\\}-[0-9][0-9]-[0-9][0-9]-" "" (file-name-base (or buffer-file-name "new buffer"))))`}
#+author: ${2:`(user-full-name)`}
#+date: ${3:`(format-time-string "%Y-%m-%d")`}
#+latex_class: chameleon

$0

PHP-Mode

function

# -*- mode: snippet -*-
# name: function
# key: fu
# uuid: fu
# expand-env: ((yas-indent-line 'fixed) (yas-wrap-around-region 'nil))
# --
${1:$$(yas-auto-next (yas-completing-read "Visibility (public): " '("public" "private" "protected") nil nil nil nil "public"))} function ${2:name}($3)
{
    $0
}

php

# -*- mode: snippet -*-
# name: <?php
# key: php
# uuid: php
# --
<?php
$0

+php-laravel-mode

__

# -*- mode: snippet -*-
# name: PHP template
# --
<?php

namespace `(s-titleized-words (file-name-directory (file-relative-name buffer-file-name (projectile-project-root))))`;

class `(s-titleized-words (file-name-base buffer-file-name))`
{
    $0
}

migration_up

# -*- mode: snippet -*-
# name: Laravel Migration method
# key: mig
# uuid: mig
# --
Schema::table('$1', function (Blueprint $table) {
    `%`$0
});

scope

# -*- mode: snippet -*-
# name: Sentry scope
# key: scope
# uuid: scope
# --
withScope(function (Scope $scope) use ($1) {
    $scope->setContext('$2', [
        $3
    ]);

    `%`$0
});

Packages

Place to put packages that don’t have a guaranteed home yet.

Disabled/unpin

Packages to be unpinned or just completely disabled

(disable-packages! evil-escape)
(unpin! vterm)
(unpin! lsp-mode)

embark-magit

Embark additions to improve magit

(package! embark-vc
  :recipe (:host github :repo "elken/embark-vc"))
(use-package! embark-vc
  :after embark)

laravel-mode

Not yet fit for human consumption, but fit for mine because I’m sub super-human

(package! laravel-mode
  :recipe (:local-repo "~/build/elisp/laravel-mode"
           :build (:not compile)))
(use-package! laravel-tinker
  :after php-mode
  :init
  (set-popup-rule! "^\\tinker:" :vslot -5 :size 0.35 :select t :modeline nil :ttl nil)
  (map! :localleader
         :map php-mode-map
         :desc "Toggle a project-local Tinker REPL" "o t" #'laravel-tinker-toggle))

prescient

Need to add this into company module when I’ve tested

(package! company-prescient)
(use-package! company-prescient
  :after company
  :hook (company-mode . company-prescient-mode)
  :hook (company-prescient-mode . prescient-persist-mode)
  :config
  (setq prescient-save-file (concat doom-cache-dir "prescient-save.el")
        history-length 1000))

Rainbow Identifiers

Fix in web-mode

Web-mode has normal text which should be ignored.

(package! rainbow-identifiers)
(use-package! rainbow-identifiers
  :hook (php-mode . rainbow-identifiers-mode)
  ;; :hook (org-mode . (lambda () (rainbow-identifiers-mode -1)))
  ;; :hook (web-mode . (lambda () (rainbow-identifiers-mode -1)))
  :config
  (setq rainbow-identifiers-faces-to-override
        '(php-variable-name
          php-property-name
          php-variable-sigil
          web-mode-variable-name-face)))

Cucumber

Needed for feature test files

(package! feature-mode)
(use-package! feature-mode
  :mode "\\.feature$")

Spelling

(setq ispell-program-name "aspell"
      ispell-extra-args '("--sug-mode=ultra" "--lang=en_GB")
      ispell-dictionary "en"
      ispell-personal-dictionary (expand-file-name ".dict" doom-private-dir))

Slack

TBD

(package! slack)
(use-package! slack
  :commands (slack-start))

Reaper

Emacs client for Harvest, the time tracker we use at $DAYJOB. Over time, this will likely become more config than just a dump of macros.

(package! reaper)
(use-package! reaper
   :init
   (map! :leader :n :desc "Track time" "t t" #'reaper))

Graphviz

Some config to help with graphviz

(package! graphviz-dot-mode)
(use-package! graphviz-dot-mode
  :init
  (require 'company-graphviz-dot))

Jira

Config related to setting up Jira.

org-jira

Used for accessing Jira tasks through org-mode. Jira’s interface is quite a mess so I’d rather not use it as much.

You know what we love? org-mode.

(package! org-jira)
(use-package! org-jira
  :init
  (let ((dir (expand-file-name ".org-jira"
                               (or (getenv "XDG_CONFIG_HOME")
                                   (getenv "HOME")))))
    (unless (file-directory-p dir)
      (make-directory dir))
    (setq org-jira-working-dir dir)))

Translate

I do a decent amount of copy/paste translating stuff for work, so let’s make this easier.

(package! google-translate)
(use-package! google-translate
  :config
  (after! google-translate
    (defun google-translate--search-tkk ()
      "Search TKK."
      (list 430675 2721866130))
    (setf (alist-get "Kirirwanda" google-translate-supported-languages-alist) "rw")
    (setq google-translate-output-destination 'kill-ring)
    (setq google-translate-translation-directions-alist
          '(("en" . "fr")
            ("en" . "rw")))))

Local settings

Needed some way to manage settings for a local machine, so let’s be lazy with it

(when (file-exists-p! "config-local.el" doom-private-dir)
  (load! "config-local.el" doom-private-dir))
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].