Anderson Fernandes

Where I casually share ideas and new learnings

Created at September 23, 2022
Configuring NeoVim with Lua

In this article I'm going to show how you can go from a fresh install of NeoVim (nvim as shorthand) to a ready-for-code setup.

First of all you need to have nvim installed, check out their installation guide on Github for the steps. Since the project is growing fast, consider installing the latest stable version to get the new features.

A bit of context

Once you have it installed, let's learn some basic concepts of how the nvim configuration works.

NeoVim will load the user configs from a init.lua(or init.vim) file located at one of those folders:

  • In Unix: ~/.config/nvim/
  • In Windows: ~/AppData/Local/nvim/
  • Or if the env $XDG_CONFIG_HOME is set: $XDG_CONFIG_HOME/nvim/

Here is the files structure that we are going to use:

Copy
1.
2└── nvim/
3    ├── init.lua
4    ├── lua/
5    │   └── custom/
6    │       └── init.lua
7    └── after/
8        └── plugin
9

The file init.lua is our startup file, the whole config that we are going to do will be sourced from here.

At the folder lua will be placed our .lua files and modules. Each subfolder can have an init.lua file that will be used as an startpoint of the module (like an index.html).

We are going to create one module called custom, containing all of our main config files, like keymaps and plugins management.

The folder after/plugin will contain files that will be automatically sourced by nvim after the file init.lua finishes its execution. Here we will do some post configurations like enabling and customizing plugins.

NeoVim 🤝 Lua

NeoVim include support to be configured using Lua, you can require any fie or module placed under the lua folder. The vim module is automatically required and available globally at any lua file. Check the nvim docs for a full list of functions and modules that can be used.

With that in mind lets add some initial config creating the lua/custom/set.lua:

Copy
1vim.opt.nu = true
2vim.opt.errorbells = false
3vim.opt.incsearch = true
4vim.opt.hidden = true
5vim.opt.wrap = false
6vim.opt.modifiable = true
7vim.opt.inccommand = "split"
8vim.opt.clipboard = "unnamedplus"
9
10vim.opt.autoindent = true
11vim.opt.smartindent = true
12vim.opt.smarttab = true
13vim.opt.expandtab = true
14vim.opt.shiftwidth = 2
15vim.opt.softtabstop = 2
16vim.opt.tabstop = 2
17
18vim.g.mapleader = ' '
19

With this piece of code we are setting some global configs like enabling line numnbers to be shown, configuring indentation and setting the mapleader to <space>.

Now we need to source the set.lua file into the root init.lua creating the file lua/custom/init.lua:

Copy
1require("custom.set")
2

And also adding the require to the custom module at the root init.lua:

Copy
1require("custom")
2

Plugins

Since nvim is basically a text editor, we do not have features such code completion or an git integration out of the box.

To get those features working at our editor we can use some plugins. We are going to use packer.nvim to help us manage our plugins.

The main plugins we are going to install are:

Lets add packer and install those plugins creating the file lua/custom/plugins.lua:

Copy
1vim.cmd [[packadd packer.nvim]]
2
3return require('packer').startup(function()
4  use 'wbthomason/packer.nvim'
5
6  use 'morhetz/gruvbox'
7  use 'kyazdani42/nvim-web-devicons'
8  use 'kyazdani42/nvim-tree.lua'
9  use 'nvim-lualine/lualine.nvim'
10
11  use {'junegunn/fzf', dir = '~/.fzf', run = './install --all' }
12  use 'junegunn/fzf.vim'
13
14  use 'tpope/vim-fugitive'
15  use 'mhinz/vim-signify'
16
17  use 'dense-analysis/ale'
18  use { 'neoclide/coc.nvim', branch = 'release' }
19end)
20

Don't forget to add the require to the plugins file at the lua/custom/init.lua file:

Copy
1require("custom.set")
2require("custom.plugins")
3

