How do I configure my neovim lazily

Motivation

As a loyal vscode user, I have been through the process of despising vimer \(\rightarrow\) understanding vimer \(\rightarrow\) becoming vimer. In the past 4 or 5 years, I tried several times to use vim, but it always ended up with me going back to vscode, as vim seems to be too hard to learn and too complicated to configure and customize. Recently, I noticed the neovim project and several open-source projects that help people configure their neovim easily and lazily. I then decided to give it a try and see if I can finally become a vimer. And... I did it! And now I am trying to switch everything to neovim.

Vi, Vim, Neovim, and AstroNvim

It all started with the vi editor, which was created in 1976 by Bill Joy who was a student at UC Berkeley at that time. He used a Lear-Siegler ADM-3A terminal to write the vi editor, and due to that the terminal keyboard has a very small and strange layout, the vi editor thus inherited this evil. For example, keys h, j, k and l serve as the cursor movement keys, which really made me confused at the beginning. I refer you to the Wikipedia page for more stories behind this.

Vim, or "vi improved", is Bram Moolenaar's improved version of vi. It brings a lot of new features and improvements, such as completion, syntax highlighting, mouse interaction, extended regular expressions, etc. And it is still being actively developed and maintained, and is used by many people around the world.

When people are trying to improve one thing, they often find that starting a new project is easier. Neovim is such an improved version of vim which is the improved version of vi. Comparing with vim, neovim is more modern with strong extensibility. It employs Lua as its configuration language, and has a built-in language server protocol (LSP) client. It is said that neovim is more friendly to plugin developers. And for us, it is easier to make it vscode-like, or whatever we want it to be.

Like three or four years ago, when I first knew about neovim, I despise the guys who were trying to make it as fancy as vscode, as I thought that it was a waste of time. But now, I am one of them. This is because many open-source projects have been created to make this process easier. Among them I have tried:

As well as AstroNvim which I am using now. (A more comprehensive list can be found at awesome-neovim). Comparing with other projects, to me, AstorNvim has the following advantages: (1) faster, (2) easier to configure. Those two reasons are important to me because I often use clusters which have very old and outdated environments, and the resources are limited (eg., on Hoffman2, the memory of login node is limited to 1 GB). In addition, the UI of AstroNvim is more aesthetic to me, and the layout is more reasonable, so I chose it to be the base of my neovim configuration.

Basics of configurations

vim configuration

Before neovim, we should know something about the ~/.vimrc file that configures vim. .vimrc file basically contains a list of command that you would type in the command mode of vim. For example, if you want to see line numbers every time you open a file, you can add the following line to your .vimrc file:

1
set number

When launching vim, it will read the .vimrc file and execute all the commands in it. But neovim is different, it uses lua language to configure itself, meaning that you have to write lua code to configure it. When I first learned about this, I felt strange, like, why do I have to learn another language to configure a text editor? But be easy, lua is very simple, and you only need to know a few basic syntax to understand the existing configurations and copy and paste to create your own.

Neovim lua configuration

Different from the logic of .vimrc which runs every line of the commands, the lua configuration file is composed of functions, dictionaries, and tables, that are more organized. For example, the same command set number in .vimrc, should be written as follows instead, in ~/.config/nvim/init.lua:

1
vim.opt.number = true

The vim.opt is a dictionary that contains all the vim options. For more sophisticated configurations of .vimrc, one should check the documentation carefully, and translate them to lua code like above.

Plugins

Neovim is known for its strong extensibility, and it is easy to install and manage plugins. There are two popular plugin managers for neovim, Packer and Lazy. They are more or less the same, and here we just show how Lazy works. Add the following code to your ~/.config/nvim/init.lua:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
vim.fn.system({
"git",
"clone",
"--filter=blob:none",
"https://github.com/folke/lazy.nvim.git",
"--branch=stable", -- latest stable release
lazypath,
})
end
vim.opt.rtp:prepend(lazypath)

require("lazy").setup(plugins, opts)

As the lua language is easy to read, you may understand that the code above basically install the lazy plugin manager if it is not installed, and then load it. The plugins and opts are the configurations of the plugins, and they can be placed in the init.lua file or in a separate file, like ~/.config/nvim/lua/plugins.lua.

With Lazy configured, plugins can be installed and configured by adding information to the plugins and opts dictionaries. For example, to install the neo-tree plugin, you can add the following code to either lua/plugins.lua or lua/plugins/neo-tree.lua:

1
2
3
4
5
6
7
8
9
10
11
12
return {
{
"nvim-neo-tree/neo-tree.nvim",
branch = "v3.x",
dependencies = {
"nvim-lua/plenary.nvim",
"nvim-tree/nvim-web-devicons", -- not strictly required, but recommended
"MunifTanjim/nui.nvim",
-- "3rd/image.nvim", -- Optional image support in preview window: See `# Preview Mode` for more information
}
}
}

