nvim ~/.config/nvim/init.lua NORMAL
EDITOR: NEOVIM MANAGER: LAZY.NVIM MULTIPLEXER: TMUX

My Neovim Config

A complete walkthrough of my Neovim setup — lazy.nvim, LSP, Telescope, tmux integration, and everything else I use to write Rust (and everything else) every day.

I left VS Code about a year ago. Not because it was bad — it's a fine editor — but because I wanted to understand my tools at a deeper level, and because the mouse was slowing me down. Neovim, combined with tmux, gave me an environment where my hands never leave the keyboard and every piece of the setup is something I chose, configured, and understand. This post is a complete walkthrough of that setup: what I run, how it's organized, and why each piece is there.

Everything here is in Lua. The config is modular, managed by lazy.nvim, and lives in a Git repo so I can clone it onto any machine and be productive in minutes. If you're thinking about making the switch, or just want to steal some ideas, this is for you.

📸
Screenshot
Full Neovim window with a Rust file open — showing catppuccin theme, lualine, nvim-tree sidebar, and inline diagnostics.
FOUNDATIONS

Directory Structure

The whole config lives at ~/.config/nvim and follows a clean module pattern. The entry point is init.lua, which loads two things: core settings and the plugin manager.

~/.config/nvim/ ├── init.lua ← entry point ├── lazy-lock.json ← lockfile for reproducible installs └── lua/jaken/ ├── lazy.lua ← lazy.nvim bootstrap ├── core/ │ ├── init.lua ← loads options + keymaps │ ├── options.lua ← editor settings │ └── keymaps.lua ← global key mappings └── plugins/ ├── init.lua ← base plugins (plenary, tmux-nav) ├── colorscheme.lua ← catppuccin ├── telescope.lua ← fuzzy finding ├── nvim-tree.lua ← file explorer ├── lualine.lua ← statusline ├── treesitter.lua ← syntax highlighting ├── nvim-cmp.lua ← autocompletion ├── rustaceanvim.lua ← Rust tooling ├── lsp/ │ ├── lspconfig.lua ← LSP config + keymaps │ └── mason.lua ← LSP server installer └── ... (15+ more)

Each plugin gets its own file. lazy.nvim automatically picks up everything in the plugins/ directory. To add a new plugin, you just drop a new Lua file in there. To remove one, delete the file. Clean separation, no giant monolithic config.

📸
Screenshot
nvim-tree sidebar showing the config directory structure itself.

Entry Point

The entire bootstrap is two lines:

~/.config/nvim/init.lua
require("jaken.core")
require("jaken.lazy")

That's it. Core settings load first (so options like mapleader are set before any plugins reference them), then lazy.nvim bootstraps and loads all plugins.

CORE SETTINGS

Options

These are the editor-wide settings that apply regardless of what plugins are loaded. The important choices:

lua/jaken/core/options.lua
local opt = vim.opt

-- Line numbers: relative + absolute on current line
opt.relativenumber = true
opt.number = true

-- 2-space indentation, spaces not tabs
opt.tabstop = 2
opt.shiftwidth = 2
opt.expandtab = true
opt.autoindent = true

opt.wrap = false               -- no line wrapping
opt.cursorline = true          -- highlight current line
opt.termguicolors = true       -- 24-bit color
opt.background = "dark"
opt.signcolumn = "yes"         -- always show sign column

-- Search: case-insensitive unless you type uppercase
opt.ignorecase = true
opt.smartcase = true

-- System clipboard integration
opt.clipboard:append("unnamedplus")

-- Splits open to the right and below
opt.splitright = true
opt.splitbelow = true

Relative line numbers are essential for Vim motions. Instead of counting lines visually, you just see the offset and type 12j to jump 12 lines down. The current line still shows its absolute number.

System clipboard (unnamedplus) means yanking in Neovim puts text on the system clipboard and vice versa. No more "+y dance.

I also configure inline diagnostics for rust-analyzer and clippy with a clean visual style:

vim.diagnostic.config({
  virtual_text = {
    prefix = "●",
    spacing = 2,
  },
  signs = true,
  underline = true,
  update_in_insert = false,
  severity_sort = true,
})
📸
Screenshot
Inline diagnostics from rust-analyzer showing a warning with the ● prefix style.

Global Keymaps

The leader key is Space. I use jk to exit insert mode — faster than reaching for Escape.

lua/jaken/core/keymaps.lua
vim.g.mapleader = " "

local keymap = vim.keymap

-- Exit insert mode
keymap.set("i", "jk", "<ESC>", { desc = "Exit insert mode with jk" })

-- Clear search highlights
keymap.set("n", "<leader>nh", ":nohl<CR>", { desc = "Clear highlights" })

