dotfiles/dot_config/emacs/lisp/init-org.el

574 lines
21 KiB
EmacsLisp
Raw Normal View History

;;; init-org.el --- org-mode customizations -*- lexical-binding: t -*-
;; Author: Andrew Scott <git at andyscott dot me>
;; 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:
<https://mocompute.codeberg.page/item/2024/2024-10-03-org-clock-out-save-buffer.html>"
(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