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