0

I started from this post and now have code that already works for the following, for each level 2 heading:

  1. Add properties drawer :ANKI_NOTE_TYPE: Basic to the level 2 heading.
  2. Add two level 3 headings (Front and Back) with empty content.
#+BEGIN_SRC emacs-lisp :results org raw
(let ((MATCH t)
      (SCOPE 'file)
      (SKIP nil)
      (spacing nil))
  (org-map-entries
         (lambda ()
               (let ((level (nth 1 (org-heading-components))))
                 (if (= level 2)
                     (save-restriction
                       (org-narrow-to-subtree)
                       ;; (org-copy-visible)
                       (org-entry-put (point) "ANKI_NOTE_TYPE" "Basic")
                       (goto-char (point-max))

                       (org-insert-heading)
                       (insert "Front \n")
                       ;; now cut tree and paste at level 3
                       (org-cut-subtree)
                       (org-paste-subtree 3)

                       (org-insert-heading)
                       (insert "Back \n")
                       ;; (yank)
                       ;; now cut tree and paste at level 3
                       (org-cut-subtree)
                       (org-paste-subtree 3)
                       )))) MATCH SCOPE SKIP))
#+END_SRC

The above is working as expected for my input org chunk:

* Rand
** What is marketing?
** What are the four Ps?

which returns

* Rand
** What's marketing?
:PROPERTIES:
:ANKI_NOTE_TYPE: Basic
:END:

*** Back 


*** Front 

** What are the four Ps?
:PROPERTIES:
:ANKI_NOTE_TYPE: Basic
:END:

*** Back 

*** Front 

I'm now trying to copy the visible level 2 heading into the contents of the newly created level 3 heading, i.e. trying to get:

* Rand
** What is marketing?
:PROPERTIES:
:ANKI_NOTE_TYPE: Basic
:END:
*** Back 

*** Front 
What is marketing?

** What are the four Ps?
:PROPERTIES:
:ANKI_NOTE_TYPE: Basic
:END:
*** Back 

*** Front 
What are the four Ps?


In my code chunk, I've tried using org-copy-visible at line 12 then yanking at line 24 (as above, after uncommenting).

However org-copy-visible seems to be failing with:

save-restriction: Wrong number of arguments: (2 . 2), 0

How should I copy the visible level 2 heading into the contents of the newly created level 3 heading?

EDIT 1: updated example to show behaviour with 2 level 2 headings.

6
  • What should happen when there is more that one visible level 2 heading?
    – NickD
    Commented Aug 28, 2022 at 14:33
  • It should copy each level 2 heading separately into its level 3 content. Updated question to show expected behavior.
    – WH Koh
    Commented Aug 28, 2022 at 22:22
  • I think you're taking pains to achieve something that might be more easily achieved using Org capture. (info "(org) Capture") Commented Aug 29, 2022 at 13:36
  • @PhilHudson: I don't know if you have the time, but I'd be very interested in a capture solution. I'm not sure why the OP wants this and my answer is purely a "don't ask questions: just do exactly what the OP asked" but it would be interesting to dig a little deeper and find out.
    – NickD
    Commented Aug 30, 2022 at 17:48
  • @NickD the crucial things to bear in mind are: 1. %i, %c (and to a lesser extent %x) are your friends. 2. %(sexp "%i") works as you'd hope. 3. In this case file+function is probably the right target type. 4. I might consider (ab)using the clock functionality (clock target, %k element) in this case. Commented Aug 30, 2022 at 21:18

1 Answer 1

0

You are almost there already: org-heading-components returns a list of six elements (numbered from 0 to 5), the fifth of which is the text of the headline. So all you need to do is remember it and insert it in the right place:

* Rand
*** What is marketing?                                                                                      :some:tag:
:PROPERTIES:
:ANKI_NOTE_TYPE: Basic
:END:

***** Back 


***** Front 
What is marketing?

*** What are the four Ps?
:PROPERTIES:
:ANKI_NOTE_TYPE: Basic
:END:

***** Back 


***** Front 
What are the four Ps?


* Code

#+BEGIN_SRC emacs-lisp :results org raw
  (defun whkoh/munge-tree ()
    (let ((MATCH t)
          (SCOPE 'file)
          (SKIP nil)
          (spacing nil))
      (org-map-entries
       (lambda ()
         (let* ((comps (org-heading-components))
                (level (nth 1 comps))
                (text (nth 4 comps)))
           (if (= level 2)
               (save-restriction
                 (org-narrow-to-subtree)
                 ;; (org-copy-visible)
                 (org-entry-put (point) "ANKI_NOTE_TYPE" "Basic")
                 (goto-char (point-max))

                 (org-insert-heading)
                 (insert "Front \n")
                 (insert (concat text "\n"))
                 ;; now cut tree and paste at level 3
                 (org-cut-subtree)
                 ;; org-paste-subtree does not know about reduced levels, only "real" levels
                 ;; if org-odd-levels-only is true, then reduced level 3 is real level 5,
                 ;; but if false, then both are 3.
                 (org-paste-subtree (if org-odd-levels-only 5 3))

                 (org-insert-heading)
                 (insert "Back \n")
                 ;; (yank)
                 ;; now cut tree and paste at level 3
                 (org-cut-subtree)
                 (org-paste-subtree (if org-odd-levels-only 5 3))
                 )))) MATCH SCOPE SKIP)))
#+END_SRC

The changes are:

  • I created a function to wrap your code. That makes it easier to use a debugger (although @Tobias has shown some code that allows debugging of arbitrary Org Babel source blocks - see Edebug orgmode source code blocks with input variables). When you evaluate the code block, the function is defined. You can then call it in the Org mode buffer with M-: (whkoh/munge-tree).

  • I save the results of calling org-heading-components and grab two of those components: the (reduced) level with (nth 1 comps), i.e. the second element of the list, and the text of the headline with (nth 4 comps), i.e. the fifht element of the list.

  • I then insert the saved text of the headline in the section entitled "Front" which is then moved bodily into (reduced) level three.

There is one important wrinkle: I've referred to the reduced level above - that's the "logical" level. The problem is that there is also a "physical" level which is the number of asterisks in front of the headline. In many (most?) cases, the "logical" level and the "physical" level are the same: they are both equal to the number of stars in front of the headline.

But Org mode allows a customization, org-odd-levels-only, which makes them different. If org-odd-levels-only is set to t, then the "logical" level (1, 2, 3, ...) corresponds to a "physical" level of (1, 3, 5, ...) stars.

If, as is likely, you are not using this customization, then you might want to skip the rest of this explanation, but you should still keep the change that I made above. Without it, if you ever do customize org-odd-levels-only to t and run the code, you will get an infinite loop (DAMHIKT).

The problem is that org-paste-subtree does not know about logical levels, only physical ones.

There are different ways to deal with the problem, but I think the least confusing one is to deal with logical levels only. That's what (nth 1 comps) returns and that's what you use in the comparison (if (= level 2) .... But what you use as the argument to org-paste-subtree has to be a physical level: that's 3 if org-odd-levels-only is nil (the default), but it should be 5 if it is customized to t. So the two calls to org-paste-subtree are slightly more complicated than what you have. As I mentioned before, without that change, if org-odd-levels only is set to t, then the code produces an infinite loop.

If you want to learn more, search the Org manual for org-odd-levels-only for additional information about this. You can do that easily in Emacs, with C-h i g(org)RET i org-odd RET. There are four references, and you can use , to cycle through them and read them.

2
  • Thanks for the solution! Will take some time to digest it - I'm still a beginner in lisp evidently. I confess I've never encountered the emacs debugger but it looks pretty nice. One newbie question: in M-:, why is the function called inside parens (whkoh/munge-tree) instead of just whkoh/munge-tree?
    – WH Koh
    Commented Aug 30, 2022 at 15:02
  • To call a function func with arguments in Lisp, you say (func arg1 arg2 ...), e,g, to call the function + with arguments 2 and 3, you say (+ 2 3) which returns 5. The function above is named whkoh/munge-tree and it takes no arguments, so to call it you say (whkoh/munge-tree). See the Emacs Lisp Intro manual for some details.
    – NickD
    Commented Aug 30, 2022 at 15:49

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.