Ultimate Neovim Setup Guide: lazy.nvim Plugin Manager
Tags: tutorial, neovim, productivity, learning
Published: 2024-06-25 00:54:33

Hello everyone! In this article I will show you how to configure the neovim editor from scratch with lazy.vim.

## 💤 Lazy.nvim
A modern plugin manager for Neovim

* Repo: [folke/lazy.nvim](https://github.com/folke/lazy.nvim)

* Outstanding features:
	* 📦 Manage all your Neovim plugins with a powerful UI
	* 🚀 Fast startup times thanks to automatic caching and bytecode compilation of Lua modules
	* 🔌 Automatic lazy-loading of Lua modules and lazy-loading on events, commands, filetypes, and key mappings
	* ⏳ Automatically install missing plugins before starting up Neovim, allowing you to start using it right away
	* 🛠️ No need to manually compile plugins
	* 🧪 Correct sequencing of dependencies
	* 📁 Configurable in multiple files
	* 🔎 Automatically check for updates

## 📚 GitHub Repository
All the code is in my Github profile at [slydragonn/nvim-lazy](https://github.com/slydragonn/nvim-lazy) repo.

## 📹 Tutorial video
{% embed https://youtu.be/-kCUks3xrCM %}

## ⚙ Requirements

- Neovim >= v0.10.0
- [Nerd Fonts](https://www.nerdfonts.com/font-downloads)
- NodeJS with npm
- [Lazy.vim](https://github.com/folke/lazy.nvim)
- A C compiler in your path and libstdc++ installed: [Windows support](https://github.com/nvim-treesitter/nvim-treesitter/wiki/Windows-support)
- [Git](https://git-scm.com/downloads)

## ✨ Features

- [folke/lazy.nvim](https://github.com/folke/lazy.nvim): A modern plugin manager for Neovim
- [nvim-neo-tree/neo-tree](https://github.com/nvim-neo-tree/neo-tree.nvim): Neovim plugin to manage the file system and other tree like structures.
- [tiagovla/tokyodark.nvim](https://github.com/tiagovla/tokyodark.nvim): A clean dark theme written in lua for neovim.
- [nvim-tree/nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons): lua fork of vim-web-devicons for neovim.
- [nvim-lualine/lualine.nvim](https://github.com/nvim-lualine/lualine.nvim): A blazing fast and easy to configure neovim statusline plugin written in pure lua.
- [nvim-treesitter/nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter): Nvim Treesitter configurations and abstraction layer.
- [windwp/nvim-ts-autotag](https://github.com/windwp/nvim-ts-autotag): Use treesitter to auto close and auto rename html tag.
- [stevearc/conform.nvim](https://github.com/stevearc/conform.nvim): Lightweight yet powerful formatter plugin for Neovim.
- [nvim-telescope/telescope.nvim](https://github.com/nvim-telescope/telescope.nvim): Highly extendable fuzzy finder over lists.
- [neovim/nvim-lspconfig](https://github.com/neovim/nvim-lspconfig): Quickstart configs for Nvim LSP
- [hrsh7th/nvim-cmp](https://github.com/hrsh7th/nvim-cmp): A completion plugin for neovim coded in Lua.
- [williamboman/mason.nvim](https://github.com/williamboman/mason.nvim): Portable package manager for Neovim that runs everywhere Neovim runs.
- [norcalli/nvim-colorizer.lua](https://github.com/norcalli/nvim-colorizer.lua): Color highlighter.
- [akinsho/toggleterm.nvim](https://github.com/akinsho/toggleterm.nvim): A neovim lua plugin to help easily manage multiple terminal windows.
- [lewis6991/gitsigns.nvim](https://github.com/lewis6991/gitsigns.nvim): Git integration for buffers.
- [windwp/nvim-autopairs](https://github.com/windwp/nvim-autopairs): Autopairs for neovim written by lua.
- [onsails/lspkind.nvim](https://github.com/onsails/lspkind.nvim): vscode-like pictograms for neovim lsp completion items.
- [L3MON4D3/LuaSnip](https://github.com/L3MON4D3/LuaSnip): Snippet Engine for Neovim written in Lua.
- [hrsh7th/cmp-nvim-lsp:](https://github.com/hrsh7th/cmp-nvim-lsp) nvim-cmp source for neovim builtin LSP client
- [hrsh7th/cmp-path:](https://github.com/hrsh7th/cmp-path) nvim-cmp source for path
- [hrsh7th/cmp-buffer:](https://github.com/hrsh7th/cmp-buffer) nvim-cmp source for buffer words
- [williamboman/mason-lspconfig.nvim:](https://github.com/williamboman/mason-lspconfig.nvim) Extension to mason.nvim that makes it easier to use lspconfig with mason.nvim.
- [WhoIsSethDaniel/mason-tool-installer.nvim:](https://github.com/WhoIsSethDaniel/mason-tool-installer.nvim) Install and upgrade third party tools automatically.

## 📚 Project Structure

```
📂 nvim/
├── 📂 lua/📂 slydragonn/
│	 └── 📂 plugins/
│        └── 📂 lsp/
│        └── ...pluginconfigfiles
│	 └── 🌑 settings.lua
│	 └── 🌑 maps.lua
│    └── 🌑 lazy.lua
└── 🌑 init.lua
```

### If you don’t have some requirements

- Nerd Fonts: https://www.nerdfonts.com/
- Neovim: https://github.com/neovim/neovim/releases/
- Node: https://nodejs.org/en/download/package-manager
- C compiler: https://github.com/nvim-treesitter/nvim-treesitter/wiki/Windows-support
- Git: https://git-scm.com/downloads

## Saving Settings
The configuration files go to a particular place, so you should create the nvim/ folder in the following path depending on your operating system:

* Windows: `C:\Users\%YOUR_USERNAME%\AppData\Local\nvim`

* Linux: `~/.configs/nvim/`

And in the nvim/ folder create the `init.lua` file
with the following code:

* Note: slydragonn is my personal folder, but you can rename it whatever you want :)

```lua
-- ~/nvim/init.lua

require("slydragonn.settings")
```

## Editor Settings
Then create a lua folder for our configuration and also for the plugins.

```lua
-- ~/nvim/lua/slydragonn/settings.lua

local global = vim.g
local o = vim.opt

-- Editor options

o.number = true -- Print the line number in front of each line
o.relativenumber = true -- Show the line number relative to the line with the cursor in front of each line.
o.clipboard = "unnamedplus" -- uses the clipboard register for all operations except yank.
o.syntax = "on" -- When this option is set, the syntax with this name is loaded.
o.autoindent = true -- Copy indent from current line when starting a new line.
o.cursorline = true -- Highlight the screen line of the cursor with CursorLine.
o.expandtab = true -- In Insert mode: Use the appropriate number of spaces to insert a <Tab>.
o.shiftwidth = 2 -- Number of spaces to use for each step of (auto)indent.
o.tabstop = 2 -- Number of spaces that a <Tab> in the file counts for.
o.encoding = "UTF-8" -- Sets the character encoding used inside Vim.
o.ruler = true -- Show the line and column number of the cursor position, separated by a comma.
o.mouse = "a" -- Enable the use of the mouse. "a" you can use on all modes
o.title = true -- When on, the title of the window will be set to the value of 'titlestring'
o.hidden = true -- When on a buffer becomes hidden when it is |abandon|ed
o.ttimeoutlen = 0 -- The time in milliseconds that is waited for a key code or mapped key sequence to complete.
o.wildmenu = true -- When 'wildmenu' is on, command-line completion operates in an enhanced mode.
o.showcmd = true -- Show (partial) command in the last line of the screen. Set this option off if your terminal is slow.
o.showmatch = true -- When a bracket is inserted, briefly jump to the matching one.
o.inccommand = "split" -- When nonempty, shows the effects of :substitute, :smagic, :snomagic and user commands with the :command-preview flag as you type.
o.splitright = true
o.splitbelow = true -- When on, splitting a window will put the new window below the current one
o.termguicolors = true
```

## Add Lazy.vim

Installing Lazy is quite easy, you just have to copy this code from [folke/lazy.nvim](https://github.com/folke/lazy.nvim) and paste it into `~/nvim/lua/slydragonn/lazy.lua`

```lua
-- ~/nvim/lua/slydragonn/lazy.lua

local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or 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("slydragonn.plugins")
```

Then in the init.lua file we import the lazy config:
```lua
-- ~/nvim/init.lua

require("slydragonn.settings")
require("slydragonn.lazy")
```

And create the plugins/ folder, where to add the plugin configuration files: `~/nvim/lua/plugins/`

Lazy will read all the files in the plugins folder, because that's how we set it, and Lazy will install them all automatically, or we can use the command `:Lazy` to see the UI.

### Lazy Commands
* Open the UI: `:Lazy`
* Install: `shift` + `L`
* Sync: `shift` + `S`
* Update: `shift` + `U`
* Clear: `shift` + `X`
* Check: `shift` + `C`
* Log: `shift` + `L`
* Restore: `shift` + `R`
* Profile: `shift` + `P`
* Debug: `shift` + `D`
* Help: `shift` + `?`

ℹ️ It is recommended to run `:checkhealth lazy` after installation.

## Plugin Configs:

### treesitter.lua
**nvim-treesitter/nvim-treesitter:** Nvim Treesitter configurations and abstraction layer.

```lua
--- ~/nvim/lua/slydragonn/plugins/treesiter.lua

return {
	"nvim-treesitter/nvim-treesitter",
	event = { "BufReadPre", "BufNewFile" },
	build = ":TSUpdate",
	dependencies = {
		"windwp/nvim-ts-autotag",
	},
	config = function()
		local treesitter = require("nvim-treesitter.configs")

		treesitter.setup({
			highlight = {
				enable = true,
				additional_vim_regex_highlighting = false,
			},
			indent = { enable = true },
			autotag = {
				enable = true,
			},
			ensure_installed = {
				"json",
				"javascript",
				"typescript",
				"tsx",
				"yaml",
				"html",
				"css",
				"markdown",
				"markdown_inline",
				"bash",
				"lua",
				"vim",
				"dockerfile",
				"gitignore",
				"c",
				"rust",
			},
			incremental_selection = {
				enable = true,
				keymaps = {
					init_selection = "<C-space>",
					node_incremental = "<C-space>",
					scope_incremental = false,
					node_decremental = "<bs>",
				},
			},
			rainbow = {
				enable = true,
				disable = { "html" },
				extended_mode = false,
				max_file_lines = nil,
			},
			context_commentstring = {
				enable = true,
				enable_autocmd = false,
			},
		})
	end,
}
```

### colorscheme.lua
**tiagovla/tokyodark.nvim:** A clean dark theme written in lua for neovim.

```lua
-- ~/nvim/lua/slydragonn/plugins/colorscheme.lua

return {
	"tiagovla/tokyodark.nvim",
	lazy = false,
	priority = 1000,
	config = function()
		vim.cmd("colorscheme tokyodark")
	end,
}
```

### autopairs.lua
**windwp/nvim-autopairs:** Autopairs for neovim written by lua.

```lua
-- ~/nvim/lua/slydragonn/plugins/autopairs.lua

return {
	"windwp/nvim-autopairs",
	event = "InsertEnter",
	config = function()
		require("nvim-autopairs").setup({
			disable_filetype = { "TelescopePrompt", "vim" },
		})
	end,
}
```

### cmp.lua
**hrsh7th/nvim-cmp:** A completion plugin for neovim coded in Lua.

```lua
-- ~/nvim/lua/slydragonn/plugins/cmp.lua

return {
	"hrsh7th/nvim-cmp",
	event = "InsertEnter",
	dependencies = {
		"hrsh7th/cmp-buffer", -- source for text in buffer
		"hrsh7th/cmp-path", -- source for file system paths
		{
			"L3MON4D3/LuaSnip",
			version = "v2.*",
			-- install jsregexp (optional!).
			build = "make install_jsregexp",
		},
		"rafamadriz/friendly-snippets",
		"onsails/lspkind.nvim", -- vs-code like pictograms
	},
	config = function()
		local cmp = require("cmp")
		local lspkind = require("lspkind")
		local luasnip = require("luasnip")

		require("luasnip.loaders.from_vscode").lazy_load()

		cmp.setup({
			snippet = {
				expand = function(args)
					luasnip.lsp_expand(args.body)
				end,
			},
			mapping = cmp.mapping.preset.insert({
				["<C-d>"] = cmp.mapping.scroll_docs(-4),
				["<C-f>"] = cmp.mapping.scroll_docs(4),
				["<C-Space>"] = cmp.mapping.complete(),
				["<C-e>"] = cmp.mapping.close(),
				["<CR>"] = cmp.mapping.confirm({
					behavior = cmp.ConfirmBehavior.Replace,
					select = true,
				}),
			}),
			sources = cmp.config.sources({
				{ name = "nvim_lsp" },
				{ name = "luasnip" },
				{ name = "buffer" },
				{ name = "path" },
			}),
		})

		vim.cmd([[
      set completeopt=menuone,noinsert,noselect
      highlight! default link CmpItemKind CmpItemMenuDefault
    ]])
	end,
}
```

### colorizer.lua
**norcalli/nvim-colorizer.lua:** Color highlighter.

```lua
-- ~/nvim/lua/slydragonn/plugins/colorizer.lua

return {
	"norcalli/nvim-colorizer.lua",
	config = function()
		require("colorizer").setup({ "*" })
	end,
}
```

### lualine.lua
**nvim-lualine/lualine.nvim:** A blazing fast and easy to configure neovim statusline plugin written in pure lua.

```lua
-- ~/nvim/lua/slydragonn/plugins/lualine.lua

return {
	"nvim-lualine/lualine.nvim",
	dependencies = { "nvim-tree/nvim-web-devicons" },
	config = function()
		require("lualine").setup()
	end,
}
```

### mason.lua
**williamboman/mason.nvim:** Portable package manager for Neovim that runs everywhere Neovim runs.

```lua
-- ~/nvim/lua/slydragonn/plugins/mason.lua

return {
	"williamboman/mason.nvim",
	dependencies = {
		"williamboman/mason-lspconfig.nvim",
		"WhoIsSethDaniel/mason-tool-installer.nvim",
	},
	config = function()
		require("mason").setup()

		require("mason-lspconfig").setup({
			automatic_installation = true,
			ensure_installed = {
				"cssls",
				"eslint",
				"html",
				"jsonls",
				"tsserver",
				"pyright",
				"tailwindcss",
			},
		})

		require("mason-tool-installer").setup({
			ensure_installed = {
				"prettier",
				"stylua", -- lua formatter
				"isort", -- python formatter
				"black", -- python formatter
				"pylint",
				"eslint_d",
			},
		})
	end,
}
```

### lspconfig.lua
**williamboman/mason-lspconfig.nvim:** Extension to mason.nvim that makes it easier to use lspconfig with mason.nvim.

```lua
-- ~/nvim/lua/slydragonn/plugins/lspconfig.lua

return {
	"neovim/nvim-lspconfig",
	event = { "BufReadPre", "BufNewFile" },
	dependencies = {
		"hrsh7th/cmp-nvim-lsp",
		{ "folke/neodev.nvim", opts = {} },
	},
	config = function()
		local nvim_lsp = require("lspconfig")
		local mason_lspconfig = require("mason-lspconfig")

		local protocol = require("vim.lsp.protocol")

		local on_attach = function(client, bufnr)
			-- format on save
			if client.server_capabilities.documentFormattingProvider then
				vim.api.nvim_create_autocmd("BufWritePre", {
					group = vim.api.nvim_create_augroup("Format", { clear = true }),
					buffer = bufnr,
					callback = function()
						vim.lsp.buf.format()
					end,
				})
			end
		end

		local capabilities = require("cmp_nvim_lsp").default_capabilities()

		mason_lspconfig.setup_handlers({
			function(server)
				nvim_lsp[server].setup({
					capabilities = capabilities,
				})
			end,
			["tsserver"] = function()
				nvim_lsp["tsserver"].setup({
					on_attach = on_attach,
					capabilities = capabilities,
				})
			end,
			["cssls"] = function()
				nvim_lsp["cssls"].setup({
					on_attach = on_attach,
					capabilities = capabilities,
				})
			end,
			["tailwindcss"] = function()
				nvim_lsp["tailwindcss"].setup({
					on_attach = on_attach,
					capabilities = capabilities,
				})
			end,
			["html"] = function()
				nvim_lsp["html"].setup({
					on_attach = on_attach,
					capabilities = capabilities,
				})
			end,
			["jsonls"] = function()
				nvim_lsp["jsonls"].setup({
					on_attach = on_attach,
					capabilities = capabilities,
				})
			end,
			["eslint"] = function()
				nvim_lsp["eslint"].setup({
					on_attach = on_attach,
					capabilities = capabilities,
				})
			end,
			["pyright"] = function()
				nvim_lsp["pyright"].setup({
					on_attach = on_attach,
					capabilities = capabilities,
				})
			end,
		})
	end,
}
```

### formatter.lua
**stevearc/conform.nvim:** Lightweight yet powerful formatter plugin for Neovim.

```lua
-- ~/nvim/lua/slydragonn/plugins/formatter.lua

return {
	"stevearc/conform.nvim",
	event = { "BufReadPre", "BufNewFile" },
	config = function()
		local conform = require("conform")

		conform.setup({
			formatters_by_ft = {
				javascript = { "prettier" },
				typescript = { "prettier" },
				javascriptreact = { "prettier" },
				typescriptreact = { "prettier" },
				css = { "prettier" },
				html = { "prettier" },
				json = { "prettier" },
				yaml = { "prettier" },
				markdown = { "prettier" },
				lua = { "stylua" },
				python = { "isort", "black" },
			},
			format_on_save = {
				lsp_fallback = true,
				async = false,
				timeout_ms = 1000,
			},
		})

		vim.keymap.set({ "n", "v" }, "<leader>f", function()
			conform.format({
				lsp_fallback = true,
				async = false,
				timeout_ms = 1000,
			})
		end, { desc = "Format file or range (in visual mode)" })
	end,
}
```

### gitsigns.lua
**lewis6991/gitsigns.nvim:** Git integration for buffers.

```lua
-- ~/nvim/lua/slydragonn/plugins/gitsigns.lua

return {
	"lewis6991/gitsigns.nvim",
	config = function()
		local gitsigns = require("gitsigns")
		gitsigns.setup({
			signs = {
				add = { text = "│" },
				change = { text = "│" },
				delete = { text = "_" },
				topdelete = { text = "‾" },
				changedelete = { text = "~" },
				untracked = { text = "┆" },
			},
			signcolumn = true, -- Toggle with `:Gitsigns toggle_signs`
			numhl = false, -- Toggle with `:Gitsigns toggle_numhl`
			linehl = false, -- Toggle with `:Gitsigns toggle_linehl`
			word_diff = false, -- Toggle with `:Gitsigns toggle_word_diff`
			watch_gitdir = {
				interval = 1000,
				follow_files = true,
			},
			attach_to_untracked = true,
			current_line_blame = false, -- Toggle with `:Gitsigns toggle_current_line_blame`
			current_line_blame_opts = {
				virt_text = true,
				virt_text_pos = "eol", -- 'eol' | 'overlay' | 'right_align'
				delay = 1000,
				ignore_whitespace = false,
			},
			current_line_blame_formatter = "<author>, <author_time:%Y-%m-%d> - <summary>",
			sign_priority = 6,
			update_debounce = 100,
			status_formatter = nil, -- Use default
			max_file_length = 40000, -- Disable if file is longer than this (in lines)
			preview_config = {
				-- Options passed to nvim_open_win
				border = "single",
				style = "minimal",
				relative = "cursor",
				row = 0,
				col = 1,
			},
			yadm = {
				enable = false,
			},
		})
	end,
}
```

### neotree.lua
**nvim-neo-tree/neo-tree:** Neovim plugin to manage the file system and other tree like structures.

```lua
-- ~/nvim/lua/slydragonn/plugins/neotree.lua

return {
	"nvim-neo-tree/neo-tree.nvim",
	branch = "v3.x",
	dependencies = {
		"nvim-lua/plenary.nvim",
		"nvim-tree/nvim-web-devicons",
		"MunifTanjim/nui.nvim",
		-- "3rd/image.nvim", -- Optional image support in preview window: See `# Preview Mode` for more information
	},
}
```

### telescope.lua
**nvim-telescope/telescope.nvim:** Highly extendable fuzzy finder over lists.

```lua
-- ~/nvim/lua/slydragonn/plugins/telescope.lua

return {
	"nvim-telescope/telescope.nvim",
	tag = "0.1.6",
	dependencies = { "nvim-lua/plenary.nvim" },
	config = function()
		require("telescope").setup()

		-- set keymaps
		local keymap = vim.keymap

		keymap.set("n", "<leader>ff", "<cmd>Telescope find_files<cr>", { desc = "Fuzzy find files in cwd" })
		keymap.set("n", "<leader>fg", "<cmd>Telescope live_grep<cr>", { desc = "Fuzzy find recent files" })
		keymap.set("n", "<leader>fb", "<cmd>Telescope buffers<cr>", { desc = "Find string in cwd" })
		keymap.set("n", "<leader>fs", "<cmd>Telescope git_status<cr>", { desc = "Find string under cursor in cwd" })
		keymap.set("n", "<leader>fc", "<cmd>Telescope git commits<cr>", { desc = "Find todos" })
	end,
}
```

### toggleterm.lua
**akinsho/toggleterm.nvim:** A neovim lua plugin to help easily manage multiple terminal windows.

```lua
-- ~/nvim/lua/slydragonn/plugins/toggleterm.lua

return {
  'akinsho/toggleterm.nvim',
  version = "*",
  config = function()
    require("toggleterm").setup({
	    size = 10,
	    open_mapping = [[<F7>]],
	    shading_factor = 2,
	    direction = "float",
	    float_opts = {
		    border = "curved",
		    highlights = {
			    border = "Normal",
			    background = "Normal",
		    },
	    },
    }) 

  end,
}
```

When all plugins are added, we write the command `:Lazy` and we press `shift` + `L` to Install or `shift` + `S` to sync.


## Editor Key bindings
Inside of init.lua requires the maps file.

```lua
-- ~/nvim/init.lua
require("slydragonn.settings")
require("slydragonn.lazy")
require("slydragonn.maps") -- key bindings
```

### maps.lua

```lua
-- ~/nvim/lua/slydragonn/maps.lua

vim.g.mapleader = " "

local function map(mode, lhs, rhs)
	vim.keymap.set(mode, lhs, rhs, { silent = true })
end


-- Save
map("n", "<leader>w", "<CMD>update<CR>")

-- Quit
map("n", "<leader>q", "<CMD>q<CR>")

-- Exit insert mode
map("i", "jk", "<ESC>")

-- NeoTree
map("n", "<leader>e", "<CMD>Neotree toggle<CR>")
map("n", "<leader>r", "<CMD>Neotree focus<CR>")

-- New Windows
map("n", "<leader>o", "<CMD>vsplit<CR>")
map("n", "<leader>p", "<CMD>split<CR>")

-- Window Navigation
map("n", "<C-h>", "<C-w>h")
map("n", "<C-l>", "<C-w>l")
map("n", "<C-k>", "<C-w>k")
map("n", "<C-j>", "<C-w>j")

-- Resize Windows
map("n", "<C-Left>", "<C-w><")
map("n", "<C-Right>", "<C-w>>")
map("n", "<C-Up>", "<C-w>+")
map("n", "<C-Down>", "<C-w>-")
```

And that's it, with this setup you should have an amazing neovim editor.

### 📚 Resources

* My neovim setup with lazy.vim: https://github.com/slydragonn/nvim-lazy 
* Youtube Video: 
* Lazy.vim: https://github.com/folke/lazy.nvim
* Neovim resources: https://neovim.io/doc/user/lua.html#lua-intro
* Lua resources: https://www.lua.org/manual/5.4/

Thanks for reading and see you later!