Opening http link under the cursor in vim

Mr. Walter Bright, creator of the D programming language, recently commented on Hacker News:

And for bug fixes, reference the issue which often gives a detailed explanation for why the code is a certain way:

Ever since I enhanced the editor I use to open the browser on links, this sort of thing has proven to be very, very handy.

I’ve never had any issue with opening links from vim: I have <leader>y set up in Visual mode to yank stuff into the system-wide clipboard which I can then paste into the browser. However, ever since I mapped <leader>gh to trigger :GBrowse that opens a browser tab instantly, the old “select, copy, alt-tab to browser, ctrl+t, ctrl+v” flow started to feel… prehistoric. Mr. Bright’s comment gave me the final nudge to actually go ahead and set it up.

The good folks from the developer encyclopedia suggested gx but for some reason, setting g:netrw_browsex_viewer didn’t seem to do anything so the command would always wget the link then tell the browser to open that downloaded file. Therefore, I cobbled together this snippet which was adapted from those stackoverflow & github threads:

function! OpenURL()
  let l:url = matchstr(expand("<cWORD>"), 'https\=:\/\/[^ >,;()]*')
  if l:url != ""
    let l:url = shellescape(l:url, 1)
    let l:command = "!xdg-open ".l:url
    echo l:command
    silent exec l:command
    echo "No URL found under cursor."

nnoremap gl :call OpenURL()<cr>

(if you’re on a Mac, replacing xdg-open with open will probably do the same thing)

Now whenever I have my cursor on an http(s) url, I can type gl from normal mode and xdg-open will use my default browser to open it up. This could be extended to any other scheme like mailto or ftp but I don’t have any practical use for them right now so that will do.

One drawback is if there’s a whitespace in the URL (which is bad practice anyway), my regex won’t match the whole thing. In such cases I’d rather resort to good old manual visual mode than try to be clever and make my URL detecting logic exponentially more complex. I’d take simple software with obvious, easily understood behavior over overcomplicated, (possibly) subtly broken balls of mud any day.

By the way, if you looked at my script and got spooked by the idea of executing a shell command composed from arbitrary, potentially unsafe input (i.e. text file content), don’t worry: that’s what shellescape() is for.

But why stop there?#

We’re using Jira at work (I know, don’t ask), and we have a convention to include the Jira ticket in all top-level git commit messages like this (French optional):

[SRE-123456] Finally fix the goddamn pipeline

That’s no URL, but the jira ticket ID pattern is pretty simple, so I simply altered the regexp a bit like this:

function! OpenJira()
  let l:jira_id = toupper(matchstr(expand("<cWORD>"), '\c\(id2\|sre\|csi\)-[0-9]\+'))
  if l:jira_id != ""
    let l:command = "!xdg-open".l:jira_id
    echo l:command
    silent exec l:command
    echo "No Jira ticket found under cursor."
nnoremap gj :call OpenJira()<cr>

Voila! Now I can press gj to open any atlassian ticket from just its ID.

Some interesting points:

On a more big-picture note, I can afford to make seemingly sloppy decisions precisely because this serves only myself, and my specific use cases are usually narrow. It’s not very general, but it works, and works precisely the way I want it. This is one of the reasons I’ve always preferred simple tooling that I can build upon, rather than following the prescribed workflows of more full-fledged IDEs.

I’m not bashing IDEs, and I’m in no way promoting vim or rolling your own emacs. I’m firmly in the “use whatever you’re comfortable with” camp. I think the whole idea of editor/IDE wars is juvenile, dumb and counterproductive (all software sucks in some way anyway, fight me). Showing nifty tricks you can do with your tools, inspiring others to either check them out or implement those on their own tools, just like how Mr. Bright has done with his little comment, is a much better use of everyone’s time. I think.