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