doom-config/gtd.el
2024-02-11 19:58:30 +01:00

326 lines
13 KiB
EmacsLisp
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; 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 khalel
"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 "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)
;; HACK: Getting an error message otherwise:
;; org-element--list-struct: Tab width in Org files must be 8, not 4. Please adjust your tab-width settings for Org mode.
;; TODO: Set tab width for org mode only
(setq-default tab-width 8)
(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 "caldav/personal.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)))
;;
;;; Reference system
(load! "roam")
;;
;;; Misc
(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))
(defvar org-clock-heading-str-limit 40
"Maximum length of `org-clock-heading' string. Will be truncated if too long.")
(defun max/org-clock-get-clock-string ()
"Form a clock string that will be shown in a status bar.
Adapted from `org-clock-get-clock-string'."
(require 'org-clock)
(when (org-clocking-p)
(let* ((currently-clocked-time
(floor (org-time-convert-to-integer
(time-since org-clock-start-time))
60))
(org-clock-heading-str (if (> (length org-clock-heading) org-clock-heading-str-limit)
(concat (substring org-clock-heading 0 org-clock-heading-str-limit) "...")
org-clock-heading))
(clocked-time (org-clock-get-clocked-time))
(work-done-str (org-duration-from-minutes clocked-time))
(current-work-done-str (org-duration-from-minutes currently-clocked-time)))
(if org-clock-effort
(let* ((effort-in-minutes (org-duration-to-minutes org-clock-effort))
(effort-str (org-duration-from-minutes effort-in-minutes)))
(format "%s (%s/%s) %s"
org-clock-heading-str work-done-str effort-str current-work-done-str))
(format "%s (%s) %s"
org-clock-heading-str
work-done-str
current-work-done-str)))))