- this post in Org format: <https://github.com/tonyaldon/posts>
- email me: aldon.tony@gmail.com
- message me: <https://twitter.com/tonyaldon>
───────────────────────────────────────────
Hey Org mode lovers,
In the article [Hyperlink (wikipedia)](https://en.wikipedia.org/wiki/Hyperlink) we can read that a hyperlink "is
a reference to data that the user can follow by clicking".
Do you believe me if I tell you that with Org mode the data we refer
to in a link can be a buffer in `magit-revision-mode` (from [magit](https://github.com/magit/magit)
package) showing us a specific commit of some git repository?
For a non-Emacs user, it's not that you can or can't believe it, it's
just that this sentence doesn't make sense.
But for me (a regular Emacs user like you), when I read that sentence
I think:
1. Really! Emacs, Org, Magit, Buffer, Link! Integration.
Non-context switching. Maybe a bit of lisp. Extensible. Yes it
makes sense.
2. AFTER ALL THIS IS EMACS!
3. Show me how to do it!
The key elements to doing so lie in:
1. Org mode provides the built-in `elisp` type link that allows to
evaluate any Elisp s-exps or to call any commands when we open this
type of link with `org-open-at-point` (bound to `C-c C-o` by default),
2. and that the command `magit-show-commit` used to visit commits in
magit buffers when we are on a commit line can also be used non
interactively as a regular function where we have to provide it the
commit hash and the repository ("module").
First we look at the syntax of `elisp` type links.
A bracket link is a valid `elisp` type link if it starts with the
identifier (the type) `elisp`, then is followed by a colon `:` and then
followed by either an Elisp form in parentheses or an Elisp command.
For instance this link:
[[elisp:(+ 1 1)]]
when followed with `org-open-at-point` prints the following in the echo
area:
(+ 1 1) => 2
And this link:
[[elisp:tetris]]
when followed with `org-open-at-point` starts the built-in Tetris.
By default, the variable `org-link-elisp-confirm-function` is set to the
function `yes-or-no-p` which means that when we open an `elisp` link we
are asked for confirmation before executing the `elisp` link.
We can set this variable to `nil` if we never want to be asked for
confirmation. But it might be dangerous (see the docstring of this
variable for an example of a dangerous `elisp` link that can remove all
our user home directory).
Besides setting `org-link-elisp-confirm-function` to `nil` in order not to
be asked confirmation everytime we open an `elisp` link we can
use the variable `org-link-elisp-skip-confirm-regexp`.
Indeed, any `elisp` link that matches the regexp
`org-link-elisp-skip-confirm-regexp` is executed without asking
confirmation.
For instance, setting up the variable
`org-link-elisp-skip-confirm-regexp` to `"tetris"` lets us open the link
`[[elisp:tetris]]` without asking us confirmation.
(setq org-link-elisp-skip-confirm-regexp "tetris")
Now things are getting spicy :)
We add the Magit layer to our `elisp` link.
The scenario is the following. We are "working" on the Org repository
that we assume is cloned under the directory `/tmp/org-mode/`. At some
point we've looked at the commit `baffebbc3` that fixes a bug in
`org-get-heading` and we want to keep in our note a link to that commit.
To do so we can use the following link:
[[elisp:(magit-show-commit "baffebbc3" nil nil "/tmp/org-mode/")]]
Now, if we open that link with `org-open-at-point`, we jump to the
following buffer in `magit-revision-mode`:
baffebbc33e600ec7abfe5e54b60ea6753d4f272
Author: XXX <XXX@XXX.com>
AuthorDate: Fri Apr 30 14:09:05 2021 +0200
Commit: YYY <YYY@YYY.com>
CommitDate: Mon Apr 25 19:40:05 2022 +0800
Parent: 240a14988 Fix typo: delete-duplicates → delete-dups
Contained: main
Follows: release_9.5.3 (433)
Fix bug in org-get-heading
Fixes #26, where fontification could make the matching and extraction of heading
components fail.
modified lisp/org.el
@@ -6167,8 +6167,9 @@ Return nil before first heading."
(let ((case-fold-search nil))
(looking-at org-complex-heading-regexp)
;; When using `org-fold-core--optimise-for-huge-buffers',
- ;; returned text may be invisible. Clear it up.
- (org-fold-core-remove-optimisation (match-beginning 0) (match-end 0))
+ ;; returned text will be invisible. Clear it up.
+ (save-match-data
+ (org-fold-core-remove-optimisation (match-beginning 0) (match-end 0)))
(let ((todo (and (not no-todo) (match-string 2)))
(priority (and (not no-priority) (match-string 3)))
(headline (pcase (match-string 4)
TAKING NOTE WITH EMACS/ORG-MODE IS F\*\*\*ING CRAZY.
DO YOU AGREE???
Now that we've seen how to use `elisp` type links with practical
examples, let's see how they are implemented.
Except for the reserved link types `coderef`, `custom-id`, `fuzzy` and
`radio` we can defined new link types or modify an existing link types
using the function `org-link-set-parameters`.
For instance the link type `elisp` is defined like this in the file
`ol.el`:
(org-link-set-parameters "elisp" :follow #'org-link--open-elisp)
Fine, but what does it mean to define a link type?
It means that we add an entry to the alist `org-link-parameters` where
we specify for a link type how we want the Org mode features related to
links to behave regarding that type.
With the default configuration the variable `org-link-parameters`
looks like this (with some link types skipped):
(("eww" :follow org-eww-open :store org-eww-store-link)
...
("info" :follow org-info-open :export org-info-export :store org-info-store-link)
...
("id" :follow org-id-open)
...
("shell" :follow org-link--open-shell)
...
("help" :follow org-link--open-help :store org-link--store-help)
("file" :complete org-link-complete-file)
("elisp" :follow org-link--open-elisp))
For instance, the variable `org-link-parameters`
tells org-mode that when we open an `elisp` type link with
`org-open-at-point` (bound to `C-c C-o` by default) the function that
finally opens the link is `org-link--open-elisp` (the one after the
keyword `:follow` in the variable `org-link-parameters`).
And for the `info` type links (links to info node) the variable
`org-link-parameters` tells org-mode:
1. to use the function `org-info-open` when we open `info` type links,
2. to use the function `org-info-export` when we export open `info` type
links,
3. to use the function `org-info-store-link` when we store `info` type
links.
If we want to write our own type link we can look at the docstring of
`org-link-parameters` to know what are the supported keys
(`:follow`, `:export`, `:store`, `:activate-func`, `:complete`,
`:display`, `:face`, `:help-echo`, `:htmlize-link`, `:keymap`, `:mouse-face`)
and what are their accepted values.
Back to `elisp` type links.
When we call `org-open-at-point` (bound to `C-c C-o` by default) without
universal argument on the following `elisp` type link:
[[elisp:(+ 1 1)]]
in its body, `org-open-at-point` parses the link at point and
"delegates" the work to the function `org-link-open` passing it as
first argument the parsed link like this:
(org-link-open
(link
(:type "elisp"
:path "(+ 1 1)"
:format bracket
:raw-link "elisp:(+ 1 1)"
...
:parent (paragraph ... :parent (section ... :parent (org-data ...)))))
nil ;; called without universal argument
)
Then the function `org-link-open` locally binds the variables `type` and
`path` respectively to `"elisp"` and `"(+ 1 1)"` getting those values out of
the link `(link (:type "elisp" :path "(+ 1 1)" ...))` using the function
`org-element-property`.
Then as `type` is not equal to any of the following types `"file"`,
`"coderef"`, `"custom-id"`, `"fuzzy"` and `"radio"` but equal to `"elisp"` the
function `org-link-open` decides to use the dedicated function
`org-link--open-elisp` to open `elisp` type links.
This is done by locally binding `f` to `org-link--open-elisp` retrieving
this value using the function `org-link-get-parameter` which is a thin
wrapper around the variable `org-link-parameters` as we can see in the
following code snippet
(defun org-link-get-parameter (type key)
"..."
(plist-get (cdr (assoc type org-link-parameters)) key))
and as `org-link--open-elisp` is a function that accepts two arguments,
`org-link-open` finally returns with the following call:
(funcall 'org-link--open-elisp "(+ 1 1)" nil)
Here are the parts of `org-link-open` we've just discussed:
(defun org-link-open (link &optional arg)
"..."
(let ((type (org-element-property :type link))
(path (org-element-property :path link)))
(pcase type
("file"
...)
((or "coderef" "custom-id" "fuzzy" "radio")
...)
(_
;; Look for a dedicated "follow" function in custom links.
(let ((f (org-link-get-parameter type :follow)))
(when (functionp f)
;; Function defined in `:follow' parameter may use a single
;; argument, as it was mandatory before Org 9.4. This is
;; deprecated, but support it for now.
(condition-case nil
(funcall (org-link-get-parameter type :follow) path arg)
(wrong-number-of-arguments
(funcall (org-link-get-parameter type :follow) path)))))))))
Now we are left with the function `org-link--open-elisp`.
Assuming we answer `yes` to the question raised by calling
`org-link-elisp-confirm-function` in the condition of the `if` form, the
THEN part of the `if` is evaluated.
As the `path` equal to `"(+ 1 1)"` starts by a left parentheses `(`, the
string `"(+ 1 1)"` is "transformed" into a lisp object by the function
`read`, then is evaluted by the function `eval`, and its result is
substituted in the second placeholder `%s` in the string of the
`message` function.
Here are the parts of `org-link--open-elisp` we've just discussed:
(defun org-link--open-elisp (path _)
"..."
(if (...)
(message "%s => %s" path
(if (eq ?\( (string-to-char path))
(eval (read path))
(call-interactively (read path))))
(user-error "Abort")))
And this is how the message `(+ 1 1) => 2` is printed in the echo area
when we call `org-open-at-point` on top of the `elisp` type link `[[elisp:(+
1 1)]]`.
WE ARE DONE!!!
If this is the first time you have seen the `read` and `eval` functions,
you may be interested in these examples:
(read "(+ 1 2)") ; (+ 1 2)
(type-of (read "(+ 1 2)")) ; cons
(functionp (read "(+ 1 2)")) ; nil
(type-of (eval (read "(+ 1 2)"))) ; integer
(read "(lambda () (+ 1 2))") ; (lambda nil (+ 1 2))
(type-of (read "(lambda () (+ 1 2))")) ; cons
(functionp (read "(lambda () (+ 1 2))")) ; t
(type-of (eval (read "(lambda () (+ 1 2))"))) ; cons
(read "foo") ; foo
(type-of (read "foo")) ; symbol
(type-of (eval (read "foo"))) ; error: (void-variable foo)
(read "\"bar\"") ; "bar"
(type-of (read "\"bar\"")) ; string
(type-of (eval (read "\"bar\""))) ; string