We also need to add some post config at the after/plugins folder:

  • Configuring the colorscheme at after/plugin/color.lua

    Copy
    1vim.cmd("syntax enable")
    2vim.opt.background = "dark"
    3
    4vim.cmd("colorscheme gruvbox")
    5
  • Setting up coc.nvim at after/plugin/completion.lua

    Copy
    1-- Add your extensions here.
    2-- Check the list of the available ones here: https://github.com/neoclide/coc.nvim/wiki/Using-coc-extensions#implemented-coc-extensions
    3vim.g.coc_global_extensions = {
    4}
    5
    6-- This will enable code completion to be triggered using the <tab> key
    7vim.cmd([[
    8  function! s:check_back_space() abort
    9    let col = col('.') - 1
    10    return !col || getline('.')[col - 1]  =~# '\s'
    11  endfunction
    12
    13  inoremap <silent><expr> <TAB>
    14    \ coc#pum#visible() ? coc#_select_confirm() :
    15    \ coc#expandableOrJumpable() ?
    16    \ "\<C-r>=coc#rpc#request('doKeymap', ['snippets-expand-jump',''])\<CR>" :
    17    \ <SID>check_back_space() ? "\<TAB>" :
    18    \ coc#refresh()
    19]])
    20
    21vim.g.coc_snippet_next = '<tab>'
    22
  • Customizing fzf interface at after/plugin/finder.lua

    Copy
    1vim.g.fzf_layout = {
    2  window = {
    3    width = 0.7,
    4    height = 0.4
    5  }
    6}
    7
    8vim.fn.setenv("FZF_DEFAULT_OPTS", "--reverse")
    9
  • Setup lualine at after/plugin/lines.lua

    Copy
    1require('lualine').setup {
    2  options = {
    3    theme = 'gruvbox-material',
    4    refresh = {
    5      statusline = 1000,
    6      tabline = 1000,
    7      winbar = 1000,
    8    },
    9  },
    10  sections = {
    11    lualine_a = {'mode'},
    12    lualine_b = {'branch', 'diff'},
    13    lualine_c = {'filename'},
    14    lualine_x = {'encoding', 'filetype'},
    15    lualine_y = {},
    16    lualine_z = {'location'}
    17  },
    18  tabline = {
    19    lualine_a = {'tabs'},
    20    lualine_b = {'filename'},
    21    lualine_c = {},
    22    lualine_x = {},
    23    lualine_y = {},
    24    lualine_z = {}
    25  },
    26  extensions = {
    27    'nvim-tree',
    28    'fugitive',
    29    'man',
    30  },
    31}
    32
  • Setup nvim-tree at after/plugin/tree.lua

    Copy
    1require("nvim-tree").setup()
    2

Remaps

Another key point of building your nvim config is customizing the keybindings. To help on the remapping process we are going to reuse this snnippet from theprimeagen dotfiles. Let's place it at the file lua/custom/keymap.lua:

Copy
1local M = {}
2
3local function bind(op, outer_opts)
4    outer_opts = outer_opts or { noremap = true }
5
6    return function(lhs, rhs, opts)
7        opts = vim.tbl_extend("force",
8            outer_opts,
9            opts or {}
10        )
11
12        vim.api.nvim_set_keymap(op, lhs, rhs, opts)
13    end
14end
15
16M.nmap = bind("n", {noremap = false})
17M.nnoremap = bind("n")
18M.vnoremap = bind("v")
19M.xnoremap = bind("x")
20M.inoremap = bind("i")
21
22return M
23

We are defining a set of functions to support all kinds of vim mappings.

Now let's create the lua/custom/remap.lua with some usefull bindings:

Copy
1local keymap = require("custom.keymap")
2local nnoremap = keymap.nnoremap
3local vnoremap = keymap.vnoremap
4local inoremap = keymap.inoremap
5local xnoremap = keymap.xnoremap
6local nmap = keymap.nmap
7
8nnoremap("Q", "<nop>")
9nmap('<esc>', ':noh <CR>')
10
11-- Add custom mappings here
12

And again, update the file lua/custom/remap.lua with require to the remap module:

Copy
1require("custom.set")
2require("custom.plugins")
3require("custom.remap")
4

Wrapping up

Now you have an NeoVim installation configured and ready to start coding.

At my personal dotfiles I dig a little bit deeper into adding more plugins and remaps, so feel free to clone the repo and get some customization insights.

Also check out ThePrimeagen youtube channnel to learn more about vim and programming in general.