Skip to content
Go back

Neovim Setup That Won't Take 6 Months to Learn

By SumGuy 9 min read
Neovim Setup That Won't Take 6 Months to Learn

The Vim Hate is Actually About Bad Config

You’ve heard it a hundred times: “Vim has a steep learning curve.” What they actually mean is “Vim with a garbage config has a steep learning curve.” Throw in six months of plugin hunting, a bloated init.vim with conflicting keybindings, and LSP wired up incorrectly, and yeah—you’ll hate it.

Here’s the thing: Neovim in 2026 is not your dad’s Vim. Native LSP support, Lua as first-class config language, and plugin managers that actually work mean you can have a solid, productive editor in a week. Not six months. Not six years.

This is the setup I’ve been using for two years. It’s minimal enough that you can understand every line, powerful enough that you won’t outgrow it, and boring enough that you’ll stop thinking about config and start thinking about your actual code.


The Philosophy: Start Minimal, Add When It Hurts

Don’t start with a framework (looking at you, LunarVim and Doom Emacs clones—wrong editor anyway). Don’t grab someone’s 2000-line config and cargo-cult it into your ~/.config/nvim/.

Start with 100 lines. Use the editor for a week. When something hurts, add a plugin. Repeat.

This config follows that exact pattern. You get:

After a week, you’ll know exactly what you actually need.


The Directory Structure

~/.config/nvim/
init.lua ← entry point, ~50 lines
lua/config/
settings.lua ← options, behavior
keymaps.lua ← all keybindings
lazy.lua ← lazy.nvim bootstrap
lua/plugins/
lsp.lua ← LSP client setup
telescope.lua ← fuzzy finder (optional)
other-plugins.lua ← everything else

Simple. Each file has one job. No nested subdirs you’ll forget about.


Part 1: The Entry Point (init.lua)

This file bootstraps everything:

~/.config/nvim/init.lua
-- Load configuration modules
require("config.settings")
require("config.keymaps")
require("config.lazy")
-- Load plugins (lazy.nvim handles the actual loading)
require("lazy").setup(require("plugins"))

That’s it. The actual work lives in the modules.


Part 2: Settings (lua/config/settings.lua)

These are the opinionated defaults that make Neovim actually usable:

~/.config/nvim/lua/config/settings.lua
local opt = vim.opt
-- Tabs and indentation
opt.tabstop = 2
opt.shiftwidth = 2
opt.expandtab = true
opt.smartindent = true
-- Display
opt.number = true
opt.relativenumber = false -- use absolute, not relative (faster for jumping)
opt.wrap = false
opt.cursorline = true
opt.signcolumn = "yes"
opt.termguicolors = true
-- Search behavior
opt.ignorecase = true
opt.smartcase = true
opt.incsearch = true
opt.hlsearch = false -- don't leave highlight on after search
-- Performance and behavior
opt.hidden = true -- allow switching buffers without saving
opt.undofile = true -- persistent undo
opt.mouse = "a" -- why not?
opt.clipboard = "unnamedplus" -- use system clipboard
-- Minimal UI cruft
opt.showcmd = false
opt.showmode = false
opt.ruler = false
opt.laststatus = 2
-- Folding (leave it off unless you want it)
opt.foldenable = false

Nothing fancy. This gives you incremental search, persistent undo, clipboard access, and a clean UI. It’s what you’d expect from an editor written in the last decade.


Part 3: Keymaps (lua/config/keymaps.lua)

Vim’s defaults are optimized for a 1987 keyboard layout. Here’s the minimal sane set:

