# Vim Tip: Open Hugo Page in Browser


When writing blog posts in Hugo, I often want to open the file that I'm
currently editing in my browser, so I can preview how the post looks. It'd be
nice to do this automatically from a vim shortcut. Fortunately, Hugo can
introspect on the pages it generates, which allows us to programatically find
the URL of the current page we're editing.

Hugo's CLI tool has a command that outputs all the pages it generates:
`hugo list`. The two fields this command outputs that we care about are `path`
and `permalink`: `path` is the filesystem path of the content file (think
`/content/path/to/my/post.md`) and permalink is the actual URL of the page that
Hugo generates.

```
$ hugo list all
path,slug,title,date,expiryDate,publishDate,draft,permalink
content/blog/2020-07-10-Vim-Tip-Open-Hugo-Page-in-Browser/index.md,,Vim Tip: Open Hugo Page in Browser,2020-07-10T08:38:03Z,0001-01-01T00:00:00Z,2020-07-10T08:38:03Z,false,https://benjamincongdon.me/blog/2020/07/10/Vim-Tip-Open-Hugo-Page-in-Browser/
content/archive.md,,Blog Archive,0001-01-01T00:00:00Z,0001-01-01T00:00:00Z,0001-01-01T00:00:00Z,false,https://benjamincongdon.me/blog/archive/
...
```

From the above, we see that the path of this post is
`content/blog/2020-07-10-Vim-Tip-Open-Hugo-Page-in-Browser/index.md`, and its
permalink is
`https://benjamincongdon.me/blog/2020/07/08/Vim-Tip-Open-Hugo-Page-in-Browser/`.

Only, this isn't quite right for local development. When previewing posts
locally, we want a localhost base URL -- usually something starting with
`http://localhost:1313`. Fortunately, we can set the `HUGO_BASEURL` environment
variable to make the permalinks work locally:

```
$ HUGO_BASEURL="http://localhost:1313" hugo list all
path,slug,title,date,expiryDate,publishDate,draft,permalink
content/blog/2020-07-10-Vim-Tip-Open-Hugo-Page-in-Browser/index.md,,Vim Tip: Open Hugo Page in Browser,2020-07-10T08:38:03Z,0001-01-01T00:00:00Z,2020-07-10T08:38:03Z,false,https://localhost:1313/blog/2020/07/10/Vim-Tip-Open-Hugo-Page-in-Browser/
...
```

In order to open the `permalink` yielded by `hugo list`, we have to extract it
from the list of results:

```sh
hugo list all | grep /path/to/page.md | head -n 1 | cut -d ',' -f 8
```

We use `grep` to filter the results to the page we want to preview, `head` to
ensure we only ever get 1 result, and `cut` to extract the `permalink` field.
Here's what that looks like translated into vimscript:

```vim
let l:url = systemlist("hugo list all | grep " . l:contentpath . " | head -n 1 | cut -d ',' -f 8")[0]
```

Next, we have to do some scripting in Vim so that the `hugo` CLI tool is
correctly run from the Hugo base directory (i.e. the directory that contains
your `config.{toml,yaml,json}`). If `hugo` isn't run from the directory
containing your site's configuration, `hugo list` won't work correctly. In the
code snippet below, `HugoBaseDirectory()` recurses up the directory hierarchy of
the provided file until it finds something that contains a config file.

And here's the final code:

```vim
let g:hugo_site_config = [ 'config.toml', 'config.yaml', 'config.json' ]
" The local Hugo server URL
let g:hugo_base_url = "http://localhost:1313/"

function! HugoBaseDirectory(filepath)
    let l:mods = ':p:h'
    let l:dirname = 'dummy'
    while !empty(l:dirname)
        let l:path = fnamemodify(a:filepath, l:mods)
        let l:mods .= ':h'
        let l:dirname = fnamemodify(l:path, ':t')
        " Check if the parent of the content directory contains a config file.
        let l:parent = fnamemodify(l:path, ":h")
        if HugoConfigFile(l:parent) != ""
            return l:parent
        endif
    endwhile

    return ""
endfunction

function! HugoConfigFile(dir)
    " :p adds the final path separator if a:dir is a directory.
    let l:dirpath = fnamemodify(a:dir, ':p')
    for config in g:hugo_site_config
        let l:file = l:dirpath . config
        if filereadable(l:file)
            return l:file
        endif
    endfor
    return ""
endfunction

function! PreviewHugoPage(filepath)
    let l:fullpath = fnamemodify(a:filepath, ':p')
    " Get Hugo's base directory
    let l:basedir = HugoBaseDirectory(l:fullpath)
    if l:basedir == ""
        return ""
    endif
    " Get the path of the content file relative to Hugo's base directory
    let l:contentpath = substitute(l:fullpath, l:basedir . '/', '', '')
    " Huse `hugo list all` to find the URL of the content file
    let l:url = systemlist("cd " . l:basedir . " && HUGO_BASEURL='" . g:hugo_base_url . "' hugo list all | grep " . l:contentpath . " | head -n 1 | cut -d ',' -f 8")[0]
    exe system("open " . l:url)
endfunction

nmap <Leader>hp :call PreviewHugoPage(expand('%'))<cr>
```

Now, you can type `,hp` in vim while editing a Hugo content file, and that file
will automatically be opened in your browser for previewing!

The nice thing about making `g:hugo_base_url` configurable is that you could
make a variant of this command that opens the deployed version of your content
post -- you'd just have to make a function that altered `g:hugo_base_url` to
point to your deployed Hugo site.

### Attribution

Some of the Hugo config file code is adapted from
[vim-hugo-helper](https://github.com/robertbasic/vim-hugo-helper/)

