5

I am beginning to use org-mode as an electronic lab notebook, which is working very well. I work on a number of different projects, each of which has its own directory. When I use org-capture, I want the task, etc., to be written to an .org file in the directory for the current project.

So if I am working in my org lab notebook for Project A and I capture a task, I want that task to go to the capture.org file in the directory for Project A, but when I capture a task from my notebook for Project B, I want the task written to capture.org in that directory instead.

It seems like I could do this with different templates, using something along these lines.

(setq org-capture-templates
'(("t" "Todo" entry (file "~/project_a/capture.org")...)
  ("u" "Todo" entry (file "~/project_b/capture.org")...)
  ("v" "Todo" entry (file "~/project_c/capture.org")...)))

The downside of this is that I have to remember or check a lot of different templates each time, making it more distracting to capture something, especially when I'm working on a lot of different projects.

An alternative would be to always insert the capture into the buffer I'm currently working on, but I'd rather not do that.

I think I could also just have a small piece of executable code at the beginning of each notebook that defines all the captures, but there must be a better way than that.

I suspect that there may be some way to do this by giving the target as a function, but I can't figure out how to do that, and I'm not sure whether it's the best way.

What would be ideal is that any capture done in ~/project_a or any of its subdirectories would go to ~/project_a/capture.org, but I can easily live with the customization applying only to the specific file (~/project_a/notebook.org) that I use as my notebooks, since I'll almost always have that open.

1

3 Answers 3

1

You can do it with advice. The code below works and will actually just set the org capture directory to a relative path as in the original answer, but by changing the definition of my-org-capture-notes-file, you can define the org-default-notes-file to be whatever you want based on the file org-capture was called from.

Note that this code will crash if, for some reason, you call org-capture from a buffer without an associated file name (such as a shell), but you could easily fix that by checking if buffer-file-name is empty.

(defun my-org-capture-notes-file (path)
  ;write a function here to define your org capture file based on a path.
  ;toy example below for proof-of-concept (does same thing as relative path)
  (concat (file-name-directory path) "org/notes.org"))

(defun my-org-capture-notes-file-from-buffer (&rest args)
  (setq org-default-notes-file (my-org-capture-notes-file buffer-file-name)))

(advice-add 'org-capture :before 'my-org-capture-notes-file-from-buffer)
2
  • Thanks so much Zorgoth! Could you clarify what the last line does?
    – njc
    Apr 3, 2019 at 19:23
  • 1
    The last line adds "advice" that causes my-org-capture-notes-file-from-buffer to be called before org-capture, whenever org-capture is called. my-org-capture-notes-file-from-buffer will receive the same arguments as org-capture, which is the reason for the &rest args. We don't use any of these arguments in this particular application of advice, but if we didn't put them somewhere, we would get an error. For more about advice, see gnu.org/software/emacs/manual/html_node/elisp/…
    – Zorgoth
    Apr 3, 2019 at 19:57
0

This is an imperfect solution, but good enough for my purposes (and the only one I've been able to come up with).

When you set the location of your capture file in your .emacs file, you can set a relative path, which will start from the location of the org file.

So I added this to my .emacs file.

(setq org-default-notes-file "org/capture.org")

The downside is that the relative path has to be the same for all org files. This works for me, because I'm structuring all my project directories the same way, but if someone can come up with a more flexible solution, I'd love to hear it.

0

This issue came up with a slightly different motivation: we wanted to be able to create agenda files that can be shared across collaborators for a particular repository.

My solution is to create a directory local variable called org-local-notes-file and set this appropriately if necessary. The following elisp snippet does this and adds it for the "Local Tasks" template.

(setq org-default-notes-file (concat org-directory "/notes.org"))

(defun my/org-local-get-target ()
  (if (boundp 'org-local-notes-file)
      (expand-file-name org-local-notes-file)
    (if (y-or-n-p "A local org note doesn't exist. Create/choose one? ")
        (progn
          (add-dir-local-variable
           nil 'org-local-notes-file
           (read-file-name "Create/choose local org notes file:"))
          (save-buffer) (kill-buffer)
          (hack-dir-local-variables-non-file-buffer)
          (when (y-or-n-p "Add it to global agenda?")
            (add-to-list 'org-agenda-files org-local-notes-file))
          org-local-notes-file)
      org-default-notes-file)))

;; set variable org-local-notes-file locally                                                                                                                                                  
(setq org-capture-templates
      '(("t" "Todo" entry (file+headline "~/org/notes.org" "Tasks")
         "* TODO %?\n Dated: %u\n Location: %a")
        ("j" "Journal" entry (file+datetree "~/org/journal.org")
         "* %?\nEntered on %U\n  %i\n  Location: %a")
        ("l" "Local Tasks" entry (file+headline my/org-local-get-target
                                                "Local")
         "* TODO %?\n Dated: %u\n Location: %a")
        ))

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.