Emacs: Open URLs or search the web, plus browse-url-handlers
| emacs, orgOn IRC, someone asked for help configuring Emacs to have a keyboard shortcut that would either open the URL at point or search the web for the region or the word at point. I thought this was a great idea that I would find pretty handy too.
Let's write the interactive function that I'll call from my keyboard shortcut.
- First, let's check if there's an active region. If there isn't, let's assume we're looking at the thing at point (could be a URL, an e-mail address, a filename, or a word).
- If there are links, open them.
- Otherwise, if there are e-mail addresses, compose a message with all those email addresses in the "To" header.
- Are we at a filename? Let's open that.
- Otherwise, do a web search. Let's make that configurable. Most people will want to use a web browser to search their favorite search engine, such as DuckDuckGo or Google, so we'll make that the default.
(defcustom my-search-web-handler "https://duckduckgo.com/html/?q="
"How to search. Could be a string that accepts the search query at the end (URL-encoded)
or a function that accepts the text (unencoded)."
:type '(choice (string :tag "Prefix URL to search engine.")
(function :tag "Handler function.")))
(defun my-open-url-or-search-web (&optional text-or-url)
(interactive (list (if (region-active-p)
(buffer-substring (region-beginning) (region-end))
(or
(and (derived-mode-p 'org-mode)
(let ((elem (org-element-context)))
(and (eq (org-element-type elem) 'link)
(buffer-substring-no-properties
(org-element-begin elem)
(org-element-end elem)))))
(thing-at-point 'url)
(thing-at-point 'email)
(thing-at-point 'filename)
(thing-at-point 'word)))))
(catch 'done
(let (list)
(with-temp-buffer
(insert text-or-url)
(org-mode)
(goto-char (point-min))
;; We add all the links to a list first because following them may change the point
(while (re-search-forward org-any-link-re nil t)
(add-to-list 'list (match-string-no-properties 0)))
(when list
(dolist (link list)
(org-link-open-from-string link))
(throw 'done list))
;; Try emails
(while (re-search-forward thing-at-point-email-regexp nil t)
(add-to-list 'list (match-string-no-properties 0)))
(when list
(compose-mail (string-join list ", "))
(throw 'done list)))
;; Open filename if specified, or do a web search
(cond
((ffap-guesser) (find-file-at-point))
((functionp my-search-web-handler)
(funcall my-search-web-handler text-or-url))
((stringp my-search-web-handler)
(browse-url (concat my-search-web-handler (url-hexify-string text-or-url))))))))
I've been really liking how consult-omni lets me do quick searches as I type from within Emacs, which is actually really cool. I've even extended it to search my bookmarks as well, so that I can find things using my words for them and not trust the internet's words for them. So if I wanted to search using consult-omni, this is how I would do it instead.
(setopt my-search-web-handler #'consult-omni)
Now I can bind that to C-c o
in my config
with this bit of Emacs Lisp.
(keymap-global-set "C-c o" #'my-open-url-or-search-web)
Here's a quick demo:
Play by play
- Opening a URL: https://example.com
- Opening several URLs in a region:
- https://example.com
- Other stuff can go here
- https://emacsconf.org
- Opening several e-mail addresses:
- test@example.com
- another.test@example.com
- maybe also yet.another.test@example.com
- A filename
- ~/.config/emacs/init.el
- With DuckDuckGo handling searches:
(setopt my-search-web-handler "https://duckduckgo.com/html?q=")
- antidisestablishmentarianism
- With consult-omni handling searches:
(setopt my-search-web-handler #'consult-omni)
- antidisestablishmentarianism
Depending on the kind of URL, I might want to look at it in different browsers. For example, some websites like https://emacswiki.org work perfectly fine without JavaScript, so opening them in EWW (the Emacs Web Wowser) is great. Then it's right there within Emacs for easy copying, searching, etc. Some websites are a little buggy when run in anything other than Chromium. For example, MailChimp and BigBlueButton (which is the webconference server we use for EmacsConf) both behave a bit better under Google Chrome. There are some URLs I want to ignore because they don't work for me or they tend to be too paywalled, like permalink.gmane.org and medium.com. I want to open Mastodon URLs in mastodon.el. I want to open the rest of the URLs in Firefox, which is my current default browser.
To change the way Emacs opens URLs, you can
customize browse-url-browser-function
and
browse-url-handlers
. For example, to set up the
behaviour I described, I can use:
(setopt browse-url-handlers
'(("https?://?medium\\.com" . ignore)
("https?://[^/]+/@[^/]+/.*" . mastodon-url-lookup)
("https?://mailchimp\\.com" . browse-url-chrome)
("https?://bbb\\.emacsverse\\.org" . browse-url-chrome)
("https?://emacswiki.org" . eww)))
(setopt browse-url-browser-browser-function 'browse-url-firefox)
Could be a fun tweak. I wonder if something like this might be handy for other people too!