Capture-templates - Reuse prompted values within a template

I am trying to do what I think the O.P. wanted in this thread.

My goal is to have an org-roam-capture-template prompt for a named value and reuse this value in different locations during template creation.

So far, I could successfully achieve that using org-template prompts, and %\\1 to reuse the value somewhere else.

The following example works fine: %\\1 in the #+filetags: part gets replaced by the bar value passed to the FOO property prompt.

    (setq! org-roam-capture-templates '(
             ("n" "Note" plain "%?"
              :target (file+head "areas/%<%Y%m%d%H%M%S>-${slug}.org"
                    "
    :PROPERTIES:
    :ID:            ${id}
    :FOO:          %^{bar}
    :END:
    #+title: ${title}
    #+filetags: :%\\1:")
              )
    ))

Please note that I am setting additional properties in the ’head’ part because, if I use the ’body’ part of the template, I end up with two property drawers. Several threads on the forum address this issue, but I lost track, so, sorry for not giving credit.

The downside of using this approach is that values cannot be shared between the ’body’ part (%? in the example above), and the ’head’ part.

Browsing the forum, I finally found the thread mentionned at the beginning of this post. At the end of that post, nobiot (whom I believe is also the author of org-roam, so thanks a ton while I am at it) gave a different approach to the problem:

    (defvar var "")
    
    (setq org-roam-capture-templates
                 '(("s" "special" plain ""
                   :target
                   (file+head "./${id}.org" "
    #+title: ${title}
    #+author: %(identity var)
    #+id: ${id}
    
    This is a template %(identity var)
    
    "))))
    
    
    (let ((var (read-string "Author: ")))
      (org-roam-capture nil "s"))

I would really like to replicate this approach because for what I gather from the read-string documentation, I could pass it a list of default values. That would help me reduce the numbers of templates in my config.

Unfortunately, I am new to (doom-)emacs and I struggle with elisp so I tried to modify it as follows.

    (defvar bar "")
    (setq! org-roam-capture-templates '(
             ("n" "Note" plain "%?"
              :target (file+head "areas/%<%Y%m%d%H%M%S>-${slug}.org"
                    "
    :PROPERTIES:
    :ID:            ${id}
    :FOO:          %(bar)
    :END:
    #+title: ${title}
    #+filetags: :%(bar):")
              )
    ))
    
    (let ((bar (read-string "Foo: ")))
      (org-roam-capture nil "n"))

With this in my config, the let statement at the end gets executed when reloading or starting up Doom-Emacs, and I have no idea how to make it work.

I know it is more an elisp issue than an org-roam issue but this code could be useful to me in many ways.

Thanks for your time.

Thank you for the summary. If I understand your issue correctly, instead of this:

    (let ((bar (read-string "Foo: ")))
      (org-roam-capture nil "n"))

Try either of this:

(defun +org-roam-capture ()
  (interactive)
  (let ((bar (read-string "Foo: ")))
    (org-roam-capture nil "n")))

;; If you want use a list of predefined values, I suggest to try
;; `completing-read' instead
(defun ++org-roam-capture ()
  (interactive)
  (let ((bar (completing-read "Foo: "
                              (list "value a" "value b" "value c"))))
    (org-roam-capture nil "n")))

Use one of the new commands you define with the code above (+org-roam-capture or ++org-roam-capture).

See how you go with this.

Looking back, I think there is a way to avoid the the variable var, which I think would be better. But that’s for another time (I’d need to confirm my understanding).

By the way, I am not an author of Org-roam. I am just one of long-term users, who stays around in this forum :slight_smile:

Sorry for the confusion about you being the author of org-roam :slight_smile: I read so many guides, tutorials and articles about org workflows in the past few weeks, I must have confused your name with another that comes up a lot as well.

Back to the code, thanks for your answer, it helped me with a couple of concepts already. First wrapping the let method in a function was of course the way to go for it not to get called on start up (silly me!). And I now have the completing-read tip that will come very handy in my config.

I will use the first option you gave me in the following snippets for the sake of simplicity:

(defun +org-roam-capture ()
  (interactive)
  (let ((bar (read-string "Foo: ")))
    (org-roam-capture nil "n")))

If I understand correctly, calling +org-roam-capture will do the following:

  • Bind the bar variable to the value passed to the Foo: prompt, let’s say foobar,
  • call the org-roam-capture function with the n template as an argument,
  • the bar variable will then be replaced with the foobar value everywhere it is referenced in the n template.

Assuming I am correct, I definitely have a problem in my template with the way I reference variables. I tried adding the +org-roam-capture function as defined above, and called it with M-x. This is the n template I am using:

(setq! org-roam-capture-templates '(
       ("n" "note" plain "%?"
        :target (file+head "areas/%<%Y%m%d%H%M%S>-${slug}.org"
              "
:PROPERTIES:
:ID:            ${id}
:FOO:          %(bar)
:END:
#+title: ${title}
#+filetags: :%(bar):")
        :jump-to-captured
        )

Let’s say I enter foobar when prompted, I expect that:

:PROPERTIES:
:ID:            xxxxx-xxxxx-xxxx-xxxxx
:FOO:           foobar
:END:
#+title: My title
#+filetags: :foobar:

Instead I am getting this:

:PROPERTIES:
:ID:            xxxxx-xxxxx-xxxx-xxxxx
:FOO:           %![Error: (void-function bar)]
:END:
#+title: My title
#+filetags: :%![Error: (void-function bar)]:

Obviously, the %(bar) symbol I got from the original snippet calls the bar function instead of refrencing the bar variable. So I think this is the first problem I need to solve.

The second problem is that, to my current knowledge, all I can do with +org-roam-capture is bind it to key and call it when I need it. I do not understand how I can use it in the ‘normal’ capture process:

  1. I call org-roam-capture,
  2. I land on the dispatch,
  3. I type n to create a node with the template used here as an example,
  4. I get prompted for the value of the bar variable by the Foo: prompt,
  5. I type foobar RET once,
  6. enter My title, and I get the following result:
:PROPERTIES:
:ID:            xxxxx-xxxxx-xxxx-xxxxx
:FOO:           foobar
:END:
#+title: My title
#+filetags: :foobar:

Sorry if I am writing too much by the way, I am trying to be as clear as possible )

The amount of text is good. Your writing is exceptionally clear; it’s easy to follow. I feel you have a knack for it. Thank you.

Try %bar instead. The syntax is (function-name) to evaluate the function and variable to evaluate the variable. bar is a variable, so it the parentheses need to be removed.

The idea of this code snippet is to use the +org-roam-capture as a replacement of the built-in equivalent.
It would look like this. But the problem is you will always get prompted for “Foo”. And your 1-6 sequence indicates that you want to choose the select the template first, and if and only if you choose “n”, you want to get prompted.

(defun +org-roam-capture ()
  (interactive)
  (let ((bar (read-string "Foo: ")))
    (org-roam-capture)))

If the prompt needs to be conditional on the template you choose, I think you have two options:

  1. Use the global variable instead of the let-bound one, and pass it inside the template with calling a function inside (you would need to experiment and see if the program can tell what template is chosen).

  2. Override the function org-roam-capture-.

Both would require more time and skills… I have a feeling I have done this somewhere on this forum, but I can’t remember which thread (or I am mistaken).

Wait a second…

You only need the bar variable in one location in the template?

Have you tried this syntax in the capture template?

%^{prompt}

Sample here: (Using “Series” as the prompt text)

Sorry, never mind. You need bar to appear twice… Sorry, I guess it’s late in my time zone.

@lyndhurst Sorry, the last one for tonight. I hope this communicates the concept, at least.

(defvar bar nil)

(defun bar () ; same name but this is a function
  "Globally set `bar' to the value you enter in the prompt."
  (setq bar (read-string "Foo: ")))

(setq! org-roam-capture-templates '(
       ("n" "note" plain "%?"
        :target (file+head "areas/%<%Y%m%d%H%M%S>-${slug}.org"
              "
:PROPERTIES:
:ID:            ${id}
:FOO:          %(bar)
:END:
#+title: ${title}
#+filetags: :%bar:")
        :jump-to-captured
        )

The first bar is the function and you get the prompt.
The second bar evaluates the global variable.

You use the normal org-roam-capture and select the template “n”, which should call the bar function and you get the prompt. But for other templates, you don’t.

Thank you for all the pointers, they are very helpful. It was late for me too yesterday, so, I could not test, but by the looks of it, you seem to have perfectly understood what I was trying to achieve.

Below is the code I tried in my config which is basically the exact snippet you posted with two added parentheses at the end that my (very rude) Emacs was complaining about.

Emacs starts without any problem, I can call org-roam-capture which selects the template correctly. But, when I get to the Foo: prompt and press RET, I get an error that prints something like Error: max-depth lisp limit.

I could not get an exact copy of the message because Emacs gets kind of unresponsive with keys making the Foo: prompt pop up over and over in something that looks like some kind of infinite loop error.

I have to kill Emacs and comment out the snippet to restart it. Worst of all, I have to do it with my old Neovim install which feels like double treason every time that happens :smile:

(defvar bar nil)

(defun bar () ; same name but this is a function
  "Globally set `bar' to the value you enter in the prompt."
  (setq bar (read-string "Foo: ")))

(setq! org-roam-capture-templates '(
       ("n" "note" plain "%?"
        :target (file+head "areas/%<%Y%m%d%H%M%S>-${slug}.org"
              "
:PROPERTIES:
:ID:            ${id}
:FOO:          %(bar)
:END:
,#+title: ${title}
,#+filetags: :%bar:")
        :jump-to-captured
        )))

Just went back to the basics that appear in the linked original issue, and applied it here.
It works on my end now. I get the prompt for “Category” with the choices: “reference”, “creation”, “index”, and “meeting” – these are my real ones.

The %\\1 notation in #+filetags: takes the value I select for FOO.

No need for a custom variable or function. Do you want to try this?

Note that I don’t use Doom so I don’t have setq! (it’s a Doom-specific wrapper macro), so I used setq instead.

(setq org-roam-capture-templates
      '(("n" "note" plain "%?"
         :target
         (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                    ":PROPERTIES:
:ID:           ${id}
:FOO:          %^{Category|reference|creation|index|meeting}
:END:
#+title: ${title}
#+filetags: :%\\1:")
         :jump-to-captured)))


This is the solution I had been using so far as I stated in my initial post, I just did not know it could chain multiple default values. I must have missed it in the doc.

I feel like I wasted your time, sorry about that. I still learnt a lot during the process, so thank you again for all the help.

No worries.

More importantly, did the last snippet work?

What part was the struggle? What did you do that sent you off track?

More importantly, did the last snippet work?

If you are referring to the +org-roam-capture we have been talking about since yesterday, I have not made any more attempts since my (before) last post describing the max-depth lisp error I was getting, I have not made any other attempts.

On the other hand, if you are talking about your last update on the org-capture-template approach using the built-in %^{category} prompt, this works like a charm. I was already using it along with %\\1 to propagate values to multiple locations as I described in my initial post on this very thread.

What drew me to the more complex solution of building a function for the same purpose, was the possibility of setting multiple value as a choice and even completion. It definitely removes the friction of remembering what options can be set in a field and the risk of typos.

The alternative of adding a template for each option is a realistic one from the workflow angle as it is only one key to type (it even beats completion), but I could already feel the pain of maintaining a dozen templates. I am not a developer, but as someone who loves computers (linux actually) and basic coding, repeating so many lines for just one variable just does not cut it !

I was aware that I could set a default value to the built-in prompt but I missed the part in the doc explaining you could actually chain several like you did in your last post i.e. {Category|meeting|creation|index}.

In my mind, only {Category|index} was possible, which is nice, but too limited for this specific use case.

I do not seem to be able to reproduce the error.

This, as it is (removed the “,” in front of #+title: and #+filetags), produces the following, which does not correctly evaluate variable bar. But no error.

But changing the template to this below works as intended on my end (note the use of the function identity to return the value of bar as is; I think I did this in the other linked exchange.

You may want to try setq instead of Doom’s setq!; I have no idea what causes the “infinite loop”.

(setq org-roam-capture-templates
      '(("n" "note" plain "%?"
         :target (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                            ":PROPERTIES:
:ID:            ${id}
:FOO:          %(bar)
:END:
#+title: ${title}
#+filetags: :%(identity bar):")
         :jump-to-captured)))

I could not test earlier, because I was away from my PC this weekend, but thank you for giving it another try.

The commas (“,”) are indeed to be removed. I use the literate config module in doom emacs, and that is the only quirk I noticed so far, but every time I do a block editing or pasting within a template, a comma gets inserted at the beginning of all lines starting with #+. Ihave no idea why this happens, but those commas are not propogated to the ‘tangled’ config.el file, so I kind of stopped noticing them…

I tested the code with and without the identity function, using setq or setq!, and I always get the same outcome. The Foo: prompt never goes away and emacs freezes or starts acting up until I kill its process.

I already have a 1000+ lines of config, so I tested on a ‘clean’ empty file (with only the modules installed) to make sure no misconfiguration in there causes some kind of conflict.

If you are using Org for this, then the comma is just an escape character, so it’s no surprise.

No idea why your Emacs behave differently. I would test with emacs -q. I heard Doom provides a facility to let you do a test without the Doom-specific parts.

Great catch ! The link actually explains exactly what I was noticing. So it is not a quirk as I was saying, it is a feature. It also explains why the commas are not present on the destination (‘tangled’) file.

With my very limited knowledge of Emacs, I do not see why it would not work either. Since I am not going to migrate from Doom to regular Emacs anytime soon (the initial configuration has already been a steep learning curve as is), I do not see it very useful in that particular case to test further than a minimal Doom Emacs config for now, especially since this thread has already solved my original problem.

To be honest, I am also getting close to finish an initial working configuration that I could start using in ‘production’, and I am eager to migrate all my tasks and notes to orgmode. Moreover, I already identified a couple of elisp roadblocks during the initial research phase that I am not sure I will be able to deal with by myself, so I’d rather focus on finishing a first tentative configuration for the time being.

I am still curious about it though, so I will add to my TODOs to do some more testing later on so that I can at least say for sure if the problem is doom related and maybe take it to the Doom discourse site if relevant. I will anyway make sure to report back here if I find anything interesting.

In the meantime, thank you again for the quality and impressive celerity of the help you have provided me with for my first (but I suspect not last) cry for help on this site.

1 Like

I agree that getting what you want working is the main thing. Let’s not boil the ocean :slight_smile: