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