Elisp Posts

org-mode links everywhere, not only in org-mode buffers | THIS IS EMACS

2022-05-04
/Tony Aldon/
comment on reddit
/
emacs revision: de7901abbc21
/
org-mode revision: af6f1298b6f6

Hey Emacsers,

What a great day to talk about EMACS :)

Let's set the scene!

We've just written a new command named my-new-command that we've added to our init file ~/.emacs.d/init.el (or whatever init file your are using except org-mode file).

To write our new command we had to look at the help of the macro pcase and the elisp info node "Current Buffer".

Now let's say we want to keep the links to these help and info buffers in comments near to the definition of our new command.

Is is possible? How do we do that?

Org links everywhere

One way to do that is to use the org-mode link syntax using the identifier info and elisp (described in the info node org#Link Abbreviations) like this:

;; [[help:pcase]]
;; [[info:elisp#Current Buffer]]
(defun my-new-command () "..." ...)

Now we can use the command org-open-at-point-global to open those links.

For instance, running M-x org-open-at-point-global on top of the link [[help:pcase]] (in the previous emacs-lisp buffer) pops up the following help buffer:

pcase is a Lisp macro.

(pcase EXP &rest CASES)

  Probably introduced at or before Emacs version 24.1.

Evaluate EXP to get EXPVAL; try passing control to one of CASES.
CASES is a list of elements of the form (PATTERN CODE...).
For the first CASE whose PATTERN "matches" EXPVAL,
evaluate its CODE..., and return the value of the last form.
If no CASE has a PATTERN that matches, return nil.

...MORE HERE...

For instance, running M-x org-open-at-point-global on top of the link [[info:elisp#Current Buffer]] (in the previous emacs-lisp buffer) pops up the following info buffer:

File: elisp.info,  Node: Current Buffer,  Next: Buffer Names,  Prev: Buffer Basics,  Up: Buffers

28.2 The Current Buffer
=======================

There are, in general, many buffers in an Emacs session.  At any time,
one of them is designated the “current buffer”—the buffer in which most
editing takes place.  Most of the primitives for examining or changing
text operate implicitly on the current buffer (*note Text::).

   Normally, the buffer displayed in the selected window is the current
buffer, but this is not always so: a Lisp program can temporarily
designate any buffer as current in order to operate on its contents,
without changing what is displayed on the screen.  The most basic
function for designating a current buffer is ‘set-buffer’.

...MORE HERE...

Thanks to org-open-at-point-global we have org-mode links everywhere!

What do you think about that "feature"?

Isn't it super cool!?

How does org-open-at-point-global work?

To understand the mechanism of org-open-at-point-global, let's see what happens when we call org-open-at-point-global on top of the link [[help:pcase]] in the previous emacs-lisp buffer?

The function org-open-at-point-global checks if point is inside a match of the regexp org-link-any-re (regular expression matching any link) using the function org-in-regexp.

As it is true (the regexp org-link-any-re is matched in the string [[help:pcase]]), the function org-open-at-point-global returns with the call:

(org-link-open-from-string "[[help:pcase]]")

Here are the parts of org-open-at-point-global we've just discussed:

(defun org-open-at-point-global ()
  "..."
  (interactive)
  (let (...)
    (cond ((org-in-regexp org-link-any-re)
           (org-link-open-from-string (match-string-no-properties 0)))
          ...
          (t (user-error "No link found")))))

So far, nothing impressive. We match something at point and apply a function on that thing.

True!

The magic happens in the function org-link-open-from-string where we use a temporary buffer in org-mode where we insert the string [[help:case]] and leverage the Org parser calling the function org-element-link-parser at the beginning of the inserted link which parses the link and returns this org object link:

(link
 (:type "help"
  :path "pcase"
  :format bracket
  :raw-link "help:pcase"
  :application nil
  :search-option nil
  :begin 1
  :end 15
  :contents-begin nil
  :contents-end nil
  :post-blank 0))

which is passed to the function org-link-open (which returns) like this:

(org-link-open
 '(link (:type "help"
         :path "pcase"
         :format bracket
         :raw-link "help:pcase"
         ...)))

And the function org-link-open "takes care" to open the help buffer describing the macro pcase.

Here is the definition of the function org-link-open-from-string:

(defun org-link-open-from-string (s &optional arg)
  "Open a link in the string S, as if it was in Org mode.
Optional argument is passed to `org-open-file' when S is
a \"file\" link."
  (interactive "sLink: \nP")
  (pcase (with-temp-buffer
           (let ((org-inhibit-startup nil))
             (insert s)
             (org-mode)
             (goto-char (point-min))
             (org-element-link-parser)))
    (`nil (user-error "No valid link in %S" s))
    (link (org-link-open link arg))))

org-open-at-point-global and link abbreviations

As we've seen in the post Did you know that Org links in property drawers are not links?, in org-mode "links" matched by the regexp org-link-any-re are not always treated as link by the Org parser.

For instance, the URL https://orgmode.org/worg/ in the property drawer of the following org-mode buffer is not a link for the Org parser but the :value of a node-property element:

* Heading
:PROPERTIES:
:MY_URL:   https://orgmode.org/worg/
:END:

And so when we call the function org-open-at-point (C-c C-o by default) on a "link" in a property drawer, org-open-at-point won't call directly the function org-link-open on the node-property element which is not a link.

The function org-open-at-point falls back by calling interactively the function org-open-at-point-global with the point in the property drawer on top of the "link".

It works fine for every links but for link abbreviations when the abbreviation is set locally with #+LINK: like in this example:

#+LINK: worg   https://orgmode.org/worg/

* Heading
:PROPERTIES:
:MY_URL: worg
:END:

This is normal if you look at implementation of org-open-at-point-global.

As we've seen above, the function org-open-at-point-global creates a temporary org-mode buffer to parse the link. So only the variable org-link-abbrev-alist which is global is "taken into account", not the local variable org-link-abbrev-alist-local which value is nil in that case.

Ignacio Casso and Ihor Radchenko discussed this case in the org-mode mailing list in the thread:

https://lists.gnu.org/archive/html/emacs-orgmode/2022-04/msg00367.html

And Ignacio Casso has a workaround for that case that uses advices:

https://lists.gnu.org/archive/html/emacs-orgmode/2022-04/msg00449.html

If you are interested in link abbreviations you can read:

WE ARE DONE!!!