dotfiles

My personal shell configs and stuff
git clone git://git.alex.balgavy.eu/dotfiles.git
Log | Files | Refs | Submodules | README | LICENSE

config.org (149753B)


      1 * macOS installation info
      2 On macOS, I use Homebrew to install Emacs from daviderestivo/emacs-head/emacs-head@28.
      3 I install with ~--HEAD --with-dbus --with-cocoa --with-xwidgets --with-native-comp~.
      4 
      5 * Why I choose use-package
      6 - provides bind-key by default
      7 - is mostly just macros that wrap the needed stuff from package.el. can check that with ~macroexpand~.
      8 - adds a bunch of performance improvements
      9 * Let me know when we're at a certain version
     10 #+begin_src emacs-lisp
     11   (when (version<= "30" emacs-version)
     12     (message "Check out C-x w d to make windows dedicated."))
     13 #+end_src
     14 * Install from source
     15 Emacs 29 ships with a way to install packages from source, here's a small wrapper around it.
     16 
     17 #+begin_src emacs-lisp
     18   (cl-defun za/package-vc-install (&key (fetcher "github") repo name rev backend load)
     19     "Install a package from a remote if it's not already installed.
     20   This is a thin wrapper around `package-vc-install' in order to
     21   make non-interactive usage more ergonomic.  Takes the following
     22   named arguments:
     23 
     24   - FETCHER the remote where to get the package (e.g., \"gitlab\").
     25     If omitted, this defaults to \"github\".
     26 
     27   - REPO should be the name of the repository (e.g.,
     28   \"slotThe/arXiv-citation\".
     29 
     30   - NAME, REV, and BACKEND are as in `package-vc-install' (which
     31     see).
     32 
     33   - LOAD is optionally a subdirectory that should be added to `load-path'."
     34     (let* ((url (cond ((string-match-p (rx bos "http" (? ?s) "://") repo)
     35                        repo)
     36                       (t (format "https://www.%s.com/%s" fetcher repo))))
     37            (iname (when name (intern name)))
     38            (pac-name (or iname (intern (file-name-base repo))))
     39            (to-load (when load
     40                       (format "%s/%s"
     41                               (package-desc-dir (package-get-descriptor pac-name))
     42                               load))))
     43       (unless (package-installed-p pac-name)
     44         (package-vc-install url iname rev backend))
     45       (when load
     46         (unless (file-directory-p to-load)
     47           (user-error "Not a readable dir: %s" to-load))
     48         (add-to-list 'load-path to-load))
     49       (message "%s" pac-name)))
     50 #+end_src
     51 
     52 You can use this in use-package with an ~:init~ clause.
     53 
     54 * exec-path-from-shell (macOS)
     55 In macOS, the path is not set correctly (i.e. as it is in the terminal) in the GUI app. This fixes it.
     56 Not needed when using emacs-plus, because it has a custom patch for it. It also defines a [[https://github.com/d12frosted/homebrew-emacs-plus?tab=readme-ov-file#system-appearance-change][custom variable]] which hopefully should be enough to detect if we're running emacs-plus.
     57 
     58 #+begin_src emacs-lisp
     59   (when (and (string-equal system-type "darwin")
     60              (not (boundp 'ns-system-appearance-change-functions)))
     61     (use-package exec-path-from-shell
     62       :config
     63       (add-to-list 'exec-path-from-shell-variables "NOTMUCH_CONFIG")
     64       (exec-path-from-shell-initialize)))
     65 #+end_src
     66 
     67 * Emacs file locations
     68 ** Auto-Save files
     69 By default, auto-save files ("#file#") are placed in the same directory as the file itself.
     70 I want to put this all in some unified place:
     71 
     72 #+begin_src emacs-lisp
     73   (let ((saves-directory "~/.local/share/emacs/saves/"))
     74     (unless (file-directory-p saves-directory)
     75       (make-directory saves-directory))
     76     (setq auto-save-file-name-transforms
     77           `((".*" ,saves-directory t))))
     78 #+end_src
     79 
     80 ** Backup files
     81 By default, backup files (those with a tilde) are saved in the same directory as the currently edited file.
     82 This setting puts them in ~/.local/share/emacs/backups.
     83 
     84 #+begin_src emacs-lisp
     85   (let ((backups-directory "~/.local/share/emacs/backups"))
     86     (unless (file-directory-p backups-directory)
     87       (make-directory backups-directory))
     88     (setq backup-directory-alist `(("." . ,backups-directory)))
     89     (setq backup-by-copying t))
     90 #+end_src
     91 
     92 ** Custom settings file
     93 Both commands are necessary.
     94 First one tells Emacs where to save customizations.
     95 The second one actually loads them.
     96 
     97 #+begin_src emacs-lisp
     98   (setq custom-file (expand-file-name (concat user-emacs-directory "custom.el")))
     99   (load custom-file)
    100 #+end_src
    101 ** Delete by trash
    102 #+begin_src emacs-lisp
    103   (setq delete-by-moving-to-trash t)
    104   (unless (fboundp 'system-move-file-to-trash)
    105     (setq trash-directory "~/.Trash"))
    106 #+end_src
    107 * Daemon
    108 I want to have a way to kill the Emacs daemon.
    109 So, define a function that kills the frame, and with a prefix kills emacs.
    110 
    111 #+begin_src emacs-lisp
    112   (defun za/emacsclient-c-x-c-c (&optional arg)
    113     "If running in emacsclient, make C-x C-c exit frame, and C-u C-x C-c exit Emacs."
    114     (interactive "P") ; prefix arg in raw form
    115     (if arg
    116         (save-buffers-kill-emacs)
    117       (save-buffers-kill-terminal)))
    118 #+end_src
    119 
    120 Then, if I'm in an emacsclient, I want to bind C-x C-c to that function (if not, I just want the default keybinding):
    121 
    122 #+begin_src emacs-lisp
    123   ;; If not running in emacsclient, use the default bindings
    124   (if (daemonp)
    125       (bind-key "C-x C-c" #'za/emacsclient-c-x-c-c))
    126 #+end_src
    127 
    128 Furthermore, I want to set the theme correctly whenever I connect with 'emacsclient':
    129 
    130 #+begin_src emacs-lisp
    131   (if (daemonp)
    132       (add-hook 'after-make-frame-functions #'za/auto-select-theme))
    133 #+end_src
    134 * Sound support
    135 On macOS, you can use afplay:
    136 
    137 #+begin_src emacs-lisp
    138   (defun za/play-sound-file-macos (file &optional volume device)
    139     "Play sound using `afplay` on macOS"
    140     (unless (file-readable-p file)
    141       (user-error "File %s not readable." file))
    142 
    143     ;; the `apply` is required here because I need to build a list of arguments
    144     (apply 'start-process `("afplay" nil
    145                             "afplay"
    146                             ,@(if volume (list "-v" (int-to-string volume)))
    147                             ,file)))
    148 #+end_src
    149 
    150 Then redefine the play-sound-file function where needed:
    151 
    152 #+begin_src emacs-lisp
    153   (cond ((and (not (fboundp 'play-sound-internal))
    154               (eq system-type 'darwin))
    155          (advice-add 'play-sound-file :override #'za/play-sound-file-macos)))
    156 #+end_src
    157 * DISABLED Fix non-dbus macOS notification
    158 macOS version might not be compiled with dbus support; in that case you can use e.g. terminal-notifier.
    159 If you use the ~sender~ option, notifications don't show
    160 unless the app is in the background. [[https://github.com/julienXX/terminal-notifier/issues/68][See this Github issue.]]
    161 
    162 #+begin_src emacs-lisp :tangle no
    163   ;; on mac without dbus:
    164   (org-show-notification-handler
    165    (lambda (str) (start-process "terminal-notifier" nil (executable-find "terminal-notifier")
    166                                 "-title" "Timer done"
    167                                 "-message" str
    168                                 "-group" "org.gnu.Emacs"
    169                                 "-ignoreDnD"
    170                                 "-activate" "org.gnu.Emacs")))
    171 #+end_src
    172 * Custom notification functions
    173 #+begin_src emacs-lisp
    174   (defun za/notify (title message)
    175     "Show notification with TITLE and MESSAGE."
    176     (ignore-errors (require 'notifications))
    177     (cond ((fboundp 'ns-do-applescript)
    178            (ns-do-applescript
    179             (format "display notification \"%s\" with title \"%s\""
    180                     (replace-regexp-in-string "\"" "#" message)
    181                     (replace-regexp-in-string "\"" "#" title))))
    182           ((string= system-type "gnu/linux")
    183            (require 'notifications)
    184            (notifications-notify :title title :body message))
    185           (t (error "No notification handler defined!"))))
    186 
    187   (defun za/send-notification-interactivity-required (&rest _)
    188     "Notify that a function needs action."
    189     (za/notify "Interactivity required" "A function requires interactivity."))
    190 
    191   (defun za/notify-on-interactivity (func &rest r)
    192     "Send a notification whenever FUNC requires interactivity.
    193   Used as :around advice, calling FUNC with arguments R."
    194     (advice-add #'y-or-n-p :before #'za/send-notification-interactivity-required)
    195     (advice-add #'yes-or-no-p :before #'za/send-notification-interactivity-required)
    196     (advice-add #'user-error :before #'za/send-notification-interactivity-required)
    197     (with-demoted-errors "Error in %s" (apply func r))
    198     (advice-remove #'y-or-n-p #'za/send-notification-interactivity-required)
    199     (advice-remove #'yes-or-no-p #'za/send-notification-interactivity-required)
    200     (advice-remove #'user-error #'za/send-notification-interactivity-required))
    201 #+end_src
    202 
    203 * Editing
    204 ** Everything is UTF-8
    205 #+begin_src emacs-lisp
    206   (set-language-environment 'utf-8)
    207   (setq locale-coding-system 'utf-8)
    208   (setq buffer-file-coding-system 'utf-8-unix)
    209   (set-terminal-coding-system 'utf-8)
    210   (set-keyboard-coding-system 'utf-8)
    211   (set-selection-coding-system 'utf-8)
    212   (prefer-coding-system 'utf-8)
    213 #+end_src
    214 ** Overwrite selection on typing
    215 Normally, when I select something and start typing, Emacs clears the selection, i.e. it deselects and inserts text after the cursor.
    216 I want to replace the selection.
    217 
    218 #+begin_src emacs-lisp
    219   (delete-selection-mode t)
    220 #+end_src
    221 
    222 ** Strip trailing whitespace
    223 You can show trailing whitespace by setting show-trailing-whitespace to 't'.
    224 But I want to automatically strip trailing whitespace.
    225 Luckily there's already a function for that, I just need to call it in a hook:
    226 
    227 #+begin_src emacs-lisp
    228   (add-hook 'before-save-hook #'delete-trailing-whitespace)
    229 #+end_src
    230 
    231 ** Formatting & indentation
    232 
    233 Show a tab as 8 spaces:
    234 
    235 #+begin_src emacs-lisp
    236   (setq-default tab-width 8)
    237 #+end_src
    238 
    239 Never insert tabs with indentation by default:
    240 
    241 #+begin_src emacs-lisp
    242   (setq-default indent-tabs-mode nil)
    243 #+end_src
    244 
    245 Allow switching between the two easily:
    246 
    247 #+begin_src emacs-lisp
    248   (defun indent-tabs ()
    249     (interactive)
    250     (setq indent-tabs-mode t))
    251   (defun indent-spaces ()
    252     (interactive)
    253     (setq indent-tabs-mode nil))
    254 #+end_src
    255 
    256 Indentation for various modes:
    257 
    258 #+begin_src emacs-lisp
    259   (setq-default sh-basic-offset 2
    260                 c-basic-offset 4)
    261 #+end_src
    262 
    263 ** Wrapping
    264 A function to toggle wrapping:
    265 
    266 #+begin_src emacs-lisp
    267   (defvar-local za/wrapping nil "Wrapping changes per buffer.")
    268 
    269   (defun za/toggle-wrap (&optional enable)
    270     "Toggle line wrapping settings. With ENABLE a positive number, enable wrapping. If ENABLE is negative or zero, disable wrapping."
    271     (interactive "P") ; prefix arg in raw form
    272 
    273     ;; If an argument is provided, prefix or otherwise
    274     (if enable
    275         (let ((enable (cond ((numberp enable)
    276                              enable)
    277                             ((booleanp enable)
    278                              (if enable 1 0))
    279                             ((or (listp enable) (string= "-" enable))
    280                              (prefix-numeric-value enable)))))
    281           ;; If zero or negative, we want to disable wrapping, so pretend it's currently enabled.
    282           ;; And vice versa.
    283           (cond ((<= enable 0) (setq za/wrapping t))
    284                 ((> enable 0) (setq za/wrapping nil)))))
    285 
    286 
    287     (let ((disable-wrapping (lambda ()
    288                               (visual-line-mode -1)
    289                               (toggle-truncate-lines t)))
    290           (enable-wrapping (lambda ()
    291                              (toggle-truncate-lines -1)
    292                              (visual-line-mode))))
    293 
    294       ;; If za/wrapping is not locally set, infer its values from the enabled modes
    295       (unless (boundp 'za/wrapping)
    296         (setq za/wrapping (and visual-line-mode
    297                                (not truncate-lines))))
    298 
    299       ;; Toggle wrapping based on current value
    300       (cond (za/wrapping
    301              (funcall disable-wrapping)
    302              (setq za/wrapping nil)
    303              (message "Wrapping disabled."))
    304             (t
    305              (funcall enable-wrapping)
    306              (setq za/wrapping t)
    307              (message "Wrapping enabled.")))))
    308 #+end_src
    309 
    310 And a keybinding to toggle wrapping:
    311 
    312 #+begin_src emacs-lisp
    313   (bind-key "C-c q w" #'za/toggle-wrap)
    314 #+end_src
    315 
    316 And fringe indicators:
    317 
    318 #+begin_src emacs-lisp
    319   (setopt visual-line-fringe-indicators '(left-curly-arrow right-curly-arrow))
    320 #+end_src
    321 ** Pager toggle
    322 M-x view-mode enables pager behavior.
    323 I want read-only files to automatically use pager mode:
    324 
    325 #+begin_src emacs-lisp
    326   (setq view-read-only t)
    327 #+end_src
    328 ** Prefer newer file loading
    329 #+begin_src emacs-lisp
    330   (setq load-prefer-newer t)
    331 #+end_src
    332 
    333 ** Automatically find tags file
    334 When opening a file in a git repo, try to discover the etags file:
    335 
    336 #+begin_src emacs-lisp
    337   (defun current-tags-file ()
    338     "Get current tags file"
    339     (let* ((tagspath ".git/etags")
    340            (git-root (locate-dominating-file (buffer-file-name) tagspath)))
    341       (if git-root
    342           (expand-file-name tagspath git-root))))
    343 
    344   (setq default-tags-table-function #'current-tags-file)
    345 #+end_src
    346 
    347 There's probably a better way to write this. I need to ask Reddit for feedback at some point.
    348 
    349 ** End sentences with one space
    350 Emacs uses the rather old-fashioned convention of treating a period followed by double spaces as end of sentence. However, it is more common these days to end sentences with a period followed by a single space.
    351 
    352 Let a period followed by a single space be treated as end of sentence:
    353 
    354 #+begin_src emacs-lisp
    355   (setopt sentence-end-double-space nil)
    356 #+end_src
    357 * Keybindings
    358 ** Expansion/completion
    359 Use hippie expand instead of dabbrev-expand:
    360 
    361 #+begin_src emacs-lisp
    362   (bind-key "M-/" #'hippie-expand)
    363 #+end_src
    364 
    365 ** Zap up to char
    366 It's more useful for me to be able to delete up to a character instead of to and including a character:
    367 
    368 #+begin_src emacs-lisp
    369   (defun za/zap-up-to-char-icase ()
    370     "Ignore case for zap-up-to-char"
    371     (interactive)
    372     (let ((case-fold-search nil))
    373       (call-interactively #'zap-up-to-char)))
    374   (bind-key "M-z" #'za/zap-up-to-char-icase)
    375 #+end_src
    376 
    377 ** Forward-word and forward-to-word
    378 Change M-f to stop at the start of the word:
    379 
    380 #+begin_src emacs-lisp
    381   (bind-key "M-f" #'forward-to-word)
    382 #+end_src
    383 
    384 Bind ESC M-f to the old functionality of M-f (stop at end of word)
    385 
    386 #+begin_src emacs-lisp
    387   (bind-key "ESC M-f" #'forward-word)
    388 #+end_src
    389 
    390 ** Rectangle insert string
    391 #+begin_src emacs-lisp
    392   (bind-key "C-x r I" #'string-insert-rectangle)
    393   (bind-key "C-x r R" #'replace-rectangle)
    394 #+end_src
    395 
    396 ** Toggle auto-revert-mode
    397 Sometimes I want to toggle auto reverting (or autoread) of buffer:
    398 
    399 #+begin_src emacs-lisp
    400   (bind-key "C-c q a" #'auto-revert-mode)
    401 #+end_src
    402 ** Fast access to view-mode (pager)
    403 I want to bind view-mode to a key for easy access:
    404 
    405 #+begin_src emacs-lisp
    406   (bind-key "C-c q r" 'view-mode)
    407 #+end_src
    408 
    409 ** Kill this buffer
    410 I like to be able to kill a buffer instantly:
    411 
    412 #+begin_src emacs-lisp
    413   (bind-key "s-<backspace>" 'kill-current-buffer)
    414 #+end_src
    415 
    416 ** Delete this file (and kill the buffer)
    417 #+begin_src emacs-lisp
    418   (defun za/delete-this-file ()
    419     "Kill the current buffer and delete its associated file."
    420     (interactive)
    421     (let ((fname (buffer-file-name))
    422           (buf (current-buffer)))
    423       (unless (and fname (file-exists-p fname))
    424         (user-error "Buffer has no associated file."))
    425 
    426       (unless (yes-or-no-p (format "Really delete %s and its buffer?" fname))
    427         (user-error "User cancelled."))
    428 
    429       (delete-file fname 'trash-if-enabled)
    430       (kill-buffer buf)
    431       (message "Deleted %s and killed its buffer." fname)))
    432 
    433   (bind-key "C-c s-<backspace>" #'za/delete-this-file)
    434 #+end_src
    435 
    436 ** Toggle fullscreen
    437 I'll use the keybinding that's standard on macOS:
    438 
    439 #+begin_src emacs-lisp
    440   (bind-key "C-s-f" #'toggle-frame-fullscreen)
    441 #+end_src
    442 
    443 ** Sexp manipulation
    444 When I write lisp, sometimes I want to switch two sexps (e.g. ~(one) (two)~ → ~(two) (one)~), so a key binding is nice for that:
    445 
    446 #+begin_src emacs-lisp
    447   (bind-key "C-S-t" #'transpose-sexps)
    448 #+end_src
    449 
    450 Also, to raise a sexp (e.g. ~(one (two))~ → ~(two)~):
    451 
    452 #+begin_src emacs-lisp
    453   (bind-key "C-S-u" #'raise-sexp)
    454 #+end_src
    455 
    456 ** Dedicated windows
    457 Sometimes I want to avoid Emacs overriding a window's contents.
    458 So I create a keybinding to toggle dedicated on a window:
    459 
    460 #+begin_src emacs-lisp
    461   (defun za/toggle-window-dedicated-p ()
    462     "Toggle set-window-dedicated-p on current window"
    463     (interactive)
    464     (cond ((window-dedicated-p (selected-window))
    465            (set-window-dedicated-p (selected-window) nil)
    466            (message "Window no longer dedicated"))
    467           (t
    468            (set-window-dedicated-p (selected-window) t)
    469            (message "Window marked as dedicated"))))
    470 
    471   (bind-key "C-x 9" #'za/toggle-window-dedicated-p)
    472 
    473 #+end_src
    474 
    475 ** Rotate windows horizontal ↔ vertical
    476 #+begin_src emacs-lisp
    477   (defun za/rotate-windows ()
    478     (interactive)
    479     (if (= (count-windows) 2)
    480         (let* ((this-win-buffer (window-buffer))
    481                (next-win-buffer (window-buffer (next-window)))
    482                (this-win-edges (window-edges (selected-window)))
    483                (next-win-edges (window-edges (next-window)))
    484                (this-win-2nd (not (and (<= (car this-win-edges)
    485                                            (car next-win-edges))
    486                                        (<= (cadr this-win-edges)
    487                                            (cadr next-win-edges)))))
    488                (splitter
    489                 (if (= (car this-win-edges)
    490                        (car (window-edges (next-window))))
    491                     'split-window-horizontally
    492                   'split-window-vertically)))
    493           (delete-other-windows)
    494           (let ((first-win (selected-window)))
    495             (funcall splitter)
    496             (if this-win-2nd (other-window 1))
    497             (set-window-buffer (selected-window) this-win-buffer)
    498             (set-window-buffer (next-window) next-win-buffer)
    499             (select-window first-win)
    500             (if this-win-2nd (other-window 1))))))
    501 #+end_src
    502 
    503 #+begin_src emacs-lisp
    504   (bind-key "C-x 7" #'za/rotate-windows)
    505 #+end_src
    506 
    507 ** Open line like in Vim
    508 I prefer to open-line the way o/O works in Vim:
    509 
    510 #+begin_src emacs-lisp
    511   ;; Autoindent open-*-lines
    512   (defvar za/open-line-newline-and-indent t
    513     "Modify the behavior of the open-*-line functions to cause them to autoindent.")
    514 
    515   (defun za/open-line (prefix)
    516     "Open line like `o`/`O` in Vim. Negative prefix for line above, positive for below."
    517     (interactive "p")
    518     (cond ((< prefix 0)
    519            (beginning-of-line)
    520            (open-line (abs prefix)))
    521           (t
    522            (end-of-line)
    523            (open-line prefix)
    524            (forward-line 1)))
    525     (when za/open-line-newline-and-indent
    526       (indent-according-to-mode)))
    527 
    528   (defun za/open-line-keep-point (prefix)
    529     "Open line like `o`/`O` in Vim but don't move point. Negative prefix for line above, positive for below."
    530     (interactive "p")
    531     (save-mark-and-excursion (za/open-line prefix)))
    532 #+end_src
    533 
    534 And keybindings:
    535 
    536 #+begin_src emacs-lisp
    537   (bind-key "C-o" #'za/open-line)
    538   (bind-key "C-M-o" #'za/open-line-keep-point)
    539 #+end_src
    540 
    541 ** Unfill region/paragraph
    542 Taken from here: https://www.emacswiki.org/emacs/UnfillParagraph
    543 
    544 #+begin_src emacs-lisp
    545   (defun za/unfill-paragraph (&optional region)
    546     "Takes a multi-line paragraph and makes it into a single line of text."
    547     (interactive (progn (barf-if-buffer-read-only) '(t)))
    548     (let ((fill-column (point-max))
    549           ;; This would override `fill-column' if it's an integer.
    550           (emacs-lisp-docstring-fill-column t))
    551       (fill-paragraph nil region)))
    552 
    553   (bind-key "M-Q" #'za/unfill-paragraph)
    554 #+end_src
    555 ** Easily edit my config
    556 Bind a keyboard shortcut to open my config.
    557 The "(interactive)" means that it can be called from a keybinding or from M-x.
    558 
    559 #+begin_src emacs-lisp
    560   (defun za/edit-config-org ()
    561     "Edit my config.org file"
    562     (interactive)
    563     (find-file (expand-file-name "config.org" user-emacs-directory)))
    564 #+end_src
    565 
    566 #+begin_src emacs-lisp
    567   (bind-key "C-c E" 'za/edit-config-org)
    568 #+end_src
    569 ** Visible mode
    570 #+begin_src emacs-lisp
    571   (bind-key (kbd "C-c q v") #'visible-mode)
    572 #+end_src
    573 ** Clone buffer indirectly by default
    574 #+begin_src emacs-lisp
    575   (bind-key (kbd "C-x x n") #'clone-indirect-buffer)
    576 #+end_src
    577 * Custom functions
    578 ** Make region readonly or writable
    579 #+begin_src emacs-lisp
    580   (defun za/set-region-read-only (begin end)
    581     "Sets the read-only text property on the marked region.
    582   Use `set-region-writeable' to remove this property."
    583     ;; See https://stackoverflow.com/questions/7410125
    584     (interactive "r")
    585     (with-silent-modifications
    586       (put-text-property begin end 'read-only t)))
    587 
    588   (defun za/set-region-writeable (begin end)
    589     "Removes the read-only text property from the marked region.
    590   Use `set-region-read-only' to set this property."
    591     ;; See https://stackoverflow.com/questions/7410125
    592     (interactive "r")
    593     (with-silent-modifications
    594       (remove-text-properties begin end '(read-only t))))
    595 #+end_src
    596 ** Insert macro as Lisp
    597 From here: https://www.masteringemacs.org/article/keyboard-macros-are-misunderstood
    598 
    599 #+begin_src emacs-lisp
    600   (use-package kmacro
    601     :ensure nil ; included with Emacs
    602     :bind (:map kmacro-keymap
    603                 ("I" . kmacro-insert-macro))
    604     :config
    605     (defalias 'kmacro-insert-macro 'insert-kbd-macro)
    606 
    607     ;; Add advice to ignore errors on `kmacro-keyboard-macro-p`, it was
    608     ;; messing up because of some entry in `obarray`
    609     (advice-add #'kmacro-keyboard-macro-p :around (lambda (fun sym) "Ignore errors." (ignore-errors (funcall fun sym)))))
    610 #+end_src
    611 ** Show local help at point when idling
    612 #+begin_src emacs-lisp
    613   (defun za/echo-area-tooltips ()
    614     "Show tooltips in the echo area automatically for current buffer."
    615     (setq-local help-at-pt-display-when-idle t
    616                 help-at-pt-timer-delay 0)
    617     (help-at-pt-cancel-timer)
    618     (help-at-pt-set-timer))
    619 #+end_src
    620 
    621 ** Info manual functions
    622 For some reason, these things don't show up in the index:
    623 
    624 #+begin_src emacs-lisp
    625   (defun elisp-info (&optional node)
    626     "Read documentation for Elisp in the info system.
    627   With optional NODE, go directly to that node."
    628     (interactive)
    629     (info (format "(elisp)%s" (or node ""))))
    630 #+end_src
    631 
    632 Though I can also just use ~info-display-manual~.
    633 
    634 ** Radio
    635 Just a wrapper function to my radio script:
    636 
    637 #+begin_src emacs-lisp
    638   (defun radio ()
    639     "Play an internet radio"
    640     (interactive)
    641     (ansi-term "radio" "*radio*"))
    642 #+end_src
    643 
    644 ** no-op
    645 #+begin_src emacs-lisp
    646   (defun za/no-op (&rest args))
    647 #+end_src
    648 
    649 ** Syncthing
    650 Some functions to start/stop syncthing.
    651 #+begin_src emacs-lisp
    652   (defconst za/st-buffer-name "*syncthing*" "Buffer name for the syncthing process.")
    653   (defun za/st ()
    654     "Start syncthing"
    655     (interactive)
    656     (if (get-buffer-process za/st-buffer-name)
    657         (user-error "Syncthing is already running."))
    658     (async-shell-command "syncthing serve --no-browser" za/st-buffer-name))
    659 
    660   (defun za/st-kill ()
    661     "Stop syncthing"
    662     (interactive)
    663     (unless (get-buffer-process za/st-buffer-name)
    664       (user-error "Syncthing is not running."))
    665     (async-shell-command "syncthing cli operations shutdown"))
    666 #+end_src
    667 ** Replace typographic quotes
    668 #+begin_src emacs-lisp
    669   (defun za/replace-typographic-quotes ()
    670     "Replace typographic quotes with plain quotes"
    671     (interactive)
    672     (save-mark-and-excursion
    673       (goto-char (point-min))
    674       (while (re-search-forward (rx (any ?“ ?”)) nil 'noerror)
    675         (replace-match "\""))
    676       (goto-char (point-min))
    677       (while (re-search-forward (rx (any "‘" "’")) nil 'noerror)
    678         (replace-match "'"))))
    679 #+end_src
    680 ** Distraction-free on current buffer
    681 #+begin_src emacs-lisp
    682   (defun za/buffer-focus-no-distractions ()
    683     "Focus on this buffer"
    684     (interactive)
    685     (cond ((or (not (boundp 'za/no-distractions))
    686                (not za/no-distractions))
    687            (olivetti-mode 1)
    688            (line-number-mode 0)
    689            (display-line-numbers-mode 0)
    690            (window-configuration-to-register ?w)
    691            (delete-other-windows)
    692            (setq-local za/tmp/mode-line-format mode-line-format)
    693            (setq-local mode-line-format nil)
    694            (setq-local za/tmp/internal-border-width (frame-parameter nil 'internal-border-width))
    695            (set-frame-parameter nil 'internal-border-width 20)
    696            (setq-local za/no-distractions t)
    697            (message "Window configuration stored in register W"))
    698           (za/no-distractions
    699            (set-frame-parameter nil 'internal-border-width za/tmp/internal-border-width)
    700            (line-number-mode 0)
    701            (display-line-numbers-mode 1)
    702            (setq-local mode-line-format za/tmp/mode-line-format)
    703            (jump-to-register ?w)
    704            (olivetti-mode 0)
    705            (setq-local za/no-distractions nil))))
    706 #+end_src
    707 * Interface
    708 ** Theme
    709 Icons required for some parts of the doom theme:
    710 
    711 #+begin_src emacs-lisp
    712   (use-package all-the-icons)
    713 #+end_src
    714 
    715 Load Doom Emacs themes:
    716 
    717 #+begin_src emacs-lisp
    718   (use-package doom-themes
    719     :config
    720     ;; Global settings (defaults)
    721     (setq doom-themes-enable-bold t    ; if nil, bold is universally disabled
    722           doom-themes-enable-italic t) ; if nil, italics is universally disabled
    723 
    724     ;; Enable flashing mode-line on errors
    725     (doom-themes-visual-bell-config)
    726 
    727     ;; Corrects (and improves) org-mode's native fontification.
    728     (doom-themes-org-config))
    729 #+end_src
    730 
    731 Define the themes I want:
    732 
    733 #+begin_src emacs-lisp
    734   (defconst za/dark-theme-name 'doom-one "A symbol representing the name of the dark theme I use.")
    735   (defconst za/light-theme-name 'jokull "A symbol representing the name of the light theme I use.")
    736   ;; I used to use doom-acario-light before writing my own theme
    737 
    738   (defun za/dark-theme ()
    739     "Switch to dark theme"
    740     (interactive)
    741     (mapc #'disable-theme custom-enabled-themes)
    742     (load-theme za/dark-theme-name t)
    743     (add-hook 'pdf-view-mode-hook #'pdf-view-midnight-minor-mode))
    744 
    745   (defun za/light-theme ()
    746     "Switch to light theme"
    747     (interactive)
    748     (mapc #'disable-theme custom-enabled-themes)
    749     (load-theme za/light-theme-name t)
    750     (remove-hook 'pdf-view-mode-hook #'pdf-view-midnight-minor-mode))
    751 #+end_src
    752 
    753 Change theme depending on the current system theme.
    754 The way I check for dark mode is defined in 'dark-mode-p'; currently I use the presence of the ~/.config/dark-theme file to indicate when dark theme is set.
    755 I quote the call to ~file-exists-p~ because I want to evaluate it on-demand, not immediately.
    756 A function ending in '-p' is a predicate, i.e. returns true or false.
    757 If calling a function that's in a variable, you have to use 'funcall'.
    758 To evaluate a quoted form, use 'eval'.
    759 
    760 #+begin_src emacs-lisp
    761   (defun za/auto-select-theme (&rest _)
    762     "Automatically select dark/light theme based on presence of ~/.config/dark-theme"
    763     (let ((dark-mode-p '(file-exists-p "~/.config/dark-theme")))
    764       (if (eval dark-mode-p)
    765           (za/dark-theme)
    766         (za/light-theme))))
    767 
    768   (za/auto-select-theme)
    769 #+end_src
    770 
    771 ** Font
    772 I want Menlo, size 12:
    773 
    774 #+begin_src emacs-lisp
    775   (add-to-list 'default-frame-alist '(font . "Menlo-13"))
    776   (custom-set-faces
    777    ; height = pt * 10
    778    '(fixed-pitch ((t (:family "Menlo" :height 130))))
    779    '(variable-pitch ((t (:family "ETBembo" :height 140))))
    780    '(org-block ((t (:inherit fixed-pitch))))
    781    '(org-table ((t (:foreground "#0087af" :inherit fixed-pitch))))
    782    '(org-indent ((t (:inherit (org-hide fixed-pitch))))))
    783 
    784   (set-face-font 'fixed-pitch "Menlo-13")
    785   (set-face-font 'variable-pitch "ETBembo-14")
    786 #+end_src
    787 
    788 I like nicer list bullets:
    789 
    790 #+begin_src emacs-lisp
    791   (font-lock-add-keywords
    792    'org-mode
    793    `((,(rx bol (* blank) (group ?-) " ")  ; list regexp
    794       1                                   ; first match
    795       '(face nil display "•"))))          ; replace with bullet point, keep same face
    796 #+end_src
    797 ** Cursor
    798 The default box cursor isn't really accurate, because the cursor is actually between letters, not on a letter.
    799 So, I want a bar instead of a box:
    800 
    801 #+begin_src emacs-lisp
    802   (setq-default cursor-type '(bar . 4)
    803                 cursor-in-non-selected-windows 'hollow)
    804 #+end_src
    805 
    806 (I use ~setq-default~ here because cursor-type is automatically buffer-local when it's set)
    807 
    808 And enable cursorline:
    809 
    810 #+begin_src emacs-lisp
    811   (global-hl-line-mode)
    812 #+end_src
    813 
    814 And visualize tab characters by stretching the cursor:
    815 
    816 #+begin_src emacs-lisp
    817   (setq-default x-stretch-cursor t)
    818 #+end_src
    819 ** Matching parentheses
    820 Don't add a delay to show matching parenthesis.
    821 Must come before show-paren-mode enable.
    822 
    823 #+begin_src emacs-lisp
    824   (setq show-paren-delay 0)
    825 #+end_src
    826 
    827 Show matching parentheses:
    828 
    829 #+begin_src emacs-lisp
    830   (show-paren-mode t)
    831 #+end_src
    832 ** Line numbers
    833 Relative line numbers:
    834 
    835 #+begin_src emacs-lisp
    836   (setq display-line-numbers-type 'relative)
    837   (global-display-line-numbers-mode)
    838 #+end_src
    839 
    840 Function to hide them:
    841 
    842 #+begin_src emacs-lisp
    843   (defun za/hide-line-numbers ()
    844     "Hide line numbers"
    845     (display-line-numbers-mode 0))
    846 #+end_src
    847 Don't display them in specific modes.  For each of the modes in
    848 'mode-hooks', add a function to hide line numbers when the mode
    849 activates (which triggers the 'mode'-hook).
    850 
    851 #+begin_src emacs-lisp
    852   (let ((mode-hooks '(doc-view-mode-hook vterm-mode-hook mpc-status-mode-hook mpc-tagbrowser-mode-hook)))
    853     (mapc
    854      (lambda (mode-name)
    855        (add-hook mode-name #'za/hide-line-numbers))
    856      mode-hooks))
    857 #+end_src
    858 ** Modeline
    859 I want to show the time and date in the modeline:
    860 
    861 #+begin_src emacs-lisp
    862   (setq display-time-day-and-date t           ; also the date
    863         display-time-default-load-average nil ; don't show load average
    864         display-time-format "%I:%M%p %e %b (%a)")   ; "HR:MIN(AM/PM) day-of-month Month (Day)"
    865   (display-time-mode 1)                  ; enable time mode
    866 #+end_src
    867 
    868 And to set the modeline format:
    869 
    870 #+begin_src emacs-lisp
    871   (setq-default mode-line-format '("%e" mode-line-front-space mode-line-mule-info mode-line-client mode-line-modified mode-line-remote mode-line-frame-identification mode-line-buffer-identification "   " mode-line-position
    872                                    (vc-mode vc-mode)
    873                                    "  " mode-line-modes mode-line-misc-info mode-line-end-spaces))
    874 #+end_src
    875 
    876 I want to hide certain modes from the modeline.
    877 For that, ~delight~ is a useful package; unlike ~diminish~, it can also change the display of /major/ modes (~diminish~ only does minor modes).
    878 
    879 #+begin_src emacs-lisp
    880     (use-package delight
    881       :config
    882       (delight 'visual-line-mode " ↩" 'simple)
    883       (delight 'auto-revert-mode " AR" 'autorevert)
    884       (delight 'abbrev-mode " Abv" 'abbrev))
    885 #+end_src
    886 ** Transparent title bar
    887 #+begin_src emacs-lisp
    888   (add-to-list 'default-frame-alist '(ns-transparent-titlebar . t))
    889 #+end_src
    890 ** Frame title
    891 #+begin_src emacs-lisp
    892   (setopt frame-title-format "%F--%b-[%f]--%Z")
    893 #+end_src
    894 
    895 ** Tab bar
    896 Only show tab bar if there's more than 1 tab:
    897 
    898 #+begin_src emacs-lisp
    899   (setq tab-bar-show 1)
    900 #+end_src
    901 ** Buffer displaying
    902 
    903 So, this is a bit hard to grok. But basically the alist contains a
    904 regular expression to match a buffer name, then a list of functions to
    905 use in order for displaying the list, and then options for those functions (each of which is an alist).
    906 
    907 #+begin_src emacs-lisp
    908   (setq
    909    ;; Maximum number of side-windows to create on (left top right bottom)
    910    window-sides-slots '(0   ;; left
    911                         1   ;; top
    912                         3   ;; right
    913                         1 ) ;; bottom
    914 
    915    display-buffer-alist `(
    916                           ;; Right side
    917                           (,(rx (or "*Help*" (seq "*helpful " (* anything) "*")))
    918                            (display-buffer-reuse-window display-buffer-in-side-window)
    919                            (side . right)
    920                            (slot . -1)
    921                            (inhibit-same-window . t))
    922                           (,(rx "*Async Shell " (* anything) "*")
    923                            (display-buffer-reuse-window display-buffer-in-side-window)
    924                            (side . right)
    925                            (slot . 0)
    926                            (inhibit-same-window . t))
    927                           (,(rx "magit-process: " (* anything))
    928                            (display-buffer-reuse-window display-buffer-in-side-window)
    929                            (side . right)
    930                            (slot . 0)
    931                            (inhibit-same-window . t))
    932 
    933                           ;; Top side
    934                           (,(rx "*Info*")
    935                            (display-buffer-reuse-window display-buffer-in-side-window)
    936                            (side . top)
    937                            (slot . 0))
    938                           (,(rx "*Man " (* anything) "*")
    939                            (display-buffer-reuse-window display-buffer-in-side-window)
    940                            (side . top)
    941                            (slot . 0))
    942 
    943                           ;; Bottom
    944                           (,(rx "*Flycheck errors*")
    945                            (display-buffer-reuse-window display-buffer-in-side-window)
    946                            (side . bottom)
    947                            (slot . 0))))
    948 #+end_src
    949 
    950 And a way to toggle those side windows:
    951 
    952 #+begin_src emacs-lisp
    953   (bind-key "C-c W" #'window-toggle-side-windows)
    954 #+end_src
    955 
    956 ** Eldoc
    957 When editing Elisp and other supported major-modes, Eldoc will display useful information about the construct at point in the echo area.
    958 
    959 #+begin_src emacs-lisp
    960   (use-package eldoc
    961     :ensure nil ; installed with Emacs
    962     :delight
    963     :config
    964     (global-eldoc-mode 1))
    965 #+end_src
    966 
    967 ** Pulse line
    968 When you switch windows, Emacs can flash the cursor briefly to guide your eyes; I like that.
    969 Set some options for pulsing:
    970 
    971 #+begin_src emacs-lisp
    972   (setq pulse-iterations 10)
    973   (setq pulse-delay 0.05)
    974 #+end_src
    975 
    976 Define the pulse function:
    977 
    978 #+begin_src emacs-lisp
    979   (defun pulse-line (&rest _)
    980     "Pulse the current line."
    981     (pulse-momentary-highlight-one-line (point)))
    982 #+end_src
    983 
    984 Run it in certain cases: scrolling up/down, recentering, switching windows.
    985 'dolist' binds 'command' to each value in the list in turn, and runs the body.
    986 'advice-add' makes the pulse-line function run after 'command'.
    987 
    988 #+begin_src emacs-lisp
    989   (dolist (command '(scroll-up-command scroll-down-command recenter-top-bottom other-window))
    990     (advice-add command :after #'pulse-line))
    991 #+end_src
    992 
    993 And set the pulse color:
    994 
    995 #+begin_src emacs-lisp
    996   (custom-set-faces '(pulse-highlight-start-face ((t (:background "CadetBlue2")))))
    997 #+end_src
    998 
    999 ** Enable all commands
   1000 By default, Emacs disables some commands.
   1001 I want to have these enabled so I don't get a prompt whenever I try to use a disabled command.
   1002 
   1003 #+begin_src emacs-lisp
   1004   (setq disabled-command-function nil)
   1005 #+end_src
   1006 ** More extensive apropos
   1007 #+begin_src emacs-lisp
   1008   (setq apropos-do-all t)
   1009 #+end_src
   1010 ** Enable recursive minibuffers
   1011 #+begin_src emacs-lisp
   1012   (setq enable-recursive-minibuffers t
   1013         minibuffer-depth-indicate-mode t)
   1014 #+end_src
   1015 ** View webp and other formats
   1016 Emacs handles common image formats internally, but for stuff like webp, you need an external converter:
   1017 
   1018 #+begin_src emacs-lisp
   1019   (setq image-use-external-converter t)
   1020 #+end_src
   1021 
   1022 You also need imagemagick installed.
   1023 
   1024 ** Repeat mode: easy repeating of commands
   1025 #+begin_src emacs-lisp
   1026   (repeat-mode 1)
   1027 #+end_src
   1028 
   1029 ** Messages
   1030 Hide some messages I don't need.
   1031 
   1032 #+begin_src emacs-lisp
   1033   (recentf-mode)
   1034   (setq inhibit-startup-message t)
   1035 #+end_src
   1036 
   1037 ** Start buffer (dashboard)
   1038 #+begin_src emacs-lisp
   1039   (use-package dashboard
   1040     :custom
   1041     (dashboard-startup-banner 'logo)
   1042     (dashboard-items '((gtd-inbox-counts . 3)
   1043                        (syncthing-status . 3)
   1044                        (recents . 5)
   1045                        (bookmarks . 5)))
   1046 
   1047 
   1048     :bind (:map dashboard-mode-map
   1049                 ("ss" . za/st)
   1050                 ("sk" . za/st-kill)
   1051                 ("J" . org-clock-goto))
   1052     :config
   1053     (add-to-list 'dashboard-item-generators '(gtd-inbox-counts . dashboard-insert-gtd-inbox-counts))
   1054     (add-to-list 'dashboard-item-generators '(syncthing-status . dashboard-insert-syncthing-status))
   1055 
   1056     (defun za/quotes-from-my-site ()
   1057       (let* ((quotes-file (concat za/my-website-dir "content/quotes.md"))
   1058              ;; Reformat quotes for display in dashboard
   1059              (file-contents (with-temp-buffer
   1060                               (insert-file-contents quotes-file)
   1061                               (re-search-forward (rx bol "> "))
   1062                               (delete-region (point-min) (pos-bol))
   1063                               (goto-char (point-min))
   1064                               (save-excursion (replace-regexp (rx bol ">" (* " ") (? "\n")) ""))
   1065                               (save-excursion (replace-regexp (rx eol "\n") "  "))
   1066                               (buffer-substring-no-properties (point-min) (point-max)))))
   1067         ;; Split file into individual quotes
   1068         (split-string file-contents "  ---  ")))
   1069 
   1070     (defun za/quotes-manual ()
   1071       (list "The Universe is under no obligation to make sense to you."
   1072         "I would like to die on Mars. Just not on impact."
   1073         "That's one small step for a man, one giant leap for mankind."
   1074         "We choose to go to the moon in this decade and do the other things, not because they are easy, but because they are hard, because that goal will serve to organize and measure the best of our energies and skills, because that challenge is one that we are willing to accept, one we are unwilling to postpone, and one which we intend to win."
   1075         "Space is for everybody. It’s not just for a few people in science or math, or for a select group of astronauts. That’s our new frontier out there, and it’s everybody’s business to know about space."
   1076         "On one side are those who believe space travel is difficult work, but who go for it anyway. On the other are those who believe caring for a goldfish is, and who don’t go after much of anything. Where we choose to seed ourselves on the spectrum of what’s possible is what will ultimately define the size of our lives."))
   1077 
   1078     ;; Use my saved quotes in the dashboard (https://alex.balgavy.eu/quotes/)
   1079     (setopt dashboard-footer-messages
   1080           (let* ((quotes (if (boundp 'za/my-website-dir) (za/quotes-from-my-site) (za/quotes-manual))))
   1081             ;; Run each quote through fill-region for better display
   1082             (require 's)
   1083             (mapcar (lambda (quote-line)
   1084                       (with-temp-buffer
   1085                         (insert (s-trim quote-line))
   1086                         (fill-region (point-min) (point-max))
   1087                         (buffer-substring-no-properties (point-min) (point-max))))
   1088                     quotes))))
   1089 
   1090   (defun dashboard-insert-gtd-inbox-counts (list-size)
   1091     (require 'org-roam)
   1092     (let* ((lines-inbox (za/org-count-headlines-in-file 1 za/org-life-inbox))
   1093            (lines-mobile (if (boundp 'za/org-life-inbox-mobile) (za/org-count-headlines-in-file 1 za/org-life-inbox-mobile) 0))
   1094            (count-docs (length (directory-files za/org-life-doc-inbox nil (rx bos (not ?.)))))
   1095            (item-list))
   1096 
   1097       (when (> lines-inbox 0)
   1098         (push (list :name "Inbox" :count lines-inbox :file za/org-life-inbox) item-list))
   1099       (when (> lines-mobile 0)
   1100         (push (list :name "Mobile" :count lines-mobile :file za/org-life-inbox-mobile) item-list))
   1101       (when (> count-docs 0)
   1102         (push (list :name "Docs" :count count-docs :file za/org-life-doc-inbox) item-list))
   1103 
   1104       (dashboard-insert-section
   1105        ;; Widget title
   1106        "GTD:"
   1107        ;; list generated for dashboard
   1108        item-list
   1109        list-size
   1110        'gtd
   1111        "t"
   1112        ;; decide what to do when clicked ("el" is automatically assigned)
   1113        `(lambda (&rest _)
   1114           (message "%s" (find-file (plist-get ',el :file))))
   1115        ;; show how list is shown in dashboard ("el" is automatically assigned)
   1116        (format "%s: %s" (plist-get el :name) (plist-get el :count)))))
   1117 
   1118   (defun dashboard-insert-syncthing-status (list-size)
   1119     (when (and (get-buffer-process za/st-buffer-name)
   1120                (boundp 'za/syncthing-api-key))
   1121       (let* ((syncstatus (json-parse-string
   1122                           (shell-command-to-string
   1123                            (format "curl -sH 'Authorization: Bearer %s' 'http://localhost:8384/rest/db/completion'" za/syncthing-api-key))))
   1124              (completion (gethash "completion" syncstatus))
   1125              (folders (json-parse-string
   1126                        (shell-command-to-string
   1127                         (format "curl -sH 'Authorization: Bearer %s' 'http://localhost:8384/rest/stats/folder'" za/syncthing-api-key))
   1128                        :false-object nil
   1129                        :null-object nil))
   1130              (org-lastsync (format-time-string "%H:%M:%S (%F)" (date-to-time (gethash "lastScan" (gethash "lifeorg" folders)))))
   1131              (devices (json-parse-string
   1132                        (shell-command-to-string
   1133                         (format "curl -sH 'Authorization: Bearer %s' 'http://localhost:8384/rest/system/connections'" za/syncthing-api-key))
   1134                        :object-type 'alist
   1135                        :false-object nil
   1136                        :null-object nil))
   1137              (connected-devices (length (seq-filter
   1138                                          (lambda (a)
   1139                                            (alist-get 'connected (cdr a))) (alist-get 'connections devices))))
   1140              (item-list `(,(format "Completion: %s%%" completion)
   1141                           ,(format "Connected devices: %s" connected-devices)
   1142                           ,(format "Org last sync: %s" org-lastsync))))
   1143 
   1144         (dashboard-insert-section
   1145          ;; Widget title
   1146          "Syncthing:"
   1147          ;; list generated for dashboard
   1148          item-list
   1149          list-size
   1150          'syncthing-status
   1151          ;; shortcut key for section
   1152          nil
   1153          ;; when clicked
   1154          (lambda (&rest _) ())
   1155          ;; show how list is shown in dashboard ("el" is automatically assigned)
   1156          (format "%s" el)))))
   1157 
   1158   (dashboard-setup-startup-hook)
   1159   (setq initial-buffer-choice (lambda () (get-buffer-create "*dashboard*")))
   1160 #+end_src
   1161 
   1162 ** Scrolling
   1163 #+begin_src emacs-lisp
   1164   (setopt scroll-step 1)
   1165   ;; Marker distance from center
   1166   (setopt scroll-conservatively 100000)
   1167   ;; Keep screen position on scroll
   1168   (setopt scroll-preserve-screen-position 1)
   1169   ;; Start scrolling when marker at top/bottom
   1170   (setopt scroll-margin 0)
   1171 
   1172   ;; Mouse scrolls 1 line at a time
   1173   (setopt mouse-wheel-scroll-amount '(1))
   1174 
   1175   ;; On a long mouse scroll keep scrolling by 1 line
   1176   (setq mouse-wheel-progressive-speed nil)
   1177 
   1178   ;; Enable pixel scroll precision mode
   1179   (unless (version< emacs-version "29")
   1180     (pixel-scroll-precision-mode))
   1181 
   1182   ;; Speed up cursor movement.
   1183   ;; https://emacs.stackexchange.com/questions/28736/emacs-pointcursor-movement-lag/28746
   1184   (setopt auto-window-vscroll nil)
   1185 #+end_src
   1186 * General packages
   1187 
   1188 ** which-key
   1189 Minor mode for Emacs that displays the key bindings following your currently entered incomplete command (a prefix) in a popup.
   1190 
   1191 #+BEGIN_SRC emacs-lisp
   1192   (use-package which-key
   1193     :delight
   1194     :config
   1195     (which-key-mode))
   1196 #+end_src
   1197 
   1198 ** counsel + ivy + swiper + prescient
   1199 Better incremental completion and selection narrowing.
   1200 And a bunch more.
   1201 Generally makes for nicer interactivity, like ido mode on steroids.
   1202 Switched to this from Helm, it's more lightweight.
   1203 
   1204 *** ivy: generic completion mechanism
   1205 #+begin_src emacs-lisp
   1206   (use-package ivy
   1207     :delight
   1208     :custom
   1209     (ivy-use-virtual-buffers t "extend searching to bookmarks")
   1210     (ivy-height 20 "set height of the ivy window")
   1211     (ivy-count-format "(%d/%d) " "count format, from the ivy help page")
   1212     (ivy-display-style 'fancy)
   1213     (ivy-format-function 'ivy-format-function-line)
   1214     (ivy-use-selectable-prompt t "to let me select exactly what I'm typing as a candidate")
   1215 
   1216     :bind (("C-x b" . ivy-switch-buffer)
   1217            ("C-c v" . ivy-push-view)
   1218            ("C-c V" . ivy-pop-view)
   1219 
   1220            ;; accidentally pressing shift-space deletes input, because
   1221            ;; by default, shift-space is bound to
   1222            ;; ~ivy-restrict-to-matches~ in the ivy minibuffer.
   1223            :map ivy-minibuffer-map
   1224            ("S-SPC" . (lambda () (interactive) (insert ?\s)))
   1225            ("<backtab>" . ivy-restrict-to-matches))
   1226     :config
   1227     (ivy-add-actions
   1228      'counsel-dired
   1229      '(("f" (lambda (dir) (counsel-fzf nil dir)) "Fzf in directory")
   1230        ("g" (lambda (dir) (counsel-ag nil dir)) "Ag in directory")))
   1231     (ivy-add-actions
   1232      'dired
   1233      '(("f" (lambda (dir) (ivy-exit-with-action (counsel-fzf nil dir))) "Fzf in directory")
   1234        ("g" (lambda (dir) (ivy-exit-with-action (counsel-ag nil dir))) "Ag in directory")))
   1235     (ivy-add-actions
   1236      'counsel-describe-function
   1237      '(("d" (lambda (fun) (ivy-exit-with-action (edebug-instrument-function (intern fun)))) "Edebug instrument function")))
   1238     (ivy-mode)
   1239 
   1240     (defun edit-script ()
   1241       "Edit a file in ~/.scripts/"
   1242       (interactive)
   1243       (let ((input (ivy--input)))
   1244         (ivy-quit-and-run (counsel-file-jump nil "~/.scripts/"))))
   1245 
   1246     (defun edit-config ()
   1247       "Edit a file in ~/.dotfiles/"
   1248       (interactive)
   1249       (let ((input (ivy--input)))
   1250         (ivy-quit-and-run (counsel-file-jump nil "~/.dotfiles/")))))
   1251 #+end_src
   1252 
   1253 *** counsel: collection of common Emacs commands enhanced using ivy
   1254 #+begin_src emacs-lisp
   1255   (use-package counsel
   1256     :demand
   1257     :delight
   1258     :config
   1259     (counsel-mode)
   1260     :bind (("M-x" . counsel-M-x)
   1261            ("C-x C-f" . counsel-find-file)
   1262            ("M-y" . counsel-yank-pop)
   1263            ("C-c c" . counsel-compile)
   1264            ("M-s g" . counsel-ag)
   1265            ("M-s f" . counsel-fzf)
   1266            ("C-c b" . counsel-bookmark)
   1267            ("C-c p" . counsel-recentf)
   1268            ("C-c o" . counsel-outline)
   1269            ("C-h f" . counsel-describe-function)
   1270            ("C-h v" . counsel-describe-variable)
   1271            ("C-h o" . counsel-describe-symbol)
   1272            ("C-c g j" . counsel-org-agenda-headlines)))
   1273 #+end_src
   1274 *** swiper: search enhanced using ivy
   1275 #+begin_src emacs-lisp
   1276   (use-package swiper
   1277     :bind (("C-s" . swiper-isearch)
   1278            ("C-r" . swiper-isearch-backward)))
   1279 #+end_src
   1280 *** prescient: scoring system for M-x
   1281 #+begin_src emacs-lisp
   1282   (use-package prescient
   1283     :config (prescient-persist-mode))
   1284 
   1285   (use-package ivy-prescient
   1286     :after counsel
   1287     :custom (ivy-prescient-retain-classic-highlighting t)
   1288     :config (ivy-prescient-mode))
   1289 #+end_src
   1290 
   1291 *** ivy-posframe: ivy in a popup
   1292 I like having ivy in a popup.
   1293 Problem: posframe does not work if emacs is too old and on macos.
   1294 See here: https://github.com/tumashu/posframe/issues/30
   1295 On Mac, ~brew install --HEAD emacs~ doesn't work either.
   1296 Solution: ~brew tap daviderestivo/emacs-head && brew install emacs-head@28 --with-cocoa~
   1297 
   1298 #+begin_src emacs-lisp
   1299   (if (and (version< emacs-version "28") (equal system-type 'darwin))
   1300       (message "ivy-posframe won't work properly, run `brew install daviderestivo/emacs-head/emacs-head@28 --with-cocoa`")
   1301     (use-package ivy-posframe
   1302       :delight
   1303       :custom
   1304       (ivy-posframe-display-functions-alist '((t . ivy-posframe-display-at-frame-center)))
   1305       (ivy-posframe-parameters
   1306        '((left-fringe . 8)
   1307          (right-fringe . 8)))
   1308       (ivy-posframe-border-width 3)
   1309       (ivy-truncate-lines nil) ;; otherwise the cursor gets hidden by long lines in posframe
   1310       :custom-face
   1311       (ivy-posframe-border ((t (:inherit mode-line-inactive))))
   1312       :config
   1313       (ivy-posframe-mode 1)))
   1314 #+end_src
   1315 
   1316 [[https://github.com/tumashu/ivy-posframe/issues/123][See here]] for cursor going offscreen in the posframe. Currently 'solved' with ~ivy-truncate-lines~ nil.
   1317 
   1318 ** DISABLED vertico + consult + marginalia + embark + posframe + prescient
   1319 Alternative to counsel/ivy/swiper, will probably switch to this at some point.
   1320 [[https://old.reddit.com/r/emacs/comments/qfrxgb/using_emacs_episode_80_vertico_marginalia_consult/hi6mfh7/][Here]] is a good comparison.
   1321 
   1322 A [[https://old.reddit.com/r/emacs/comments/11lqkbo/weekly_tips_tricks_c_thread/jbe06qv/][comment here to follow]] when I switch to vertico.
   1323 #+begin_src emacs-lisp :tangle no
   1324   (dolist (pack '(vertico consult marginalia embark vertico-posframe vertico-prescient))
   1325     (unless (package-installed-p pack)
   1326       (package-install pack))
   1327     (require pack))
   1328 
   1329   (vertico-mode 1)
   1330   (vertico-posframe-mode 1)
   1331   (marginalia-mode 1)
   1332   (vertico-prescient-mode 1)
   1333   (setq completion-styles '(basic substring partial-completion flex))
   1334 
   1335   (global-set-key (kbd "M-o") #'embark-act)
   1336   (global-set-key (kbd "C-s") #'consult-line)
   1337 
   1338 #+end_src
   1339 ** company: completion mechanism
   1340 #+begin_src emacs-lisp
   1341   (use-package company)
   1342 #+end_src
   1343 
   1344 ** wgrep: writable grep
   1345 #+begin_src emacs-lisp
   1346   (use-package wgrep)
   1347 #+end_src
   1348 ** avy: jump to any position
   1349 This lets me jump to any position in Emacs rather quickly, sometimes it's useful.
   1350 ~avy-goto-char-timer~ lets me type a part of the text before avy kicks in.
   1351 
   1352 #+begin_src emacs-lisp
   1353   (use-package avy
   1354     :custom
   1355     (avy-single-candidate-jump nil "Often I want to perform an action, never jump automatically")
   1356     :bind
   1357     (("C-:" . avy-goto-char-timer)))
   1358 #+end_src
   1359 
   1360 ** calendar
   1361 #+begin_src emacs-lisp
   1362   (use-package calendar
   1363     :ensure nil ; comes with Emacs
   1364     :custom
   1365     (calendar-week-start-day 1))
   1366 #+end_src
   1367 ** calfw: graphical calendar
   1368 Basically provides a way to show the org agenda as a standard GUI calendar app would.
   1369 
   1370 #+begin_src emacs-lisp
   1371   (use-package calfw
   1372     :config
   1373     (use-package calfw-org)
   1374     :custom
   1375     (cfw:org-overwrite-default-keybinding t))
   1376 #+end_src
   1377 
   1378 ** vanish: hide parts of the file
   1379 #+begin_src emacs-lisp
   1380   (use-package vanish
   1381     :init
   1382     (za/package-vc-install :repo "thezeroalpha/vanish.el" :rev "develop")
   1383     (require 'vanish)
   1384     :ensure nil
   1385     :bind (:map vanish-mode-map
   1386                 ("C-c q h h" . vanish-hide-dwim)
   1387                 ("C-c q h u r" . vanish-show-all-regions)
   1388                 ("C-c q h u e" . vanish-elt-unhide)
   1389                 ("C-c q h u u" . vanish-show-all)))
   1390 #+end_src
   1391 ** magit
   1392 #+begin_src emacs-lisp
   1393   (use-package magit)
   1394 #+end_src
   1395 ** vterm
   1396 Emacs has a bunch of built-in terminal emulators.
   1397 And they all suck.
   1398 (OK not really, eshell is alright, but not for interactive terminal programs like newsboat/neomutt)
   1399 
   1400 Also use emacsclient inside vterm as an editor, because that'll open documents in the existing Emacs session.
   1401 And I'm not gonna be a heretic and open Vim inside of Emacs.
   1402 
   1403 #+begin_src emacs-lisp
   1404   (use-package vterm
   1405     :hook
   1406     (vterm-mode . (lambda () (unless server-process (server-start))))
   1407     :bind (("C-c t" . switch-to-vterm))
   1408     :config
   1409     (defun switch-to-vterm ()
   1410       "Switch to a running vterm, or start one and switch to it."
   1411       (interactive)
   1412       (if (get-buffer vterm-buffer-name)
   1413           (switch-to-buffer vterm-buffer-name)
   1414         (vterm))))
   1415 #+end_src
   1416 ** sr-speedbar
   1417 Make speed bar show in the current frame.
   1418 
   1419 #+begin_src emacs-lisp
   1420   (use-package sr-speedbar
   1421     :bind (("C-c F" . za/jump-to-speedbar-or-open)
   1422            :map speedbar-mode-map
   1423            ("q" . sr-speedbar-close))
   1424     :custom
   1425     (sr-speedbar-right-side nil)
   1426 
   1427     :config
   1428     (defun za/jump-to-speedbar-or-open ()
   1429       "Open a speedbar or jump to it if already open."
   1430       (interactive)
   1431       (if (or (not (boundp 'sr-speedbar-exist-p))
   1432               (not (sr-speedbar-exist-p)))
   1433           (sr-speedbar-open))
   1434       (sr-speedbar-select-window)))
   1435 #+end_src
   1436 ** expand-region
   1437 Expand the selected region semantically.
   1438 
   1439 #+begin_src emacs-lisp
   1440   (use-package expand-region
   1441     :bind ("C-=" . er/expand-region))
   1442 #+end_src
   1443 ** flycheck
   1444 Install flycheck:
   1445 
   1446 #+begin_src emacs-lisp
   1447   (use-package flycheck)
   1448 #+end_src
   1449 ** rainbow-mode: visualise hex colors
   1450 'rainbow-mode' lets you visualise hex colors:
   1451 
   1452 #+begin_src emacs-lisp
   1453   (use-package rainbow-mode)
   1454 #+end_src
   1455 ** hl-todo: highlight TODO keywords
   1456 I want to highlight TODO keywords in comments:
   1457 
   1458 #+begin_src emacs-lisp
   1459   (use-package hl-todo
   1460     :custom-face
   1461     (hl-todo ((t (:inherit hl-todo :underline t))))
   1462     :custom
   1463     (hl-todo-keyword-faces '(("TODO"   . "#ff7060")
   1464                              ("FIXME"  . "#caa000")))
   1465     :config
   1466     (global-hl-todo-mode t))
   1467 #+end_src
   1468 ** undo-tree
   1469 Sometimes it's better to look at undo history as a tree:
   1470 
   1471 #+begin_src emacs-lisp
   1472   (use-package undo-tree
   1473     :delight
   1474     :custom
   1475     (undo-tree-history-directory-alist
   1476      (progn (let ((undo-tree-dir (concat user-emacs-directory "undo-tree/")))
   1477               (unless (file-directory-p undo-tree-dir) (make-directory undo-tree-dir))
   1478               `(("." . ,undo-tree-dir)))))
   1479 
   1480     :config
   1481     (global-undo-tree-mode))
   1482 #+end_src
   1483 
   1484 *** TODO undo tree dir should be configurable
   1485 ** eglot
   1486 A good LSP plugin.
   1487 
   1488 #+begin_src emacs-lisp
   1489   (use-package eglot)
   1490 #+end_src
   1491 ** crdt
   1492 Collaborative editing in Emacs:
   1493 
   1494 #+begin_src emacs-lisp
   1495   (use-package crdt)
   1496 #+end_src
   1497 ** git gutter
   1498 General git gutter:
   1499 
   1500 #+begin_src emacs-lisp
   1501   (use-package git-gutter
   1502     :bind (("C-c d n" . git-gutter:next-hunk)
   1503            ("C-c d p" . git-gutter:previous-hunk))
   1504     :config
   1505     (global-git-gutter-mode 1))
   1506 #+end_src
   1507 ** keycast
   1508 In case I want to show what keys I'm pressing.
   1509 
   1510 #+begin_src emacs-lisp
   1511   (use-package keycast)
   1512 #+end_src
   1513 ** ace-window: better window switching
   1514 Window switching with ~other-window~ sucks when I have more than 2 windows open. Too much cognitive load.
   1515 This lets me select a window to jump to using a single key, sort of like ~avy~.
   1516 
   1517 #+begin_src emacs-lisp
   1518   (use-package ace-window
   1519     :custom
   1520     (aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l) "I prefer using home-row keys instead of numbers")
   1521 
   1522     :custom-face
   1523     ;; I want something a little more contrasty
   1524     (aw-leading-char-face ((t (:inherit font-lock-keyword-face :height 2.0))))
   1525 
   1526     :bind ("M-o" . ace-window))
   1527 #+end_src
   1528 ** decide-mode for dice rolling
   1529 #+begin_src emacs-lisp
   1530   (use-package decide
   1531     :init (za/package-vc-install :repo "lifelike/decide-mode" :name "decide")
   1532     :ensure nil
   1533     :bind ("C-c q ?" . decide-mode))
   1534 #+end_src
   1535 
   1536 ** try: try out different packages
   1537 #+begin_src emacs-lisp
   1538   (use-package try)
   1539 #+end_src
   1540 ** dumb-jump
   1541 "jump to definition" package, minimal configuration with no stored indexes.
   1542 Uses The Silver Searcher ag, ripgrep rg, or grep to find potential definitions of a function or variable under point.
   1543 
   1544 #+begin_src emacs-lisp
   1545   (use-package dumb-jump)
   1546 #+end_src
   1547 
   1548 Enable xref backend:
   1549 
   1550 #+begin_src emacs-lisp
   1551   (add-hook 'xref-backend-functions #'dumb-jump-xref-activate)
   1552   (setq xref-show-definitions-function #'xref-show-definitions-completing-read)
   1553 #+end_src
   1554 ** DISABLED command-log-mode
   1555 Simple real-time logger of commands.
   1556 
   1557 #+begin_src emacs-lisp :tangle no
   1558   (use-package command-log-mode)
   1559 #+end_src
   1560 ** package-lint
   1561 Linter for the metadata in Emacs Lisp files which are intended to be packages.
   1562 
   1563 #+begin_src emacs-lisp
   1564   (use-package package-lint)
   1565   (use-package flycheck-package)
   1566   (eval-after-load 'flycheck
   1567     '(flycheck-package-setup))
   1568 #+end_src
   1569 ** prism: change color of text depending on depth
   1570 Prism changes the color of text depending on their depth. Makes it easier to see where something is at a glance.
   1571 
   1572 #+begin_src emacs-lisp
   1573   (use-package prism)
   1574 #+end_src
   1575 ** olivetti: distraction-free writing
   1576 #+begin_src emacs-lisp
   1577   (use-package olivetti
   1578     :diminish)
   1579 #+end_src
   1580 ** nov.el: EPUB support
   1581 #+begin_src emacs-lisp
   1582   (use-package nov)
   1583   (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))
   1584 #+end_src
   1585 ** god-mode: reduce the need to hold down modifier keys
   1586 - All commands are assumed to use the control modifier (C-) unless otherwise indicated.
   1587 - g is used to indicate the meta modifier
   1588 - G is used to indicate both the control and meta modifiers
   1589 #+begin_src emacs-lisp
   1590   (use-package god-mode
   1591     :bind
   1592     (("s-<escape>" . god-mode-all)
   1593      :map god-local-mode-map
   1594      ("z" . repeat)
   1595      ("i" . god-local-mode))
   1596     :hook    (post-command . za/god-mode-update-mode-line)
   1597     :config
   1598     (defun za/god-mode-update-mode-line ()
   1599       "Update the color of the modeline depending on god-mode."
   1600       (cond (god-local-mode
   1601              (set-face-attribute 'mode-line nil :background "#770085"))
   1602             (t
   1603              (let* ((current-theme (car custom-enabled-themes))
   1604                      (theme-settings (get current-theme 'theme-settings)))
   1605                 (dolist (theme-setting theme-settings)
   1606                   (if (and (eq (car theme-setting) 'theme-face)
   1607                            (eq (cadr theme-setting) 'mode-line))
   1608                       (let* ((face-def (caar (last theme-setting)))
   1609                              (properties (car (last face-def)))
   1610                              (bg (plist-get properties :background)))
   1611                         (set-face-attribute 'mode-line nil :background bg)))))))))
   1612 #+end_src
   1613 ** devil: alternative to god-mode that uses a comma
   1614 #+begin_src emacs-lisp
   1615   (use-package devil
   1616     :init
   1617     (za/package-vc-install :repo "susam/devil")
   1618     (require 'devil)
   1619     :custom
   1620     (devil-lighter " \u272A")
   1621     (devil-prompt "\u272A %t")
   1622     :config (global-devil-mode)
   1623     :bind ("C-," . global-devil-mode))
   1624 #+end_src
   1625 ** academic-phrases
   1626 Gives ideas for phrases to use in academic writing.
   1627 #+begin_src emacs-lisp
   1628   (use-package academic-phrases)
   1629 #+end_src
   1630 ** ediff
   1631 #+begin_src emacs-lisp
   1632   (use-package ediff
   1633     :custom
   1634     ((ediff-keep-variants nil "Prompt to remove unmodifid buffers after session")
   1635      (ediff-make-buffers-readonly-at-startup nil "Don't make all buffers read-only at startup")
   1636      (ediff-show-clashes-only t "Only show diff regions where both buffers disagree with ancestor")
   1637      (ediff-split-window-function 'split-window-horizontally "I want long vertical side-by-side windows")
   1638      (ediff-window-setup-function 'ediff-setup-windows-plain "Everything in one frame please")))
   1639 #+end_src
   1640 ** highlight-indent-guides
   1641 #+begin_src emacs-lisp
   1642   (use-package highlight-indent-guides
   1643     :hook (yaml-mode . highlight-indent-guides-mode)
   1644     :custom
   1645     ((highlight-indent-guides-method 'character))
   1646     :custom-face
   1647     (highlight-indent-guides-character-face ((t (:foreground "#adadad")))))
   1648 #+end_src
   1649 ** cc-avy
   1650 #+begin_src emacs-lisp
   1651   (use-package cc-avy
   1652     :ensure nil ; local
   1653     :bind ("C-M-:" . cc/avy-menu))
   1654 #+end_src
   1655 ** annotate
   1656 #+begin_src emacs-lisp
   1657   (use-package annotate
   1658     :custom (annotate-annotation-position-policy :margin)
   1659     :config
   1660     (defun za/annotate-initialize-extra-hooks ()
   1661       (add-hook 'after-save-hook #'annotate-save-annotations t t))
   1662     (defun za/annotate-shutdown-extra-hooks ()
   1663       (remove-hook 'after-save-hook #'annotate-save-annotations t))
   1664     (advice-add 'annotate-initialize :after #'za/annotate-initialize-extra-hooks)
   1665     (advice-add 'annotate-shutdown :after #'za/annotate-shutdown-extra-hooks))
   1666 
   1667 #+end_src
   1668 ** yasnippet
   1669 #+begin_src emacs-lisp
   1670   (use-package yasnippet
   1671     :config (yas-global-mode)
   1672     :delight)
   1673 #+end_src
   1674 * Mode/language specific packages
   1675 ** Org
   1676 *** Custom functions
   1677 **** Get number of headlines in a file
   1678 #+begin_src emacs-lisp
   1679   (defun za/org-count-headlines-in-file (level filename)
   1680     "Count number of level LEVEL headlines in FILENAME. If LEVEL is 0, count all."
   1681     (let ((headline-str (cond ((zerop level) "^\*+")
   1682                               (t (format "^%s " (apply 'concat (make-list level "\\*")))))))
   1683       (save-mark-and-excursion
   1684         (with-temp-buffer
   1685           (insert-file-contents filename)
   1686           (count-matches headline-str (point-min) (point-max))))))
   1687 #+end_src
   1688 
   1689 **** Yank URL
   1690 #+begin_src emacs-lisp
   1691   (defun org-yank-link-url ()
   1692     (interactive)
   1693     (kill-new (org-element-property :raw-link (org-element-context)))
   1694     (message "Link copied to clipboard"))
   1695 #+end_src
   1696 *** Installation
   1697 Install Org and require additional components that I use.
   1698 
   1699 #+begin_src emacs-lisp
   1700   (use-package org
   1701     :custom
   1702     (org-outline-path-complete-in-steps nil "Complete path all at once (needed for completion frameworks")
   1703     (org-format-latex-options (plist-put org-format-latex-options :scale 2.0) "Larger latex previews")
   1704     (org-goto-interface 'outline-path-completion "Use outline path completion for org-goto, instead of its weird interface")
   1705     (org-insert-heading-respect-content t "Insert headings after current subtree")
   1706     (org-id-link-to-org-use-id 'create-if-interactive "If org-store-link is called directly, create an ID.")
   1707     (org-clock-mode-line-total 'today)
   1708     (org-return-follows-link t "Easier link following. Actual enter is still possible with ~C-q C-j~.")
   1709     (org-hide-emphasis-markers t "Don't show italics/bold markers")
   1710     (org-babel-python-command "python3")
   1711     (org-confirm-babel-evaluate nil)
   1712     (org-file-apps '((auto-mode . emacs)
   1713                      (directory . emacs)
   1714                      ("\\.mm\\'" . default)
   1715                      ("\\.x?html?\\'" . default)
   1716                      ("\\.pdf\\'" . emacs)))
   1717     (org-link-elisp-confirm-function #'y-or-n-p)
   1718     (org-link-elisp-skip-confirm-regexp "^org-noter$")
   1719     (org-clock-sound (concat user-emacs-directory "notification.wav"))
   1720     (org-export-backends '(ascii html icalendar latex md odt org pandoc confluence-ext jira))
   1721     (org-catch-invisible-edits 'show-and-error
   1722                                "Sometimes when text is folded away, I might accidentally edit text inside of it. This option prevents that. I wanted to do 'smart', but that has a 'fixme' so it might change in the future...Instead, show what's being edited, but don't perform the edit.")
   1723     (org-src-tab-acts-natively t "a tab in a code block indents the code as it should")
   1724     (org-attach-store-link-p 'attached)
   1725     (org-attach-archive-delete 'query)
   1726     (org-stuck-projects '("/PROJ"
   1727                           ("NEXT" "STARTED")
   1728                           nil nil)
   1729                         "List projects that are stuck (don't have a next action)")
   1730     (org-tag-alist (let ((za/org-tag-energy-levels
   1731                           '((:startgroup)
   1732                             ("sport" . ?h) ; Sport (deep focus, long tasks, no interruptions, at least an hour)
   1733                             ("cruise" . ?l) ; Cruise (shallow focus, can be interrupted, can batch lots of quick tasks together)
   1734                             ("parked" . ?e) ; Parked (take a break, look into the distance, walk the dog, stretch, etc.)
   1735                             ("errand" . ?o) ; Errand (anything that involves me being out of the house)
   1736                             (:endgroup)))
   1737                          (za/org-tag-1-3-5
   1738                           '(; 1-3-5 tagging
   1739                             (:startgroup)
   1740                             ("_1" . ?1) ; 1 big task, 3-4 hrs
   1741                             ("_3" . ?3) ; 3 medium tasks, 1-2 hrs
   1742                             ("_5" . ?5) ; 5 small tasks, 30min-1hr
   1743                             (:endgroup))))
   1744                      `(,@za/org-tag-contexts ,@za/org-tag-energy-levels ,@za/org-tag-1-3-5)))
   1745 
   1746     :bind (("C-c a" . org-agenda)
   1747            ("C-c n" . org-capture)
   1748            ("C-c l" . org-store-link)
   1749            :map org-mode-map
   1750            ("C-M-<return>" . org-insert-todo-heading)
   1751            ("C-c M-y" . org-yank-link-url)
   1752            ("C-c N" . org-noter)
   1753            ("C-M-i" . completion-at-point)
   1754            ("C-c SPC" . org-table-blank-field)
   1755            ("C-c C-w" . za/org-refile-wrapper))
   1756     :hook ((org-mode . abbrev-mode)
   1757            (org-mode . za/echo-area-tooltips)
   1758            (org-mode . org-superstar-mode)
   1759            (org-mode . org-indent-mode)
   1760            (org-mode . za/settings-on-org-mode)
   1761            (org-mode . org-pretty-table-mode)
   1762            (org-mode . variable-pitch-mode))
   1763     :config
   1764     (za/package-vc-install :repo "Fuco1/org-pretty-table")
   1765     (require 'org-pretty-table)
   1766     (delight 'org-pretty-table nil)
   1767 
   1768 
   1769     (za/package-vc-install :repo "https://git.sr.ht/~bzg/org-contrib" :load "lisp/")
   1770     (require 'org-contrib)
   1771     (require 'org-checklist)
   1772     (delight 'org-indent-mode nil 'org-indent)
   1773     (defun za/settings-on-org-mode ()
   1774       "Settings on enabling org mode"
   1775       (za/toggle-wrap t))
   1776 
   1777     (defcustom za/org-inline-images-desired-screen-proportion (/ (float 3) 4)
   1778       "Percentage of the window (as a float) that Org inline images should take up."
   1779       :type 'float)
   1780 
   1781     (defun za/org-display-inline-images-set-width (&rest _)
   1782       "Set `org-image-actual-width` dynamically before displaying images."
   1783       (if (window-system)
   1784           (let* ((total-width (window-pixel-width))
   1785                  (image-width (round (* total-width za/org-inline-images-desired-screen-proportion))))
   1786             (setq-local org-image-actual-width image-width))))
   1787 
   1788     (advice-add 'org-display-inline-images :before #'za/org-display-inline-images-set-width)
   1789 
   1790     (defun za/org-attach-tag (old/org-attach-tag &rest args)
   1791       "Wraps :around org-attach-tag (as OLD/ORG-ATTACH-TAG) with ARGS.
   1792   When inside capture for org-roam, attaching fails at
   1793   org-attach-tag. This function prevents that error interrupting
   1794   org-attach."
   1795       (if ; there's no heading
   1796           (not (org-element-lineage (org-element-at-point)
   1797                                     '(headline inlinetask)
   1798                                     'include-self))
   1799           nil ; there's no point attaching a tag
   1800                                           ; otherwise, normal attach
   1801         (apply old/org-attach-tag args)))
   1802 
   1803     (advice-add #'org-attach-tag :around #'za/org-attach-tag)
   1804     (defun za/org-clear-1-3-5 ()
   1805       "Clears the _1/_3/_5 daily tags from all antries."
   1806       (interactive)
   1807       (let ((number-of-entries
   1808              (length (org-map-entries
   1809                       (lambda ()
   1810                         (let* ((tags-1-3-5 '("_1" "_3" "_5"))
   1811                                (tags-without-1-3-5 (seq-remove (lambda (e) (member e tags-1-3-5))
   1812                                                                org-scanner-tags)))
   1813                           (org-set-tags tags-without-1-3-5)))
   1814                       "_1|_3|_5"
   1815                       'agenda-with-archives))))
   1816         (message "Modified %d entries." number-of-entries)))
   1817     (defun za/archive-finished-tickler ()
   1818       (interactive)
   1819       (progn
   1820         (find-file-other-window za/org-life-tickler)
   1821         (length (org-map-entries
   1822                  (lambda ()
   1823                    (org-fold-show-subtree)
   1824                    (when (y-or-n-p "Archive? ")
   1825                      (org-archive-subtree-default)
   1826                      (save-buffer)))
   1827                  "TIMESTAMP<*\"<today>\""
   1828                  'file))))
   1829 
   1830     (require 'org-tempo)
   1831     (require 'org-habit)
   1832     (require 'org-id)
   1833     (use-package ob-async)
   1834     (use-package ob-rust)
   1835     (org-babel-do-load-languages
   1836      'org-babel-load-languages
   1837      '((emacs-lisp . t)
   1838        (R . t)
   1839        (python . t)
   1840        (ruby . t)
   1841        (shell . t)
   1842        (sqlite . t)
   1843        (rust . t)))
   1844     (use-package inf-ruby)
   1845     (use-package org-superstar
   1846       :custom
   1847       (org-superstar-leading-bullet ?\s))
   1848 
   1849     ;; Linking to emails via notmuch
   1850     (use-package ol-notmuch)
   1851 
   1852     ;; Improved search
   1853     (use-package org-ql)
   1854 
   1855     ;; My override for org clock resolve
   1856     (require 'org-clock-override)
   1857 
   1858     ;; Tempo expansions
   1859     (add-to-list 'org-structure-template-alist '("se" . "src emacs-lisp"))
   1860     (add-to-list 'org-structure-template-alist '("sb" . "src bibtex"))
   1861     (add-to-list 'org-structure-template-alist '("ss" . "src sh"))
   1862     (add-to-list 'org-structure-template-alist '("sy" . "src yaml")))
   1863 #+end_src
   1864 *** Agenda & GTD
   1865 **** Agenda mode settings
   1866 #+begin_src emacs-lisp
   1867   (use-package org-agenda
   1868     :ensure org
   1869     :bind (:map org-agenda-mode-map
   1870                 ("C-c TAB" . za/org-agenda-goto-narrowed-subtree)
   1871                 ("@" . za/org-agenda-show-context-tags))
   1872     :custom
   1873     (org-agenda-files (list za/org-life-main
   1874                             za/org-life-inbox
   1875                             za/org-life-tickler))
   1876     (org-agenda-text-search-extra-files
   1877      (directory-files za/org-life-dir t (rx bol (not ?.) (* anything) ".org"))
   1878      "I want to search all Org files in the life directory")
   1879 
   1880     :config
   1881     (defun za/org-agenda-show-context-tags ()
   1882       "Show the context tags (e.g. @computer) applicable to the current item."
   1883       (interactive)
   1884       (let* ((tags (org-get-at-bol 'tags))
   1885              (context-tag-p (lambda (tag) (string-prefix-p "@" tag)))
   1886              (context-tags (seq-filter context-tag-p tags)))
   1887         (if context-tags
   1888             (message "Contexts are :%s:"
   1889                      (org-no-properties (mapconcat #'identity context-tags ":")))
   1890           (message "No contexts associated with this line"))))
   1891     (defun za/org-agenda-goto-narrowed-subtree ()
   1892       "Jump to current agenda item and narrow to its subtree."
   1893       (interactive)
   1894       (delete-other-windows)
   1895       (org-agenda-goto)
   1896       (org-narrow-to-subtree)
   1897       (outline-hide-subtree)
   1898       (org-show-children 1)
   1899       (other-window 1)))
   1900 #+end_src
   1901 
   1902 Fix tag display by dynamically calculating the column.
   1903 
   1904 #+begin_src emacs-lisp
   1905   (defun za/settings-org-agenda-mode ()
   1906     "My settings for org agenda mode"
   1907     )
   1908   (add-hook 'org-agenda-mode-hook #'za/settings-org-agenda-mode)
   1909 #+end_src
   1910 
   1911 **** Opening files
   1912 Convenience functions to make opening the main file faster:
   1913 
   1914 #+begin_src emacs-lisp
   1915   (defun gtd () "GTD: main file" (interactive) (find-file za/org-life-main))
   1916   (defun gtd-inbox ()
   1917     "GTD: inbox"
   1918     (interactive)
   1919     (let ((count-docs (length (directory-files za/org-life-doc-inbox nil (rx bos (not ?.))))))
   1920       (find-file za/org-life-inbox)
   1921       (when (> count-docs 0)
   1922         (dired-other-window za/org-life-doc-inbox)
   1923         (dired-revert)
   1924         (other-window 1))))
   1925   (defun gtd-inbox-mobile () "GTD: mobile inbox" (interactive) (find-file za/org-life-inbox-mobile))
   1926   (defun gtd-archive () "GTD: archive" (interactive) (find-file za/org-life-archive))
   1927   (defun gtd-someday () "GTD: someday" (interactive) (find-file za/org-life-someday))
   1928   (defun gtd-tickler () "GTD: tickler" (interactive) (find-file za/org-life-tickler))
   1929 #+end_src
   1930 
   1931 Bind keys to those functions:
   1932 
   1933 #+begin_src emacs-lisp
   1934   (bind-keys :prefix "M-g t"
   1935              :prefix-map za/gtd-files-map
   1936              :prefix-docstring "Visit GTD file"
   1937              ("i" . gtd-inbox)
   1938              ("l" . gtd)
   1939              ("a" . gtd-archive)
   1940              ("s" . gtd-someday)
   1941              ("t" . gtd-tickler))
   1942 #+end_src
   1943 
   1944 To improve jumping to any headline via counsel, filter returned candidates to include source file.
   1945 
   1946 #+begin_src emacs-lisp
   1947   (defun za/counsel-org-agenda-headlines--candidates-with-filename (candidates)
   1948     "Convert CANDIDATES to include source filename for each candidate."
   1949     (mapcar (lambda (candidate)
   1950               (let ((name (nth 0 candidate))
   1951                     (path (nth 1 candidate))
   1952                     (pos (nth 2 candidate)))
   1953                 (list (format "%s/%s" (file-name-nondirectory path) name)
   1954                       path
   1955                       pos)))
   1956             candidates))
   1957 
   1958   (advice-add #'counsel-org-agenda-headlines--candidates :filter-return #'za/counsel-org-agenda-headlines--candidates-with-filename)
   1959 #+end_src
   1960 
   1961 *** Processing inbox
   1962 I made a function for processing the inbox, focusing on one item at a time:
   1963 
   1964 #+begin_src emacs-lisp
   1965   (defun za/gtd-inbox-next-item ()
   1966     (interactive)
   1967     (unless (string= (buffer-file-name) (file-truename za/org-life-inbox))
   1968       (user-error "You're not in your GTD inbox file."))
   1969     (widen)
   1970     (org-first-headline-recenter)
   1971     (org-narrow-to-subtree))
   1972 #+end_src
   1973 
   1974 And a conditional binding:
   1975 
   1976 #+begin_src emacs-lisp
   1977   (bind-key "C-c g n" #'za/gtd-inbox-next-item 'org-mode-map (string= (buffer-file-name) (file-truename za/org-life-inbox)))
   1978 #+end_src
   1979 
   1980 And a function for importing other inboxes:
   1981 
   1982 #+begin_src emacs-lisp
   1983   (defun za/gtd-inbox-import ()
   1984     (interactive)
   1985     (unless (string= (buffer-file-name) (file-truename za/org-life-inbox))
   1986       (user-error "You're not in your GTD inbox file"))
   1987     (when (directory-files za/org-life-dir nil "\\.sync-conflict-")
   1988         (user-error "Sync conflicts found, please fix them"))
   1989     (let ((mobile (if (boundp 'za/org-life-inbox-mobile) (file-truename za/org-life-inbox-mobile) nil))
   1990           (calendar (if (boundp 'za/org-life-calendar-inbox) (file-truename za/org-life-calendar-inbox) nil)))
   1991       (save-mark-and-excursion
   1992         (goto-char (point-max))
   1993         (when mobile
   1994           (insert-file mobile)
   1995           (goto-char (point-max))
   1996           (write-region "" nil mobile))
   1997         (when calendar
   1998           (insert-file calendar)
   1999           (write-region "" nil calendar)
   2000           (goto-char (point-max)))
   2001         (message "Imported other inboxes."))))
   2002 #+end_src
   2003 
   2004 Also with a conditional binding:
   2005 
   2006 #+begin_src emacs-lisp
   2007   (bind-key "C-c g i" #'za/gtd-inbox-import 'org-mode-map (string= (buffer-file-name) (file-truename za/org-life-inbox)))
   2008 #+end_src
   2009 *** Refiling & archiving
   2010 #+begin_src emacs-lisp
   2011   (use-package org-refile
   2012     :ensure org
   2013     :custom
   2014     (org-refile-targets `((,za/org-life-main :maxlevel . 3)
   2015                           (,za/org-life-someday :level . 1)
   2016                           (,za/org-life-tickler :maxlevel . 3))
   2017                         "Where I want to be able to move subtrees (doesn't include inbox because I never refile to that, and the archive has its own keybining)")
   2018     (org-archive-location (concat za/org-life-archive "::datetree/")
   2019                           "I want to archive to a specific file, in a date tree")
   2020     (org-refile-use-outline-path 'file
   2021                                  "Include the destination file as an element in the path to a heading, and to use the full paths as completion targets rather than just the heading text itself")
   2022     (org-outline-path-complete-in-steps nil
   2023                                         "Tell Org that I don’t want to complete in steps; I want Org to generate all of the possible completions and present them at once (necessary for Helm/Ivy)")
   2024     (org-refile-allow-creating-parent-nodes 'confirm
   2025                                             "Allow me to tack new heading names onto the end of my outline path, and if I am asking to create new ones, make me confirm it"))
   2026 #+end_src
   2027 
   2028 *** Quick capture
   2029 Quick capture lets me send something to my inbox very quickly, without thinking about where it should go.
   2030 The inbox is processed later.
   2031 
   2032 Templates for quick capture:
   2033 
   2034 #+begin_src emacs-lisp
   2035   (use-package org-capture
   2036     :ensure org
   2037     :custom
   2038     (org-capture-templates `(("t" "Todo [inbox]" entry
   2039                               (file ,za/org-life-inbox)
   2040                               "* TODO %i%?")
   2041 
   2042                              ("s" "Save for read/watch/listen" entry
   2043                               (file+headline ,za/org-life-someday "Read/watch/listen")
   2044                               "* %?[[%^{link}][%^{description}]] %^g"))))
   2045 #+end_src
   2046 
   2047 *** Todo & custom agenda views
   2048 Todo keywords based on the GTD system (pipe separates incomplete from complete).
   2049 Apart from the logging-on-done configured [[*Logging][below]], I also want to log a note & timestamp when I start waiting on something.
   2050 In ~org-todo-keywords~, ~@~ means note+timestamp, ~!~ means timestamp, ~@/!~ means note+timestamp on state entry and timestamp on leave.
   2051 
   2052 #+begin_src emacs-lisp
   2053   (custom-set-variables '(org-todo-keywords '((sequence "TODO(t)" "NEXT(n)" "STARTED(s)" "WAITING(w@)" "PROJ(p)" "|" "DONE(d)" "CANCELLED(c)")))
   2054                         '(org-todo-keyword-faces '(("TODO" . org-todo)
   2055                                                    ("NEXT" . org-todo)
   2056                                                    ("WAITING" . org-todo)
   2057                                                    ("STARTED" . org-todo)
   2058                                                    ("PROJ" . org-todo)
   2059                                                    ("DONE" . org-done)
   2060                                                    ("CANCELLED" . org-done))))
   2061 #+end_src
   2062 
   2063 
   2064 Something is a habit if: it has a HABIT tag, STYLE is habit, LOGGING is logrepeat, it has a scheduled repeater from today.
   2065 
   2066 #+begin_src emacs-lisp
   2067   (defun za/mark-as-habit ()
   2068     "This function makes sure that the current heading has:
   2069   (1) a HABIT tag
   2070   (2) todo set to TODO
   2071   (3) LOGGING property set to logrepeat
   2072   (4) a scheduled repeater from today"
   2073     (interactive)
   2074     (org-back-to-heading t)
   2075     (org-set-property "TODO" "TODO")
   2076     (org-set-property "LOGGING" "logrepeat")
   2077     (org-set-property "STYLE" "habit")
   2078     (org-toggle-tag "HABIT" 'on)
   2079     (org-schedule nil))
   2080 #+end_src
   2081 
   2082 +I decided that projects will not be TODO items, but their progress will be tracked with a progress cookie ([x/y]). This function converts an item to a project: it adds a PROJECT tag, sets the progress indicator to count all checkboxes in sub-items (only TODO items), and removes any existing TODO keywords. Finally, PROJECT tags shouldn't be inherited (i.e. subtasks shouldn't be marked as projects).+
   2083 In the end, I want NEXT items that are part of a project to be shown as such (so inherit that PROJECT tag), but projects themselves will have a PROJ todo keyword.
   2084 This function converts an item to a project.
   2085 
   2086 #+begin_src emacs-lisp
   2087   (defun za/mark-as-project ()
   2088     "This function makes sure that the current heading has
   2089       (1) the tag PROJECT
   2090       (2) the todo keyword PROJ
   2091       (3) the property COOKIE_DATA set to \"todo recursive\"
   2092       (4) a progress indicator"
   2093     (interactive)
   2094     (org-back-to-heading t)
   2095     ;; Step 1: clear out everything
   2096     (org-set-property "TODO" "")
   2097 
   2098     ;; org-set-property errors via org-priority if you try to clear
   2099     ;; priority of an item that doesn't have priority. Stupid design,
   2100     ;; but I can't change that so we gotta jump through hoops:
   2101     (let ((have-priority (org-element-property :priority (org-element-at-point))))
   2102       (when have-priority
   2103         (org-set-property "PRIORITY" "")))
   2104 
   2105     ;; Step 2: set info (stats cookie, todo, tag, properties drawer)
   2106     (forward-whitespace 1)
   2107     (insert "[/] ")
   2108     (org-set-property "TODO" "PROJ")
   2109     (org-toggle-tag "PROJECT" 'on)
   2110     (org-set-property "COOKIE_DATA" "todo recursive")
   2111     (org-update-statistics-cookies nil))
   2112 #+end_src
   2113 
   2114 And a keybinding for it:
   2115 
   2116 #+begin_src emacs-lisp
   2117   (bind-key "C-c g p" #'za/mark-as-project 'org-mode-map)
   2118 #+end_src
   2119 
   2120 Want all tags to be inherited:
   2121 
   2122 #+begin_src emacs-lisp
   2123   (custom-set-variables '(org-tags-exclude-from-inheritance nil))
   2124 #+end_src
   2125 
   2126 Define a function to skip items if they're part of a project (i.e. one of their parents has a "PROJECT" tag).
   2127 +The problem is, the "PROJECT" tag isn't inherited. So, we temporarily disable excluding from inheritance, just for the ~org-get-tags~ call. Then check if "PROJECT" is one of the tags.+ That tag is now inherited.
   2128 
   2129 #+begin_src emacs-lisp
   2130   (defun za/skip-if-in-project ()
   2131     "Skip items that are part of a project but not a project themselves."
   2132     (let ((skip (save-excursion (org-end-of-subtree t)))
   2133           (keep nil)
   2134           (item-tags (let ((org-use-tag-inheritance t)) (org-get-tags)))
   2135           (item-tags-without-inherited (let ((org-use-tag-inheritance nil)) (org-get-tags))))
   2136       (if (and (member "PROJECT" item-tags)
   2137                (not (member "PROJECT" item-tags-without-inherited)))
   2138           skip
   2139         keep)))
   2140 #+end_src
   2141 
   2142 Also, define a function to skip tasks (trees) that are not habits (i.e. don't have the STYLE property ~habit~):
   2143 
   2144 #+begin_src emacs-lisp
   2145   (defun za/skip-unless-habit ()
   2146     "Skip trees that are not habits"
   2147     (let ((skip (save-excursion (org-end-of-subtree t)))
   2148           (keep nil))
   2149       (if (string= (org-entry-get nil "STYLE") "habit")
   2150           keep
   2151         skip)))
   2152 #+end_src
   2153 
   2154 And one to skip tasks that /are/ habits:
   2155 
   2156 #+begin_src emacs-lisp
   2157   (defun za/skip-if-habit ()
   2158     "Skip trees that are not habits"
   2159     (let ((skip (save-excursion (org-end-of-subtree t)))
   2160           (keep nil))
   2161       (if (string= (org-entry-get nil "STYLE") "habit")
   2162           skip
   2163         keep)))
   2164 #+end_src
   2165 
   2166 Skip ones with a habit tag:
   2167 
   2168 #+begin_src emacs-lisp
   2169   (defun za/skip-if-has-habit-tag ()
   2170     (let ((skip (save-excursion (org-end-of-subtree t)))
   2171           (keep nil)
   2172           (item-tags-without-inherited (let ((org-use-tag-inheritance nil)) (org-get-tags))))
   2173       (if (or (member "HABIT" item-tags-without-inherited)
   2174               (member "flatastic" item-tags-without-inherited))
   2175           skip
   2176         keep)))
   2177 #+end_src
   2178 
   2179 And another function, to skip tasks that are blocked:
   2180 
   2181 #+begin_src emacs-lisp
   2182   (defun za/skip-if-blocked ()
   2183     "Skip trees that are blocked by previous tasks"
   2184     (let ((skip (save-excursion (org-end-of-subtree t)))
   2185           (keep nil))
   2186       (if (org-entry-blocked-p)
   2187           skip
   2188         keep)))
   2189 #+end_src
   2190 
   2191 For listing tasks without a context - skip if it has a context tag:
   2192 
   2193 #+begin_src emacs-lisp
   2194   (defun za/skip-if-has-context ()
   2195     (let ((skip (save-excursion (org-end-of-subtree t)))
   2196           (keep nil)
   2197           (item-tags-without-inherited (let ((org-use-tag-inheritance nil)) (org-get-tags)))
   2198           (context-tag-p (lambda (s) (eq (aref s 0) ?@))))
   2199       (if (cl-some context-tag-p item-tags-without-inherited)
   2200           skip
   2201         keep)))
   2202 #+end_src
   2203 
   2204 For listing tasks without an energy level - skip if it has an energy level:
   2205 
   2206 #+begin_src emacs-lisp
   2207   (defun za/skip-if-has-energy-level ()
   2208     (let ((skip (save-excursion (org-end-of-subtree t)))
   2209           (keep nil)
   2210           (item-tags-without-inherited (let ((org-use-tag-inheritance nil)) (org-get-tags)))
   2211           (energy-tag-p (lambda (s) (member s '("sport" "cruise" "parked" "errand")))))
   2212       (if (cl-some energy-tag-p item-tags-without-inherited)
   2213           skip
   2214         keep)))
   2215 #+end_src
   2216 
   2217 #+begin_src emacs-lisp
   2218   (defun za/skip-if-scheduled-in-future ()
   2219     (let* ((skip (save-excursion (org-end-of-subtree t)))
   2220            (keep nil)
   2221            (scheduled-time (org-get-scheduled-time (point))))
   2222       (if (and scheduled-time (time-less-p (current-time) scheduled-time))
   2223           skip
   2224         keep)))
   2225 #+end_src
   2226 
   2227 #+begin_src emacs-lisp
   2228   (defun za/skip-if-scheduled ()
   2229     (let* ((skip (save-excursion (org-end-of-subtree t)))
   2230            (keep nil)
   2231            (scheduled-time (org-get-scheduled-time (point))))
   2232       (if scheduled-time
   2233           skip
   2234         keep)))
   2235 #+end_src
   2236 
   2237 Create custom agenda view based on those keywords.
   2238 Agenda views are made up of blocks, appearing in the order that you declare them.
   2239 The first two strings are what shows up in the agenda dispatcher (the key to press and the description).
   2240 
   2241 #+begin_src emacs-lisp
   2242   (setq org-agenda-custom-commands
   2243         '(("n" "Next actions"
   2244            todo "NEXT" ((org-agenda-overriding-header "Next actions:")
   2245                         (org-agenda-sorting-strategy '(priority-down alpha-up))
   2246                         (org-agenda-skip-function #'za/skip-if-scheduled)))
   2247           ("q" "Query" (lambda (&rest _) (call-interactively #'org-ql-search)))
   2248 
   2249           ("W" "Waiting"
   2250            ((todo "WAITING" ((org-agenda-overriding-header "Waiting:")))))
   2251           ("S" . "Saved for later...")
   2252           ("Sw" "Saved to watch"
   2253            ((tags-todo "WATCH" ((org-agenda-overriding-header "To watch:")
   2254                                 (org-agenda-files `(,za/org-life-someday ,@org-agenda-files))))))
   2255 
   2256           ("Sr" "Saved to read"
   2257            ((tags-todo "READ" ((org-agenda-overriding-header "To read:")
   2258                                (org-agenda-files `(,za/org-life-someday ,@org-agenda-files))))))
   2259           ("Sl" "Saved to listen"
   2260            ((tags-todo "LISTEN" ((org-agenda-overriding-header "To listen:")
   2261                                  (org-agenda-files `(,za/org-life-someday ,@org-agenda-files))))))
   2262 
   2263           ("a" . "Agenda with schedule only...")
   2264           ("aw" "This week"
   2265            ((agenda "" ((org-agenda-span 'week)))))
   2266           ("aD" "Today"
   2267            ((agenda "" ((org-agenda-span 'day)))))
   2268           ("ad" "Today (no habits)"
   2269            ((agenda "" ((org-agenda-span 'day)
   2270                         (org-agenda-skip-function 'za/skip-if-has-habit-tag)))))
   2271           ("at" "Tomorrow (no habits)"
   2272            ((agenda "" ((org-agenda-span 'day)
   2273                         (org-agenda-start-day "+1d")
   2274                         (org-agenda-skip-function 'za/skip-if-has-habit-tag)))))
   2275           ("aT" "Tomorrow"
   2276            ((agenda "" ((org-agenda-span 'day)
   2277                         (org-agenda-start-day "+1d")))))
   2278 
   2279           ("w" "Week Agenda + Next Actions"
   2280            ((agenda "" ((org-agenda-overriding-header "Week agenda:")))
   2281             (todo "NEXT" ((org-agenda-overriding-header "Next actions:")))))
   2282 
   2283           ("o" "Month agenda"
   2284            ((agenda "" ((org-agenda-overriding-header "Month agenda:")
   2285                         (org-agenda-span 'month)))))
   2286 
   2287           ("d" "Day Agenda + Habits graph + Waiting"
   2288            ((agenda "" ((org-agenda-overriding-header "Day:")
   2289                         (org-agenda-span 'day)
   2290                         (org-habit-show-habits nil)
   2291                         (org-agenda-skip-function 'za/skip-if-has-habit-tag)))
   2292             (todo "STARTED" ((org-agenda-overriding-header "In progress:")))
   2293             (todo "WAITING" ((org-agenda-overriding-header "Waiting:")))
   2294             (agenda "" ((org-agenda-overriding-header "Habits:")
   2295                         (org-agenda-span 'day)
   2296                         (org-agenda-use-time-grid nil)
   2297                         (org-agenda-skip-function 'za/skip-unless-habit)
   2298                         (org-habit-show-habits t) (org-habit-show-habits-only-for-today nil)
   2299                         (org-habit-show-all-today t)))))
   2300           ("D" "Day Agenda with habit tags + Habits + Waiting"
   2301            ((agenda "" ((org-agenda-overriding-header "Day:")
   2302                         (org-agenda-span 'day)
   2303                         (org-habit-show-habits nil)))
   2304             (todo "STARTED" ((org-agenda-overriding-header "In progress:")))
   2305             (todo "WAITING" ((org-agenda-overriding-header "Waiting:")))
   2306             (agenda "" ((org-agenda-overriding-header "Habits:")
   2307                         (org-agenda-span 'day)
   2308                         (org-agenda-use-time-grid nil)
   2309                         (org-agenda-skip-function 'za/skip-unless-habit)
   2310                         (org-habit-show-habits t) (org-habit-show-habits-only-for-today nil)
   2311                         (org-habit-show-all-today t)))))
   2312 
   2313 
   2314           ("k" "Kanban view"
   2315            ((todo "DONE" ((org-agenda-overriding-header "Done:") (org-agenda-sorting-strategy '(deadline-up priority-down alpha-up))))
   2316             (todo "STARTED" ((org-agenda-overriding-header "In progress:") (org-agenda-sorting-strategy '(deadline-up priority-down alpha-up))))
   2317             (todo "NEXT" ((org-agenda-overriding-header "To do:") (org-agenda-sorting-strategy '(deadline-up priority-down alpha-up))))))
   2318 
   2319           ("p" "Projects"
   2320            ((todo "PROJ" ((org-agenda-overriding-header "Projects:")
   2321                           (org-agenda-prefix-format '((todo . " %i %-22(let ((deadline (org-entry-get nil \"DEADLINE\"))) (if deadline deadline \"\"))")))
   2322                           (org-agenda-dim-blocked-tasks nil)
   2323                           (org-agenda-sorting-strategy '((todo deadline-up alpha-down)))))))
   2324           ("1" "1-3-5"
   2325            ((tags "_1" ((org-agenda-overriding-header "Big tasks:")
   2326                         (org-agenda-skip-function 'za/skip-if-scheduled-in-future)
   2327                         (org-agenda-sorting-strategy '(todo-state-down deadline-up priority-down alpha-up))))
   2328             (tags "_3" ((org-agenda-overriding-header "Medium tasks:")
   2329                         (org-agenda-skip-function 'za/skip-if-scheduled-in-future)
   2330                         (org-agenda-sorting-strategy '(todo-state-down deadline-up priority-down alpha-up))))
   2331             (tags "_5" ((org-agenda-overriding-header "Small tasks:")
   2332                         (org-agenda-skip-function 'za/skip-if-scheduled-in-future)
   2333                         (org-agenda-sorting-strategy '(todo-state-down deadline-up priority-down alpha-up))))))
   2334 
   2335           ;; Useful thread for opening calfw: https://github.com/kiwanami/emacs-calfw/issues/18
   2336           ("c" "Calendar view" (lambda (&rest _)
   2337                                  (interactive)
   2338                                  (let ((org-agenda-skip-function 'za/skip-if-habit))
   2339                                    (cfw:open-org-calendar))))
   2340           ("f" . "Find & fix...")
   2341           ("f@" "Next actions missing context"
   2342            todo "NEXT" ((org-agenda-overriding-header "Missing context:")
   2343                         (org-agenda-sorting-strategy '(priority-down alpha-up))
   2344                         (org-agenda-skip-function 'za/skip-if-has-context)))
   2345           ("fe" "Next actions missing energy"
   2346            todo "NEXT" ((org-agenda-overriding-header "Missing energy level:")
   2347                         (org-agenda-sorting-strategy '(priority-down alpha-up))
   2348                         (org-agenda-skip-function 'za/skip-if-has-energy-level)))
   2349           ("ff" "Finished tasks that aren't in a project"
   2350            ((tags "TODO=\"DONE\"|TODO=\"CANCELLED\"" ((org-agenda-overriding-header "Finished tasks:")
   2351                                                       (org-agenda-skip-function 'za/skip-if-in-project)))))
   2352           ("ft" "Tasks without a scheduled time"
   2353            alltodo "" ((org-agenda-overriding-header "Missing scheduled time:")
   2354                        (org-agenda-skip-function '(org-agenda-skip-entry-if 'scheduled 'deadline 'timestamp))))))
   2355 #+end_src
   2356 
   2357 In calfw, I don't want to show habits:
   2358 
   2359 #+begin_src emacs-lisp
   2360   (add-hook 'cfw:calendar-mode-hook (setq-local org-agenda-skip-function 'za/skip-if-habit))
   2361 #+end_src
   2362 
   2363 *** Automatically mark next project item as NEXT
   2364 Unless the current item is a project, when a project item is done, the next item in the project should be marked "NEXT".
   2365 I tried org-edna but I couldn't get it working after an hour of effort. So a bit of lisp is the easier solution.
   2366 
   2367 #+begin_src emacs-lisp
   2368   (defun za/gtd-auto-next ()
   2369     "Automatically mark project item as next."
   2370     (save-excursion
   2371       (org-back-to-heading)
   2372       (when (buffer-narrowed-p)
   2373         (widen))
   2374       (when (and (member org-state org-done-keywords)
   2375                  (not (member "PROJECT" (org-get-tags nil 'local)))
   2376                  (member "PROJECT" (let ((org-use-tag-inheritance t))
   2377                                      (org-get-tags nil))))
   2378         (when (org-goto-sibling)
   2379           (org-entry-put (point) "TODO" "NEXT")))))
   2380 
   2381   (add-hook #'org-after-todo-state-change-hook #'za/gtd-auto-next)
   2382 #+end_src
   2383 
   2384 *** Logging for tasks
   2385 I want to log into the LOGBOOK drawer (useful when I want to take quick notes):
   2386 
   2387 #+begin_src emacs-lisp
   2388   (setq org-log-into-drawer "LOGBOOK")
   2389 #+end_src
   2390 
   2391 I also want to log when I finish a task (useful for archiving).
   2392 Furthermore, when I'm done, I want to add a note (any important
   2393 workarounds/tips). And when I reschedule, I want to know the reason.
   2394 I can disable logging on state change for a specific task by adding ~:LOGGING: nil~ to the ~:PROPERTIES:~ drawer.
   2395 
   2396 #+begin_src emacs-lisp
   2397   (setq org-log-done 'time
   2398         org-log-reschedule 'note)
   2399 #+end_src
   2400 
   2401 I want to hide drawers on startup. This variable has options:
   2402 - 'overview': Top-level headlines only.
   2403 - 'content': All headlines.
   2404 - 'showall': No folding on any entry.
   2405 - 'show2levels: Headline levels 1-2.
   2406 - 'show3levels: Headline levels 1-3.
   2407 - 'show4levels: Headline levels 1-4.
   2408 - 'show5levels: Headline levels 1-5.
   2409 - 'showeverything: Show even drawer contents.
   2410 
   2411 #+begin_src emacs-lisp
   2412   (setq org-startup-folded 'content)
   2413 #+end_src
   2414 
   2415 *** Task ordering
   2416 Some tasks should be ordered, i.e. they should be done in steps.
   2417 Those have the ~:ORDERED: t~ setting in ~:PROPERTIES:~, and it should be enforced:
   2418 
   2419 #+begin_src emacs-lisp
   2420   (setq org-enforce-todo-dependencies t)
   2421 #+end_src
   2422 
   2423 Furthermore, tasks that are ordered and can't be done yet because of previous steps should be dimmed in the agenda:
   2424 
   2425 #+begin_src emacs-lisp
   2426   (setq org-agenda-dim-blocked-tasks t)
   2427 #+end_src
   2428 
   2429 I might also want to set ~org-enforce-todo-checkbox-dependencies~, but not convinced on that one yet.
   2430 
   2431 *** Time tracking & effort
   2432 Time tracking should be done in its own drawer:
   2433 
   2434 #+begin_src emacs-lisp
   2435   (setq org-clock-into-drawer "CLOCK")
   2436 #+end_src
   2437 
   2438 And to customize how clock tables work:
   2439 
   2440 #+begin_src emacs-lisp
   2441   (setq org-clocktable-defaults '(:lang "en" :scope agenda-with-archives  :wstart 1 :mstart 1 :compact t :maxlevel nil))
   2442   (setq org-agenda-clockreport-parameter-plist '(:link t :maxlevel nil))
   2443 #+end_src
   2444 
   2445 I want to set effort in hours:minutes:
   2446 
   2447 #+begin_src emacs-lisp
   2448   (add-to-list 'org-global-properties '("Effort_ALL" . "0:05 0:10 0:15 0:20 0:30 0:45 1:00 1:30 2:00 4:00 6:00 8:00"))
   2449 #+end_src
   2450 
   2451 I want column view to look like this:
   2452 
   2453 | To do        | Task      | Tags | Sum of time elapsed | Sum of time estimated (effort) |
   2454 |--------------+-----------+------+---------------------+--------------------------------|
   2455 | todo keyword | task name | tags | sum of clock        | sum of estimated time          |
   2456 | ...          | ...       | ...  | ...                 | ...                            |
   2457 
   2458 #+begin_src emacs-lisp
   2459   (setq org-columns-default-format "%7TODO (To Do) %32ITEM(Task) %TAGS(Tags) %11CLOCKSUM_T(Clock) %10Difficulty(Difficulty) %8Effort(Effort){:}")
   2460 #+end_src
   2461 
   2462 Fix column alignment in agenda.
   2463 
   2464 #+begin_src emacs-lisp
   2465   (set-face-attribute 'org-column nil
   2466                       :height (face-attribute 'default :height)
   2467                       :family (face-attribute 'default :family))
   2468   (set-face-attribute 'org-agenda-date-today nil
   2469                       :height (face-attribute 'default :height))
   2470 #+end_src
   2471 
   2472 *** Calculate time since timestamp
   2473 #+begin_src emacs-lisp
   2474   (defun za/org-time-since ()
   2475     "Print the amount of time between the timestamp at point and the current date and time."
   2476     (interactive)
   2477     (unless (org-at-timestamp-p 'lax)
   2478       (user-error "Not at timestamp"))
   2479 
   2480     (when (org-at-timestamp-p 'lax)
   2481       (let ((timestamp (match-string 0)))
   2482         (with-temp-buffer
   2483           (insert timestamp
   2484                   "--"
   2485                   (org-time-stamp '(16)))
   2486           (org-evaluate-time-range)))))
   2487 #+end_src
   2488 
   2489 Also a method to add overlays with that timestamp:
   2490 
   2491 #+begin_src emacs-lisp
   2492   (defvar-local za/org-timestamp-overlays--list nil "Buffer-local list of overlays with timestamps")
   2493   (defvar-local za/org-timestamp-overlays--show nil "Buffer-local boolean to show overlays.")
   2494   (defun za/org-timestamp-overlays-clear ()
   2495     "Clear all overlays with timestamps in current buffer."
   2496     (dolist (ov za/org-timestamp-overlays--list)
   2497       (delete-overlay ov))
   2498     (setq-local za/org-timestamp-overlays--list nil))
   2499 
   2500   (defun za/org-timestamp-overlays-add ()
   2501     "Add overlays for active timestamps in current buffer."
   2502     (let ((markup-string (lambda (s) (propertize (format "{%s}" s)
   2503                                                  'face 'org-habit-ready-future-face))))
   2504       (save-excursion
   2505         (let* ((beg (point-min))
   2506                (end (point-max)))
   2507           (goto-char beg)
   2508           (while (re-search-forward (org-re-timestamp 'active) end t)
   2509             (let ((ov (make-overlay (point) (point))))
   2510               (overlay-put ov 'before-string (funcall markup-string (za/org-time-since)))
   2511               (add-to-list 'za/org-timestamp-overlays--list ov)))))))
   2512 
   2513   (defun za/org-timestamp-overlays-redraw ()
   2514     "Redraw all overlays for active timestamps."
   2515     (za/org-timestamp-overlays-clear)
   2516     (za/org-timestamp-overlays-add))
   2517 
   2518   (defun za/org-timestamp-hook-fn (&rest _)
   2519     (za/org-timestamp-overlays-redraw))
   2520 
   2521   (bind-key "C-c q p" #'tmp/p)
   2522   (defun za/org-timestamp-overlays-toggle (&optional prefix)
   2523     "With no prefix, toggle showing timestamp overlay.
   2524   With PREFIX = 0, redraw overlays.
   2525   With PREFIX > 0, show overlays.
   2526   With PREFIX < 0, hide overlays."
   2527     (interactive "P")
   2528     (let ((overlays-hide (lambda ()
   2529                            (za/org-timestamp-overlays-clear)
   2530                            (remove-hook 'org-cycle-hook #'za/org-timestamp-hook-fn)
   2531                            (setq za/org-timestamp-overlays--show nil)
   2532                            (message "Overlays hidden.")))
   2533           (overlays-show (lambda ()
   2534                            (za/org-timestamp-overlays-redraw)
   2535                            (add-hook 'org-cycle-hook #'za/org-timestamp-hook-fn)
   2536                            (setq za/org-timestamp-overlays--show t)
   2537                            (message "Overlays showing.")))
   2538           (overlays-redraw-maybe (lambda ()
   2539                                    (when za/org-timestamp-overlays--show
   2540                                      (za/org-timestamp-overlays-redraw)
   2541                                      (message "Redrawing overlays."))))
   2542           (prefix-num (prefix-numeric-value prefix)))
   2543       (cond ((not prefix)
   2544              (cond (za/org-timestamp-overlays--show
   2545                     (funcall overlays-hide))
   2546                    (t
   2547                     (funcall overlays-show))))
   2548             ((zerop prefix-num)
   2549              )
   2550             ((> prefix-num 0)
   2551              (funcall overlays-show))
   2552             ((< prefix-num 0)
   2553              (funcall overlays-hide)))))
   2554 
   2555 #+end_src
   2556 
   2557 Bind a key:
   2558 
   2559 #+begin_src emacs-lisp
   2560   (bind-key "C-c q d" #'za/org-timestamp-overlays-toggle 'org-mode-map)
   2561   (bind-key "C-c q d" #'za/org-timestamp-overlays-toggle 'org-agenda-mode-map)
   2562 #+end_src
   2563 *** Priorities: how important something is
   2564 I usually have a lot of 'next' actions, so I prefer 4 priority levels instead of the default 3: A (urgent, ASAP), B (important),  C (if you have nothing else, do this), D (do in free time):
   2565 
   2566 #+begin_src emacs-lisp
   2567   (setq org-priority-highest ?A
   2568         org-priority-lowest ?D
   2569         org-priority-default ?C)
   2570 #+end_src
   2571 
   2572 Faces for priorities in agenda:
   2573 
   2574 #+begin_src emacs-lisp
   2575   (setq org-priority-faces `((?A . (:foreground ,(face-foreground 'error)))
   2576                              (?B . (:foreground ,(face-foreground 'org-todo)))
   2577                              (?C . (:foreground ,(face-foreground 'font-lock-constant-face) :weight semi-light))
   2578                              (?D . (:foreground ,(face-foreground 'font-lock-string-face) :slant italic :weight light))))
   2579 #+end_src
   2580 
   2581 And to be able to bulk-set priorities in agenda:
   2582 
   2583 #+begin_src emacs-lisp
   2584   (setq org-agenda-bulk-custom-functions '((?P (lambda nil (org-agenda-priority 'set)))))
   2585 #+end_src
   2586 *** Energy requirement: how difficult something is
   2587 #+begin_src emacs-lisp
   2588   (add-to-list 'org-global-properties '("Difficulty_ALL" . "low medium high"))
   2589 #+end_src
   2590 *** Org export backends
   2591 #+begin_src emacs-lisp
   2592   (use-package ox-pandoc)
   2593 #+end_src
   2594 
   2595 *** org publishing
   2596 I decided, after trying many different things, to settle on org-publish.
   2597 
   2598 #+begin_src emacs-lisp
   2599   (defconst za/org-roam-top-name "Top" "The name of the top-level Org-roam node.")
   2600   (defun za/org-roam-sitemap-function (title list)
   2601     "Customized function to generate sitemap for org-roam, almost the same as `org-publish-sitemap-default`."
   2602     (concat "#+TITLE: " title "\n\n"
   2603             (format "[[file:%s][%s]]\n\n"
   2604                     (file-name-nondirectory (org-roam-node-file
   2605                                              (org-roam-node-from-title-or-alias za/org-roam-top-name)))
   2606                     "Click here for entrypoint.")))
   2607   ;; (org-list-to-org list)))  <-- this is taken care of by Zola
   2608 
   2609 #+end_src
   2610 
   2611 To make this work with Zola, I need to export Github-flavored markdown (fenced code blocks with language):
   2612 
   2613 #+begin_src emacs-lisp
   2614   (require 'ox-publish)
   2615   (require 'ox-md)
   2616 
   2617   (use-package ox-gfm
   2618     :init
   2619     (with-eval-after-load 'org (require 'ox-gfm)))
   2620 #+end_src
   2621 
   2622 First difficulty: Zola needs front matter with ~+++...+++~.
   2623 The default Markdown backend doesn't provide that, so need to customize it by advising the default ~org-md-template~.
   2624 
   2625 #+begin_src emacs-lisp
   2626   (defun za/org-md-template-zola (contents info)
   2627     "Markdown template compatible with Zola (generates the necessary front matter from CONTENTS and INFO)."
   2628     (let ((title (org-md-plain-text (org-element-interpret-data (plist-get info :title)) info)))
   2629       (concat "+++\n"
   2630               (format "title = \"%s\"\n" (string-replace "\"" "'" title))
   2631 
   2632               ;; If the note contains a math org-roam tag
   2633               (when (member "math" (plist-get info :filetags))
   2634                 "template = \"page-math.html\"\n")
   2635 
   2636               "+++\n"
   2637               (format "# %s\n" title)
   2638               contents)))
   2639 #+end_src
   2640 
   2641 Second difficulty: links need to be reformatted and changed for static data (like images).
   2642 This function filters the return value of ~org-md-link~.
   2643 
   2644 #+begin_src emacs-lisp
   2645   (defun za/org-md-link-zola (linkstr)
   2646     "A filter function for the return value of
   2647           `org-md-link` (LINKSTR) to generate a link compatible with Zola."
   2648     (cond ((string-match-p (rx ".md") linkstr)
   2649            (string-replace "](" "](@/org-roam/" linkstr))
   2650           ((string-match-p (rx "](" (? (* alnum) "://") "/") linkstr)
   2651            (replace-regexp-in-string (rx "](" (? (* alnum) "://") "/" (* any) "/org-roam/data") "](/org-roam-data" linkstr))
   2652           (t linkstr)))
   2653 #+end_src
   2654 
   2655 A wrapper to set the right image link:
   2656 
   2657 #+begin_src emacs-lisp
   2658   (defun za/org-html--format-image (args)
   2659     "Modify source image link to work with my Org roam setup"
   2660     (let ((source (nth 0 args))
   2661           (_attributes (nth 1 args))
   2662           (_info (nth 2 args)))
   2663       (list (replace-regexp-in-string (rx bos "data/") "/org-roam-data/" source)
   2664             _attributes
   2665             _info)))
   2666 #+end_src
   2667 
   2668 And here's the custom publish function that adds/removes the necessary advice:
   2669 
   2670 #+begin_src emacs-lisp
   2671   (defun za/org-gfm-publish-to-gfm-zola (plist filename pub-dir)
   2672     "Run `org-gfm-publish-to-gfm`, advising the necessary
   2673   functions to generate Zola-compatible markdown."
   2674     (let* ((org-export-output-file-name-locked (lambda (extension &rest _)
   2675                                                  (concat (plist-get plist :publishing-directory)
   2676                                                          "locked-"
   2677                                                          (file-name-base filename)
   2678                                                          extension)))
   2679            (node (car (seq-filter
   2680                        (lambda (node) (file-equal-p (org-roam-node-file node) filename))
   2681                        (org-roam-node-list))))
   2682            (locked-p (cond ((file-equal-p filename
   2683                                           (file-name-concat (plist-get plist :base-directory) (plist-get plist :sitemap-filename)))
   2684                             nil)
   2685                            (t
   2686                             (member "locked" (org-roam-node-tags node)))))
   2687            (advice '((org-gfm-inner-template :override za/org-md-template-zola)
   2688                      (org-md-link :filter-return za/org-md-link-zola)
   2689                      (org-html--format-image :filter-args za/org-html--format-image)
   2690                      (org-gfm-table :override org-md--convert-to-html)))) ; Zola uses CommonMark, so doesn't support Markdown tables
   2691 
   2692       (dolist (orig-type-new advice) (apply #'advice-add orig-type-new))
   2693       (unwind-protect
   2694           (cond (locked-p
   2695                  (advice-add #'org-export-output-file-name :override org-export-output-file-name-locked)
   2696                  (unwind-protect
   2697                      (org-gfm-publish-to-gfm plist filename pub-dir)
   2698                    (advice-remove #'org-export-output-file-name org-export-output-file-name-locked)))
   2699                 (t
   2700                  (org-gfm-publish-to-gfm plist filename pub-dir)))
   2701         (dolist (orig-type-new advice)
   2702           (advice-remove (nth 0 orig-type-new)
   2703                          (nth 2 orig-type-new))))))
   2704 #+end_src
   2705 
   2706 Finally, the list of things we can publish with their respective publishin functions:
   2707 
   2708 #+begin_src emacs-lisp
   2709   (if (boundp 'za/my-website-dir)
   2710       (setq org-publish-project-alist
   2711             `(
   2712               ("org-notes"
   2713                :base-directory ,za/org-roam-dir
   2714                :base-extension "org"
   2715                :publishing-directory ,(concat za/my-website-dir "content/org-roam/")
   2716                :publishing-function za/org-gfm-publish-to-gfm-zola
   2717                :recursive t
   2718                :sitemap-filename "_index.md"
   2719                :sitemap-title "Org Roam"
   2720                :sitemap-function za/org-roam-sitemap-function
   2721                :auto-sitemap t)
   2722 
   2723               ("org-notes-data"
   2724                :base-directory ,(concat za/org-roam-dir "/data")
   2725                :base-extension any
   2726                :publishing-directory ,(concat za/my-website-dir "static/org-roam-data/")
   2727                :recursive t
   2728                :publishing-function org-publish-attachment)
   2729 
   2730               ("org-roam" :components ("org-notes" "org-notes-data"))))
   2731     (warn "za/my-website-dir not bound, not setting org publishing targets."))
   2732 #+end_src
   2733 
   2734 And a function to rsync to my VPS:
   2735 
   2736 #+begin_src emacs-lisp
   2737   (defun za/publish-upload-to-website ()
   2738     "Upload my website to my VPS"
   2739     (interactive)
   2740     (async-shell-command (format "cd %s && zola build && yes|publish" za/my-website-dir) "*Async Shell publish*"))
   2741 #+end_src
   2742 *** Rebuild org cache
   2743 
   2744 #+begin_src emacs-lisp
   2745   (defun za/force-org-rebuild-cache ()
   2746     "Rebuild the `org-mode' and `org-roam' cache."
   2747     (interactive)
   2748     (org-id-update-id-locations)
   2749     ;; Note: you may need `org-roam-db-clear-all'
   2750     ;; followed by `org-roam-db-sync'
   2751     (org-roam-db-sync)
   2752     (org-roam-update-org-id-locations))
   2753 #+end_src
   2754 *** Sync with Flatastic
   2755 API work is handled via an external ruby script.
   2756 
   2757 #+begin_src emacs-lisp
   2758   (defun za/org-flatastic-sync-tasks ()
   2759     "Add tasks from flatastic to inbox"
   2760     (interactive)
   2761     (unless (json-available-p)
   2762       (user-error "JSON not available"))
   2763     (unless (boundp 'za/org-life-inbox)
   2764       (user-error "Please set za/org-life-inbox"))
   2765     (let* ((api-data (json-parse-string
   2766                       (progn
   2767                         (require 'exec-path-from-shell)
   2768                         (exec-path-from-shell-copy-envs
   2769                          '("FLATASTIC_API_KEY" "FLATASTIC_USER_ID"))
   2770                         (shell-command-to-string "~/.local/share/rbenv/shims/ruby ~/.scripts/flatastic.rb"))
   2771                       :object-type 'alist))
   2772            (format-data-as-org (lambda (l)
   2773                                  (format "* TODO %s :flatastic:\n  SCHEDULED: <%s>\n  Points: %d\n"
   2774                                          (alist-get 'description l)
   2775                                          (alist-get 'scheduled_due_date l)
   2776                                          (alist-get 'point_value l))))
   2777            (org-flatastic-items (mapcar format-data-as-org api-data)))
   2778       (with-current-buffer (find-file-noselect za/org-life-inbox)
   2779         (goto-char (point-max))
   2780         (insert "\n" (string-join org-flatastic-items "\n")))
   2781       (message "Synced %d Flatastic tasks to inbox" (length api-data))))
   2782 #+end_src
   2783 *** Link to Thunderbird messages
   2784 Create a custom link to open thunderbird emails by ID:
   2785 
   2786 #+begin_src emacs-lisp
   2787   (org-link-set-parameters
   2788    "thunderbird"
   2789    :follow #'za/org-link-thunderbird-follow)
   2790 
   2791   (defun za/org-link-thunderbird-follow (messageid)
   2792     "Open the message with id `messageid` in Thunderbird"
   2793     (shell-command (format "thunderbird mid:%s" (shell-quote-argument messageid))))
   2794 #+end_src
   2795 *** Inverse refile
   2796 #+begin_src emacs-lisp
   2797   (defun za/org-refile-to-point (refloc)
   2798     "Prompt for a heading and refile it to point."
   2799     (interactive (list (org-refile-get-location "Heading: ")))
   2800     (let* ((file (nth 1 refloc))
   2801            (pos (nth 3 refloc)))
   2802       (save-excursion
   2803         (with-current-buffer (find-file-noselect file 'noward)
   2804           (save-excursion
   2805             (save-restriction
   2806               (widen)
   2807               (goto-char pos)
   2808               (org-copy-subtree 1 t))))
   2809         (org-paste-subtree nil nil nil t))))
   2810 
   2811 
   2812   (defun za/org-refile-wrapper (arg)
   2813     "Wrap org-refile so that it does the inverse with a negative argument"
   2814     (interactive "P")
   2815     (if (minusp (prefix-numeric-value arg))
   2816         (call-interactively #'za/org-refile-to-point)
   2817       (org-refile arg)))
   2818 
   2819 #+end_src
   2820 
   2821 *** org-caldav
   2822 This lets me sync my Org agenda to my CalDAV server.
   2823 The main reason is because Orgzly doesn't have a calendar view and can't (yet) search for events on a specific day, so if someone asks "are you free on that day", it's a bit hard for me to answer if I don't have my computer with me.
   2824 This way, I can just check my calendar.
   2825 
   2826 #+begin_src emacs-lisp
   2827   (if (and (boundp 'za/caldav-url)
   2828            (boundp 'za/caldav-org-calendar-id)
   2829            (boundp 'za/org-life-calendar-inbox))
   2830       (use-package org-caldav
   2831         :init
   2832         (defconst za/org-life-calendar-inbox (concat za/org-life-dir "calendar-inbox.org"))
   2833         :custom
   2834         (org-caldav-url za/caldav-url)
   2835         (org-caldav-calendar-id za/caldav-org-calendar-id)
   2836         (org-caldav-inbox za/org-life-calendar-inbox)
   2837         (org-caldav-files (cons (car (split-string org-archive-location "::")) org-agenda-files))
   2838         (org-caldav-sync-todo nil)
   2839         (org-icalendar-include-todo nil)
   2840         (org-icalendar-use-deadline '(event-if-todo event-if-not-todo todo-due))
   2841         (org-icalendar-use-scheduled '(todo-start event-if-todo event-if-not-todo))
   2842         (org-caldav-exclude-tags '("HABIT")
   2843                                  "I don't want to export habits, because those will just clutter up my calendar. The calendar is supposed to be for one-off stuff, or rarely repeating stuff. Yes, I have to manually add the HABIT tag to every habit. Perhaps nicer would be to exclude based on the property ~:STYLE: habit~, but I haven't figured that one out yet.")
   2844         (org-caldav-todo-percent-states '((0 "TODO")
   2845                                           (0 "WAITING")
   2846                                           (1 "NEXT")
   2847                                           (2 "STARTED")
   2848                                           (0 "PROJ")
   2849                                           (100 "DONE")
   2850                                           (100 "CANCELLED")))
   2851         :config
   2852         (defun za/caldav-after-sync-notify () (za/notify "org-caldav sync complete" "Finished syncing"))
   2853         (advice-add #'org-caldav-sync :after #'za/caldav-after-sync-notify)
   2854         (advice-add #'org-caldav-sync :around #'za/notify-on-interactivity))
   2855     (warn "za/caldav-url, za/caldav-org-calendar-id, za/org-life-calendar-inbox not bound, not using org-caldav."))
   2856 #+end_src
   2857 
   2858 Maybe check [[https://old.reddit.com/r/orgmode/comments/8rl8ep/making_orgcaldav_useable/e0sb5j0/][this]] for a way to sync on save.
   2859 
   2860 *** org-ref
   2861 #+begin_src emacs-lisp
   2862   (use-package org-ref)
   2863 #+end_src
   2864 *** org-roam
   2865 #+begin_src emacs-lisp
   2866   (use-package org-roam
   2867     :custom
   2868     (org-roam-directory za/org-roam-dir)
   2869     (org-roam-completion-everywhere t)
   2870     (org-roam-dailies-capture-templates
   2871      '(("d" "default" entry
   2872         "* %U\n%?"
   2873         :target (file+head "%<%Y-%m-%d>.org"
   2874                            "#+title: %<%Y-%m-%d>\n"))))
   2875     :config
   2876                                           ; can't use nil because org-roam-ui checks for boundp on this and
   2877                                           ; errors if bound but nil.
   2878     (with-eval-after-load 'org-roam-dailies
   2879       (makunbound 'org-roam-dailies-directory))
   2880     (defun za/org-roam-dailies-goto-latest-note ()
   2881       (interactive)
   2882       (unless (boundp 'org-roam-dailies-directory)
   2883         (za/org-roam-dailies-select-dir))
   2884       (let* ((dailies (org-roam-dailies--list-files))
   2885              (latest-note (car (last dailies))))
   2886         (unless latest-note
   2887           (user-error "Can't find latest note"))
   2888         (find-file latest-note)
   2889         (run-hooks 'org-roam-dailies-find-file-hook)))
   2890     (org-roam-setup)
   2891     (bind-keys :prefix "C-c w"
   2892                :prefix-map za/org-roam-map
   2893                :prefix-docstring "Org roam"
   2894                ("n" . org-roam-capture)
   2895                ("f" . org-roam-node-find)
   2896                ("w" . org-roam-buffer-toggle)
   2897                ("i" . org-roam-node-insert))
   2898     (bind-keys :prefix "C-c j"
   2899                :prefix-map za/org-roam-dailies-map
   2900                :prefix-docstring "Org roam dailies"
   2901                ("s" . za/org-roam-dailies-select-dir)
   2902                ("n" . org-roam-dailies-capture-today)
   2903                ("j" . org-roam-dailies-goto-today)
   2904                ("+" . org-roam-dailies-goto-tomorrow)
   2905                (">" . org-roam-dailies-goto-next-note)
   2906                ("-" . org-roam-dailies-goto-yesterday)
   2907                ("<" . org-roam-dailies-goto-previous-note)
   2908                ("g" . org-roam-dailies-goto-date)
   2909                ("l" . za/org-roam-dailies-goto-latest-note)
   2910                ("." . org-roam-dailies-find-directory))
   2911 
   2912     (defun za/org-roam-dailies--daily-note-p (&optional file)
   2913       "Replacement of default function. Return t if FILE is an Org-roam daily-note, nil otherwise.
   2914   If FILE is not specified, use the current buffer's file-path."
   2915       (when-let ((path (expand-file-name
   2916                         (or file
   2917                             (buffer-file-name (buffer-base-buffer)))))
   2918                  (directory (expand-file-name org-roam-dailies-directory org-roam-directory)))
   2919         (setq path (expand-file-name path))
   2920         (save-match-data
   2921           (and
   2922            ;; (org-roam-file-p path) ; don't want this, dailies might not be in org-roam path
   2923            (org-roam-descendant-of-p path directory)))))
   2924     (advice-add #'org-roam-dailies--daily-note-p :override #'za/org-roam-dailies--daily-note-p)
   2925 
   2926     (defun za/org-roam-dailies-select-dir ()
   2927       "Select an org-roam-dailies folder."
   2928       (interactive)
   2929       (let* ((choices (cons '(?0 nil) za/org-roam-dailies-dirs))
   2930              (choice (nth 1 (read-multiple-choice "org-roam-dailies dir" choices))))
   2931         (if choice
   2932             (progn (setq org-roam-dailies-directory choice)
   2933                    (message "Selected org-roam-dailies directory: %s" org-roam-dailies-directory))
   2934           (makunbound 'org-roam-dailies-directory))))
   2935 
   2936     (defun za/org-roam-dailies-calendar-mark-entries-p ()
   2937       "Only mark dailies entries in calendar if a dailies directory is set."
   2938       (boundp 'org-roam-dailies-directory))
   2939     (advice-add #'org-roam-dailies-calendar-mark-entries :before-while #'za/org-roam-dailies-calendar-mark-entries-p)
   2940 
   2941     ;; Before doing anything journal-related, check that a journal is
   2942     ;; selected, or prompt for one.
   2943     (defun za/org-roam-dailies--capture-check-non-nil-dailies-dir (&rest _)
   2944       (unless (boundp 'org-roam-dailies-directory)
   2945         (za/org-roam-dailies-select-dir))
   2946       (unless (boundp 'org-roam-dailies-directory)
   2947         (user-error "No org-roam-dailies-directory selected!")))
   2948 
   2949     (advice-add #'org-roam-dailies--capture :before #'za/org-roam-dailies--capture-check-non-nil-dailies-dir)
   2950     (advice-add #'org-roam-dailies-goto-date :before #'za/org-roam-dailies--capture-check-non-nil-dailies-dir)
   2951     (require 'org-roam-export))
   2952 #+end_src
   2953 
   2954 *** org-roam-ui
   2955 #+begin_src emacs-lisp
   2956   (use-package org-roam-ui)
   2957 #+end_src
   2958 *** org-download
   2959 Drag-and-drop images to Emacs Org mode.
   2960 
   2961 #+begin_src emacs-lisp
   2962   (use-package org-download
   2963     :custom
   2964     (org-download-method 'attach)
   2965     (org-download-backend t))
   2966 #+end_src
   2967 
   2968 *** org-sticky-header
   2969 Displays in the header-line the Org heading for the node that’s at the top of the window.
   2970 
   2971 #+begin_src emacs-lisp
   2972   (use-package org-sticky-header)
   2973 #+end_src
   2974 *** org-timestone
   2975 #+begin_src emacs-lisp
   2976   (use-package org-timestone
   2977     :init (za/package-vc-install :repo "thezeroalpha/org-timestone.el")
   2978     :ensure nil
   2979     :after org
   2980     :bind (:map org-mode-map
   2981                 ("C-c C-t" . org-timestone-org-todo-wrapper)))
   2982 #+end_src
   2983 *** org-noter
   2984 #+begin_src emacs-lisp
   2985   (use-package org-noter
   2986     :config
   2987     ;; Fix disabling of line wrap by no-opping set-notes-scroll
   2988     (advice-add 'org-noter--set-notes-scroll :override 'za/no-op))
   2989 #+end_src
   2990 *** el-easydraw
   2991 Lets you draw stuff in org mode documents.
   2992 
   2993 #+begin_src emacs-lisp :tangle no
   2994   (za/package-vc-install :repo "misohena/el-easydraw" :name "edraw")
   2995   (with-eval-after-load 'org
   2996     (require 'edraw-org)
   2997     (edraw-org-setup-default)
   2998     (bind-key "C-c q c" #'edraw-color-picker-insert-color))
   2999 #+end_src
   3000 *** ox-jira
   3001 #+begin_src emacs-lisp
   3002   (use-package ox-jira)
   3003 #+end_src
   3004 *** org-confluence
   3005 ox-confluence with some custom code to remove the theme & create expandable drawers.
   3006 Add to confluence by pressing ~ctrl + shift + d~ when editing a page and inserting confluence wiki text.
   3007 
   3008 #+begin_src emacs-lisp
   3009   (require 'ox-confluence)
   3010   (org-export-define-derived-backend 'confluence-ext 'confluence
   3011     :translate-alist '((drawer . za/org-confluence-drawer))
   3012     :filters-alist '((:filter-src-block . za/org-confluence--code-block-remove-theme))
   3013     :menu-entry
   3014     '(?F "Export to Confluence (ext)"
   3015          ((?F "As Confluence buffer (ext)" za/org-confluence-export-as-confluence))))
   3016 
   3017   (defun za/org-confluence-export-as-confluence
   3018       (&optional async subtreep visible-only body-only ext-plist)
   3019     (interactive)
   3020     (org-export-to-buffer 'confluence-ext "*org CONFLUENCE Export*"
   3021       async subtreep visible-only body-only ext-plist (lambda () (text-mode))))
   3022 
   3023   (defun za/org-confluence--code-block-remove-theme (block _backend _info)
   3024     "Remove the theme from the block"
   3025     (replace-regexp-in-string (rx "\{code:theme=Emacs" (? "|")) "\{code:" block))
   3026 
   3027 
   3028   (defun za/org-confluence-drawer (drawer contents info)
   3029     "Handle custom drawers"
   3030     (let* ((name (org-element-property :drawer-name drawer)))
   3031       (concat
   3032        (format "\{expand:%s\}\n" name)
   3033        contents
   3034        "\{expand\}")))
   3035 #+end_src
   3036 *** TODO the path for org-roam export and data export should be configurable, not hard-coded
   3037 ** Mail mode for neomutt
   3038 When editing a message from neomutt, I want to use mail mode.
   3039 Even though I won't be sending the email from there, I like the syntax highlighting :)
   3040 
   3041 #+begin_src emacs-lisp
   3042   (add-to-list 'auto-mode-alist '("/neomutt-" . mail-mode))
   3043 #+end_src
   3044 ** DISABLED Semantic mode
   3045 Disabled for now, don't use it much.
   3046 SemanticDB is written into ~/.emacs.d/semanticdb/.
   3047 
   3048 #+begin_src emacs-lisp :tangle no
   3049   (use-package semantic
   3050     :bind (:map semantic-mode-map
   3051                 ("C-c , ." . semantic-ia-show-summary))
   3052     :custom
   3053     (semantic-default-submodes '(global-semantic-idle-scheduler-mode ; reparse buffer when idle
   3054                                  global-semanticdb-minor-mode ; maintain database
   3055                                  global-semantic-idle-summary-mode  ; show information (e.g. types) about tag at point
   3056                                  global-semantic-stickyfunc-mode))) ; show current func in header line
   3057 
   3058 
   3059 #+end_src
   3060 
   3061 ** Bib(la)tex
   3062 #+begin_src emacs-lisp
   3063   (use-package bibtex
   3064     :config
   3065     (bibtex-set-dialect "biblatex"))
   3066 #+end_src
   3067 
   3068 ** Python
   3069 In Python, I want to enable flycheck and semantic mode:
   3070 
   3071 #+begin_src emacs-lisp
   3072   (add-hook 'python-mode-hook #'flycheck-mode)
   3073   ;;(add-hook 'python-mode-hook #'semantic-mode)
   3074 #+end_src
   3075 
   3076 ** Elisp
   3077 #+begin_src emacs-lisp
   3078   (use-package emacs-lisp
   3079     :ensure nil ; preinstalled
   3080     :hook ((emacs-lisp-mode . flycheck-mode)
   3081            (emacs-lisp-mode . rainbow-mode)
   3082            (emacs-lisp-mode . outline-minor-mode)
   3083            (emacs-lisp-mode . company-mode)))
   3084 #+end_src
   3085 ** lean-mode
   3086 Specifically for the Lean prover.
   3087 I also install company-lean and helm-lean, which are suggested on the [[https://github.com/leanprover/lean-mode][Github page]].
   3088 Then I map company-complete only for lean-mode.
   3089 
   3090 #+begin_src emacs-lisp
   3091   (use-package lean-mode
   3092     :config
   3093     (use-package company-lean)
   3094     :bind (:map lean-mode-map
   3095                 ("S-SPC" . company-complete)))
   3096 #+end_src
   3097 
   3098 ** sh-mode
   3099 #+begin_src emacs-lisp :results value
   3100   (use-package sh-script
   3101     :hook (sh-mode . flycheck-mode))
   3102 #+end_src
   3103 
   3104 ** anki-editor
   3105 Some extra keybindings that are not set up by default.
   3106 anki-editor doesn't provide a keymap so I have to set one up here:
   3107 
   3108 #+begin_src emacs-lisp
   3109   (use-package anki-editor
   3110     :init
   3111     (defvar anki-editor-mode-map (make-sparse-keymap))
   3112     (add-to-list 'minor-mode-map-alist (cons 'anki-editor-mode
   3113                                              anki-editor-mode-map))
   3114     :custom
   3115     (anki-editor-use-math-jax t)
   3116 
   3117     :bind (:map anki-editor-mode-map
   3118                 ("C-c t" . org-property-next-allowed-value)
   3119                 ("C-c i" . anki-editor-insert-note)
   3120                 ("C-c p" . anki-editor-push-notes)
   3121                 ("C-c c" . anki-editor-cloze-dwim)))
   3122 #+end_src
   3123 ** pdf-tools
   3124 A better replacement for DocView:
   3125 
   3126 #+begin_src emacs-lisp
   3127   (use-package pdf-tools
   3128     :init
   3129     (pdf-tools-install)
   3130 
   3131     :custom
   3132     (pdf-annot-default-annotation-properties '((t
   3133                                                 (label . "Alex Balgavy"))
   3134                                                (text
   3135                                                 (icon . "Note")
   3136                                                 (color . "#0088ff"))
   3137                                                (highlight
   3138                                                 (color . "yellow"))
   3139                                                (squiggly
   3140                                                 (color . "orange"))
   3141                                                (strike-out
   3142                                                 (color . "red"))
   3143                                                (underline
   3144                                                 (color . "blue"))))
   3145     :bind (:map pdf-isearch-minor-mode-map
   3146                 ("C-s" . isearch-forward)
   3147                 :map pdf-view-mode-map
   3148                 ;; Save position & jump back
   3149                 ("C-SPC" . (lambda () (interactive) (message "Position saved") (pdf-view-position-to-register ?x)))
   3150                 ("C-u C-SPC" . (lambda () (interactive) (pdf-view-jump-to-register ?x))))
   3151     :hook
   3152     (pdf-annot-list-mode . pdf-annot-list-follow-minor-mode)
   3153     (pdf-annot-edit-contents-minor-mode . org-mode)
   3154     (pdf-view-mode . (lambda () (display-line-numbers-mode 0)))
   3155 
   3156     :config
   3157     ;; The arrow tooltip does not show properly when jumping to a
   3158     ;; location. Maybe this is a Mac-only thing. See here:
   3159     ;; https://github.com/politza/pdf-tools/issues/145
   3160     ;; This ~:override~ advice fixes it, color is customized via ~tooltip~ face
   3161     (advice-add #'pdf-util-tooltip-arrow :override #'za/pdf-util-tooltip-arrow)
   3162     (defun za/pdf-util-tooltip-arrow (image-top &optional timeout)
   3163       "Fix up `pdf-util-tooltip-arrow`, the original doesn't show the arrow."
   3164       (pdf-util-assert-pdf-window)
   3165       (when (floatp image-top)
   3166         (setq image-top
   3167               (round (* image-top (cdr (pdf-view-image-size))))))
   3168       (let* (x-gtk-use-system-tooltips ;allow for display property in tooltip
   3169              (dx (+ (or (car (window-margins)) 0)
   3170                     (car (window-fringes))))
   3171              (dy image-top)
   3172              (pos (list dx dy dx (+ dy (* 2 (frame-char-height)))))
   3173              (vscroll
   3174               (pdf-util-required-vscroll pos))
   3175              (tooltip-frame-parameters
   3176               `((border-width . 0)
   3177                 (internal-border-width . 0)
   3178                 ,@tooltip-frame-parameters))
   3179              (tooltip-hide-delay (or timeout 3)))
   3180         (when vscroll
   3181           (image-set-window-vscroll vscroll))
   3182         (setq dy (max 0 (- dy
   3183                            (cdr (pdf-view-image-offset))
   3184                            (window-vscroll nil t)
   3185                            (frame-char-height))))
   3186         (when (overlay-get (pdf-view-current-overlay) 'before-string)
   3187           (let* ((e (window-inside-pixel-edges))
   3188                  (xw (pdf-util-with-edges (e) e-width)))
   3189             (cl-incf dx (/ (- xw (car (pdf-view-image-size t))) 2))))
   3190         (pdf-util-tooltip-in-window "\u2192" dx dy))))
   3191 #+end_src
   3192 
   3193 *** TODO this clobbers register x. Find a way to not clobber a register
   3194 ** virtualenvwrapper
   3195 Like virtualenvwrapper.sh, but for Emacs.
   3196 
   3197 #+begin_src emacs-lisp
   3198   (use-package virtualenvwrapper
   3199     :custom
   3200     (venv-location "~/.config/virtualenvs")
   3201 
   3202     :config
   3203     (venv-initialize-interactive-shells)
   3204     (venv-initialize-eshell))
   3205 #+end_src
   3206 
   3207 ** ledger
   3208 #+begin_src emacs-lisp
   3209   (use-package ledger-mode
   3210     :mode ("\\.ledger\\'")
   3211     :hook (ledger-mode . company-mode)
   3212     :custom
   3213     (ledger-clear-whole-transactions t)
   3214     (ledger-reconcile-default-commodity "eur")
   3215     (ledger-reports
   3216      '(("unreconciled" "%(binary) [[ledger-mode-flags]] -f %(ledger-file) --start-of-week=1 reg --uncleared")
   3217        ("net-worth-changes" "%(binary) [[ledger-mode-flags]] -f %(ledger-file) reg ^Assets ^Liabilities -R -M -X eur --effective -n")
   3218        ("budget-last-month" "%(binary) -f %(ledger-file) --start-of-week=1 --effective -X eur --period \"last month\" budget ^expenses:budgeted")
   3219        ("budget-this-month" "%(binary) -f %(ledger-file) --start-of-week=1 --effective -X eur --period \"this month\" budget ^expenses:budgeted")
   3220        ("expenses-this-month-vs-budget" "%(binary) -f %(ledger-file) --start-of-week=1 --effective --period \"this month\" --period-sort \"(amount)\" bal ^expenses:budgeted --budget -R")
   3221        ("expenses-last-month-vs-budget" "%(binary) -f %(ledger-file) --start-of-week=1 --effective --period \"last month\" --period-sort \"(amount)\" bal ^expenses:budgeted --budget -R")
   3222        ("expenses-last-month" "%(binary) -f %(ledger-file) --start-of-week=1 --effective --period \"last month\" --period-sort \"(amount)\" bal ^expenses -X eur -R")
   3223        ("expenses-this-month" "%(binary) -f %(ledger-file) --start-of-week=1 --effective --period \"this month\" --period-sort \"(amount)\" bal ^expenses -X eur -R")
   3224        ("expenses-vs-income-this-month" "%(binary) -f %(ledger-file) --start-of-week=1 --effective --period \"this month\" --period-sort \"(amount)\" bal ^income ^expenses -X eur -R")
   3225        ("expenses-vs-income-last-month" "%(binary) -f %(ledger-file) --start-of-week=1 --effective --period \"last month\" --period-sort \"(amount)\" bal ^expenses ^income -X eur -R")
   3226        ("bal-assets-czk" "%(binary) -f %(ledger-file) --start-of-week=1 bal Assets Liabilities -X czk -R")
   3227        ("bal-assets" "%(binary) -f %(ledger-file) --start-of-week=1 bal Assets Liabilities -R")
   3228        ("bal" "%(binary) -f %(ledger-file) --start-of-week=1 bal -B -R")
   3229        ("bal-assets-eur" "%(binary) -f %(ledger-file) --start-of-week=1 bal Assets Liabilities -X eur -R")
   3230        ("monthly-balance-abn-checking" "%(binary) -f %(ledger-file) --start-of-week=1 --effective reg --monthly 'Assets:ABN Checking' -R")
   3231        ("monthly-expenses" "%(binary) -f %(ledger-file) --monthly register ^expenses --effective --collapse -X eur -R")
   3232        ("reg" "%(binary) -f %(ledger-file) --start-of-week=1 reg -R")
   3233        ("payee" "%(binary) -f %(ledger-file) --start-of-week=1 reg @%(payee) -R")
   3234        ("account" "%(binary) -f %(ledger-file) --start-of-week=1 reg %(account) -R")
   3235        ("reg-org-table" "%(binary) -f %(ledger-file) csv --csv-format '|%(scrub(date))|%(scrub(display_account))|%(scrub(payee))|%(scrub(display_amount))|%(scrub(display_total))|
   3236   ' %(account) -R")))
   3237     :config
   3238     (with-eval-after-load 'ledger-mode
   3239       (setq ledger-amount-regex
   3240             (rx
   3241              (group (or (= 2 " ") ?\t (seq " " ?\t)))
   3242              (zero-or-more (any " " ?\t))
   3243              (opt "=")
   3244              (zero-or-more space)
   3245              (opt "-")
   3246              (opt "(")
   3247              (one-or-more (opt (group
   3248                                 (one-or-more (any "A-Z" "$(_£€₹"))
   3249                                 (zero-or-more blank)))
   3250                           (group (opt "-")
   3251                                  (or (one-or-more (any "0-9"))
   3252                                      (+\? (any "0-9" ",."))))
   3253                           (opt (group (any ",.")
   3254                                       (one-or-more (any "0-9" ")"))))
   3255                           (opt (group (zero-or-more blank)
   3256                                       (one-or-more (any "\"_£€₹" word))))
   3257                           (opt (zero-or-more (any blank))
   3258                                (any "*+/-")
   3259                                (zero-or-more (any blank))))
   3260              (opt ")")
   3261              (opt (group (zero-or-more (any blank))
   3262                          (any "=@{")
   3263                          (opt "@")
   3264                          (+? (not (any ?\xA ";")))))
   3265              (opt (group (or (seq (one-or-more (any blank)) ";" (+\? nonl))
   3266                              (zero-or-more (any blank)))))
   3267              eol))))
   3268 #+end_src
   3269 
   3270 #+RESULTS:
   3271 : ((\.[pP][dD][fF]\' . pdf-view-mode) (/neomutt- . mail-mode) (\.odc\' . archive-mode) (\.odf\' . archive-mode) (\.odi\' . archive-mode) (\.otp\' . archive-mode) (\.odp\' . archive-mode) (\.otg\' . archive-mode) (\.odg\' . archive-mode) (\.ots\' . archive-mode) (\.ods\' . archive-mode) (\.odm\' . archive-mode) (\.ott\' . archive-mode) (\.odt\' . archive-mode) (\.epub\' . nov-mode) (\.[Ss][Aa][Ss]\' . SAS-mode) (\.Sout\' . S-transcript-mode) (\.[Ss]t\' . S-transcript-mode) (\.Rd\' . Rd-mode) (DESCRIPTION\' . conf-colon-mode) (/Makevars\(\.win\)?\' . makefile-mode) (\.[Rr]out\' . ess-r-transcript-mode) (CITATION\' . ess-r-mode) (NAMESPACE\' . ess-r-mode) (\.[rR]profile\' . ess-r-mode) (\.[rR]\' . ess-r-mode) (/R/.*\.q\' . ess-r-mode) (\.[Jj][Aa][Gg]\' . ess-jags-mode) (\.[Bb][Mm][Dd]\' . ess-bugs-mode) (\.[Bb][Oo][Gg]\' . ess-bugs-mode) (\.[Bb][Uu][Gg]\' . ess-bugs-mode) (\.lean$ . lean-mode) (\.ledger\' . ledger-mode) (/git-rebase-todo\' . git-rebase-mode) (\.\(?:md\|markdown\|mkd\|mdown\|mkdn\|mdwn\)\' . markdown-mode) (\.gpg\(~\|\.~[0-9]+~\)?\' nil epa-file) (\.elc\' . elisp-byte-code-mode) (\.zst\' nil jka-compr) (\.dz\' nil jka-compr) (\.xz\' nil jka-compr) (\.lzma\' nil jka-compr) (\.lz\' nil jka-compr) (\.g?z\' nil jka-compr) (\.bz2\' nil jka-compr) (\.Z\' nil jka-compr) (\.vr[hi]?\' . vera-mode) (\(?:\.\(?:rbw?\|ru\|rake\|thor\|jbuilder\|rabl\|gemspec\|podspec\)\|/\(?:Gem\|Rake\|Cap\|Thor\|Puppet\|Berks\|Brew\|Vagrant\|Guard\|Pod\)file\)\' . ruby-mode) (\.re?st\' . rst-mode) (\.py[iw]?\' . python-mode) (\.m\' . octave-maybe-mode) (\.less\' . less-css-mode) (\.scss\' . scss-mode) (\.cs\' . csharp-mode) (\.awk\' . awk-mode) (\.\(u?lpc\|pike\|pmod\(\.in\)?\)\' . pike-mode) (\.idl\' . idl-mode) (\.java\' . java-mode) (\.m\' . objc-mode) (\.ii\' . c++-mode) (\.i\' . c-mode) (\.lex\' . c-mode) (\.y\(acc\)?\' . c-mode) (\.h\' . c-or-c++-mode) (\.c\' . c-mode) (\.\(CC?\|HH?\)\' . c++-mode) (\.[ch]\(pp\|xx\|\+\+\)\' . c++-mode) (\.\(cc\|hh\)\' . c++-mode) (\.\(bat\|cmd\)\' . bat-mode) (\.[sx]?html?\(\.[a-zA-Z_]+\)?\' . mhtml-mode) (\.svgz?\' . image-mode) (\.svgz?\' . xml-mode) (\.x[bp]m\' . image-mode) (\.x[bp]m\' . c-mode) (\.p[bpgn]m\' . image-mode) (\.tiff?\' . image-mode) (\.gif\' . image-mode) (\.png\' . image-mode) (\.jpe?g\' . image-mode) (\.webp\' . image-mode) (\.te?xt\' . text-mode) (\.[tT]e[xX]\' . tex-mode) (\.ins\' . tex-mode) (\.ltx\' . latex-mode) (\.dtx\' . doctex-mode) (\.org\' . org-mode) (\.dir-locals\(?:-2\)?\.el\' . lisp-data-mode) (\.eld\' . lisp-data-mode) (eww-bookmarks\' . lisp-data-mode) (tramp\' . lisp-data-mode) (/archive-contents\' . lisp-data-mode) (places\' . lisp-data-mode) (\.emacs-places\' . lisp-data-mode) (\.el\' . emacs-lisp-mode) (Project\.ede\' . emacs-lisp-mode) (\.\(scm\|sls\|sld\|stk\|ss\|sch\)\' . scheme-mode) (\.l\' . lisp-mode) (\.li?sp\' . lisp-mode) (\.[fF]\' . fortran-mode) (\.for\' . fortran-mode) (\.p\' . pascal-mode) (\.pas\' . pascal-mode) (\.\(dpr\|DPR\)\' . delphi-mode) (\.\([pP]\([Llm]\|erl\|od\)\|al\)\' . perl-mode) (Imakefile\' . makefile-imake-mode) (Makeppfile\(?:\.mk\)?\' . makefile-makepp-mode) (\.makepp\' . makefile-makepp-mode) (\.mk\' . makefile-bsdmake-mode) (\.make\' . makefile-bsdmake-mode) (GNUmakefile\' . makefile-gmake-mode) ([Mm]akefile\' . makefile-bsdmake-mode) (\.am\' . makefile-automake-mode) (\.texinfo\' . texinfo-mode) (\.te?xi\' . texinfo-mode) (\.[sS]\' . asm-mode) (\.asm\' . asm-mode) (\.css\' . css-mode) (\.mixal\' . mixal-mode) (\.gcov\' . compilation-mode) (/\.[a-z0-9-]*gdbinit . gdb-script-mode) (-gdb\.gdb . gdb-script-mode) ([cC]hange\.?[lL]og?\' . change-log-mode) ([cC]hange[lL]og[-.][0-9]+\' . change-log-mode) (\$CHANGE_LOG\$\.TXT . change-log-mode) (\.scm\.[0-9]*\' . scheme-mode) (\.[ckz]?sh\'\|\.shar\'\|/\.z?profile\' . sh-mode) (\.bash\' . sh-mode) (/PKGBUILD\' . sh-mode) (\(/\|\`\)\.\(bash_\(profile\|history\|log\(in\|out\)\)\|z?log\(in\|out\)\)\' . sh-mode) (\(/\|\`\)\.\(shrc\|zshrc\|m?kshrc\|bashrc\|t?cshrc\|esrc\)\' . sh-mode) (\(/\|\`\)\.\([kz]shenv\|xinitrc\|startxrc\|xsession\)\' . sh-mode) (\.m?spec\' . sh-mode) (\.m[mes]\' . nroff-mode) (\.man\' . nroff-mode) (\.sty\' . latex-mode) (\.cl[so]\' . latex-mode) (\.bbl\' . latex-mode) (\.bib\' . bibtex-mode) (\.bst\' . bibtex-style-mode) (\.sql\' . sql-mode) (\(acinclude\|aclocal\|acsite\)\.m4\' . autoconf-mode) (\.m[4c]\' . m4-mode) (\.mf\' . metafont-mode) (\.mp\' . metapost-mode) (\.vhdl?\' . vhdl-mode) (\.article\' . text-mode) (\.letter\' . text-mode) (\.i?tcl\' . tcl-mode) (\.exp\' . tcl-mode) (\.itk\' . tcl-mode) (\.icn\' . icon-mode) (\.sim\' . simula-mode) (\.mss\' . scribe-mode) (\.f9[05]\' . f90-mode) (\.f0[38]\' . f90-mode) (\.indent\.pro\' . fundamental-mode) (\.\(pro\|PRO\)\' . idlwave-mode) (\.srt\' . srecode-template-mode) (\.prolog\' . prolog-mode) (\.tar\' . tar-mode) (\.\(arc\|zip\|lzh\|lha\|zoo\|[jew]ar\|xpi\|rar\|cbr\|7z\|squashfs\|ARC\|ZIP\|LZH\|LHA\|ZOO\|[JEW]AR\|XPI\|RAR\|CBR\|7Z\|SQUASHFS\)\' . archive-mode) (\.oxt\' . archive-mode) (\.\(deb\|[oi]pk\)\' . archive-mode) (\`/tmp/Re . text-mode) (/Message[0-9]*\' . text-mode) (\`/tmp/fol/ . text-mode) (\.oak\' . scheme-mode) (\.sgml?\' . sgml-mode) (\.x[ms]l\' . xml-mode) (\.dbk\' . xml-mode) (\.dtd\' . sgml-mode) (\.ds\(ss\)?l\' . dsssl-mode) (\.js[mx]?\' . javascript-mode) (\.har\' . javascript-mode) (\.json\' . js-json-mode) (\.[ds]?va?h?\' . verilog-mode) (\.by\' . bovine-grammar-mode) (\.wy\' . wisent-grammar-mode) (\.erts\' . erts-mode) ([:/\]\..*\(emacs\|gnus\|viper\)\' . emacs-lisp-mode) (\`\..*emacs\' . emacs-lisp-mode) ([:/]_emacs\' . emacs-lisp-mode) (/crontab\.X*[0-9]+\' . shell-script-mode) (\.ml\' . lisp-mode) (\.ld[si]?\' . ld-script-mode) (ld\.?script\' . ld-script-mode) (\.xs\' . c-mode) (\.x[abdsru]?[cnw]?\' . ld-script-mode) (\.zone\' . dns-mode) (\.soa\' . dns-mode) (\.asd\' . lisp-mode) (\.\(asn\|mib\|smi\)\' . snmp-mode) (\.\(as\|mi\|sm\)2\' . snmpv2-mode) (\.\(diffs?\|patch\|rej\)\' . diff-mode) (\.\(dif\|pat\)\' . diff-mode) (\.[eE]?[pP][sS]\' . ps-mode) (\.\(?:PDF\|EPUB\|CBZ\|FB2\|O?XPS\|DVI\|OD[FGPST]\|DOCX\|XLSX?\|PPTX?\|pdf\|epub\|cbz\|fb2\|o?xps\|djvu\|dvi\|od[fgpst]\|docx\|xlsx?\|pptx?\)\' . doc-view-mode-maybe) (configure\.\(ac\|in\)\' . autoconf-mode) (\.s\(v\|iv\|ieve\)\' . sieve-mode) (BROWSE\' . ebrowse-tree-mode) (\.ebrowse\' . ebrowse-tree-mode) (#\*mail\* . mail-mode) (\.g\' . antlr-mode) (\.mod\' . m2-mode) (\.ses\' . ses-mode) (\.docbook\' . sgml-mode) (\.com\' . dcl-mode) (/config\.\(?:bat\|log\)\' . fundamental-mode) (/\.\(authinfo\|netrc\)\' . authinfo-mode) (\.\(?:[iI][nN][iI]\|[lL][sS][tT]\|[rR][eE][gG]\|[sS][yY][sS]\)\' . conf-mode) (\.la\' . conf-unix-mode) (\.ppd\' . conf-ppd-mode) (java.+\.conf\' . conf-javaprop-mode) (\.properties\(?:\.[a-zA-Z0-9._-]+\)?\' . conf-javaprop-mode) (\.toml\' . conf-toml-mode) (\.desktop\' . conf-desktop-mode) (/\.redshift\.conf\' . conf-windows-mode) (\`/etc/\(?:DIR_COLORS\|ethers\|.?fstab\|.*hosts\|lesskey\|login\.?de\(?:fs\|vperm\)\|magic\|mtab\|pam\.d/.*\|permissions\(?:\.d/.+\)?\|protocols\|rpc\|services\)\' . conf-space-mode) (\`/etc/\(?:acpid?/.+\|aliases\(?:\.d/.+\)?\|default/.+\|group-?\|hosts\..+\|inittab\|ksysguarddrc\|opera6rc\|passwd-?\|shadow-?\|sysconfig/.+\)\' . conf-mode) ([cC]hange[lL]og[-.][-0-9a-z]+\' . change-log-mode) (/\.?\(?:gitconfig\|gnokiirc\|hgrc\|kde.*rc\|mime\.types\|wgetrc\)\' . conf-mode) (/\.mailmap\' . conf-unix-mode) (/\.\(?:asound\|enigma\|fetchmail\|gltron\|gtk\|hxplayer\|mairix\|mbsync\|msmtp\|net\|neverball\|nvidia-settings-\|offlineimap\|qt/.+\|realplayer\|reportbug\|rtorrent\.\|screen\|scummvm\|sversion\|sylpheed/.+\|xmp\)rc\' . conf-mode) (/\.\(?:gdbtkinit\|grip\|mpdconf\|notmuch-config\|orbital/.+txt\|rhosts\|tuxracer/options\)\' . conf-mode) (/\.?X\(?:default\|resource\|re\)s\> . conf-xdefaults-mode) (/X11.+app-defaults/\|\.ad\' . conf-xdefaults-mode) (/X11.+locale/.+/Compose\' . conf-colon-mode) (/X11.+locale/compose\.dir\' . conf-javaprop-mode) (\.~?[0-9]+\.[0-9][-.0-9]*~?\' nil t) (\.\(?:orig\|in\|[bB][aA][kK]\)\' nil t) ([/.]c\(?:on\)?f\(?:i?g\)?\(?:\.[a-zA-Z0-9._-]+\)?\' . conf-mode-maybe) (\.[1-9]\' . nroff-mode) (\.art\' . image-mode) (\.avs\' . image-mode) (\.bmp\' . image-mode) (\.cmyk\' . image-mode) (\.cmyka\' . image-mode) (\.crw\' . image-mode) (\.dcr\' . image-mode) (\.dcx\' . image-mode) (\.dng\' . image-mode) (\.dpx\' . image-mode) (\.fax\' . image-mode) (\.heic\' . image-mode) (\.hrz\' . image-mode) (\.icb\' . image-mode) (\.icc\' . image-mode) (\.icm\' . image-mode) (\.ico\' . image-mode) (\.icon\' . image-mode) (\.jbg\' . image-mode) (\.jbig\' . image-mode) (\.jng\' . image-mode) (\.jnx\' . image-mode) (\.miff\' . image-mode) (\.mng\' . image-mode) (\.mvg\' . image-mode) (\.otb\' . image-mode) (\.p7\' . image-mode) (\.pcx\' . image-mode) (\.pdb\' . image-mode) (\.pfa\' . image-mode) (\.pfb\' . image-mode) (\.picon\' . image-mode) (\.pict\' . image-mode) (\.rgb\' . image-mode) (\.rgba\' . image-mode) (\.tga\' . image-mode) (\.wbmp\' . image-mode) (\.webp\' . image-mode) (\.wmf\' . image-mode) (\.wpg\' . image-mode) (\.xcf\' . image-mode) (\.xmp\' . image-mode) (\.xwd\' . image-mode) (\.yuv\' . image-mode) (\.tgz\' . tar-mode) (\.tbz2?\' . tar-mode) (\.txz\' . tar-mode) (\.tzst\' . tar-mode))
   3272 
   3273 org-capture lets me add transactions from anywhere in Emacs:
   3274 
   3275 Budget throws an error when there's multiple commodities involved.
   3276 See discussion here: https://github.com/ledger/ledger/issues/1450#issuecomment-390067165
   3277 
   3278 #+begin_src emacs-lisp
   3279   (defconst za/ledger-budget-fix-string
   3280     "-X eur -F '%(justify(scrub(get_at(display_total, 0)), 20, -1, true, false)) %(justify(get_at(display_total, 1) ? -scrub(get_at(display_total, 1)) : 0.0, 20,            20 + 1 + 20, true, false)) %(justify(get_at(display_total, 1) ? (get_at(display_total, 0) ?           -(scrub(get_at(display_total, 1) + get_at(display_total, 0))) :           -(scrub(get_at(display_total, 1)))) : -(scrub(get_at(display_total, 0))), 20,            20 + 1 + 20 + 1 + 20, true, false))%(get_at(display_total, 1) and (abs(quantity(scrub(get_at(display_total, 0))) /           quantity(scrub(get_at(display_total, 1)))) >= 1) ?  \" \" : \" \")%(justify((get_at(display_total, 1) ?           (100% * (get_at(display_total, 0) ? scrub(get_at(display_total, 0)) : 0.0)) /              -scrub(get_at(display_total, 1)) : \"na\"),            5, -1, true, false))  %(!options.flat ? depth_spacer : \"\")%-(partial_account(options.flat))\n%/%$2 %$3 %$4 %$6\n%/%(prepend_width ? \" \" * int(prepend_width) : \"\")    ----------------     ----------------     ---------------- -----\n'"
   3281     "Append this to a ledger budget to fix errors with multiple commodities.")
   3282 #+end_src
   3283 
   3284 ** Notmuch
   3285 #+begin_src emacs-lisp
   3286   (use-package notmuch
   3287     :custom
   3288     (notmuch-saved-searches
   3289      `((:name "inbox: personal" :query ,(format "folder:/%s/ tag:inbox" za/email-personal) :key ,(kbd "ip") :search-type 'tree)
   3290        (:name "inbox: school" :query ,(format "folder:/%s/ tag:inbox" za/email-vu) :key ,(kbd "is") :search-type 'tree)
   3291        (:name "archive: personal" :query ,(format "folder:/%s/ tag:archive" za/email-personal) :key ,(kbd "ap") :search-type 'tree)
   3292        (:name "archive: school" :query ,(format "folder:/%s/ tag:archive" za/email-vu) :key ,(kbd "as") :search-type 'tree))
   3293      "Define some saved searches (i.e. mailboxes)")
   3294     (notmuch-hello-sections
   3295      '(notmuch-hello-insert-header
   3296        notmuch-hello-insert-saved-searches
   3297        notmuch-hello-insert-search
   3298        notmuch-hello-insert-alltags
   3299        notmuch-hello-insert-footer)
   3300      "Define the main screen sections")
   3301     (notmuch-search-oldest-first nil "Show newest mail first")
   3302     (notmuch-archive-tags '("-inbox" "+archive"))
   3303     (notmuch-tagging-keys '(("a" notmuch-archive-tags "Archive")
   3304                             ("r" notmuch-show-mark-read-tags "Mark read")
   3305                             ("u" notmuch-show-mark-unread-tags "Mark unread")
   3306                             ("d" notmuch-delete-tags "Delete")))
   3307 
   3308     :bind (("C-c m" . notmuch)
   3309            :map notmuch-show-mode-map
   3310            ("C-c M-y" . shr-copy-url))
   3311     ;; Run notmuch-hook script on hello refresh, to move messages to
   3312     ;; folders according to their tags:
   3313     :hook (notmuch-hello-refresh . za/notmuch-hook-tags2folders)
   3314     :init (setenv "NOTMUCH_CONFIG" "/Users/alex/.config/notmuch/config")
   3315     :config
   3316     (setq notmuch-show-mark-unread-tags '("+unread"))
   3317     (setq notmuch-delete-tags '("-inbox" "+trash"))
   3318     (defun za/notmuch-hook-tags2folders ()
   3319       "Run notmuch-hook to organise email in folders based on tags."
   3320       (start-process "notmuch-hook" nil "notmuch-hook" "--tags2folders")))
   3321 #+end_src
   3322 
   3323 ** MPC
   3324 #+begin_src emacs-lisp
   3325   (use-package mpc
   3326     :custom
   3327     (mpc-browser-tags '(AlbumArtist Album Genre Playlist)
   3328                       "Set the windows I want to show")
   3329 
   3330     :bind (:map mpc-mode-map
   3331                 ("a" . mpc-playlist-add)
   3332                 ("P" . mpc-playlist)
   3333                 ("x" . mpc-playlist-delete)
   3334                 ("p" . mpc-toggle-play)
   3335                 ("t" . mpc-select-toggle)
   3336                 ("f" . za/mpc-seek-forward-20-seconds)
   3337                 ("b" . za/mpc-seek-backward-20-seconds))
   3338     :config
   3339     (defun za/mpc-seek-forward-20-seconds ()
   3340       "Seek forward 20 seconds"
   3341       (interactive)
   3342       (mpc-seek-current "+20"))
   3343 
   3344     (defun za/mpc-seek-backward-20-seconds ()
   3345       "Seek backward 20 seconds"
   3346       (interactive)
   3347       (mpc-seek-current "-20")))
   3348 #+end_src
   3349 ** Dired
   3350 #+begin_src emacs-lisp
   3351   (use-package dired
   3352     :ensure nil ; installed with Emacs
   3353     :bind (:map dired-mode-map
   3354                 ;; 'i' expands subdirs, so I want to be able to close them too.
   3355                 ("M-k" . dired-kill-subdir))
   3356     :custom
   3357     (dired-listing-switches "-alhv")
   3358     (dired-dwim-target t "If I have another dired window open, use that as target")
   3359     ;; By default, hide details (show again by pressing oparen):
   3360     :hook (dired-mode . dired-hide-details-mode))
   3361 #+end_src
   3362 
   3363 ** ess: statistics (R, SAS...)
   3364 #+begin_src emacs-lisp
   3365   (use-package ess)
   3366 #+end_src
   3367 
   3368 ** help mode
   3369 #+begin_src emacs-lisp
   3370   (use-package help-mode
   3371     :ensure nil ; included with Emacs
   3372     :hook (help-mode . za/settings-on-help-mode)
   3373     :config
   3374     (defun za/settings-on-help-mode ()
   3375       "Settings on enabling help mode"
   3376       (za/toggle-wrap t)))
   3377 #+end_src
   3378 ** helpful
   3379 An alternative to the built-in Emacs help that provides much more contextual information.
   3380 I use counsel, so I use the keybindings in [[*counsel + ivy + swiper]].
   3381 I just augment the functions counsel uses.
   3382 Also, counsel doesn't provide some keybindings that I can get from helpful.
   3383 
   3384 #+begin_src emacs-lisp
   3385   (use-package helpful
   3386     :custom
   3387     (counsel-describe-symbol-function #'helpful-symbol)
   3388     (counsel-describe-function-function #'helpful-callable)
   3389     (counsel-describe-variable-function #'helpful-variable)
   3390 
   3391     :bind (("C-h k" . helpful-key)
   3392            ("C-h C" . helpful-command)
   3393            :map helpful-mode-map
   3394            ("l" . za/helpful-previous)
   3395            ("r" . za/helpful-next))
   3396 
   3397     :hook (helpful-mode . za/settings-on-helpful-mode)
   3398     :config
   3399 
   3400     (defun za/settings-on-helpful-mode ()
   3401       "Settings on enabling helpful mode"
   3402       (za/toggle-wrap t))
   3403 
   3404     ;; Then, a way to jump forward and backward in the window:
   3405     (defvar za/helpful-buffer-ring-size 20
   3406       "How many buffers are stored for use with `helpful-next'.")
   3407 
   3408     (defvar za/helpful--buffer-ring (make-ring za/helpful-buffer-ring-size)
   3409       "Ring that stores the current Helpful buffer history.")
   3410 
   3411     (defun za/helpful--buffer-index (&optional buffer)
   3412       "If BUFFER is a Helpful buffer, return it’s index in the buffer ring."
   3413       (let ((buf (or buffer (current-buffer))))
   3414         (and (eq (buffer-local-value 'major-mode buf) 'helpful-mode)
   3415              (seq-position (ring-elements za/helpful--buffer-ring) buf #'eq))))
   3416 
   3417     (defun za/helpful--new-buffer-a (help-buf)
   3418       "Update the buffer ring according to the current buffer and HELP-BUF."
   3419       :filter-return #'helpful--buffer
   3420       (let ((buf-ring za/helpful--buffer-ring))
   3421         (let ((newer-buffers (or (za/helpful--buffer-index) 0)))
   3422           (dotimes (_ newer-buffers) (ring-remove buf-ring 0)))
   3423         (when (/= (ring-size buf-ring) za/helpful-buffer-ring-size)
   3424           (ring-resize buf-ring za/helpful-buffer-ring-size))
   3425         (ring-insert buf-ring help-buf)))
   3426 
   3427     (advice-add #'helpful--buffer :filter-return #'za/helpful--new-buffer-a)
   3428 
   3429     (defun za/helpful--next (&optional buffer)
   3430       "Return the next live Helpful buffer relative to BUFFER."
   3431       (let ((buf-ring za/helpful--buffer-ring)
   3432             (index (or (za/helpful--buffer-index buffer) -1)))
   3433         (cl-block nil
   3434           (while (> index 0)
   3435             (cl-decf index)
   3436             (let ((buf (ring-ref buf-ring index)))
   3437               (if (buffer-live-p buf) (cl-return buf)))
   3438             (ring-remove buf-ring index)))))
   3439 
   3440 
   3441     (defun za/helpful--previous (&optional buffer)
   3442       "Return the previous live Helpful buffer relative to BUFFER."
   3443       (let ((buf-ring za/helpful--buffer-ring)
   3444             (index (1+ (or (za/helpful--buffer-index buffer) -1))))
   3445         (cl-block nil
   3446           (while (< index (ring-length buf-ring))
   3447             (let ((buf (ring-ref buf-ring index)))
   3448               (if (buffer-live-p buf) (cl-return buf)))
   3449             (ring-remove buf-ring index)))))
   3450 
   3451     (defun za/helpful-next ()
   3452       "Go to the next Helpful buffer."
   3453       (interactive)
   3454       (when-let (buf (za/helpful--next))
   3455         (funcall helpful-switch-buffer-function buf)))
   3456 
   3457     (defun za/helpful-previous ()
   3458       "Go to the previous Helpful buffer."
   3459       (interactive)
   3460       (when-let (buf (za/helpful--previous))
   3461         (funcall helpful-switch-buffer-function buf))))
   3462 #+end_src
   3463 ** Tex-mode
   3464 #+begin_src emacs-lisp
   3465   (use-package tex-mode
   3466     :ensure nil ; installed with Emacs
   3467     :hook (tex-mode . za/settings-on-tex-mode)
   3468     :config
   3469     (defun za/settings-on-tex-mode ()
   3470       "Settings on enabling helpful mode"
   3471       (setq comment-add 0)))
   3472 #+end_src
   3473 ** Quail
   3474 #+begin_src emacs-lisp
   3475   (use-package quail
   3476     :ensure nil) ; provided by Emacs
   3477 #+end_src
   3478 ** Markdown
   3479 #+begin_src emacs-lisp
   3480   (use-package markdown-mode)
   3481 #+end_src
   3482 ** vdirel (contacts)
   3483 #+begin_src emacs-lisp
   3484   (use-package vdirel
   3485     :config
   3486     (vdirel-switch-repository "~/.local/share/contacts/default"))
   3487 #+end_src
   3488 ** Yaml
   3489 #+begin_src emacs-lisp
   3490   (use-package yaml-mode
   3491     :commands yaml-mode
   3492     :init
   3493     (add-hook 'yaml-mode-hook
   3494               (lambda ()
   3495                 (setq-local outline-regexp (rx (* blank)))
   3496                 (outline-minor-mode))))
   3497 #+end_src
   3498 ** calc
   3499 #+begin_src emacs-lisp
   3500   (use-package calc
   3501     :config
   3502     (setq math-additional-units
   3503      ;; elements:
   3504      ;; - symbol identifying the unit,
   3505      ;; - expression indicatingv alue of unit or nil for fundamental units
   3506      ;; - textual description
   3507      '((b nil "Bit")
   3508        (B "b * 8" "Bytes")
   3509        (KiB "1024 * B" "Kibibyte")
   3510        (MiB "1024 * KiB" "Mebibyte")
   3511        (GiB "1024 * MiB" "Gibibyte")
   3512        (TiB "1024 * GiB" "Tebibyte")
   3513        (PiB "1024 * TiB" "Pebibyte")
   3514        (EiB "1024 * PiB" "Exbibyte")
   3515        (ZiB "1024 * EiB" "Zebibyte")
   3516        (YiB "1024 * ZiB" "Yobibyte")
   3517        (KB "1000 * B" "Kilobyte")
   3518        (MB "1000 * KB" "Megabyte")
   3519        (GB "1000 * MB" "Gigabyte")
   3520        (TB "1000 * GB" "Terabyte")
   3521        (PB "1000 * TB" "Petabyte")
   3522        (EB "1000 * PB" "Exabyte")
   3523        (ZB "1000 * EB" "Zettabyte")
   3524        (YB "1000 * ZB" "Yottabyte")
   3525        (Kib "1024 * b" "Kibibit")
   3526        (Mib "1024 * Kib" "Mebibit")
   3527        (Gib "1024 * Mib" "Gibibit")
   3528        (Kb "1000 * b" "Kilobit")
   3529        (Mb "1000 * Kb" "Megabit")
   3530        (Gb "1000 * Mb" "Gigabit")))
   3531     (setq math-units-table nil))
   3532 #+end_src
   3533 ** casual
   3534 #+begin_src emacs-lisp
   3535   (use-package casual
   3536     :bind (:map calc-mode-map
   3537            ("C-o" . 'casual-calc-tmenu)
   3538            :map dired-mode-map
   3539            ("C-o" . 'casual-dired-tmenu)
   3540            ("C-u C-o" . 'dired-display-file)
   3541            :map calendar-mode-map
   3542            ("C-o" . 'casual-calendar-tmenu)))
   3543 #+end_src
   3544 ** json
   3545 #+begin_src emacs-lisp
   3546   (use-package json-mode)
   3547 #+end_src
   3548 ** rust
   3549 #+begin_src emacs-lisp
   3550   (use-package rust-mode)
   3551 #+end_src
   3552 ** artist mode
   3553 #+begin_src emacs-lisp
   3554   (use-package artist
   3555     :ensure nil ; included with emacs
   3556     :custom (artist-figlet-default-font "term"))
   3557 #+end_src
   3558 * Override some faces
   3559 #+begin_src emacs-lisp
   3560   (with-eval-after-load 'org-faces
   3561     (set-face-attribute 'org-table nil :inherit 'fixed-pitch)
   3562     (set-face-attribute 'org-block nil :inherit 'fixed-pitch))
   3563 #+end_src
   3564 * Shortdoc
   3565 Set a better keybinding (I'm never gonna use ~view-hello-file~ anyways):
   3566 
   3567 #+begin_src emacs-lisp
   3568   (bind-key "C-h h" #'shortdoc-display-group)
   3569 #+end_src
   3570 * Upcoming new features
   3571 In a new version of use-package, I can use the :vc keyword, so check for when that's available.
   3572 See [[https://git.savannah.gnu.org/cgit/emacs.git/commit/?id=2ce279680bf9c1964e98e2aa48a03d6675c386fe][commit]] and [[https://tony-zorman.com/posts/use-package-vc.html][article]].
   3573 
   3574 #+begin_src emacs-lisp
   3575   (when (fboundp 'use-package-vc-install)
   3576     (user-error "use-package :vc keyword now available!"))
   3577 #+end_src
   3578 * References
   3579 Here's a list of good articles I encountered about configging emacs:
   3580 - [[https://karthinks.com/software/batteries-included-with-emacs/][Batteries included with Emacs]]
   3581 - [[https://karthinks.com/software/more-batteries-included-with-emacs/][More batteries included with emacs]]
   3582 
   3583 For Org mode, [[https://www.youtube.com/playlist?list=PLVtKhBrRV_ZkPnBtt_TD1Cs9PJlU0IIdE][Rainer König's tutorials]] are the best.
   3584 [[https://emacs.cafe/emacs/orgmode/gtd/2017/06/30/orgmode-gtd.html][Here's a good reference for setting up gtd in org mode]]