I have not found a builtin way to accomplish this "easily", so I wrote the following two functions which can manually be invoked to change the status of all todo states
in a subtree to CANCELLED
and back. You can use undo
right after cancelling to reset it but this won't work some days later, therefore the uncancel-function.
IMPORTANT: This includes ALL todo
keywords. If you created some other keywords like HOLD
or WAITING
, they will also be changed to CANCELLED
and on "uncancelling" they will be set to TODO
. Done
keywords will remain as they are. (check the example)
One Logbook entry will automatically be created for the main headline if desired.
You can change the CANCELLED
to CANCELED
in the first line (I didn't know both spellings are okay).
;; Since there are different spellings for that word we rather have a variable for it
(setq org-cancel-keyword "CANCELLED")
;; Taken from http://doc.norang.ca/org-mode.html#Projects
(defun bh/is-project-p ()
"Any task with a todo keyword subtask"
(save-restriction
(widen)
(let ((has-subtask)
(subtree-end (save-excursion (org-end-of-subtree t)))
(is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
(save-excursion
(forward-line 1)
(while (and (not has-subtask)
(< (point) subtree-end)
(re-search-forward "^\*+ " subtree-end t))
(when (member (org-get-todo-state) org-todo-keywords-1)
(setq has-subtask t))))
(and is-a-task has-subtask))))
(defun my/org-cancel-subtree ()
(interactive)
(save-mark-and-excursion
(let ((projects (list)))
(org-map-entries
(lambda ()
(if (org-entry-is-todo-p)
(if (bh/is-project-p)
(push (point) projects)
(org-todo org-cancel-keyword))))
nil
'tree)
(dolist (project projects)
(goto-char project)
(org-todo org-cancel-keyword))))
;; We need to mark the main project as cancelled, too
(if (org-entry-is-todo-p) (org-todo org-cancel-keyword)))
(defun my/org-uncancel-subtree ()
(interactive)
(save-mark-and-excursion
;; Ignore the first line, so that the note will be inserted on the first line
(let ((first-header (save-excursion (org-back-to-heading)(point))))
(org-map-entries
(lambda () (if (not (= (point) first-header)) (org-todo "TODO")))
(concat "/" org-cancel-keyword)
'tree)))
(org-todo "TODO"))
Here an example.
Before cancellation:
* TODO My Tree
** TODO Some todo
** TODO A Project with TODOs in it
*** TODO Some todo
*** DONE A DONE todo
*** TODO another project within a project
**** TODO some todo
**** HOLD This todo is on HOLD
:LOGBOOK:
- State "HOLD" from [2020-01-05 Sun 14:08] \\
reason
:END:
**** TODO some more todo
**** Just an information - not a todo
** TODO more TODOs
** more Informations
** TODO Another TODO
After Cancellation:
* CANCELLED My Tree :CANCELLED:
CLOSED: [2020-01-05 Sun 15:57]
:LOGBOOK:
- State "CANCELLED" from "TODO" [2020-01-05 Sun 15:57] \\
This is my cancellation Note.
:END:
** CANCELLED Some todo :CANCELLED:
CLOSED: [2020-01-05 Sun 15:57]
** CANCELLED A Project with TODOs in it :CANCELLED:
CLOSED: [2020-01-05 Sun 15:57]
*** CANCELLED Some todo :CANCELLED:
CLOSED: [2020-01-05 Sun 15:57]
*** DONE A DONE todo
*** CANCELLED another project within a project :CANCELLED:
CLOSED: [2020-01-05 Sun 15:57]
**** CANCELLED some todo :CANCELLED:
CLOSED: [2020-01-05 Sun 15:57]
**** CANCELLED This todo is on HOLD :CANCELLED:
CLOSED: [2020-01-05 Sun 15:57]
:LOGBOOK:
- State "HOLD" from [2020-01-05 Sun 14:08] \\
reason
:END:
**** CANCELLED some more todo :CANCELLED:
CLOSED: [2020-01-05 Sun 15:57]
**** Just an information - not a todo
** CANCELLED more TODOs :CANCELLED:
CLOSED: [2020-01-05 Sun 15:57]
** more Informations
** CANCELLED Another TODO :CANCELLED:
CLOSED: [2020-01-05 Sun 15:57]
After "uncancellation":
* TODO My Tree
:LOGBOOK:
- State "TODO" from "CANCELLED" [2020-01-05 Sun 17:30]
- State "CANCELLED" from "TODO" [2020-01-05 Sun 15:57] \\
This is my cancellation Note.
:END:
** TODO Some todo
** TODO A Project with TODOs in it
*** TODO Some todo
*** DONE A DONE todo
*** TODO another project within a project
**** TODO some todo
**** TODO This todo is on HOLD
:LOGBOOK:
- State "HOLD" from [2020-01-05 Sun 14:08] \\
reason
:END:
**** TODO some more todo
**** Just an information - not a todo
** TODO more TODOs
** more Informations
** TODO Another TODO
Funfact: It took me about 3 hours and some useless debugging to find out that /
prefix matches TODOs