From init.vim to init.lua  - a crash course
Tags: productivity, vim
Published: 2021-11-02 20:32:56

---
title: From init.vim to init.lua  - a crash course
published: true
date: 2021-11-01 21:21:12 UTC
tags: productivity,vim
canonical_url: https://www.notonlycode.org/neovim-lua-config/
cover_image: https://www.notonlycode.org/content/images/2021/07/2021-07-lua-blog.jpg
---

Earlier this year maintainers of Neovim have released version 0.5 which, among other features, allows developers to configure their editor using Lua instead of VimL. In this article I'll share a few basic rules on how to transition from one configuration to another. This is not a complete guide, but it covers almost 100% of what I needed in order to completely move away from init.vim to init.lua. In the bottom of the article you're also find a link to my config files so that you can look at them and copy if needed.

## Lua in 1 minute

First, it's good to spend 10-15 minutes learning Lua in order to easily write the new config. I used the [Learn X in Y minutes page](https://learnxinyminutes.com/docs/lua/), but I guess anything works. If you want to spend 1 minute instead, here you go:

```lua
-- this is a comment
num = 22 -- this global variable represents a number
local num2 = 33 -- local variable
str1 = 'this is a string'
str2 = "and so is this"
str3 = [[and this is a string too]]
str4 = "string " .. "concatenation"
val = true and not false -- booleans and logical operators

if str1 == 'something' then
  print("YES")
elseif str2 ~= 'is not equal' then
  print('Maybe')
else
  print('no')
end

function printText(text)
  print(text)
  return true
end

tab1 = { 'this', 'is, 'a', 'table' } -- aka array
-- tables are both arrays and dictionaries
tab2 = { also = 'this is a table' }
tab2["new_key"] = "new value"

print(tab2["also"])

require('plugins') -- will find and execute plugins.lua file
```

There's of course way more than that, but for me this + copying some stuff from plugins documentation was enough to write my config files.

## Config basics

Ok, now onto the config. In Vim we use a number of functions that are dedicated to the editor configuration (the whole language is dedicated to it, really). In Lua we're using a general programming language and we'll use an API to interact with Neovim configuration:

- `vim.cmd("set notimeout")` - this is a safety net, whatever string you pass as parameter to `vim.cmd` will be interpreted as VimL. For multiple lines, wrap string in double brackets:

```
vim.cmd([[
set notimeout
set encoding=utf-8
]])
```

- `vim.g.mapleader = ","` is equivalent of `let g:mapleader = ','`; `vim.g` is a table represeting global variables
- `vim.o.encoding="utf-8"` is equivalent of `set encoding=utf-8`; `vim.o` is for global options, `vim.wo` for window options and `vim.bo` for buffer options
- `vim.fn` is a table representing functions. You can refer to a function `thisIsMyFun` using `vim.fn.thisIsMyFun` or `vim.fn["thisIsMyFun"]` and you can call it using `vim.fn.thisIsMyFun()` or `vim.fn["thisIsMyFun"]()`
- `vim.api` is a collection of API functions. I used only one: `vim.api.nvim_set_keymap` that maps certain key combinations to some functions (more about it below)

## Moving settings to Lua

Moving most of the settings is pretty straightforward. You just replace `set x = y` with `vim.o.x = "y"`. There are however some catches:

- pairs of boolean settings are merged into one setting, e.g. instead of `set wrap` and `set nowrap` you write `vim.o.wrap = true` and `vim.o.wrap = false`
- home directory problems - I had issue using `~` as a reference to home directory for some backup files etc. so instead I set `HOME` variable that I used by writing `HOME = os.getenv("HOME")`
- string concatenation uses `..` operator, so to refer to my backup dir I wrote `vim.o.backupdir = HOME .. "/.vim/backup"`
- double backslash - if you want to pass a special character `\t` to Vim, you need to write it as `"\\t"` in Lua

## Mapping keys

The Lua API has a function to map keys to some functions. The function signature is `vim.api.nvim_set_keymap(mode, keys, mapping, options)`, where `mode` refers to a letter representing editor mode ( `n` for normal, `i` for insert etc.) just like in original vim functions like `nmap` or `imap`, `keys` is a string representing a combination of keys, `mapping` is a string representing what the keys map to, and options are a table where you can pass some additional settings. Example:

```lua
vim.api.nvim_set_keymap(
  "n",
  "<leader>a",
  ":Git blame<cr>",
  { noremap = true }
 )
```

is equivalent of `nnoremap <leader>a <cmd>Git blame<cr>`.

I didn't check what are all the options that can be passed in the 4th argument, 2 that I used are `noremap = true` and `silent = true`.

I also wrote myself a few simple functions to avoid typing `vim.api...` every time:

```lua
function map(mode, shortcut, command)
  vim.api.nvim_set_keymap(mode, shortcut, command, { noremap = true, silent = true })
end

function nmap(shortcut, command)
  map('n', shortcut, command)
end

function imap(shortcut, command)
  map('i', shortcut, command)
end
```

With these functions my example above became just `nmap("<leader>a", "<cmd>Git blame<cr>")`.

## Package manager

It's very probable that you already use some package manager for Neovim, and when moving to Lua you don't need to change it, you can just wrap your whole plugin list in `vim.cmd` and continue using it as before.

I decided to try a new manager called [Packer](https://github.com/wbthomason/packer.nvim), which is written in Lua and requires Neovim 0.5. The installation was a bit troublesome, because I didn't know what `packpath` is and that Packer requires some very specific names of the directories to find packages. Anyway, I moved it to `~/.config/nvim/pack/packer/start/packer.nvim` directory and it worked nicely (though I'm sure there's a better way to install it).

Besides the installation, Packer is both easy to use and has all the features I need (and a lot that I don't need). The basic config example looks like this:

```lua
return require('packer').startup(function()
  use 'wbthomason/packer.nvim'

  -- common
  use 'tpope/vim-fugitive' -- Git commands
  use { 'tpope/vim-rails', ft = "ruby" } -- only load when opening Ruby file
end)
```

If you need some more advanced functionality, I recommend checking the documentation.

## Other plugins

There is a number of other plugins that are available for Neovim 0.5 and I found myself replacing some of plugins I had used before with the new alternatives. I won't cover them here (maybe in another post), but here are a few that you can check out:

- `nvim-lspconfig` together with `nvim-lspinstall` and `lspsaga.nvim` use the new, built-in LSP in Neovim and provide some useful functions. Together they allow me to easily install and use language servers (e.g. to display function documentation or jump to a definition). Together with `nvim-compe` (autocompletion) I use them to replace `Ale` and `coc.vim`, 
- `telescope.nvim` replaces any search plugin you use ( `ctrl-p`, `fzf.vim` etc.)
- `gitsigns` replaces `vim-gitgutter`

There are a few more like `lualine.vim` that can replace `powerline` or `airline`, but I'm yet to try it out.

## Summary

The whole process of moving 350 lines of `init.vim` to `init.lua` took me around 2h, including time to organize the files (Lua allows you to use multiple config files, see my example below) and excluding time to play with new plugins. I spent around 1 hour moving 90-95% of the content, and another hour solving some issues like home directory or some broken config. In the end I found the whole process rather quick and definitely rewarding, though I'm sure a lot of things can be done better. If you plan to use new capabilities of Neovim 0.5 I definitely recommend moving your config to Lua.

**My config in VimL and Lua:** [https://github.com/arnvald/viml-to-lua](https://github.com/arnvald/viml-to-lua)