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

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

📹 Tutorial video

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 (3)

Collapse
 
axiomaabsurdo profile image
Matias Mortara

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

Collapse
 
drsimplegraffiti profile image
Abayomi Ogunnusi

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

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