Neovim config from scratch

Bouteiller < A2N > Alan
10 min readOct 24, 2023

So you want to pimp your neovim ? I’ve erased mine to show you how to do it, so let’s go !

Basics

So we go into the .config folder and create the nvim folder.
Then we create an init.lua, that is the first file loaded by nvim.
After that we add a folder called lua then, in it, a folder called a2n.
Each folder present on the lua folder is callable with lua.

For example, if we put a print in the first init file then create a second init file in the a2n folder, with a print too we have both of the print showed when we launch nvim.

So far we have done that :

➜ tree
.
├── init.lua
└── lua
└── a2n
└── init.lua

In the first init.lua, add the following line for loading the init.lua file created in the lua/a2n folder :

require("a2n")

Now, how to install plugins ?
For that we are going to use packer, a plugin manager.

You just have to run the following command in your terminal for installing it :

git clone - depth 1 https://github.com/wbthomason/packer.nvim\
~/.local/share/nvim/site/pack/packer/start/packer.nvim

Then we create a file named packer.lua in the a2n folder.
And we add the following code in it :

vim.cmd [[packadd packer.nvim]]
return require('packer').startup(function(use)
use 'wbthomason/packer.nvim'
end)

Then we can close neovim, add the following line in the init.lua file from the a2n folder :

require("a2n.packer")

Save and type PackerSync. And voilà Packer is set up!

Telescope

Now let’s go with Telescope. Telescope is a fuzzy finder, it means that this plugin gives us the ability to navigate and search files and folders.

We just have to add the following line to our packer.lua file :

use {
'nvim-telescope/telescope.nvim', tag = '0.1.4', requires = { {'nvim-lua/plenary.nvim'} }
}

Pro tips : hit the = key if you want to indent correctly the line you have selected with v.

And then PackerSync.

Now we create a directory called after in the root of our config, and next we create a directory named plugin in it.

You should have something like that :

.
├── after
│ └── plugin
├── init.lua
├── lua
│ └── a2n
│ ├── init.lua
│ └── packer.lua
└── plugin
└── packer_compiled.lua

Now we create a file named telescope.lua in this folder. Next go to the telescope repository and grab the first two lines of the config right there and add it to the file :

local builtin = require('telescope.builtin')
vim.keymap.set('n', '<leader>ff', builtin.find_files, {})

If you want, you can change the shortcut, for example if you want to replace ff with sf :

vim.keymap.set('n', '<leader>pf', builtin.find_files, {})

Telescope is packed with a lot of built-in finders so, for example, we can only search git file by adding the following shortcut (lead+f+g):

vim.keymap.set('n', '<leader>fg', builtin.git_files, {})

Remaps key

By default, the leader key is the backslash and I don’t want this key, I want the space key (the super key is not recognized by most of the terminal this is why I use space).
For that we are going to create a file named remap.lua in the a2n folder.

And we add the following line :

vim.g.mapleader = " "

Then save and reopen neovim and hit space+f+f and voila !

Telescope find files in action

Treesitter

Now the blazing fast plugin : treesitter. This stuff parse your code like Flash (the superhero not the dead language) and build an incremental tree for each change you do, in live. That gives you as a result to have highlighting and indenting for example.

If you want to use it (don’t be that guy, use it) you just have to add this line to the packer.lua file :

use { 'nvim-treesitter/nvim-treesitter', run = ':TSUpdate' }

You can have an error here with the TSUpdate command. This is a known bug. But it will run correctly when updating, so we can add the following code to avoid that (in place of the TSUpdate) :

run = function()
local ts_update = require('nvim-treesitter.install').update({ with_sync = true })
ts_update()
end,

Or you can just call PackerUpdate.

Now we have to initialize treesitter. And you guess well, let’s create a file under the plugin folder called treesitter.lua.

Then add the example config found on the repo in it, remove all the comments if you want, do the same with the options you don’t want, add some language in the required parser, then boom a clean config file :

require'nvim-treesitter.configs'.setup {
ensure_installed = { "rust", "javascript", "typescript", "c", "lua", "vim", "vimdoc", "query" },
sync_install = false,
auto_install = true,
highlight = {
enable = true,
additional_vim_regex_highlighting = false,
},
}

Write the file then exit and re-open it, and if everything is set up correctly you may view at the bottom treesitter downloading all the parser we have put on the option key.

Now you may have colorization like so :

Colorization via the lua parser

Fugitive

Fugitive gives us the possibility to do a lot of stuff with git. For example, if you want to check the blame you have to do a :Git blame :

Git blame with fugitive

The same install process is applied here :
- add the use line to the packer.lua file
- write and close the file
- reopen neovim and do a PackerSync
- create shortcut for the command you want to use :)

