123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420 |
- (require 'org)
- (eval-when-compile
- (require 'cl))
- (defcustom org-depend-tag-blocked t
- "Whether to indicate blocked TODO items by a special tag."
- :group 'org
- :type 'boolean)
- (defcustom org-depend-find-next-options
- "from-current,todo-only,priority-up"
- "Default options for chain-find-next trigger"
- :group 'org
- :type 'string)
- (defmacro org-depend-act-on-sibling (trigger-val &rest rest)
- "Perform a set of actions on the next sibling, if it exists,
- copying the sibling spec TRIGGER-VAL to the next sibling."
- `(catch 'exit
- (save-excursion
- (goto-char pos)
-
- (condition-case nil
- (outline-forward-same-level 1)
- (error (throw 'exit t)))
-
- ,@rest
-
- (org-entry-add-to-multivalued-property
- nil "TRIGGER" ,trigger-val))))
- (defvar org-depend-doing-chain-find-next nil)
- (defun org-depend-trigger-todo (change-plist)
- "Trigger new TODO entries after the current is switched to DONE.
- This does two different kinds of triggers:
- - If the current entry contains a TRIGGER property that contains
- \"chain-siblings(KEYWORD)\", it goes to the next sibling, marks it
- KEYWORD and also installs the \"chain-sibling\" trigger to continue
- the chain.
- - If the current entry contains a TRIGGER property that contains
- \"chain-siblings-scheduled\", we go to the next sibling and copy
- the scheduled time from the current task, also installing the property
- in the sibling.
- - Any other word (space-separated) like XYZ(KEYWORD) in the TRIGGER
- property is seen as an entry id. Org-mode finds the entry with the
- corresponding ID property and switches it to the state TODO as well."
-
- (let* ((type (plist-get change-plist :type))
- (pos (plist-get change-plist :position))
- (from (plist-get change-plist :from))
- (to (plist-get change-plist :to))
- (org-log-done nil)
- trigger triggers tr p1 kwd)
- (catch 'return
- (unless (eq type 'todo-state-change)
-
- (throw 'return t))
- (unless (and (member from org-not-done-keywords)
- (member to org-done-keywords))
-
- (throw 'return t))
-
-
-
- (setq trigger (org-entry-get pos "TRIGGER")
- triggers (and trigger (org-split-string trigger "[ \t]+")))
-
- (while (setq tr (pop triggers))
- (cond
- ((and (not org-depend-doing-chain-find-next)
- (string-match "\\`chain-find-next(\\b\\(.+?\\)\\b\\(.*\\))\\'" tr))
-
- (let* ((org-depend-doing-chain-find-next t)
- (kwd (match-string 1 tr))
- (options (match-string 2 tr))
- (options (if (or (null options)
- (equal options ""))
- org-depend-find-next-options
- options))
- (todo-only (string-match "todo-only" options))
- (todo-and-done-only (string-match "todo-and-done-only"
- options))
- (from-top (string-match "from-top" options))
- (from-bottom (string-match "from-bottom" options))
- (from-current (string-match "from-current" options))
- (no-wrap (string-match "no-wrap" options))
- (priority-up (string-match "priority-up" options))
- (priority-down (string-match "priority-down" options))
- (effort-up (string-match "effort-up" options))
- (effort-down (string-match "effort-down" options)))
- (save-excursion
- (org-back-to-heading t)
- (let ((this-item (point)))
-
- (org-up-heading-safe)
- (let ((end (save-excursion (org-end-of-subtree t)
- (point)))
- (done nil)
- (items '()))
- (outline-next-heading)
- (while (not done)
- (if (not (looking-at org-complex-heading-regexp))
- (setq done t)
- (let ((todo-kwd (match-string 2))
- (tags (match-string 5))
- (priority (org-get-priority (or (match-string 3) "")))
- (effort (when (or effort-up effort-down)
- (let ((effort (org-get-effort)))
- (when effort
- (org-duration-string-to-minutes effort))))))
- (push (list (point) todo-kwd priority tags effort)
- items))
- (unless (org-goto-sibling)
- (setq done t))))
-
- (setq items
- (cond (from-top (nreverse items))
- (from-bottom items)
- ((or from-current no-wrap)
- (let* ((items (nreverse items))
- (pos (position this-item items :key #'first))
- (items-before (subseq items 0 pos))
- (items-after (subseq items pos)))
- (if no-wrap items-after
- (append items-after items-before))))
- (t (nreverse items))))
- (setq items (remove-if
- (lambda (item)
- (or (equal (first item) this-item)
- (and (not todo-and-done-only)
- (member (second item) org-done-keywords))
- (and (or todo-only
- todo-and-done-only)
- (null (second item)))))
- items))
- (setq items
- (sort
- items
- (lambda (item1 item2)
- (let* ((p1 (third item1))
- (p2 (third item2))
- (e1 (fifth item1))
- (e2 (fifth item2))
- (p1-lt (< p1 p2))
- (p1-gt (> p1 p2))
- (e1-lt (and e1 (or (not e2) (< e1 e2))))
- (e2-gt (and e2 (or (not e1) (> e1 e2)))))
- (cond (priority-up
- (or p1-gt
- (and (equal p1 p2)
- (or (and effort-up e1-gt)
- (and effort-down e1-lt)))))
- (priority-down
- (or p1-lt
- (and (equal p1 p2)
- (or (and effort-up e1-gt)
- (and effort-down e1-lt)))))
- (effort-up
- (or e1-gt (and (equal e1 e2) p1-gt)))
- (effort-down
- (or e1-lt (and (equal e1 e2) p1-gt))))))))
- (when items
- (goto-char (first (first items)))
- (org-entry-add-to-multivalued-property nil "TRIGGER" tr)
- (org-todo kwd)))))))
- ((string-match "\\`chain-siblings(\\(.*?\\))\\'" tr)
-
- (setq kwd (match-string 1 tr))
- (org-depend-act-on-sibling (format "chain-siblings(%s)" kwd)
- (org-todo kwd)))
- ((string-match "\\`\\(\\S-+\\)(\\(.*?\\))\\'" tr)
-
- (setq id (match-string 1 tr)
- kwd (match-string 2 tr)
- p1 (org-find-entry-with-id id))
- (when p1
-
- (save-excursion
- (goto-char p1)
- (org-todo kwd))))
- ((string-match "\\`chain-siblings-scheduled\\'" tr)
- (let ((time (org-get-scheduled-time pos)))
- (when time
- (org-depend-act-on-sibling
- "chain-siblings-scheduled"
- (org-schedule nil time))))))))))
- (defun org-depend-block-todo (change-plist)
- "Block turning an entry into a TODO.
- This checks for a BLOCKER property in an entry and checks
- all the entries listed there. If any of them is not done,
- block changing the current entry into a TODO entry. If the property contains
- the word \"previous-sibling\", the sibling above the current entry is checked.
- Any other words are treated as entry id's. If an entry exists with the
- this ID property, that entry is also checked."
-
- (let* ((type (plist-get change-plist :type))
- (pos (plist-get change-plist :position))
- (from (plist-get change-plist :from))
- (to (plist-get change-plist :to))
- (org-log-done nil)
- blocker blockers bl p1
- (proceed-p
- (catch 'return
-
-
- (when (or (not (eq type 'todo-state-change))
- (member from (cons 'done org-done-keywords))
- (member to (cons 'todo org-not-done-keywords))
- (not to))
- (throw 'return t))
-
-
-
- (setq blocker (org-entry-get pos "BLOCKER")
- blockers (and blocker (org-split-string blocker "[ \t]+")))
-
- (while (setq bl (pop blockers))
- (cond
- ((equal bl "previous-sibling")
-
- (catch 'ignore
- (save-excursion
- (goto-char pos)
-
- (condition-case nil
- (outline-backward-same-level 1)
- (error (throw 'ignore t)))
-
- (unless (org-entry-is-done-p)
-
- (org-mark-ring-push)
- (throw 'return nil)))))
- ((setq p1 (org-find-entry-with-id bl))
-
- (save-excursion
- (goto-char p1)
- (unless (org-entry-is-done-p)
-
- (org-mark-ring-push)
- (throw 'return nil))))))
- t
- )))
- (when org-depend-tag-blocked
- (org-toggle-tag "blocked" (if proceed-p 'off 'on)))
- proceed-p))
- (add-hook 'org-trigger-hook 'org-depend-trigger-todo)
- (add-hook 'org-blocker-hook 'org-depend-block-todo)
- (provide 'org-depend)
|