# My Spacemacs User Config ## Preamble This is the [[literate configuration]] source for my [[spacemacs]] user config. I use [[org-babel]] to tangle it together into the actual config file. # Table of Contents 1. [Preamble](#Preamble) 2. [Coding](#Coding) 1. [PHP](#PHP) 3. [Completion](#Completion) 4. [Productivity](#Productivity) 1. [org](#org) 5. [Writing and knowledge management](#Writing%20and%20knowledge%20management) 1. [Writing mode](#Writing%20mode) 2. [word counts](#word%20counts) 3. [citations](#citations) 4. [org-roam](#org-roam) 6. [Look'n'feel](#Look%27n%27feel) 1. [Themes](#Themes) 2. [Solaire](#Solaire) 3. [Tabs (centaur)](#Tabs%20%28centaur%29) 4. [Helm](#Helm) 5. [Scrolling](#Scrolling) 7. [Communications](#Communications) 1. [mu4e (mail)](#mu4e%20%28mail%29) 2. [IRC (erc)](#IRC%20%28erc%29) 8. [Misc](#Misc) 1. [Long lines](#Long%20lines) 2. [Tidal](#Tidal) 3. [cook-mode](#cook-mode) 4. [undo tree](#undo%20tree) ## Coding ### PHP #### Set up keyboard shortcuts for PHPUnit ```elisp (with-eval-after-load 'php-mode (define-key php-mode-map (kbd "C-c C-t t") 'phpunit-current-test) (define-key php-mode-map (kbd "C-c C-t c") 'phpunit-current-class) (define-key php-mode-map (kbd "C-c C-t p") 'phpunit-current-project)) ``` #### Use web-mode for Laravel templates. ```elisp (add-to-list 'auto-mode-alist '("\\.blade.php\\'" . web-mode)) ``` ## Completion Ignoring completion case mainly for org-roam - hope it doesn't muss with other stuff. ```elisp (setq completion-ignore-case t) ``` ## Productivity ### org #### Agenda ##### Use org-super-agenda a nicer looking agenda. ```elisp (org-super-agenda-mode) (setq org-agenda-custom-commands '(("g" "Super groups" agenda "" ((org-super-agenda-groups '((:auto-property "agenda-group"))))) ("u" "Super view" agenda "" ((org-super-agenda-groups '(;; Each group has an implicit boolean OR operator between its selectors. (:name "Today" ; Optionally specify section name :time-grid t ; Items that appear on the time grid :tag "today" :todo "TODAY") ; Items that have this TODO keyword (:name "Important" ;; Single arguments given alone :tag "bills" :priority "A") ;; Set order of multiple groups at once (:name "Quick wins (< 20 mins)" :effort< "0:20") (:name "Cleaning" :tag "cleaning" :order 5) (:name "Chores" :tag "chore" :order 5) (:name "Stuck" :tag "stuck" :order 2) (:name "Town" :tag ("town" "@town") :order 9) (:order-multi (2 (:name "Shopping in town" ;; Boolean AND group matches items that match all subgroups :and (:tag "shopping" :tag "@town")) ;;(:name "Personal" ;; :habit t ;; :tag "personal") (:name "Food-related" ;; Multiple args given in list with implicit OR :tag ("food" "dinner")))) ;; Groups supply their own section names when none are given (:todo "WAITING" :order 8) ; Set order of this section (:todo ("SOMEDAY" "TO-READ" "CHECK" "TO-WATCH" "WATCHING") ;; Show this group at the end of the agenda (since it has the ;; highest number). If you specified this group last, items ;; with these todo keywords that e.g. have priority A would be ;; displayed in that group instead, because items are grouped ;; out in the order the groups are listed. :order 9) (:priority<= "B" ;; Show this section after "Today" and "Important", because ;; their order is unspecified, defaulting to 0. Sections ;; are displayed lowest-number-first. :order 3) ;; After the last group, the agenda will display items that didn't ;; match any of these groups, with the default order position of 99 )))))) ``` ##### org-timeline For getting a visual representation of a day plan. https://github.com/Fuco1/org-timeline ```elisp (require 'org-timeline) (add-hook 'org-agenda-finalize-hook 'org-timeline-insert-timeline :append) ``` ##### org-schedule-effort See [[Calculating effort estimates and scheduled times in org-mode]]. ```elisp (defun org-schedule-effort () (interactive) (save-excursion (org-back-to-heading t) (let* ( (element (org-element-at-point)) (effort (org-element-property :EFFORT element)) (scheduled (org-element-property :scheduled element)) (ts-year-start (org-element-property :year-start scheduled)) (ts-month-start (org-element-property :month-start scheduled)) (ts-day-start (org-element-property :day-start scheduled)) (ts-hour-start (org-element-property :hour-start scheduled)) (ts-minute-start (org-element-property :minute-start scheduled)) ) (org-schedule nil (concat (format "%s" ts-year-start) "-" (if (< ts-month-start 10) (concat "0" (format "%s" ts-month-start)) (format "%s" ts-month-start)) "-" (if (< ts-day-start 10) (concat "0" (format "%s" ts-day-start)) (format "%s" ts-day-start)) " " (if (< ts-hour-start 10) (concat "0" (format "%s" ts-hour-start)) (format "%s" ts-hour-start)) ":" (if (< ts-minute-start 10) (concat "0" (format "%s" ts-minute-start)) (format "%s" ts-minute-start)) "+" effort)) ))) ``` #### Capturing ##### capture templates ```elisp (require 'org-protocol) ;(add-to-list 'load-path "/home/neil/.emacs.d/private/org-protocol-capture-html") ;(require 'org-protocol-capture-html) (setq org-capture-templates (quote (("c" "TODO scheduled today" entry (file+headline "~/org/_GTD.org" "Inbox") "** TODO %?\n SCHEDULED: %t\n") ;; ("w" "Web site" ;; entry (file+olp "/home/shared/commonplace/clippings.org" "Clippings") ;; "** %c :website:\n%U %?%:initial") ))) ;; to start in insert mode when creating via capture template (add-hook 'org-capture-mode-hook 'evil-insert-state) ``` #### Refiling ```elisp (setq org-refile-targets '((nil :maxlevel . 9) (org-agenda-files :maxlevel . 9))) (setq org-outline-path-complete-in-steps nil) ; Refile in a single go (setq org-refile-use-outline-path t) ; Show full paths for refiling (setq org-agenda-files (list "~/org/_GTD.org" "~/org/Inbox.org")) ``` #### Babel ```elisp ;; babel (with-eval-after-load 'org (org-babel-do-load-languages 'org-babel-load-languages '((sql . t) (python . t) (plantuml . t) (sqlite . t) (shell . t)))) ``` Without this, the indentation in org-babel src blocks always gets indented without me wanting it to. See: https://github.com/syl20bnr/spacemacs/issues/13255 ```elisp (setq org-src-preserve-indentation t) ``` #### Misc ##### Deleting links See: https://emacs.stackexchange.com/questions/10707/in-org-mode-how-to-remove-a-link ```elisp (defun ngm/org-delete-link () "Replace an org link of the format [[LINK][DESCRIPTION]] with DESCRIPTION. If the link is of the format [[LINK]], delete the whole org link. In both the cases, save the LINK to the kill-ring. Execute this command while the point is on or after the hyper-linked org link." (interactive) (when (derived-mode-p 'org-mode) (let ((search-invisible t) start end) (save-excursion (when (re-search-backward "\\[\\[" nil :noerror) (when (re-search-forward "\\[\\[\\(.*?\\)\\(\\]\\[.*?\\)*\\]\\]" nil :noerror) (setq start (match-beginning 0)) (setq end (match-end 0)) (kill-new (match-string-no-properties 1)) ; Save the link to kill-ring (replace-regexp "\\[\\[.*?\\(\\]\\[\\(.*?\\)\\)*\\]\\]" "\\2" nil start end))))))) ``` #### Extracting bolded sections from a block of text into a bullet list ```elisp (defun ngm/bold-to-bullets () "Extract bolded sections from the selected region and place them after it in a bullet list." (interactive) (if (region-active-p) (let ((bolded-sections '()) (case-fold-search nil) (region-start (region-beginning)) (region-end (region-end))) (goto-char region-start) (while (re-search-forward "\\*\\([^*]+\\)\\*" region-end t) (push (match-string 1) bolded-sections)) (setq bolded-sections (reverse bolded-sections)) (goto-char region-end) (insert "\n- ") (insert (mapconcat 'identity bolded-sections "\n- ")) (newline)) (message "No region selected."))) ``` ## Writing and knowledge management I do my writing mostly in org-journal and org-roam. ```elisp (setq org-journal-file-format "%Y-%m-%d.org") ``` ### Writing mode A couple of customisations to make writing prose a nice experience. ```elisp (defun ngm-visual-line-motion () "So j and k move up and down more like you'd expect in visual line mode" (interactive) (define-key evil-motion-state-map "j" 'evil-next-visual-line) (define-key evil-motion-state-map "k" 'evil-previous-visual-line)) (defun ngm-journal-mode () "Set up journalling mode the way that I like it" (interactive) (olivetti-mode) (variable-pitch-mode 1) (face-remap-add-relative 'variable-pitch '(:family "Roboto Slab" :height 140)) (ngm-visual-line-motion) (setq company-backends '(company-capf)) ; for org-roam completion ) ``` ### word counts Needs the org-wc package. ```elisp (require 'org-wc) (spacemacs/declare-prefix "o" "own-menu") (spacemacs/set-leader-keys "ow" 'org-wc-display nil) (spacemacs/set-leader-keys "oc" 'org-cite-insert nil) ``` ### citations Handy links: - https://kristofferbalintona.me/posts/202206141852/ - https://blog.tecosaur.com/tmio/2021-07-31-citations.html#working-with-zotero ```elisp (require 'citeproc) (require 'oc-csl) (require 'oc-biblatex) (setq org-cite-export-processors '((latex csl) (t csl))) (setq org-cite-csl-styles-dir (expand-file-name "~/Nextcloud2/Zotero/styles/")) ``` ### org-roam org-roam builds on top of org-mode, but I feel like it deserves it's own section. #### Setup ```elisp (setq org-roam-directory "/home/neil/commonplace") (setq org-roam-dailies-directory "journal") ``` #### Load my helper files ```elisp (load "~/.emacs.d/private/commonplace-lib/commonplace-lib.el") ``` #### Customise the slug function ```elisp (with-eval-after-load 'org-roam (cl-defmethod org-roam-node-slug ((node org-roam-node)) (let ((title (org-roam-node-title node))) (commonplace/slugify-title title))) ) ``` #### Linking to other files Because I use export heavily, I'm kind of dependent on file-based links right now (as far as I understand). This is going to be problematic when org-roam v2 rolls around, but cross that bridge when we come to it. ```elisp (setq org-roam-prefer-id-links t) ``` #### Prefer immediate DB update method. This updates the DB on save, rather than on an idle timer. I was finding idle timer frustrating, as the unexpected DB update interrupted my flow. Updating on save works better for me, as I tend to pause momentarily after a save anyway, as I usually save at the end of a sentence. ```elisp (org-roam-db-autosync-enable) ``` #### Wikilink syntax for adding links For inserting links to other wiki pages more quickly, essentially with wikilink syntax. See: [[Using fuzzy links AKA wikilinks in org-roam]]. ```elisp (require 'key-chord) (key-chord-mode 1) (key-chord-define org-mode-map "[[" #'ngm/insert-roam-link) (defun ngm/insert-roam-link () "Inserts an Org-roam link." (interactive) (insert "[[roam:]]") (backward-char 2)) ``` #### Tags ```elisp (setq org-roam-tag-sources '(prop last-directory)) ``` #### org-roam capture templates Add CREATED and LASTMODIFIED properties to the new note. ```elisp (setq org-roam-capture-templates '(("d" "default" plain "%?" :if-new (file+head "${slug}.org" "#+TITLE: ${title} #+CREATED: %u #+LAST_MODIFIED: %U ") :unnarrowed t))) ;; (setq org-roam-capture-templates ;; '(("d" "default" plain (function org-roam--capture-get-point) ;; "%?" ;; :file-name "${slug}" ;; :head "#+title: ${title}\n#+CREATED: %U\n#+LAST_MODIFIED: %U\n\n" ;; :unnarrowed t))) ;; (setq org-roam-dailies-capture-templates '(("d" "daily" plain (function org-roam-capture--get-point) "" ;; :immediate-finish t ;; :file-name "journal/%<%Y-%m-%d>" ;; :head "#+TITLE: %<%Y-%m-%d>"))) ``` #### Updating timestamps on save I would prefer to do this on org-roam files only. See [Update a field (#+LASTMODIFIED: ) at save - How To - Org-roam](https://org-roam.discourse.group/t/update-a-field-last-modified-at-save/321/19). Doesn't seem to work though. ```elisp (setq time-stamp-active t time-stamp-start "#\\+LAST_MODIFIED:[ \t]*" time-stamp-end "$" time-stamp-format "\[%Y-%02m-%02d %3a %02H:%02M\]") (add-hook 'before-save-hook 'time-stamp nil) ``` Using the org-roam-timestamps package. ```elisp (add-hook 'org-mode-hook (org-roam-timestamps-mode)) ``` #### Graph settings Exclude some of the big files from the graph. ```elisp (setq org-roam-graph-exclude-matcher '("sitemap" "index" "recentchanges")) ``` #### org-roam-ui Requires v2. Used to be org-roam-server. ```elisp ;(require 'websocket) ;(add-to-list 'load-path "~/.emacs.d/private/org-roam-ui") ;(load-library "org-roam-ui") ;(use-package websocket ; :after org-roam) ;(use-package org-roam-ui ; :after org-roam ;; or :after org ; :hook (org-roam . org-roam-ui-mode) ; :config ; ) ``` #### org-roam-bibtex ```elisp (setq org-cite-global-bibliography '("~/commonplace/My Library.bib")) ``` #### Misc See: https://org-roam.discourse.group/t/possible-to-ignore-directories-within-the-org-directory/2454/ ```elisp (setq org-roam-file-exclude-regexp (concat "^" (expand-file-name org-roam-directory) "/tempdir/")) ``` ## Look'n'feel ### Themes ```elisp (doom-themes-treemacs-config) (doom-themes-org-config) ``` ### Solaire See https://github.com/hlissner/emacs-solaire-mode This appears to have been updated and I need to do something ```elisp ;;(require 'solaire-mode) ;; Enable solaire-mode anywhere it can be enabled ;; (solaire-global-mode +1) ;; ;; To enable solaire-mode unconditionally for certain modes: ;; (add-hook 'ediff-prepare-buffer-hook #'solaire-mode) ;; ;; ...if you use auto-revert-mode, this prevents solaire-mode from turning ;; ;; itself off every time Emacs reverts the file ;; (add-hook 'after-revert-hook #'turn-on-solaire-mode) ;; ;; highlight the minibuffer when it is activated: ;; ;(add-hook 'minibuffer-setup-hook #'solaire-mode-in-minibuffer) ;; ;; if the bright and dark background colors are the wrong way around, use this ;; ;; to switch the backgrounds of the `default` and `solaire-default-face` faces. ;; ;; This should be used *after* you load the active theme! ;; ;; ;; ;; NOTE: This is necessary for themes in the doom-themes package! ;; (solaire-mode-swap-bg) ``` ### Tabs (centaur) Not currently using this, as I think it broke something. ```elisp ;; centaur-tabs configuration ;; https://github.com/ema2159/centaur-tabs ;(require 'centaur-tabs) ;(centaur-tabs-mode t) ;(global-set-key (kbd "C-") 'centaur-tabs-backward) ;(global-set-key (kbd "C-") 'centaur-tabs-forward) ;(centaur-tabs-mode) ;(centaur-tabs-headline-match) ;(setq centaur-tabs-set-modified-marker t ; centaur-tabs-modified-marker " ● " ; centaur-tabs-cycle-scope 'tabs ; centaur-tabs-height 35 ; centaur-tabs-set-icons t ; centaur-tabs-close-button " × ") ;(dolist (centaur-face '(centaur-tabs-selected ; centaur-tabs-selected-modified ; centaur-tabs-unselected ; centaur-tabs-unselected-modified)) ; (set-face-attribute centaur-face nil :family "Noto Sans Mono" :height 100)) ``` ### Helm ```elisp (defun open-local-file-projectile (directory) "Helm action function, open projectile file within DIRECTORY specify by the keyword projectile-default-file define in `dir-locals-file'" (let ((default-file (f-join directory (nth 1 (car (-tree-map (lambda (node) (when (eq (car node) 'projectile-default-file) (format "%s" (cdr node)))) (dir-locals-get-class-variables (dir-locals-read-from-dir directory)))))))) (if (f-exists? default-file) (find-file default-file) (message "The file %s doesn't exist in the select project" default-file) ) ) ) (with-eval-after-load "helm-projectile" (helm-add-action-to-source "Open default file" 'open-local-file-projectile helm-source-projectile-projects) ) ;; (add-to-list 'helm-source-projectile-projects-actions '("Open default file" . open-local-file-projectile) t) ;; https://github.com/syl20bnr/spacemacs/issues/13100 ;(setq completion-styles '(helm-flex)) ``` #### Remove duplicates in helm command history See: https://github.com/syl20bnr/spacemacs/issues/13564 ```elisp (setq history-delete-duplicates t) ``` ### Scrolling ```elisp (good-scroll-mode 1) ``` ## Communications ### mu4e (mail) ```elisp ;; mu4e (setq mu4e-maildir "~/Maildir" mu4e-attachment-dir "~/downloads" mu4e-sent-folder "/Sent" mu4e-drafts-folder "/Drafts" mu4e-trash-folder "/Trash" mu4e-refile-folder "/Archive") (setq user-mail-address "neil@doubleloop.net" user-full-name "Neil Mather") ;; Get mail (setq mu4e-get-mail-command "mbsync protonmail" mu4e-change-filenames-when-moving t ; needed for mbsync mu4e-update-interval 120) ; update every 2 minutes (defun htmlize-and-send () "When in an org-mu4e-compose-org-mode message, htmlize and send it." (interactive) (when (member 'org~mu4e-mime-switch-headers-or-body post-command-hook) (org-mime-htmlize) (message-send-and-exit))) (add-hook 'org-ctrl-c-ctrl-c-hook 'htmlize-and-send t) ;; composing mail ;(setq mu4e-compose-format-flowed nil) ;(add-hook 'mu4e-compose-mode-hook (lambda () (turn-off-auto-fill) (use-hard-newlines -1))) ;; enable format=flowed ;; - mu4e sets up visual-line-mode and also fill (M-q) to do the right thing ;; - each paragraph is a single long line; at sending, emacs will add the ;; special line continuation characters. ;; - also see visual-line-fringe-indicators setting below (setq mu4e-compose-format-flowed t) ;; because it looks like email clients are basically ignoring format=flowed, ;; let's complicate their lives too. send format=flowed with looong lines. :) ;; https://www.ietf.org/rfc/rfc2822.txt (setq fill-flowed-encode-column 998) ;; in mu4e with format=flowed, this gives me feedback where the soft-wraps are (setq visual-line-fringe-indicators '(left-curly-arrow right-curly-arrow)) ;; Send mail (setq message-send-mail-function 'smtpmail-send-it smtpmail-auth-credentials "~/.authinfo" ;; Here I assume you encrypted the credentials smtpmail-smtp-server "127.0.0.1" smtpmail-smtp-service 1025) ;; look'n'feel (setq mu4e-html2text-command 'mu4e-shr2text) (setq shr-color-visible-luminance-min 60) (setq shr-color-visible-distance-min 5) (setq shr-use-colors nil) (advice-add #'shr-colorize-region :around (defun shr-no-colourise-region (&rest ignore))) ``` ### IRC (erc) ```elisp (setq erc-hide-list '("JOIN" "PART" "QUIT")) ``` ## Misc ### Long lines Fix problem with long lines. Was mainly giving me grief with Magit - [[Magit performance on minified JS and CSS]]. ```elisp (global-so-long-mode 1) ``` ### Tidal ```elisp ;; tidal ;;(add-to-list 'load-path "/home/neil/.emacs.d/private/tidal") ;;(require 'tidal) ``` ### cook-mode ```elisp (load "~/.emacs.d/private/cook-mode/cook-mode.el") ``` ### undo tree It was leaving lots of junk files on the filesystem. See: https://github.com/syl20bnr/spacemacs/issues/15426 This disables it globally. #### TODO Need to review this, I think it'll be resolved more elegantly upstream at some point. ```elisp (with-eval-after-load 'undo-tree (setq undo-tree-auto-save-history nil)) ```