If you want to have inline blame you can use the following plugin : lewis6991/gitsigns.nvim.

I’m stopping there for the plugin because you have to check what kind of plugin you want among the huge choice available out there :)
If you want some ideas I have put at the end of this article a little list of plugins I used.

The LSP

The LSP is the heart of our setup, especially if your plan is to use neovim for coding :)
This is the stuff that gives you auto-completion on context based on the current language or framework your working with.

First I want to install j-hui/fidget.nvim because it gives a little but practical information : is the current buffer loading a lsp ?

rust lsp on launch

As always we add a use line on our plugin.lua file like so :

use { 'j-hui/fidget.nvim', tag = 'legacy' }

After a reload and a PackerSync we create the now well-known, config file (named fidget.lua) under after/plugin and call the main function :

require('fidget').setup {}

Ok now the lsp in itself !
We are going to use lsp-zero for that. Let’s copy the code available on the repo right there and paste it on the packer file, like always. Uncomment the two first lines and voilà :

use {
'VonHeikemen/lsp-zero.nvim',
branch = 'v3.x',
requires = {
- manage LSP from neovim
{'williamboman/mason.nvim'},
{'williamboman/mason-lspconfig.nvim'},
- LSP Support
{'neovim/nvim-lspconfig'},

- Autocompletion
{'hrsh7th/nvim-cmp'},
{'hrsh7th/cmp-nvim-lsp'},
{'L3MON4D3/LuaSnip'},
}
}

Reload, do PackerSync and then create a file named lsp.lua under the after/plugin directory.

Ok, now we copy and paste the following code into our config file. That allows lsp-zero to load his shortcuts only if a lsp is active on the current buffer.
This is very handy because we can create a shortcut that uses gd only if a lsp is present and, on the other case, uses the same shortcut for something else.

local lsp_zero = require('lsp-zero')
lsp_zero.on_attach(function(client, bufnr)
lsp_zero.default_keymaps({buffer = bufnr})
end)

Now we have two options for handling the server we want to install on our setup :
- you dl the lsp you want via the information provided by this page and add the init function of each lsp into the lsp.lua file
- you use the mason plugin that allows you to automatically handle the lsp you want via lsp-zero

I’m gonna fit with mason because I love to have automatic setup, especially on lsp that is a kind of boring stuff when it’s installed manually.

The two packages we need (mason and mason-lspconfig) are already loaded in our config (remember the two lines we have un-commented).

Next, we just have to implement it into the config file of lsp-zero like so :

require('mason').setup({})
require('mason-lspconfig').setup({
ensure_installed = {'tsserver', 'rust_analyzer'},
handlers = {
lsp_zero.default_setup,
},
})

Here we have this line lsp_zero.default_setup that automatically handle the config for us.
And in the ensure_installed key we can put all the lsp we want from this list.

For example with Lua we add lua_ls, write and close the file then reopen neovim and tada! :

Autocomplete !

You can also use directly the Mason command for doing so but keep in mind that this solution doesn’t add the lsp name to this list.

LSP auto install with mason

In the same way keep in mind that certain lsp need certain prerequisite, for example the lsp for html, css and javascript need to have npm so you have to install it.

You have all the default shortcut right there : https://github.com/VonHeikemen/lsp-zero.nvim/blob/v3.x/doc/md/api-reference.md#lsp-actions.

Finally I’m going to change the default icon showed in the gutter via this config :

lsp_zero.set_sign_icons({
error = '✘',
warn = '▲',
hint = '⚑',
info = '»'
})

Final setup

Ok, now I just have to do one or two things for having neovim like I love. For example, I don’t have any line number.

For that we have to create another config file under lua/a2n, in general call it base.lua and we need to load it via the init.lua from lua/a2n.

So I add the require line in the init.lua file :

require("a2n.base")

And now I add the following settings to the base.lua file :

