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
- emacs.d/malb.org at master · malb/emacs.d · GitHub
- Former Emacs maintainer John Wiegley’s config
- GitHub - munen/emacs.d: My emacs configuration documented in literate program…
- Literate Emacs Configuration - Look here for org-export customizations
- GitHub - joseph8th/literatemacs: My literate Emacs config
- Robert Zaremba “Scale it”, Emacs cheatsheet
- “ - str ’(kf-htmlegalize-region skeleton-point (point)) ”
- Quick Help: Emacs as a Text Productivity Platform : emacs
- My Emacs Configuration
- GitHub - karthink/.emacs.d: My personal emacs settings
- From Doom to Vanilla Emacs - blog.dornea.nu
- GitHub - dorneanu/dotemacs: Collection of Emacs configurations
- Two Years of Emacs Solo: 35 Modules, Zero External Packages, and a Full Refac…
- nixos-config/dotfiles/doom/config.org at master · joshuablais/nixos-config · …
- Common config anti-patterns - Configuration - Doom Emacs Discourse
- GitHub - tecosaur/emacs-config: My configuration for Doom Emacs. Mirror of ht…
- dot-doom/doom.org at master · zzamboni/dot-doom · GitHub
- Emacs Configs
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.
- `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.
- `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
- Git Fork Workflow Using Rebase. Here is a suggested git forking… | by Ruth M….
- Working with Git and patches in Emacs
- The advantages of an email-driven git workflow
- Pro Git Free Book
- Karl Broman Github Tutorial
- Version Control (Git) · Missing Semester
Setting up SSH
Get a github account. Download and install git.
sudo apt install gitVerify 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.comIf 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=250Signing 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
- Syncing forked project
Add the remote (original repo that you forked) and call it “upstream”
git remote add upstream https://github.com/original-repo/goes-here.git
Fetch (not pull, remember that a pull = fetch and merge in git parlance) all branches of remote upstream
git fetch upstream
Make sure you are on the correct branch
git checkout master
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
Rewrite your master with upstream’s master using git reset. Will NOT preserve your commits on master
git reset –hard upstream/master
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
- Read Pro Git At some point you will have to read https://git-scm.com/book/en/v2 pro git.
- Learn to use Ediff to resolve conflicts https://www.sentia.com.au/blog/ediff-for-the-brainically-challenged
- Cleanup Github Forks
Install
curlandjq. Use the following command line to get the names of all your repositories on your Github account. Paste list into new filerepo-delete-list.txtsudo 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'- Add YOURGITHUBACCOUNT/ to the beginning of each line. From
example-repotoBenedictHW/example-repo. - Register a new personal access token with a deleterepo permission at https://github.com/settings/tokens/new and save it.
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 jqOn 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”} }
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
- Make sure to generate a personal access token from Github
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”
- You may need to restart emacs.
- Run
M-x forge-pullwhile inside a buffer of the relevant project. - By pressing
SPC g sto invokemagit-statuswe 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
- What work to do. Which task, in what order, at what scope.
- What context to build before doing the work. Which files to read, what to research, what background to provide.
- How to describe the work. Clear enough that Claude doesn’t waste tokens figuring out what you meant.
- How to verify the output. What does good look like, and how will you check.
- Explore: Ask Claude to read relevant files. Be explicit: “Read the authentication module and the user model. Don’t write any code yet.”
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!
- Code: Once you’ve confirmed the plan, ask Claude to implement it. “Implement the plan. Verify each change compiles before moving on.”
- “Write tests for [feature] based on these requirements. Don’t implement the feature yet — I want the tests to fail initially.”
- Confirm the tests are comprehensive.
- “Now implement the feature to make the tests pass. Don’t modify the tests.”
Review:
- Claude A implements a feature
- /clear or start Claude B in another terminal
- Claude B reviews A’s work, identifies issues
- 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
- For frontend work, give Claude a way to see its output:
- Set up the Puppeteer MCP server (or similar)
- 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
- 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.
- 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
- The golden mean, the dictionary that has the first say, the lovingly crafted life’s work of Noah Webster: Webster Unabridged 1913
- Thesauri - Synonyms
- Soule’s. For the organized hierarchy
- Moby Thesaurus II. Remarkable compilation of a single man. Claims to be the largest thesaurus.
- 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.
- The accepted authority on the English language, the dictionary that has the last say, the magnum opus: The Oxford English Dictionary 2nd Ed. 1989
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’.
- The Britannica Concise (2006)
- Specialized encyclopedic dictionaries
- Bouvier’s Law
- Elements Database
Biblio Config
- <–Project Jerome
- Citations in org-mode: Org-cite and Citar | Kristoffer Balintona
- Org-Cite 2021 Release Notes
- Citation handling in Emacs
;;---------------------------------------------------------------------------
(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
- org babel and ledger
- conquering your finances with emacs and ledger
- CSV Import · ledger/ledger Wiki · GitHub
- My Emacs Ledger reporting configuration - philnewton.net
- GitHub - captainflasmr/bank-buddy: Your Financial Analysis Companion for Emacs
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.
- Install Ledger CLI
- Install Reckon to convert
.csvstatements to Ledger’s journal format. - Add
financelayer to the dotspacemacs file. - Start by reading the Documentation. Sections 1-4.
- Refer to the .ledgerrc configuration file.
- Download
.csvfile from your bank or financial institution and runReckonagainst it. - 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
- Org Mode Workshop Example Config
- Particle Physics Researcher - Advanced Emacs org-mode examples and cookbook
- Worg - Org Mode Community
- Org Babel & Org mode cookbook
- Setup Emacs on WSL 2
- Phil Newton Org mode use cases
- Official Org-Mode Documentation
- Org-mode features You May Not Know - Bastien Guerry
- 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.
- Tree Manipulation Look at options under
, swhile 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 callingorg-clone-subtree-with-time-shift. The first thing that comes to mind, is the creation of routine events. - 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
- Getting things done: the art of stress-free productivity
- org-gtd.el/org-gtd.org at master · Trevoke/org-gtd.el · GitHub
- Read Getting things Done by David Allen
- Orgmode for GTD
- Org Mode - Organize Your Life In Plain Text! This is how the configuration is structured.
- GitHub - et2010/org-gtd: Private spacemacs layer for GTD.
- Get Things Done with Emacs
- GitHub - juanmanuelferrera/gtd-E
| 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:
- Obvious reason is that it reminds you what you were working on in that
particular place/context.
- 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.
- If an item has a real deadline = B priority. Real deadline means not a self-imposed deadline. Those are meaningless.
- If an item needs to be done As Soon As Possible (ASAP) = A priority.
- 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))
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
- 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
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.
- 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.
- Reference Material - project-jerome-index.org & contacts.org
- 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))
Org Mode Export
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\">⇱</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
- <–Linux Program Directory
- <–Communication in Emacs
- Mu4e with Microsoft Outlook 365
- All Computers Are Brilliant, Inc. > Alternative to self-hosting
Amazon.com: Run Your Own Mail Server (IT Mastery): 9781642350784: Lucas, Mich… Book for if I want to self-host.
Use gmail’s send as feature with porkbun. Check email compliance status
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.
- Click Create a new filter.
- Enter {(to:me) (deliveredto:USERNAME@gmail.com)} in the Has the words field (replacing USERNAME with your actual Gmail username).
- Click Create filter.
- 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:
- mu4e config in your dotspacemacs file and
- the .authinfo file found in the $HOME directory
- the .mbsyncrc file found in the $HOME directory
- Delete .mu as well as the dir contents of Maildir.
- 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
- <–Communication in Emacs
- GitHub - larrasket/rssc: rssc provides a real-time, self-hostable regex-orien…
- elfeed-paywall: Avoid paywalls and retrieve content from a feed entry’s link
- GitHub - leafac/kill-the-newsletter: Convert email newsletters into Atom feeds
Extending elfeed with PDF viewer and subtitles fetcher : emacs
- 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:
- View the page’s source code
- Look for the following text (ctrl-f): externalID
- Get the value for that element
- Replace that value into above URL:
- 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
;;---------------------------------------------------------------------------
(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
- Using Units in Emacs Calc
- WorgSheet Calc Intro
- Calc Manual SS 6.8
- Irreal Blog’s on Calc
- Make typing mathematical equations in LaTeX easier with Emacs’ Calc
- Why use Emacs Calc mode?
| 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
- 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.
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 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 - 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
- Jumps within visible file/buffer or visible buffer search (buffer navigation)
My scheme for navigating the visible buffer:
/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-1on s, since s in vim is basically useless (see r and c). These are fully compatible with vim commands. So e.g. I used 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-swoopin 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
- The rx Structured Regexp Notation
- Emacs Regexp Primer
- Vim search tips
- nfdn: Bulk Search & Replace Commands for Files and Buffers in Emacs
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.
C-vand highlight 1- M-x cua-rectangle-mark-mode
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.
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
- 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
- Jim Dennis on understanding Vi
- Jared Carroll’s definitive guide to Vim Text Objects
- Ismail Badawi - The Compositional Nature of Vim
- Helpful Image
- Vi in the context of EVIL (Emacs emulation of vim)
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
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.
- When Emacs 29 is configured with the flag
--with-native-compilationEmacs Lisp bytecode is translated to C and then machine code, yielding performance benefits. - 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.
- Debian does not build emacs with the flag
--with-xwidgetsfor 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. - 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
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- Whichkey Lag Due to issue described here: justbur/emacs-which-key#226 Lots of time spent inside which-key–maybe-replace
How to use the universal argument in spacemacs, as C-u is evil-scroll-up?
SPC ualso seeevil-want-C-u-scrollvariable.- If you run into graphical issues with VcXsrv or X2Go try starting emacs in
emas -nwmode first, to generate .elc (compiled emacs lisp) files. - Should the numbering of windows be off after resuming from sleep, such that
when creating a new frame window one
SPC 1cannot be selected:SPC F Dshould 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.