Advanced Config

You learned the basics of vim (and a little bit about nvim). From this chapter on I'll mostly talk about what takes nvim from being a good editor to a great editor, its plugin-rich ecosystem and configurability.

Personal Config vs Preconfigured Configuration

Preconfigured config

Preconfigured configurations are great, they provide a fast way to get a good config which is maintained by a community. But they add a "layer" of abstraction in the config, when installing new plugins that don't interact with builtin plugins everything works, but if the plugin has to interact with a builtin plugin it can become tricky. You have to read how your preconfigured configurations expose those options which sometimes confusing and inconvenient.

It's like trying to fix stuff in a rented house, you don't know exactly how everything works but you learn as you try, sometimes you just don't want to mess with that.

Personal config

When you build your own config, you build your own house, so you know exactly how the electricity works, how the plumbing works, and how everything is connected.

Your config = your codebase, configs of nvim are written in Lua, when you write your own config you write a mini project. You know each file, you know the "flow" of the config, you manage your own codebase from scratch rather than "diving" into the existing codebase of your preconfigured configuration.

Conclusion

I think that maintaining a personal config is the better option, it takes time and effort at first but it's worthwhile. Not everyone should write their own config but I suggest you try it.

Note: If you stick with a preconfigured configuration, some parts of the next chapters are irrelevant, I'll make sure to let you know you can skip them.


Why do I need to configure my editor using a programming language?

At first I didn't understand why do I need to learn and use Lua to configure my editor but soon enough I understood it's the right way to configure your editor.
It gives the ability to utilize your coding knowledge to improve your own experience in the editor.

A great basic example for that is my go to definition keybind.

goto_def = function()
	local ft = api.nvim_buf_get_option(0, 'filetype')
	if ft == 'man' then
		api.nvim_command(':Man ' .. vim.fn.expand('<cWORD>'))
	elseif ft == 'help' then
		api.nvim_command(':help ' .. vim.fn.expand('<cword>'))
	else
		require'telescope.builtin'.lsp_definitions()
	end
end

Which provides me a goto in man page, help page or just a regular code block, all in the same bind! When I'm browsing man I feel like I browse code.

In other editors it would be a value in a .json which a builtin/plugin function handles, so in order to achieve this kind of behavior I would have to write a plugin.


Basic Lua

Regardless of whether you use a preconfigured or a personal config, you need to learn a bit of Lua. Fortunately it's quite a simple language, I suggest you to learn it the same as you would with Python, for instance.

You can find Lua resources and the basic of Lua in nvim here. It's a long guide, I used it as a reference document to start with, afterwards I switched to the :help nvim_* method, which I'll cover later.

Lua modules, packages and tables

To understand how module and package work, you have to understand what's a table in Lua.

Lua table

Tables are the main (in fact, the only) data structuring mechanism in Lua, and a powerful one. We use tables to represent ordinary arrays, symbol tables, sets, records, queues, and other data structures, in a simple, uniform, and efficient way. As far as I know this is the reason Lua is so fast.

Table usage examples (click to expand)
local my_table = {
	ofir = "gal"
}

for key, value in pairs(my_table) do
	print("key: " .. key .. ", value: " .. value)
end
-- Output:
--   key: ofir, value: gal
print(my_table.ofir) -- Output: gal

-------------------------------

local my_array = { 5, 6, 7 }
for _, value in ipairs(my_array) do
	print(value)
end
-- Output:
--  5
--  6
--  7

-------------------------------

for index, value in ipairs(my_array) do
	print(index, value)
end
-- Output:
--  1       5
--  2       6
--  3       7

-------------------------------

local my_dict = {
	["ofir"] = "gal"
}

for key, value in pairs(my_dict) do
	print("key: " .. key .. ", value: " .. value)
end
-- Output:
--   key: ofir, value: gal
print(my_dict.ofir) -- Output: gal
print(my_dict["ofir"]) -- Output: gal
print(my_table["ofir"]) -- Output: gal

More examples here


Lua module

Module is a table of exported functions and vars.

Module example (click to expand)

module_example.lua:

local M = {} -- Initialize the table of the module

-- Example for private function
local function private_func(input)
	print("Foo: " .. input)
end

-- Example of exported function
M.public_func = function(input)
	private_func(input)
end

return M -- Return the exported functions table

Usage example:

require('module_example').public_func("ofir")

local mod = require('module_example')
mod.public_func("ofir")

Lua package

Lua package is a folder which has an init.lua which is a Lua module.

Package example (click to expand)

package_example/init.lua:

local M = {}
local submod = require('anothermodule')

M.boo = function()
	submod.goo("ofir gal")
end

M.goo = submod.goo -- Expose submod.goo through the package

return M

package_example/anothermodule.lua:

