I'm using Org-mode 8.2.10 on Xubuntu 16.04 and despite having tried many combinations, I can't manage to view all scheduled tasks that don't have "DONE" and "CANCELLED" as a state. For example,

(setq org-agenda-custom-commands
     ("+" "Scheduled" 
       ((todo ""
         ((org-agenda-overriding-header "\nScheduled")
          (org-agenda-skip-function '(org-agenda-skip-entry-if 'notscheduled 'todo '("DONE" "CANCELLED")))

shows all scheduled tasks but including (instead of excluding) those with "DONE" and "CANCELLED" states...


The problem seems to be a bug in the function org-agenda-skip-if. Here's the source code, with irrelevant portions snipped out by [...]:

(defun org-agenda-skip-if (subtree conditions)
  (let (beg end m)
    (org-back-to-heading t)
    (setq beg (point)
          end [...])
    (goto-char beg)
      (and (memq 'scheduled conditions)
           (re-search-forward org-scheduled-time-regexp end t))
      (and (memq 'notscheduled conditions)
           (not (re-search-forward org-scheduled-time-regexp end t)))
      (and (or [...]
            (setq m (memq 'todo conditions)))
           (org-agenda-skip-if-todo m end)))

The idea seems to be that each clause under the outer or calls re-search-forward to skip things matching a regex appropriate to that clause. If the search doesn't find anything, then re-search-forward returns nil, and the function checks the next clause. (The subroutine org-agenda-skip-if-todo does the same thing inside that last clause.) Presumably the function as a whole is called inside a save-excursion so that things that shouldn't be skipped aren't; the value returned on a skip (end) is pre-computed, so the function doesn't pay attention to where it's leaving point.

The problem is that not form wrapping re-search-forward under the notscheduled clause. That just means that the function keeps going, but re-search-forward has already moved point past anything that might be found by the later re-search-forward inside org-agenda-skip-if-todo.

The quick and (very) dirty solution, therefore, is to redefine org-agenda-skip-if with a version wrapping that re-search-forward form inside its own save-excursion.

EDIT: Here's a less dirty solution using advice and noflet:

(defun patch/org-agenda-skip-if (oldfun subtree conditions)
  "Advice patching the \"bad negation\" bug in `org-agenda-skip-if'.

The function `org-agenda-skip-if' does not behave correctly when
given multiple conditions, at least one of which is a
negative (e.g., `notscheduled' or `nottodo').  This function
corrects the problem when applied as :around advice."
  (let ((use-excursion t))
    (noflet ((re-search-forward (&rest args)
                                (if use-excursion
                                    (save-excursion (apply this-fn args))
                                  (apply this-fn args)))
             (outline-next-heading (&rest args)
                                   (let ((use-excursion nil)) (apply this-fn args))))
      (funcall oldfun subtree conditions))))

(advice-add 'org-agenda-skip-if :around #'patch/org-agenda-skip-if)

Alternatively, for your particular case, you might consider just labeling both DONE and CANCELLED as "done" keywords. If you do that, then they'll be skipped by default, and you can drop that second clause from the skip function:

(setq org-agenda-custom-commands
      '(("+" "Scheduled" 
           ((org-agenda-overriding-header "\nScheduled")
             '(org-agenda-skip-entry-if 'notscheduled))))))))
| improve this answer | |
  • Many thanks Aaron! I followed your last advice, by adding a "|" in my todo-keywords: (setq org-todo-keywords '((type "TODO" "NEXT" "STARTED" "WAITING" "FROZEN" "REFERENCE" "DELEGATED" "|" "CANCELLED" "DONE"))) – crocefisso Sep 9 '17 at 6:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.