I had a task in my org file for which I wanted to keep track of the number of times a task is marked as DONE when it's recurring.

I am logging my change to the drawer and thus it should be possible to get the count.

Currently, I count the number of times the task was executed manually but want to automate this effort. This option would help me keep track of the number of times the task was marked DONE. It would also be amazing if we could reset the count after getting it done a fixed number of times.

I am not a programmer so I'm struggling with implementing a solution in elisp to do the same.

Thanks for all your help.


We write two functions: one to count the number of DONE entries in the drawer and one to add the count as a property to the headline. Here's an example Org mode document delineating the basic assumptions:

* TODO foo
  SCHEDULED: <2020-07-07 Tue +1d>
  :LOG_INTO_DRAWER: logging
  :VISIBILITY: folded
  :LAST_REPEAT: [2020-07-01 Wed 16:03]
- State "DONE"       from "TODO"       [2020-07-01 Wed 16:03]
- State "DONE"       from "TODO"       [2020-07-01 Wed 16:03]
- State "DONE"       from "TODO"       [2020-07-01 Wed 16:03]
- State "DONE"       from "TODO"       [2020-07-01 Wed 15:58]
- State "DONE"       from "TODO"       [2020-07-01 Wed 13:38]
- State "DONE"       from "TODO"       [2020-07-01 Wed 13:38]

We assume that there is a LOG_INTO_DRAWER property naming the drawer where the logging is done (logging in this case), and we have transitioned to DONE a number of times.

Here's the (decidedly less clunky, thanks to @erikstokes's comment) code:

(defun ndk/count-done ()
    ;; we need to end up *before* the start of the drawer in order
    ;; to parse it correctly, so we back up one line from where org-log-beginning tells us.
    (goto-char (org-log-beginning))
    (forward-line -1)
    (let ((contents (cadr (org-element-drawer-parser nil nil))))
      (count-lines (plist-get contents :contents-begin)
                   (plist-get contents :contents-end)))))

(defun ndk/put-count ()
  (let ((count (ndk/count-done)))
    (org-entry-put (point) "done-count" (format "%d" count))))

We assume that point is at the beginning of the headline. Both functions are declared interactive so you can call them with M-x ndk/count-done or M-x ndk/put-count for debugging, but only the second really needs to be.

So if you place the cursor on the * of the headline and say M-x ndk/count-done you should get 6 as a result in the echo area. The function gets to the beginning of the log and then calls org-element-drawer-parser to parse the drawer and extract the beginning and end of the contents of the drawer. It then passes those two to count-lines and returns the result.

Doing M-x ndk/put-count at the same point calls the previous function to count the number of DONE lines and then inserts the count as a property done-count in the property drawer.

Resetting the count after every N times that the task is done is just a matter of calculating a remainder when dividing the count by N. Here's an implementation where N is obtained as the value of the reset-count property (but if there is no such property, we take N=10):

(defun ndk/count (count)
  (let* ((reset-count-prop (org-entry-get (point) "reset-count"))
         (reset-count (or (and reset-count-prop (string-to-number reset-count-prop))
    (% count reset-count)))

(defun ndk/put-count ()
  (let ((count (ndk/count-done)))
    (org-entry-put (point) "done-count" (format "%d" (ndk/count count)))))
  • 1
    Rather than searching for the starting of the logging drawer, the function org-log-beginning returns the start of the logs, even if something is hiding it
    – erikstokes
    Jul 2 '20 at 0:36
  • Thanks! That takes care of most of the clunkiness.
    – NickD
    Jul 2 '20 at 0:40
  • @erikstokes - thanks for the fix. Embarassing...
    – NickD
    Jul 2 '20 at 15:08
  • The function seems to be giving me a count one lower than the actual. I edited the function ndk/put-count () with (org-entry-put (point) "done-count" (format "%d" (+ count 1)) to get the accurate number. This might be due to the function not counting the start or the end line. Is there a way to get the reset count from a property value in the drawer so that I can have different reset number for different tasks rather than hard coding the value?
    – Alen
    Jul 3 '20 at 19:41
  • You mean you get 5 for the example above? I hope not...
    – NickD
    Jul 3 '20 at 19:46

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.