dotfiles

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

config.org (147655B)


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