Hello everyone!
In the last week, I decided to switch from vscode to Neovim as my main Code Editor, so in this article I will show you how I set up Neovim with Lua script and what you need to use this setup.
Note: All the code is on my Github profile and at the end of this article I put some resources if you want to investigate more about Neovim and Lua.
The first thing I did was learn the Lua script because I want to use it and I think it's a really good way to organize my setup. You can use Lua in neovim from version 0.7.0
Let´s code!
⚙ Requirements
To use this setup, you should have installed the following dependencies:
- NerdFonts
- Neovim ≥ 0.8.1
- NodeJS with npm
- Packer installed
- A C compiler in your path and libstdc++ installed
- Git
✨ Features
- wbthomason/packer: A use-package inspired plugin manager for Neovim.
- nvim-neo-tree/neo-tree: Neovim plugin to manage the file system and other tree like structures.
- nvim-tree/nvim-web-devicons: lua fork of vim-web-devicons for neovim.
- rebelot/kanagawa: NeoVim dark colorscheme inspired by the colors of the famous painting by Katsushika Hokusai.
- nvim-lualine/lualine.nvim: A blazing fast and easy to configure neovim statusline plugin written in pure lua.
- nvim-treesitter/nvim-treesitter: Nvim Treesitter configurations and abstraction layer.
- windwp/nvim-ts-autotag: Use treesitter to auto close and auto rename html tag.
- nvim-telescope/telescope.nvim: Highly extendable fuzzy finder over lists.
- neovim/nvim-lspconfig: Quickstart configs for Nvim LSP
- hrsh7th/nvim-cmp: A completion plugin for neovim coded in Lua.
- jose-elias-alvarez/null-ls.nvim: Use Neovim as a language server to inject LSP diagnostics, code actions, and more via Lua.
- williamboman/mason.nvim: Portable package manager for Neovim that runs everywhere Neovim runs.
- norcalli/nvim-colorizer.lua: Color highlighter.
- akinsho/toggleterm.nvim: A neovim lua plugin to help easily manage multiple terminal windows.
- lewis6991/gitsigns.nvim: Git integration for buffers.
- iamcco/markdown-preview.nvim: Markdown preview plugin.
- windwp/nvim-autopairs: Autopairs for neovim written by lua.
- xiyaowong/nvim-transparent: Remove all background colors to make nvim transparent
- onsails/lspkind.nvim: vscode-like pictograms for neovim lsp completion items.
- L3MON4D3/LuaSnip: Snippet Engine for Neovim written in Lua.
- hrsh7th/cmp-nvim-lsp: nvim-cmp source for neovim builtin LSP client
- hrsh7th/cmp-path: nvim-cmp source for path
- hrsh7th/cmp-buffer: nvim-cmp source for buffer words
- williamboman/mason-lspconfig.nvim: Extension to mason.nvim that makes it easier to use lspconfig with mason.nvim.
📚 Project Structure
📂 ~/.config/nvim
├── 📂 lua/
│ └── 📂 configs/**plugin configs**
│ └── 🌑 plugins.lua
│ └── 🌑 settings.lua
│ └── 🌑 maps.lua
└── 🌑 init.lua
📚 GitHub Repository
https://github.com/slydragonn/dotfiles
📹 Tutorial video
If you don’t have some requirements
- Nerd Fonts: Nerd Fonts - Iconic font aggregator, glyphs/icons collection, & fonts patcher
- Neovim: https://github.com/neovim/neovim/releases/
- Node: Descarga | Node.js (nodejs.org)
- Packer: wbthomason/packer.nvim: A use-package inspired plugin manager for Neovim. Uses native packages, supports Luarocks dependencies, written in Lua, allows for expressive config (github.com)
- C compiler: Windows support · nvim-treesitter/nvim-treesitter Wiki (github.com)
- Git: Git - Downloads (git-scm.com)
Saving Settings
Before, we need to save the configuration in a particular place, so that neovim can recognize our configuration.
- Windows C:\Users\%YOUR_USERNAME%\AppData\Local\nvim
- Linux ~/.configs/nvim/
To set up neovim with lua, you should put the config files inside of lua folder and require all these settings in a file called init.lua like this:
📂 ~/.config/nvim
├── 📂 lua/**config files**
└── 🌑 init.lua
Creating the files and folder structure
init.lua: It´s the main file and here is where all settings will load. We start with the settings file.
-- 🌑 init.lua
require("settings")
Editor settings
Then in settings.lua we write the editor options:
-- 📂lua/🌑settings.lua
local global = vim.g
local o = vim.o
vim.scriptencoding = "utf-8"
-- Map <leader>
global.mapleader = " "
global.maplocalleader = " "
-- 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.fileencoding = "utf-8" -- Sets the character encoding for the file of this buffer.
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.splitbelow = "splitright" -- When on, splitting a window will put the new window below the current one
Add plugins
First, we should Install Packer in:
- Linux:
git clone --depth 1 https://github.com/wbthomason/packer.nvim\
~/.local/share/nvim/site/pack/packer/start/packer.nvim
- Windows Powershell:
git clone https://github.com/wbthomason/packer.nvim "$env:LOCALAPPDATA\nvim-data\site\pack\packer\start\packer.nvim"
Then in the init.lua file we add the plugins file, like this:
-- 🌑 init.lua
require("settings")
require("plugins")
And inside of plugins.lua file writes the next code:
-- 📂lua/🌑plugins.lua
-- Automatically run: PackerCompile
vim.api.nvim_create_autocmd("BufWritePost", {
group = vim.api.nvim_create_augroup("PACKER", { clear = true }),
pattern = "plugins.lua",
command = "source <afile> | PackerCompile",
})
return require("packer").startup(function(use)
-- Packer
use("wbthomason/packer.nvim")
-- Common utilities
use("nvim-lua/plenary.nvim")
-- Icons
use("nvim-tree/nvim-web-devicons")
-- Colorschema
use("rebelot/kanagawa.nvim")
-- Statusline
use({
"nvim-lualine/lualine.nvim",
event = "BufEnter",
config = function()
require("configs.lualine")
end,
requires = { "nvim-web-devicons" },
})
-- Treesitter
use({
"nvim-treesitter/nvim-treesitter",
run = function()
require("nvim-treesitter.install").update({ with_sync = true })
end,
config = function()
require("configs.treesitter")
end,
})
use({ "windwp/nvim-ts-autotag", after = "nvim-treesitter" })
-- Telescope
use({
"nvim-telescope/telescope.nvim",
tag = "0.1.1",
requires = { { "nvim-lua/plenary.nvim" } },
})
-- LSP
use({
"neovim/nvim-lspconfig",
config = function()
require("configs.lsp")
end,
})
use("onsails/lspkind-nvim")
use({
"L3MON4D3/LuaSnip",
-- follow latest release.
tag = "v<CurrentMajor>.*",
})
-- cmp: Autocomplete
use({
"hrsh7th/nvim-cmp",
event = "InsertEnter",
config = function()
require("configs.cmp")
end,
})
use("hrsh7th/cmp-nvim-lsp")
use({ "hrsh7th/cmp-path", after = "nvim-cmp" })
use({ "hrsh7th/cmp-buffer", after = "nvim-cmp" })
-- LSP diagnostics, code actions, and more via Lua.
use({
"jose-elias-alvarez/null-ls.nvim",
config = function()
require("configs.null-ls")
end,
requires = { "nvim-lua/plenary.nvim" },
})
-- Mason: Portable package manager
use({
"williamboman/mason.nvim",
config = function()
require("mason").setup()
end,
})
use({
"williamboman/mason-lspconfig.nvim",
config = function()
require("configs.mason-lsp")
end,
})
-- File manager
use({
"nvim-neo-tree/neo-tree.nvim",
branch = "v2.x",
requires = {
"nvim-lua/plenary.nvim",
"nvim-tree/nvim-web-devicons",
"MunifTanjim/nui.nvim",
},
})
-- Show colors
use({
"norcalli/nvim-colorizer.lua",
config = function()
require("colorizer").setup({ "*" })
end,
})
-- Terminal
use({
"akinsho/toggleterm.nvim",
tag = "*",
config = function()
require("configs.toggleterm")
end,
})
-- Git
use({
"lewis6991/gitsigns.nvim",
config = function()
require("configs.gitsigns")
end,
})
-- Markdown Preview
use({
"iamcco/markdown-preview.nvim",
run = function()
vim.fn["mkdp#util#install"]()
end,
})
-- Auto pairs
use({
"windwp/nvim-autopairs",
config = function()
require("configs.autopairs")
end,
})
-- Background Transparent
use({
"xiyaowong/nvim-transparent",
config = function()
require("transparent").setup({
enable = true,
extra_groups = {
"BufferLineTabClose",
"BufferlineBufferSelected",
"BufferLineFill",
"BufferLineBackground",
"BufferLineSeparator",
"BufferLineIndicatorSelected",
},
exclude = {},
})
end,
})
end)
Before of install the plugins, we should create the config files for the plugins that need them.
Note: First, create the configs folder inside of lua folder and here we put the plugins config.
Plugin configs
lualine
-- 📂lua/📂configs/🌑lualine.lua
local status, lualine = pcall(require, "lualine")
if not status then
return
end
lualine.setup({
options = {
icons_enabled = true,
theme = "powerline",
component_separators = { left = "", right = "" },
section_separators = { left = "", right = "" },
disabled_filetypes = {
statusline = {},
winbar = {},
},
ignore_focus = {},
always_divide_middle = true,
globalstatus = false,
refresh = {
statusline = 1000,
tabline = 1000,
winbar = 1000,
},
},
sections = {
lualine_a = { "mode" },
lualine_b = { "branch", "diff", "diagnostics" },
lualine_c = { "filename" },
lualine_x = { "encoding", "fileformat", "filetype" },
lualine_y = { "progress" },
lualine_z = { "location" },
},
inactive_sections = {
lualine_a = {},
lualine_b = {},
lualine_c = { "filename" },
lualine_x = { "location" },
lualine_y = {},
lualine_z = {},
},
tabline = {},
winbar = {},
inactive_winbar = {},
extensions = {},
})
treesitter
-- 📂lua/📂configs/🌑tresitter.lua
local status, ts = pcall(require, "nvim-treesitter.configs")
if not status then
return
end
ts.setup({
highlight = {
enable = true,
additional_vim_regex_highlighting = false,
},
context_commentstring = {
enable = true,
enable_autocmd = false,
},
ensure_installed = {
"markdown",
"tsx",
"typescript",
"javascript",
"toml",
"c_sharp",
"json",
"yaml",
"rust",
"css",
"html",
"lua",
},
rainbow = {
enable = true,
disable = { "html" },
extended_mode = false,
max_file_lines = nil,
},
autotag = { enable = true },
incremental_selection = { enable = true },
indent = { enable = true },
})
local parser_config = require("nvim-treesitter.parsers").get_parser_configs()
parser_config.tsx.filetype_to_parsername = { "javascript", "typescript.tsx" }
autopairs
-- 📂lua/📂configs/🌑autopairs.lua
local status, autopairs = pcall(require, "nvim-autopairs")
if not status then
return
end
autopairs.setup({
disable_filetype = { "TelescopePrompt", "vim" },
})
cmp
-- 📂lua/📂configs/🌑cmp.lua
local status, cmp = pcall(require, "cmp")
if not status then
return
end
local lspkind = require("lspkind")
cmp.setup({
snippet = {
expand = function(args)
require("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 = "buffer" },
}),
})
vim.cmd([[
set completeopt=menuone,noinsert,noselect
highlight! default link CmpItemKind CmpItemMenuDefault
]])
gitsigns
-- 📂lua/📂configs/🌑gitsigns.lua
local status, gitsigns = pcall(require, "gitsigns")
if not status then
return
end
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,
},
})
lsp
-- 📂lua/📂configs/🌑lsp.lua
local status, nvim_lsp = pcall(require, "lspconfig")
if not status then
return
end
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.formatting_seq_sync()
end,
})
end
end
local capabilities = require("cmp_nvim_lsp").default_capabilities()
-- TypeScript
nvim_lsp.tsserver.setup({
on_attach = on_attach,
capabilities = capabilities,
})
-- CSS
nvim_lsp.cssls.setup({
on_attach = on_attach,
capabilities = capabilities,
})
-- Tailwind
nvim_lsp.tailwindcss.setup({
on_attach = on_attach,
capabilities = capabilities,
})
mason-lsp
-- 📂lua/📂configs/🌑mason-lsp.lua
local status, masonlsp = pcall(require, "mason-lspconfig")
if not status then
return
end
masonlsp.setup({
automatic_installation = true,
ensure_installed = {
"cssls",
"eslint",
"html",
"jsonls",
"tsserver",
"pyright",
"tailwindcss",
},
})
null-ls
-- 📂lua/📂configs/🌑null-ls.lua
local status, nls = pcall(require, "null-ls")
if not status then
return
end
local augroup = vim.api.nvim_create_augroup("LspFormatting", {})
local fmt = nls.builtins.formatting
local dgn = nls.builtins.diagnostics
local cda = nls.builtins.code_actions
nls.setup({
sources = {
-- Formatting
fmt.prettierd,
fmt.eslint_d,
fmt.prettier.with({
filetypes = { "html", "json", "yaml", "markdown", "javascript", "typescript" },
}),
fmt.stylua,
fmt.rustfmt,
-- Diagnostics
dgn.eslint_d,
dgn.shellcheck,
dgn.pylint.with({
method = nls.methods.DIAGNOSTICS_ON_SAVE,
}),
-- Code Actions
cda.eslint_d,
cda.shellcheck,
},
on_attach = function(client, bufnr)
if client.supports_method("textDocument/formatting") then
vim.api.nvim_clear_autocmds({ group = augroup, buffer = bufnr })
vim.api.nvim_create_autocmd("BufWritePre", {
group = augroup,
buffer = bufnr,
callback = function()
vim.lsp.buf.format({ bufnr = bufnr })
end,
})
end
end,
})
toggleterm
-- 📂lua/📂configs/🌑toggleterm.lua
local status, toggleterm = pcall(require, "toggleterm")
if not status then
return
end
toggleterm.setup({
size = 10,
open_mapping = [[<F7>]],
shading_factor = 2,
direction = "float",
float_opts = {
border = "curved",
highlights = {
border = "Normal",
background = "Normal",
},
},
})
kanagawa
-- 📂lua/📂configs/🌑kanagawa.lua
local status, kanagawa = pcall(require, "kanagawa")
if not status then
return
end
kanagawa.setup({
undercurl = true, -- enable undercurls
commentStyle = { italic = true },
functionStyle = {},
keywordStyle = { italic = true },
statementStyle = { bold = true },
typeStyle = {},
variablebuiltinStyle = { italic = true },
specialReturn = true, -- special highlight for the return keyword
specialException = true, -- special highlight for exception handling keywords
transparent = false, -- do not set background color
dimInactive = false, -- dim inactive window `:h hl-NormalNC`
globalStatus = false, -- adjust window separators highlight for laststatus=3
terminalColors = true, -- define vim.g.terminal_color_{0,17}
colors = {},
overrides = {},
theme = "default", -- Load "default" theme or the experimental "light" theme
})
Inside of init.lua we put the color scheme config
-- 🌑init.lua
require("settings")
require("plugins")
-- colorscheme config: kanagawa
local themeStatus, kanagawa = pcall(require, "kanagawa")
if themeStatus then
vim.cmd("colorscheme kanagawa")
else
return
end
✅ When the config files are all right, we write the command:
:PackerSync
or
nvim +PackerSync
⌨ Editor key bindings
Inside of init.lua file requires the key bindings configs:
-- 🌑init.lua
require("settings")
require("plugins")
require("maps") -- key mappings
-- colorscheme config: kanagawa
local themeStatus, kanagawa = pcall(require, "kanagawa")
if themeStatus then
vim.cmd("colorscheme kanagawa")
else
return
end
maps
-- 📂lua/🌑maps.lua
local function map(mode, lhs, rhs)
vim.keymap.set(mode, lhs, rhs, { silent = true })
end
local status, telescope = pcall(require, "telescope.builtin")
if status then
-- Telescope
map("n", "<leader>ff", telescope.find_files)
map("n", "<leader>fg", telescope.live_grep)
map("n", "<leader>fb", telescope.buffers)
map("n", "<leader>fh", telescope.help_tags)
map("n", "<leader>fs", telescope.git_status)
map("n", "<leader>fc", telescope.git_commits)
else
print("Telescope not found")
end
-- Save
map("n", "<leader>w", "<CMD>update<CR>")
-- Quit
map("n", "<leader>q", "<CMD>q<CR>")
-- Exit insert mode
map("i", "jk", "<ESC>")
-- Windows
map("n", "<leader>ñ", "<CMD>vsplit<CR>")
map("n", "<leader>p", "<CMD>split<CR>")
-- NeoTree
map("n", "<leader>e", "<CMD>Neotree toggle<CR>")
map("n", "<leader>o", "<CMD>Neotree focus<CR>")
-- Buffer
map("n", "<TAB>", "<CMD>bnext<CR>")
map("n", "<S-TAB>", "<CMD>bprevious<CR>")
-- Terminal
map("n", "<leader>th", "<CMD>ToggleTerm size=10 direction=horizontal<CR>")
map("n", "<leader>tv", "<CMD>ToggleTerm size=80 direction=vertical<CR>")
-- Markdown Preview
map("n", "<leader>m", "<CMD>MarkdownPreview<CR>")
map("n", "<leader>mn", "<CMD>MarkdownPreviewStop<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>-")
✨ With this, you should have the neovim editor as an IDE and ready for hacking!
📚 Resources
- My dotfiles and Neovim setup: slydragonn/dotfiles: My dotfiles (github.com)
- Youtube Video: Neovim Setup
- Neovim resources: Lua - Neovim docs
- Lua resources: Lua 5.4 Reference Manual - contents
Top comments (5)
so good, man!
Thanks for your detailed tutorial. Works really fine for me.
This is one of the finest tutorial I have ever seen on neovim configuration. Thank you so much
This tutorial is so helpful , thanks!
Hey, excellent guide. One detail:
For everyone doing this, update the tag from telescope to 0.1.5, the 0.1.1 has quite some issues. The tag is in the plugins.lua