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