Neovim Config: from init.vim to init.lua
Tags: neovim, lua, dotfiles, vimscript
Published: 2023-08-22 03:32:30

It's 9:30PM, I'm on vacation, and I drank an energy drink an hour ago. It's time to shave some yaks as hard as possible.

Of course I'm talking about my Neovim configuration file, and migrating it from the Vimscript-based `init.vim` to a Lua-based `init.lua`.

## Why would you do this to yourself?

A few reasons:

- `init.lua` is backwards compatible with `init.vim`, but not the other way around
- Lua has applications outside of Vim, and this is a good excuse to familiarize myself with Lua more.
- I haven't done any benchmarking whatsoever, but Lua is probably faster than Vimscript, right? I mean, Lua is pretty fast...
- I wanted to try some Lua-based plugins like NvimTree
- Oh, do _you_ know Vimscript? Yeah, didn't think so.

The API docs for the Lua + Neovim are here: https://neovim.io/doc/user/api.html

## Migration Strategy

My overall strategy was pretty simple: first wrap everything in `vim.cmd([[ ... ]])` and then pluck things out individually as candidates for conversion to Lua.

The code that follows exemplifies this process, and highlights certain categories of settings and what the Lua code looked like in the end.

### vim.cmd

Vim's Lua APIs provide a convenience function `vim.cmd` that takes a string literal as its only argument. Anything in that string is evaluated as Vimscript. Kind of scary, but since we're controlling what goes into it it becomes extremely convenient for our migration.

Just:
```lua
vim.cmd([[
    " the contents of your existing init.vim
]])
```

And you're off to the races... but where's the fun in that? Where's the Lua?

### Settings and Options

The first low-hanging fruit is your Vimscript `set` directives like `set wrap`. You have three options here and it can be a little confusing as to which options live where. I hate to sound like one of those vim people, but `:help` is really good here.

#### vim.g

This is for global commands, basically anything in the `let g:***` Vimscript namespace. For example, `let g:mapleader = " "` becomes `vim.g.mapleader = " "`

#### vim.o

This will likely be the bread and butter of your migration, as it's the most consistent 1:1 mapping of `set` options and Lua expressions. See for yourself:

```lua
vim.o.spell = "yes"
vim.o.splitbelow = true
vim.o.splitright = true
vim.o.swapfile = false
vim.o.synmaxcol = 30
```

Note they even kept the weird `"yes"`-instead-of-`true` convention.

#### vim.opt

This is a special Lua table (read: hash map or associative array) that allows you to use more Lua-friendly syntax to set options.

For example, `vim.opt.listchars` is pretty nice:

```lua
vim.opt.listchars = {
  tab = "» ",
  trail = "·",
  extends = "›",
  precedes = "‹",
  nbsp = "␣",
}
```

### vim.api: Keymapping and Autocommands

For more advanced functionality like keymapping and autocommands, you have `vim.api`

```lua
vim.api.nvim_create_autocmd({ "FileType" }, {
    pattern = "rust",
    callback = function()
        vim.api.nvim_buf_set_option(0, "tabstop", 4)
        vim.api.nvim_buf_set_option(0, "shiftwidth", 4)
        vim.api.nvim_buf_set_option(0, "softtabstop", 4)
    end
})
```

```lua
vim.api.nvim_set_keymap('n', '<leader>s', ':split<CR>',
    { noremap = true })
```

## Conclusion

These are just some of the ideas and examples I was able to pull out of my experience in migrating to a Lua-based Neovim config. What do you think?

Another highlight of this adventure in yak-shaving is that it makes me review my old configuration code, allowing me to change, edit, and re-contextualize the ideas that I had and my previous intentions.

How about you? Have you done this same migration? Did I miss anything? Have you shaved any good yaks recently? I'd love to hear from you.