![header](/img/header_neovim_customization_new.jpg) > [!tldr] > This is a long ass guide. so to cut to the chase, you can find the entire config can be found in my [dotfiles repo i.e. DoomDots](https://github.com/iamb4uc/DoomDots) So the oldest blog in this site I ever wrote is about configuring neovim / vim using the old vim scipt method but for the last few years I have been using Lua for the config and stuff where I did configure a minimal config but some issues with my understanding of how the configuration actually took place and other shit like that, than I tried [LazyVim](https://www.lazyvim.org/) which is basically a "disto" for neovim like everthing preconfigured and stuff like that with minimal configurations and customizations as per liking. But TBH, I did not like how it spoon feeded me, I used it to save time and me not needing to customize it all the time but since last month I got plenty of time to make a completely new neovim config that is as per my liking and keeps the minimal-ness of vim. This wont be a complete IDE like vscode but a zen no bullshit development environment that is easy to maintain and blazingly fast. So lets configure a minimal neovim config. ## Setting up the environment You need lua and the latest v0.12.x version of neovim so install the following: ### For Debian ```sh sudo apt install neovim ``` > [!info] Fair warning > Debian stable repos are usually behind, so if `apt` gives you > anything older than 0.12, either switch to Debian unstable/sid, grab the > AppImage from the [GitHub releases](https://github.com/neovim/neovim/releases), > or just do: ```sh curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim-linux-x86_64.tar.gz tar -xzf nvim-linux-x86_64.tar.gz mv ./nvim-linux-x86_64/bin/nvim ~/.local/bin/nvim ``` ### For Arch ```sh sudo pacman -S neovim ``` Arch repos stay current so you'll get whatever the latest release is. No drama. ### For RHEL/Fedora #### Fedora ```sh sudo dnf install neovim ``` ### For Void Linux ```sh sudo xbps-install neovim ``` Void keeps neovim reasonably up to date in the main repo. Should be fine. One thing worth noting neovim bundles LuaJIT internally, so you don't actually need a system Lua install just to run your config. ## Configuring Neovim This would be the general stucture of config ```plaintext $XDG_CONFIG_DIR/nvim/init.lua <- this is the entry point of the config and will have all the modules load $XDG_CONFIG_DIR/nvim/lua/config/ <- will have all the configurations, options, keymaps, autocmds and other shit $XDG_CONFIG_DIR/nvim/lua/plugins/ <- will have all the plugins and plugin configurations ``` Basically, it is a similar stucture to `lazyvim` but with less bloat. So first things first we should configure our `init.lua`, `lua/config/keymaps.lua`, `lua/config/options.lua` and `lua/config/autocmd.lua`. Since for making the process quick these things help with jumping around files and other shits. Here are the configs which you can copy paste. ### $XDG_CONFIG_DIR/nvim/init.lua ```lua vim.loader.enable() require("config.options") require("config.keymaps") require("config.autocmd") require("config.lazy") ``` `config.lazy` or `lua/config/lazy.lua` will be used to configure our plugin manager which will be [folke/lazy.nvim](https://github.com/folke/lazy.nvim) ### $XDG_CONFIG_DIR/nvim/lua/config/options.lua ```lua local vo = vim.opt local vg = vim.g local vd = vim.diagnostic vg.mapleader = " " vg.maplocalleader = "\\" vo.backup = false vo.breakindent = true vo.clipboard = "unnamedplus" vo.completeopt = { "menu", "menuone", "noselect", "popup" } vo.cursorline = true vo.expandtab = true vo.hlsearch = false vo.ignorecase = true vo.inccommand = "split" vo.incsearch = true vo.linebreak = true vo.number = true vo.relativenumber = true vo.scrolloff = 8 vo.shiftwidth = 4 vo.showmode = false vo.sidescrolloff = 8 vo.signcolumn = "yes" vo.smartcase = true vo.smartindent = true vo.softtabstop = 4 vo.splitbelow = true vo.splitright = true vo.swapfile = false vo.tabstop = 4 vo.termguicolors = true vo.timeoutlen = 400 vo.undofile = true vo.updatetime = 250 vo.wrap = false vd.config({ virtual_text = { source = "if_many", prefix = "* ", }, signs = true, underline = true, update_in_insert = false, severity_sort = true, float = { border = "rounded", source = true, }, }) ``` we will be adding more config with time or when we add plugins that requires us to add more options. All options related to `vim.*` will be added to this file. ### $XDG_CONFIG_DIR/nvim/lua/config/keymaps.lua ```lua local function map(m, k, v, desc) vim.keymap.set(m, k, v, { silent = true, desc = desc }) end local function isMiniOpen() for _, win in ipairs(vim.api.nvim_list_wins()) do local buf = vim.api.nvim_win_get_buf(win) if vim.bo[buf].filetype == "minifiles" then return true end end return false end map("n", "fs", ":w", "Save file") map("n", "fsa", ":wall", "Save all files") map("n", "qq", ":q", "Quit") map("n", "qf", ":q!", "Force quit") map("n", "fsq", ":x", "Save and quit") -- Tab splits map("n", "|", ":vsplit", "create vertical split") map("n", "-", ":split", "create horizontal split") map("n", "wc", "c", "Close window") map("n", "", "l", "Move right") map("n", "", "k", "Move up") map("n", "", "j", "Move down") map("n", "", "h", "Move left") -- Buffers map("n", "H", ":bprevious", "Previous buffer") map("n", "L", ":bnext", "Next buffer") map("n", "bd", ":bdelete", "Delete buffer") map("n", "a", ':! compiler "%:p"', "Compile file") -- Open corresponding .pdf/.html or preview map("n", "p", ':! opout "%:p" ', "Open output") -- Open specific files map("n", "oc", ":e ~/.config/nvim/", "Open Neovim config") map("n", "sz", ":e ~/.config/zsh/.zshrc", "Open zshrc") map("n", "la", ":Lazy", "Open Lazy") map("v", "J", ":m '>+1gv=gv", "Move selection down") map("v", "K", ":m '<-2gv=gv", "Move selection up") map("n", "y", '"+y', "Yank to clipboard") map("n", "Y", '"+Y', "Yank line to clipboard") map("v", "y", '"+y', "Yank to clipboard") map("n", "r", [[:%s/\<\>//gc]], "Replace word") -- Application Key maps -- Mini File browser key mapps map("n", "e", function() local mf = require("mini.files") -- iykyk if isMiniOpen() then mf.close() return end local path = vim.api.nvim_buf_get_name(0) if path == "" then path = vim.uv.cwd() end mf.open(path, true) end, "Toggle file explorer") -- Telescope map("n", "ff", ":Telescope find_files", "Find files") map("n", "", ":Telescope find_files", "Find files") map("n", "lg", ":Telescope live_grep", "Live Grep") map("n", "bb", ":Telescope buffers", "List Buffers") map("n", "ht", ":Telescope help_tags", "Help Tags") map("n", "of", ":Telescope oldfiles", "Old files") map("n", "/", ":Telescope grep_string", "Find word under cursor") map("n", "kk", ":Telescope keymaps", "Find keymaps") map("n", "fd", ":Telescope diagnostics", "Find diagnostics") map("n", "ds", ":Telescope lsp_document_symbols", "Document Symbols") map("n", "gc", ":Telescope git_commits", "Git Commits") map("n", "gs", ":Telescope git_status", "Git status") -- map("n", "", ":Telescope ", "") -- LSP vim.api.nvim_create_autocmd("LspAttach", { group = vim.api.nvim_create_augroup("user_lsp_keymaps", { clear = true, }), callback = function(args) local function lsp_map(mode, key, action, desc) vim.keymap.set(mode, key, action, { buffer = args.buf, silent = true, desc = desc, }) end lsp_map("n", "gd", vim.lsp.buf.definition, "Go to definition") lsp_map("n", "gD", vim.lsp.buf.declaration, "Go to declaration") lsp_map("n", "gi", vim.lsp.buf.implementation, "Go to implementation") lsp_map("n", "gr", vim.lsp.buf.references, "Go to references") lsp_map("n", "K", vim.lsp.buf.hover, "Hover documentation") lsp_map("n", "rn", vim.lsp.buf.rename, "Rename symbol") lsp_map({ "n", "v" }, "ca", vim.lsp.buf.code_action, "Code action") end, }) -- Diagnostics map("n", "[d", ":lua vim.diagnostic.jump({ count = -1, float = true })", "Previous diagnostic") map("n", "]d", ":lua vim.diagnostic.jump({ count = 1, float = true })", "Next diagnostic") map("n", "dd", ":lua vim.diagnostic.open_float()", "Line diagnostics") map("n", "dl", ":lua vim.diagnostic.setloclist()", "Diagnostics location list") -- Formatting map("n", "fm", ":lua require('conform').format({ async = true, lsp_format = 'fallback' })", "Format file") -- Git map("n", "hp", ":Gitsigns preview_hunk", "Preview hunk") map("n", "hs", ":Gitsigns stage_hunk", "Stage hunk") map("n", "hr", ":Gitsigns reset_hunk", "Reset hunk") map("n", "hS", ":Gitsigns stage_buffer", "Stage buffer") map("n", "hR", ":Gitsigns reset_buffer", "Reset buffer") map("n", "hb", ":Gitsigns blame_line", "Blame line") map("n", "hd", ":Gitsigns diffthis", "Diff this") -- Trouble map("n", "xx", ":Trouble diagnostics toggle", "Trouble diagnostics") map("n", "xX", ":Trouble diagnostics toggle filter.buf=0", "Trouble buffer diagnostics") map("n", "xs", ":Trouble symbols toggle focus=false", "Trouble symbols") map("n", "xl", ":Trouble lsp toggle focus=false win.position=right", "Trouble LSP") map("n", "xq", ":Trouble qflist toggle", "Trouble quickfix") -- whichkey map("n", "?", ":WhichKey", "Show keymaps") -- term map("n", "tt", ":ToggleTerm direction=float", "Toggle floating terminal") map("n", "t-", ":ToggleTerm direction=horizontal size=12", "Toggle horizontal terminal") map("n", "t|", ":ToggleTerm direction=vertical size=80", "Toggle vertical terminal") map("n", "ta", ":ToggleTermToggleAll", "Toggle all terminals") map("t", "", "", "Terminal normal mode") map("t", "tt", ":ToggleTerm direction=float", "Toggle floating terminal") map("t", "", "h", "Move to left window") map("t", "", "j", "Move to lower window") map("t", "", "k", "Move to upper window") map("t", "", "l", "Move to right window") -- zenmode map("n", "zz", ":ZenMode", "Toggle zen mode") -- flash map("n", "s", ":lua require('flash').jump()", "Flash jump") map("n", "S", ":lua require('flash').treesitter()", "Flash treesitter") ``` This includes all the keymaps from the plugins too so be mindful of what we use, if you dont need something dont add it or if you need something then append it to this file but for organization keep the keybindings in this `keymaps.lua` file. ### $XDG_CONFIG_DIR/nvim/lua/config/autocmd.lua ```lua local augroup = vim.api.nvim_create_augroup("user_config", { clear = true }) vim.api.nvim_create_autocmd("TextYankPost", { group = augroup, desc = "Highlight yanked text", callback = function() vim.highlight.on_yank({ timeout = 150 }) end, }) vim.api.nvim_create_autocmd("BufReadPost", { group = augroup, desc = "Return to last edit position", callback = function() local mark = vim.api.nvim_buf_get_mark(0, '"') local line_count = vim.api.nvim_buf_line_count(0) if mark[1] > 0 and mark[1] <= line_count then pcall(vim.api.nvim_win_set_cursor, 0, mark) end end, }) vim.api.nvim_create_autocmd("FileType", { group = augroup, desc = "Disable automatic comment continuation", callback = function() vim.opt_local.formatoptions:remove({ "c", "r", "o" }) end, }) vim.api.nvim_create_autocmd("FileType", { group = augroup, desc = "Start native treesitter highlighting", pattern = { "lua", "vim", "vimdoc", "bash", "sh", "zsh", "markdown", "json", "yaml", "toml", "python", "go", "rust", "c", "cpp", }, callback = function(args) pcall(vim.treesitter.start, args.buf) end, }) ``` ### $XDG_CONFIG_DIR/nvim/lua/config/lazy.lua We are using lazy since I belive not all plugin need loading during startup. This is where lazy.vim shines as it is out of the box compatible with lazy loading. ```lua local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" if not vim.uv.fs_stat(lazypath) then vim.fn.system({ "git", "clone", "--filter=blob:none", "https://github.com/folke/lazy.nvim.git", "--branch=stable", lazypath, }) end vim.opt.rtp:prepend(lazypath) require("lazy").setup({ spec = { { import = "plugins" }, }, defaults = { lazy = true, }, install = { missing = true, }, checker = { enabled = false, }, change_detection = { notify = false, }, performance = { rtp = { disabled_plugins = { "gzip", "matchit", "matchparen", "netrwPlugin", "tarPlugin", "tohtml", "tutor", "zipPlugin", }, }, }, }) ``` ## Configuring and adding desired plugins For Plugins we will only have few so here are the must have list. 1. [`nvim-treesitter/nvim-treesitter`](https://github.com/nvim-treesitter/nvim-treesitter) 2. [`nvim-telescope/telescope.nvim`](https://github.com/nvim-telescope/telescope.nvim) 3. [`nvim-mini/mini.files`](https://github.com/nvim-mini/mini.files) 4. [`lewis6991/gitsigns.nvim`](https://github.com/lewis6991/gitsigns.nvim) 5. [`saghen/blink.cmp`](https://github.com/saghen/blink.cmp) 6. [`stevearc/conform.nvim`](https://github.com/stevearc/conform.nvim) 7. [`neovim/nvim-lspconfig`](https://github.com/neovim/nvim-lspconfig) 8. [`folke/trouble.nvim`](https://github.com/folke/trouble.nvim) 9. [`mfussenegger/nvim-lint`](https://github.com/mfussenegger/nvim-lint) 10. [`mason-org/mason.nvim`](https://github.com/mason-org/mason.nvim) 11. [`WhoIsSethDaniel/mason-tool-installer.nvim`](https://github.com/WhoIsSethDaniel/mason-tool-installer.nvim) 12. [`nvim-mini/mini.surround`](https://github.com/nvim-mini/mini.surround) 13. [`nvim-mini/mini.pairs`](https://github.com/nvim-mini/mini.pairs) Also some quality of life pluggins 1. [`akinsho/toggleterm.nvim`](https://github.com/akinsho/toggleterm.nvim) 2. [`folke/todo-comments.nvim`](https://github.com/folke/todo-comments.nvim) 3. [`folke/zen-mode.nvim`](https://github.com/folke/zen-mode.nvim) 4. [`folke/which-key.nvim`](https://github.com/folke/which-key.nvim) 5. [`ellisonleao/gruvbox.nvim`](https://github.com/ellisonleao/gruvbox.nvim) 6. [`nvim-mini/mini.statusline`](https://github.com/nvim-mini/mini.statusline) 7. [`folke/flash.nvim`](https://github.com/folke/flash.nvim) You can do your own research on what these things do and add more if you want to. I am not going to write all the configs here, but you can check out each one on my [DoomDots repo](https://github.com/iamb4uc/DoomDots). In the end, its just configuring the bare minimum and using it for a week and writing down what is missing or how neovim can be better, for me I recall which-key and how useful it is from DoomEmacs and LazyVim So I had to use it in my config too. Your case can be different which is understandable and you are free to tinker with this configs. All my configs are public for the sake of others to modify and share on their own. If you have any suggestions on what else can be added or would be useful be free to raise a pr or and issue in the repository. And if you dont like how I had done things, then you can go fuck yourself. I am not entertaining "_erm! you should use xyz plugin instead of abc_". Keep that shit to yourself.