2

Suppose we're located at the Child header below:

  ** Parent
  :LOGBOOK:
  CLOCK: [2018-02-01 Thu 15:16]--[2018-02-01 Thu 15:19] =>  0:03
  :END:
  *** Child
  :LOGBOOK:
  CLOCK: [2018-02-01 Thu 15:16]--[2018-02-01 Thu 15:19] =>  0:03
  :END:

Question: How can one programmatically move the clocked time from the child to the parent, as below:

  ** Parent
  :LOGBOOK:
  CLOCK: [2018-02-01 Thu 15:16]--[2018-02-01 Thu 15:19] =>  0:03
  CLOCK: [2018-02-01 Thu 15:16]--[2018-02-01 Thu 15:19] =>  0:03
  :END:
  *** Child

?

3

Function org-move-logbook-up below exploits the org element api to move :LOGBOOK: drawer one heading level higher.

The strategy is:

  • parsing the buffer with org-element-parse-buffer
  • modifying the parse tree with functions like
    • org-element-map,
    • org-element-adopt-elements,
    • org-element-set-contents
  • and printing of the modified parse tree with org-element-interpret-data

Some restrictions of the current implementation:

  • the function may take long for large org files since the full buffer is parsed. You may narrow the buffer to a part with valid org syntax to reduce processing time.
  • only the first :LOGBOOK: drawer is considered (in parent and child), You can extend the program yourself if you need to consider multiple :LOGBOOK: drawers in one section.
  • The indention of the org buffer is not preserved because it is not part of the syntax. Currently the full buffer is re-indented after moving the :LOGBOOK: drawer.
  • position of point is also not preserved, it is approximated by searching for the headline where point was on
(require 'subr-x) ; for when-let

(defun org-element-contains-point-p (el)
  "Return org element EL if `point' is in it."
  (and (>= (point) (org-element-property :begin el))
       (<= (point) (org-element-property :end el))
       el))

(defun org-element-inner (el1 el2)
  "Return org element EL1 if it is contained in EL2.
Return EL2 otherwise."
  (if (and
       (<= (org-element-property :begin el2)
           (org-element-property :begin el1))
       (<= (org-element-property :end el1)
           (org-element-property :end el2)))
      el1 el2))

(defsubst org-element-section (headline)
  "Get section of HEADLINE contents"
  (assoc 'section (org-element-contents headline)))

(defun org-element-logbook (section)
  "Get clock drawer from SECTION."
  (org-element-map section 'drawer
                  (lambda (drawer)
                    (let ((name (org-element-property :drawer-name drawer)))
                      (and (string-equal name "LOGBOOK")
                           drawer)))))

(defun org-move-logbook-up ()
  "Return parse tree with logbook of current headline moved to the parent headline."
  (interactive)
  (when-let ((tree (org-element-parse-buffer))
             (headlines
              (org-element-map tree 'headline #'org-element-contains-point-p))
             (headline (cl-reduce #'org-element-inner headlines))
             (parent-headline (org-element-property :parent headline))
             (section (org-element-section headline))
             (parent-section (org-element-section parent-headline))
             (clock-drawer (car (org-element-logbook section)))
             (clocks (org-element-map clock-drawer 'clock #'identity)))
    (let ((parent-clock-drawer (car (org-element-logbook parent-section))))
      (if parent-clock-drawer
          (apply #'org-element-adopt-elements parent-clock-drawer clocks)
        (org-element-adopt-elements parent-section clock-drawer))
      (org-element-set-contents section (remove clock-drawer (org-element-contents section))))
    (let ((ret (org-element-interpret-data tree)))
      (when (called-interactively-p 'any)
        (delete-region (point-min) (point-max))
        (insert ret)
        (indent-region (point-min) (point-max) nil)
        (goto-char (point-min))
        (org-goto-local-search-headings (org-element-property :raw-value headline) nil t))
      ret)))
2
  • The only thing that I'm confused about is this: "The indention of the org buffer is not preserved because it is not part of the syntax. Currently the full buffer is re-indented after moving the :LOGBOOK: drawer." Why is this? I've used this function and seems to work perfectly except it does have, as a side effect, a changing of the indentation format of my of my file. Is there any way around this side effect? (Great answer BTW).
    – George
    Feb 2 '18 at 20:46
  • @George AFAIK that effect is not avoidable because: 1. The full buffer is translated into an abstract syntax tree. 2. The desired transformation takes place on the syntax tree. 3. The original text of the buffer is deleted and the modified syntax tree is printed out instead by the interpreter. The interpreter does preserve the old indentation. But you can control indentation of org-mode to some extent by org-adapt-indentation.
    – Tobias
    Jul 18 '18 at 12: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.