6
\$\begingroup\$

Motivation

Previously, on the Emacs stack exchange:

In Org mode, when I open a link (C-c C-o) [...] [that] contains a wildcard, such as file:3_o*.pdf, Emacs opens it through dired instead [...] within Emacs.

I would instead have it open the first match of that pattern in the system application …

The handling of file: links is hard-coded in Org mode. If there is any wildcard in the given path, then org-link-open will nope out of the situation and instead call dired. And while one could change Org mode's source code in the local elisp files, I remembered that you can change the arguments or results of functions with advices.

Solution

I opted for an advice :around org-link-open with the following code:

(defun my-org-expand-file-link (link)
  "Expand a pattern in LINK to the first matching file.

Returns the original LINK if no file matches the pattern in LINK or if it is not
a link of type 'file:'.  See info node `(org)External links' for more information."
  (let ((type (org-element-property :type link))
        (path (org-element-property :path link)))
    (cond
     ((equal type "file")
      (let ((candidates (file-expand-wildcards path)))
        (if candidates
            (org-element-put-property (org-element-copy link) :path (car candidates))
          link)))
     (t link))))

(defun my-org-link-open-advice (orig-fun link &rest args)
  "Advice for ORIG-FUN that expands patterns in 'file:' LINKs.
See `my-org-expand-file-link' for more information.
Optional argument ARGS are passed as-is."
  (apply orig-fun (my-org-expand-file-link link) args))

(advice-add 'org-link-open :around #'my-org-link-open-advice)

So whenever you open a link with C-c C-o (org-open-at-point which calls org-link-open), you end up with a concrete file.

link expansion

Some remarks

I recommend using a custom link type like first: or wrapping the code above minor mode, as the advice is somewhat invasive and might surprise users that really want to get a list of matching files instead.

I'm open to any feedback on the elisp code, especially since I'm still a elisp newbie.

\$\endgroup\$
2
+50
\$\begingroup\$

That's a cool feature I've to say. And the code looks excellent, I suppose you could argue for one or the other wrt. cond vs. if etc. but that's pointless pedantry IMO.

Given that it works flawlessly (tried locally and via tramp as well), I was unsure there was actually anything to say here, but I found two points that might be of interest:

Firstly, while the :around combinator works fine, it might be more appropriate to use :filter-args, since it's really just filtering the arguments:

(defun my-org-link-open-advice (args)
  "Advice for ORIG-FUN that expands patterns in 'file:' LINKs.
See `my-org-expand-file-link' for more information.
Optional argument ARGS are passed as-is."
  (list* (my-org-expand-file-link (first args)) (rest args)))

(advice-add 'org-link-open :filter-args #'my-org-link-open-advice)

The functionality is absolutely unchanged of course.

Secondly, and this is conflicting with the previous point, I could imagine using the prefix argument (so C-u C-c C-o) to distinguish the two cases of whether to run the original behaviour, or not, like so:

(defun my-org-link-open-advice (orig-fun link &optional arg &rest args)
  "Advice for ORIG-FUN that expands patterns in 'file:' LINKs.
See `my-org-expand-file-link' for more information.
Optional argument ARGS are passed as-is."
  (apply orig-fun (if arg link (my-org-expand-file-link link)) arg args))

To quote the function docstring for org-link-open:

ARG is an optional prefix argument. Some link types may handle it. For example, it determines what application to run when opening a "file" link.

This might interfere with the above idea, however practically I couldn't see a difference with the unadvised function and this argument being present or not, YMMV.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Thanks for the review. TIL about the aliases first and rest for car and cdr! \$\endgroup\$
    – Zeta
    Mar 12 at 13:13
  • \$\begingroup\$ Right, I actually thought for teaching purposes to use first and rest, normally I go for the shorter ones :) \$\endgroup\$
    – ferada
    Mar 12 at 20:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.