Vimscript to lua: everything you need to know

Vimscript to lua: everything you need to know

A complete guide to translating/writing your configuration in Lua for neovim.

Have you been wanting to write your own neovim configuration in Lua, or translate your old vimrc? If the answer is yes, you're in the right place. In this article, I'm going to tell you everything you need to know to accomplish this goal.

Introduction to Lua

If you already know the syntax of the Lua programming language, you can skip this section. Alternatively, you can watch a quick introduction to the language on YouTube, there are a lot of valid ones. Here, I'm going to provide a quick overview of most of the features used for vim configuration you need to know about.

Before I provide some usage examples, you need to know that the Lua programming language uses one single data structure, tables, which emulate arrays and maps/dictionaries. Note that the indexing starts at 1.

var1 = 1 -- global variable
local var2 = 2 -- local variable
strings = "1" .. "2" .. [[3]] -- ways to write a string and concatenation

condition = not false -- booleans and if statments
if false then
    print(var1)
elseif condition then
    print(var2)
else
    print(strings)
end

function say_hi_to(name)
    print("Hi " .. name)
end

local table = { "hello", "goodbye" } -- used as array
table["hello"] = "world" -- and dictionary

for k, v in ipairs(table) do -- for range loop
    -- ipairs only gets indexed values (with no user-defined key)
    -- pairs gets all keys and values in tables
    print(k, v)
end

require('plugin-name') -- used to import files or use plugins

Getting started

To get started configuring your neovim with Lua, you need to create an init.lua file, in the same directory where you would put your vimrc for neovim. For instance, on Linux, it would be something like $HOME/.config/nvim/init.lua.

From here, you can have two different approaches:

  • writing your entire config inside the init file

  • dividing your config into different files

If you choose the second approach, you'll have to put all the files except your init.lua inside the lua folder, in your nvim config folder. Here is an example:

.config/nvim/
|  init.lua
|  lua/
|  |  options.lua
|  |  plugins/
|  |  |  init.lua
|  |  |  <plugin-name>.lua

In this case, your main init.lua would look like this:

require("options") -- no file extension
require("plugins") -- require a folder

If you require a folder, what Lua actually does is search for an init.lua inside that folder, so inside that file, you would have to manually require all the other files in the folder.

Setting options

Now you're finally ready to start translating or writing your config in Lua. The first thing I'll go over is setting options. To do so, you'll most likely always use the vim module.

vim.opt.tabstop = 4
vim.opt.autoindent = true

vim.opt is the most used method and works pretty much the same way :set does. If you're not sure what to use, this is the recommended way to set any option, if you don't have specific needs.

You can also set general settings with vim.o or buffer-scoped settings with vim.bo.

In some cases, you need to use vim.cmd, for example in setting the color scheme vim.cmd.colorscheme("gruvbox") which is equivalent to vim.cmd("colorscheme gruvbox").
The string insidevim.cmd() is interpreted as Vimscript.

Lastly, global options are set with vim.g, which is mostly used by plugins, and to set the leader key vim.g.leader = " ".

Setting key mappings

To set keymaps in Lua, you have mainly two options: vim.api.nvim_set_keymap() or vim.keymap.set(), where the latter is basically a wrapper for the former. The main difference in terms of configuration is that using vim.keymap.set allows you to assign a mapping to a Lua function in a more elegant way. Here are examples for both:

-- opts set in a table to not repeat them everytime
local opts = { noremap = true, silent = true }
-- (mode, lhs, rhs, options)
-- lhs = keys to be mapped, rhs = command executed
vim.api.nvim_set_keymap("v", ">", ">gv", opts) -- like vnoremap

-- (mode, lhs, rhs, options)
-- here mode can be a either string or a table
-- rhs can be either a string with commands or a lua function
function my_func()
    vim.cmd([[echo "hello world"]]) -- this is a string
end
vim.keymap.set({"v", "n"}, ">", my_func, opts)

-- map a command
vim.keymap.set("v", "<leader>a", ":wq<CR>", opts)

Plugins

To finish off your configuration, you'll probably want to add some plugins to your neovim, possibly replacing Vimscript plugins with Lua ones.

Plugin manager

Changing your plugin manager is the first step, and will make configuring all the other plugins a lot easier. You can use for example packer.nvim. Refer to the official GitHub page for installation and configuration. Here is a simple example showing how to install a plugin.

require("packer").startup(function()
    use("plugin name")
end)

Plugin customization

Most neovim plugins will then have either a setup or configure function, which will allow you to customize the plugin's options. Here is an example:

require("plugin name").setup({
    option1 = "hello",
    option2 = {
        a = "aaa",
        b = "bbb",
    },
})

Refer to the specific plugin wiki to see how it's possible to configure it.

Lua plugins

In this section, I will try to provide some examples of plugins to act as replacements for your old vim plugins.

You can try:

To replace coc and ale, you'll probably need to install more than two plugins. Neovim is in fact well known for its built-in LSP, which can be configured in many ways.
The quickest one is by using lsp-zero, which sets up automatically language servers and autocompletion, without you having to write everything on your own.
Alternatively, you can manually set up mason, nvim-cmp, null-ls and more, to achieve a similar result with your own efforts. I will write another blog post about this, so stay tuned.

One last cool plugin I'd like to suggest is toggleterm, which allows you to use the terminal inside neovim in different layouts and styles.