-- Increment/decrement numbers
keymap.set("n", "<leader>+", "<C-a>", { desc = "Increment number" })
keymap.set("n", "<leader>-", "<C-x>", { desc = "Decrement number" })

Window & Tab Management

PLUGIN MANAGER

lazy.nvim

lazy.nvim is the plugin manager. It's fast, supports lazy-loading out of the box, and has a lockfile (lazy-lock.json) so installs are reproducible across machines. The bootstrap code auto-installs it if it's not already present:

lua/jaken/lazy.lua
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not 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("jaken.plugins")

That last line tells lazy.nvim to scan every file in lua/jaken/plugins/ and load them as plugin specs. Each file returns a table (or list of tables) describing one or more plugins.

LOOK & FEEL

Colorscheme — Catppuccin

I use Catppuccin with transparent background enabled so my terminal background shows through. This matters because I run this inside tmux, and I want the terminal's background to be consistent across panes.

lua/jaken/plugins/colorscheme.lua
return {
  "catppuccin/nvim",
  name = "catppuccin",
  priority = 1000,
  config = function()
    require("catppuccin").setup({
      transparent_background = true,
      integrations = {
        cmp = true,
        gitsigns = true,
        nvimtree = true,
        treesitter = true,
        telescope = { enabled = true },
      },
    })
    vim.cmd.colorscheme("catppuccin")
  end,
}

The priority = 1000 ensures the colorscheme loads before any other plugin tries to set highlight groups. The integrations table tells Catppuccin to provide theme support for specific plugins rather than relying on generic highlight groups.

📸
Screenshot
Catppuccin theme with transparent background — showing how the terminal background shows through Neovim.

Statusline — Lualine

lualine replaces Neovim's built-in statusline with something actually useful. I run a custom color theme that changes the mode indicator color based on what mode you're in — blue for Normal, green for Insert, violet for Visual, yellow for Command, red for Replace.

It also shows pending lazy.nvim updates in the statusline so I know when plugins need updating without having to check manually.

📸
Screenshot
Lualine statusbar showing mode indicator, file info, encoding, filetype, and lazy update count.

Buffer Tabs — Bufferline

bufferline adds VS Code-style tabs at the top. I have it running in tabs mode with a slant separator style. Simple, but it makes tab navigation visual rather than something you have to remember.

Dashboard — Alpha

When I open Neovim without a file, alpha-nvim shows a custom dashboard with a greeting (complete with cowboy emoji), the current date/time, and quick-launch buttons for common actions:

-- The dashboard header
dashboard.section.header.val = {
  "🤠",
  "",
  "Howdy, Mr. Herman",
  "Today is " .. os.date("%A") .. " " .. os.date("%d") ..
    " " .. os.date("%B") .. " " .. os.date("%Y"),
  "The time is " .. os.date("%H:%M"),
  "",
  "We've got cases to solve!"
}

The quick-launch buttons map directly to the keymaps I use most: new file, toggle file explorer, find file, find word, restore session, and quit.

📸
Screenshot
Alpha dashboard on startup — showing the cowboy greeting, date, and quick-launch menu.
NAVIGATION

Telescope

Telescope is the single most-used plugin in my setup. It's a fuzzy finder for everything: files, grep results, LSP references, diagnostics, todo comments. It replaces the need for a file browser in most cases.

I compile telescope-fzf-native for faster fuzzy matching and use C-j / C-k to navigate results (keeping my hands on the home row).

My Telescope Keymaps

Tip The <leader><leader> mapping (double-tap Space) for quick file open is something I stole from the VS Code muscle memory of Shift+Shift in JetBrains IDEs. Extremely useful when you know the filename but not the path.
📸
Screenshot
Telescope file finder open with fuzzy matching — showing the preview pane on the right.
📸
Screenshot
Telescope live grep searching across the project — showing matched lines with file context.

File Explorer — nvim-tree

nvim-tree is a sidebar file explorer. I keep it at 35 columns wide with relative line numbers (so I can jump with Vim motions even inside the tree). It shows indent markers, custom folder arrows, and hides .DS_Store files. Git-ignored files are shown (not hidden) because I often need to reference them.

The window picker is disabled so that opening files from the tree always goes to the expected split, not whichever window happens to be focused. This makes nvim-tree work predictably alongside window splits.

📸
Screenshot
nvim-tree sidebar open alongside a code file — showing indent markers, folder arrows, and git status indicators.

Which-Key

which-key shows a popup of available keybindings when you start a key sequence and pause. If I press <leader> and wait 500ms, it shows every mapping that starts with Space. This is invaluable for learning your own config and for discovering mappings you've forgotten about.

