Yet another guide to build your Neovim environment from the ground up using Lazy.nvim.
Recently I switched my neovim plugin manager to Lazy.nvim. This article is going to be second version of Neovim setup from scratch using Lazy.nvim.
I will be using a docker instance and step by step work through to how to transform that into feature complete text editor.
Let's go ahead and spin up a container, if you want to setup in your local computer its fine too.
Make sure you have installed the latest version of Neovim and have git installed.
docker run -it --rm ubuntu:latest bash
The next step is to create a Neovim configuration file.
Go and checkout nvim folder under your home directory ~/.config/nvim
. You can create one if you don't have any.
Before we dive into crazy plugin system, I like to add basic sets and remaps for my editor.
Here is how I like to my configuration folder structure.
└── ~/.config/nvim/
├── lua/
│ ├── core
│ └── plugins
└── init.lua
I will be putting all the default neovim configurations in core
directory for example remaps
and sets
.
Create a file called sets.lua
under core directory.
-- lua/core/sets.lua
vim.g.netrw_banner = 0
vim.opt.guicursor = ""
vim.opt.spelllang = "en_us"
vim.opt.nu = true
vim.opt.relativenumber = true
vim.opt.clipboard = "unnamedplus"
vim.opt.tabstop = 4
vim.opt.softtabstop = 4
vim.opt.shiftwidth = 4
vim.opt.expandtab = true
vim.opt.smartindent = true
vim.opt.wrap = false
vim.opt.hlsearch = false
vim.opt.incsearch = true
vim.opt.termguicolors = true
vim.opt.scrolloff = 8
vim.opt.signcolumn = "yes"
vim.opt.isfname:append("@-@")
vim.opt.updatetime = 50
vim.opt.colorcolumn = "80"
vim.opt.swapfile = false
vim.opt.backup = false
vim.opt.undodir = os.getenv("HOME") .. "/.vim/undodir"
vim.opt.undofile = true
vim.o.tabstop = 2
vim.o.shiftwidth = 2
vim.o.expandtab = true
And a file called remaps.lua
as well.
-- lua/core/remaps.lua
-- leader key mapping
vim.g.mapleader = " ";
-- Open netrw
vim.keymap.set("n","<leader>pv",vim.cmd.Ex);
-- move up and down
vim.keymap.set("v", "K", ":m '<-2<CR>gv=gv")
vim.keymap.set("v", "J", ":m '>+1<CR>gv=gv")
-- half page movement
vim.keymap.set("n", "<C-d>", "<C-d>zz")
vim.keymap.set("n", "<C-u>", "<C-u>zz")
-- jk escape
vim.keymap.set("i", "jk", "<ESC>")
To check back your directory should look like this
.
└── ~/.config/nvim/
├── lua/
│ ├── core/
│ │ ├── sets.lua
│ │ └── remaps.lua
│ └── plugins
└── init.lua
We now have two configuration files but if you check your Neovim , you will see nothing is happening. The thing is we just created two seperate lua files and we will have to include those modules for Neovim to know.
In Neovim, you can set all your user specific configurations to init.lua
.
All we have to do is import those configurations to init.lua
.
Create a file named init.lua
inside core
directory and include those two modules.
-- /core/init.lua
require("core.remaps")
require("core.sets")
Finally, you will have to include the core
module itself into root's init.lua
file.
-- /.config/nvim/init.lua
require("core")
After that you can now quit Neovim and launch it agian . You should see all the configuration working properly.
Your final directory should look like this.
.
└── ~/.config/nvim/
├── lua/
│ ├── core/
│ │ ├── init.lua
│ │ ├── sets.lua
│ │ └── remaps.lua
│ └── plugins
└── init.lua
As for next step, let's install our lazy.nvim .
Create a file named lazi.lua
under lua folder. You can checkout lazy.nvim github for more options and configurations. Here is what I will be using in this setup.
-- /.config/nvim/lua/lazi.lua
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({
{ import = 'plugins' },
})
Include the lazy configuration to your root init file.
-- /.config/nvim/init.lua
require("core")
require("lazi")
After this is all you have to do is restart neovim.
Once you restart neovim, you should see message like this, no module plugins found.
Let's take a step back and look at our configuration file.
require('lazy').setup({
{ import = 'plugins' },
})
In our lazi.lua
, you can see import = 'plugins'
. What it means is lazy is looking for plugins in plugins
directory we defined which is currently empty.
Its time for us to throw our favorite plugins in the folder.
Let's see how we can install plugins with lazy.
To install treesitter , you need to create a file named treesitter.lua
inside plugin folder.
-- ~/.config/nvim/lua/plugins/treesitter.lua
return {
"nvim-treesitter/nvim-treesitter",
config = function()
local configs = require("nvim-treesitter.configs")
configs.setup({
ensure_installed = { "c", "lua", "vim", "vimdoc", "rust", "javascript", "html" },
sync_install = false,
highlight = { enable = true },
indent = { enable = true },
})
end;
}
You can check out more configuration in treesitter official documentation. Restart Neovim and then sit back and relax you should see lazy doing all the work for you.
To install mason , create another file in plugins directory.
return {
"williamboman/mason.nvim",
dependencies = {
"williamboman/mason-lspconfig.nvim",
"WhoIsSethDaniel/mason-tool-installer.nvim",
},
config = function()
-- import mason
local mason = require("mason")
-- import mason-lspconfig
local mason_lspconfig = require("mason-lspconfig")
local mason_tool_installer = require("mason-tool-installer")
-- enable mason and configure icons
mason.setup({
ui = {
icons = {
package_installed = "✓",
package_pending = "➜",
package_uninstalled = "✗",
},
},
})
mason_lspconfig.setup({
-- list of servers for mason to install
ensure_installed = {
"tsserver",
"html",
"cssls",
"tailwindcss",
"svelte",
"lua_ls",
"graphql",
"emmet_ls",
},
-- auto-install configured servers (with lspconfig)
automatic_installation = true, -- not the same as ensure_installed
})
mason_tool_installer.setup({
ensure_installed = {
"prettier", -- prettier formatter
"stylua", -- lua formatter
"eslint_d", -- js linter
},
})
end,
}
To check back your directory structure should look like this.
.
└── ~/.config/nvim/
├── lua/
│ ├── core/
│ │ ├── init.lua
│ │ ├── sets.lua
│ │ └── remaps.lua
│ └── plugins
│ │ ├── treesitter.lua
│ │ ├── .....lua
│ │ └── mason.lua
│ └── lazi.lua
└── init.lua
return {
"neovim/nvim-lspconfig",
config = function()
-- Setup language servers.
local lspconfig = require("lspconfig")
lspconfig.pyright.setup({})
lspconfig.tsserver.setup({})
lspconfig.emmet_language_server.setup({
filetypes = {
"css",
"svelte",
"eruby",
"html",
"javascript",
"javascriptreact",
"less",
"sass",
"scss",
"pug",
"typescriptreact",
},
-- Read more about this options in the [vscode docs](https://code.visualstudio.com/docs/editor/emmet#_emmet-configuration).
-- **Note:** only the options listed in the table are supported.
init_options = {
---@type table<string, string>
includeLanguages = {},
--- @type string[]
excludeLanguages = {},
--- @type string[]
extensionsPath = {},
--- @type table<string, any> [Emmet Docs](https://docs.emmet.io/customization/preferences/)
preferences = {},
--- @type boolean Defaults to `true`
showAbbreviationSuggestions = true,
--- @type "always" | "never" Defaults to `"always"`
showExpandedAbbreviation = "always",
--- @type boolean Defaults to `false`
showSuggestionsAsSnippets = false,
--- @type table<string, any> [Emmet Docs](https://docs.emmet.io/customization/syntax-profiles/)
syntaxProfiles = {},
--- @type table<string, string> [Emmet Docs](https://docs.emmet.io/customization/snippets/#variables)
variables = {},
},
})
lspconfig.svelte.setup({})
lspconfig.clangd.setup({})
lspconfig.tailwindcss.setup({})
lspconfig.rust_analyzer.setup({
-- Server-specific settings. See `:help lspconfig-setup`
settings = {
["rust-analyzer"] = {
checkOnSave = {
command = "clippy",
},
},
},
})
-- Global mappings.
-- See `:help vim.diagnostic.*` for documentation on any of the below functions
--
vim.keymap.set("n", "<leader>dp", vim.diagnostic.goto_prev)
vim.keymap.set("n", "<leader>dn", vim.diagnostic.goto_next)
vim.keymap.set("n", "<space>df", vim.diagnostic.open_float)
vim.keymap.set("n", "<space>dl", vim.diagnostic.setloclist)
-- Use LspAttach autocommand to only map the following keys
-- after the language server attaches to the current buffer
vim.api.nvim_create_autocmd("LspAttach", {
group = vim.api.nvim_create_augroup("UserLspConfig", {}),
callback = function(ev)
-- Enable completion triggered by <c-x><c-o>
vim.bo[ev.buf].omnifunc = "v:lua.vim.lsp.omnifunc"
-- Buffer local mappings.
-- See `:help vim.lsp.*` for documentation on any of the below functions
local opts = { buffer = ev.buf }
vim.keymap.set("n", "gD", vim.lsp.buf.declaration, opts)
vim.keymap.set("n", "gd", vim.lsp.buf.definition, opts)
vim.keymap.set("n", "K", vim.lsp.buf.hover, opts)
vim.keymap.set("n", "gi", vim.lsp.buf.implementation, opts)
vim.keymap.set("n", "gt", vim.lsp.buf.type_definition, opts)
vim.keymap.set("n", "gr", vim.lsp.buf.references, opts)
vim.keymap.set("n", "<leader>r", vim.lsp.buf.rename, opts)
vim.keymap.set({ "n", "v" }, "<leader>ca", vim.lsp.buf.code_action, opts)
vim.keymap.set("n", "<C-k>", vim.lsp.buf.signature_help, opts)
vim.keymap.set("n", "<space>wa", vim.lsp.buf.add_workspace_folder, opts)
vim.keymap.set("n", "<space>wr", vim.lsp.buf.remove_workspace_folder, opts)
vim.keymap.set("n", "<space>wl", function()
print(vim.inspect(vim.lsp.buf.list_workspace_folders()))
end, opts)
vim.keymap.set("n", "<space>f", function()
vim.lsp.buf.format({ async = true })
end, opts)
end,
})
end,
}
You should see the pattern by now. If you want to add a plugin, create a file in plugins folder and then let lazy do the job. Most of the plugins now have guide to install lazy readily.
Lazy also watch for changes and you should see it automatically installing new plugins.
You can also manually open the menu using :Lazy
command as well.
That's it. I will not be focusing a lot on Neovim and plugins related stuffs. Hopefully this article is helpful for you if you are planning to try lazy.nvim.
I recommend you definitely should.
Join my web development newsletter to receive the latest updates, tips, and trends directly in your inbox.