0

Is it possible to define a yasnippet that will pick up contents of a heading?

Say, I have something like this:

* Heading 1 :PROPERTIES: :CUSTOM_ID: MyHead :END: Text under Heading 1 

I will like to have a yasnippet that, when expanded, will display 'Text under Heading 1'.

The expansion can happen anywhere in the same file. And yasnippet will know which heading to pick from its CUSTOM_ID.

Can this be done? How?

3
  • Presumably, the snippet is going to be expanded somewhere other than in that headline: maybe a different point in the same file, maybe a different buffer altogether. How are you going to tell the snippet which headline's contents to harvest? Commented Jul 19, 2024 at 22:34
  • @NickD Thanks. I have updated the question. Commented Jul 20, 2024 at 1:45
  • 1
    So you need to pass the custom ID to the snippet and have the snippet search for the headline based on the passed-in custom ID, retrieve the text and insert it - correct? As an editorial comment, unless you have a special need to do that repeatedly, I would cut-and-paste manually, perhaps with the aid of a function that does the search and adds the text to the kill ring; you might be able to write a snippet that uses that function, but templates and snippets should (IMO of course) only be used for simple purposes, like inserting repetitive text. Complicated logic should be avoided in general. Commented Jul 20, 2024 at 2:29

1 Answer 1

1

This is only a partial answer, providing a couple of functions that can be used to get the text under a headline, given its custom ID. It does not attempt to incorporate anything into a snippet (which, as I explained in a comment, I don't think is a good idea ). It does provide an interactive command, bound to a key for convenient access.

Here is an Org file that can be used to define and test the functions:

 * Link https://emacs.stackexchange.com/questions/81758/use-contents-of-a-heading-in-yasnippet * Heading 1 :PROPERTIES: :CUSTOM_ID: MyHead :END: Text under Heading 1 * Code :noexport: #+begin_src elisp :results drawer (defun my/org-get-text-of-headline (beg end) (let ((s (buffer-substring beg end))) (with-temp-buffer (insert s) (org-mode) ;; skip the headline and property drawer and ;; don't include trailing newlines. (let* ((e (org-element-parse-buffer)) (parlist (org-element-map e '(paragraph) (lambda (x) (org-element-property :begin x)))) (end (org-element-property :end e))) (string-trim (buffer-substring (car parlist) end)))))) (defun my/org-get-text-under-given-heading (id) (save-excursion (org-link-search (format "#%s" id)) (let* ((e (org-element-at-point)) (beg (org-element-property :begin e)) (end (org-element-property :end e))) (my/org-get-text-of-headline beg end)))) #+end_src #+RESULTS: :results: my/org-get-text-under-given-heading :end: #+begin_src elisp :results drawer (my/org-get-text-under-given-heading "MyHead") #+end_src #+RESULTS: :results: Text under Heading 1 :end: 

C-c C-c on the first source block to define the functions and then C-c C-c on the second source block to test.

The second function finds the headline with the given Custom ID, parses the headline to find out where it begins and where it ends and then passes that region to the first function.

The first function creates a temporary buffer and copies the region into it, sets up the major mode to be org-mode and then parses the buffer (that's why I copied the headline into a temp buffer: parsing an Org mode file is a bit slow, so if the original file had tens or hundreds of headings, I didn't want to have to parse all of them - I just need one).

The function then uses the resulting parse tree to skip the initial stuff by selecting just the paragraphs. In fact, we only need the beginning of the first paragraph: we get the region between that and the end of the node and that's the wanted text.

There is no error checking, and there is an assumption that the relevant node is simple, i.e. it does not have any subtrees (you might want to try to see what happens if that assumption is not valid).

The functions can be used as a building block for other things, e.g. you can write a command that calls my/org-get-text-under-given-heading and stuffs the resulting text into the kill ring - you can then insert it wherever you want by yanking with C-y. And you can bind it to a key for easy access:

#+begin_src elisp :results drawer (defun my/text-under-heading-as-kill (&optional custom-id) (interactive "sCustom ID: ") (kill-new (my/org-get-text-under-given-heading custom-id))) (define-key global-map (kbd "C-c z") #'my/text-under-heading-as-kill) #+end_src 

You type C-c z, enter the custom ID at the prompt and you now have the string in your kill ring. Move to wherever you want to insert it and yank with C-y.

3
  • Modifying ever so slightly Nick's last function: (defun my/grab-text-under-heading-with-ID (custom-id) (interactive "sCustom ID: ") (insert (my/org-get-text-under-given-heading custom-id))) you could set snippet type to command and in the body of the snippet include (call-interacively 'my/grab-text-under-heading-with-ID) But yeah... seems like it's venturing beyond how I understand most people use snippets Commented Jul 21, 2024 at 9:21
  • 1
    @NickD Thanks. That is a very useful general purpose code. By the way, I tried with a heading with sub-heading. And it works as expected. The entire stuff that belongs to MyHead heading is copied. Commented Jul 22, 2024 at 3:32
  • OK - as long as it was what you expected :-) Commented Jul 22, 2024 at 11:50

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.