📸
Screenshot
which-key popup showing all <leader> mappings organized by category.
EDITING

Autopairs

nvim-autopairs automatically closes brackets, quotes, and parentheses. It integrates with both Treesitter (so it's context-aware about when to add pairs) and nvim-cmp (so accepting a completion that includes a function call also adds the closing paren).

Surround

nvim-surround lets you add, change, and delete surrounding characters. Select a word and surround it with quotes, change " to ', delete the wrapping () — all with a few keystrokes. Once you internalize the motions, it becomes one of those things you can't live without.

Substitute

substitute.nvim gives you a proper substitute operator. Yank some text, then use s{motion} to replace the target with what's in your register. ss substitutes the whole line, S substitutes to end of line. Faster than the :s/old/new/ command for quick replacements.

Comment

Comment.nvim with ts-context-commentstring means gcc comments out a line and gc in visual mode comments out a selection. The Treesitter integration is key — in a .tsx file, it knows to use {/* */} inside JSX and // outside it.

LSP & COMPLETION

Mason & LSP Config

The LSP setup is split into two files. Mason handles installing language servers, and nvim-lspconfig configures them.

Installed Language Servers

Mason auto-installs these on first run:

-- Language servers
tsserver        -- TypeScript / JavaScript
html            -- HTML
cssls           -- CSS
tailwindcss     -- Tailwind CSS
svelte          -- Svelte
lua_ls          -- Lua
graphql         -- GraphQL
emmet_ls        -- Emmet (HTML/CSS shortcuts)
prismals        -- Prisma ORM
pyright         -- Python

-- Formatters & linters
prettier        -- JS/TS/CSS/HTML/JSON/YAML/Markdown/GraphQL
stylua          -- Lua
eslint_d        -- JS/TS linting

LSP Keymaps

These only activate when an LSP server attaches to a buffer:

📸
Screenshot
LSP hover documentation (K) showing type info for a Rust function — including doc comments and signature.

Autocompletion — nvim-cmp

nvim-cmp is the completion engine. It pulls from four sources in priority order:

  1. LSP — language server completions (types, functions, modules)
  2. LuaSnip — snippet expansions (via friendly-snippets)
  3. Buffer — words from the current buffer
  4. Path — file system paths

I use C-j / C-k to navigate the completion menu (home row), C-Space to manually trigger it, and Enter to confirm. The lspkind plugin adds VS Code-style icons to the completion menu so you can tell at a glance whether you're looking at a function, variable, snippet, or type.

📸
Screenshot
nvim-cmp completion popup in a Rust file — showing LSP completions with lspkind icons.
RUST

Rustaceanvim

For Rust specifically, I use rustaceanvim instead of the generic lspconfig setup. It provides a tighter integration with rust-analyzer and comes preconfigured with Rust-specific features.

The key setting: I have clippy as the check-on-save command instead of the default cargo check. This means every time I save, clippy runs and surfaces lint warnings inline — not just compilation errors, but stylistic issues, performance suggestions, and common pitfalls.

lua/jaken/plugins/rustaceanvim.lua
vim.g.rustaceanvim = {
  server = {
    capabilities = require('cmp_nvim_lsp').default_capabilities(),
    default_settings = {
      ['rust-analyzer'] = {
        checkOnSave = true,
        check = {
          command = 'clippy',
          extraArgs = { '--all', '--', '-W', 'clippy::all' },
        },
      },
    },
  },
}

The -W clippy::all flag enables all clippy lint groups, including pedantic and nursery lints that aren't on by default. This is aggressive, but I prefer catching things early.

📸
Screenshot
Rust file with clippy diagnostics showing inline — warnings, suggestions, and error hints from rust-analyzer.
GIT

Gitsigns

gitsigns adds git change indicators in the sign column (green for added, blue for changed, red for deleted) and current line blame — an inline annotation showing who last changed the line and when. The blame delay is set to 300ms so it doesn't flicker as you move around.

Git Keymaps

📸
Screenshot
Gitsigns in action — sign column showing added/changed/deleted indicators with inline blame text on the current line.

LazyGit

lazygit.nvim opens LazyGit in a floating terminal inside Neovim. <leader>lg and I have a full-featured Git TUI without leaving the editor. Staging, committing, rebasing, cherry-picking — all without typing git commands.

📸
Screenshot
LazyGit floating window inside Neovim — showing staged changes, commit history, and diff view.
CODE QUALITY

Formatting — Conform

conform.nvim handles formatting. Rather than relying on the LSP formatter (which can be slow or inconsistent), conform calls external formatters directly. My setup:

-- Formatter assignments
javascript/typescript/jsx/tsx/svelte  →  prettier
css/html/json/yaml/markdown/graphql   →  prettier
lua                                   →  stylua
python                                →  isort + black
rust                                  →  rustfmt

Format-on-save is disabled. I prefer to format manually with <leader>mp when I'm ready. This avoids the jarring experience of your code jumping around while you're still thinking.

Treesitter

Treesitter provides real syntax highlighting (not regex-based) by building an AST of your code. The difference is especially noticeable in complex files like TSX or Svelte where multiple languages are embedded. I auto-install parsers for 17 languages.

Incremental selection is enabled: C-Space selects the current node, repeating expands the selection to the parent node, and Backspace shrinks it. This is faster than visual mode for selecting logical code blocks.

Trouble & Todo Comments

Trouble is a better quickfix list. It shows diagnostics, references, and TODOs in a structured panel. todo-comments highlights TODO, FIXME, HACK, etc. in your code and makes them searchable through both Telescope and Trouble.

📸
Screenshot
Trouble panel open at the bottom showing workspace diagnostics — errors, warnings, and hints sorted by severity.

Indent Blankline

indent-blankline draws subtle indent guides () so you can visually track nesting depth. Essential in deeply nested code, and it's one of those things you don't appreciate until it's gone.

TMUX INTEGRATION

tmux + Neovim

I run Neovim inside tmux. The combination gives me persistent sessions, split panes for terminals alongside the editor, and the ability to detach and reattach from anywhere.

The critical glue is vim-tmux-navigator, which makes Ctrl-h/j/k/l navigate seamlessly between Neovim splits and tmux panes. Without this, you'd need separate key sequences for "move to the Neovim split on the left" vs. "move to the tmux pane on the left." With it, there's one set of keys and it just works — the plugin detects whether the current pane is running Neovim and routes the navigation accordingly.

Setup Note vim-tmux-navigator requires a matching configuration on the tmux side. You need to add the corresponding key bindings to your ~/.tmux.conf. See the plugin docs for the exact lines.

My typical tmux layout: one large pane running Neovim (usually 70-80% of the screen), a smaller pane below for running tests or cargo commands, and occasionally a third pane for a dev server or log tail. The Neovim splits handle code files, and the tmux panes handle everything else. Ctrl-h/j/k/l moves between all of them uniformly.

📸
Screenshot
Full tmux session — Neovim in the main pane with nvim-tree open, a terminal pane below running cargo test, and seamless pane navigation.
QUALITY OF LIFE

Session Management — Auto-Session

auto-session saves and restores your editing session per project directory. Close Neovim, come back the next day, and <leader>wr restores exactly where you left off — open files, splits, cursor positions, everything. Auto-restore is disabled (I prefer to trigger it explicitly), and common directories like ~/ and ~/Downloads are excluded so they don't pollute the session list.

Dressing

dressing.nvim replaces Neovim's built-in vim.ui.select and vim.ui.input with better-looking floating windows. Every time a plugin asks you to pick from a list or enter text, you get a Telescope-style picker instead of the default command-line prompt.

Vim Maximizer

vim-maximizer lets you temporarily maximize a split to full screen with <leader>sm, then restore it to its original size with the same key. Useful when you're working in a split but need to focus on one file for a moment.

GETTING STARTED

How to Set This Up

If you want to run this exact config (or use it as a starting point):

Prerequisites

# Neovim 0.9+ (required for lazy.nvim)
brew install neovim

# tmux
brew install tmux

# Required for Telescope live grep
brew install ripgrep

# Required for telescope-fzf-native
brew install make

# Node.js (required for many LSP servers)
brew install node

# A Nerd Font (for icons in nvim-tree, lualine, etc.)
brew install --cask font-jetbrains-mono-nerd-font

Install

# Back up your existing config if you have one
mv ~/.config/nvim ~/.config/nvim.bak

# Clone the config
git clone https://github.com/jakenherman/nvim-config.git ~/.config/nvim

# Open Neovim — lazy.nvim will auto-install everything
nvim

On first launch, lazy.nvim bootstraps itself, then installs all plugins. Mason will then install the configured language servers and formatters. Give it a minute or two on the first run.

Verify

# Inside Neovim, check plugin status:
:Lazy

# Check installed LSP servers:
:Mason

# Check Treesitter parsers:
:TSInstallInfo
📸
Screenshot
The :Lazy plugin manager UI showing all installed plugins with their load times and status.
REFERENCE

Complete Keymap Reference

General

Navigation

LSP

Git

Session

This config isn't done. It probably never will be. The whole point of a Neovim setup is that it evolves with how you work. But this is where it is today, and it's the most productive I've ever been in a text editor. If you have questions about any of it, feel free to reach out.

J scroll down   K scroll up