DEV Community

Cover image for How to Set up Neovim for Windows and Linux with Lua and Packer
Alejandro Londoño
Alejandro Londoño

Posted on • Edited on

How to Set up Neovim for Windows and Linux with Lua and Packer

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

📚 Project Structure

📂 ~/.config/nvim ├── 📂 lua/  └── 📂 configs/**plugin configs**  └── 🌑 plugins.lua  └── 🌑 settings.lua  └── 🌑 maps.lua └── 🌑 init.lua 
Enter fullscreen mode Exit fullscreen mode

📚 GitHub Repository

https://github.com/slydragonn/dotfiles

If you don’t have some requirements

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 
Enter fullscreen mode Exit fullscreen mode

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") 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode
  • Windows Powershell:
git clone https://github.com/wbthomason/packer.nvim "$env:LOCALAPPDATA\nvim-data\site\pack\packer\start\packer.nvim" 
Enter fullscreen mode Exit fullscreen mode

Then in the init.lua file we add the plugins file, like this:

-- 🌑 init.lua require("settings") require("plugins") 
Enter fullscreen mode Exit fullscreen mode

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) 
Enter fullscreen mode Exit fullscreen mode

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 = {}, }) 
Enter fullscreen mode Exit fullscreen mode

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" } 
Enter fullscreen mode Exit fullscreen mode

autopairs

-- 📂lua/📂configs/🌑autopairs.lua local status, autopairs = pcall(require, "nvim-autopairs") if not status then return end autopairs.setup({ disable_filetype = { "TelescopePrompt", "vim" }, }) 
Enter fullscreen mode Exit fullscreen mode

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 ]]) 
Enter fullscreen mode Exit fullscreen mode

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, }, }) 
Enter fullscreen mode Exit fullscreen mode

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, }) 
Enter fullscreen mode Exit fullscreen mode

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", }, }) 
Enter fullscreen mode Exit fullscreen mode

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, }) 
Enter fullscreen mode Exit fullscreen mode

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", }, }, }) 
Enter fullscreen mode Exit fullscreen mode

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 }) 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

✅ When the config files are all right, we write the command:

:PackerSync 
Enter fullscreen mode Exit fullscreen mode

or

nvim +PackerSync 
Enter fullscreen mode Exit fullscreen mode

⌨ 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 
Enter fullscreen mode Exit fullscreen mode

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>-") 
Enter fullscreen mode Exit fullscreen mode

✨ With this, you should have the neovim editor as an IDE and ready for hacking!

📚 Resources

Top comments (6)

Collapse
 
atag_ster_6059b5bf1d2e9a7 profile image
Atag Ster

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

Collapse
 
izaias_correaizaias_2f profile image
Izaias Correa (Izaias)

so good, man!

Collapse
 
axiomaabsurdo profile image
Matias Mortara

Thanks for your detailed tutorial. Works really fine for me.

Collapse
 
drsimplegraffiti profile image
DrSimple

This is one of the finest tutorial I have ever seen on neovim configuration. Thank you so much

Collapse
 
aixuexi profile image
FelixLee

This tutorial is so helpful , thanks!

Collapse
 
alejandrofnadal profile image
Alejandro Nadal

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