1

I'd like to track my deep work so I've thought about adding a hook to org-clock-out-hook to achieve this.

(defun my/clock-out-deep-work ()
  (when (yes-or-no-p "Was that a deep work session?")
...
  (org-capture nil "d")
))

(add-hook 'org-clock-out-hook 'my/org-clock-out-hook)
    

I'm having trouble formulating the rest of the function which I'd like to be setting variables to be parsed to a org capture template to add the data to an org table. Namely, I want the duration for the last clock session (not the total time spent on the task) to be set to the variable org-clocked-duration. This is the template below

    ("d" "Deep Work Task" table-line (file ,(concat org-directory "deep_work.org"))
         "| %U | %(identity org-clocked-duration) | %K | %^{Activty Type} | %^{Location} | %? |"
         :unnarrowed t
         )

I keep the org clock data in the logbook drawer (i.e. org-log-into-drawer set to true) which means I can't use (org-entry-get nil "CLOCK"). I am on org mode version 9.6, emacs version 28.2 running Doom Emacs (issue unrelated to doom however)

Solution with help of answer below

Here is the solution that works for me thus far:

(defun my/last-duration ()
  (let* ((e (org-element-at-point))
         (type (org-element-type e)))
    (when (and (eq type 'drawer) (string= (org-element-property :drawer-name e) "LOGBOOK"))
      (let ((contents-beg (org-element-property :contents-begin e)))
        (save-excursion
          (goto-char contents-beg)
          (let* ((e (org-element-at-point))
                 (type (org-element-type e)))
            (when (eq type 'clock)
              (org-element-property :duration e))))))))

(defun my/get-last-clocked-task ()
  "Get the name of the last clocked task."
  (save-excursion
    (goto-char (car org-clock-history))
    (let ((title-with-props (org-get-heading t))
          (keyword (org-get-todo-state)))
      (substring-no-properties title-with-props (if keyword (1+ (length keyword))))
      )
    ))

(defun my/org-clock-out-hook ()
  "Run after clocking out of a task to display time clocked in."
  (when (yes-or-no-p "Was that a deep work session?")
    (save-excursion
      (org-back-to-heading)
      (search-forward ":LOGBOOK:" nil t)
      (let* ((org-clocked-duration (my/last-duration))
             (org-last-clocked-task (my/get-last-clocked-task))
             (org-capture-templates
              `(("d" "Deep Work Task" table-line
                 (file ,(concat org-directory "deep_work.org"))
                 "| %U | %(with-current-buffer (org-capture-get :original-buffer) org-clocked-duration) | %(identity org-last-clocked-task) | %^{Activty Type} | %^{Location} | %? |"
                 :unnarrowed t
                 )))
             )
        (org-capture nil "d"))
      )
    ))

(add-hook 'org-clock-out-hook 'my/org-clock-out-hook)

1 Answer 1

1

You basically need to set point to the beginning of the appropriate clock line and do something like this:

   ...
   (let* ((e (org-element-at-point))
          (duration (org-element-property :duration e)))
      ;;; use duration however your like - it's a string.
      ...

But you have to be at the right point to do that. I'm not sure where you are going to be starting from, but let's assume for the sake of argument that point is at the very beginning of :LOGBOOK:. Then you can find the beginning of the contents like this:

    ...
    (let* ((e (org-element-at-point))
           (contents-beg (org-element-property :contents-begin e)))
       (save-excursion
          (goto-char contents-beg)
          ;; we are at the beginning of the first CLOCK line - maybe
          ;; so we can use the code above to get the duration
          (let* ((e (org-element-at-point))
                 (duration (org-element-property :duration e)))
             ;;; use duration however your like - it's a string.
             ...

This assumes that:

  • the first CLOCK line is right at the beginning of the drawer. If there are other things in the drawer, you will have to skip them until you get to a clock.

  • the most recent CLOCK line is the first one. I have not checked but I think that depends on the setting of org-log-states-order-reversed, so whatever function you come up with will probably have to act differently if that is the case (and presumably even if you start with a function that just deals with the way you have things set up, eventually you'll want to make your function available to others, which will mean you will have to add an if to deal with the other case).

  • it assumes perfect conditions with everything as expected, but you'll need to deal with errors: checking using (org-element-type e) at each point where you call org-element-at-point will be necessary to make sure that you are at the point where you expected to be - and bail if not.

Not a complete answer by any means, but I hope it helps.


Here's a function more or less along the lines described above. It does some type checking to make sure that it is at the proper place(s) to address the last assumption above, but it does nothing to address the first two. The function returns the clock duration as a string if it is called with point on the :LOGBOOK: line, as long as the first entry in the drawer is a CLOCK line with a duration; it returns nil otherwise:

#+begin_src elisp :results drawer
  (defun my/last-duration ()
    (let* ((e (org-element-at-point))
           (type (org-element-type e)))
      (when (and (eq type 'drawer) (string= (org-element-property :drawer-name e) "LOGBOOK"))
        (let ((contents-beg (org-element-property :contents-begin e)))
          (save-excursion
            (goto-char contents-beg)
            (let* ((e (org-element-at-point))
                   (type (org-element-type e)))
              (when (eq type 'clock)
                (org-element-property :duration e))))))))
#+end_src

So if you place your cursor on your :LOGBOOK: line and type M-: (my/last-duration) you should get what you want. And you should be able to use this in your function to construct the capture template.

It is up to you to move point to the :LOGBOOK: line: the function does not worry about that. Some functions in org-clock.el move to the drawer by using a regexp search. You could do the same.

3
  • I'm assuming you meant to add an extra close bracket before the save excursion function for the body of the first let function? The other issue it seems to go to the properties drawer (the properties drawer is before the logbook drawer)
    – sab hoque
    May 12, 2023 at 16:08
  • 1
    No: see the doc string of save-excursion - (save-excursion ...) basically remembers where you were, so that the form inside can move point at will; when it is done, point is restored. For your other issue, see the edit adding the function plus the last paragraph.
    – NickD
    May 12, 2023 at 16:30
  • 1
    Sorry - you are right about the missing paren. I misinterpreted what you said. Fixed. Thanks!
    – NickD
    May 12, 2023 at 20:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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