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
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 (6)
Tiny correction in lsp.lua
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()
vim.lsp.buf.format({ async = false })
end,
})
end
end
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