doom-config/gtd.el
2024-02-10 18:37:50 +01:00

291 lines
12 KiB
EmacsLisp

;;; gtd.el -*- lexical-binding: t; -*-
(after! org
(setq org-return-follows-link t
org-complete-tags-always-offer-all-agenda-tags t
org-agenda-files (mapcar (lambda (filename) (expand-file-name filename org-directory))
'("gtd/inbox.org"
"gtd/tasks.org"
"caldav/personal.org" ;; synced by org-caldav
"gtd/journal.org")))
;; (file-expand-wildcards (concat org-directory "gtd/*.org"))))
;; org-agenda-files (append (mapcar 'file-truename
;; (file-expand-wildcards (concat org-directory "gtd/*.org")))
;; (list (expand-file-name (funcall #'gtd-get-current-journal) org-directory))))
(setq org-capture-templates
`(("i" "Inbox" entry (file "gtd/inbox.org")
,(concat "* %?\n"
":PROPERTIES:\n:CREATED: %U\n:END:") :prepend t)
;; ("j" "Journal" entry (file+datetree ,(funcall #'gtd-get-current-journal))
("l" "Link" entry (file "gtd/inbox.org")
,(concat "* Process %:annotation\n"
":PROPERTIES:\n:CREATED: %U\n:END:") :prepend t)
("j" "Journal" entry (file+datetree "gtd/journal.org")
,(concat "* %?\n"
":PROPERTIES:\n:CREATED: %U\n:END:"))
("@" "Inbox [mu4e]" entry (file "gtd/inbox.org")
;; "* Process [[mu4e:msgid:%:message-id][%:fromname - %:subject]] :email:\n%U\n"))
,(concat "* Process %a %?\n"
":PROPERTIES:\n:CREATED: %U\n:END:") :prepend t)))
(setq org-refile-allow-creating-parent-nodes 'confirm
org-refile-target-verify-function (lambda (&rest _) (null (org-get-todo-state)))
;; org-refile-target-verify-function nil
;; org-refile-targets '(("tasks.org" . (:maxlevel . 2))))
org-refile-targets `(("tasks.org" . (:maxlevel . 2))
(,(expand-file-name "caldav/personal.org" org-directory) . (:level . 0))
(,(expand-file-name "gtd/someday.org" org-directory) . (:maxlevel . 1)))))
;; Getting the following on capture:
;;
;; Greedy org-protocol handler. Killing client.
;; No server buffers remain to edit
;;
;; See https://github.com/alphapapa/org-protocol-capture-html/issues/40
;; (use-package! org-protocol-capture-html
;; :after org-protocol
;; :config
;; (add-to-list 'org-capture-templates
;; '("w" "Website" entry (file "")
;; "* %a :website:\n\n%U %?\n\n%:initial"))
(defun gtd-capture-inbox ()
(interactive)
(org-capture nil "i"))
(map! "C-c i" #'gtd-capture-inbox)
(defun gtd-capture-journal ()
(interactive)
(org-capture nil "j"))
(map! "C-c j" #'gtd-capture-journal)
(defun gtd-capture-email ()
(interactive)
(call-interactively 'org-store-link)
(org-capture nil "@"))
(map! :after mu4e
:map (mu4e-headers-mode-map mu4e-view-mode-map)
:vne "C-c p" #'gtd-capture-email)
;; Insert CREATED property timestamps for non-captured headlines
(use-package! org-expiry
:after org
:config
(map! :map org-mode-map
"C-c c" #'org-expiry-insert-created)
(setq org-expiry-inactive-timestamps t))
;; Automatically trigger state changes in projects
(use-package! org-edna
:after org
:config
(org-edna-mode)
(setq org-edna-use-inheritance t))
(use-package! org-super-agenda
:after org
:config
(org-super-agenda-mode)
(setq org-agenda-window-setup 'current-window
org-agenda-restore-windows-after-quit t
org-agenda-block-separator nil
org-agenda-compact-blocks t
org-agenda-start-with-log-mode t
org-agenda-hide-tags-regexp "."
org-super-agenda-header-map (make-sparse-keymap)
org-agenda-custom-commands
'(("g" "Get Things Done (GTD)"
((agenda ""
((org-agenda-skip-function
'(org-agenda-skip-entry-if 'deadline))
(org-deadline-warning-days 0)))
;; TODO combine the two queries into one
(tags "inbox" ((org-agenda-overriding-header "")
(org-agenda-prefix-format " %?-14t% s")
(org-super-agenda-groups
'((:name "Inbox"
:anything t)))))
(alltodo "" ((org-agenda-overriding-header "")
(org-agenda-prefix-format " %i %-14:c ")
;; (org-agenda-sorting-strategy '(category-up))
(org-agenda-skip-function
'(org-agenda-skip-entry-if 'scheduled))
(org-super-agenda-groups
'((:name "Deadlines"
:deadline t)
(:name "Tasks"
:todo "NEXT")
(:name "Blocked/Delegated"
:todo "WAIT")
(:name "Completed today"
:log 'closed)
(:discard (:anything t)))))))))))
;; Automatically save gtd files after some actions on them
;; Source: https://www.labri.fr/perso/nrougier/GTD/index.html
(defun gtd-save-org-buffers ()
"Save `org-agenda-files' buffers without user confirmation.
See also `org-save-all-org-buffers'"
(interactive)
(message "Saving org-agenda-files buffers...")
(save-some-buffers t (lambda ()
(when (member (buffer-file-name) org-agenda-files)
t)))
(message "Saving org-agenda-files buffers... done"))
;; TODO: use org-after-refile-insert-hook ??
(defun gtd-update-statistics-cookies ()
"Save `org-agenda-files' buffers without user confirmation.
See also `org-save-all-org-buffers'"
(interactive)
(message "Updating cookies in org-agenda-files buffers...")
(save-some-buffers t (lambda ()
(when (member (buffer-file-name) org-agenda-files)
t)))
(message "Updating cookies in org-agenda-files buffers... done"))
(after! org
;; (advice-add 'org-refile :after (lambda (&rest _) (org-update-statistics-cookies t)))
(advice-add 'org-refile :after (lambda (&rest _) (gtd-save-org-buffers)))
(advice-add 'org-todo :after (lambda (&rest _) (gtd-save-org-buffers)))
(advice-add 'org-add-note :after (lambda (&rest _) (gtd-save-org-buffers)))
(advice-add 'org-clock-in :after (lambda (&rest _) (gtd-save-org-buffers))))
(defun gtd-agenda ()
(interactive)
(org-agenda nil "g"))
(map! :map 'override
"<f1>" #'gtd-agenda)
;;
;;; Archiving
;; Store archived entries in <current_file>.org_archive in a datetree
(after! org
(setq org-archive-location "%s_archive::datetree/"))
;;
;;; Calendar
(defun max/khalel--insert-import-file-header (sdate edate)
"Insert imported events file header information into current buffer.
SDATE and EDATE denote the start and end dates, respectively, of
the current import date range."
(when khalel-import-org-file-read-only
(insert "# -*- buffer-read-only: 1; -*-\n"))
(insert khalel-import-org-file-header)
(insert (format "*Events scheduled between %s and %s*:\n" sdate edate)))
(use-package! khalel
:after org
:config
(advice-add #'khalel--insert-import-file-header :override #'max/khalel--insert-import-file-header)
(advice-add #'khalel-import-events :around #'doom-shut-up-a)
(setq khalel-default-calendar "personal"
khalel-import-org-file-confirm-overwrite nil
khalel-import-start-date "-15d"
khalel-import-end-date "+30d"
khalel-import-org-file (expand-file-name "gtd/calendar.org" org-directory)
khalel-import-format "* {title} {cancelled} :{calendar}:\n\
:PROPERTIES:\n:CALENDAR: {calendar}\n\
:LOCATION: {location}\n\
:ID: {uid}\n\
:END:\n\
<{start-date-long} {start-time}>--<{end-date-long} {end-time}>\n\
{description}\n\
[[elisp:(khalel-edit-calendar-event)][Edit this event]]\
[[elisp:(progn (khalel-run-vdirsyncer) (khalel-import-events))]\
[Sync and update all]]\n"
khalel-import-org-file-header "#+TITLE: Calendar\n\n\
*NOTE*: this file has been generated by \
[[elisp:(khalel-import-events)][khalel-import-events]] \
and /any changes to this document will be lost on the next import/!
Instead, use =khalel-edit-calendar-event= or =khal edit= to edit the \
underlying calendar entries, then re-import them here.
You can use [[elisp:(khalel-run-vdirsyncer)][khalel-run-vdirsyncer]] \
to synchronize with remote calendars.
Consider adding this file to your list of agenda files so that events \
show up there.\n\n"))
(after! org
(khalel-add-capture-template))
(defun gtd-capture-event ()
(interactive)
(org-capture nil "e"))
(map! "C-c e" #'gtd-capture-event)
;;; TODO: not working still
;;; https://www.reddit.com/r/orgmode/comments/8rl8ep/making_orgcaldav_useable/
(use-package! org-caldav
:autoload org-caldav-sync
:init
(defvar +gtd/org-caldav-directory nil
"Directory where org-caldav synchronizes the calendars to.")
(setq org-icalendar-alarm-time 10)
:config
(setq org-caldav-resume-aborted 'always
+gtd/org-caldav-directory (expand-file-name "caldav" org-directory)
org-caldav-save-directory +gtd/org-caldav-directory
org-caldav-backup-file (expand-file-name "org-caldav-backup.org" +gtd/org-caldav-directory)
org-caldav-url "https://nextcloud.maxschlueter.com/remote.php/dav/calendars/max"
org-caldav-calendar-id "test"
org-caldav-files `(,(expand-file-name "personal.org" +gtd/org-caldav-directory))
org-caldav-inbox (expand-file-name "personal.org" +gtd/org-caldav-directory))
;; (after! org-agenda
;; (add-to-list 'org-agenda-files max/org-calendar-file t)
;; (setq org-agenda-include-diary t
;; org-agenda-insert-diary-strategy 'top-level
;; org-agenda-insert-diary-extract-time t
;; org-agenda-diary-file max/org-calendar-file))
;; (setq org-caldav-calendars
;; `((:calendar-id "personal"
;; :files (,(expand-file-name "personal.org" +gtd/org-caldav-directory)))))
;; :inbox ,(concat max/org-calendar-directory "personal.org"))))
(defun max/org-caldav-get-inbox ()
(let* ((calid (completing-read "Calendar: "
(cl-loop for cal in org-caldav-calendars
collect (plist-get cal :calendar-id))
nil t))
(calinbox (cl-loop for cal in org-caldav-calendars
when (string= (plist-get cal :calendar-id) calid)
return (plist-get cal :inbox))))
calinbox)))
(after! org
(setq org-clock-persist t
;; Useful when clocking in on a waiting task
org-clock-in-switch-to-state "NEXT"
org-clock-out-when-done '("DONE" "CNCL" "WAIT")
org-clock-persist-query-resume nil))
(use-package! org-clock-convenience
:after org
:config
;; Fix for macos: https://github.com/dfeich/org-clock-convenience/issues/9#issuecomment-646842338
(when IS-MAC (load "org-clock-convenience"))
(map! :map org-agenda-mode-map
"<S-up>" #'org-clock-convenience-timestamp-up
"<S-down>" #'org-clock-convenience-timestamp-down
"o" #'org-clock-convenience-fill-gap
"e" #'org-clock-convenience-fill-gap-both))
(after! org-pomodoro
(setq org-pomodoro-length 25
org-pomodoro-short-break-length 5
org-pomodoro-long-break-length 20))
;;
;;; Reference system
(load! "roam")