FULL example of org-mode links: internal links and search options
I hope you are doing well :)
My goal was to walk the path from pressing
RET (if you have
org-return-follows-link set to
C-c C-o on top of the abbreviated
[[emacs:lisp/simple.el::(defun next-error (&optional]]
to the file
lisp/simple.el in the Emacs source code (cloned locally)
with the point at the beginning of the function
We have seen that this path follows the following "call stack":
org-open-at-point │ └> org-link-open │ └> org-link-open-as-file │ └> org-open-file │ └> org-link-search
As the post has become bigger than I expected, I decided not to talk about the last call to the function org-link-search, despite my desire to do so.
Some time later, I decided to give it a chance. I looked org-link-search in the eyes and I saw that I won't be able to give a clear explanation without talking a least in one post about the catch/throw pattern in Elisp. So, I postponed it and wrote the post A tour of the catch/throw pattern in the Emacs source code (2022-04-13) in which we discuss the catch/throw pattern.
And today I'm trying to give a clear explanation of its implementation and I realized that the way to do it is not by showing bits of code and talking about those bits.
There is too much details to bring some value doing it like this (at least, I still haven't figured out how to do it that way for that function).
So let's try another approach that may give you some "tools" to examine its implementation if you are interested.
Next, we give some practical examples demonstrating part of the Elisp API dealing with strings and regexps functions that are used in org-link-search.
Full example of org links that use org-link-search when followed
Notes on the example:
For three of those links to work you need to cloned the org-mode
repository under the directory
/tmp/org-mode/. You can do this by
running the following command:
cd /tmp/ && git clone git://git.sv.gnu.org/emacs.git
The example is written inside a unique source block (org-mode), so you have several options to take advantage of it:
if you are reading this post on Reddit, copy/paste the block in an org-mode buffer and start playing with it,
if you are reading this post from inside Emacs (using this document https://github.com/tonyaldon/posts) with the point inside the source block you can hit
org-edit-specialby default) and start playing with the example in a org-mode buffer.
For convenience and to make the following example almost
selfcontained, we use for instance the link
[[#custom-id-2]] that is of
custom-id instead of demonstrating the use of org-link-search
with a link like this
[[/path-to-file.org::#custom-id-2]] which is of type
file with the search option component equal to
But it doesn't matter much because, in the end, both are treated
the same way by the same function org-link-search, and the exact last
call in both cases is
In the example, the links are presented in the same order as they are
treated in the
cond special form in the body of org-link-search. If
you finally decide to look at the implementation of org-link-search,
you'll be able to follow the "flow" in the implementation following
the "flow" of the example or vice-versa, maybe side by side using two
different buffers (by the way this is how I built the example, side by
Lastly, the info node related to this example are the following:
Here is the example (if you're reading in your browser, you don't
see the double brackets around links, for instance the first link is
represented like this
#custom-id-2, but is instead
(ref:coderef) doesn't appear in the first source block):
* headline 1 With link we jump (with org-open-at-point) in this document to the heading with the :CUSTOM_ID property equal to custom-id-2. With link we jump (with org-open-at-point) to (ref:coderef) in the source block below: #+BEGIN_SRC emacs-lisp (let ((x 2)) (1+ x)) #+END_SRC Let assume we have the org repository cloned under the directory /tmp/org-mode/. With we jump (with org-open-at-point) to an Occur buffer like this one #+BEGIN_SRC text 6 matches for "org-link-search" in buffer: ol.el 340:(defcustom org-link-search-must-match-exact-headline 'query-to-create 1093: (org-link-search 1132:(defun org-link-search (s &optional avoid-pos stealth) 1245: ;; `org-link-search-must-match-exact-headline'. 1247: (eq org-link-search-must-match-exact-headline 'query-to-create) 1257: (or starred org-link-search-must-match-exact-headline)) #+END_SRC that matches the occurences of org-link-search~(the text surrounded by two slashes / after the two colons :: in the previous link) in the file /tmp/org-mode/lisp/ol.el. With link we jump (with org-open-at-point) to the target <<target>> that is located in the first paragraph of the section headline 2. With link we jump (with org-open-at-point) to the previous source block that is named a named source block via the statment #+NAME: a named source block (because there is no target <<a named source block>> in the document). With link we jump (with org-open-at-point) to the next headline headline 2, whatever the value of the variable org-link-search-must-match-exact-headline, because: 1) this headline exists, 2) there is no target <<headline 2>> in the document, 3) there is no named block, named paragagraph, etc. (see Affiliated Keywords in the org syntax) named with the statment #+NAME headline 2. This paragagraph named headline 3 contains a target <<headline 3>>, so we can't use the link to jump to the existing headline headline 3. But, we can use the following link (starting with a star *) to jump to the existing headline headline 3. And this work whatever the value of the variable org-link-search-must-match-exact-headline. When org-link-search-must-match-exact-headline is set to query-to-create (which is the default value) #+BEGIN_SRC emacs-lisp (setq org-link-search-must-match-exact-headline 'query-to-create) #+END_SRC calling org-open-at-point on link offers to create a new headline headline 4 at the end of this org document. If we choose to add it we will jump to that new headline and if not nothing happens and we don't move. When org-link-search-must-match-exact-headline is set to something other than query-to-create, for instance t or nil like this: #+BEGIN_SRC emacs-lisp (setq org-link-search-must-match-exact-headline nil) ;; or (setq org-link-search-must-match-exact-headline t) #+END_SRC calling org-open-at-point on the link (starting with a star *) that points to a non existing headline raises the following error: : No match for fuzzy expression: * headline 5 When org-link-search-must-match-exact-headline is set t #+BEGIN_SRC emacs-lisp (setq org-link-search-must-match-exact-headline t) #+END_SRC calling org-open-at-point on the link (and there is no existing headline headline 5, there is no target <<headline 5>> and there is no named element headline 5 in the document) raises the following error: : No match for fuzzy expression: * headline 5 When org-link-search-must-match-exact-headline is set nil #+BEGIN_SRC emacs-lisp (setq org-link-search-must-match-exact-headline nil) #+END_SRC calling org-open-at-point on the link (and there is no existing headline headline 5, there is no target <<headline 5>> and there is no named element headline 5 in the document) jumps to first occurence of headline 5 in the current document. We still assume we have the org repository cloned under the directory /tmp/org-mode/. With we jump (with org-open-at-point) to the first occurence of org-link-search in the file /tmp/org-mode/lisp/ol.el which happens to be on the variable org-link-search-must-match-exact-headline: #+BEGIN_SRC emacs-lisp (defcustom org-link-search-must-match-exact-headline 'query-to-create ...) #+END_SRC Note that as the file /tmp/org-mode/lisp/ol.el is not "open" in org-mode, org-link-search does a fuzzy text search and doesn't look for target, named elements or headlines. If the search option (or internal links) we've used doesn't "match" one of the previous search, org-link-search raises an error. We still assume we have the org repository cloned under the directory /tmp/org-mode/. With we jump (with org-open-at-point) to jump to the line 5 in the file /tmp/org-mode/lisp/ol.el. Although this link is of type file with its search option equal to 5, the "jump" isn't done by org-link-search but by org-goto-line in the function org-open-file. * headline 2 :PROPERTIES: :CUSTOM_ID: custom-id-2 :END: I'm the <<target>>! * headline 3
Elisp API dealing with strings and regexps
Remember that when we are working with Elisp we can obtain information
about any symbols with the command describe-symbol bound by default to
(let ((case-fold-search t)) (string-match "FOO" "foo")) ; 0 (let ((case-fold-search nil)) (string-match "FOO" "foo")) ; nil
In org-link-search, the search string
s provided can contain newlines
followed by any numbers of spaces or tabs. Those patterns are
replaced by one space. This is done using the function
replace-regexp-in-string like this:
(let ((s "search \n \t\t option")) (replace-regexp-in-string "\n[ \t]*" " " s)) ;; "search option"
The search option string
s given to org-link-search can start:
with a star
*when we search specifically a headline (for instance in the link
[[* headline 3]]used in the above example) or,
with a hash
#when we search a custom id (for instance in the link
[[#custom-id-2]]used in the above example)
The function string-to-char returns the first character of a string and we can use like this:
(string-to-char "*foo") ; 42 ?* ; 42 (string-to-char "#bar") ; 35 ?# ; 35 (eq (string-to-char "* headline 3") ?*) ; t (eq (string-to-char "#custom-id-2") ?#) ; t
In org-link-search, the searches are not done directly against its
s but against different regexps depending on the context
The function split-string is used to split one of the string into substrings bounded by whitespace like this:
(split-string "foo bar baz") ; ("foo" "bar" "baz")
When the given string
s starts with a star
*, the star is removed
using the function substring like this:
(substring "*foo" 1) ; "foo"
The searches are done using the function re-search-forward that searches in the current buffer for regular expression. So, we have to be careful when we give it a string to search for, that string must be a regexp.
that returns a regexp string which matches exactly the string we gave it and this is how org-link-search does it.
For instance, the characters
* are "special" in regexps, so
if we want to match them in a regexp we must escape them and we can do
it using the function regexp-quote like this:
(regexp-quote "foo.bar") ; "foo\\.bar" (regexp-quote "foo+bar") ; "foo\\+bar" (regexp-quote "foo?bar") ; "foo\\?bar" (regexp-quote "foo*bar") ; "foo\\*bar"
We can go on and on but we won't, I think that's enought to get started :)
WE ARE DONE !!!
I take the opportunity of this post to thank Ihor Radchenko for his work on org-mode.
In addition to his contributions he always answers quickly in https://orgmode.org/worg/org-mailing-list.html.
Thank you Ihor Radchenko.