Customizing Neovim Updated

Post Details
Contents

header

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

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 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, 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
1$XDG_CONFIG_DIR/nvim/init.lua     <- this is the entry point of the config and
2                                     will have all the modules load
3$XDG_CONFIG_DIR/nvim/lua/config/  <- will have all the configurations, options,
4                                     keymaps, autocmds and other shit
5$XDG_CONFIG_DIR/nvim/lua/plugins/ <- will have all the plugins and plugin
6                                     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
1vim.loader.enable()
2
3require("config.options")
4require("config.keymaps")
5require("config.autocmd")
6require("config.lazy")

config.lazy or lua/config/lazy.lua will be used to configure our plugin manager which will be folke/lazy.nvim

$XDG_CONFIG_DIR/nvim/lua/config/options.lua

lua
 1local vo = vim.opt
 2local vg = vim.g
 3local vd = vim.diagnostic
 4
 5vg.mapleader = " "
 6vg.maplocalleader = "\\"
 7
 8vo.backup = false
 9vo.breakindent = true
10vo.clipboard = "unnamedplus"
11vo.completeopt = { "menu", "menuone", "noselect", "popup" }
12vo.cursorline = true
13vo.expandtab = true
14vo.hlsearch = false
15vo.ignorecase = true
16vo.inccommand = "split"
17vo.incsearch = true
18vo.linebreak = true
19vo.number = true
20vo.relativenumber = true
21vo.scrolloff = 8
22vo.shiftwidth = 4
23vo.showmode = false
24vo.sidescrolloff = 8
25vo.signcolumn = "yes"
26vo.smartcase = true
27vo.smartindent = true
28vo.softtabstop = 4
29vo.splitbelow = true
30vo.splitright = true
31vo.swapfile = false
32vo.tabstop = 4
33vo.termguicolors = true
34vo.timeoutlen = 400
35vo.undofile = true
36vo.updatetime = 250
37vo.wrap = false
38
39vd.config({
40    virtual_text = {
41        source = "if_many",
42        prefix = "* ",
43    },
44    signs = true,
45    underline = true,
46    update_in_insert = false,
47    severity_sort = true,
48    float = {
49        border = "rounded",
50        source = true,
51    },
52})

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
  1local function map(m, k, v, desc)
  2    vim.keymap.set(m, k, v, { silent = true, desc = desc })
  3end
  4
  5local function isMiniOpen()
  6    for _, win in ipairs(vim.api.nvim_list_wins()) do
  7        local buf = vim.api.nvim_win_get_buf(win)
  8
  9        if vim.bo[buf].filetype == "minifiles" then
 10            return true
 11        end
 12    end
 13
 14    return false
 15end
 16
 17map("n", "<leader>fs", ":w<CR>", "Save file")
 18map("n", "<leader>fsa", ":wall<CR>", "Save all files")
 19map("n", "<leader>qq", ":q<CR>", "Quit")
 20map("n", "<leader>qf", ":q!<CR>", "Force quit")
 21map("n", "<leader>fsq", ":x<CR>", "Save and quit")
 22
 23-- Tab splits
 24map("n", "<leader>|", ":vsplit<cr>", "create vertical split")
 25map("n", "<leader>-", ":split<cr>", "create horizontal split")
 26
 27map("n", "<leader>wc", "<C-w>c", "Close window")
 28map("n", "<C-l>", "<C-w>l", "Move right")
 29map("n", "<C-k>", "<C-w>k", "Move up")
 30map("n", "<C-j>", "<C-w>j", "Move down")
 31map("n", "<C-h>", "<C-w>h", "Move left")
 32
 33-- Buffers
 34map("n", "H", ":bprevious<CR>", "Previous buffer")
 35map("n", "L", ":bnext<CR>", "Next buffer")
 36map("n", "<leader>bd", ":bdelete<CR>", "Delete buffer")
 37
 38map("n", "<leader>a", ':! compiler "%:p"<CR><CR>', "Compile file")
 39
 40-- Open corresponding .pdf/.html or preview
 41map("n", "<leader>p", ':! opout "%:p" <CR><CR>', "Open output")
 42
 43-- Open specific files
 44map("n", "<leader>oc", ":e ~/.config/nvim/<CR>", "Open Neovim config")
 45map("n", "<leader>sz", ":e ~/.config/zsh/.zshrc<CR>", "Open zshrc")
 46map("n", "<leader>la", ":Lazy<CR>", "Open Lazy")
 47
 48map("v", "J", ":m '>+1<CR>gv=gv", "Move selection down")
 49map("v", "K", ":m '<-2<CR>gv=gv", "Move selection up")
 50map("n", "<leader>y", '"+y', "Yank to clipboard")
 51map("n", "<leader>Y", '"+Y', "Yank line to clipboard")
 52map("v", "<leader>y", '"+y', "Yank to clipboard")
 53map("n", "<leader>r", [[:%s/\<<C-r><C-w>\>/<C-r><C-w>/gc<Left><Left><Left>]], "Replace word")
 54
 55-- Application Key maps
 56
 57-- Mini File browser key mapps
 58map("n", "<leader>e", function()
 59    local mf = require("mini.files") -- iykyk
 60
 61    if isMiniOpen() then
 62        mf.close()
 63        return
 64    end
 65
 66    local path = vim.api.nvim_buf_get_name(0)
 67
 68    if path == "" then
 69        path = vim.uv.cwd()
 70    end
 71
 72    mf.open(path, true)
 73end, "Toggle file explorer")
 74
 75-- Telescope
 76map("n", "<leader>ff", ":Telescope find_files<cr>", "Find files")
 77map("n", "<leader><leader>", ":Telescope find_files<cr>", "Find files")
 78map("n", "<leader>lg", ":Telescope live_grep<cr>", "Live Grep")
 79map("n", "<leader>bb", ":Telescope buffers<cr>", "List Buffers")
 80map("n", "<leader>ht", ":Telescope help_tags<cr>", "Help Tags")
 81map("n", "<leader>of", ":Telescope oldfiles<cr>", "Old files")
 82map("n", "<leader>/", ":Telescope grep_string<cr>", "Find word under cursor")
 83map("n", "<leader>kk", ":Telescope keymaps<cr>", "Find keymaps")
 84map("n", "<leader>fd", ":Telescope diagnostics<cr>", "Find diagnostics")
 85map("n", "<leader>ds", ":Telescope lsp_document_symbols<cr>", "Document Symbols")
 86map("n", "<leader>gc", ":Telescope git_commits<cr>", "Git Commits")
 87map("n", "<leader>gs", ":Telescope git_status<cr>", "Git status")
 88-- map("n", "<leader>", ":Telescope ", "")
 89
 90-- LSP
 91vim.api.nvim_create_autocmd("LspAttach", {
 92    group = vim.api.nvim_create_augroup("user_lsp_keymaps", {
 93        clear = true,
 94    }),
 95    callback = function(args)
 96        local function lsp_map(mode, key, action, desc)
 97            vim.keymap.set(mode, key, action, {
 98                buffer = args.buf,
 99                silent = true,
100                desc = desc,
101            })
102        end
103
104        lsp_map("n", "gd", vim.lsp.buf.definition, "Go to definition")
105        lsp_map("n", "gD", vim.lsp.buf.declaration, "Go to declaration")
106        lsp_map("n", "gi", vim.lsp.buf.implementation, "Go to implementation")
107        lsp_map("n", "gr", vim.lsp.buf.references, "Go to references")
108        lsp_map("n", "K", vim.lsp.buf.hover, "Hover documentation")
109        lsp_map("n", "<leader>rn", vim.lsp.buf.rename, "Rename symbol")
110        lsp_map({ "n", "v" }, "<leader>ca", vim.lsp.buf.code_action, "Code action")
111    end,
112})
113
114-- Diagnostics
115map("n", "[d", ":lua vim.diagnostic.jump({ count = -1, float = true })<CR>", "Previous diagnostic")
116map("n", "]d", ":lua vim.diagnostic.jump({ count = 1, float = true })<CR>", "Next diagnostic")
117map("n", "<leader>dd", ":lua vim.diagnostic.open_float()<CR>", "Line diagnostics")
118map("n", "<leader>dl", ":lua vim.diagnostic.setloclist()<CR>", "Diagnostics location list")
119
120-- Formatting
121map("n", "<leader>fm", ":lua require('conform').format({ async = true, lsp_format = 'fallback' })<CR>", "Format file")
122
123-- Git
124map("n", "<leader>hp", ":Gitsigns preview_hunk<CR>", "Preview hunk")
125map("n", "<leader>hs", ":Gitsigns stage_hunk<CR>", "Stage hunk")
126map("n", "<leader>hr", ":Gitsigns reset_hunk<CR>", "Reset hunk")
127map("n", "<leader>hS", ":Gitsigns stage_buffer<CR>", "Stage buffer")
128map("n", "<leader>hR", ":Gitsigns reset_buffer<CR>", "Reset buffer")
129map("n", "<leader>hb", ":Gitsigns blame_line<CR>", "Blame line")
130map("n", "<leader>hd", ":Gitsigns diffthis<CR>", "Diff this")
131
132-- Trouble
133map("n", "<leader>xx", ":Trouble diagnostics toggle<CR>", "Trouble diagnostics")
134map("n", "<leader>xX", ":Trouble diagnostics toggle filter.buf=0<CR>", "Trouble buffer diagnostics")
135map("n", "<leader>xs", ":Trouble symbols toggle focus=false<CR>", "Trouble symbols")
136map("n", "<leader>xl", ":Trouble lsp toggle focus=false win.position=right<CR>", "Trouble LSP")
137map("n", "<leader>xq", ":Trouble qflist toggle<CR>", "Trouble quickfix")
138
139-- whichkey
140map("n", "<leader>?", ":WhichKey<CR>", "Show keymaps")
141
142-- term
143map("n", "<leader>tt", ":ToggleTerm direction=float<CR>", "Toggle floating terminal")
144map("n", "<leader>t-", ":ToggleTerm direction=horizontal size=12<CR>", "Toggle horizontal terminal")
145map("n", "<leader>t|", ":ToggleTerm direction=vertical size=80<CR>", "Toggle vertical terminal")
146map("n", "<leader>ta", ":ToggleTermToggleAll<CR>", "Toggle all terminals")
147
148map("t", "<Esc>", "<C-\\><C-n>", "Terminal normal mode")
149map("t", "<leader>tt", "<C-\\><C-n>:ToggleTerm direction=float<CR>", "Toggle floating terminal")
150
151map("t", "<C-h>", "<C-\\><C-n><C-w>h", "Move to left window")
152map("t", "<C-j>", "<C-\\><C-n><C-w>j", "Move to lower window")
153map("t", "<C-k>", "<C-\\><C-n><C-w>k", "Move to upper window")
154map("t", "<C-l>", "<C-\\><C-n><C-w>l", "Move to right window")
155
156-- zenmode
157map("n", "<leader>zz", ":ZenMode<CR>", "Toggle zen mode")
158
159-- flash
160map("n", "s", ":lua require('flash').jump()<CR>", "Flash jump")
161map("n", "S", ":lua require('flash').treesitter()<CR>", "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
 1local augroup = vim.api.nvim_create_augroup("user_config", { clear = true })
 2
 3vim.api.nvim_create_autocmd("TextYankPost", {
 4    group = augroup,
 5    desc = "Highlight yanked text",
 6    callback = function()
 7        vim.highlight.on_yank({ timeout = 150 })
 8    end,
 9})
10
11vim.api.nvim_create_autocmd("BufReadPost", {
12    group = augroup,
13    desc = "Return to last edit position",
14    callback = function()
15        local mark = vim.api.nvim_buf_get_mark(0, '"')
16        local line_count = vim.api.nvim_buf_line_count(0)
17
18        if mark[1] > 0 and mark[1] <= line_count then
19            pcall(vim.api.nvim_win_set_cursor, 0, mark)
20        end
21    end,
22})
23
24vim.api.nvim_create_autocmd("FileType", {
25    group = augroup,
26    desc = "Disable automatic comment continuation",
27    callback = function()
28        vim.opt_local.formatoptions:remove({ "c", "r", "o" })
29    end,
30})
31
32vim.api.nvim_create_autocmd("FileType", {
33    group = augroup,
34    desc = "Start native treesitter highlighting",
35    pattern = {
36        "lua",
37        "vim",
38        "vimdoc",
39        "bash",
40        "sh",
41        "zsh",
42        "markdown",
43        "json",
44        "yaml",
45        "toml",
46        "python",
47        "go",
48        "rust",
49        "c",
50        "cpp",
51    },
52    callback = function(args)
53        pcall(vim.treesitter.start, args.buf)
54    end,
55})

$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
 1local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
 2
 3if not vim.uv.fs_stat(lazypath) then
 4    vim.fn.system({
 5        "git",
 6        "clone",
 7        "--filter=blob:none",
 8        "https://github.com/folke/lazy.nvim.git",
 9        "--branch=stable",
10        lazypath,
11    })
12end
13
14vim.opt.rtp:prepend(lazypath)
15
16require("lazy").setup({
17    spec = {
18        { import = "plugins" },
19    },
20
21    defaults = {
22        lazy = true,
23    },
24
25    install = {
26        missing = true,
27    },
28
29    checker = {
30        enabled = false,
31    },
32
33    change_detection = {
34        notify = false,
35    },
36
37    performance = {
38        rtp = {
39            disabled_plugins = {
40                "gzip",
41                "matchit",
42                "matchparen",
43                "netrwPlugin",
44                "tarPlugin",
45                "tohtml",
46                "tutor",
47                "zipPlugin",
48            },
49        },
50    },
51})

Configuring and adding desired plugins

For Plugins we will only have few so here are the must have list.

  1. nvim-treesitter/nvim-treesitter
  2. nvim-telescope/telescope.nvim
  3. nvim-mini/mini.files
  4. lewis6991/gitsigns.nvim
  5. saghen/blink.cmp
  6. stevearc/conform.nvim
  7. neovim/nvim-lspconfig
  8. folke/trouble.nvim
  9. mfussenegger/nvim-lint
  10. mason-org/mason.nvim
  11. WhoIsSethDaniel/mason-tool-installer.nvim
  12. nvim-mini/mini.surround
  13. nvim-mini/mini.pairs

Also some quality of life pluggins

  1. akinsho/toggleterm.nvim
  2. folke/todo-comments.nvim
  3. folke/zen-mode.nvim
  4. folke/which-key.nvim
  5. ellisonleao/gruvbox.nvim
  6. nvim-mini/mini.statusline
  7. 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.

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.

See Also

home(7), posts(1), about(7), projects(1), colophon(7)