- NEOVIM
vim.opt.termguicolors = true - enabled 24bit RGB color
vim.opt.signcolumn = 'yes' - always draw the sign column
vim.opt.updatetime = 50 - update time for the swap file and for the cursorHold event
vim.opt.colorcolumn = '80' - colorized the 80th column
vim.opt.clipboard:append { 'unnamedplus' } - force to use the clipboard for all the operations
- BACKUP
vim.opt.backup = false - no backup of the current file is made
- FILE LINES
vim.wo.number = true - show line number
vim.wo.relativenumber = true - set line number format to relative
vim.opt.wrap = false - wrap lines
vim.opt.scrolloff = 8 - min nb of line around your cursor (8 above, 8 below)
- INDENT
vim.opt.smartindent = true - try to be smart w/ indent
vim.opt.autoindent = true - indent new line the same amount as the line before
vim.opt.shiftwidth = 2 - width for autoindents
- TAB
vim.opt.expandtab = true - converts tabs to white space
vim.opt.tabstop = 2 - nb of space for a tab in the file
vim.opt.softtabstop = 2 - nb of space for a tab in editing operations
- SEARCH
vim.opt.ignorecase = true - case insensitive UNLESS /C or capital in search
vim.opt.hlsearch = true - highlight all the result found
vim.opt.incsearch = true - incremental search (show result on live)
vim.opt.wildignore:append { '*/node_modules/*', '*/vendor/*' } - the search ignore this folder
- CONTEXTUAL
vim.opt.title = true - set the title of the window automaticaly, usefull for tabs plugin
vim.opt.path:append { '**' } - search (gf or :find) files down into subfolders

If you don’t want backup but undotree

By default, neovim use a backup file for each buffer that you open.
Another option you want to use, more familiar, especially if you use git, is the `undotree` plugin.

We just have to install it via packer, as always :

- in packer.lua
use 'mbbill/undotree'

And you have to set some options in the base.lua file too :

vim.opt.swapfile = false - disable the default backup behavior
vim.opt.backup = false - disable the default backup behavior
vim.opt.undofile = true - activate the undofile behavior
vim.opt.undodir = os.getenv('HOME') .. '/.vim/undodir' - use the directory of undotree plugin for managing the history

With that you can view the undo tree created by neovim :

Undotree

And you can navigate the history like in git ! This is very powerful.

We also can add the shortcut via a config file :

vim.keymap.set('n', '<leader><F5>', vim.cmd.UndotreeToggle)

Theme

Okai, for the end of this tutorial a little bit of art, because we all love an eyes candy setup !

I’m going to use the catppuccin theme, you have the repo for neovim just here.

We install it like any other plugin : use { ‘catppuccin/nvim’, as = ‘catppuccin’ }.
Don’t forget to do PackerSync.

And then we create the config file (as always in after/plugin), the file is called catppuccin.lua :

require('catppuccin').setup({
flavour = 'mocha'
})
vim.cmd.colorscheme 'catppuccin'

And voilà :

Tada 🥳

That’s all folks, thanks for reading :)

List of plugins I use or have used :

use 'wbthomason/packer.nvim'
use { 'catppuccin/nvim', as = 'catppuccin' } -- theme
use 'nvim-lualine/lualine.nvim' -- statusline
use 'nvim-lua/plenary.nvim' -- common utilities
use 'onsails/lspkind-nvim' -- vscode-like pictograms
use 'hrsh7th/cmp-buffer' -- nvim-cmp source for buffer words
use 'hrsh7th/cmp-nvim-lsp' -- nvim-cmp source for neovim built-in LSP
use 'hrsh7th/nvim-cmp' -- auto completion
use 'neovim/nvim-lspconfig' -- LSP
use 'jose-elias-alvarez/null-ls.nvim' -- use Neovim as a language server to inject LSP diagnostics, code actions, and more via Lua
use 'MunifTanjim/prettier.nvim' -- prettier plugin for Neovim built-in LSP client
use 'williamboman/mason.nvim' -- lsp auto handler
use 'williamboman/mason-lspconfig.nvim' -- lsp auto handler
use 'glepnir/lspsaga.nvim' -- LSP UIs
use 'L3MON4D3/LuaSnip' -- Snippet Engine
use { 'nvim-treesitter/nvim-treesitter', run = ':TSUpdate' }
use 'kyazdani42/nvim-web-devicons' -- file icons
use 'nvim-telescope/telescope.nvim' -- file browser
use 'nvim-telescope/telescope-file-browser.nvim' -- file browser
use 'nvim-lua/popup.nvim'
use {'nvim-telescope/telescope-fzf-native.nvim', run = 'make' } -- fzf in telescope
use 'windwp/nvim-autopairs' -- auto pairs for general symbol
use 'windwp/nvim-ts-autotag' -- auto tag for ts
use 'norcalli/nvim-colorizer.lua' -- give the real color under the css value
use 'folke/zen-mode.nvim'
use({ "iamcco/markdown-preview.nvim", run = function() vim.fn["mkdp#util#install"]() end, })
use {'akinsho/bufferline.nvim', tag = "v2.*", requires = 'kyazdani42/nvim-web-devicons'}
use 'lewis6991/gitsigns.nvim' -- git blame inline
use 'dinhhuy258/git.nvim' -- For git blame & browse
use 'goolord/alpha-nvim' -- dashboard greeter
use 'j-hui/fidget.nvim' -- workspace lsp status

--

--