;;; 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 "" #'gtd-agenda) ;; ;;; Archiving ;; Store archived entries in .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 "" #'org-clock-convenience-timestamp-up "" #'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)))))