And add the plugins dictionary in init.lua:

1
local plugins = require("plugins")

before the line require("lazy").setup(plugins, opts). This line just load everything from lua/plugins.lua and everything else from the folder lua/plugins.

AstroNvim configuration

Bearing the above knowledge in mind, it is super easy to understand the AstroNvim configuration. Follow the official website, it is simply a matter of git clone:

1
2
3
4
5
6
7
8
# Backup the old configuration
mv ~/.local/share/nvim ~/.local/share/nvim.bak
mv ~/.local/state/nvim ~/.local/state/nvim.bak
mv ~/.cache/nvim ~/.cache/nvim.bak
# Clone the AstroNvim configuration
git clone --depth 1 https://github.com/AstroNvim/AstroNvim ~/.config/nvim
# Launch it!
nvim

Looking into the ~/.config/nvim folder, the structure is something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
❯ tree ~/.config/nvim -L 2
/Users/chuanjin/.config/nvim
├── LICENSE
├── config.ld
├── init.lua
└── lua
├── astronvim
├── lazy_snapshot.lua
├── plugins
├── resession
└── user

6 directories, 4 files

Here, init.lua calls everything in lua/astronvim and lua/plugins, and they are part of the git repo. The lua/user/ folder, however, is included in the .gitignore file, meaning that it is for your own customization.

AstroNvim also provides a convenient way to configure other plugins, as you don't have time to set everything up by yourself. That is, AstroNvim has a community-driven plugin configuration repo, that is AstroCommunity. And simply by adding import lines, most popular plugins can be easily configured perfectly, and that was initially why I turned to AstroNvim rather than other projects. Take copilot as an example, the configuration concerns with the copilot.lua, as well as the the settings for cmp, and it drove me crazy, as sometimes I wanted to have copilot completion word by word, rather than a whole block. But with this community configuration, all I need to do is to add the following line to lua/user/plugins/community.lua:

1
2
3
4
5
6
return {
"AstroNvim/astrocommunity",
{
import = "astrocommunity.completion.copilot-lua-cmp",
},
}

which loads AstroNvim/astrocommunity and then the astrocommunity.completion.copilot-lua-cmp configuration. By reading the configuration file in the git repo, I get to know that <C-l> is the key to trigger the one-word completion, and <C-j> is to trigger the one-line completion.

Snippets

VSCode snippet system is the feature that I cannot live without. It is so powerful and easy to configure. But here, neovim can also replicate it with the help of the LuaSnip plugin, just put the following content in lua/user/plugins/luasnip.lua:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
return {
{
"L3MON4D3/LuaSnip",
config = function(plugin, opts)
-- include the default astronvim config that calls the setup call
require "plugins.configs.luasnip"(plugin, opts)
-- load snippets paths
require("luasnip.loaders.from_vscode").lazy_load {
-- this can be used if your configuration lives in ~/.config/nvim
-- if your configuration lives in ~/.config/astronvim, the full path
-- must be specified in the next line
paths = { "./lua/user/snippets" }
}
end,
},
}

which allows the LuaSnip plugin to load the snippets from the lua/user/snippets folder, in which a package.json file should be created to contain the information of your snippets, for example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"name": "user snippets",
"engines": {
"vscode": "^1.11.0"
},
"contributes": {
"snippets": [
{
"language": "markdown",
"path": "./markdown.json"
}
]
}
}

Lastly, the markdown.json file should contain the snippets, for example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"[MATH] Bold Symbol": {
"prefix": ">bm",
"body": [
"\\boldsymbol{$1}"
]
},
"[MATH] aligned": {
"prefix": ">aligned",
"body": [
"$$\n\\begin{aligned}",
"\t$1",
"\\end{aligned}\n$$"
]
}
}

Pointing the contents >bm and >aligned to the corresponding snippets. Please note that no comments are allowed in these json files, different from VSCode.

Dashboard

Although I admit that I truly think it is worthing doing, but man, you can't resist the temptation to customize all decorations in your neovim. I recommend to generate ASCII art with the help of patorjk.com, or take whatever you like from the ASCII art archive, and add them to lua/user/plugins/dashboard.lua. I generated the :wq ASCII art as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
return {
{
"goolord/alpha-nvim",
opts = function(_, opts) -- override the options using lazy.nvim
opts.section.header.val = { -- change the header section value
" ____ __ ____ ______ ",
" _ \\ \\ / \\ / / / __ \\ ",
"(_) \\ \\/ \\/ / | | | | ",
" \\ / | | | | ",
" _ \\ /\\ / | `--' '--. ",
"(_) \\__/ \\__/ \\_____\\_____\\",
" ",
" https://write-n-quit.com",
}
end,
},
}

Note that here the backslashes are doubled. The result is something like: