BHW Emacs Configuration

My literate Emacs config dotfile


Updated: 2026-05-08 Fri 21:56

This configuration file serves as the single source of truth for all Emacs customizations. It includes notes on setup and usage. It is continuously updated to reflect my current usage patterns.


1. Emacs

Greetings! I currently use Doom Emacs on Windows Subsystem for Linux (WSL). Instructions for installing Emacs on WSL2. Contact me if something is unclear or should you have any questions. Run the following on a new computer to clone my configuration,

  # Runtime dependencies of my Emacs config.
  sudo apt install mu4e aspell fd-find ripgrep curl hunspell sdcv transmission-daemon
  mkdir ~/.ssh
  # Download Keepass and SSH keys from Cloud backup.
  # Copy over SSH private keys through Windows Explorer =\\wsl$=
  # in Windows Explorer address bar.
  chmod 600 ~/.ssh/github
  chmod 600 ~/.ssh/gitlab
  # See heading "Github Tutorial" if you are unfamiliar with git.
  # https://github.com/doomemacs/doomemacs?tab=readme-ov-file#install
  git clone git@gitlab.com:bhw/emacs-config.git ~/.config/doom
  git clone git@gitlab.com:bhw/project-maria.git ~/project-maria
  # Copy over project-jerome manually
  scp -r root@oci-a1-flex:/project-jerome ~/

If you are setting up on a new computer then you will have install and configure everything under B. Important in Linux Program Directory. See doom emacs private config

SPC i y to autocomplete paste SPC h r r SPC h k SPC s E SPC h d f SPC h d p c Spacemacs refugee, Winum config, doom-localleader-key, org mode bold highlighting

1.1. Emacs Initialization

Doom Emacs configuration below is ordered in the same order they appear in file:///home/ben/.config/doom/init.el, as that is the order they are loaded in. Individual packages installed by myself are found at the end of the most relevant Doom modules.

;;; init.el -*- lexical-binding: t; -*-
(setf evil-respect-visual-line-mode t)
(doom!
 :completion
 (corfu +orderless)
 vertico

 :ui
 doom
 dashboard
 hl-todo
 (modeline +light)
 (popup +defaults)
 (window-select +numbers)
 workspaces

 :editor
 (evil +everywhere)
 fold
 (format +onsave)
 lispy
 multiple-cursors
 snippets
 (whitespace +guess +trim)
 word-wrap

 :emacs
 (dired +dirvish)
 electric
 eww
 tramp
 undo
 vc

 :term
 vterm

 :checkers
 syntax
 (spell +hunspell)

 :tools
 biblio
 (eval +overlay)
 lookup
 (magit +forge)
 pdf

 :lang
 common-lisp
 data
 emacs-lisp
 json
 (latex +cdlatex)
 ledger
 markdown
 (org +noter +pandoc +contacts)
 plantuml
 graphviz
 sh

 :email
 (mu4e +org +mbsync)

 :app
 (rss +org +youtube)

 :config
 (default +bindings +smartparens))

1.2. Package Configuration

;; -*- no-byte-compile: t; -*-
;;; $DOOMDIR/packages.el

(package! evil-snipe :disable t)
(package! mu4e-alert :disable t)
(package! ef-themes)
(package! consult-mu
  :recipe (:host github :repo "armindarvish/consult-mu" :files (:defaults "extras/*.el")))
(package! ement)
(package! org-node)
(package! org-contacts)
(package! anki-editor)
(package! org-transclusion)
(package! transmission)
(package! lexic)
(package! casual)
(package! biome)
(package! webfeeder)
(package! casual)
(package! claude-code-ide
  :recipe (:host github :repo "manzaltu/claude-code-ide.el"))
;; (package! mu4e-send-delay
;;   :recipe (:host github :repo "krisbalintona/mu4e-send-delay"))
;; (package! greader)
;; (package! literate-calc-mode)
;; (package! listen)
;; (package! org-fragtog)
;; Have my own version of this?
;; org-analyzer

1.3. Emacs Configuration

Miscellaneous configuration for features built in to Emacs which do not belong clearly to a well-defined package.

;;; $DOOMDIR/config.el -*- lexical-binding: t; -*-
;;---------------------------------------------------------------------------
(defconst +project-maria-dir+ (expand-file-name "~/project-maria/")
  "Absolute path to the project-maria directory.")
(defconst +project-jerome-dir+ (expand-file-name "~/project-jerome/")
  "Absolute path to the project-jerome directory.")

;; might have to install xprop and wmctrl. See fullscreen.sh
(add-to-list 'initial-frame-alist '(fullscreen . fullboth))
(add-to-list 'default-frame-alist '(fullscreen . fullboth))