~/.config/nvim/lua/config/keymaps.lua
local map = vim.keymap.set
-- Leader key (use space, it's huge and easy to hit)
vim.g.mapleader = " "
vim.g.maplocalleader = " "
-- Disable arrow keys (yes, really—forces muscle memory)
map("", "<Up>", "<Nop>")
map("", "<Down>", "<Nop>")
map("", "<Left>", "<Nop>")
map("", "<Right>", "<Nop>")
-- Splits and windows
map("n", "<Leader>v", "<C-w>v", { noremap = true }) -- vertical split
map("n", "<Leader>h", "<C-w>s", { noremap = true }) -- horizontal split
map("n", "<Leader>w", "<C-w>w", { noremap = true }) -- switch window
map("n", "<Leader>q", "<C-w>q", { noremap = true }) -- close window
-- Buffers
map("n", "<Leader>bn", ":bnext<CR>", { noremap = true }) -- next buffer
map("n", "<Leader>bp", ":bprev<CR>", { noremap = true }) -- prev buffer
map("n", "<Leader>bd", ":bdelete<CR>", { noremap = true }) -- delete buffer
-- LSP (gets extended in lsp.lua)
map("n", "gd", vim.lsp.buf.definition, { noremap = true }) -- go to definition
map("n", "gr", vim.lsp.buf.references, { noremap = true }) -- find references
map("n", "K", vim.lsp.buf.hover, { noremap = true }) -- hover docs
map("n", "<Leader>rn", vim.lsp.buf.rename, { noremap = true })-- rename symbol
-- Quick things
map("n", "<Esc>", ":nohl<CR>", { noremap = true }) -- clear search highlight
map("n", "j", "gj", { noremap = true }) -- move by visual line
map("n", "k", "gk", { noremap = true })

That’s 25 keybindings. You don’t need 200. These cover: window management, buffer switching, and LSP basics. Everything else you’ll add as you discover you need it.


Part 4: Lazy Plugin Manager Bootstrap (lua/config/lazy.lua)

This bootstraps lazy.nvim, which is the only plugin manager that actually makes sense in 2026:

~/.config/nvim/lua/config/lazy.lua
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or vim.loop).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(require("plugins"), {
install = { colorscheme = { "tokyonight" } },
})

That’s it. It clones lazy.nvim on first run, sets up the runtime path, and kicks off plugin loading. Plugins live in lua/plugins/.


Part 5: Plugins (lua/plugins/)

Create a lua/plugins/ directory with individual plugin files. Start with the absolute essentials:

lua/plugins/lsp.lua — The Secret Sauce

LSP is what makes modern editors useful. Here’s the minimal setup:

~/.config/nvim/lua/plugins/lsp.lua
return {
{
"neovim/nvim-lspconfig",
event = { "BufReadPre", "BufNewFile" },
dependencies = {
"hrsh7th/cmp-nvim-lsp",
},
config = function()
local lspconfig = require("lspconfig")
local capabilities = require("cmp_nvim_lsp").default_capabilities()
-- Lua (via lua_ls)
lspconfig.lua_ls.setup({
capabilities = capabilities,
settings = {
Lua = {
runtime = { version = "LuaJIT" },
diagnostics = { globals = { "vim" } },
},
},
})
-- Python (via pylsp or pyright)
lspconfig.pylsp.setup({ capabilities = capabilities })
-- JavaScript/TypeScript (via ts_ls)
lspconfig.ts_ls.setup({ capabilities = capabilities })
-- Rust (via rust_analyzer)
lspconfig.rust_analyzer.setup({ capabilities = capabilities })
-- Go (via gopls)
lspconfig.gopls.setup({ capabilities = capabilities })
-- JSON
lspconfig.jsonls.setup({ capabilities = capabilities })
-- YAML
lspconfig.yamlls.setup({ capabilities = capabilities })
end,
},
-- Autocompletion
{
"hrsh7th/nvim-cmp",
event = "InsertEnter",
dependencies = {
"hrsh7th/cmp-nvim-lsp",
"hrsh7th/cmp-buffer",
"hrsh7th/cmp-path",
"hrsh7th/cmp-cmdline",
},
config = function()
local cmp = require("cmp")
cmp.setup({
mapping = cmp.mapping.preset.insert({
["<C-b>"] = cmp.mapping.scroll_docs(-4),
["<C-f>"] = cmp.mapping.scroll_docs(4),
["<C-Space>"] = cmp.mapping.complete(),
["<C-e>"] = cmp.mapping.abort(),
["<CR>"] = cmp.mapping.confirm({ select = true }),
["<Tab>"] = cmp.mapping(function(fallback)
if cmp.visible() then
cmp.select_next_item()
else
fallback()
end
end, { "i", "s" }),
}),
sources = cmp.config.sources({
{ name = "nvim_lsp" },
{ name = "buffer" },
{ name = "path" },
}),
})
end,
},
}

