Multiple Emacs modes recognize URIs so that you can follow them in a web
browser, usually with a mouse left click. In modes that do not support it, one
can still call browse-url-at-point
interactively with the cursor positioned on
a URI.
I wanted to make link handling more convenient for some time; this is what I ended up with.
Key bindings
There is no global Emacs concept of following links, so modes deal with them
differently. I wanted to use the same key binding everywhere; I went for C-o
(“o” for “open”) since I never use open-line
. I also wanted the ability to use
Eww —the Emacs
builtin web browser— when it is convenient. So I added a function to select the
secondary browser when the prefix is set. This way C-o
opens Firefox, and C-u C-o
opens Eww.
(setq browse-url-secondary-browser-function 'eww-browse-url)
(defun g-browse-url-at-point (arg)
(interactive "P")
(let ((browse-url-browser-function
(if arg
browse-url-secondary-browser-function
browse-url-browser-function)))
(browse-url-at-point)))
(keymap-global-set "C-o" 'g-browse-url-at-point)
Ultimately the function calls browse-url-at-point
which identifies the URI
under the cursor (i.e. at the current point) and calls browse-url
.
Selecting a web browser
The browse-url
function is fully configurable; two mechanisms are used to
select a web browser for each link:
- the
browse-url-handlers
andbrowse-url-default-handlers
association lists, used to match specific URIs using predicates or regular expression; - the
browse-url-browser-function
variable (by defaultbrowser-url-default-browser
) referencing a function which looks for available web browsers on the current machine.
Since I use multiple Firefox profiles, I needed the ability to open URIs in the right profile depending of the context. For example links in a professional email read in Gnus should be open in my work context.
The following function returns a handler that can be used in
browse-url-handlers
or as value for browse-url-browser-function
:
(defun g-browse-url-firefox-function (profile)
(lambda (uri &rest args)
(let ((process-environment (browse-url-process-environment))
(process-name (concat "firefox " uri))
(process-args `("--new-tab" ,uri
,@(if profile (list "-P" profile)))))
(apply #'start-process process-name nil "firefox" process-args))))
For example, (g-browse-url-firefox-function "test")
returns a browse function
that will open the link in a new tab of a Firefox window associated with the
“test” profile.
To change the logic deciding how to handle links in a buffer with a specific
major mode, all you have to do is to add a hook to set
browse-url-browser-function
in a way that only affects the current buffer.
Since this variable is not
buffer-local
by default, you have to take care to use setq-local
when changing it.
Links in Gnus messages
Gnus messages are displayed using the gnus-article-mode
major mode.
We first write a function to identify the account associated with the current
group (assuming you are using nnimap
for your email accounts as I do):
(defun g-current-gnus-account ()
(let ((group gnus-newsgroup-name))
(when (string-match "^nnimap\\+\\([^:]+\\):" group)
(match-string 1 group))))
Then we define the actual browse function:
(defun g-gnus-browse-url (url &rest args)
(let ((profile
(pcase (g-current-gnus-account)
("my-company"
"my-company-profile")
("a-client"
"the-client-profile")
(_
"default"))))
(apply (g-browse-url-firefox-function profile) url args)))
And the hook function:
(defun g-gnus-set-browser-function ()
(setq-local browse-url-browser-function 'g-gnus-browse-url))
Finally we update the hook:
(use-package gnus-art
:hook
((gnus-article-mode-hook . g-gnus-set-browser-function)))
Links in emails are now open in the right Firefox profile!
Links in the Circe IRC client
Same idea for Circe, select the right Firefox profile based on the network and channel of the current channel buffer.
We define the function to be called during the initialization of each channel buffer:
(defun g-circe-set-browser-function ()
(let* ((network (with-circe-server-buffer
circe-network))
(channel circe-chat-target)
(profile (cond
((and (equal network "my-network")
(equal channel "#my-channel"))
"the-right-profile")
(t
"default"))))
(setq-local browse-url-browser-function
(g-browse-url-firefox-function profile))))
And add it as a hook:
(use-package circe
:hook
((circe-channel-mode-hook . g-circe-set-browser-function))