an even better video wharf
A couple of days ago, i was writing about embark and my first experiment defining a new embarking to play remote video streams. Omar AntolĂn Camarena, embark's author, has been kind enough to not only read it, but comment on a couple of significant improvements that i think well deserve this follow-up.
First, you'll remember that we were defining a function to detect a video URL:
(defun jao-video-finder () "Check whether we're looking at a video URL. Return (video-url . <URL>) if so." (when-let ((url (thing-at-point-url-at-point))) (when (string-match-p jao-video-url-rx url) (cons 'video-url url))))
Once we've got a non-null url
value, even if it's not a video URL,
it's still certainly a URL, and embark has a url
category, so we could
save a new parsing by the default URL finder by saying:
(when-let ((url (thing-at-point-url-at-point))) (cons (if (string-match-p jao-video-url-rx url) 'video-url 'url) url))
This has the potential drawback that we're overriding embark's finder,
embark-target-url-at-point
, and we might prefer to keep the latter.
Turns out that we can do that thanks to embark's target transformers.
One can add to embark-transformers-alist
an arbitrary function to be
applied to a target of any given category, and embark will apply its
actions to the transformed value. Omar calls this process, very
aptly, a refinement of the target; here's how we would do it:
(defun jao-refine-url-type (url) "Refine type of URL in case it is a video." (cons (if (string-match-p jao-video-url-rx url) 'video-url 'url) url)) (add-to-list 'embark-transformer-alist '(url . jao-refine-url-type))
With this strategy, we don't need jao-video-finder
at all, and it also
makes lots of sense, conceptually, to have our video-url
defined as a
refinement rather than a new target1. Omar's second suggestion
is also in line with this concept: surely we want all actions
available for url
also for our video-url
, don't we? Well, that's
exactly the reason why the embark-define-keymap
macro we used to
define our actions can inherit all the actions already defined in
another keymap, using the :parent
keyword2:
(embark-define-keymap jao-video-url-map "Actions on URLs pointing to remote video streams." :parent embark-url-map ("p" jao-play-video-url)) (add-to-list 'embark-keymap-alist '(video-url . jao-video-url-map))
It is worth noting that this ability to inherit a keymap is not really
an embark add-on: vanilla Emacs keymaps already have it, via the
standard function set-keymap-parent
. You could actually define
jao-video-url-map
without using embark-define-keymap
at all, and it'd
work exactly the same.
So, our code has become shorter and more featureful: thanks, Omar!
Footnotes:
There's a scenario where keeping jao-video-finder could make
sense, namely, if we want to alter the URL detection function. For
instance, i use emacs-w3m, and there often a URL is stored as a text
property (the actual text being the link text). To retrieve the URL
at point there, one needs to call w3m-anchor
, and
embark-target-url-at-point
will miss it. For that scenario, i ended
up writing (and using) jao-video-finder
defined with:
(when-let ((url (or (w3m-anchor) (thing-at-point-url-at-point)))) (cons (if (string-match-p jao-video-url-rx url) 'video-url 'url) url))
Another way of accomplishing the same thing (with another tip of the hat to Omar) would be to add a specific finder for w3m anchors (and keep using the transformer for video-url):
(defun jao-w3m-url-finder () (when-let ((url (w3m-anchor))) (cons 'url url))) (add-to-list 'embark-target-finders #'jao-w3m-url-finder)
This way is more modular and, depending on your taste, more elegant. These functions are small and there's not a big difference between the two approaches, but if one keeps adding finders, things can easily get uglier with the former approach.
In my original example, i was adding also browse-url
and
browse-url-firefox
to the video map. The former is no longer
necessary, because it's already present in embark-url-map
. If we
wanted to make browse-url-firefox
available to all URLs, we could add
it to embark-url-map
(remember, embark's keymaps are just Emacs
keymaps). That's yet another simple way of extending embark.