(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.
Anything that’s missing from here might also be located in the archive, so it’s worth skimming from there too.
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 :)
Depends on Emacs 29 because of some super cool stuff, so if you’re not using it please don’t open any issues about missing variables.
(buffer-file-name)
- FAQ
- Globals
- Constants and Variables
- Lexical binding
- Frame title format
- Default projectile path
- Lookup provider URLs
- Subword-mode
- Auto-revert-mode
- Prevent flickering
- Clear snippets before loading
- Load env after reload
- Reload config without sync
- Bury compile buffer
- Evil
- Lispyville
- Default scratch mode
- Auth info
- fetch-auth-source
- Magit
- EShell
- User setup
- vterm
- Default modes
- Convert a URL to a valid package recipe
- Constants and Variables
- Keybindings
- Graphical setup
- Org Mode
- Languages
- Completion
- Snippets
- Packages
- Spelling
- Local settings
None yet because luckily nobody else has seen this spaghetti junction
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: t; -*-
Making a marginally more useful title format, since we often times might not necessarily running Emacs through the host.
(setq-default
frame-title-format
'(:eval (format "[%%b%s] - %s"
(if (buffer-modified-p)
" •"
"")
system-name)))
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"))
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")
("Arch Wiki" "https://wiki.archlinux.org/index.php?search=%s&title=Special%3ASearch&wprov=acrw1")
("AUR" "https://aur.archlinux.org/packages?O=0&K=%s")))
Subword mode is a good start because some languages use a lot of CamelCase and it makes refactoring slightly easier
(global-subword-mode 1)
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)))
Noticed some odd flickering here and there, apparently this should resolve it
(add-to-list 'default-frame-alist '(inhibit-double-buffering . t))
Some attempt to make them reproducible.
(add-hook! 'org-babel-pre-tangle-hook
(when (file-directory-p (expand-file-name "snippets" doom-user-dir))
(require 'async)
(async-start
(lambda ()
(delete-directory (expand-file-name "snippets" doom-user-dir) t (not (null delete-by-moving-to-trash))))
(lambda (result)
(print! "Delete snippets dir got: " result)))))
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))
Seems there’s a bug I’m too lazy to look into atm on this, so let’s just define
another reload command for me to use that doesn’t depend on doom sync
.
(defun doom/reload-without-sync ()
(interactive)
(mapc #'require (cdr doom-incremental-packages))
(doom-context-with '(reload modules)
(doom-run-hooks 'doom-before-reload-hook)
(doom-load (file-name-concat doom-user-dir doom-module-init-file) t)
(with-demoted-errors "PRIVATE CONFIG ERROR: %s"
(general-auto-unbind-keys)
(unwind-protect
(startup--load-user-init-file nil)
(general-auto-unbind-keys t)))
(doom-run-hooks 'doom-after-reload-hook)
(message "Config successfully reloaded!")))
(define-key! help-map "rc" #'doom/reload-without-sync)
Assuming the buffer finishes successfully, close after 1 second.
(defun bury-compile-buffer-if-successful (buffer string)
"Bury a compilation buffer if succeeded without warnings "
(when (and (eq major-mode 'comint-mode)
(string-match "finished" string)
(not
(with-current-buffer buffer
(search-forward "warning" nil t))))
(run-with-timer 1 nil
(lambda (buf)
(let ((window (get-buffer-window buf)))
(when (and (window-live-p window)
(eq buf (window-buffer window)))
(delete-window window))))
buffer)))
(add-hook 'compilation-finish-functions #'bury-compile-buffer-if-successful)
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)
I don’t need this because I, like all programmers, make 0 mistaeks.
(setq evil-want-fine-undo t)
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)
When we overwrite text in visual mode, say vip
, don’t add to the kill ring.
(setq evil-kill-on-visual-paste nil)
Some of them are quite useful, and I normally use them in the DE.
(setq evil-disable-insert-state-bindings t)
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)))
Make the scratch buffer start in lisp mode
(setq doom-scratch-initial-major-mode 'lisp-interaction-mode)
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")
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))))
Allow forge to create repos under my name
(setq forge-owned-accounts '(("elken")))
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 directorygit
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))
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)
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]")
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
Fixes a weird bug with native-comp, and I don’t use guix anymore.
(setq vterm-always-compile-module t)
If the process exits, kill the vterm
buffer
(setq vterm-kill-buffer-on-exit t)
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")))))
Useful functions for the shell-side integration provided by vterm.
(after! vterm
(setf (alist-get "woman" vterm-eval-cmds nil nil #'equal)
'((lambda (topic)
(woman topic))))
(setf (alist-get "magit-status" vterm-eval-cmds nil nil #'equal)
'((lambda (path)
(magit-status path))))
(setf (alist-get "dired" vterm-eval-cmds nil nil #'equal)
'((lambda (dir)
(dired dir)))))
(package! multi-vterm)
(use-package! multi-vterm
:after vterm)
Noticed a few weird cases where chsh
doesn’t quite apply, so let’s force that to be the case instead.
(setq vterm-shell "/bin/zsh")
Ensuring that correct modes are loaded for given file extensions
(add-to-list 'auto-mode-alist '("\\.jsonc\\'" . jsonc-mode))
(after! nerd-icons
(setf (alist-get "yuck" nerd-icons-extension-icon-alist)
'(nerd-icons-fileicon "lisp" :face nerd-icons-orange))
(setf (alist-get 'yuck-mode nerd-icons-mode-icon-alist)
'(nerd-icons-fileicon "lisp" :face nerd-icons-orange))
(setf (alist-get "jsonc" nerd-icons-extension-icon-alist)
'(nerd-icons-fileicon "config-js" :v-adjust -0.05 :face nerd-icons-orange))
(setf (alist-get 'jsonc-mode nerd-icons-mode-icon-alist)
'(nerd-icons-fileicon "config-js" :v-adjust -0.05 :face nerd-icons-orange)))
Useful when copying links to try new packages. Attempt to create one using
straight-hosts
, but fall back if one can’t be found.
(defun lkn/url->package (string &optional arg)
"Interactively select a URL from the kill-ring and create a package! block."
(interactive (list (consult--read-from-kill-ring) current-prefix-arg))
(require 'consult)
(require 'straight)
(let ((url (thread-first
string
substring-no-properties
(substring 1 (length string))
string-trim
url-generic-parse-url)))
(if-let ((host
(cl-find-if (lambda (cell)
(member (url-host url) cell))
straight-hosts)))
(insert
(concat
"(package! "
(car (last (string-split (url-filename url) "/")))
"\n:recipe (:host "
(symbol-name (car host))
" :repo \""
(substring (url-filename url) 1)
"\"))"))
(insert
(concat
"(package! "
(car (last (string-split (url-filename url) "/")))
"\n:recipe (:host nil :repo \""
string
"\"))")))
(call-interactively #'indent-region)))
It’s not a custom config without some fancy keybinds
Back to a simpler time…
(map! :g "C-s" #'save-buffer)
Swiper Consult is much better than isearch
(map! :after evil :gnvi "C-f" #'consult-line)
Dired should behave better with evil mappings
(map! :map dired-mode-map
:n "h" #'dired-up-directory
:n "l" #'dired-find-alternate-file)
This is something I’m likely to use quite often, especially with an easy convenience binding
(after! org-journal
(setq org-journal-find-file #'find-file-other-window)
(map! :leader :desc "Open today's journal" "j" #'org-journal-open-current-journal-file))
Emacs 29 has some new hotness, including a cool new scrolling thing.
(when (version< "29.0.50" emacs-version)
(pixel-scroll-precision-mode))
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 is part of the Vertico stack, and is responsible for all the fancy faces and extra information.
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)))))
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))
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)
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.
(after! doom-modeline
(setq auto-revert-check-vc-info t
doom-modeline-major-mode-icon t
doom-modeline-buffer-file-name-style 'relative-to-project
doom-modeline-github nil
doom-modeline-vcs-max-length 60)
(remove-hook 'doom-modeline-mode-hook #'size-indication-mode)
(doom-modeline-def-modeline 'main
'(matches bar modals workspace-name window-number persp-name selection-info buffer-info remote-host debug vcs matches)
'(github mu4e grip gnus check misc-info repl lsp " ")))
Configure the fonts across all used platforms (slightly different names).
(setq doom-font (font-spec :family "Iosevka Nerd Font" :size 16)
doom-variable-pitch-font (font-spec :family "Montserrat" :size 16)
doom-unicode-font (font-spec :family "Symbols Nerd Font Mono" :size 16))
Ligatures are a mess in programming languages, however they make org documents quite nice so let’s just use them here until a good fix is found.
(setq-hook! org-mode
prettify-symbols-alist '(("#+end_quote" . "”")
("#+END_QUOTE" . "”")
("#+begin_quote" . "“")
("#+BEGIN_QUOTE" . "“")
("#+end_src" . "«")
("#+END_SRC" . "«")
("#+begin_src" . "»")
("#+BEGIN_SRC" . "»")
("#+name:" . "»")
("#+NAME:" . "»")))
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
`(tree-sitter-hl-face:constructor :foreground ,(doom-color 'blue))
`(tree-sitter-hl-face:number :foreground ,(doom-color 'orange))
`(tree-sitter-hl-face:attribute :foreground ,(doom-color 'magenta) :weight bold)
`(tree-sitter-hl-face:variable :foreground ,(doom-color 'base7) :weight bold)
`(tree-sitter-hl-face:variable.builtin :foreground ,(doom-color 'red))
`(tree-sitter-hl-face:constant.builtin :foreground ,(doom-color 'magenta) :weight bold)
`(tree-sitter-hl-face:constant :foreground ,(doom-color 'blue) :weight bold)
`(tree-sitter-hl-face:function.macro :foreground ,(doom-color 'teal))
`(tree-sitter-hl-face:label :foreground ,(doom-color 'magenta))
`(tree-sitter-hl-face:operator :foreground ,(doom-color 'blue))
`(tree-sitter-hl-face:variable.parameter :foreground ,(doom-color 'cyan))
`(tree-sitter-hl-face:punctuation.delimiter :foreground ,(doom-color 'cyan))
`(tree-sitter-hl-face:punctuation.bracket :foreground ,(doom-color 'cyan))
`(tree-sitter-hl-face:punctuation.special :foreground ,(doom-color 'cyan))
`(tree-sitter-hl-face:type :foreground ,(doom-color 'yellow))
`(tree-sitter-hl-face:type.builtin :foreground ,(doom-color 'blue))
`(tree-sitter-hl-face:tag :foreground ,(doom-color 'base7))
`(tree-sitter-hl-face:string :foreground ,(doom-color 'green))
`(tree-sitter-hl-face:comment :foreground ,(doom-color 'base6))
`(tree-sitter-hl-face:function :foreground ,(doom-color 'cyan))
`(tree-sitter-hl-face:method :foreground ,(doom-color 'blue))
`(tree-sitter-hl-face:function.builtin :foreground ,(doom-color 'cyan))
`(tree-sitter-hl-face:property :foreground ,(doom-color 'blue))
`(tree-sitter-hl-face:keyword :foreground ,(doom-color 'magenta))
`(corfu-default :font "Iosevka Nerd Font Mono" :background ,(doom-color 'bg-alt) :foreground ,(doom-color 'fg))
`(adoc-title-0-face :foreground ,(doom-color 'blue) :height 1.2)
`(adoc-title-1-face :foreground ,(doom-color 'magenta) :height 1.1)
`(adoc-title-2-face :foreground ,(doom-color 'violet) :height 1.05)
`(adoc-title-3-face :foreground ,(doom-lighten (doom-color 'blue) 0.25) :height 1.0)
`(adoc-title-4-face :foreground ,(doom-lighten (doom-color 'magenta) 0.25) :height 1.1)
`(adoc-verbatim-face :background nil)
`(adoc-list-face :background nil)
`(adoc-internal-reference-face :foreground ,(face-attribute 'font-lock-comment-face :foreground)))
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))
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))))
Maximise emacs on startup when not running under awesome
(when (string= "" (shell-command-to-string "pgrep awesome"))
(add-to-list 'default-frame-alist '(fullscreen . maximized)))
Add some transparency when running under awesome
(unless (string= "" (shell-command-to-string "pgrep awesome"))
(set-frame-parameter (selected-frame) 'alpha-background 90)
(add-to-list 'default-frame-alist '(alpha-background . 90)))
This definitely feels like something that should be on ootb, but hey ho.
(defun org-id-complete-link (&optional arg)
"Create an id: link using completion"
(concat "id:" (org-id-get-with-outline-path-completion)))
(after! org
(org-link-set-parameters "id" :complete 'org-id-complete-link))
Keep the content centered on the page when writing org documents
(package! visual-fill-column)
(use-package! visual-fill-column
:custom
(visual-fill-column-width 300)
(visual-fill-column-center-text t)
:hook (org-mode . visual-fill-column-mode))
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-pretty-mode)
(elken/org-font-setup))
(add-hook! org-mode #'elken/org-setup-hook)
Let’s set a sane default directory based on where I am
(setq org-directory "~/Nextcloud/org"
org-agenda-files '("~/Nextcloud/org/Home.org" "~/Nextcloud/org/Work.org" "~/Nextcloud/org/Notes.org"))
Font setup to prettify the fonts. Uses Montserrat 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
(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 "Montserrat" :weight 'regular :height (cdr face) :slant 'unspecified))
;; 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))
This may be the solution to so many weird issues with src blocks.
(setq org-use-property-inheritance t)
Tried out org-modern recently, it is very nice but also detracts away from some
of the org markup and makes editing it too hard, so back to (:lang org +pretty)
we go.
I don’t feel the need for fancy characters to discern depth, I found this on someone else’s config and I actually quite like the minimal look.
(setq org-superstar-headline-bullets-list '("› "))
Barely any adjustment here, just make them look a bit nicer.
(setq org-superstar-item-bullet-alist '((?* . ?⋆)
(?+ . ?‣)
(?- . ?•)))
When a drawer is collapsed, show a nice dropdown arrow.
(setq org-ellipsis " ▾")
Default keywords are far too minimal. This will need further tweaking as I start using org mode for organisation more.
Some tasks we want to file an action for, eg DONE
, KILL
and WAIT
occur and we want to list a reason why. org-todo-keywords
handles this natively by simply adding @/!
after the shortcut key.
The below is courtesy of gagbo.
(after! org
(setq org-todo-keywords
'((sequence "TODO(t)" "INPROG(i)" "PROJ(p)" "STORY(s)" "WAIT(w@/!)" "|" "DONE(d@/!)" "KILL(k@/!)")
(sequence "[ ](T)" "[-](S)" "[?](W)" "|" "[X](D)"))
;; The triggers break down to the following rules:
;; - Moving a task to =KILLED= adds a =killed= tag
;; - Moving a task to =WAIT= adds a =waiting= tag
;; - Moving a task to a done state removes =WAIT= and =HOLD= tags
;; - Moving a task to =TODO= removes all tags
;; - Moving a task to =NEXT= removes all tags
;; - Moving a task to =DONE= removes all tags
org-todo-state-tags-triggers
'(("KILL" ("killed" . t))
("HOLD" ("hold" . t))
("WAIT" ("waiting" . t))
(done ("waiting") ("hold"))
("TODO" ("waiting") ("cancelled") ("hold"))
("NEXT" ("waiting") ("cancelled") ("hold"))
("DONE" ("waiting") ("cancelled") ("hold")))
;; This settings allows to fixup the state of a todo item without
;; triggering notes or log.
org-treat-S-cursor-todo-selection-as-state-change nil))
(setq org-agenda-start-with-log-mode t)
(setq org-log-done 'time)
(setq org-log-into-drawer t)
Cycle by default (no idea why this isn’t default)
(setq org-cycle-emulate-tab nil)
Default folding is very noisy, I rarely need to see everything expanded
(setq org-startup-folded 'content)
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))
Enable mixed-pitch-mode
to enable the more readable fonts where it makes sense.
(package! mixed-pitch)
(setq +zen-mixed-pitch-modes '(org-mode LaTeX-mode markdown-mode gfm-mode Info-mode rst-mode adoc-mode))
(dolist (hook +zen-mixed-pitch-modes)
(add-hook (intern (concat (symbol-name hook) "-hook")) #'mixed-pitch-mode))
Adjust the format of archived org files (so they don’t show up in orgzly)
(setq org-archive-location "archive/Archive_%s::")
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)
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 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)
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)
Useful settings and functions for maintaining modified dates in org files
(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")))
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 '("els" . "src elisp"))
(add-to-list 'org-structure-template-alist '("el" . "src emacs-lisp")))
Let’s jump on the bandwagon and start taking useful notes.
(setq org-roam-directory (expand-file-name "roam" org-directory))
(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))))
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))
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 (nerd-icons-octicon "nf-oct-stop" :face 'nerd-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 nerd-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 doom 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 "nerd-icons-" (plist-get declaration :set))))
(face (intern (concat "nerd-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))
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
(defun +org-capture/replace-brackets (link)
(mapconcat
(lambda (c)
(pcase (key-description (vector c))
("[" "(")
("]" ")")
(_ (key-description (vector c)))))
link))
(setq org-capture-templates
(doct `(("Home" :keys "h"
:icon ("nf-fa-home" :set "faicon" :color "cyan")
:file "Home.org"
:prepend t
:headline "Inbox"
:template ("* TODO %?"
"%i %a"))
("Work" :keys "w"
:icon ("nf-fa-building" :set "faicon" :color "yellow")
:file "Work.org"
:prepend t
:headline "Inbox"
:template ("* TODO %?"
"SCHEDULED: %^{Schedule:}t"
"DEADLINE: %^{Deadline:}t"
"%i %a"))
("Note" :keys "n"
:icon ("nf-fa-sticky_note" :set "faicon" :color "yellow")
:file "Notes.org"
:template ("* %?"
"%i %a"))
("Journal" :keys "j"
:icon ("nf-fa-calendar" :set "faicon" :color "pink")
:type plain
:function (lambda ()
(org-journal-new-entry t)
(unless (eq org-journal-file-type 'daily)
(org-narrow-to-subtree))
(goto-char (point-max)))
:template "** %(format-time-string org-journal-time-format)%^{Title}\n%i%?"
:jump-to-captured t
:immediate-finish t)
("Protocol" :keys "P"
:icon ("nf-fa-link" :set "faicon" :color "blue")
:file "Notes.org"
:template ("* TODO %^{Title}"
"Source: %u"
"#+BEGIN_QUOTE"
"%i"
"#+END_QUOTE"
"%?"))
("Protocol link" :keys "L"
:icon ("nf-fa-link" :set "faicon" :color "blue")
:file "Notes.org"
:template ("* TODO %?"
"[[%:link][%:description]]"
"Captured on: %U"))
("Project" :keys "p"
:icon ("nf-oct-repo" :set "octicon" :color "silver")
:prepend t
:type entry
:headline "Inbox"
:template ("* %{keyword} %?"
"%i"
"%a")
:file ""
:custom (:keyword "")
:children (("Task" :keys "t"
:icon ("nf-cod-checklist" :set "codicon" :color "green")
:keyword "TODO"
:file +org-capture-project-todo-file)
("Note" :keys "n"
:icon ("nf-fa-sticky_note" :set "faicon" :color "yellow")
:keyword "%U"
:file +org-capture-project-notes-file)))))))
A necessary evil. I hate it, it hates me, but it makes my PDF documents look nice.
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.")
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 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"))
Simple base header shared by all defines classes
\\documentclass[10pt]{scrartcl}
[PACKAGES]
[DEFAULT-PACKAGES]
[EXTRA]
\\setmainfont[Ligatures=TeX]{Montserrat}
\\setmonofont[Ligatures=TeX]{Iosevka Nerd Font Mono}
% Using chameleon
<<base-template>>
% Using work
<<base-template>>
\\usepackage{fontawesome5}
\\usepackage{tcolorbox}
\\usepackage{fancyhdr}
\\usepackage{lastpage}
\\pagestyle{fancy}
\\fancyhead{}
\\fancyhead[RO, LE]{}
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 defaults for them
(after! ox-latex
(setq org-latex-tables-booktabs t
org-latex-default-class "chameleon"
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}"))
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)
("" "cleveref" nil)))
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))
(use-package! engrave-faces-html
:after ox-html
:config
(setq org-latex-listings 'engraved))
(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
:after #'org-latex-inline-src-block-engraved
(setq org-export-has-code-p t))
(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)))
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)
Starting to look into beamer for creating presentations, seems like we need to steal borrow more config from Tecosaur.
Metropolis is a nice theme, with a tiny adjustment it might be the best.
(setq org-beamer-theme "[progressbar=foot]metropolis")
(defun org-beamer-p (info)
(eq 'beamer (and (plist-get info :back-end)
(org-export-backend-name (plist-get info :back-end)))))
(add-to-list 'org-latex-conditional-features '(org-beamer-p . beamer) t)
(add-to-list 'org-latex-feature-implementations '(beamer :requires .missing-koma :prevents (italic-quotes condensed-lists)) t)
(add-to-list 'org-latex-feature-implementations '(.missing-koma :snippet "\\usepackage{scrextend}" :order 2) t)
And lastly, a small tweak to improve how sections are divided
(setq org-beamer-frame-level 2)
Annoying having to gate these, so let’s fix that
(setq org-export-with-sub-superscripts '{})
Defines a minor mode I can use to automatically export a PDF on save.
(defun +org-auto-export ()
(org-beamer-export-to-pdf t))
(define-minor-mode org-auto-export-mode
"Toggle auto exporting the Org file."
:global nil
:lighter ""
(if org-auto-export-mode
;; When the mode is enabled
(progn
(add-hook 'after-save-hook #'+org-auto-export :append :local))
;; When the mode is disabled
(remove-hook 'after-save-hook #'+org-auto-export :local)))
Interact with org-mode from other applications, including my web browser. Being able to create things like tasks and other org items from anywhere sounds ideal.
(use-package! org-protocol
:defer t)
# DO NOT EDIT THIS
# I have been generated from <<file-name()>>
[Desktop Entry]
Name=org-protocol
Comment=Intercept calls from emacsclient to trigger custom actions
Categories=Other;
Keywords=org-protocol;
Icon=emacs
Type=Application
Exec=emacsclient -- %u
Terminal=false
StartupWMClass=Emacs
MimeType=x-scheme-handler/org-protocol;
Configuration for various programming languages.
Buffers are pretty great, but sometimes they can be named … less usefully.
(package! epithet
:recipe (:host github :repo "oantolin/epithet"))
(use-package! epithet
:hook (clojure-mode . epithet-rename-buffer)
:init
;; (setq-hook! 'clojure-mode-hook doom-modeline-buffer-file-name-style 'buffer-name)
(defun epithet-for-clojure ()
"Suggest a name for a `clojure-mode' buffer."
(when (and (require 'cider nil t)
(derived-mode-p 'clojure-mode))
(after! doom-modeline
(setq-local doom-modeline-buffer-file-name-style 'buffer-name))
(format
"%s <%s>"
(substring-no-properties (cider-current-ns))
(projectile-project-name))))
:config
(add-to-list 'epithet-suggesters #'epithet-for-clojure)
(after! doom-modeline
(advice-add #'epithet-rename-buffer :after #'doom-modeline-update-buffer-file-name)))
This portal thing looks pretty cool yanno.
(defvar lkn/clj-portal-viewers '(":portal.viewer/inspector"
":portal.viewer/pprint"
":portal.viewer/table"
":portal.viewer/tree"
":portal.viewer/hiccup"))
(defun lkn/clj-start-of-sexp ()
(save-excursion
(paredit-backward-up)
(point)))
(defun lkn/clj-end-of-sexp ()
(save-excursion
(paredit-forward-up)
(point)))
(defun lkn/portal-open ()
(interactive)
(cider-nrepl-sync-request:eval
"(do (ns dev) (def portal ((requiring-resolve 'portal.api/open) {:launcher :emacs})) (add-tap (requiring-resolve 'portal.api/submit)))"))
(defun lkn/portal-clear ()
(interactive)
(cider-nrepl-sync-request:eval "(portal.api/clear)"))
(defun lkn/portal-close ()
(interactive)
(cider-nrepl-sync-request:eval "portal.api/close"))
(defun lkn/portal-tap-contained (&optional viewer)
(interactive (list (when (consp current-prefix-arg)
(completing-read "Default Viewer: " lkn/clj-portal-viewers))))
(let ((beg (lkn/clj-start-of-sexp))
(end (lkn/clj-end-of-sexp)))
(cider-interactive-eval
(format "(tap> ^{:portal.viewer/default %s} %s)" (or viewer (car lkn/clj-portal-viewers)) (buffer-substring-no-properties beg end)))))
(defun lkn/portal-tap-last (&optional viewer)
(interactive (list (when (consp current-prefix-arg)
(completing-read "Default Viewer: " lkn/clj-portal-viewers))))
(cider-interactive-eval
(format "(tap> ^{:portal.viewer/default %s} %s)" (or viewer (car lkn/clj-portal-viewers)) (cider-last-sexp))))
(map! :map (clojure-mode-map clojurescript-mode-map clojurec-mode-map)
:localleader
(:prefix ("p p" . "Portal")
:desc "Open Portal"
"o" #'lkn/portal-open
:desc "Clear layers"
"c" #'lkn/portal-clear
:desc "Close current session"
"q" #'lkn/portal-close
:desc "Send sexp around point"
"s" #'lkn/portal-tap-contained
:desc "Send sexp next to point"
"S" #'lkn/portal-tap-last))
First things first; we need a project mode for my awesomewm config.
(def-project-mode! +awesome-config-mode
:modes '(lua-mode)
:files ("rc.lua")
:when (string-prefix-p (expand-file-name "awesome" (xdg-config-home)) default-directory))
(add-hook '+awesome-config-mode-hook #'rainbow-mode)
New year new me. This year, it’s Ruby.
I don’t yet see a case for not having this on all the time, so for now we just always have this on.
Easier to manage things with rbenv.
(global-rbenv-mode)
Seems that even just having some remnant of rvm around is enough to trigger it, and that breaks a lot.
So, I’d rather just have it always be off.
(setq rspec-use-rvm nil)
Not yet sure which of these is best, so for now until I get a compelling reason I’d rather stick with solargraph.
(after! lsp-mode
(add-to-list 'lsp-disabled-clients 'rubocop-ls)
(add-to-list 'lsp-disabled-clients 'solargraph)
(add-to-list 'lsp-disabled-clients 'typeprof-ls))
Another language that’s missing this…
(add-hook 'ruby-mode-hook #'rainbow-delimiters-mode)
By default this is way too short.
(setq dap-ui-variable-length 200)
The default completions are quite bad
(after! lsp-mode
(setq +lsp-company-backends
'(:separate company-capf company-yasnippet company-dabbrev)))
Completion is handled by the amazing VERTICO stack, most of which we can just rely on stock Doom setup.
There are however a few minor changes we want…
In order to get a slightly nicer UI, we can set this manually rather than relying on the default completing-read
.
(autoload #'consult--read "consult")
;;;###autoload
(defun +vertico/projectile-completion-fn (prompt choices)
"Given a PROMPT and a list of CHOICES, filter a list of files for
`projectile-find-file'."
(interactive)
(consult--read
choices
:prompt prompt
:sort nil
:add-history (thing-at-point 'filename)
:category 'file
:history '(:input +vertico/find-file-in--history)))
(setq projectile-completion-system '+vertico/projectile-completion-fn)
(defun flatten-imenu-index (index &optional prefix)
"Flatten an org-mode imenu index."
(let ((flattened '()))
(dolist (item index flattened)
(let* ((name (propertize (car item) 'face (intern (format "org-level-%d" (if prefix (+ 2 (cl-count ?/ prefix)) 1)))))
(prefix (if prefix (concat prefix "/" name) name)))
(if (imenu--subalist-p item)
(setq flattened (append flattened (flatten-imenu-index (cdr item) prefix)))
(push (cons prefix (cdr item)) flattened))))
(nreverse flattened)))
;;;###autoload
(defun +literate-jump-heading ()
"Jump to a heading in the literate org file."
(interactive)
(let* ((+literate-config-file (file-name-concat doom-user-dir "config.org"))
(buffer (or (find-buffer-visiting +literate-config-file)
(find-file-noselect +literate-config-file t))))
(with-current-buffer buffer
(let* ((imenu-auto-rescan t)
(org-imenu-depth 8)
(index (flatten-imenu-index (imenu--make-index-alist))))
(let ((c (current-window-configuration))
(result nil))
(unwind-protect
(progn
(switch-to-buffer buffer)
(cond
((modulep! :completion vertico)
(setq result (consult-org-heading)))
(t
(let ((entry (assoc (completing-read "Go to heading: " index nil t) index)))
(setq result entry)
(imenu entry)))))
(unless result
(set-window-configuration c))))))))
(map! :leader :n :desc "Open heading in literate config" "f o" #'+literate-jump-heading)
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…
A collection of snippets tangled using clever magic.
# -*- 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: \today
#+latex_class: chameleon
$0
# -*- mode: snippet -*-
# name: Standup template
# --
*Standup*
$1: $2
`(format-time-string "%d/%m/%y" (current-time))`: $3
*Blocked*: $4
Place to put packages that don’t have a guaranteed home yet.
Packages to be unpinned or just completely disabled
(disable-packages! evil-escape org-yt)
(unpin! evil-collection)
(package! apheleia
:recipe (:local-repo "~/build/elisp/apheleia"))
(package! engrave-faces
:recipe (:host github :repo "elken/engrave-faces"))
(package! ox-chameleon
:recipe (:host github :repo "elken/ox-chameleon"))
Embark additions to improve various vc operations
(package! embark-vc)
(use-package! embark-vc
:after embark)
Need to add this into company module when I’ve tested
(when (modulep! :completion company)
(package! company-prescient))
(when (modulep! :completion company)
(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)))
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)))
Needed for feature test files
(package! feature-mode)
(use-package! feature-mode
:mode "\\.feature$")
Starting to actually write more of these now, so this is an easy sell
(package! systemd)
(use-package! systemd
:mode "\\.service$")
Needed for rpm files to not be treated poorly (there, there)
(package! rpm-spec-mode
:recipe (:host github :repo "bhavin192/rpm-spec-mode"))
(use-package! rpm-spec-mode
:mode "\\.spec\\(\\.in\\)?$")
Needed for a very WIP theme, otherwise not needed.
(package! autothemer)
Setup for my package for integrating with Bamboo HR
(package! bhr
:recipe (:host github :repo "elken/bhr.el"))
(use-package! bhr
:commands (bhr-view-timesheet bhr-submit-multiple))
yadm is my preferred dotfile manager of choice, but by default because of the nature of how the repo is handled; it’s quite a pain to manage from Emacs.
tramp-yadm to the rescue! This lets me use magit & projectile as expected on the repo; allowing me to manage dotfile changes with the superior git client.
(package! tramp-yadm
:recipe (:host github :repo "seanfarley/tramp-yadm"))
(use-package! tramp-yadm
:defer t
:init
(defun yadm-status ()
"Invoke magit on the yadm repo"
(interactive)
(magit-status "/yadm::~")
(setq-local magit-git-executable (executable-find "yadm"))
(setq-local magit-remote-git-executable (executable-find "yadm")))
(after! magit
(tramp-yadm-register)
(map! :leader :desc "Open yadm status" "g p" #'yadm-status)))
Keychain is amazing. It wraps ssh-agent and gpg-agent so I never have to.
The problem is Emacs doesn’t always detect it nicely … until now!
(defun +keychain-startup-hook ()
"Load keychain env after emacs"
(let* ((ssh (shell-command-to-string "keychain -q --noask --agents ssh --eval"))
(gpg (shell-command-to-string "keychain -q --noask --agents gpg --eval")))
(list (and ssh
(string-match "SSH_AUTH_SOCK[=\s]\\([^\s;\n]*\\)" ssh)
(setenv "SSH_AUTH_SOCK" (match-string 1 ssh)))
(and ssh
(string-match "SSH_AGENT_PID[=\s]\\([0-9]*\\)?" ssh)
(setenv "SSH_AGENT_PID" (match-string 1 ssh)))
(and gpg
(string-match "GPG_AGENT_INFO[=\s]\\([^\s;\n]*\\)" gpg)
(setenv "GPG_AGENT_INFO" (match-string 1 gpg))))))
(add-hook 'after-init-hook #'+keychain-startup-hook)
(package! adoc-mode)
Some config to help with graphviz
(package! graphviz-dot-mode)
(use-package! graphviz-dot-mode
:init
(after! company
(require 'company-graphviz-dot)))
Exercism is a useful site for learning a programming language by performing various exercises. You can opt to use either an in-browser editor or your own via a local CLI.
Which do you think I want?
(package! exercism-modern
:recipe (:files (:defaults "icons")
:host github :repo "elken/exercism-modern"))
(use-package! exercism-modern
:commands (exercism-modern-jump exercism-modern-view-tracks))
Trying to find a decent structural editor I like…
(package! evil-cleverparens
:recipe (:host github :repo "tomdl89/evil-cleverparens" :branch "fix/delete-escaped-parens"))
(package! paredit)
(use-package! paredit
:hook (emacs-lisp-mode . paredit-mode)
:hook (clojure-mode . paredit-mode))
(use-package! evil-cleverparens
:when (modulep! :editor evil +everywhere)
:hook (paredit-mode . evil-cleverparens-mode))
This is literally the coolest package ever…
(package! litable
:recipe (:host github :repo "Fuco1/litable"))
(use-package! litable
:custom
(litable-list-file (expand-file-name "litable-lists.el" doom-cache-dir))
:init
(map! :localleader
:map emacs-lisp-mode-map
(:prefix ("t" . "toggle")
"l" #'litable-mode)))
A simple package to add file icons in magit views.
(package! magit-file-icons
:pin "0006e243b0e7aa005f6e8900b4b2e3a92c2d0532"
:recipe (:host github :repo "gekoke/magit-file-icons"))
(use-package! magit-file-icons
:after magit
:init
(magit-file-icons-mode 1))
(setq ispell-program-name "aspell"
ispell-extra-args '("--sug-mode=ultra" "--lang=en_GB")
ispell-dictionary "en"
ispell-personal-dictionary "~/Nextcloud/dict")
(after! cape
(setq cape-dict-file (if (file-exists-p ispell-personal-dictionary) ispell-personal-dictionary cape-dict-file)))
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))
Better handle setting of environment variables needed for various tools
(package! dotenv
:recipe (:host github :repo "pkulev/dotenv.el"))
(use-package! dotenv
:init
(when (file-exists-p (expand-file-name ".env" doom-user-dir))
(add-hook! 'doom-init-ui-hook
(defun +dotenv-startup-hook ()
"Load .env after starting emacs"
(dotenv-update-project-env doom-user-dir))))
:config
(add-hook! 'projectile-after-switch-project-hook
(defun +dotenv-projectile-hook ()
"Load .env after changing projects."
(dotenv-update-project-env (projectile-project-root)))))