1

The SCHEDULED property does not respect task inheritance. This is a notable omission but is known. For task prioritization, I have a helper function which performs inheritance manually:

;;; called with `property=SCHEDULED' in `user-defined-up'
(defun task-inherited-property (org-marker property)
  (save-window-excursion
    (switch-to-buffer (marker-buffer org-marker))
    (goto-char org-marker)
    (until-nil org-up-heading-safe)
    (org-entry-get (point) property)))

;;; helper macros
(defmacro until-nil (action)
  `(until (null (,action)) nil))
(defmacro until (test &rest body)
  (declare (indent defun))
  `(while (not ,test) ,@body))

However, I do not know how to apply this in filtering. Right now I use (setq org-agenda-todo-ignore-scheduled 'future) but I want something that respects SCHEDULED inheritance too. How can I do this?

Matthew Piziak
  • 5,958
  • 3
  • 29
  • 77
  • Maybe this helps, it's for deadline but should be easily adaptable. Especially the advice (there is one for version 8 and one for 9): https://stackoverflow.com/questions/4872088/is-there-any-way-for-subtasks-to-inherit-deadlines-in-org-mode – Hubisan Jul 05 '19 at 06:54
  • Thanks @Hubisan. That definitely seems relevant, but I tried evaluating the version-9 elisp (with `DEADLINE` replaced with `SCHEDULED`) and I'm still getting child tasks showing up even when the parent tasked is filtered out. Maybe `org-agenda-todo-ignore-scheduled` uses some other interface? – Matthew Piziak Jul 05 '19 at 23:09

2 Answers2

1

I'm guessing we can move the point to the parent entry and check there:

(defun org-agenda-ignore-inherited-p (&optional end)
  (catch 'found
    (while (org-up-heading-safe)
      (when (org-get-scheduled-time nil)
        (throw 'found (org-agenda-check-for-timestamp-as-reason-to-ignore-todo-item end))))))

(advice-add 'org-agenda-check-for-timestamp-as-reason-to-ignore-todo-item
            :after-until #'org-agenda-ignore-inherited-p)

Another option is to check if the parent entry is scheduled in the future:

(defun org-agenda-ignore-inherited-p (&optional end)
  (catch 'found
    (while (org-up-heading-safe)
      (when (and (org-get-scheduled-time nil)
                 (eq org-agenda-todo-ignore-scheduled 'future)
                 (re-search-forward org-scheduled-time-regexp end t)
                 (> (org-time-stamp-to-now
                     (match-string 1) 
                     org-agenda-todo-ignore-time-comparison-use-seconds)
                    0))
        (throw 'found t)))))

Tested with Emacs version 26.2 and Org version 9.2.4 with and without emacs -Q:

(add-to-list 'load-path "~/path/to/org-mode/lisp")
(add-to-list 'load-path "~/path/to/org-mode/contrib/lisp" t)
(setq org-agenda-files '("~/path/to/test.org"))
(setq org-agenda-todo-ignore-scheduled 'future)

For testing purposes I added the following entries to test.org:

* TODO foo
SCHEDULED: <2040-07-06 Sat>
** TODO foobar
  • Without the advice and with org-agenda-todo-ignore-scheduled set to nil, when I call org-todo-list, both entries are displayed.
  • Without the advice and with org-agenda-todo-ignore-scheduled set to future, only foobar is displayed.
  • With the advice and with org-agenda-todo-ignore-scheduled set to future, nothing is displayed.
jagrg
  • 3,824
  • 4
  • 19
  • Hi @jargrg, I evaluated this and then ran my agenda, but the agenda construction seems to be in an infinite loop. Either that or it's taking a really long time. – Matthew Piziak Jul 05 '19 at 19:56
  • Hi @MatthewPiziak I don't see the loop and I'm not sure how to trigger the loop. I've updated the answer with additional information in case it helps. – jagrg Jul 06 '19 at 12:17
  • 1
    Performance can be a problem when doing such stuff with agenda items. How many items need to be checked? Try this and let me know if this work, use the same advice as in jagrg's solution (would only post as answer if it is faster than the current solution): `(defun org-agenda-ignore-inherited-p (&optional end) (save-excursion (let (ignore-item-p) (while (and (org-up-heading-safe) (null ignore-item-p)) (setq ignore-item-p (org-agenda-check-for-timestamp-as-reason-to-ignore-todo-item nil))) ignore-item-p)))` – Hubisan Jul 06 '19 at 19:56
  • @Hubisan your function works perfectly for me, thank you! I'm going to award jagrg a 50-point bounty for his efforts (thanks jagrg!), but if you post this enhancement as an answer I will award another 50-point bounty to you as well. – Matthew Piziak Jul 06 '19 at 22:22
1

This can be achieved

  • by using a skip function (globally, locally, with a custom org-agenda command) or
  • by changing the default behavior with an advice.

Both solutions are using the built-in function org-agenda-check-for-timestamp-as-reason-to-ignore-todo-item. Therefore all org-agenda ignore timestamp settings are supported: org-agenda-todo-ignore-with-date, org-agenda-todo-ignore-scheduled, org-agenda-todo-ignore-deadlines and org-agenda-todo-ignore-timestamp.

I would use the skip function as it's more flexible and less prone to unexpected side effects. In addition the skip function can be applied to any org-agenda command.

Use a skip function

This can be done globally (will be applied to every agenda match) by setting org-agenda-skip-function-global to the skip function, locally with let-binding org-agenda-skip-function or by using a custom agenda command.

The skip function

(defun my-org-agenda-skip-if-inherited-timestamp ()
  "Skip item with an inherited timestamp according to the org-agenda settings..
Uses built-in `org-agenda-check-for-timestamp-as-reason-to-ignore-todo-item'."
  (let ((subtree-end (save-excursion (org-end-of-subtree t)))
        (ignore-item-p (org-agenda-check-for-timestamp-as-reason-to-ignore-todo-item nil)))
    (while (and (org-up-heading-safe) (null ignore-item-p))
      (setq ignore-item-p (org-agenda-check-for-timestamp-as-reason-to-ignore-todo-item nil)))
    (when ignore-item-p
      subtree-end)))

Skip globally

(setq org-agenda-skip-function-global
      #'my-org-agenda-skip-if-inherited-timestamp)

;; Now the all agenda commands will ignore inherited timestamps.
(let ((org-agenda-todo-ignore-scheduled 'future))
  (org-todo-list))

Skip locally

(let ((org-agenda-todo-ignore-scheduled 'future)
      (org-agenda-skip-function #'my-org-agenda-skip-if-inherited-timestamp))
  (org-todo-list))

Skip with custom agenda command

You can add a custom org agenda command (see help for org-agenda-custom-commands). Here is an example for such a custom command that uses the skip function and ignores scheduled items in the future.

(setq org-agenda-custom-commands
      '(("T" "Todo list, ignoring inherited timestamps." todo ""
         ((org-agenda-todo-ignore-scheduled 'future)
          (org-agenda-skip-function #'my-org-agenda-skip-if-inherited-timestamp)))))

Change the default behavior with an advice

This solution uses an advice to change the default behavior of the built-in function org-agenda-check-for-timestamp-as-reason-to-ignore-todo-item. This will change the behavior of org-agenda-list and org-diary.

(defun my-org-agenda-ignore-inherited-timestamp-p (&optional end)
  "Check parents of heading for timestamp and return t if found.
Uses built-in `org-agenda-check-for-timestamp-as-reason-to-ignore-todo-item'."
  (save-excursion
    (let (ignore-item-p)
      (while (and (org-up-heading-safe) (null ignore-item-p))
        (setq ignore-item-p (org-agenda-check-for-timestamp-as-reason-to-ignore-todo-item nil)))
      ignore-item-p)))

;; Add an advice which is called if the original function returns nil to check
;; if any parent has a timestamp.
(advice-add 'org-agenda-check-for-timestamp-as-reason-to-ignore-todo-item
            :after-until #'my-org-agenda-ignore-inherited-timestamp-p)

;; Now the todo list will ignore inherited timestamps as well.
(let ((org-agenda-todo-ignore-scheduled 'future))
  (org-todo-list))
Hubisan
  • 1,623
  • 7
  • 10