(use-package org :bind ( ("C-c a" . org-agenda) ("C-c c" . org-capture) ("C-c l" . org-store-link) ) :mode ("\\.\\(org\\|org_archive\\|txt\\)\\'" . org-mode) :config ;; Lots of stuff from https://doc.norang.ca/org-mode.html ;; Some stuff from https://github.com/novoid/dot-emacs/blob/master/config.org (setq org-directory "~/org/") (setq org-agenda-files '("~/org/" "~/doc/")) ;; TODO: Only ~/org/, make ~/doc/ special maybe. (setq org-todo-keywords '((sequence "TODO(t)" "NEXT(n)" "|" "DONE(d)") (sequence "WAITING(w@/!)" "HOLD(h@/!)" "|" "CANCELLED(c@/!)" "PHONE" "MEETING"))) (setq org-log-state-notes-into-drawer t) (setq org-todo-keyword-faces '(("TODO" :foreground "red" :weight bold) ("NEXT" :foreground "blue" :weight bold) ("DONE" :foreground "forest green" :weight bold) ("WAITING" :foreground "orange" :weight bold) ("HOLD" :foreground "magenta" :weight bold) ("CANCELLED" :foreground "forest green" :weight bold) ("MEETING" :foreground "forest green" :weight bold) ("PHONE" :foreground "forest green" :weight bold))) (setq org-use-fast-todo-selection t) (setq org-use-speed-commands t) (setq org-treat-S-cursor-todo-selection-as-state-change nil) (setq org-todo-state-tags-triggers '(("CANCELLED" ("CANCELLED" . t)) ("WAITING" ("WAITING" . t)) ("HOLD" ("WAITING") ("HOLD" . t)) (done ("WAITING") ("HOLD")) ("TODO" ("WAITING") ("CANCELLED") ("HOLD")) ("NEXT" ("WAITING") ("CANCELLED") ("HOLD")) ("DONE" ("WAITING") ("CANCELLED") ("HOLD")))) (setq org-clock-in-resume t org-clock-out-remove-zero-time-clocks t) (defun my/org-is-task-p () "Is current org node a task (has todo keyword)?" (member (org-get-todo-state) org-todo-keywords-1)) (defun my/org-task-is-next-p () "Is current org node a NEXT task?" (s-equals-p (org-get-todo-state) "NEXT")) (defun my/org-subtree-any (pred) "Does pred hold true for any of the nodes in this subtree?" (save-restriction (widen) (let ((pred-t) (subtree-end (save-excursion (org-end-of-subtree t)))) (save-excursion (forward-line 1) (with-restriction (point) subtree-end (while (and (not pred-t) (outline-next-heading)) (setq pred-t (funcall pred))))) pred-t))) (defun my/org-parent-any (pred) "Does pred hold true for any of the parents of this node?" (save-restriction (widen) (let ((pred-t) (subtree-end (save-excursion (org-end-of-subtree t)))) (save-excursion (while (and (not pred-t) (org-up-heading-safe)) (setq pred-t (funcall pred)))) pred-t))) (defun my/org-is-stuck-project-p () "Is current org node a stuck project (no NEXT to be found in the subtree)?" (and (my/org-is-project-p) (not (my/org-subtree-any #'my/org-task-is-next-p)))) (defun my/org-is-project-p () "Is current org node a project (has subtasks)?" (and (my/org-is-task-p) (my/org-subtree-any #'my/org-is-task-p))) (defun my/org-is-subtask-p () "Is current org node a subtask (has parent task a.k.a. project)?" (and (my/org-is-task-p) (my/org-parent-any #'my/org-is-task-p))) (defun my/org-switch-state-to-next-when-clocking-in (_) "Switch a task from TODO to NEXT when clocking in." (when (not (and (boundp 'org-capture-mode) org-capture-mode)) (cond ((and (member (org-get-todo-state) '("TODO")) (my/org-is-task-p) (not (my/org-is-project-p))) "NEXT") ((and (member (org-get-todo-state) '("NEXT")) (my/org-is-project-p)) "TODO")))) (setq org-clock-in-switch-to-state #'my/org-switch-state-to-next-when-clocking-in) (defun my/org-remove-empty-drawer-in-node () (interactive) (save-excursion (beginning-of-line 0) (org-remove-empty-drawer-at (point)))) (add-hook 'org-clock-out-hook #'my/org-remove-empty-drawer-in-node 'append) (setq org-refile-targets '((nil . (:maxlevel . 9)) (org-agenda-files . (:maxlevel . 9))) org-refile-use-outline-path t org-outline-path-complete-in-steps nil org-refile-allow-creating-parent-nodes 'confirm) (defun my/org-verify-refile-target () "Exclude DONE state tasks from refile targets." (not (member (nth 2 (org-heading-components)) org-done-keywords))) (setq org-refile-target-verify-function #'my/org-verify-refile-target) (defun my/org-skip-non-stuck-projects () "Skip trees that are not stuck projects." (save-restriction (widen) (unless (my/org-is-stuck-project-p) (save-excursion (or (outline-next-heading) (point-max)))))) (defun my/org-skip-stuck-projects () "Skip trees that are stuck projects (but keep other projects)." (save-restriction (widen) (unless (and (my/org-is-project-p) (not (my/org-is-stuck-project-p))) (save-excursion (or (outline-next-heading) (point-max)))))) (defun my/org-skip-non-projects () "Skip trees that are not projects." (save-restriction (widen) (unless (my/org-is-project-p) (save-excursion (or (outline-next-heading) (point-max)))))) (defun my/org-skip-projects-and-single-tasks () "Skip trees that are projects, keep tasks that have a parent." (save-restriction (widen) (unless (and (not (my/org-is-project-p)) (my/org-is-subtask-p)) (save-excursion (or (outline-next-heading) (point-max)))))) (setq org-agenda-custom-commands '((" " "Agenda" ((agenda "" nil) (tags "INBOX" ((org-agenda-overriding-header "Tasks to refile") (org-tags-match-list-sublevels nil))) (tags-todo "-CANCELLED/!" ((org-agenda-overriding-header "Stuck projects") (org-agenda-skip-function #'my/org-skip-non-stuck-projects) (org-agenda-sorting-strategy '(category-keep)))) (tags-todo "-HOLD-CANCELLED/!" ((org-agenda-overriding-header "Projects") (org-agenda-skip-function #'my/org-skip-stuck-projects) (org-tags-match-list-sublevels 'indented) (org-agenda-sorting-strategy '(category-keep)))) (tags-todo "-CANCELLED/!NEXT" ((org-agenda-overriding-header "Project Next Tasks") (org-agenda-skip-function #'my/org-skip-projects-and-single-tasks) (org-tags-match-list-sublevels t) (org-agenda-todo-ignore-scheduled t) (org-agenda-todo-ignore-deadlines t) (org-agenda-todo-ignore-with-date t) (org-agenda-sorting-strategy '(todo-state-down effort-up category-keep)))) )))) (setq org-clock-report-include-clocking-task t) (require 'ox-md) (org-babel-do-load-languages 'org-babel-load-languages '( (shell . t) ) ) (setq org-enforce-todo-dependencies t org-blank-before-new-entry '((heading . t) (plain-list-item . nil)) org-log-done 'time) (setq org-default-notes-file (f-join org-directory "inbox.org")) (defvar my/org-cgm-file (f-join org-directory "cgm.org")) (defvar my/org-capture-default-template "* %?\n:PROPERTIES:\n:CREATED: %U\n:END:\n%a\n\n") (defvar my/org-capture-todo-template "* TODO %?\n:PROPERTIES:\n:CREATED: %U\n:END:\n%a\n\n") (defvar my/org-capture-todo-jira-template "* TODO [[CGMNL:%^{Jira ID}][CGMNLAPOHI-%\\1]] %?\n:PROPERTIES:\n:CREATED: %U\n:END:\n%a\n\n") (setq org-capture-templates `(("I" "Inbox" entry (file org-default-notes-file) ,my/org-capture-default-template :empty-lines 2 :clock-in t :clock-resume t) ("J" "Jira Issue" entry (file my/org-cgm-file) ,my/org-capture-todo-jira-template :empty-lines 2 :clock-in t :clock-resume t))) (setq org-link-abbrev-alist `(("CGMNL" . "https://jira.cgm.ag/browse/CGMNLAPOHI-%s"))) ) (setq org-startup-folded 'fold org-startup-indented t org-cycle-hide-drawer-startup t org-link-descriptive nil org-agenda-compact-blocks t org-agenda-span 'day org-agenda-time-grid (list '(daily today remove-match) (number-sequence 500 2100 100) " ┄┄┄┄┄ " "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄"))