;;; init-org.el --- org-mode customizations -*- lexical-binding: t -*- ;; Author: Andrew Scott ;; Keywords: org, convenience, tools ;; URL: https://codeberg.org/andyscott/dotfiles ;; This file is not part of GNU Emacs ;; Copyright (c) 2024 Andrew Scott ;; MIT No Attribution ;; Permission is hereby granted, free of charge, to any person obtaining a copy of this ;; software and associated documentation files (the "Software"), to deal in the Software ;; without restriction, including without limitation the rights to use, copy, modify, ;; merge, publish, distribute, sublicense, and/or sell copies of the Software, and to ;; permit persons to whom the Software is furnished to do so. ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, ;; INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A ;; PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT ;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ;; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE ;; SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ;;; Commentary: ;; My org-mode customizations. ;;; Code: ;;;; Org ;;;;; Org Mode (use-feature org :custom ;;;;;; `org.el' (org-directory (expand-file-name "~/Nextcloud/Documents/org")) (org-adapt-indentation nil "No, see `org-indent-mode'") (org-agenda-files (append (directory-files org-directory t "\\`[^.].*\\.org\\'" t nil) (directory-files (expand-file-name "projects" org-directory) t "\\`[^.].*\\.org\\'" t nil))) (org-archive-location (expand-file-name "archive/%s_archive::" org-directory) "Set location and format for archives") (org-blank-before-new-entry '((heading . t) (plain-list-item . auto)) "Heading: always, List: maybe") (org-deadline-warning-days 5 "Decrease warning time") (org-default-notes-file (expand-file-name "inbox.org" org-directory) "Default file for `org-capture'") ;; FIXME: `org-superstar' doesn't like this? ;; (org-ellipsis " >> " "Until I can stop using ... myself...") (org-enforce-todo-checkbox-dependencies t "Use checkboxes as blockers") (org-hide-emphasis-markers t "Hide markers for italics/bold etm.") (org-insert-heading-respect-content t "Add headlines to end of subtree") (org-log-done 'time "Add timestamp when item completed") (org-log-into-drawer t "Hide logs in a drawer") (org-log-redeadline 'note "Require a note if deadline changes") ;; (org-M-RET-may-split-line nil "Don't split when adding headings/items") (org-pretty-entities t "Use UTF-8 for entities") (org-pretty-entities-include-sub-superscripts nil "Don't mess with underscores") (org-reverse-note-order t "Add/refile at the beginning") (org-special-ctrl-a/e t "Once: Move point around headline text, Twice: move point normally") ;; (org-special-ctrl-k t "Change behavior of `kill-line' depending on position") (org-startup-folded 'show2levels "Show two headings only") (org-startup-indented t "Turn on `org-indent-mode' automatically") (org-startup-numerated t "Turn on `org-num-mode' automatically") (org-tags-column 0 "Put tags right after headline") ;; (org-tags-exclude-from-inheritance '("projects" ;; "JobApplications" ;; "Hybrid" ;; "Remote" ;; "InOffice") "Don't tag every headline") (org-todo-keywords '((sequence "TODO(t)" "NEXT(x)" "PROG(p)" "WAIT(w@/!)" "|" "DONE(d)" "WONT(n@/!)") (sequence "IDEA(i)" "|")) "Add NEXT, PROG, WAIT, WONT, IDEA") (org-todo-repeat-to-state "TODO" "Reset to TODO regardless of previous state") ;;;;;; `org-agenda.el' (org-agenda-block-separator ?- "Use simple hyphens") (org-agenda-skip-deadline-if-done t "Don't show deadline when already done") ;; (org-agenda-skip-scheduled-if-deadline-is-shown nil "Show deadlines and schedules") (org-agenda-skip-scheduled-if-done t "Don't show scheduled when already done") (org-agenda-start-on-weekday nil "Start with today") (org-agenda-start-with-log-mode '(clock closed state) "Show all logged items") (org-agenda-tags-column 0 "Put tags right after headline") ;; REVIEW: Is this useful data to keep? ;; :PROPERTIES: ;; :CREATED: %U ;; :END: ;;;;;; `org-capture.el' (org-capture-templates `(("i" "Inbox") ("in" "Note" entry (file+headline "inbox.org" "Notes") "** %?" :empty-lines 1) ("it" "Task" entry (file+headline "inbox.org" "Tasks") "** TODO [#B] %? %i\n%a" :empty-lines 1) ("d" "Denote" plain (file denote-last-path) #'denote-org-capture :no-save t :immediate-finish nil :kill-buffer t :jump-to-captured t) ("j" "Job Application" entry (file+olp "projects/jobs.org" "Applications") "*** TODO [#A] %^{Job Title} %^{Company} %?\nSCHEDULED: %t\n **** Find Contacts at %\\2\nSCHEDULED: %t\n" :clock-in t :clock-keep t :jump-to-captured t :empty-lines 1))) ;;;;;; `org-clock.el' (org-clock-in-resume t "Resume if clock already started") (org-clock-in-switch-to-state "PROG" "Switch items to In-Progress") (org-clock-persist t "Save clock if Emacs is closed") (org-clock-rounding-minutes 5 "Round to 5 minutes") (org-clock-string-limit 30 "Don't crowd the mode line") ;;;;;; `org-faces.el' - Custom Settings (org-fontify-quote-and-verse-blocks t "Make #+begin_quote etm. look nicer") (org-todo-keyword-faces '(("TODO" . (:inherit dired-mark :inverse-video t)) ("PROG" . (:inherit match :inverse-video t)) ("NEXT" . (:inherit dired-special :inverse-video t)) ("WAIT" . (:inherit shadow :inverse-video t)) ("WONT" . (:inherit org-done)) ("DONE" . (:inherit org-done)) ("IDEA" . (:inherit bold :inverse-video t)))) (org-priority-faces '((?A . (:inherit error :weight semibold :inverse-video t)) (?B . (:inherit warning :weight semibold :inverse-video t)) (?C . (:inherit mode-line-inactive :weight semibold :inverse-video t)))) ;;;;;; `org-fold.el' (org-fold-catch-invisible-edits 'show-and-error "Abort edit, show point & throw error") ;;;;;; `org-goto.el' (org-goto-interface 'outline-path-completion "Use completion interface") ;;;;;; `org-id.el' (org-id-link-to-org-use-id 'create-if-interactive "Only if I say so") (org-id-locations-file (locate-user-emacs-file "org-id-locations") "Don't use a hidden file") (org-id-method 'ts "Timestamps instead of UUID") (org-id-ts-format "%Y%m%dT%H%M%S%z" "True ISO 8601 timestamp") ;;;;;; `org-keys.el' (org-mouse-1-follows-link 'double "Otherwise timestamps automatically open agenda") (org-return-follows-link t "Follow links on RET") (org-use-extra-keys t "Graphical users like useful keybinds too") ;;;;;; `org-list.el' (org-list-allow-alphabetical t "A. a. B. b. C. c. ...") (org-list-demote-modify-bullet '(("+" . "-") ("-" . "+") ("*" . "+")) "Alternating bullets") ;;;;;; `org-num.el' (org-num-face 'org-level-6 "Use consistent face for num-mode") ;;;;;; `org-refile.el' (org-outline-path-complete-in-steps nil "Not needed with completion") (org-refile-allow-creating-parent-nodes 'confirm "Ask before creating new nodes") (org-refile-targets '((nil :maxlevel . 3) (org-agenda-files :maxlevel . 2)) "Current file: 3 levels, Agenda files: 2 levels") (org-refile-use-outline-path 'file "Include filename in path") ;;;;;; `org-src.el' (org-edit-src-content-indentation 0 "Don't indent after #+begin") :custom-face ;;;;;; `org-faces.el' - Custom Faces ;; Headlines (org-level-1 ((t (:inherit outline-1 :height 1.3)))) (org-level-2 ((t (:inherit outline-2 :height 1.2)))) (org-level-3 ((t (:inherit outline-3 :height 1.1)))) ;; (org-level-4 ((t (:inherit outline-4 :height 1.2)))) ;; (org-level-5 ((t (:inherit outline-5 :height 1.1)))) ;; Ensure monospace Keywords/Tags/Blocks (org-block ((t (:inherit fixed-pitch)))) ; inherited by `org-code' (org-block-begin-line ((t (:inherit fixed-pitch)))) (org-block-end-line ((t (:inherit (org-block-begin-line fixed-pitch))))) (org-checkbox ((t (:inherit (org-todo fixed-pitch))))) (org-checkbox-statistics-todo ((t (:inherit (org-todo fixed-pitch))))) (org-date ((t (:inherit fixed-pitch)))) (org-document-info-keyword ((t (:inherit (shadow fixed-pitch))))) (org-drawer ((t (:inherit fixed-pitch)))) (org-indent ((t (:inherit (org-hide fixed-pitch))))) (org-link ((t (:foreground "dark cyan" :underline t)))) (org-list-dt ((t (:inherit fixed-pitch)))) (org-meta-line ((t (:inherit (fixed-pitch))))) (org-property-value ((t (:inherit fixed-pitch)))) (org-special-keyword ((t (:inherit fixed-pitch)))) (org-table ((t (:inherit fixed-pitch)))) (org-tag ((t (:inherit (fixed-pitch) :height 0.8)))) (org-verbatim ((t (:inherit fixed-pitch)))) :bind (:prefix-map as/org-prefix-map :prefix "C-c o") (:map as/org-prefix-map ("a" . org-agenda) ; `org-agenda.el' ("c" . org-capture) ; `org-capture.el' ("g" . org-clock-goto) ; `org-clock.el' ("H" . consult-org-agenda) ; `consult-org.el' ("l" . org-store-link) ; `ol.el' ("m" . org-tags-view)) ; `org-agenda.el' (:map org-mode-map ("C-c o c" . as/org-set-created-property) ("C-c o d" . org-priority-down) ("C-c o h" . consult-org-heading) ; `consult-org.el' ("C-c o l" . embark-org-copy-link-target) ; `embark-org.el' ("C-c o L" . org-toggle-link-display) ; `ol.el' ("C-c o s" . org-insert-subheading) ("C-c o u" . org-priority-up) ("C-c o y" . org-todo-yesterday)) :demand t :hook (org-mode . (lambda () (setq-local fill-column 100) (variable-pitch-mode) (visual-line-fill-column-mode))) :config (defun as/org-set-created-property () "Add a CREATED property to org headlines with the current time" (interactive) (org-entry-put nil "CREATED" (format-time-string (org-time-stamp-format t t nil) (current-time))))) ;;;;; Appearance ;;;;;; Org Appear (use-package org-appear :custom (org-appear-autoentities t "Toggle markers automatically") (org-appear-autolinks 'just-brackets "Only show brackets, not target") (org-appear-autosubmarkers t "Toggle sub/superscript markers automatically") (org-appear-trigger 'on-change "Only auto-toggle if changed or selected") :hook (org-mode . org-appear-mode)) ;;;;;; Org Superstar (use-package org-superstar :custom (org-superstar-headline-bullets-list '(?◉ ?🞛 ?○ ?▷ ?✸ ?✿) "Customize bullets") (org-indent-mode-turns-on-hiding-stars nil "Required by `org-superstar-leading-bullet'") (org-superstar-leading-bullet ?\s "Replace leading stars with 1 whitespace char") (org-superstar-hide-leading-stars t "Enable hiding") (org-superstar-special-todo-items t "Replace todo checkboxes") (org-superstar-todo-bullet-alist '(("TODO" . ?☐) ("NEXT" . ?☐) ("PROG" . ?☐) ("WAIT" . ?☐) ("DONE" . ?☑) ("WONT" . ?☑)) "Customize todo checkboxes") :hook (org-mode . org-superstar-mode)) ;;;;; Agenda ;;;;;; Org Agenda (use-feature org-agenda :after (org) :bind (:map org-agenda-mode-map ("C-c C-r" . as/org-agenda-reconcile-inbox-item) ("C-c C-y" . org-agenda-todo-yesterday) ("C-c o p" . org-pomodoro)) :config (defun as/org-agenda-reconcile-inbox-item () "Process and refile an org-agenda item" (interactive) (org-with-wide-buffer (org-agenda-set-tags) (org-agenda-priority) (if (y-or-n-p "Schedule item?") (org-agenda-schedule nil nil)) (org-agenda-refile nil nil t)))) ;;;;;; Org Super Agenda (use-package org-super-agenda :hook (org-agenda-mode . org-super-agenda-mode) :init (setq org-agenda-custom-commands '(("u" "Super agenda" ((agenda "" ((org-agenda-prefix-format " %-12t% s%?-6e") ;;(org-agenda-prefix-format " %?-12t %s") (org-agenda-remove-tags t) (org-agenda-skip-function '(org-agenda-skip-entry-if 'todo '("WONT"))) (org-agenda-span 'day))) (alltodo "" ((org-agenda-overriding-header "\nI'M DOING IT...") (org-agenda-prefix-format " %-20T") ;; (org-agenda-prefix-format " %?-12t %s") (org-agenda-remove-tags t) (org-super-agenda-groups '( (:name "Done Today" ; FIXME :todo "DONE" :log t :order 95) (:name "Overdue" :deadline past :scheduled past :order 1) (:name "Waiting" :todo "WAIT" :order 80) (:name "Upcoming" :deadline future :scheduled future :order 99) (:name "High Priority" :priority "A" :order 5) (:name "Now" :todo ("PROG" "NEXT") :order 10) (:name "Today's Personal Tasks" :and (:deadline today :tag "personal") :and (:scheduled today :tag "personal") :order 20) (:name "Today's Household Tasks" :and (:deadline today :tag "household") :and (:scheduled today :tag "household") :order 30) (:name "Today's Projects" :deadline today :scheduled today :order 15) (:name "Research" :tag "research" :order 45) (:name "Personal Backlog" :and (:todo "TODO" :tag "personal") :and (:todo "TODO" :tag "job_search") :order 40) (:name "Household Backlog" :and (:todo "TODO" :tag "household") :order 50) (:name "Project Backlog" :todo "TODO" :order 35) (:name "Inbox" :tag "inbox" :order 90) ))))) ))) ) ;;;;; Contacts ;; Last commit before requiring org 9.7: 048942e8063044accca7a5c42b81a1e27b7a0876 (use-package org-contacts :ensure (org-contacts :repo "https://repo.or.cz/org-contacts.git" :ref "048942e8") :custom (org-contacts-files `(,(expand-file-name "contacts.org" org-directory)) "Set single contacts file") :commands (org-contacts org-contacts-agenda)) ;;;;; Navigation ;;;;; Notes ;;;;;; Org Noter (use-package org-noter :custom (org-noter-notes-search-path `(,(expand-file-name "notes" org-directory)) "Note paths (non-recursive)") :bind ("C-c n n" . org-noter) (:map dired-mode-map ("C-c n n" . org-noter-start-from-dired)) :hook (dired-mode . org-noter-enable-update-renames)) ;;;;;; Org Remark (use-package org-remark :custom (org-remark-line-ellipsis "---" "Default '...' is too similar to folded headlines") (org-remark-notes-file-name (expand-file-name "org-remark.org" org-directory) "Default too similar to `marginalia.el'") :bind ("C-c n m" . org-remark-mark) ("C-c n l" . org-remark-mark-line) (:map org-remark-mode-map ("C-c n o" . org-remark-open) ("C-c n ]" . org-remark-view-next) ("C-c n [" . org-remark-view-prev) ("C-c n r" . org-remark-remove) ("C-c n d" . org-remark-delete)) :init (org-remark-global-tracking-mode) :hook (elpaca-after-init . org-remark-global-tracking-mode) (nov-mode . org-remark-nov-mode)) ;;;;; Org Citations ;;;;;; Org Cite (use-feature oc :custom (org-cite-export-processors '((latex biblatex) (t csl)) "Use biblatex for LaTeX, csl for everything else") (org-cite-global-bibliography `(,(expand-file-name "~/Nextcloud/Library/Bibliography/Library.bib")) "Set bib files") :defer t :config (when (elpaca-installed-p 'citar) (setopt org-cite-activate-processor 'citar org-cite-follow-processor 'citar org-cite-insert-processor 'citar))) ;;;;;; Org Cite CSL (use-feature oc-csl :if (elpaca-installed-p 'citeproc) :after (citeproc oc) :config (let ((zotero_styles (expand-file-name "Zotero/styles/" (or (getenv "XDG_DATA_HOME") "~/.local/share/")))) (if (file-directory-p zotero_styles) (setopt org-cite-csl-styles-dir zotero_styles)))) ;;;;;; Citar (use-package citar :custom (citar-bibliography `(,(expand-file-name "~/Nextcloud/Library/Bibliography/Library.bib")) "Set bib files") (citar-library-paths `(,(expand-file-name "~/Nextcloud/Library/")) "Set paths to sources") :bind ("C-c n c" . citar-create-note) :hook (org-mode . citar-capf-setup) :config (when (elpaca-installed-p 'citar-embark) (setopt citar-at-point-function 'embark-act))) ;;;;;; Citar + Embark (use-package citar-embark :if (and (elpaca-installed-p 'citar) (elpaca-installed-p 'embark)) :after (citar embark) :config (citar-embark-mode)) ;;;;;; Citeproc ;; Dependency for other packages (use-package citeproc :defer t) ;;;;; Project Management ;;;;;; Org Edna (use-package org-edna :custom (org-edna-finder-use-cache t "Improve performance with caching") :bind (:map org-mode-map ("C-c o e" . org-edna-edit)) :hook (org-mode . org-edna-mode)) ;;;;; Time Management ;;;;;; Org Clock (use-feature org-clock :after (org) :config (defun as/save-after-org-clock-in (&rest r) "Save org buffer after clocking in" (with-current-buffer (org-clocking-buffer) (save-buffer))) (defun as/save-after-org-clock-out (func &rest r) "Save org buffer after clocking out of a task `:around' is used here in the event the current buffer is not the same as `org-clocking-buffer'. Thanks to @mocompute for the idea: " (let ((buffer (org-clocking-buffer))) (apply func r) (when buffer (with-current-buffer buffer (save-buffer))))) (advice-add #'org-clock-in :after #'as/save-after-org-clock-in) (advice-add #'org-clock-out :around #'as/save-after-org-clock-out)) ;;;;;; Org Pomodoro (use-package org-pomodoro :after (org-clock) :custom (alert-user-configuration '((((:category . "org-pomodoro")) libnotify nil)) "Send alerts to libnotify") (org-pomodoro-audio-player (concat (executable-find "pw-cat") " -p --volume 0.25") "Use PipeWire & reduce volume") (org-pomodoro-clock-breaks t "Include break time") (org-pomodoro-format "%s" "Only display the timer") (org-pomodoro-keep-killed-pomodoro-time t "Log manually ended clocks") (org-pomodoro-length 50 "Increase pomodoro time") (org-pomodoro-long-break-frequency 2 "Decrease long break frequency") (org-pomodoro-manual-break t "Require manually clocking out") (org-pomodoro-short-break-length 10 "Increase short break length") :bind (:map org-mode-map ("C-c o p" . org-pomodoro)) :commands (org-pomodoro)) ;;;;;; Org Timeblock (use-package org-timeblock :custom (org-timeblock-new-task-time 'pick "Ask me for new time") (org-timeblock-show-future-repeats 'next "Only show next repeat") (org-timeblock-scale-options (cons 7 20) "Start at 7:00, end at 20:00") (org-timeblock-span 3 "Number of days to display") :bind (:map as/org-prefix-map ("b" . org-timeblock))) (provide 'init-org) ;;; init-org.el ends here