How to dynamically create ID to other files?

I have many org-roam v2 files and want to link subheadings from each other. Currently, the approach I am following is-

  1. Use org-store-link to generate ID for the (sub) heading in file A.
  2. Use org-roam-node-insert to insert the previously generated ID in file B.

I wish to automate these 2 steps into 1. There should be a way in which I can select all the subheading in all org roam files and just select it to insert a link to it. If there was no ID property to it, then create it, else, use it.

I have found counsel-org-link to work similarly according to my requirement, but, it works for only a single file. I want to work with multiple files. Please help.

1 Like

I think you’d need to create your own code.

Below is an outline of the program I’d make.

You’d need a basic understanding of Elisp and how marker works (easy), and familiarity with libraries org-element and completing-read (will take time…).

It is not difficult, but time-consuming to get right (this is as far as I can do).

  • Function org-roam-list-files gives you a list of all the files in a list
  • For each .org file in the list, get all the headline elements (you should be able to use the built-in org-element library to parse each .org file and get the headlines with the marker (file and position) and headline title
  • Create a minibuffer selection. This would depend on your completion framework (especially Ivy/Counsel), but for Vertico/Consult, it would be completing-read (you can see how completing-read does this)
  • Based on the user’s selection of the headline from the minibuffer, you can visit the file and headline, and get the existing ID or create an new one (eg org-id-get-create)
    EDIT: Sorry, you mention org-store-link – I think it will work too, assuming your configuration is correctly set to create an ID when it does not exist
  • At this stage, the new ID is not in the org-roam-db, so you’d need construct the ID link (not difficult because you have the ID and the headline title)
    EDIT: If you use org-store-link, then you should be able to use org-insert-link for the ID
1 Like

I like the idea - I already have a way to parse all the headings of a file - I think I can code this.
But we have to solve a few problems. Firstly if we search for all the headings in all the files the process might be cpu intensive - can you work with first selecting the file then getting a list of the subheadings and headings?

or do you want to arbitarily search for all the subheadings and headings? We can cook something up. Lets work on it - let me know how you conceive of the workflow.

An alternative is to use regex for the headers in the files in org-roam-dir. This is probably better for performance than my first suggestion to use org-element to parse the files.

1 Like

Consult ripgrep can be utilised to search for all the headlines efficiently. The glob specifies the exclusion parameters to exclude from search. The rest is just tacking a org-id creation and insert mechanism.

(defun 0/find-headline ()
  (interactive)
  (let ((consult-ripgrep-args "rg --null --ignore-case --type=org --glob=!{org-exports,guides,journal,resources,references} --line-buffered --color=never --line-number"))
    (consult-ripgrep org-roam-directory "^\\*+\\s# ")))


(defun 0/insert-headline ()
  (interactive)
  (save-excursion
    (let* ((current-buffer (current-buffer))
	   (consult-ripgrep-args "rg --null --ignore-case --type=org --glob=!{org-exports,guides,journal,resources,references} --line-buffered --color=never --line-number")
	   (headline (consult-ripgrep org-roam-directory "^\\*+\\s# ")))
      (when headline
	(let* ((headline-text (org-get-heading t t t t))
	       (id (org-id-get-create)))
	  (save-buffer)
	  (switch-to-buffer current-buffer)
	  (insert (format "[[id:%s][%s]]" id headline-text)))))))
1 Like