dotfiles

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

config.org (145236B)


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