local M = {}

M.goo = function(input)
	print("submod.goo: ".. input)
end

return M

Usage example:

require('package_example').boo()
require('package_example').goo("amit tamari")
require('package_example.anothermodule').goo("direct call to submodule")
require('package_example/anothermodule').goo("another way to direct call to submodule")

How to create a personal config

Launch nvim in separate environments

The first thing you'll want to do is to start your config in a git repo, you can manage your config in a separate repo and use it as a git submodule in your dotfiles or make it a part of your dotfiles repo.

nvim loads its config from ~/.config/nvim by default, but we can set it to load from a different dir:

XDG_CONFIG_HOME=~/wip_config/ XDG_DATA_HOME=~/.local/share/wip_nvim XDG_STATE_HOME=~/.local/state/wip_nvim nvim

This line launches nvim, loads the config from ~/wip_config/nvim, saves the plugins in ~/.local/share/wip_nvim and the state at ~/.local/state/wip_nvim.

This creates a virtualenv-esque setup for nvim. I recommend to set an alias to launch your personal nvim (for example pnv) until your personal config is stable enough.

Folder structure

Your config is your codebase, so you have to maintain the order. One of the fundamentals for a clean config is the folder structure.

Find a structure which allows you to maintain a clean config that doesn't add overhead when editing/adding plugins.

Example of basic config folder structure

To understand how nvim loads the config I made a simple folder structure with notes:

📂 ~/wip_config/nvim
├── 🌑 init.lua # <---- Entry point
├── 📂 lua
│  ├── 🌑 module_example.lua # <---- Interpreted at `require('module_example')`
│  └── 📂 package_example
│     ├── 🌑 init.lua # <---- Interpreted at `require('package_example')`
│     └── 🌑 anothermodule.lua # <---- Interpreted at `require('package_example/anothermodule')`
├── 📂 vim  # <---- vimscript config files
└── 📁 pack # <---- `packer` compile dir, added it to .gitignore

This is my personal opinion for how to manage nvim config, feel free to scan through other dotfiles/nvim configs for inspiration.

📂 ~/wip_config/nvim
├── 🌑 init.lua # Entrypoint, requires lua/* modules by desired order
└── 📂 lua
   ├── 🌑 autocmds.lua    # Generic autocmds
   ├── 🌑 keymaps.lua     # Keymaps
   ├── 🌑 plugin_list.lua # Setup `packer`   (plugin list)
   ├── 🌑 settings.lua    # nvim settings    (vim.opt)
   ├── 🌑 ui.lua          # UI plugins setup (loaded before other plugins)
   ├── 🌑 usercmds.lua    # Generic usercmds
   ├── 🌑 utils.lua       # Utils functions  (can be a package too)
   └── 📂 plugins
      ├── 🌑 init.lua         # Loads all the submodules (plugins setup)
      ├── 🌑 autocomplete.lua # Autocomplete engine setup
      ├── 🌑 debug.lua        # Debug related plugins
      ├── 🌑 git.lua          # Git related plugins
      ├── 🌑 hydra.lua        # Hydras
      ├── 🌑 lsp.lua          # LSP configuration and related plugins
      ├── 🌑 misc.lua         # Miscellaneous plugins
      ├── 🌑 telescope.lua    # Telescope + extensions setup
      └── 🌑 treesitter.lua   # Treesitter + extensions setup

I suggest to recreate this folder structure and write the files while reading the guide.

Config Setup

Let's start with settings.lua, transfer your vim options (vim.opt.*) to settings.lua.
Now setup init.lua to require settings.lua like so:

require('settings')

Restart your nvim and check that your vim options are set.
Remember you can use print

After that I recommend to write keymaps.lua and autocmds.lua.

Installing the first plugin

Let's start with writing plugin_list.lua, in this file you manage your installed plugins.
There are several package managers, one that is well-known is packer.
TL;DR copy paste this into plugin_list.lua and add use '{github user}/{repo}' beneath the -- My plugins here comment.

If you reset your nvim :Packer commands won't exist, you must require it from init.lua like so:

require('plugin_list')

Make sure to execute :PackerInstall to install the added plugin.

After you installed your first plugin successfully it's time to set it up.

Create plugins/init.lua file and copy the code that requires all submodules from here and add require('plugins') in init.lua.

Now you can add each plugin setup to its corresponding file and restart nvim.


Lua usage in nvim & nvim lua api

nvim has an api for almost everything in lua, each lua function generates a help doc, which you can access by :help nvim_*.

For example, the docs for vim.api.nvim_buf_get_name can be accessed using :h nvim_buf_get_name

You can run lua from the command line, :lua print(vim.api.nvim_buf_get_name(0))

Important reference links from nanotee/nvim-lua-guide and :help tags: