Customize Vim's gx mapping

February 28, 2021

  • vim
  • elixir
  • npm
  • javascript

One of my favorite Vim mappings is gx. When hovering over a URL with the cursor in normal mode and using this mapping, Vim will open the URL under the cursor with your default browser.

To learn more about this mapping, which is part of the built-in NETRW plugin, type :h gx inside Vim.

What if we could take this a step further?

When you’re in a package.json file or mix.exs, what if you could press gx and have Vim open the npmjs.org or hexdocs.pm page for the package under the cursor? Turns out it’s not that hard to do!

JavaScript: package.json + gx

For package.json we will need to parse the package name, then construct the npm url and pass it to netrw#BrowseX function, which in turn will open it in the default browser. First let’s create a function to do just that:

function! PackageJsonGx() abort
  let l:line = getline('.')
  let l:package = matchlist(l:line, '\v"(.*)": "(.*)"')

  if len(l:package) > 0
    let l:package_name = l:package[1]
    let l:url = 'https://www.npmjs.com/package/' . l:package_name
    call netrw#BrowseX(l:url, 0)
  endif
endfunction

Then we need to override the gx mappings to call our function whenever we are inside of a package.json file:

augroup PackageJsonGx
  autocmd!
  autocmd BufRead,BufNewFile package.json nnoremap <buffer> <silent> gx :call PackageJsonGx()<cr>
augroup END

Elixir: mix.exs + gx

Thankfully there is an Elixir plugin that will allow us to open the hexdocs.pm page or Github page for the package under the cursor. All we have to do is set the mappings to activate whenever we are in a mix.exs file.

First, make sure you install the plugin:

Plug 'lucidstack/hex.vim'

Then add the following to your vimrc to have gx open the Hex Docs page, and gh to open the Github page:

" Elixir mix.exs
augroup MixExsGx
  autocmd!
  autocmd BufRead,BufNewFile mix.exs nnoremap <buffer> <silent> gx :HexOpenHexDocs<cr>
  autocmd BufRead,BufNewFile mix.exs nnoremap <buffer> <silent> gh :HexOpenGithub<cr>
augroup END
" }}}

Bonus: VimPlug

If you’re using VimPlug as your Vim plugin manager, you can also get the gx mapping to work inside Plug windows (like the one that opens when you update your plugins with :PlugUpdate). This code was originally posted in VimPlug’s wiki (and was my inspiration for customizing gx for other scenarios), but I modified it so it also works inside my plugin definition file (~/.vimrc.bundles):

" For VimPlug
function! PlugGx()
  let l:line = getline('.')
  let l:sha  = matchstr(l:line, '^  \X*\zs\x\{7,9}\ze ')

  if (&filetype ==# 'vim-plug')
    " inside vim plug splits such as :PlugStatus
    let l:name = empty(l:sha)
                  \ ? matchstr(l:line, '^[-x+] \zs[^:]\+\ze:')
                  \ : getline(search('^- .*:$', 'bn'))[2:-2]
  else
    " in .vimrc.bundles
    let l:name = matchlist(l:line, '\v/([A-Za-z0-9\-_\.]+)')[1]
  endif

  let l:uri  = get(get(g:plugs, l:name, {}), 'uri', '')
  if l:uri !~? 'github.com'
    return
  endif
  let l:repo = matchstr(l:uri, '[^:/]*/'.l:name)
  let l:url  = empty(l:sha)
              \ ? 'https://github.com/'.l:repo
              \ : printf('https://github.com/%s/commit/%s', l:repo, l:sha)
  call netrw#BrowseX(l:url, 0)
endfunction

augroup PlugGxGroup
  autocmd!
  autocmd BufRead,BufNewFile .vimrc.bundles nnoremap <buffer> <silent> gx :call PlugGx()<cr>
  autocmd FileType vim-plug nnoremap <buffer> <silent> gx :call PlugGx()<cr>
augroup END

Hope you find it this post useful and that it inspires you to customize gx to support your favorite package manager / programming language. If you do write your own customization please @/DM me on Twitter - I’m interested in hearing what you come up with!


Written by Dorian Karter, a Sr. Software Engineer
Github | Twitter

© 2021, Dorian Karter