This does three things:

  1. Sets up LSP clients for common languages (you’ll install the actual servers separately via your package manager or :LspInstall)
  2. Wires in autocompletion powered by LSP
  3. Maps Tab to cycle through completions

Install language servers with your package manager:

Terminal window
# macOS (Homebrew)
brew install lua-language-server pylsp typescript-language-server gopls rust-analyzer
# Arch
sudo pacman -S lua-language-server python-pylsp typescript-language-server go gopls rust
# Ubuntu/Debian
sudo apt install lua-language-server pylsp nodejs npm
npm install -g typescript typescript-language-server
# etc.

lua/plugins/telescope.lua — Fuzzy Finder

One plugin that earns its weight:

~/.config/nvim/lua/plugins/telescope.lua
return {
{
"nvim-telescope/telescope.nvim",
tag = "0.1.8",
dependencies = { "nvim-lua/plenary.nvim" },
config = function()
local telescope = require("telescope.builtin")
vim.keymap.set("n", "<Leader>ff", telescope.find_files, {})
vim.keymap.set("n", "<Leader>fg", telescope.live_grep, {})
vim.keymap.set("n", "<Leader>fb", telescope.buffers, {})
vim.keymap.set("n", "<Leader>fh", telescope.help_tags, {})
end,
},
}

This gives you fuzzy file search (<Leader>ff), ripgrep text search (<Leader>fg), buffer search, and help tag search. That’s 90% of what you actually use.

lua/plugins/colorscheme.lua — One Less Thing to Think About

~/.config/nvim/lua/plugins/colorscheme.lua
return {
{
"folke/tokyonight.nvim",
lazy = false,
priority = 1000,
config = function()
vim.cmd([[colorscheme tokyonight-night]])
end,
},
}

One good colorscheme. No debate. Done.

lua/plugins/init.lua — Plugin Index

~/.config/nvim/lua/plugins/init.lua
return {
require("plugins.lsp"),
require("plugins.telescope"),
require("plugins.colorscheme"),
-- Add more as you discover you need them
}

Part 6: Actually Using This Thing

  1. Create the structure:

    Terminal window
    mkdir -p ~/.config/nvim/lua/config ~/.config/nvim/lua/plugins
  2. Copy the files above into their respective paths.

  3. Install language servers for the languages you actually use (see the LSP section above).

  4. Open nvim:

    Terminal window
    nvim

    lazy.nvim will auto-install plugins on first run. It’ll take 30 seconds.

  5. Try the basics:

    • <Leader>ff to find a file
    • <Leader>fg to grep for text
    • gd to jump to a function definition (if LSP is running)
    • K to see hover docs
    • :LspInfo to check if your language server is actually running

If LSP isn’t showing up, make sure you installed the server for your language and Neovim can find it in your $PATH.


The Decision: Should You Use This?

You should if: You want a real editor that doesn’t require a PhD in configuration. You’re tired of VSCode’s bloat (1GB RAM on day one). You like tinkering but hate evangelizing. You spend more time in terminals than GUIs. You work on a 10-year-old laptop where every MB counts.

You probably shouldn’t if: You need a debugger GUI (nvim-dap exists but it’s not that). You’re not comfortable with the command line. Your team all uses VSCode and you need to fit in. You care more about clicking buttons than understanding your tools.


What You Don’t Get (And Why That’s Fine)


One Year Later

In a year, you’ll have added maybe 10 more plugins. You’ll understand Lua well enough to write custom keybindings. You’ll have tuned LSP settings for your specific languages. You won’t think about the editor anymore—you’ll just use it.

That’s the point. The goal is to spend less time configuring and more time shipping.

Start here. Add one thing at a time when it hurts. Don’t be the person with 500 lines of config you don’t understand. Be the person with 200 lines you wrote yourself and can explain to someone else.

Your future self (the one debugging that 2 AM bug) will thank you.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Next Post
WebAuthn & Passkeys for Sysadmins

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts