Skip to content

Instantly share code, notes, and snippets.

@habamax
Last active February 5, 2025 11:37
Show Gist options
  • Save habamax/0a6c1d2013ea68adcf2a52024468752e to your computer and use it in GitHub Desktop.
Save habamax/0a6c1d2013ea68adcf2a52024468752e to your computer and use it in GitHub Desktop.

"better" gx for vim

Vim has built-in gx mapping to open an URL under the cursor defined in the bundled netrw plugin. It is limited to barebone urls and quite often that is not enough.

I have come up with a better gx mapping (for me of course) that is able to open:

  • markdown links [Hacker news](https://news.ycombinator.com 'link to Hacker news website');

  • asciidoc links https://news.ycombinator.com[Hacker news];

  • html links <a href="https://news.ycombinator.com">Hacker news</a>;

  • and usual barebone urls https://news.ycombinator.com.

" Better gx to open URLs.
func! BetterGx() abort
    if exists("$WSLENV")
        lcd /mnt/c
        let cmd = ":silent !cmd.exe /C start"
    elseif has("win32") || has("win32unix")
        let cmd = ':silent !start'
    elseif executable('xdg-open')
        let cmd = ":silent !xdg-open"
    elseif executable('open')
        let cmd = ":silent !open"
    else
        echohl Error
        echomsg "Can't find proper opener for an URL!"
        echohl None
        return
    endif

    " URL regexes
    let rx_base = '\%(\%(http\|ftp\|irc\)s\?\|file\)://\S'
    let rx_bare = rx_base . '\+'
    let rx_embd = rx_base . '\{-}'

    let URL = ""

    " markdown URL [link text](http://ya.ru 'yandex search')
    try
        let save_view = winsaveview()
        if searchpair('\[.\{-}\](', '', ')\zs', 'cbW', '', line('.')) > 0
            let URL = matchstr(getline('.')[col('.')-1:], '\[.\{-}\](\zs'.rx_embd.'\ze\(\s\+.\{-}\)\?)')
        endif
    finally
        call winrestview(save_view)
    endtry

    " asciidoc URL http://yandex.ru[yandex search]
    if empty(URL)
        try
            let save_view = winsaveview()
            if searchpair(rx_bare . '\[', '', '\]\zs', 'cbW', '', line('.')) > 0
                let URL = matchstr(getline('.')[col('.')-1:], '\S\{-}\ze[')
            endif
        finally
            call winrestview(save_view)
        endtry
    endif

    " HTML URL <a href='http://www.python.org'>Python is here</a>
    "          <a href="http://www.python.org"/>
    if empty(URL)
        try
            let save_view = winsaveview()
            if searchpair('<a\s\+href=', '', '\%(</a>\|/>\)\zs', 'cbW', '', line('.')) > 0
                let URL = matchstr(getline('.')[col('.')-1:], 'href=["'."'".']\?\zs\S\{-}\ze["'."'".']\?/\?>')
            endif
        finally
            call winrestview(save_view)
        endtry
    endif

    " barebone URL http://google.com
    if empty(URL)
        let URL = matchstr(expand("<cfile>"), rx_bare)
    endif

    if empty(URL)
        return
    endif

    exe cmd . ' "' . escape(URL, '#%!')  . '"'

    if exists("$WSLENV") | lcd - | endif
endfunc

nnoremap <silent> gx :call BetterGx()<CR>

@ubaldot
Copy link

ubaldot commented Feb 5, 2025

Super! Here is a Vim9 porting, but keep in mind that in the ported version:

  1. The opener command shall be defined in the g:start_cmd variable,
  2. Many string concatenations may be changed into string interpolation,
  3. The last part of the script is slightly different: not knowing what $WSLENV is, I just removed its references,
  4. It is only tested on Windows where I have set g:start_cmd = "explorer.exe".

Feel free to add comments, so I can edit the code for the best. :)

def BetterGx()

  if !exists('g:start_cmd')
    echohl Error
    echomsg "Can't find proper opener for an URL!"
    echohl None
    return
  endif

  # URL regexes
  var rx_base = '\%(\%(http\|ftp\|irc\)s\?\|file\)://\S'
  var rx_bare = rx_base .. '\+'
  var rx_embd = rx_base .. '\{-}'

  var URL = ""
  var save_view = winsaveview()

  # markdown URL [link text](http://ya.ru 'yandex search')
  try
    if searchpair('\[.\{-}\](', '', ')\zs', 'cbW', '', line('.')) > 0
      URL = matchstr(getline('.')[col('.') - 1 : ], '\[.\{-}\](\zs' .. rx_embd
        .. '\ze\(\s\+.\{-}\)\?)')
    endif
  finally
    winrestview(save_view)
  endtry

  # asciidoc URL http://yandex.ru[yandex search]
  if empty(URL)
    try
      if searchpair(rx_bare .. '\[', '', '\]\zs', 'cbW', '', line('.')) > 0
        URL = matchstr(getline('.')[col('.') - 1 : ], '\S\{-}\ze[')
      endif
    finally
      winrestview(save_view)
    endtry
  endif

  # HTML URL <a href='http://www.python.org'>Python is here</a>
  #          <a href="http://www.python.org"/>
  if empty(URL)
    try
      if searchpair(' < a\s\+href=', '', '\%(</a>\|/>\)\zs', 'cbW', '',
          line('.')) > 0
        URL = matchstr(getline('.')[col('.') - 1 : ], 'href=["' .. "'"
          .. ']\?\zs\S\{-}\ze["' .. "'" .. ']\?/\?>')
      endif
    finally
      winrestview(save_view)
    endtry
  endif

  # barebone URL http://google.com
  if empty(URL)
    URL = matchstr(expand("<cfile>"), rx_bare)
  endif

  if !empty(URL)
    silent exe $'!{g:start_cmd} {escape(URL, '#%!')}'
  else
    echo "Empty URL"
  endif

enddef

@habamax
Copy link
Author

habamax commented Feb 5, 2025

@ubaldot
Copy link

ubaldot commented Feb 5, 2025

Ahahah! Sorry, I didn't know that you already ported to Vim9! :D At first glance, the difference is epsilon.
Damn, 2 hours lost this morning and I have a pile of things to do! :D

@habamax
Copy link
Author

habamax commented Feb 5, 2025

for the difference, I use new bundled vim9.Open() func nowadays

@ubaldot
Copy link

ubaldot commented Feb 5, 2025

Yes, I noticed that ;-)
I also noted a defer ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment