dotfiles

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

config.org (144716B)


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