(defun get-authinfo-password (machine login)
  (if-let* ((credential (car (auth-source-search :max 1
                                                 :host machine
                                                 :user login
                                                 :require '(:secret))))
            (secret (plist-get credential :secret)))
      (if (functionp secret) (funcall secret) secret)
    (message "No password found for %s@%s" login machine)))

(load! "private-packages/personal-info.el")

(defun site/always-save-advice (oldfn &optional arg)
  "Overwrite `yes-or-no-p' in OLDFN.
    The new temporary function will return non-nil, when the message
    wants to save modified buffers, without querying the user.
    Otherwise the original behaviour is preserves, and ARG is passed
    on to OLDFN."
  (cl-letf* ((real-yes-or-no-p (symbol-function 'yes-or-no-p))
             ((symbol-function 'yes-or-no-p)
              (lambda (msg)
                (or (string= msg "Modified buffers exist; exit anyway? ")
                    (funcall real-yes-or-no-p msg)))))
    (funcall oldfn arg)))

(advice-add #'save-buffers-kill-emacs :around #'site/always-save-advice)

(setf org-directory +project-maria-dir+
      auto-save-interval 1000
      auto-revert-interval 30
      auto-save-timeout nil
      browse-url-generic-program  "/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe"
      browse-url-browser-function #'browse-url-generic
      initial-major-mode 'org-mode)
(transient-mark-mode 1)

1.3.1. Completion Module Config

C-; to embark act.

Emacs Carnival Feb 2026 wrap-up: Completion :: Sacha Chua

;;---------------------------------------------------------------------------
(after! vertico
  (map! :map vertico-map
        "C-d" #'vertico-quick-jump))

(map! :n ";" #'consult-line
      :leader "SPC" #'consult-buffer)
Consult Config
;;---------------------------------------------------------------------------
(after! consult
  (require 'org-node)

  (defvar bhw/consult-source-filesystem
    `(:name     "Find"
      :narrow   ?f
      :category file
      :face     consult-file
      :history  file-name-history
      ;; Synchronously fetch the file list using fd.
      ;; NOTE: pass "~/" as an explicit path argument — without it, fd uses CWD
      ;; implicitly and the global ignore file (~/.config/fd/ignore) is not
      ;; applied, so Trash and other excluded dirs leak through.
      :items    ,(lambda ()
                   (when (executable-find "fdfind")
                     (let ((home (expand-file-name "~")))
                       ;; fd requires an explicit pattern before the path:
                       ;;   fdfind [opts] . /home/ben
                       ;; A bare path (no leading pattern) is parsed as the
                       ;; pattern and errors.  Without an explicit path fd
                       ;; uses CWD implicitly, which bypasses the global
                       ;; ignore file (~/.config/fd/ignore), causing Trash
                       ;; and other excluded dirs to appear.
                       ;; fd returns absolute paths when given an absolute
                       ;; search root, so strip the prefix before re-adding ~/
                       (mapcar (lambda (file)
                                 (concat "~/" (string-remove-prefix (concat home "/") file)))
                               (ignore-errors
                                 (process-lines "fdfind"
                                                "--follow"
                                                "--type" "f"
                                                "--hidden"
                                                "--exclude" ".git"
                                                "--color" "never"
                                                "." home))))))
      :action   ,#'consult--file-action))

  (defvar bhw/consult-source-project-maria
    `(:name     "Project-Maria rg"
      :narrow   ?m
      :category consult-grep
      :history  consult--grep-history
      :state    ,#'consult--grep-state
      :action   ,(lambda (c) (consult--jump (consult--grep-position c)))
      :async
      ,(consult--process-collection
           (consult--ripgrep-make-builder
            (list (expand-file-name "~/project-maria")))
         :transform
         (consult--grep-format
          (consult--ripgrep-make-builder
           (list (expand-file-name "~/project-maria"))))
         :file-handler t)))

  ;; (defvar bhw/consult-source-emacs-commands
  ;;   (list :name "Emacs Commands"
  ;;         :narrow ?e
  ;;         :category 'command
  ;;         :items (lambda ()
  ;;                  (let ((cmds))
  ;;                    (mapatoms (lambda (elt)
  ;;                                (when (commandp elt)
  ;;                                  (push (symbol-name elt) cmds))))
  ;;                    cmds))
  ;;         :action (lambda (cmd-str)
  ;;                   (command-execute (intern-soft cmd-str))))
  ;;   "A simple consult source for Emacs commands.")

  (defvar org-node-history nil
    "History list for org-node selections in `consult-buffer'. The `:history 'org-node-history` property in `bhw/consult-source-org-node` tells `consult` to record selections into the variable `org-node-history`. But that variable was never declared with `defvar`, so it was void. When `consult--multi` tried to call `(add-to-history org-node-history \"Ecclesiastes 3:1\")`, Emacs raised a `void-variable` error.")

  (defvar bhw/consult-source-org-node
    (list :name     "Org Node"
          :narrow   ?n
          :category 'org-node
          :face     'consult-file
          :history  'org-node-history
          :items    (lambda ()
                      (org-node-cache-ensure)
                      (hash-table-keys org-node--candidate<>entry))
          :action   (lambda (cand)
                      (require 'org-id)
                      (let ((node (gethash cand org-node--candidate<>entry)))
                        (if node
                            (org-node-goto node)
                          ;; Fallback if the user somehow selects a non-existent item
                          ;; though :new usually handles creation
                          (org-node-create cand (org-id-new)))))
          :new      (lambda (cand)
                      (require 'org-id)
                      ;; Handle blank input for creation specifically
                      (let ((title (if (string-blank-p cand)
                                       (funcall org-node-blank-input-title-generator)
                                     cand)))
                        (org-node-create title (org-id-new)))))
    "Source for `org-node' to be used in `consult-buffer'.")

  (setf consult-buffer-sources '(consult-source-buffer
                                 bhw/consult-source-org-node
                                 bhw/consult-source-project-maria
                                 bhw/consult-source-filesystem
                                 ;; bhw/consult-source-emacs-commands
                                 )))

1.3.2. User Interface Config

;;---------------------------------------------------------------------------
(dotimes (i 10)
  (define-key doom-leader-map (number-to-string i)
              (intern (format "winum-select-window-%s"
                              (if (= i 0) "0-or-10" (number-to-string i))))))

(setf doom-font (font-spec :family "IosevkaTermSlab Nerd Font" :size 26.0 :weight 'semi-light)
      doom-theme 'modus-flexoki-dark
      doom-modeline-height 14
      display-line-numbers-type nil
      confirm-kill-emacs nil
      confirm-kill-processes nil)

(after! doom-ui
  (global-set-key [remap delete-frame] nil))

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

(after! nerd-icons
  (setf nerd-icons-font-family "IosevkaTermSlab Nerd Font"))

(dolist (fn '(+dashboard-widget-banner
              +dashboard-widget-loaded
              +dashboard-widget-footer))
  (remove-hook '+dashboard-functions fn))

1.3.3. Modus Flexoki Config

Modus Flexoki provides Modus-themes variants built on the Flexoki palette. The package autoloads register the theme directory with custom-theme-load-path, so :defer t is sufficient — switch via doom-theme or M-x load-theme.

(use-package! modus-flexoki
  :defer t)

1.3.4. Editor Config

<- Searching - File Content <- Windows 10 Reference Manual

For a guide, see Github - Noctuid Evil Guide

joaotavora/yasnippet#998 expand with no trigger key

;;---------------------------------------------------------------------------
(+global-word-wrap-mode +1)
(dolist (pair '(([?\(] . [?\[]) ([?\[] . [?\(])
                ([?\)] . [?\]]) ([?\]] . [?\)])))
  (define-key key-translation-map (car pair) (cdr pair)))
;; Bind `s` in both motion and normal state maps for avy.  Normal state
;; needs an explicit binding so modes with `s` as a prefix (e.g. dirvish)
;; can still override it locally.
(dolist (m (list evil-motion-state-map evil-normal-state-map))
  (define-key m (kbd "s") 'avy-goto-word-or-subword-1))
(define-key evil-normal-state-map (kbd "q") 'kill-current-buffer)

(map! :n
      ","   nil ; See `doom-localleader-key' below
      "C-j" #'+evil/insert-newline-below
      "C-k" #'+evil/insert-newline-above)
(setf doom-localleader-key ","
      avy-all-windows t
      +word-wrap-extra-indent nil)
Savehist Config (unused)

For more session management solutions. spacemacs - High CPU/memory usage and abnormally large savehist file? - Emacs…

If you recreate this situation, and the file is really massive, opening it in Emacs to check might not be a great idea. I would use grep to find out the offset for each variable, and just look for any really big jumps:

  $ grep -E -b -o '^\(setq [^ ]+' ~/.config/emacs/.local/cache/savehist

(Or whatever your savehist-file path is – the standard filename is ~/.emacs.d/history but I presume ~/.emacs.d/.cache/savehist is what Spacemacs configures.)

  (setf history-length 25
        savehist-save-minibuffer-history nil
        savehist-autosave-interval nil
        kill-ring-max 200
        savehist-mode nil)
  (delq 'mark-ring savehist-additional-variables)
  (delq 'global-mark-ring savehist-additional-variables)
  (delq 'search-ring savehist-additional-variables)
  (delq 'regexp-search-ring savehist-additional-variables)
  (delq 'extended-command-history savehist-additional-variables)
  (delq 'kill-ring savehist-additional-variables)
  (put 'bibtex-completion-cache 'history-length 10)
  (push 'bibtex-completion-cache savehist-additional-variables)
  (push 'helm-ff-history savehist-additional-variables)
  (push 'org-clock-history savehist-additional-variables)
  ;; Emacs profiler shows `savehist-autosave' is very performance intensive.
  (add-hook 'kill-emacs-hook #'savehist-save) ; Savehist only on exit.

1.3.5. Emacs Config


1.3.6. Dired Config

;;---------------------------------------------------------------------------
(map! :after dirvish                    ; Doom overrides Dired with Dirvish.
      :map dirvish-mode-map
      :n ";" #'consult-line
      :n "A" #'fuco/org-attach-visit-headline-from-dired)

;; Doom's dired module binds `s` as a prefix (ss/sS/sh → symlink ops) in
;; dirvish-mode-map's normal-state auxiliary keymap.  evil-local-set-key
;; sets a buffer-local binding that has higher priority, so pressing `s`
;; in dired gives avy instead of waiting for a second key.
(add-hook 'dired-mode-hook
          (lambda ()
            (evil-local-set-key 'normal (kbd "s") #'avy-goto-word-or-subword-1)
            (evil-local-set-key 'normal (kbd "e") #'wdired-change-to-wdired-mode)))

1.3.7. EWW Config

;;---------------------------------------------------------------------------
(after! eww
  (add-hook! 'eww-after-render-hook #'eww-readable)
  (map! :map eww-mode-map
        :n "Y" #'+org/yank-link))

1.3.8. Term Config

Key Effect
M-! Shell-command. Prefix with C-u (SPC-u for spacemacs) to insert output at point
M-S-\ Shell-command-on-region. Pipe region into shell command
M-: Eval s-exp

Shell Tricks That Actually Make Life Easier (And Save Your Sanity) | Larvitz …

;;---------------------------------------------------------------------------
(after! vterm
  (map! :map vterm-mode-map
        :n "S" #'vterm-send-C-r
        :n ", <escape>" #'vterm-send-escape))

1.3.9. Checkers Config

;;---------------------------------------------------------------------------
(setf ispell-alternate-dictionary "/usr/share/hunspell/en_CA.dic")
(defun +spell/correct-previous-highlight ()
  "Jump to previous error, correct it, then return to the original position in insert mode."
  (interactive)
  ;; 1. Save the current position with a Marker
  (let ((origin (point-marker)))
    ;; 2. Ensure marker stays at the end if we are typing at the very end of the line
    (set-marker-insertion-type origin t)

    (when (featurep 'spell-fu)
      ;; 3. Go to error and correct
      (spell-fu-goto-previous-error)
      (+spell/correct))

    ;; 4. Jump back to the marker (the original position)
    (goto-char origin)

    ;; 5. Clean up the marker to free memory
    (set-marker origin nil)

    ;; 6. Force Evil into Insert Mode so you can keep typing immediately
    (evil-insert 1)))
(map! :g   "C-s" nil
      :nim "C-s" #'+spell/correct-previous-highlight)

1.3.10. Tools Config

;;---------------------------------------------------------------------------
Pdf-Tools Config

PDF Tools uses a C library to convert PDF (which are images) to PNG format to store in Emacs memory. Installation: for the first time run, please make sure to M-x pdf-tools-install

pdf-occur notes: lines matching PRCE means Perl Compatible Regular Expressions. See regex list here: PCRE Regular Expression Cheatsheet - Debuggex

  ;; If bash $top shows high emacs memory usage, try the following.
  ;; Not sure if they work tbh.
  (pdf-cache-clear-data)
  (garbage-collect)
;;---------------------------------------------------------------------------
;; Disable evil-collection's pdf bindings so we have full control
;; over pdf-view-mode-map (same pattern used for ement below).
(after! evil-collection
  (setf evil-collection-mode-list (delq 'pdf evil-collection-mode-list)))

(map! :after pdf-tools
      :map pdf-view-mode-map
      :n "i"              #'org-noter-insert-note
      :n "M-i"            #'bhw/org-noter-insert-precise-quote
      :n "d"              #'pdf-view-scroll-up-or-next-page
      :n "u"              #'pdf-view-scroll-down-or-previous-page
      :n "s"              #'avy-goto-word-or-subword-1
      :n "f"              #'pdf-view-set-slice-from-bounding-box
      :n ";"              #'pdf-occur
      :n "q"              #'bhw/org-noter-quit
      :n "gt"             #'pdf-view-goto-page
      :n "w"              #'bhw/pdf-view-fit-width
      :n "y"              #'bhw/pdf-view-yank
      :n [down-mouse-1]   #'bhw/pdf-view-mouse-set-region
      :n [C-down-mouse-1] #'pdf-view-mouse-extend-region
      :n [M-down-mouse-1] #'pdf-view-mouse-set-region-rectangle)

;; Prevent Evil from entering visual-mode when pdf-view activates the
;; mark for text selection.  Without this, Evil hijacks the region and
;; tries to select the PDF image object, breaking click-drag selection.
;; (Mirrors evil-collection-pdf-disable-visual-mode.)
(add-hook! 'pdf-view-mode-hook
  (pdf-view-midnight-minor-mode)
  (run-at-time "0.1 sec" nil (lambda () (when (derived-mode-p 'pdf-view-mode)
                                          (pdf-view-redisplay t))))
  (remove-hook 'activate-mark-hook 'evil-visual-activate-hook t))

(defun bhw/pdf-view-fit-width ()
  "Fit PDF page width, re-displaying first to avoid stale image errors."
  (interactive)
  (pdf-view-redisplay t)
  (pdf-view-fit-width-to-window))

(defun bhw/pdf-view-mouse-set-region (event)
  "Start a PDF text selection, re-displaying first to avoid stale image errors."
  (interactive "@e")
  (pdf-view-redisplay t)
  (pdf-view-mouse-set-region event))

(defun bhw/pdf-view-yank ()
  "Yank the text of the active PDF region into the kill ring."
  (interactive)
  (pdf-view-assert-active-region)
  (let ((txt (pdf-view-active-region-text)))
    (pdf-view-deactivate-region)
    (kill-new (mapconcat #'identity txt "\n"))
    (message "Yanked %d characters." (length (car kill-ring)))))

;; Fix: `pdf-view-image-size' passes the raw display property to the C
;; primitive `image-size' when DISPLAYED-P is nil.  After slicing
;; (`pdf-view-set-slice-from-bounding-box'), the display property
;; becomes ((slice X Y W H) (image …)) — a compound form that the C
;; primitive `image-size' cannot handle (it expects (image …)).  This
;; advice unwraps the image spec from the sliced form so `image-size'
;; receives a bare (image …) descriptor.
(defadvice! bhw/pdf-view-image-size-handle-slice-a (fn &optional displayed-p window page)
  :around #'pdf-view-image-size
  (if displayed-p
      ;; `image-display-size' already handles the sliced compound form.
      (funcall fn displayed-p window page)
    ;; Replicate the display-prop lookup that `pdf-view-image-size' does
    ;; internally, but unwrap any slice wrapper before calling `image-size'.
    (let ((display-prop (if pdf-view-roll-minor-mode
                            (let ((w (if (windowp window) window (selected-window))))
                              (overlay-get (pdf-roll-page-overlay
                                            (or page (pdf-view-current-page w)) w)
                                           'display))
                          (image-get-display-property))))
      (image-size (if (eq (car-safe (car display-prop)) 'slice)
                      (cadr display-prop)   ; ((slice …) (image …)) → (image …)
                    display-prop)           ; already (image …)
                  t))))

(after! pdf-tools
  (setf pdf-view-use-scaling nil
        pdf-view-max-image-width 4800
        pdf-cache-image-limit 512
        pdf-cache-prefetch-delay 0.3
        ;; see also `pdf-view-midnight-minor-mode'
        pdf-view-midnight-invert nil
        pdf-tools-enabled-modes
        '(pdf-view-dark-minor-mode
          pdf-history-minor-mode
          pdf-isearch-minor-mode
          pdf-links-minor-mode
          pdf-misc-minor-mode
          pdf-misc-size-indication-minor-mode
          pdf-occur-global-minor-mode))
  (pdf-cache-prefetch-minor-mode -1))

Old config:

  (setf
   pdf-view-use-scaling t
   pdf-view-display-size 'fit-width
   pdf-view-resize-factor 1.1
   image-cache-eviction-delay 128
   pdf-cache-image-limit 128
   pdf-view-restore-filename "~/.emacs.d/.cache/.pdf-view-restore")
  ;; Custom function to allow double page scrolling by calling
  ;; my-pdf-view-double-scroll-horizontal-view
  (defun my-pdf-view-double-scroll-up-or-next-page (&optional arg)
    "Scroll page up ARG lines if possible, else go to the next page.
  When `pdf-view-continuous' is non-nil, scrolling upward at the
  bottom edge of the page moves to the next page.  Otherwise, go to
  next page only on typing SPC (ARG is nil)."
    (interactive "P")
    (if (or pdf-view-continuous (null arg))
        (let ((hscroll (window-hscroll))
              (cur-page (pdf-view-current-page)))
          (when (or (= (window-vscroll) (image-scroll-up arg))
                    ;; Workaround rounding/off-by-one issues.
                    (memq pdf-view-display-size
                          '(fit-height fit-page)))
            (pdf-view-next-page 2)
            (when (/= cur-page (pdf-view-current-page))
              (image-bob)
              (image-bol 1))
            (set-window-hscroll (selected-window) hscroll)))
      (image-scroll-up arg)))
  (defun my-pdf-view-double-scroll-horizontal-view ()
    (interactive)
    (my-pdf-view-double-scroll-up-or-next-page)
    (other-window 1)
    (my-pdf-view-double-scroll-up-or-next-page)
    (other-window 1))
  ;; add spacemacs major mode keybind
  (spacemacs/set-leader-keys-for-major-mode 'pdf-view-mode "d" 'my-pdf-view-double-scroll-horizontal-view)
  ;; Allow rotating of sheet music in pdfs
  (defun pdf-view--rotate (&optional counterclockwise-p page-p)
    "Rotate PDF 90 degrees.  Requires pdftk to work.\n
  Clockwise rotation is the default; set COUNTERCLOCKWISE-P to
  non-nil for the other direction.  Rotate the whole document by
  default; set PAGE-P to non-nil to rotate only the current page.
  \nWARNING: overwrites the original file, so be careful!"
    ;; error out when pdftk is not installed
    (if (null (executable-find "pdftk"))
        (error "Rotation requires pdftk")
      ;; only rotate in pdf-view-mode
      (when (eq major-mode 'pdf-view-mode)
        (let* ((rotate (if counterclockwise-p "left" "right"))
               (file   (format "\"%s\"" (pdf-view-buffer-file-name)))
               (page   (pdf-view-current-page))
               (pages  (cond ((not page-p)                        ; whole doc?
                              (format "1-end%s" rotate))
                             ((= page 1)                          ; first page?
                              (format "%d%s %d-end"
                                      page rotate (1+ page)))
                             ((= page (pdf-info-number-of-pages)) ; last page?
                              (format "1-%d %d%s"
                                      (1- page) page rotate))
                             (t                                   ; interior page?
                              (format "1-%d %d%s %d-end"
                                      (1- page) page rotate (1+ page))))))
          ;; empty string if it worked
          (if (string= "" (shell-command-to-string
                           (format (concat "pdftk %s cat %s "
                                           "output %s.NEW "
                                           "&& mv %s.NEW %s")
                                   file pages file file file)))
              (pdf-view-revert-buffer nil t)
            (error "Rotation error!"))))))

  (defun pdf-view-rotate-clockwise (&optional arg)
    "Rotate PDF page 90 degrees clockwise.  With prefix ARG, rotate
  entire document."
    (interactive "P")
    (pdf-view--rotate nil (not arg)))

  (defun pdf-view-rotate-counterclockwise (&optional arg)
    "Rotate PDF page 90 degrees counterclockwise.  With prefix ARG,
  rotate entire document."
    (interactive "P")
    (pdf-view--rotate :counterclockwise (not arg)))

  (define-key spacemacs-pdf-view-mode-map (kbd "R") 'pdf-view-rotate-clockwise)
Magit Config
;;---------------------------------------------------------------------------
(after! magit
  (map! :map magit-status-mode-map
        "SPC" #'doom/leader
        "j" #'evil-next-line
        "k" #'evil-previous-line
        "h" #'evil-backward-char
        "l" #'evil-forward-char
        "p" #'magit-push
        "v" #'evil-visual-line
        "V" #'evil-visual-line
        "gg" #'evil-goto-first-line
        "G" #'evil-goto-line))

GitHub - anticomputer/gh-notify: Veneer for the Magit/Forge GitHub porcelain git - Magit: how remove push branch? - Emacs Stack Exchange Cheatsheet · magit/magit Wiki · GitHub git - Add change to a previous commit with Magit - Emacs Stack Exchange To perform code review on Github from emacs, see https://github.com/charignon/github-review, and another iteration on top of it,GitHub - wandersoncferreira/code-review: Code Reviews in Emacs

Key Effect
+ Widen/Narrow hunk scope
M-n/M-p In a commit buffer, go back to previous commit messages
\ or C-t Switch to text mode in a magit buffer

Thanks for linking the HN discussion, good stuff. The comments mention this functionality is already available in magit-blame, though not very well advertised.

  1. `M-x RET magit-blame RET m` (or `b`) inside a source code buffer to activate the magit blame minor mode and then move the pointer around. There should be blame context in the minibuffer.
  2. `M-x RET magit-blame-cycle-style` (`c` when in magit-blame mode). This is awesome. Might just replace `SPC g f l (magit-log-buffer-file)` and `C-x v g (vc-annotate)` for me.

(“<remap> <vc-diff>” . magit-diff-buffer-file) (“<remap> <vc-print-log>” . magit-log-buffer-file) (“<remap> <vc-print-root-log>” . magit-log-all) (“<remap> <vc-annotate>” . magit-blame-addition)

  (setf forge-owned-accounts '(("BenedictHW" :remote-name "origin"))
        magit-save-repository-buffers 'dontask)
Magit Layer

use magit-file-rename to rename files already tracked by git. Once again, Git in Spacemacs/Emacs with Magit - YouTube .

Key Effect
SPC g s Open Git Status
s to stage selected file SPC-u S to stage all changes.
c to commit
,, confirm final commit
SPC g f l Find history of all commits that affected the highlighted region
C-x v g To complement SPC g f l
     # Git is also garbage collected through other commands
     git gc --aggressive
Github Tutorial

<- Dotfiles & Configuration Files

About SSH - GitHub Docs

  1. Setting up SSH

    Get a github account. Download and install git.

      sudo apt install git
    

    Verify that ~/.gitconfig exists and the contents match Dotfiles & Configuration Files

    Look to see if you have files ~/.ssh/idrsa and ~/.ssh/idrsa.pub. If not, create such public/private keys: Open a terminal/shell and type:

        ssh-keygen -o -t ed25519 -C "your_email@example.com" # Github login email.
    

    Copy your public key (the contents of the newly-created idrsa.pub file) into your clipboard. Paste your ssh public key into your github account settings.

    Go to your github Account Settings Click “SSH Keys” on the left. Click “Add SSH Key” on the right. Add a label (like “My laptop”) and paste the public key into the big text box.

    In a terminal/shell, type the following to test it:

       ssh -T git@github.com
    

    If it says something like the following, it worked:

    Hi username! You’ve successfully authenticated, but Github does not provide shell access.

    Now clone your repositories.

    Reduce the size of the git folder:

       git repack -a -d --depth=250 --window=250
    
  2. Signing commits with gpg Assuming you already have a key pair created and Github knows about it.

       # Search for "GPG" inside KeePass and write to private-key.asc.
       gpg --import private-key.asc
       # https://www.bhw.name/contact to find and write to public-key.asc.
       gpg --import public-key.asc
       # Verify that ~/.gnupg/gpg-agend.conf exists, see dotfile.org.
       # Edit gpg key to raise trust level to 5 - ultimate.
       gpglogin # see .bashrc.
    

    git - How to automatically sign commits with magit? - Emacs Stack Exchange

  3. Syncing forked project
    1. Add the remote (original repo that you forked) and call it “upstream”

      git remote add upstream https://github.com/original-repo/goes-here.git

    2. Fetch (not pull, remember that a pull = fetch and merge in git parlance) all branches of remote upstream

      git fetch upstream

    3. Make sure you are on the correct branch

      git checkout master

    4. Rewrite your master with upstream’s master using git rebase. Will preserve your commits on master, to replay those commits on top of the current upstream/master.

      git rebase upstream/master

    5. Rewrite your master with upstream’s master using git reset. Will NOT preserve your commits on master

      git reset –hard upstream/master

    6. Push your updates to master. You may need to force the push with “–force”, as rebasing recreates new versions of each commit. So if those same commits already exist on your origin repo, which is likely, git will not recognize them and assume your origin is ahead of local and ask you to pull before pushing. the “–force” flag overrides this behaviour.

      git push origin master –force

  4. Read Pro Git At some point you will have to read https://git-scm.com/book/en/v2 pro git.
  5. Learn to use Ediff to resolve conflicts https://www.sentia.com.au/blog/ediff-for-the-brainically-challenged
  6. Cleanup Github Forks
    1. Install curl and jq. Use the following command line to get the names of all your repositories on your Github account. Paste list into new file repo-delete-list.txt

             sudo apt install jq # curl is probably installed by default
             # Note page number in URL. If number of repo > 100, change it.
             curl "https://api.github.com/users/YOUR_GITHUB_ACCOUNT/repos?per_page=100&page=1" | jq -r '.[] | .name'
      
    2. Add YOURGITHUBACCOUNT/ to the beginning of each line. From example-repo to BenedictHW/example-repo.
    3. Register a new personal access token with a deleterepo permission at https://github.com/settings/tokens/new and save it.
    4. Execute the command in shell.

            cd /path/to/repo-delete-list.txt
            while read repo; do curl -X DELETE -H "Authorization: token YOUR_TOKEN" "https://api.github.com/repos/$repo"; done < repo_list_deleting.txt
            sudo apt purge jq
      

      On Windows:

            get-content C:\repo_list_deleting.txt | ForEach-Object { Invoke-WebRequest -Uri https://api.github.com/repos/$_ -Method “DELETE” -Headers @{“Authorization”=”token Your_TOKEN”} }
      
Conventional Commits

Keeps my commit history tidy. Able to be used as extracted changelogs. Like essay or citation conventions.

Feat A new feature
Fix A bug fix
Docs Documentation only changes
Style Changes that do not affect the meaning of the code (white-space formatting)
Refactor Refactoring A code change that neither fixes a bug nor adds a feature
Perf A code change that improves performance
Tests Adding missing tests or correcting existing tests
Build Changes that affect the build system or external dependencies (quicklisp)
CI Changes to our Continous Integration/Continous Delivery configuration files and scripts
Chore Other changes that don’t modify src or test files
Revert Reverts a previous commit
Magit Forged

Magit Forged is a way to view/and compose issues and pull requests of a particular git repository while remaining in Emacs.

  • Setup
    1. Make sure to generate a personal access token from Github
    2. Edit whatever file is designated as your .authinfo file. You can check this via M-x describe-variable RET auth-sources. The relevant entry for magit forged is,

      machine api.github.com login “insertusernamehere”forge password “insertyourpasswordhere”

    3. You may need to restart emacs.
    4. Run M-x forge-pull while inside a buffer of the relevant project.
    5. By pressing SPC g s to invoke magit-status we can then press ? to see that Forge commands are listed under @
Claude Code IDE Config

Beginner’s Guide to Claude Code The Advanced Claude Code Setup Guide | Reading.sh https://github.com/affaan-m/everything-claude-code?tab=readme-ov-file Claude Code Cheat Sheet

  1. What work to do. Which task, in what order, at what scope.
  2. What context to build before doing the work. Which files to read, what to research, what background to provide.
  3. How to describe the work. Clear enough that Claude doesn’t waste tokens figuring out what you meant.
  4. How to verify the output. What does good look like, and how will you check.
  5. Explore: Ask Claude to read relevant files. Be explicit: “Read the authentication module and the user model. Don’t write any code yet.”
  6. Plan: Ask for a plan. “Think hard about how to implement password reset. What files need to change? What edge cases should we handle?”

    think think hard think harder ultrathink

    Give good feedback on the plan. Be explicit about what not to do. Provide examples of desired input and output!

  7. Code: Once you’ve confirmed the plan, ask Claude to implement it. “Implement the plan. Verify each change compiles before moving on.”
    1. “Write tests for [feature] based on these requirements. Don’t implement the feature yet — I want the tests to fail initially.”
    2. Confirm the tests are comprehensive.
    3. “Now implement the feature to make the tests pass. Don’t modify the tests.”
  8. Review:

    1. Claude A implements a feature
    2. /clear or start Claude B in another terminal
    3. Claude B reviews A’s work, identifies issues
    4. Claude A (or a fresh instance) addresses the feedback

    The separation produces better results than single-Claude self-review.

Ctrl+V to paste images into Claude Code:

  • UI mockups as implementation references
  • Screenshots of errors or unexpected behaviour
  • Diagrams explaining desired architecture

File References with Tab Completion Type @ followed by a path and use tab completion to reference files:

Visual Iteration

  1. For frontend work, give Claude a way to see its output:
  2. Set up the Puppeteer MCP server (or similar)
  3. Provide a mockup image “Implement this design. After each change, take a screenshot and compare it to the mockup. Iterate until they match.”

Escape to Interrupt Press Escape during any phase—thinking, tool execution, file editing. Context is preserved. You can redirect: “Stop. Let’s try a different approach.”

Double-Escape to Rewind Press Escape twice to jump back in conversation history. Select an earlier point and continue from there, discarding everything after. Useful when Claude went down a wrong path several turns ago.

/clear for Fresh Context /compact for Compression /btw for side questions For an index of all interactive commands, see Interactive mode - Claude Code Docs

Custom Slash Commands Create reusable prompts as markdown files in .claude/commands/:

;;---------------------------------------------------------------------------
(use-package! claude-code-ide
  :config
  (claude-code-ide-emacs-tools-setup)
  (add-to-list 'load-path (expand-file-name "private-packages" doom-user-dir))
  (require 'claude-code-ide-extensions)
  (claude-code-ide-extensions-setup)
  (defun bhw/claude-code-ide-start-or-toggle-window ()
    "Start claude-code-ide if needed; otherwise toggle the chat window."
    (interactive)
    (condition-case _
        (call-interactively #'claude-code-ide-toggle)
      (error (call-interactively #'claude-code-ide))))

  (map! :leader
        (:prefix-map ("d" . "claude-code-ide")
         :desc "claude-code-ide start/toggle window" "SPC" #'bhw/claude-code-ide-start-or-toggle-window
         :desc "claude-code-ide-insert-at-mentioned" "d" #'claude-code-ide-insert-at-mentioned
         :desc "claude-code-ide-menu" "m" #'claude-code-ide-menu)))

Now /project:review runs this workflow. The $ARGUMENTS keyword passes parameters: /project:fix-issue 1234 with a command containing $ARGUMENTS substitutes the issue number.

Hooks Hooks are shell commands that run at specific lifecycle points: PreToolUse: Before Claude executes any tool PostToolUse: After successful tool execution Stop: When Claude completes a task Example: Run linters automatically after every file edit. Configure via /hooks.

JSON Snippet (~/.claude/settings.json):

{
  "permissions": {
    "defaultMode": "bypassPermissions"
  }
}

Use the above with: https://github.com/justi/claude-code-project-boundary

https://github.com/gsd-build/get-shit-done https://github.com/garrytan/gstack

https://www.reddit.com/r/LocalLLaMA/comments/1nwx1rx/the_most_important_ai_paper_of_the_decade_no/ https://www.reddit.com/r/LocalLLaMA/comments/1oakwgs/stanford_just_dropped_55hrs_worth_of_lectures_on/

Discussion on how to use gptel with org mode buffers. https://mentat.za.net/blog/2026/01/26/gemini-for-code-patches-in-emacs/?utm_source=atom_feed

Background: {1hr Talk} Intro to Large Language Models - YouTube Spreadsheets are all you need.ai – A low-code way to learn AI

I can install a Large Language Model (LLM) locally on our own computer or on a server. I chose the easiest solution to install, the open source LLM created by Meta and packaged by Justine and the open source community: LLaMAfile.

To run on my local WSL system, note the pitfalls and workarounds.

To install on my OCI server,

  ssh oci-a1-flex
  mkdir /opt/llava
  cd /opt/llava
  curl "DOWNLOAD_LINK" -L -O
  chmod +x llava-v1.5-7b-q4.llamafile
  # https://github.com/Mozilla-Ocho/llamafile?tab=readme-ov-file#gotchas
  sudo wget -O /usr/bin/ape https://cosmo.zip/pub/cosmos/bin/ape-$(uname -m).elf
  sudo chmod +x /usr/bin/ape
  sudo sh -c "echo ':APE:M::MZqFpD::/usr/bin/ape:' >/proc/sys/fs/binfmt_misc/register"
  sudo sh -c "echo ':APE-jart:M::jartsr::/usr/bin/ape:' >/proc/sys/fs/binfmt_misc/register"

Create /etc/systemd/system/llamafile.service with the following contents,

  [Unit]
  Description=Run Large Language Model locally through llama.cpp and Cosmopolitan Libc.

  [Service]
  Type=exec
  User=root
  WorkingDirectory=/opt/llava
  ExecStart=/opt/llava/llava-v1.5-7b-q4.llamafile -ngl 9999 --nobrowser --port 8090 --embedding
  Restart=on-failure
  RestartSec=30s

  [Install]
  WantedBy=multi-user.target

Modify caddy reverse proxy to redirect requests to server. E.g.

https://github.com/karthink/gptel/issues/237

Right now I can’t get it to run in the server so I’m running it locally as a systemd service. Praise the Lord, and all the people working on open science.

https://chat.lmsys.org/ to use Claude3-opus and test other models.

https://www.reddit.com/r/LocalLLaMA/ to check up on the latest news.

;;---------------------------------------------------------------------------
(use-package! claude-code-ide
  :config
  (defun bhw/claude-code-ide-start-or-toggle-window ()
    "Start claude-code-ide if needed; otherwise toggle the chat window."
    (interactive)
    (condition-case _
        (call-interactively #'claude-code-ide-toggle-recent)
      (error (call-interactively #'claude-code-ide))))

  (map! :leader
        (:prefix-map ("d" . "claude-code-ide")
         :desc "claude-code-ide start/toggle window" "SPC" #'bhw/claude-code-ide-start-or-toggle-window
         :desc "claude-code-ide-menu" "m" #'claude-code-ide-menu)))
Lexic Config

https://web.archive.org/web/20230622042529/http://download.huzheng.org/

  mkdir /home/ben/.stardict/
  ln -s /home/ben/project-jerome/400-philology/420-english-anglosaxon-languages/.stardict/dic /home/ben/.stardict/
;;---------------------------------------------------------------------------
(use-package! lexic
  :init
  (map! :leader
        :desc "lexic-search-word-at-point" "sx" #'lexic-search-word-at-point
        :desc "lexic-search"               "sX" #'lexic-search)
  :config
  (map! :after lexic
        :map lexic-mode-map
        "SPC" #'doom/leader
        "j"   #'evil-next-line
        "k"   #'evil-previous-line
        "h"   #'evil-backward-char
        "l"   #'evil-forward-char
        "q"   #'lexic-return-from-lexic
        "RET" #'lexic-search-word-at-point
        "a"   #'outline-show-all
        "h"   #'outline-hide-body
        "o"   #'lexic-toggle-entry
        "d"   #'lexic-next-entry
        "u"   #'lexic-previous-entry
        "p"   #'lexic-search-history-backwards
        "n"   #'lexic-search-history-forwards)
  (setf lexic-dictionary-specs '
        (("Webster's Revised Unabridged Dictionary (1913)"
          :short "===========================================================\n Webster's Revised Unabridged Dictionary (1913)\n==========================================================="
          :formatter lexic-format-webster
          :priority 1)
         ("Soule's Dictionary of English Synonyms"
          :short "===========================================================\n Soule's Dictionary of English Synonyms (1871)\n==========================================================="
          :formatter lexic-format-soule
          :priority 2)
         ("Online Etymology Dictionary"
          :short "===========================================================\n Online Etymology Dictionary (2000)\n==========================================================="
          :formatter lexic-format-online-etym
          :priority 3)
         ("Oxford English Dictionary 2nd Ed P1"
          :short "===========================================================\n Oxford English Dictionary 2nd Ed. (1989)\n==========================================================="
          :formatter lexic-format-online-etym
          :priority 4)
         ("Oxford English Dictionary 2nd Ed P2"
          :short "===========================================================\n Oxford English Dictionary 2nd Ed. (1989)\n==========================================================="
          :formatter lexic-format-online-etym
          :priority 5)
         ("latin-english"
          :short "===========================================================\n Latin > English\n==========================================================="
          :formatter lexic-format-online-etym
          :priority 6))))
Lexic Installation & Background

You’re probably using the wrong dictionary. The first place I looked was Oleh Krehel’s defined word package. It does not allow you to query multiple dictionaries/thesaurus. A couple of other Emacs users were similiarly inspired: Webster and Emacs | Irreal. Similarly, these two later posts are also related: The Webster 1913 Dictionary | Irreal Zamansky 56: Dictionaries and Thesauri | Irreal. Following Zamansky’s advice, if you go to Melpa.org and search for the word dictionary a couple of packages will pop up.Out of the 12 the pop-up, only a couple are relevant to our particular use case.Let’s first go to the process of elimination and list a couple of unsuitable packages.

Helm dictionary is a bit limited in its scope, as we can see per issue number insert link, that it does not support the DICT format for dictionaries. The package that requires the least fiddling around with DV dictionary package. There is just one minor problem, this dictionary package out-of-the-box is configured to query the online server DICT.org for results. If you are okay with being connected to the Internet, then your search would stop here. However if you insist on having an off-line dictionary as I do, will have to go a bit further. You have 2 options. Install you own DICT server, or use SDCV. DICT standard specification can be found at:RFC 2229 - A Dictionary Server Protocol, and the website the dictionary.el tool queries is dict.org. SDCV main website can be found StarDict - The best dictionary program in linux and windows . The downloads can be found here StarDict Dictionaries – 星际译王词库. It is much easier to use SDCV and it does not require configuring your own server.

There is a fork of the DICT standard format dictionary called StarDICT. Installing the pre-built package on Debian would be

 sudo apt install sdcv

Download https://tecosaur.com/resources/config/stardict.tar.gz and extract it to ~/.stardict/dic/. You can test your sdcv installation at this point in the terminal with

  sdcv word
  1. Construct lexic-dictionary-specs Make the dictionary known to lexic.el

Now inside the newly extracted directory (you may need to give yourself read/write permissions) will be the .ifo file. Lets install lexic and customize lexic-dictionary-specs.

It should be noted that dictionaries DO NOT need to have a format function. You can most definitely use the dictionary without. i.e.

The currently pre-defined format functions are:

If your use case is not included in the above, feel free to take look into lexic.el and then open an Issue or submit a pull request.

  1. Lexic.el uses outline.el (think org-mode), so here are the usage commands inside lexic mode

Endnotes: recent article on stardict https://owenh.net/stardict Thank you to: removed ;; Quotations (“Oxford Dictionary of Quotations” :short “From Oxford Dictionary of Quotations” :priority 10) ;; For Learning. Because I am always learning (“Oxford Advanced Learner’s Dictionary 8th Ed.” :short “From the Oxford Advanced Learner’s Dictionary 8th Ed.” :priority 5) (“Oxford Collocations Dictionary 2nd Ed. (En-En)” :short “From the Oxford Collocations Dictionary 2nd Ed.” :priority 6)

Check out lexic-dictionary-help

Lexic Order of Dictionaries
  1. The golden mean, the dictionary that has the first say, the lovingly crafted life’s work of Noah Webster: Webster Unabridged 1913
  2. Thesauri - Synonyms
    1. Soule’s. For the organized hierarchy
    2. Moby Thesaurus II. Remarkable compilation of a single man. Claims to be the largest thesaurus.
    3. WordNet 3.0. Leverages the power of computing to map synonyms or word relations. No word, or human, is born into the world alone/self-sufficient. That perfection is reserved God alone. A systemic way of approaching linguistics. Yes, designed for machines so the essence of a definition may remain, but the hard to describe humanity (artistic flavor? humanness?) of a word is lost.
  3. The accepted authority on the English language, the dictionary that has the last say, the magnum opus: The Oxford English Dictionary 2nd Ed. 1989
  4. Encyclopedia Taken from Dictionary vs. encyclopedia The term lexicon is ambiguous both in prescientific and in linguistic usage, since it may mean either a dictionary or an encyclopedia. The latter two terms are employed in linguistics whenever the distinction matters.

    A dictionary provides information on expressions typically words of a language, while an encyclopedia tells one what is known about an object, or objects of a certain kind.1 The contrast is brought out in the following table: Dictionary vs. encyclopedia criterion dictionary encyclopedia object linguistic properties of linguistic units represented by lemmas properties of objects designated by lemmas describes use of linguistic units world knowledge lemmas any word class only nouns

    A dictionary gives information on all linguistic aspects of its lemmas, on their significans, significatum, grammatical properties and aspects of usage. Focusing on the purely semantic aspect of a lexicon, we may say that a dictionary gives information about the sigificata of its lemmas, while an encyclopedia gives information on their denotata.

    The lemmas of an encyclopedia are nouns. However, contrary to a terminological dictionary, not only common nouns, but also proper nouns can be lemmas.

    The bilingual dictionary is particularly apt to illustrate the difference between the two kinds of information provided by a dictionary and an encyclopedia: If you encounter the German word Reiher and don’t know what it means, you consult a German-English dictionary. It tells you that Reiher means ‘heron’. From that you may infer that, mutatis mutandis, the German word Reiher is used like the English word heron. You have not been told what a Reiher (or a heron) is. If you don’t know, the dictionary will not help you; you will have to consult an encyclopedia.

    In the individual mind, the two kinds of information may, to some extent, be independent. Suppose you have never seen a heron and have no knowledge about it except that it is a large bird. So much (knowing a hyperonym) would be purely linguistic knowledge. It would enable you to actively and passively use the word heron without arousing anybody’s attention; unless of course you mix in ornithological circles.

    An example of an English dictionary is The New Merriam-Webster Dictionary (Springfield, MA: Merriam-Webster). An example of an English encyclopedia is the Encyclopaedia Britannica (London: International). Orthographic dictionaries, i.e. ones that only show how a word is spelt, may be the most widespread kind of dictionary, but are nevertheless untypical of the linguistic concept of dictionary since they lack most kinds of information that make up knowledge of the language in question.

    The distinction between dictionary and encyclopedia is a theoretically-based distinction that is practically useful: If I want to know what Reiher means, I do not want to find an article on herons; and if I need information on herons, I do not need to be told that heron is a common noun whose plural may be herons or heron. However, the psychological reality of the distinction, i.e. whether it has a neat counterpart in the mental lexicon, is less clear. Much of what we know about the meaning of a word is probably intertwined with knowledge about the object it designates. There are therefore hybrid forms between dictionary and encyclopedia, sometimes explicitly called ‘encyclopedic dictionary’.

    1. The Britannica Concise (2006)
  5. Specialized encyclopedic dictionaries
    1. Bouvier’s Law
    2. Elements Database
Biblio Config

<- Project Jerome

;;---------------------------------------------------------------------------
(use-package! citar
  :config
  (setf
   citar-bibliography (list (concat +project-maria-dir+ "project-jerome.bib"))
   citar-notes-paths (list (concat +project-maria-dir+ "bibtex-notes"))
   citar-library-paths
   (let* ((base (expand-file-name "~/project-jerome"))
          (excluded (mapcar #'expand-file-name
                            '("~/project-jerome/email-archive"
                              "~/project-jerome/org-attach-data"
                              "~/project-jerome/keepass-database"))))
     (cl-remove-if (lambda (d) (cl-some (lambda (ex) (string-prefix-p ex d)) excluded))
                   (cons base (cl-remove-if-not #'file-directory-p
                                                (directory-files-recursively base "" t))))))
  (map! :leader
        :desc "citar-open" "s SPC" #'citar-open))

1.3.11. Languages Config

;;---------------------------------------------------------------------------
Ledger Config

<- ledgerrc <- Personal Economics

hledger vs ledger: https://www.reddit.com/r/plaintextaccounting/comments/1381lfo/hledger_equivalent_to_ledger_payee_subdirective/

https://daryl.wakatara.com/tracking-your-finances-with-reckon-and-ledger/

Command line interface (CLI) double entry accounting system.

  1. Install Ledger CLI
  2. Install Reckon to convert .csv statements to Ledger’s journal format.
  3. Add finance layer to the dotspacemacs file.
  4. Start by reading the Documentation. Sections 1-4.
  5. Refer to the .ledgerrc configuration file.
  6. Download .csv file from your bank or financial institution and run Reckon against it.
  7. Copy over the journal entries printed to stdout into your ledger journal.

How ledger-autosync does payee matching and how ledger can use regular expressions to match unknown payee fields. Say you are paying off loans that are classed under liabilities but also want to include it in an expense, you can look at Virtual Postings.

Quick reports to run:

  # See expenses divided by month and accounts
  ledger -M register Income:* Expenses:*
  # See expenses divided by month and accounts
  ledger balance --real
Common Lisp Config

<- Common Lisp Environment Setup

;;---------------------------------------------------------------------------
(setf common-lisp-hyperspec-root
      (concat "file://" +project-jerome-dir+
              "000-generalities-information-computers/000-computer-science/HyperSpec/"))
;; https://emacs.stackexchange.com/questions/62536/what-does-making-browse-url
;; -browser-function-local-to-eww-while-let-bound-m
(advice-add 'hyperspec-lookup
            :around
            (lambda (orig-fun &rest args)
              (setq-local browse-url-browser-function 'eww-browse-url)
              (apply orig-fun args)))
Org Mode Config
  1. Time Clocking See Org Manual 8.6, Timers (The Org Manual). We can insert notes relative to an arbitrary timer so starting both the audio (lecture) recording at the same time as the org timer allows us to take notes with reference to where exactly in a lecture it was found. See org-timer-item.
  2. Tree Manipulation Look at options under , s while in org mode for tree options. Of note is org-kill-note-or-show-branches (bound to C-c C-k). See also Org Manual 2.5 Sparse trees. This next tip is amazing for creating multiple subtrees with timestamps shifted. Try calling org-clone-subtree-with-time-shift. The first thing that comes to mind, is the creation of routine events.
  3. Internal linking hyperlinks - Radio targets in external org mode file, for glossary applicatio… Org-mode Hidden Gems - 03 Hyperlinks

It is worth noting that packages like org gcal, which do not come in a pre-configured spacemacs layer, are kept separate from this org mode config tree. Packages which do contain some boilerplate configuration, such as org re reveal, can be found as a subtree to this heading. As well as org-plus-contrib packages.

Text manipulation > or < to indent visually selected text M-RET to create next list item.

;;---------------------------------------------------------------------------
(after! evil-org
  (remove-hook 'org-tab-first-hook #'+org-cycle-only-current-subtree-h))

(after! org
  (add-hook! 'org-mode-hook (electric-indent-local-mode -1))
  (add-hook! 'org-mode-hook
    (add-hook 'completion-at-point-functions #'cape-file -10 t))

  (advice-remove #'org-mark-ring-push #'doom-set-jump-a)
  (setf org-adapt-indentation nil
        org-startup-indented nil
        ;; Be strict about extensions: match ".org" and ".org.gpg" only.
        org-id-extra-files (directory-files-recursively +project-maria-dir+ "\\.org\\(\\.gpg\\)?$"))
  (require 'org-depend)
  (require 'cl-lib)

  (defun afs/org-replace-link-by-link-description ()
    "Replace an org link by its description or if empty its address"
    (interactive)
    (if (org-in-regexp org-link-bracket-re 1)
        (save-excursion
          (let ((remove (list (match-beginning 0) (match-end 0)))
                (description
                 (if (match-end 2)
                     (org-match-string-no-properties 2)
                   (org-match-string-no-properties 1))))
            (apply 'delete-region remove)
            (insert description)))))

  (map! :leader
        :desc "Agenda" "a" #'ben/default-custom-agenda))
Org Agenda Config

<- Task Mangement

Key Effect
SPC m s r org-agenda-refile

.org in project maria that I consider to be agenda files:

  • hq.org
  • contacts.org
  • someday.org

From this, things are pretty self explanatory. Remember Meetings are given an active time stamp NOT with a :scheduled: date-stamp. Org Gcal solves calendar synchronization between laptop and phone.

Noteworthy Todo Keyword is PROG (formerly IN-MOTION) for IN-PROGRESS. Author has found this a nice feature for two reasons:

  1. Obvious reason is that it reminds you what you were working on in that

particular place/context.

  1. The interesting reason is that it restricts the amount of open loops,

as per little’s law. Credit must be given here:. What qualifies as “too many” open loops is an individual quality, but a lower time is better.

There exists 3 priorities, A > B > C.

  1. If an item has a real deadline = B priority. Real deadline means not a self-imposed deadline. Those are meaningless.
  2. If an item needs to be done As Soon As Possible (ASAP) = A priority.
  3. Otherwise = C priority.

Eisenhower matrix can be imitated with a combination of deadlines, scheduled and priorities.

;;---------------------------------------------------------------------------
(after! org
  (org-clock-persistence-insinuate)

  (load! "private-packages/org-agenda-find-free-time.el")

  (defun my-org-mode-ask-effort ()
    "Ask for an effort estimate when clocking in if none exists."
    (unless (org-entry-get (point) "Effort")
      (let ((effort
             (completing-read
              "Effort: "
              (org-entry-get-multivalued-property (point) "Effort"))))
        (unless (equal effort "")
          (org-set-property "Effort" effort)))))

  (add-hook 'org-clock-in-prepare-hook #'my-org-mode-ask-effort)

  (defun bhw/clock-in ()
    "Smart clock-in/out command.

- No running clock: call `org-clock-in' with a prefix arg to select
  from recent clock history.
- Running clock < 3 hours: clock out of the current task.
- Running clock ≥ 3 hours: the clock is likely stale; interactively
  resolve it via `org-resolve-clocks' before doing anything else."
    (interactive)
    (if (org-clocking-p)
        (let* ((elapsed-secs (float-time (time-since org-clock-start-time)))
               (three-hours-secs (* 3 60 60)))
          (if (>= elapsed-secs three-hours-secs)
              ;; Clock has been running for over 3 hours — needs resolution.
              (progn
                (message "Clock has been running for over 3 hours. Resolving…")
                (org-resolve-clocks))
            ;; Normal running clock — just clock out.
            (org-clock-out)))
      ;; No running clock — select from history.
      (org-clock-in '(4))))

  (defun bh/verify-refile-target ()
    "Exclude todo keywords with a done state from refile targets"
    (not (member (nth 2 (org-heading-components)) org-done-keywords)))

  ;; Press t to change task todo state
  (setf
   org-agenda-files
   (cl-loop for agenda-file in
            '("hq.org")
            collect
            (concat +project-maria-dir+ agenda-file))
   inhibit-compacting-font-caches t
   org-agenda-start-day "+0d"
   org-use-fast-todo-selection t
   org-treat-S-cursor-todo-selection-as-state-change t
   ;; Require exit notes for modifying a scheduled for deadline date
   org-log-reschedule 'time
   org-log-redeadline 'note
   org-log-done 'time
   org-todo-keywords
   '((sequence "TODO(t)" "PROJ(p)" "APPT(a)" "PROG(i)"
      "WAIT(w@/!)" "|" "DONE(d)" "CXLD(c@/!)"))
   org-todo-keyword-faces
   '(("PROJ" :foreground "DarkSlateBlue" :weight bold)
     ("TODO" :foreground "tomato1" :weight bold)
     ("WAIT" :foreground "orchid3" :weight bold)
     ("PROG" :foreground "DeepSkyBlue3" :weight bold)
     ("DONE" :foreground "SpringGreen3" :weight bold)
     ("APPT" :foreground "tomato3" :weight bold)
     ("CXLD" :foreground "sienna" :weight bold))
   org-enforce-todo-dependencies t
   org-agenda-dim-blocked-tasks t
   org-habit-graph-column 80
   org-agenda-skip-scheduled-if-deadline-is-shown t
   org-agenda-skip-deadline-prewarning-if-scheduled 'pre-scheduled
   org-agenda-skip-scheduled-if-done t
   org-agenda-skip-deadline-if-done t
   org-agenda-todo-ignore-scheduled 'future
   org-agenda-todo-ignore-deadlines t
   org-deadline-warning-days 7
   ;; 6) Adding New Tasks Quickly with Org Capture
   ;; Capture templates for: TODO tasks, Notes, appointments, phone calls, meetings, and org-protocol
   ;; \n is newline in the template. Functions as RET would in insert mode
   ;; placing a backslash before " in TRIGGER below to have the string not end
   org-capture-templates
   '(("t" "Todo Task" entry (file+headline "~/project-maria/hq.org" "Inbox") "* TODO [#C] %?\n:PROPERTIES:\n:EFFORT:   %^{0:00|0:10|0:30|1:00|1:30|2:00|2:30|3:00}\n:ASSIGNED: %U\n:END:\n" :empty-lines 1)
     ("a" "Appointment" entry (file+headline "~/project-maria/hq.org" "Inbox") "* APPT %?\nSCHEDULED: %^T\n:PROPERTIES:\n:LOCATION: %^{LOCATION|TBD}\n:EFFORT:   %^{0:00|0:10|0:30|1:00|1:30|2:00|2:30|3:00}\n:ASSIGNED: %U\n:END:\n" :empty-lines 1)
     ("j" "Journal Entry" entry (file+headline "~/project-maria/hq.org" "Inbox")"* TODO [#C] JOURNAL ENTRY %<Y%YW%V%B%d>\n:PROPERTIES:\n:EFFORT: 0:10\n:ASSIGNED: %U\n:END:\n%?" :empty-lines 1)
     ("h" "Habit" entry (file+headline "~/project-maria/hq.org" "Inbox")"* TODO %?\nSCHEDULED: %(format-time-string \"%\")\n:PROPERTIES:\n:STYLE: habit\n:REPEAT_TO_STATE: TODO\n:ASSIGNED: %U\n:END:" :empty-lines 1)
     ("c" "Contacts" entry (file "~/project-maria/contacts.org") "* %(org-contacts-template-name)\n:PROPERTIES:\n:PHONE: %?\n:EMAIL:\n:ADDRESS:\n:BIRTHDAY:\n:NOTE: Added on: %U\n:END:" :empty-lines 1)
     ("p" "Project" entry (file "~/project-maria/hq.org") "* PROJ %? [/] [%] %^G\n:PROPERTIES:\n:ASSIGNED: %U\n:CATEGORY: %^{CATEGORY|Misc.}\n:END:\n** TODO [#C]\n:PROPERTIES:\n:EFFORT:%^{0:00|0:10|0:30|1:00|1:30|2:00|2:30|3:00}\n:ASSIGNED: %U\n:END:\n" :empty-lines 1))
   ;; **** 9) Clocking
   org-clock-in-switch-to-state "PROG"
   org-clock-out-remove-zero-time-clocks t
   org-clock-out-when-done t
   org-clock-persist t
   org-clock-in-resume t
   org-clock-persist-query-resume nil
   org-clock-auto-clock-resolution 'when-no-clock-is-running
   org-clock-report-include-clocking-task t
   org-time-stamp-rounding-minutes '(1 1)
   org-agenda-clockreport-parameter-plist
   '(:link t :maxlevel 10 :fileskip0 t :stepskip0 t :compact t :narrow 80)
   org-log-into-drawer t
   org-clock-history-length 35
   ;; **** 7) Refiling Tasks
   org-refile-targets '((nil :maxlevel . 9)
                        (org-agenda-files :maxlevel . 9))
   org-outline-path-complete-in-steps nil
   org-refile-use-outline-path 'file
   org-refile-target-verify-function 'bh/verify-refile-target
   ;; **** 11) Context Tags with fast selection keys
   org-tag-alist '(;; Sets geo-spatial and context tags
                   ;; Startgroup and endgroup make tags mutually
                   ;; exclusive (:startgroup)
                   ("home" . ?h)
                   ("office" . ?o)
                   ("errand" . ?e)
                   ;; (:endgroup)
                   ;; Person(s) can be contexts too.
                   ("father" . ?d)
                   ("workteam1" . ?d)
                   ("docket" . ?d))
   org-fast-tag-selection-single-key 'expert
   org-tags-column 0
   ;; For tag searches ignore tasks with scheduled and deadline dates
   org-agenda-tags-todo-honor-ignore-options t
   ;; **** 14) Stuck Projects
   org-stuck-projects   '("+TODO=\"PROJ\"" ("TODO" "PROG" "WAIT") nil nil)
   ;; **** 15) Archiving
   org-archive-default-command 'org-archive-subtree
   org-archive-location
   (concat +project-maria-dir+
           "archived-tasks/taskings-"
           (format-time-string "%Y") ".org::datetree/")
   org-archive-save-context-info '(time category olpath ltags itags)
   org-habit-show-habits t
   ;; To speed up org agenda generation
   org-agenda-dim-blocked-tasks nil
   org-agenda-inhibit-startup t
   org-agenda-ignore-properties '(ASSIGNED LAST_REPEAT)
   org-agenda-sticky nil)

  (defun my/org-agenda-calculate-efforts (limit)
    "Sum the efforts of scheduled entries up to LIMIT in the agenda buffer."
    (let ((total-minutes 0))
      (save-excursion
        (while (< (point) limit)
          (when (member (org-get-at-bol 'type) '("scheduled" "past-scheduled" "timestamp"))
            (let* ((marker (org-get-at-bol 'org-hd-marker))
                   (effort (when marker (org-entry-get marker "EFFORT"))))
              (when effort
                (setq total-minutes (+ total-minutes (org-duration-to-minutes effort))))))
          (forward-line)))
      (org-duration-from-minutes total-minutes)))

  (defun my/org-agenda-insert-efforts ()
    "Insert the efforts for each day inside the agenda buffer."
    (save-excursion
      (let (pos)
        (while (setq pos (text-property-any
                          (point) (point-max) 'org-agenda-date-header t))
          (goto-char pos)
          (end-of-line)
          (insert-and-inherit
           (concat " ("
                   (my/org-agenda-calculate-efforts
                    (or (text-property-any
                         (point) (point-max) 'org-agenda-date-header t)
                        (point-max)))
                   ")"))
          (forward-line)))))

  (add-hook 'org-agenda-finalize-hook #'my/org-agenda-insert-efforts)

  (defface ben/stale-assigned-face
    '((t :background "#4d3028" :extend t))
    "Face for TODO items assigned over a month ago (Priority C, no schedule/deadline).")

  (defun ben/highlight-stale-assigned-todos ()
    "Highlight agenda TODO items assigned over a month ago.
Only applies to Priority C items with no scheduled date or deadline.
Batches source-buffer lookups to minimize buffer switching."
    (let ((one-month-ago (time-subtract (current-time) (days-to-time 30)))
          (agenda-buf (current-buffer))
          candidates)
      ;; Pass 1: collect candidate lines (agenda-local checks only)
      (save-excursion
        (goto-char (point-min))
        (while (not (eobp))
          (let ((marker (or (org-get-at-bol 'org-hd-marker)
                            (org-get-at-bol 'org-marker))))
            (when (and marker
                       (equal (org-get-at-bol 'todo-state) "TODO")
                       (let ((pri (org-get-at-bol 'priority)))
                         (or (null pri) (= pri 0))))
              (push (list marker (line-beginning-position) (line-end-position))
                    candidates)))
          (forward-line)))
      ;; Pass 2: group by source buffer, single switch per buffer
      (let ((by-buffer (make-hash-table :test 'eq)))
        (dolist (c candidates)
          (let ((buf (marker-buffer (car c))))
            (when buf
              (push c (gethash buf by-buffer)))))
        (maphash
         (lambda (buf entries)
           (with-current-buffer buf
             (dolist (entry entries)
               (let ((marker (nth 0 entry))
                     (bol (nth 1 entry))
                     (eol (nth 2 entry)))
                 (goto-char marker)
                 (let ((scheduled (org-get-scheduled-time (point)))
                       (deadline (org-get-deadline-time (point)))
                       (assigned-str (org-entry-get (point) "ASSIGNED")))
                   (when (and (null scheduled)
                              (null deadline)
                              assigned-str
                              (time-less-p (org-time-string-to-time assigned-str)
                                           one-month-ago))
                     (let ((ov (make-overlay bol eol agenda-buf)))
                       (overlay-put ov 'face 'ben/stale-assigned-face)
                       (overlay-put ov 'ben/stale-assigned t))))))))
         by-buffer))))

  (add-hook 'org-agenda-finalize-hook #'ben/highlight-stale-assigned-todos)

  (defun ben/default-custom-agenda()
    "Functionally call custom agenda command bound to KEY"
    (interactive)
    (org-agenda nil "d"))

  (defun ben/org-capture-set-priority-on-deadline ()
    "Set the priority of an org-capture entry to [#B] if a deadline exists.
                          This function is intended to be used with `org-capture-before-finalize-hook`."
    (save-excursion
      (goto-char (point-min))
      ;; Check if a DEADLINE: timestamp exists in the entry
      (when (re-search-forward "^[ \t]*DEADLINE:" nil t)
        ;; If a deadline is found, set the priority to 'B'
        (org-priority ?B))))

  (add-hook 'org-capture-before-finalize-hook #'ben/org-capture-set-priority-on-deadline)

  (defun my/org-agenda-deadline-for-prefix ()
    "Return the deadline relative to today (e.g. 'In 5 d.'), formatted to 9 chars.
   Returns 9 spaces if no deadline exists."
    (let ((deadline-time (org-get-deadline-time (point))))
      (if deadline-time
          (let* ((days (- (org-time-string-to-absolute
                           (format-time-string "%Y-%m-%d" deadline-time))
                          (org-today)))
                 (result-string
                  (cond
                   ((< days 0) (format "%dd ago" (abs days))) ;; Overdue: "2d ago"
                   ((= days 0) "Today")                       ;; Due today
                   (t (format "%d d." days)))))               ;; Future: "5 d."
            ;; Format to exactly 6 characters, left-aligned
            (format "%-6s" result-string))
        ;; If no deadline, return 6 spaces to maintain alignment
        (make-string 6 ?\s))))

  (defun my/org-agenda-effort-for-prefix ()
    "Return the effort estimate formatted as '[HH:MM] ', or spacers if no effort."
    (let ((effort (org-entry-get (point) "EFFORT")))
      (if effort
          (format "[%-4s] " effort) ;; Result: "[0:30] "
        "       ")))                ;; 7 spaces to match length of "[0:30] "

  (setf
   org-agenda-block-separator 61
   org-agenda-breadcrumbs-separator " | "
   ;; https://stackoverflow.com/questions/58820073/s-in-org-agenda-prefix-format-doesnt-display-dates-in-the-todo-view
   org-agenda-prefix-format
   '((agenda . "%-t %s")
     (todo . "%s")
     (tags . "%s")
     (search . "%s"))
   org-agenda-deadline-leaders '("D: " "D%2d: " "OD%2d: ")
   org-agenda-scheduled-leaders '("" "S%2d: ")
   org-agenda-time-grid '((daily today remove-match)
                          (0600 0900 1200 1500 1800 2100)
                          "......" "----------------")
   org-columns-default-format-for-agenda "%75ITEM(Task) %DEADLINE %10Effort(Estim){:} %10CLOCKSUM(ActTime)"
   org-columns-default-format "%75ITEM(Task) %DEADLINE %10Effort(Estim){:} %10CLOCKSUM(ActTime)"
   org-global-properties '(("Effort_ALL" . "0:00 0:10 0:30 1:00 1:30 2:00 2:30 3:00 4:00 5:00 6:00 7:00 8:00")
                           ("STYLE_ALL" . "habit"))
   org-agenda-columns-add-appointments-to-effort-sum t
   org-agenda-default-appointment-duration 0
   org-agenda-log-mode-items '(closed state clock)
   org-agenda-start-with-log-mode t
   org-agenda-start-with-entry-text-mode nil
   org-agenda-add-entry-text-maxlines 5
   org-agenda-entry-text-maxlines 5
   org-agenda-start-with-clockreport-mode nil
   org-priority-default ?C
   org-agenda-custom-commands
   '(
     ;; Default Agenda
     ("d" "Default (Master) Agenda"
      ((agenda "" ((org-agenda-span 'day)
                   (org-deadline-warning-days 1)
                   (org-agenda-overriding-header "Today's Agenda\n")))
       (tags "TODO=\"PROG\""
             ((org-agenda-sorting-strategy '(priority-down deadline-up effort-down))
              (org-agenda-prefix-format
               '((tags . "  %-3:c %(my/org-agenda-deadline-for-prefix)%(my/org-agenda-effort-for-prefix)")))
              (org-agenda-todo-keyword-format "%-3s")
              (org-agenda-overriding-header "\nTasks in Progress\n")))
       (tags "TODO=\"TODO\""
             ((org-agenda-sorting-strategy '(priority-down deadline-up effort-down))
              (org-agenda-todo-ignore-deadlines nil)
              (org-agenda-prefix-format
               '((tags . "  %-3:c %(my/org-agenda-deadline-for-prefix)%(my/org-agenda-effort-for-prefix)")))
              (org-agenda-todo-keyword-format "%-3s")
              (org-agenda-skip-function '(org-agenda-skip-entry-if 'scheduled))
              (org-agenda-overriding-header "\nTodo List\n")))
       (agenda "" ((org-agenda-span 30)
                   (org-agenda-start-day "+1d")
                   (org-agenda-start-on-weekday nil)
                   (org-agenda-entry-types '(:timestamp :sexp :scheduled))
                   (org-agenda-overriding-header "Calendar\n"))))
      ((org-agenda-tag-filter-preset '("-SDAY"))))
     ;; Review Agenda
     ("r" "Review Agenda"
      ((tags "TODO=\"DONE\""
             ((org-agenda-sorting-strategy '(priority-down deadline-up))
              (org-agenda-todo-keyword-format "%-3s")
              (org-agenda-overriding-header "\nCompleted Tasks\n")))
       (tags "TODO=\"CXLD\""
             ((org-agenda-sorting-strategy '(tsia-up))
              (org-agenda-todo-keyword-format "%-3s")
              (org-agenda-overriding-header "\nTerminated Tasks\n")))
       (tags "+TODO=\"WAIT\""
             ((org-agenda-sorting-strategy '(timestamp-down))
              (org-agenda-todo-keyword-format "%-3s")
              (org-agenda-overriding-header "\nDelegated/Waiting For\n")))
       (stuck "" ((org-agenda-overriding-header "\nStuck Projects\n")))
       (agenda "" ((org-agenda-span 120)
                   (org-agenda-start-on-weekday nil)
                   (org-agenda-entry-types '(:timestamp :sexp :scheduled))
                   (org-agenda-overriding-header "Calendar\n"))))
      ((org-agenda-tag-filter-preset '("-SDAY" "-bio1200" "-his2500" "-cla154" "-lat122" "-mus221" "-the249" "-the274" "-phl300" "-the219")))))
   org-agenda-window-setup 'current-window)
  (map! :after evil-org-agenda
        :map evil-org-agenda-mode-map
        :m "s" #'avy-goto-word-or-subword-1)
  (map! :map (org-mode-map)
        :localleader
        (:prefix ("x" . "text")
         :desc "bold emphasis         " "b" (cmd! (org-emphasize ?*))
         :desc "italic emphasis       " "i" (cmd! (org-emphasize ?/))
         :desc "underline emphasis    " "u" (cmd! (org-emphasize ?_))
         :desc "verbatim emphasis     " "v" (cmd! (org-emphasize ?=))
         :desc "code emphasis         " "c" (cmd! (org-emphasize ?~))
         :desc "strikethrough emphasis" "s" (cmd! (org-emphasize ?+)))
        (:prefix ("v" . "links")
         :desc "org-insert-structure-template" "v" #'org-insert-structure-template))
  (map! :leader
        :desc "bhw/clock-in" "nc" #'bhw/clock-in))
Organizational Buckets i.e. inbox dumps

Tasks are broken down into kinds. Because humans cannot bi-locate, all tasks needs to have a valid contextual location in which to accomplish them. This is done with tags which are prefixed with “@”, errand - Tasks that must be completed out and about home - 0office 0docket - Instead of having a @John Doe, @Alice, @Bob tagging a task, you can signify such a group with a custom context. Tasks that must be completed in such a meeting setting. @John Doe - Say you have an agenda list of items for the next time you run into John Doe. Tasks that must be completed in John Doe’s company

  1. A Projects list & Project Support Materials - hq.org A comprehensive list. Can include delegated projects.
    • Critical for control and focus
    • Alleviates subtle tensions
      • Projects often evolve.
    • Core of the Weekly Review
    • Facilitates relationship management
  2. Calendar actions and information - hq.org

    Does this action require you to be at a specific place in a specific time frame? If so, prefix with APPT keyword.

    Does a project belong under 0projects or 0calendar? Answer this: Is the time specific appointment a subset of the project or vice versa? Should an item that has a time context be put in 0calendar.org?

    • YES - time specific - Appointments to be tracked Uses a timestamp, synced with google calendar.
    • NO - day-specific action - time slot enlarged to entire days Use a scheduled timestamp, org refile to 0projects or 0solo
    • NO - day-specific information - items that do not require action from YOU. Useful nonetheless: Use the WAIT keyword and a scheduled timestamp, refile to 0projects or 0solo.
      • Special events with a certain lead time for handling (product launches, fund- raisers, etc.)
      • Regular events that you need to prepare for, such as budget reviews, annual conferences, planning events, or meetings (e.g., when should you add next year’s “Annual sales conference” or “Get kids set up for next school year”to your Projects list?)
      • Key dates for significant people that you might want to do something about (birthdays, anniversaries, holiday gift giving, etc.)
      • Seminars, conferences, speeches, and social and cultural events. It’s OK to decide not to decideas long as you have a decide-not-to-decide system.
      • Big life or strategy decisions that require: Additional information needed from internal sources. Ex. you need to sleep on it. Justifiable reason to delay until all factors are visible and understood Create future trigger so you can feel comfortable just “hanging it out” for now.
  3. Next Actions lists - 0solo.org One off actions that you cannot neatly organize under a project. Ideally, of course, with enough self agency, most tasks should be under your projects list.
  4. Reference Material - project-jerome-index.org & contacts.org
  5. A Someday/Maybe list - someday.org Refile a task here without changing any TODO keywords or tags. The file tag “-SDAY” excludes such entries from the Agenda. If on reflection you realize that an optional project doesn’t have a chance of getting your attention for the next few months or more, move it to this list. Whatever ideas/ex-projects/processes on this list can have its own reference materials. Keep in mind: ex-projects are projects that have lost part of their definition: action-ability. Please refer to the 5 step workflow diagram. Difference between 0someday items and WAIT keyword items is the intersection between this organizational group and the calendar. Non actionable items that may need an action in the future. Refer to the incubate heading under chapter 6. Falls under calendar rule 3. day specific information. In order to keep with the theme of the calendar as sacred “hardlined” ground, ticklers (HOLD keyword tasks) must have a scheduled date, and exist in 0project or 0solo files.
    • Things to get or build for your home
    • Hobbies to take up
    • Skills to learn
    • Creative expressions to explore
    • Clothes and accessories to buy
    • Toys (hi-tech and otherwise!) to acquire
    • Trips to take
    • Organizations to join
    • Service projects to contribute to
    • Things to see and do
    • Childrenthings to do with them
    • Books to read
    • Music to download
    • Movies to see
    • Gift ideas
    • Web sites to explore
    • Weekend trips to take
    • IdeasMisc. (meaning you don’t know where else to put them!)
Org Attach Config

Attached images will show up in inline by using the , i l RET attachment: with no description. Toggle on with , T i. If you look at the org mode manual, it will provide detail into how attachments work. What is more pressing is how to delete attachments after you have deleted their associated headline.

;;---------------------------------------------------------------------------
(defun fuco/org-attach-visit-headline-from-dired ()
  "Go to the headline corresponding to this org-attach directory."
  (interactive)
  (let* ((id-parts (last (split-string default-directory "/" t) 2))
         (id (apply #'concat id-parts)))
    (let ((m (org-id-find id 'marker)))
      (unless m (user-error "Cannot find entry with ID \"%s\"" id))
      (pop-to-buffer (marker-buffer m))
      (goto-char m)
      (move-marker m nil)
      (org-fold-show-context))))
(setf
 org-attach-id-dir "~/project-jerome/org-attach-data/"
 ;; https://helpdeskheadesk.net/2022-03-13/
 ;; For org attach, change org timestamps to more human readable format.
 org-id-method 'ts
 org-attach-id-to-path-function-list
 '(org-attach-id-ts-folder-format org-attach-id-uuid-folder-format)
 org-attach-method 'mv)
Org Mem Config
;;---------------------------------------------------------------------------
(use-package! org-mem
  :after org
  :config
  (setf org-mem-watch-dirs (list +project-maria-dir+))
  (org-mem-updater-mode))
Org Node Config
;;---------------------------------------------------------------------------
(use-package! org-node
  :init
  (map! :leader
        :desc "org-node-find" "sf" #'org-node-find)
  :config
  (org-node-cache-mode)
  (setf org-node-backlink-do-drawers t)
  (org-node-backlink-mode)
  (map! :map (org-mode-map)
        :localleader
        (:prefix ("l" . "links")
         :desc "org-node-insert-link" "n" #'org-node-insert-link))
  )
Org Noter Config
;;---------------------------------------------------------------------------
(after! org-noter
  (setf org-noter-always-create-frame nil
        org-noter-hide-other nil
        org-noter-auto-save-last-location t
        org-noter-arrow-delay -1)

  (defun bhw/org-noter-quit ()
    "Kill org-noter session without closing the emacs client frame.
Un-dedicates windows first to avoid Doom's `switch-to-prev-buffer' error,
then shadows `delete-frame' so the session teardown cannot close the frame."
    (interactive)
    (org-noter--with-valid-session
     (let ((frame (org-noter--session-frame session)))
       (dolist (win (window-list frame))
         (set-window-dedicated-p win nil))
       (cl-letf (((symbol-function 'delete-frame) #'ignore))
         (org-noter-kill-session session))
       (when (frame-live-p frame)
         (unless (doom-real-buffer-p (current-buffer))
           (switch-to-buffer (doom-fallback-buffer)))))))

  (map! :map org-noter-notes-mode-map
        :n "q" #'bhw/org-noter-quit)

  (defun bhw/org-noter-insert-precise-quote (&optional toggle-highlight)
    "Insert a quotation block from selected PDF text with org-cite reference.
With prefix argument, fall back to the original `org-noter-insert-precise-note'."
    (interactive "P")
    (if toggle-highlight
        (org-noter-insert-precise-note toggle-highlight)
      (org-noter--with-valid-session
       (let ((selected-text (run-hook-with-args-until-success
                             'org-noter-get-selected-text-hook
                             (org-noter--session-doc-mode session))))
         (if (or (null selected-text) (string-empty-p selected-text))
             (org-noter-insert-precise-note)
           (let* ((location (org-noter--doc-approx-location
                             (or (org-noter--get-precise-info) 'interactive)))
                  (page (car location))
                  (cite-key (file-name-sans-extension
                             (file-name-nondirectory
                              (org-noter--session-property-text session))))
                  (ast (org-noter--parse-root))
                  (window (org-noter--get-notes-window 'force))
                  (view-info (org-noter--get-view-info
                              (org-noter--get-current-view) location))
                  (ref (org-noter--view-info-reference-for-insertion view-info)))
             (let ((inhibit-quit t))
               (with-local-quit
                 (select-frame-set-input-focus (window-frame window))
                 (select-window window)
                 (if ref
                     (goto-char (org-element-property
                                 (if (eq (car ref) 'before) :begin :end)
                                 (cdr ref)))
                   (goto-char (or (org-element-map (org-element-contents ast)
                                      'section
                                    (lambda (s)
                                      (org-element-property :end s))
                                    nil t org-element-all-elements)
                                  (point-max))))
                 (unless (bolp) (insert "\n"))
                 (insert "\n#+BEGIN_QUOTE\n" selected-text
                         "\n" (format "[cite:p.%s@%s]" page cite-key)
                         "\n#+END_QUOTE\n"))
               (when quit-flag
                 (select-frame-set-input-focus (org-noter--session-frame session))
                 (select-window (get-buffer-window
                                 (org-noter--session-doc-buffer session))))))))))))
Org Transclusion Config
;;---------------------------------------------------------------------------
(use-package! org-transclusion
  :after org
  :init
  (map! :leader :prefix "n"
        :desc "Toggle Org Transclusion Mode" "t" #'org-transclusion-mode))
Ob Tangle Sync Config

{ANN} lisp/ob-tangle-sync.el - Mehmet Tekman

;;---------------------------------------------------------------------------
(load! "private-packages/ob-tangle-sync.el")
(setf org-babel-tangle-sync-files
      (list (concat +project-maria-dir+ "dotemacs.org")))
(org-babel-tangle-sync-mode)
Org Contacts Config

A flat file of org headings is the simplest and in the unlikely case that I exceed dunbar’s number, there is GitHub - girzel/ebdb: An EIEIO port of BBDB, Emacs’ contact-management package For further customization options, check out: GitHub - tmalsburg/helm-org-contacts: A helm source address books in org-cont…

An evaluation of other contact management software in Emacs.

Install the below if we’re going to import contacts into Emacs. GitHub - jwiegley/ecard: Library for representing vCard data using EIEIO classes

;; Require org-contacts to work with mu4e
(require 'org-contacts)
(setf org-contacts-files (list (concat +project-maria-dir+ "contacts.org")))
Org Download Config

I don’t like the fixed image width setting for images. So, I wrote a custom function for a bit more dynamic rescaling of images:

https://github.com/sainathadapa/emacs-spacemacs-config/blob/mac/org-display-inline-images-custom.el

Always conserve the aspect ratio

Image shouldn’t exceed the current window’s width (minus 100 pixels)

Image shouldn’t exceed half of the current window’s height

Resize only if the actual dimensions do not conform to the above two points

Press 0 w (zero, then w) or C-0 w. This executes the command dired-copy-filename-as-kill with a zero prefix argument, which tells it to copy the absolute (full) file name

(setf org-download-method 'attach
      ;; https://www.reddit.com/r/emacs/comments/1ow0gza/some_tips_for_using_emacs_on_wsl/
      org-download-screenshot-method
      "powershell.exe -Command \"(Get-Clipboard -Format image).Save('$(wslpath -w %s)')\"")
Ox Publish Config

https://nicolasknoebber.com/posts/blogging-with-emacs-and-org.html

Note that when editing this config you MUST properly escape " characters and avoid the use of the literal % character unless you are using it as intended i.e. %C

;;---------------------------------------------------------------------------
(after! ox-publish
  ;; ox-extra's `ignore-headlines' lets us exclude a heading itself from
  ;; the ToC while still exporting its body.
  ;; https://emacs.stackexchange.com/questions/30183/orgmode-export-skip-ignore-first-headline-level
  (require 'ox-extra)
  (ox-extras-activate '(ignore-headlines))
  (require 'ox-bibtex)
  (require 'webfeeder)

  ;; https://www.taingram.org/blog/org-mode-blog.html
  (setf org-html-head-include-default-style nil
        org-html-htmlize-output-type 'css
        org-export-global-macros
        '(("timestamp" . "@@html:<span class=\"timestamp\">[$1]</span>@@")))

  (defun my/org-sitemap-date-entry-format (entry style project)
    "Format ENTRY in org-publish PROJECT Sitemap with a date prefix."
    (let ((filename (org-publish-find-title entry project)))
      (if (= (length filename) 0)
          (format "*%s*" entry)
        (format "{{{timestamp(%s)}}} [[file:%s][%s]]"
                (format-time-string "%Y-%m-%d"
                                    (org-publish-find-date entry project))
                entry
                filename))))

  (setf org-publish-project-alist
        '(("blog"
           :base-directory "~/project-maria/blog"
           :html-extension "html"
           :base-extension "org"
           :recursive t
           :publishing-function org-html-publish-to-html
           :publishing-directory "~/common-lisp/project-isidore/assets/blog"
           :section-numbers t
           :table-of-contents t
           :exclude "rss.org"
           :with-title nil
           :auto-sitemap t
           :sitemap-filename "archive.org"
           :sitemap-title "Blog Archive"
           :sitemap-sort-files anti-chronologically
           :sitemap-style tree
           :sitemap-format-entry my/org-sitemap-date-entry-format
           ;; https://orgmode.org/manual/HTML-doctypes.html#HTML-doctypes
           :html-doctype "html5"
           :html-html5-fancy t
           :html-head "
                      <link rel=\"stylesheet\" type=\"text/css\" href=\"../global.css\"/>
                      <link rel=\"stylesheet\"
                            href=\"//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.2.0/styles/base16/solarized-light.min.css\">
                      <script src=\"//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.2.0/highlight.min.js\" defer></script>
                      <script>var hlf=function(){Array.prototype.forEach.call(document.querySelectorAll(\"pre.src\"),function(t){var e;e=t.getAttribute(\"class\"),e=e.replace(/src-(\w+)/,\"src-$1 $1\"),console.log(e),t.setAttribute(\"class\",e),hljs.highlightBlock(t)})};addEventListener(\"DOMContentLoaded\",hlf);</script>"
           :html-preamble "
                                    <div class=\"header header-fixed\">
                                      <div class=\"navbar container\">
                                        <div class=\"logo\"><a href=\"/\">BHW</a></div>
                                        <input type=\"checkbox\" id=\"navbar-toggle\" >
                                        <label for=\"navbar-toggle\"><i></i></label>
                                        <nav class=\"menu\">
                                          <ul>
                                            <li><a href=\"/about\">About</a></li>
                                            <li><a href=\"/work\">Work</a></li>
                                            <li><a href=\"/assets/blog/archive.html\">Blog</a></li>
                                            <li><a href=\"/contact\">Contact</a></li>
                                          </ul>
                                        </nav>
                                      </div>
                                    </div>
                                    <h1 class=\"title\">%t</h1>
                                    <p class=\"subtitle\">%s</p> <br/>
                                    <p class=\"updated\"><a href=\"/contact#article-history\">Updated:</a> %C</p>"
           :html-postamble "<script>
                              const headers = Array.from( document.querySelectorAll('h2, h3, h4, h5, h6') );

                              headers.forEach( header => {
                                header.insertAdjacentHTML('afterbegin',
                                 '<a href=\"#table-of-contents\">&#8689;</a>'
                                );
                              });
                              </script>
                              <hr/>
                              <footer>
                                <div class=\"copyright-container\">
                                    Comments? Corrections? <a href=\"https://bhw.name/contact\"> Please do reach out.</a><a href=\"https://bhw.name/assets/blog/atom.xml\"> RSS Feed. </a><a href=\"https://bhw.name/subscribe\"> Mailing List. </a><br/>
                                    Copyright 2021 Benedict H. Wang. <br/>
                                    Blog content is available under <a rel=\"license\" href=\"http://creativecommons.org/licenses/by-sa/4.0/\"> CC-BY-SA 4.0 </a> unless otherwise noted.<br/>
                                    Created with %c on <a href=\"https://www.gnu.org\">GNU</a>/<a href=\"https://www.kernel.org/\">Linux</a><br/>
                                </div>
                              </footer>")))

  ;; https://alhassy.github.io/AlBasmala#Clickable-Headlines
  (defun my/ensure-headline-ids (&rest _)
    "Give every Org tree without a CUSTOM_ID a slug derived from its heading.
Non-alphanumerics collapse to '-'. Duplicate slugs abort with `quit-flag'.

E.g., \"We'll go on a ∀∃⇅ adventure\" ↦ \"We'll-go-on-a-adventure\"."
    (interactive)
    (let ((ids))
      (org-map-entries
       (lambda ()
         (org-with-point-at (point)
           (let ((id (org-entry-get nil "CUSTOM_ID")))
             (unless id
               (thread-last (nth 4 (org-heading-components))
                            (s-replace-regexp "[^[:alnum:]']" "-")
                            (s-replace-regexp "-+" "-")
                            (s-chop-prefix "-")
                            (s-chop-suffix "-")
                            (setq id))
               (if (not (member id ids))
                   (push id ids)
                 (message-box "Oh no, a repeated id!\n\n\t%s" id)
                 (undo)
                 (setq quit-flag t))
               (org-entry-put nil "CUSTOM_ID" id))))))))
  (advice-add 'org-html-export-to-html   :before 'my/ensure-headline-ids)
  (advice-add 'org-md-export-to-markdown :before 'my/ensure-headline-ids)

  (defun ben/publish-blog ()
    "Publish the blog project and rebuild atom.xml via webfeeder."
    (interactive)
    (org-publish "blog")
    (webfeeder-build
     "atom.xml"
     "~/common-lisp/project-isidore/assets/blog"
     "https://bhw.name/"
     ;; Skip archive.html and any temp ".#…" files when collecting feed entries.
     (remove "archive.html"
             (directory-files "~/common-lisp/project-isidore/assets/blog"
                              nil "^[^LICENSE]*\.html"))
     :title "BHW Blog"
     :description "Ben's personal blog")))
Org Re Reveal Config INACTIVE

Add audio to presentation? oer / emacs-reveal · GitLab

  (require 'org-re-reveal)
  (setf org-re-reveal-revealjs-version "4"
        org-re-reveal-root  "https://cdn.jsdelivr.net/npm/reveal.js")
Cdlatex Config

Mathpix: Snipping Tool LaTeX Input for Impatient Scholars | Karthinks

;;---------------------------------------------------------------------------
Plantuml Config

<- Plant UML

For collaborative diagrams and brainstorming, consider https://plus.excalidraw.com/

  sudo apt install plantuml

Download Plantuml.jar and place it in the root of your home directory, at ~/plantuml.jar. Control output type with plantuml-output-type. Org babel file header is required.

Old config:

  (setf plantuml-default-exec-mode 'jar
        plantuml-jar-path "/usr/share/plantuml/plantuml.jar"
        org-plantuml-jar-path "/usr/share/plantuml/plantuml.jar"
        plantuml-output-type "txt")
  (with-eval-after-load "org-mode"
    (add-to-list 'org-src-lang-modes '("plantuml" . plantuml)))
Python Config

Installation | uv Installation | ty Installing Ruff | Ruff

Add the following to your ~/.config/doom/init.el,

(lsp +peek)
(python +lsp +tree-sitter +uv)
;;---------------------------------------------------------------------------
;; lsp-mode ships a built-in ty client (ty-ls) at priority -1; promote it
;; so it wins over pyright/pylsp when ty is on PATH.
(after! lsp-python-ty
  (setf (lsp--client-priority (gethash 'ty-ls lsp-clients)) 1))

1.3.12. Email Config

Gmail Configuration Tips

Use POP3 forwarding on proper emails. Then check box enabling us to send as those addresses. Inbox options should be default with all categories checked off. Finally must use custom filters to disable gmail auto spam filter. I don’t want important messages auto tagged as spam. Let me decide for myself please gmail.

  1. Click Create a new filter.
  2. Enter {(to:me) (deliveredto:USERNAME@gmail.com)} in the Has the words field (replacing USERNAME with your actual Gmail username).
  3. Click Create filter.
  4. In addition, create another one with the Has the words field with “is:spam” and of course, set all filters to never send items to the spam folder.

Note you can convert Gmail takeout’s mbox format to the format used by Mu4e, maildir, by using mb2md in the debian apt repositories.

Usage
  • Updating Inbox & Sorting Mail view mu4e-update process by list-process command in SPC a p.

    Key Effect
    d mark for deletion
    r mark as read
    x execute marked actions
    * mark for unknown actions
    # resolve unknown actions
  • Reading Mail For html mail, use the view in browser capability

    Key Effect
    aV view email in browser (can be eww,firefox etc)
    K yank links, followed by a numerical argument

    use M-x customize-variable to find possible values for browse-url-browser-function

  • Composing Mail Emails are currently composed within org-mode and then exported to html. Search through contacts.org and then email the appropriate person. Make sure to edit the html before sending it out.

    Locate recipient of email in contacts.org Compose emails first in org mode through email-template.org in project-maria export through “org-mime-org-subtree-htmlize Hit” “, e s” to export the subtree to mu4e compose Verify html renders correctly send with “, ,” TIP: press tab to autocomplete addresses

  • Switching Accounts Make sure to edit:
    1. mu4e config in your dotspacemacs file and
    2. the .authinfo file found in the $HOME directory
    3. the .mbsyncrc file found in the $HOME directory
    4. Delete .mu as well as the dir contents of Maildir.
    5. Afterwards, run these commands.
      • (1) create maildir with mu mkdir ~/Maildir/Inbox
      • (2) (assuming you are using mbsync) setup your ~/.mbsyncrc file

and run the program mbsync -a to download your email.

  • (3) run mu init --maildir=~/project-jerome/email-archive --my-address=REPLACEWITHYOUR@gmail.com --my-address=2NDEMAILADDRESS@gmail.com
  • (4) Now in emacs `M-x mu4e’ should work.
Key Effect
P Toggle threading
W Show related messages
S Search while in mu4e header buffer
SPC s e Search all emails through helm completion
\ Mu4e headers search narrow. C-u to remove results limit for the next search
M-left/right Previous, next query
o Save email attachment - gnus-mime-save-part
SPC a o l org-store-link (C-c M-l) org-insert-last-stored-link
Key Effect
OG Search group
;;---------------------------------------------------------------------------
(require 'mu4e-contrib)
(use-package! mu4e
  :init
  (map! :leader
        :desc "Email"           "oe" #'mu4e)
  :config
  ;; https://mu-discuss.narkive.com/hXk7RbcH/set-from-address-depending-on-to-address-header
  (defun mu4e-compose-set-from-address-dwim ()
    "Set the From address based on the To address of the original. Added to
  `mu4e-compose-pre-hook'"
    (let ((msg mu4e-compose-parent-message))
      (when msg
        ;; In `mu4e-compose-set-from-address-dwim`, you are using `setf
        ;; user-mail-address`. This sets the *global* value. If you have
        ;; multiple compose buffers open, switching the account in one might
        ;; unexpectedly change the identity in another if they are not properly
        ;; isolated.
        (setq-local user-mail-address
                    (or (seq-find (lambda (addr)
                                    (mu4e-message-contact-field-matches msg :to addr))
                                  my/mu4e-address-routing)
                        my/default-mail-address)))))

  (evil-set-initial-state 'mu4e-headers-mode 'normal)
  (evil-set-initial-state 'mu4e-view-mode 'normal)
  (evil-set-initial-state 'mu4e-compose-mode 'insert)

  (setf mu4e-change-filenames-when-moving t  ; mbsync specific.
        ;; see an ASCII table for the character decimal codes

        mu4e-bookmarks '(("maildir:/INBOX" "Inbox" 105 )
                         ("\"maildir:/[Gmail]/All Mail\" and flag:unread" "Unread" 85)
                         ("\"maildir:/[Gmail]/All Mail\"" "All Mail" 97)
                         ("\"maildir:/[Gmail]/Sent Mail\"" "Sent Mail" 115))
        user-mail-address my/default-mail-address
        user-full-name "Ben H. W."
        ;; mu4e-compose-signature
        mail-user-agent 'mu4e-user-agent
        mu4e-attachment-dir "/mnt/c/Users/bened/Downloads/"
        mu4e-drafts-folder "/[Gmail]/Drafts"
        mu4e-sent-folder "/[Gmail]/Sent Mail"
        mu4e-trash-folder "/[Gmail]/Trash"
        mu4e-refile-folder "/[Gmail]/All Mail"
        send-mail-function 'smtpmail-send-it
        smtpmail-stream-type 'starttls
        smtpmail-default-smtp-server "smtp.gmail.com"
        smtpmail-smtp-server "smtp.gmail.com"
        smtpmail-smtp-service 587
        message-sendmail-f-is-evil t
        mu4e-index-update-in-background t
        mu4e-update-interval 3600
        mu4e-autorun-background-at-startup t
        mu4e-get-mail-command "mbsync -a"
        mu4e-hide-index-messages t
        mu4e-enable-mode-line nil
        ;; If this is enabled, prompts for new gpg fingerprints will not show up.
        ;; Instead emails will silently fail to send.
        mu4e-enable-async-operations nil
        mu4e-search-skip-duplicates t
        ;; Prefer text/plain over text/html in multipart/alternative messages.
        mm-discouraged-alternatives '("text/html" "text/richtext")
        gnus-blocked-images "."
        mu4e-org-link-query-in-headers-mode nil
        ;; mu4e-org-contacts-file (concat +project-maria-dir+ "contacts.org")
        message-kill-buffer-on-exit t
        mu4e-confirm-quit nil
        ;; mu4e-headers-time-format "%y/%m/%d %H:%M"
        ;; mu4e-headers-fields
        ;; '((:human-date . 14)
        ;;   (:from-or-to . 20)
        ;;   (:subject))
        mml-secure-openpgp-sign-with-sender t
        mml-secure-openpgp-signers '("06DDA93690F775E3715B628CCA949A6D46BC2BBE")
        mu4e-compose-complete-addresses t
        mu4e-compose-complete-only-after "2018-01-01"
        browse-url-filename-alist
        '(("^/\\(ftp@\\|anonymous@\\)?\\([^:/]+\\):/*" . "ftp://\\2/")
          ("^/\\([^:@/]+@\\)?\\([^:/]+\\):/*" . "ftp://\\1\\2/")
          ;; For gnus-article-browse-html-article on Windows Subsystem for Linux.
          ("^/+" . "file://///wsl$/Debian/"))
        mu4e-modeline-support nil
        mu4e-search-include-related nil)

  (add-hook 'mu4e-compose-pre-hook #'mu4e-compose-set-from-address-dwim)
  ;; (add-hook
  ;;  'mu4e-headers-mode-hook
  ;;  (lambda () (define-key evil-motion-state-map (kbd "RET") nil)))
  ;; (add-hook
  ;;  'mu4e-view-mode-hook
  ;;  (lambda () (define-key evil-normal-state-map (kbd "a") nil)))

  ;; (evil-define-key 'normal mu4e-headers-mode-map
  ;;   "RET" #'mu4e-headers-view-message
  ;;   "s" #'avy-goto-word-or-subword-1
  ;;   "e" #'mu4e-headers-flag-all-read
  ;;   "E" #'mu4e-headers-mark-all)
  (map! :after mu4e
        :map mu4e-headers-mode-map
        :n "RET" #'mu4e-headers-view-message
        :n "s"   #'avy-goto-word-or-subword-1
        :n "e"   #'mu4e-headers-flag-all-read
        :n "E"   #'mu4e-headers-mark-all
        :map mu4e-view-mode-map
        :n "RET" #'browse-url-at-point
        :n "s"   #'avy-goto-word-or-subword-1
        :n "L"   #'mu4e-view-save-url
        :n "A"   #'mu4e-view-save-url))

;; Forwarded HTML bodies can contain unbalanced `<`/`>` (e.g. inside
;; `<style>` blocks), which makes `forward-sexp' under `mml-syntax-table'
;; signal `scan-error' and abort sending. The CID-image rewriting this
;; function performs is only useful when local inline images are present,
;; so fall back to the original `cont' on failure.
(defun +mml-expand-html-into-multipart-related-safe-a (orig cont)
  (condition-case nil
      (funcall orig cont)
    (scan-error cont)))
(advice-add 'mml-expand-html-into-multipart-related :around
            #'+mml-expand-html-into-multipart-related-safe-a)

;; (require 'mu4e-send-delay)
;; (use-package! mu4e-send-delay
;;   :config
;;   (advice-remove 'org-msg-ctrl-c-ctrl-c #'mu4e-send-delay-org-msg-ctrl-c-ctrl-c)
;;   (add-hook! 'mu4e-main-mode-hook 'mu4e-send-delay-setup))

(after! recentf
  (add-to-list 'recentf-exclude "~/project-jerome/email-archive/")
  (add-to-list 'recentf-exclude "/tmp/"))
Consult-Mu Config
;;---------------------------------------------------------------------------
(use-package! consult-mu
  :after (mu4e consult)
  :init
  (map! :leader
        :desc "Search Email"    "se" #'consult-mu
        :desc "Search .emacs.d" "sE" #'+default/search-emacsd)
  :config
  (require 'consult-mu-embark)
  (require 'consult-mu-compose)
  (require 'consult-mu-compose-embark)
  (require 'consult-mu-contacts)
  (require 'consult-mu-contacts-embark)
  (consult-mu-compose-embark-bind-attach-file-key)
  (setf consult-mu-maxnum 200
        consult-mu-preview-key 'any
        consult-mu-mark-previewed-as-read nil
        consult-mu-mark-viewed-as-read t
        consult-mu-use-wide-reply 'ask
        consult-mu-headers-template
        (lambda () (concat "%f" (number-to-string (floor (* (frame-width) 0.15))) "%s" (number-to-string (floor (* (frame-width) 0.5))) "%d13" "%g" "%x"))
        consult-mu-saved-searches-async '("#flag:unread")
        consult-mu-saved-searches-dynamic '("flag:unread")
        consult-mu-compose-preview-key "M-o"
        consult-mu-embark-attach-file-key "C-a"
        consult-mu-contacts-ignore-list '("^.*no.*reply.*")
        consult-mu-contacts-ignore-case-fold-search t
        consult-mu-compose-use-dired-attachment 'in-dired))

1.3.13. Application Config

;;---------------------------------------------------------------------------
Elfeed Config

Extending elfeed with PDF viewer and subtitles fetcher : emacs

  1. Youtube Feeds For youtube channel subscriptions, use:https://www.youtube.com/feeds/videos.xml?channel_id=THE_CHANNEL_ID_HERE To get the channel ID’s:
    1. View the page’s source code
    2. Look for the following text (ctrl-f): externalID
    3. Get the value for that element
    4. Replace that value into above URL:
    5. For some videos, instead of watching it, use elfeed-tube to read the subtitles more effectively.
;;---------------------------------------------------------------------------
(use-package! elfeed
  :init
  (map! :leader
        :desc "Web Feed - Elfeed" "ow" #'elfeed)
  :config

  (defun elfeed-mark-all-as-read ()
    "Marks entire buffer before tagging marked region as read"
    (interactive)
    (mark-whole-buffer)
    (elfeed-search-untag-all-unread))

  (defun ben/elfeed-search-browse-url (&optional use-generic-p)
    "Visit the current entry in your browser using `browse-url'.
  If there is a prefix argument, visit the current entry in the
  browser defined by `browse-url-generic-program'."
    (interactive "P")
    (let ((buffer (current-buffer))
          (entries (elfeed-search-selected)))
      (cl-loop for entry in entries
               for link = (elfeed-entry-link entry)
               do (elfeed-untag entry 'unread)
               when link
               do (if use-generic-p
                      (browse-url-generic link)
                    (eww link)))
      ;; `browse-url' could have switched to another buffer if eww or another
      ;; internal browser is used, but the remainder of the functions needs to
      ;; run in the elfeed buffer.
      (with-current-buffer buffer
        (mapc #'elfeed-search-update-entry entries)
        (unless (or elfeed-search-remain-on-entry (use-region-p))
          (forward-line)))))

  (defhydra ben/hydra-elfeed (:exit t)
    ("g" (elfeed-search-set-filter "@6-months-ago +unread +gbl") "Global News")
    ("l" (elfeed-search-set-filter "@6-months-ago +unread +lcl") "Local News")
    ("s" (elfeed-search-set-filter "@6-months-ago +unread +sci") "Science & Tech")
    ("c" (elfeed-search-set-filter "@6-months-ago +unread +rel") "Catholic")
    ("f" (elfeed-search-set-filter "@6-months-ago +unread +frm") "Forums")
    ("o" (elfeed-search-set-filter "@6-months-ago +unread +pod") "Podcasts")
    ("b" (elfeed-search-set-filter "@6-months-ago +unread +blog") "Misc Blogs")
    ("y" (elfeed-search-set-filter "@6-months-ago +unread +vid") "Youtube")
    ("a" (elfeed-search-set-filter "@6-months-ago +unread") "All")
    ("q" nil "quit" :color blue))

  (add-hook 'elfeed-search-mode-hook #'elfeed-update)

  (map! :after elfeed
        :map elfeed-search-mode-map
        :n "s"  #'avy-goto-word-or-subword-1
        :n "r"  #'elfeed-mark-all-as-read
        :n "S"  #'elfeed-search-live-filter
        :n "f"  #'ben/hydra-elfeed/body
        :n "b"  #'ben/elfeed-search-browse-url
        :n "B"  #'elfeed-search-browse-url
        :n "R"  #'elfeed-search-update--force
        :n ";"  #'consult-line
        :map elfeed-show-mode-map
        :n "s"  #'avy-goto-word-or-subword-1
        :n "b"  #'ben/elfeed-search-browse-url
        :n "B"  #'elfeed-search-browse-url
        :n ";"  #'consult-line))
Ement Config

<- Communication in Emacs

;;---------------------------------------------------------------------------
(use-package! ement
  :init
  (map! :leader
        :desc "Ement"           "oc" #'ement-notifications
        :desc "Ement (Login)"   "oC" #'ement-connect
        :desc "ement-room-view" "sc" #'ement-room-view)
  :config
  (require 'org)
  (require 'url-util)

  (defun my/ement-schedule-message (time-str message)
    (interactive
     (let ((msg (if (derived-mode-p 'ement-room-compose-mode)
                    (buffer-substring-no-properties (point-min) (point-max))
                  (read-string "Message to schedule: "))))
       (list (org-read-date nil nil nil "Schedule for: ")
             msg)))
    (let ((room ement-room)
          (session ement-session)
          (time (org-time-string-to-time time-str)))
      (unless (and room session)
        (user-error "Not in an Ement room context"))
      (when (time-less-p time (current-time))
        (user-error "Scheduled time must be in the future"))
      (run-at-time time nil
                   (lambda (r s m)
                     (ement-room-send-message r s :body m))
                   room session message)
      (message "Message scheduled for %s" time-str)
      (when (derived-mode-p 'ement-room-compose-mode)
        (erase-buffer)
        (kill-buffer))))

  (defun my/ement-mark-all-read ()
    (interactive)
    (let ((count 0))
      (dolist (session-pair ement-sessions)
        (let ((session (cdr session-pair)))
          (dolist (room (ement-session-rooms session))
            (when (ement--room-unread-p room session)
              (let* ((timeline (ement-room-timeline room))
                     (latest-event (car (last timeline))))
                (when (and latest-event (ement-event-id latest-event))
                  (cl-incf count)
                  (ement-api session
                    (format "rooms/%s/receipt/m.read/%s"
                            (url-hexify-string (ement-room-id room))
                            (url-hexify-string (ement-event-id latest-event)))
                    :method 'post
                    :then (lambda (_)
                            (message "Read receipt confirmed for %s"
                                     (ement-room-display-name room)))
                    :else (lambda (plz-error)
                            (message "Error marking read: %s" plz-error)))))))))
      (if (> count 0)
          (message "Sending read receipts for %d rooms..." count)
        (message "No unread rooms found."))
      (when (derived-mode-p 'ement-room-list-mode)
        (ement-room-list))))

  ;; When closing the notifications buffer, mark everything read.
  ;; We run it on a 0-delay timer so it fires *after* the buffer is gone.
  (defun my/ement-notifications-run-after-kill ()
    (when (derived-mode-p 'ement-notifications-mode)
      (run-at-time 0 nil #'my/ement-mark-all-read)))

  (add-hook 'ement-notifications-mode-hook
            (lambda ()
              (add-hook 'kill-buffer-hook #'my/ement-notifications-run-after-kill nil t)))

  (map! :after ement-room
        :map ement-room-mode-map
        :n "RET"   #'ement-room-send-message
        :n "M-RET" #'ement-room-compose-message
        :n "s"     #'avy-goto-word-or-subword-1
        :n "r"     #'ement-room-write-reply
        :n "D"     #'ement-room-download-file
        :n ";"     #'ement-room-occur
        :n "gg"    #'ement-room-scroll-down-command
        :n "G"     #'ement-room-scroll-up-mark-read
        :n "m"     #'ement-room-mark-read
        :n "e"     #'ement-room-edit-message-prepare
        :n "a e"   #'ement-room-send-emote
        :n "a f"   #'ement-room-send-file
        :n "a i"   #'ement-room-send-image
        :n "a r"   #'ement-room-send-reaction)

  (add-hook 'ement-room-compose-hook 'ement-room-compose-org)
  (setf ement-save-sessions t
        ement-room-mark-rooms-read 'send
        ement-room-send-typing nil
        ement-auto-sync t
        ement-room-images t
        ement-room-image-thumbnail-height 1
        ement-room-image-thumbnail-height-min 1500)

  ;; Upstream ement-room-list crashes with (wrong-type-argument number-or-marker-p nil)
  ;; when unread_notifications is present but notification_count or highlight_count is nil.
  ;; Redefine the "Unread" column to coerce nil -> 0.
  (ement-room-list-define-column
    #("Unread" 0 6 (help-echo "Unread events (Notifications:Highlights)"))
    (:align 'right)
    (pcase-let* ((`[,(cl-struct ement-room unread-notifications) ,_session] item)
                 ((map notification_count highlight_count) unread-notifications)
                 (n (or notification_count 0))
                 (h (or highlight_count 0)))
      (if (or (not unread-notifications)
              (and (zerop n) (zerop h)))
          ""
        (concat (ement-propertize (number-to-string n)
                  'face (if (zerop h) 'default 'ement-room-mention))
                ":"
                (ement-propertize (number-to-string h)
                  'face 'highlight))))))

(after! evil-collection
  (setq evil-collection-mode-list (delq 'ement evil-collection-mode-list))
  (map! :map ement-notifications-mode-map
        :n "<return>" #'ement-notifications-jump
        :n "RET"      #'ement-notifications-jump ;; Bind both to be safe
        :n "r"          #'ement-notify-reply))
Transmission Config

Initial setup:

sudo apt install transmission-daemon -y
sudo systemctl start transmission-daemon

Transmission settings.json file and verify that rpc-authentication-required is set to true.

;;---------------------------------------------------------------------------
(use-package! transmission
  :after evil-collection
  :init
  (map! :leader
        :desc "transmission"    "oT" #'transmission)
  :config
  (evil-collection-transmission-setup)
  (map! :map transmission-mode-map
        :n "s" #'avy-goto-word-or-subword-1)
  (setf transmission-refresh-modes
        '(transmission-mode
          transmission-files-mode
          transmission-info-mode
          transmission-peers-mode)))
Shannon Key Logger Config
;;---------------------------------------------------------------------------
(add-to-list 'load-path "~/.config/emacs/.local/")
(require 'shannon-max)
(setq shannon-max-jar-file
      (expand-file-name "~/.config/emacs/.local/target/emacskeys-0.1.0-SNAPSHOT-standalone.jar"))
(shannon-max-start-logger)
Calendar Config
;;---------------------------------------------------------------------------
(map! :leader
      :desc "gregorian calendar" "og" #'calendar)
Anki Editor Config
;;---------------------------------------------------------------------------
(require 'anki-editor)
Biome Config
;;---------------------------------------------------------------------------
(use-package! biome
  :config
  (biome-def-preset meteorology-detroit-weather
    ((:name . "NOAA GFS & HRRR (U.S.)")
     (:group . "hourly")
     (:params
      ("hourly" "wind_speed_10m" "cloud_cover" "precipitation" "apparent_temperature")
      ("longitude" . -83.1386)
      ("latitude" . 42.4205))))
  (biome-def-preset meteorology-toronto-weather
    ((:name . "GEM (Canada)")
     (:group . "hourly")
     (:params
      ("hourly" "wind_speed_10m" "cloud_cover" "precipitation" "apparent_temperature")
      ("longitude" . -79.337021)
      ("latitude" . 43.856098))))
  (map! :leader
        :desc "biome" "om" #'meteorology-detroit-weather))
Casual Emacs Calc Config
Key Command Effect
C-x * :   calc-grab-sum-down
C-x * 0 calc-reset reset calc stack

M-x calc, or SPC a * will enter calc-dispatch. y will yank top to stack to the last edited buffer, x is like M-x but for calc functions only.

Make your way through

For a quick start. The complete calc manual is available here https://www.gnu.org/software/emacs/manual/html_mono/calc.html]], and is written by the author of the calc package: Dave Gillespie (who also wrote cl-lib, so massive respect). What is RPN (Reverse Polish Notation)? A comprehensive tutorial outlined it, RPN Tutorial. nfdn: Mathing in Emacs with Casual

;;---------------------------------------------------------------------------
(use-package! casual-calc
  :config
  (dolist (m (list calc-mode-map calc-alg-map))
    (map! :map m "SPC" #'doom/leader)
    (keymap-set m "C-o" #'casual-calc-tmenu)))
Emacs Reader Config

Reinstall guide: #122 - Trouble Installing via VC - divyaranjan/emacs-reader - Codeberg.org

;;---------------------------------------------------------------------------
;; (add-to-list 'load-path "/usr/local/src/emacs-reader/")
;; (require 'reader-saveplace)
;; (require 'reader)
;; (add-to-list 'auto-mode-alist '("\\.docx\\'" . reader-mode))
;; (define-key reader-mode-map (kbd "d") #'reader-scroll-down-or-next-page)
;; (define-key reader-mode-map (kbd "u") #'reader-scroll-up-or-prev-page)
;; (define-key reader-mode-map (kbd "gt") #'reader-goto-page)
;; (define-key reader-mode-map (kbd "q") #'reader-close-doc)
;; (spacemacs/set-leader-keys-for-major-mode 'reader-mode "fh" 'reader-fit-to-height)
;; (spacemacs/set-leader-keys-for-major-mode 'reader-mode "fw" 'reader-fit-to-width)
;; (spacemacs/set-leader-keys-for-major-mode 'reader-mode "o" 'reader-outline-show)
;; (spacemacs/set-leader-keys-for-major-mode 'reader-mode "ss" 'reader-search-mode)
;; (load! "private-packages/emacs-reader-noter.el")

1.4. Emacs Navigation & Searching

Search is divided into two categories: File name search, and File content search.

It is highly recommended to swap ESC and Caps Lock. It is also highly recommended to swap ctrl and alt for ergonomic reasons as well. Search for a “space cadet keyboard” and you will see that the “Emacs pinky” is not intended. One is to use the strongest digit of your finger, the thumb, to activate most key chords, regardless if you use Evil Vim Emulation or not. C-g will also act as ESC in situations where fd or ESC fail you. SPC SPC acts as M-x

1.4.1. Searching - File Content

<- Editor Config

  1. Scope: Computer (All Files) using fd-find. Note org-recoll as an option to search all file contents, such as all PDF’s in Project Jerome.
  2. Scope: Project (projectile.el defines this as any directory you have defined as the “root directory” by placing a .projectile file in said directory) Thanks to: the silver searcher (ag) as it allows as to fuzzy search.

    -G*.cljs -w time - search for the word “time” in all .cljs files

    -tclojure time - search for “time” in all .{clj,cljs,cljc} files

    -uno\ due\ tre - search for the string “uno duo tre”

    -C5 foo - search for “foo” but show 5 lines of context before and after the match.

    -(?:^|[^\w-])time(?:[^\w-]|$) - search for lisp-word “time”, i.e. search for the full word “time” while considering “-” to be a word characer

    • Search for
    Key Effect
    SPC / Also performs search project, same as SPC s p
    SPC * Performs SPC / command with symbol under cursor
    SPC p t Opens up treemacs sidebar in project view
    g d With cursor in function, search that function
    SPC SPC custom project maria consult
  3. Scope: File/Buffer Thanks to: Helm-occur

    Key Effect
    SPC s c Clears the red underlining of search matched text
    ? Helm-occur with symbol under cursor
    1. Jumps within visible file/buffer or visible buffer search (buffer navigation) My scheme for navigating the visible buffer:
      • For short hops within a line or a couple of lines in a buffer I

/t/F/T. ct) etc are goldies. Remember s and p stand for sentence and paragraph respectively when used in context with a vim operator. Ex. d a s translates to “delete around a sentence”.

  • For medium hops across all visible frames, windows and buffers, I use avy-goto-word-or-subword-1 on s, since s in vim is basically useless (see r and c). These are fully compatible with vim commands. So e.g. I use d s <two char sequence> all the time for quickly deleting blocks of text.
  • Hoping past what I can see, but still within the file, helm-swoop simply outclasses /. I almost never use /, so rebind it to helm-swoop in your evil config.

avy-goto-char-timer > type more than one letter. timer can be shortened or lengthened.

Key Effect
SPC j j Avy-timer > Jump to specific letter
SPC j w Avy-word > Jump to specific word
SPC j b Go to previous jump location
g ; Go to last edit
ctrl-o jump back

1.4.2. Searching - File Name

<- Navigation - Buffer & Window Manipulation

We only need one command for this: helm-for-files. Use C-h f to find out docstrings, but basically it runs through our buffer list (what is a buffer?), then our recentf list, then our bookmarks, our file caches, then files in current dir before resorting to helm-fd.

See Unix locate (implemented as mlocate, plocate etc) and Unix find (implemented as GNU find and fd)

This command will allow you to find files ANYWHERE. As long as you know the pathname, of course. A primer on fd.

1.4.3. Searching - Directory

Key Effect
SPC f d Call helm-find-files with a prefix
SPC f b Access bookmarks list

1.4.4. Searching - World Wide Web

Google alternatives:

Wikipedia https://hn.algolia.com/ Appending reddit to search term https://search.marginalia.nu/ Duck Duck go, Bing etc.

Web Scraping

1.4.5. Searching - Handling Directories with Dired

Key Effect
( Toggles simple view
0w (dired-copy-filename-as-kill &optional ARG
S Toggle sort by A-Z and last edited

Explanation of w command: Copy names of marked (or next ARG) files into the kill ring. The names are separated by a space. With a zero prefix arg, use the absolute file name of each marked file.

1.4.6. Navigation - Project Manipulation

What is a project? Spacemacs uses projectile.el (pre-installed) to manage projects.

  • For those with coding backgrounds, any directory with a git repo will automatically be considered a project.
  • Absent a .git folder, a .projectile file will mark it as a project to projectile.
Key Effect
SPC SPC then type * *=projectile-add-known-project
SPC p p Switch Projects

1.4.7. Search and Replace

How do I search and replace a section of the buffer? Turn on and use line numbers, SPC t n a.

     :5,12s/foo/bar/g

How do I yank all matching lines into one buffer?

         :g/^{pattern}/yank A

This runs the global command to yank any line that matches ^{pattern} and put it in register a. Because a is uppercase, instead of just setting the register to the value, it will append to it. Since the global command run the command against all matching lines, as a result you will get all lines appended to each other.

What this means is that you probably want to reset the register to an empty string before starting: :let @a=“”.

And naturally, you can use the same with any named register. How can I execute vim commands on matching patterns?

          :g/{pattern}/normal nd$

Explanation: On each line, where pattern matches, execute the following normal mode commands nd$. With the cursor at the start of the line, ’n’ jumps to the pattern, and ’d$’ deletes to the end of the line.

How to execute a vim commands on every single line?

   :%norm fED

This tells vim to press fED on each line as if you had typed it in normal mode. fE will move the cursor forward to the first E, and D deletes everything until the end of the line.

How to capture all subsequent text on a line after a pattern match

   \(.*\)

captures all subsequent text on the line.

Incrementally replace a given string pic1, pic1, pic1 > pic1, pic2, pic3 etc.

  1. C-v and highlight 1
  2. M-x cua-rectangle-mark-mode
  3. M-n and set appropriate values

    See the following if the above does not work EmacsWiki: Numbers In Registers.

How do I remove all blank lines from a buffer?

This is a frequent question so I figured I’d mention the solution here:

You want to remove all empty (blank) lines from a buffer. How do you do it? Well, it’s super easy.

Mark what you want to change (or use C-x h to mark the whole buffer) and run this:

M-x flush-lines RET ^$ RET

And you’re done. So what does that mean? Well, M-x flush-lines will flush (remove) lines that match a regular expression, and ^$ contain the meta-characters ^ for beginning of string and $ for end of string. Ergo, if the two meta-characters are next to eachother, it must be a blank line.

We can also generalize it further and remove lines that may have whitespace (only!) characters:

M-x flush-lines RET ^\s-*$ RET

In this case \s- is the syntax class (type C-h s to see your buffer’s syntax table) for whitespace characters. The * meta-character, in case you are not a regexp person, means zero or more of the preceding character.

Source: https://emacs.stackexchange.com/questions/48526/how-do-i-delete-all-blank-lines-in-a-buffer

How do I match any candidate within a range of characters

[0-9] or [A-Z] or [a-z]

How to I reuse the matched pattern?

\0 or \1

https://unix.stackexchange.com/questions/35206/replace-using-vim-reuse-part-of-the-search-pattern

How do I insert a newline?

\n

:%s/Ver. [0-9]../\0\n/g

How do I remove page break or form feed characters?

First be sure to in fundamental-mode to properly see the ^L or ^M chars. In EmacsLisp, the form-feed character is written `?\f’, and `\f’ represents it in strings. (`\f’ is also used in C.) M-: (replace-string “\f” “”)

How do I match a pattern only at the beginning of a line?

Use the ^ character.

https://docstore.mik.ua/orelly/unix3/vi/ch06_03.htm

Regular Expressions

The pattern that matches substrings in parentheses having no other ( and ) characters in between (like (xyz 123) in Text (abc(xyz 123)) is

\([^()]*\) %s/<chapter[^<>]*>//g Details:

\( - an opening round bracket (note that in POSIX BRE, ( should be used, see sed example below) [^()]* - zero or more (due to the * Kleene star quantifier) characters other than those defined in the negated character class/POSIX bracket expression, that is, any chars other than ( and ) \) - a closing round bracket (no escaping in POSIX BRE allowed)

1.4.8. Navigation - Buffer & Window Manipulation

<- Searching - File Name

  • Buffers = instances of files, SPC b. All buffers have ONE major mode. Can have multiple minor modes.
  • Windows = Display area that a buffer is shown, SPC w
  • Frame = what is conventionally understood as a window. Has the minimize, maximize and close in top right/left.
Key Effect
SPC # Switch to Window number, hjkl directional keys work too.
SPC tab Switch between previous buffer, current buffer in window
SPC b b Access recent files, open buffers and bookmarks in one place
SPC f b Access just bookmarks > Useful for bookmarking directories
SPC w / Split window vertically
SPC w - Split window horizontally
SPC t g Enable Golden Ratios
C-f Page down
C-b Page up
Major / Minor Mode
Key Effect
SPC m Major mode leader. Access to all bindings specific to major mode
, Major mode leader. Access to all bindings specific to major mode

Minor mode can be reached with SPC t. See spacemacs documentation.

1.4.9. Navigation - Marks and Registers

Key Effect
C-SPC Set-mark-command = different from evil-set-marker (m) which is for buffer scope
SPC r m helm-all-mark-rings = access earlier marks set by the previous command

Above is the emacs way, utilizing registers. For vim(evil)-style marking (primarily used within a buffer-wide scope)

Key Effect
m a drop a mark named a, m b to drop a mark named b etc.
’ a goto line mark named a
` a goto exact pointer mark named a

The above marks and registers offer a greater degree of manual control. If you would like something similar to a back button in a web browser, please M-x helm-for-files “evil-jumps.el” under the evil package.

Convenient functions also from the evil package.

Key Effect
g ; goto-last-change
g i evil-insert-resume

1.5. Text Manipulation

1.5.1. Vim Style Shortcuts

  1. Jim Dennis on understanding Vi
  2. Jared Carroll’s definitive guide to Vim Text Objects
  3. Ismail Badawi - The Compositional Nature of Vim
  4. Helpful Image
  5. Vi in the context of EVIL (Emacs emulation of vim)
  1. Find and replace whole words in vim You can use \< to match the beginning of a word and \> to match the end:

    %s/\<word\>/newword/g

  2. How to include forward slash in vi search & replace? Here are two ways:

    escape the / which is the default substitute separator: :s/usrbin/\/usr\/bin use another substitute separator, e.g., using the hash # character: :s#usrbin#/usr/bin. Note that there are characters that you can’t use as a separator: ", \, |

1.5.2. Emacs Style Shortcuts

Emacs 12.5 Documentation on Rectangles has useful information

Key Effect
C-x h mark-whole-buffer
C-x r t Replace rectangle contents with string on each line (string-rectangle)
M-x “undo” In addition to SPC a u for the tree, this command can be used for a specific region
C-Backspace Calls backward-kill-sexp which kill previous symbolic expression
delete-whitespace-rectangle Mark region, then call this command.
C-M-\ Indent-region
C-q C-l Insert linebreak
Emacs Macro

The Emacs way to define macros is available behind the prefix: SPC K

To start recording a keyboard macro:

Press SPC K k (uppercase then lowercase) to start recording a macro. Perform the actions that should be recorded. Press SPC K K (uppercase twice) to stop recording the macro.

To execute the last recorded macro press: SPC K K The macro can be executed again by pressing K one or more times. The single character replay works until another key than K is pressed.

1.5.3. Evil Multiple Cursors

grm - evil-mc-make-all-cursors gru - evil-mc-undo-all-cursors grs - evil-mc-pause-cursors grr - evil-mc-resume-cursors grf - evil-mc-make-and-goto-first-cursor grl - evil-mc-make-and-goto-last-cursor grh - evil-mc-make-cursor-here grj - evil-mc-make-cursor-move-next-line grk - evil-mc-make-cursor-move-prev-line M-n - evil-mc-make-and-goto-next-cursor grN - evil-mc-skip-and-goto-next-cursor M-p - evil-mc-make-and-goto-prev-cursor grP - evil-mc-skip-and-goto-prev-cursor C-n - evil-mc-make-and-goto-next-match grn - evil-mc-skip-and-goto-next-match C-t - evil-mc-skip-and-goto-next-match C-p - evil-mc-make-and-goto-prev-match grp - evil-mc-skip-and-goto-prev-match

1.5.4. Spell Checking

In addition to the layer README, know that your personal Dictionary is located at: ~/.aspell.en.pws

Key Effect
C-; flyspell-auto-correct-previous-word, repeat for next suggestion

Note, flyspell-auto-correct-previous-word only corrects what’s visible on the screen (Thank God).

1.6. Layouts and Workspaces

Refer to official spacemacs documentation on the same wording.

1.7. Help & Errors & Troubleshooting

1.7.1. General Help & Documentation

When Emacs freezes for some unknown reason, end the process with

pkill -9 emacs27

When updating org, or any large and complex packages, if you run into bugs it is good to delete all .elc files. This is because any changes to macros require the recompilation of elisp files. Delete all .elc files under ~/.emacs.d and then run M-x spacemacs/recompile-elpa.

  cd ~/.emacs.d && find . -name '*.elc' -print0 | xargs -0 -r rm -rf
Install Emacs from source

I wrote the initial tutorial for Windows 10, for Emacs was also my introduction into the world of UNIX and Lisp. That, combined with the desire to remind my future forgetful self was the reason for the surface-level depth and hand holding steps. Now if you count yourself comfortable in such an environment, I can think of a few good reasons to install Emacs from source at this point in time.

  1. When Emacs 29 is configured with the flag --with-native-compilation Emacs Lisp bytecode is translated to C and then machine code, yielding performance benefits.
  2. WSLg uses Wayland and Emacs 29.0.50 has the pure GTK feature instead of relying on the older X window system. This leads to a smoother user experience on multiple, high DPI monitors.
  3. Debian does not build emacs with the flag --with-xwidgets for security reasons. With this feature enabled, Emacs is able to embed a Webkit browser widget inside a buffer. Compared to Emacs EWW which has HTML and limited CSS support, xwidget-webkit offers an experience closer to a conventional web browser.
  4. Emacs 29 introduces tree-sitter support, a powerful parsing library that enhances its understanding of source code. With this integration, Emacs gains features like precise syntax highlighting, accurate indentation and easier extensibility.

Currently Debian bookworm packages Emacs 28.2. And for the record, I find such attention to stability perfectly reasonable. I am not familiar with the typical C build process, so Mr. Batsov’s advice helped. Onto the build process,

   sudo apt update
   # Install build dependencies ./configure will tell you if a build dependency is missing.
   sudo apt install git build-essential libgtk-3-dev libgnutls28-dev \
    libtiff5-dev libgif-dev libjpeg-dev libpng-dev libxpm-dev \
    libncurses-dev texinfo autoconf libxml2-dev libwebp-dev \
    librsvg2-dev libsqlite3-dev liblcms2-dev libgpm-dev libotf-dev \
    libacl1-dev libjansson4 libjansson-dev libgccjit-12 libgccjit-12-dev \
    gcc-12 g++-12 libtree-sitter-dev
   export CC=/usr/bin/gcc-12 CXX=/usr/bin/gcc-12
   sudo git clone git://git.sv.gnu.org/emacs.git /usr/local/src/emacs
   cd /usr/local/src/emacs
   sudo ./autogen.sh
   # See ./configure --help for more options. "C-h v"
   # 'system-configuration-options' to see what your Emacs is built with.
   sudo ./configure --with-mailutils --with-wide-int --with-pgtk \
               --with-native-compilation=aot --with-json --with-tree-sitter
   # gconf is deprecated in favour of gsettings.
   # I have 4 threads.
   sudo make -j4
   # Default install in /usr/local/ and emacs-29.0.50 binary under
   # /usr/local/bin.
   sudo make install
   # If you keep your Emacs source folder around, it will know how to uninstall
   # or to git pull from master and rebuild.
   sudo make uninstall
   # sudo git clean -dfX in case of rebuild.

1.7.2. Specific Troubleshooting and Known Issues

  1. Copy paste from WSL2 to Windows is broken?

    Paste something from the kill ring via SPC r y. Or restart your computer. Emacs on WSLg: Copying to the Windows Clipboard | Lukas Barth

  2. Whichkey Lag Due to issue described here: justbur/emacs-which-key#226 Lots of time spent inside which-key–maybe-replace
  3. How to use the universal argument in spacemacs, as C-u is evil-scroll-up?

    SPC u also see evil-want-C-u-scroll variable.

  4. If you run into graphical issues with VcXsrv or X2Go try starting emacs in emas -nw mode first, to generate .elc (compiled emacs lisp) files.
  5. Should the numbering of windows be off after resuming from sleep, such that when creating a new frame window one SPC 1 cannot be selected: SPC F D should delete the “hidden” frame.

1.7.3. Performance

Use the Emacs profiler SPC h T to track performance issues.

If poorly written Emacs lisp packages aren’t a problem, then look at garbage collection.

Afterwards, use SPC h p to browse installed packages and uninstall any extraneous packages.

Lastly look at the enabled global minor modes and disable any that are unneeded.

WSL-specific:

If you feel input delay and general sluggishness, as in if you press the d key once and multiple d’s are entered, then it could be likely that Windows firewall scanning is causing the slowdown. Windows Security > Virus & threat protection settings > Exclusions > Add or remove exclusions > Add Folder \\wsl.localhost\Debian and Add Process msrdc.exe.