One of the many selling point of using Org-mode as a personal wiki (aka digital garden, see previous post) is its (hyper)link feature.

As we’ve seen previously, we can define internal links between Org-file (and/or their outlines), but also external links targeting external files & sytems. Those can either point to stuff openable in Emacs itself (e.g. link to files, to a shell command…) or external applications if configured as such (e.g. web pages).

URL abbrevs

Conveniently, pasted URL are directly resolved as http(s) links.

As usually, we may have a bunch of links to a few websites, it can be convenient to be able to have those in a shorter form.

Conveniently, Org-mode support link abbreviations.

For example, we may define our list of recurrent linked website.

(setq org-link-abbrev-alist '(("gh" . "https://github.com/")
                              ("gl" . "https://gitlab.com/")
                              ("hn" . "https://news.ycombinator.com/item?id=")
                              ("lines" . "https://llllllll.co/t/")
                              ;; [...]
                              ("thing" . "https://www.thingiverse.com/thing:")))

One would just have to type [[gh:emacs-mirror/emacs]] instead of https://github.com/emacs-mirror/emacs to insert a link to Emacs’ github mirror.

Even faster & shorter URL abbrevs

The above solution is nice if we use org-insert-link to insert links.

But I personally find this command cumbersome. It prompts me 3 times (link type, actual link, description).

I want to go faster and just having to type gh:emacs-mirror/emacs to get a valid link.

One way to achieve this is to declare custom link types.

The solution becomes:

(require 'dash)

(setq my-org-link-abbrev-alist '(("gh" . "https://github.com/")
                                 ;; [...]
                                 ("thing" . "https://www.thingiverse.com/thing:")))

(--each my-org-link-abbrev-alist
  (let* ((link-prefix (car it))
         (browse-fn `(lambda (e)
                       (let ((org-link-abbrev-alist my-org-link-abbrev-alist))
                         (browse-url (org-link-expand-abbrev (concat ,link-prefix ":" e)))))))
    (org-link-set-parameters link-prefix :follow browse-fn)))

Please note that we renamed org-link-abbrev-alist to my-org-link-abbrev-alist to prevent having duplicated prefix entries if we ever want to call org-insert-link.

Most of the time, I just copy/paste an URL into an Org buffer.

Wouldn’t it be convenient if it would automagically shorten it if it correspond to a known abbrev?

Thankfully, this is relatively trivial by advising org-yank.

(require 's)

(defun my-org-link-apply-prefix (txt)
  "Rework link TXT, swapping prefix w/ shorted one if matches `my-org-link-abbrev-alist'."
  (let ((prfx (--some (and (s-starts-with? (cdr it) txt) (not (string= (cdr it) txt)) it) my-org-link-abbrev-alist)))
    (if prfx
        (s-replace (cdr prfx) (concat (car prfx) ":") txt)
      txt)))

(defadvice org-yank (around prf/org-yank-prefix-link activate)
  "Advice around `org-yank' that will auto-compact current entry in `kill-ring' if it matches `my-org-link-abbrev-alist'."
  (let* ((kill (or (and kill-ring (current-kill 0)) ""))
         (new-kill (my-org-link-apply-prefix kill)))
    (unless (s-blank? new-kill)
      (kill-new new-kill t))
    ad-do-it))

Please not that my-org-link-apply-prefix only covers “basic” prefix abbrevs. It doesn’t support formatted abbrevs (%s, %h) nor formatting using a custom function (%(<CUSTOM-FN>) for which we’d need the inverse function).

Conclusion

As always, this shows how flexible Emacs is and how expressive Elisp can be (once you are used to its quirks).

We showcased a very basic example of custom links. One could really go crazy with those.

For more in-depth examples, check out this article from the The Kitchin Research Group blog. They wrote a bunch of advanced articles about Org, notably about Babel (Org’s JupyterLab equivalent).


Tagged #emacs, #elisp, #productivity.