progfolio / doct

DOCT: Declarative Org Capture Templates for Emacs
GNU General Public License v3.0
380 stars 8 forks source link

:clock-in on root heading #20

Closed kehoen closed 4 years ago

kehoen commented 4 years ago

Hello,

Thanks for this package - I think it is a nice idea for developing capture templates. I have successfully set up a template which I want to use :clock-in t. This works fine, however, if I also use %? to assert where the cursor should be after the template is created, the clock will be assigned to that (sub)headline.

Is there any way to ensure that the clock is assigned to the highest headline within the template? As a work around, I am removing the %?.

progfolio commented 4 years ago

Thank you for the kind words.

Can you provide an example template declaration so I can investigate the behavior you're seeing?

kehoen commented 4 years ago

Sure. I didn’t copy everything but this is an example that I would like to have a clock in at the highest headline but put the cursor under notes.

`

                  ("Meeting" :keys "m"
                   :file "~/Notes/meetings.org"
                   :children (
                              ("Meetings" :keys "m"
                               :template ("* %t %?"
                                          ":PROPERTIES:"
                                          ":CREATED: %U"
                                          ":PURPOSE: %^{Meeting purpose}"
                                          ":END:"
                                          "** Participants"
                                          "- "
                                          "** Action items"
                                          "- None."
                                          "** Decision made"
                                          "- Not applicable"
                                          "** Notes"
                                          "*** "
                                          "*** Other business")
                               :children (
                                          ("Meeting notes (clocked)"
                                           :keys "m"
                                           :clock-in t
                                           )
                                          ("Meeting notes (not clocked)"
                                           :keys "M"
                                           :clock-in nil
                                           )
                                          )
                               )
                              ("Phone call" :keys "p"
                               :template ("* %t %?"
                                          ":PROPERTIES:"
                                          ":CREATED: %U"
                                          ":PURPOSE: Phone call with %^{Other party}"
                                          ":END:"
                                          "** Action items"
                                          "- None."
                                          "** Decision made"
                                          "- Not applicable"
                                          "** Notes"
                                          "*** "
                                          "*** Other business")
                               :children (
                                          ("Phone call (clocked)"
                                           :keys "p"
                                           :clock-in t
                                           )
                                          ("Phone call (not clocked)"
                                           :keys "P"
                                           :clock-in nil
                                           )
                                          )
                               )
                              )
                   )

`

progfolio commented 4 years ago

Thanks for the example. I understand what you're going for now.

This is a little tricky due to org-capture's implementation. org-capture executes in this order:

What you want to do is:

In order to work around this I'm going to use a hook function, advice, and restructure the templates.

The first step is to split the template into two parts: the heading we want clocked and the rest of the template. I'm using the :template keyword for the heading you want clocked, so org-capture's clocking functionality should work as it normally does. The child entries of the clocked heading are then split out into a custom keyword. I've chosen :body below, but you could make it anything you like. We can't insert the rest of the template during org-capture-mode-hook because org-capture invokes org-clock-in after the hook runs. Instead, I've advised org-clock-in to fill and insert the :body template string and then position the cursor after it executes. The same function removes this temporary advice.

A simplified example similar to the declarations you've provided:

(let ((org-capture-templates
       (doct `("clock-in parent test"
               :keys "t"
               :file "/tmp/clock-in-test.org"
               :clock-in t
               ;;I've defined this function inline in the declaration, but one could define it elsewhere.
               :advice ,(defun +doct-insert-body ()
                          "Insert template's filled :body, position cursor, remove `org-clock-in' advice."
                          (unwind-protect
                              (when-let ((body (condition-case nil
                                                   (org-capture-fill-template (string-join (doct-get :body) "\n"))
                                                 (quit (org-capture-kill)))))
                                (goto-char (point-max))
                                (insert "\n" body)
                                (org-capture--position-cursor (point-min) (point-max)))
                            (advice-remove 'org-clock-in #'+doct-insert-body)))

               :hook ,(defun +doct-advice-org-clock-in ()
                        "Install `org-clock-in' advice."
                        (advice-add 'org-clock-in :after #'+doct-insert-body))
               :template ("* Clock Here")
               :body     ("** %^{subheading}"
                          "*** %?")))))
  (org-capture nil "t"))

Note this will create an extra window when filling in the :body.

Does that help?

kehoen commented 4 years ago

Yes, thank you. Running that code does exactly what I need. Now, I just need to best determine how to integrate it into my use case. Thanks for taking the time to do this.

kehoen commented 4 years ago

Sorry for the second msg - if you have a minute, can you help show me how to define the function outside of the capture template so I can reuse it on a number of templates? I'm having trouble accomplishing that.

progfolio commented 4 years ago

Sure. The defun form needs to be moved out of the declaration and should be before anything that uses it.

(defun +doct-insert-body ()
  "Insert template's filled :body, position cursor, remove `org-clock-in' advice."
  (unwind-protect
      (when-let ((body (condition-case nil
                           (org-capture-fill-template (string-join (doct-get :body) "\n"))
                         (quit (org-capture-kill)))))
        (goto-char (point-max))
        (insert "\n" body)
        (org-capture--position-cursor (point-min) (point-max)))
    (advice-remove 'org-clock-in #'+doct-insert-body)))

(setq ((org-capture-templates
        (doct `("clock-in parent test"
                :keys "t"
                :file "/tmp/clock-in-test.org"
                :clock-in t
                :hook ,(defun +doct-advice-org-clock-in ()
                         "Install `org-clock-in' advice."
                         (advice-add 'org-clock-in :after #'+doct-insert-body))
                :template ("* Clock Here")
                :body     ("** %^{subheading}"
                           "*** %?")))))
      (org-capture nil "t"))
kehoen commented 4 years ago

does there need to be an advice: declaration in the setq example above?

progfolio commented 4 years ago

No. I was merely storing the function there in the example. :advice is not a recognized doct keyword. It was just a convenient way to declare the function with the rest of the declaration if you only needed it in one spot.