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