A complete Neovim setup built around Rust development, with LSP, autocomplete, formatting, git integration, and a polished UI.
If you're brand new to Neovim, read this top-to-bottom. If you already know Vim, skip to the shortcuts cheat sheet.
- What you get
- Install
- The most important Vim concept: modes
- Doing the basics
- Shortcuts cheat sheet
- Plugin list
- Common workflows
- Troubleshooting
- LSP (autocomplete, go-to-definition, error highlighting, hover docs) for Rust + Lua, with
masonto install language servers automatically - Inline error squiggles via clippy on save (Rust)
- Inlay hints showing inferred types and parameter names
- Format-on-save via
conform.nvim(rustfmt for Rust, stylua for Lua, prettier for JS/TS/JSON/Markdown/YAML, taplo for TOML) - Fuzzy file/text finder (
telescope) - File tree sidebar (
neo-tree) - Git markers in the gutter + hunk navigation (
gitsigns) - Cargo.toml live version annotations (
crates.nvim) - Auto-close brackets/quotes (
nvim-autopairs) - Surround motions for changing/adding/deleting wrapper chars (
nvim-surround) - Comment toggling with
gcc(Comment.nvim) - VS Code-style buffer tabs at the top (
bufferline) - Color highlighting for hex/rgb/Tailwind (
nvim-colorizer) - Inline labels at the end of long blocks (
nvim-biscuits) - Custom status bar (
lualine, Solarized Osaka theme): magenta rounded mode pill, hexagon section icons, branch + git diff stats, LSP indicator, diagnostic dots - Leader-key cheat popup (
which-key) - Transparent background (
transparent.nvim) - Solarized Osaka color theme (
solarized-osaka.nvim, by craftzdog; Catppuccin Mocha kept installed as a fallback) - Floating per-split filename label, color-matched to the theme (
incline.nvim) - Live-preview LSP rename: every occurrence updates as you type the new name (
inc-rename.nvim) - Smart increment/decrement of numbers, dates, booleans, semver,
let/constwithCtrl+aandCtrl+x(dial.nvim) - Bracket-motion jumps for buffers, indents, jumps, oldfiles, undo states, treesitter nodes (
mini.bracketed) - Cowboy mode: a friendly nag after 10 rapid
hjklpresses, to nudge you toward real motions (custom, seelua/discipline.lua) - Animated cursor trail (
smear-cursor.nvim) - Floating command-line + pretty notifications (
noice.nvim) - Smooth scroll and window-resize animations (
mini.animate) - Indent guides with an animated current-scope highlight (
indent-blankline+mini.indentscope) - Rainbow-colored matching brackets (
rainbow-delimiters) - Color-coded
TODO/FIXME/HACKbadges (todo-comments) - Distraction-free writing mode (
zen-mode) - In-buffer Markdown rendering — colored headers, code-fence borders, rendered checkboxes, list bullets (
render-markdown.nvim) - Editor polish: relative line numbers, cursor-line highlight, hidden end-of-buffer tildes, brighter Catppuccin-tinted indent guides
You need:
- Neovim 0.11 or newer —
brew install neovim - Rust toolchain (for rustfmt + clippy via rust-analyzer) —
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - Node (for prettier) —
brew install node - A Nerd Font in your terminal — see Terminal + Nerd Font below
Optional:
styluaandtaplowill be auto-installed bymasonprettier— install globally withnpm install -g prettier
The icons everywhere (file tree, status bar, bufferline tabs, telescope) require a Nerd Font in your terminal. Without it, all icons render as ? boxes.
This config was set up with JetBrainsMono Nerd Font, but any Nerd Font works. To install it on macOS:
brew install --cask font-jetbrains-mono-nerd-fontThen point your terminal at the new font (font lists are cached on launch — fully quit and reopen your terminal first):
| Terminal | How to set the font |
|---|---|
| Warp | ⌘ , → Appearance → Text → Font → search JetBrains → pick JetBrainsMono Nerd Font Mono |
| Kitty | Add to ~/.config/kitty/kitty.conf: font_family JetBrainsMono Nerd Font |
| iTerm2 | Preferences → Profiles → Text → Font → pick JetBrainsMono Nerd Font |
| Ghostty | Add to ~/.config/ghostty/config: font-family = "JetBrainsMono Nerd Font" |
| Alacritty | In ~/.config/alacritty/alacritty.toml: [font.normal] family = "JetBrainsMono Nerd Font" |
After switching the font, restart Neovim so it re-renders with the correct glyphs.
Note on Warp specifically: the cursor-trail animation from smear-cursor.nvim and the smooth scrolling from mini.animate will look choppier in Warp than in Kitty/Ghostty/WezTerm because Warp isn't a pure GPU-rendered TUI. If the animations matter, run Neovim in Kitty or Ghostty.
# Back up any existing config first
mv ~/.config/nvim ~/.config/nvim.backup 2>/dev/null
# Clone this repo to where Neovim looks for config
git clone https://github.com/<your-username>/my-nvim-config ~/.config/nvim
# Launch Neovim — Lazy will auto-install all plugins on first run
nvimThe first launch downloads ~30 plugins. Wait for it to finish (you'll see a green checkmark on each), close the Lazy window with q, then quit with :qa! and reopen for a clean start.
Vim has modes. Pressing the same key does different things in different modes. This is the #1 thing that confuses beginners.
| Mode | What it's for | How to enter |
|---|---|---|
| Normal | Navigation + commands. The default mode. You move around and run commands here. | Esc from anywhere |
| Insert | Typing text into the file. | i, a, o, O from normal mode |
| Visual | Selecting text. | v, V, or Ctrl+v from normal mode |
| Command | Running :save, :quit, etc. |
: from normal mode |
The big mental shift: you can't just open a file and start typing. You're in normal mode by default. Press i to enter insert mode and then type. Press Esc to leave insert mode when you're done typing.
You will press Esc thousands of times. That's normal.
Look at the bottom-left of the screen. The lualine status bar shows the current mode:
NORMALINSERTVISUALCOMMAND
From your terminal:
nvim # opens with no file (you can browse from here)
nvim somefile.rs # opens that file
nvim . # opens the current directory (browse with the file tree)
cd ~/Projects/foo && nvim . # open a whole projectInside Neovim:
:e path/to/file # open a specific file by path
Press one of these in normal mode:
i— insert before the cursora— insert after the cursor (good for appending)o— open a new line below and enter insert modeO— open a new line above and enter insert mode
Now you can type freely.
Press Esc, or quickly type jj (this config maps jj → Esc in insert mode so your hands never leave the home row). You're back in normal mode.
In normal mode, type:
:w
and press Enter. Or use the shortcut Space + w (the leader key is Space).
:q # quit current window
:qa # quit all windows
:qa! # quit all without saving
:wq # save and quit
Or Space + q to quit the current window.
u— undoCtrl+r— redo
The leader key is Space. So <leader>w means "press Space then w."
| Shortcut | Action |
|---|---|
h j k l |
Left, down, up, right (use these, not arrows) |
w |
Jump forward one word |
b |
Jump back one word |
e |
Jump to end of word |
0 |
Start of line |
$ |
End of line |
gg |
Top of file |
G |
Bottom of file |
Ctrl+d |
Half-page down |
Ctrl+u |
Half-page up |
{ / } |
Jump by paragraph |
% |
Jump to matching bracket |
f<char> |
Jump to next occurrence of <char> on the line |
* |
Search for the word under cursor |
| Shortcut | Action |
|---|---|
i a o O |
Enter insert mode (see above) |
x |
Delete the character under cursor (goes to black hole, does not overwrite yank) |
dd |
Delete the entire line |
5dd |
Delete 5 lines |
yy |
Yank (copy) the line |
p |
Paste below cursor / after cursor (uses default register, can be overwritten by deletes) |
P |
Paste above / before cursor |
Space + d / Space + D |
Delete without yanking (works in normal + visual). Use when deleting something you do NOT want to overwrite your last copy. |
Space + p / Space + P |
Paste from the yank register ("0). Always pastes the last copy, never a deleted thing. |
u |
Undo |
Ctrl+r |
Redo |
dw |
Delete word |
cw |
Change word (delete it + enter insert mode) |
ci" |
Change inside " (cursor anywhere on the string) |
ci( |
Change inside () |
da{ |
Delete around {} (including the braces) |
gcc |
Toggle comment on the line |
gc (visual) |
Toggle comment on selection |
| Shortcut | Action |
|---|---|
Space + w |
Save file |
Space + q |
Quit current window |
:w |
Save (command form) |
:qa! |
Force quit everything |
| Shortcut | Action |
|---|---|
:vsplit |
Split vertically (new window opens to the right of current; the current file stays put) |
:split |
Split horizontally (new window opens below current) |
Ctrl+w then h j k l |
Move between splits |
Ctrl+w then = |
Equalize split sizes |
Ctrl+w then q |
Close current split |
The right/below behavior comes from splitright and splitbelow set in vim-options.lua. Default Neovim opens new splits left/above which most people find disorienting.
| Shortcut / behavior | Action |
|---|---|
/foo |
Search forward for foo |
?foo |
Search backward |
n / N |
Next / previous match |
* |
Search for word under cursor |
| Case sensitivity | ignorecase + smartcase: /foo matches Foo/FOO/foo, but /Foo only matches Foo |
:%s/old/new/g |
Replace all old with new in the file. A live preview pane opens showing every change as you type, thanks to inccommand = "split". |
| Shortcut | Action |
|---|---|
Shift+l |
Next tab |
Shift+h |
Previous tab |
Space + b + d |
Close current tab |
| Shortcut | Action |
|---|---|
Ctrl+n |
Toggle the file tree sidebar |
(inside tree) j k |
Move up/down |
(inside tree) Enter |
Open file / expand folder |
(inside tree) a |
Add new file (end name with / for folder) |
(inside tree) d |
Delete file |
(inside tree) r |
Rename |
(inside tree) c |
Copy |
(inside tree) m |
Move |
(inside tree) H |
Toggle hidden files |
(inside tree) ? |
See all neo-tree shortcuts |
| Shortcut | Action |
|---|---|
Ctrl+p |
Fuzzy find files by name (respects .gitignore) |
Ctrl+f |
Search file contents (live grep) |
;f |
Find files including hidden / git-ignored |
;r |
Live grep including hidden / git-ignored |
;t |
Browse :help tags |
;e |
List diagnostics across all open buffers |
;s |
Browse functions/variables/symbols (treesitter) in current file |
;; |
Resume the previous telescope picker (with your last query) |
\\ |
List open buffers |
Space + f + b |
File browser scoped to the current buffer's directory |
(inside telescope) Enter |
Open the highlighted result |
(inside telescope) Esc |
Cancel |
(inside telescope) Ctrl+u / Ctrl+d |
Scroll preview up/down |
[ jumps backward, ] jumps forward, then a letter for what to jump between.
| Shortcut | Action |
|---|---|
[b / ]b |
Previous / next buffer |
[i / ]i |
Previous / next indent change |
[j / ]j |
Previous / next jump in jumplist |
[l / ]l |
Previous / next item in location list |
[n / ]n |
Previous / next sibling treesitter node |
[o / ]o |
Previous / next file in :oldfiles (recently opened) |
[u / ]u |
Previous / next undo state |
[x / ]x |
Previous / next git conflict marker |
[c / ]c |
Previous / next git hunk (from gitsigns, not mini.bracketed) |
[d / ]d |
Previous / next diagnostic (from LSP, not mini.bracketed) |
| Shortcut | Action |
|---|---|
Ctrl+a |
Increment number / date / boolean / semver / let↔const under cursor |
Ctrl+x |
Decrement same |
Examples: on 42 it goes to 43. On true it flips to false. On 1.2.3 it bumps to 1.2.4. On let it swaps to const. On 2025/05/11 it advances the date by a day.
| Shortcut | Action |
|---|---|
K |
Show hover documentation |
gd |
Go to definition |
gr |
Show all references |
Space + r + n |
Rename symbol everywhere (uses inc-rename: every occurrence updates live as you type the new name; Esc to cancel, Enter to commit) |
Space + c + a |
Code actions (quick fixes / refactors) |
Space + t + h |
Toggle inlay hints on/off (the gray inferred-type / parameter-name annotations) |
[d |
Jump to previous diagnostic |
]d |
Jump to next diagnostic |
Ctrl+o |
Jump back to previous location |
| Shortcut | Action |
|---|---|
Ctrl+Space |
Trigger completion menu |
Tab / Shift+Tab |
Navigate suggestions |
Enter |
Accept selected suggestion |
Ctrl+e |
Dismiss menu |
Ctrl+d / Ctrl+u |
Scroll long doc popups |
| Shortcut | Action |
|---|---|
Space + f |
Format current file (or selection) |
| (auto) | Saves automatically format the file via the right tool |
| Shortcut | Action |
|---|---|
cs"' |
Change "hello" to 'hello' |
ds" |
Delete surrounding " from "hello" → hello |
ysiw" |
Wrap inner word in " → hello becomes "hello" |
ysiw) |
Wrap inner word in tight parens → hello becomes (hello) |
ysiw{ |
Wrap inner word in spaced braces → hello becomes { hello } |
S" (visual mode) |
Wrap selection in " |
| Shortcut | Action |
|---|---|
]c |
Jump to next changed hunk |
[c |
Jump to previous changed hunk |
Space + h + s |
Stage hunk under cursor |
Space + h + r |
Reset/discard hunk |
Space + h + p |
Preview hunk diff |
Space + h + b |
Blame current line |
| Shortcut | Action |
|---|---|
Space + c + t |
Toggle inline annotations |
Space + c + u |
Update crate under cursor |
Space + c + U |
Upgrade all crates |
Space + c + v |
Show available versions |
Space + c + f |
Show available features |
| Shortcut / command | Action |
|---|---|
Space + z |
Toggle Zen mode (centers buffer, hides UI) |
:TodoTelescope |
Open a telescope picker of every TODO/FIXME/HACK in the project |
:TodoQuickFix |
Send all TODOs to the quickfix list |
:Noice |
Open the noice message history UI |
:NoiceDismiss |
Dismiss any visible noice popups |
:IBLToggle |
Toggle indent guide lines |
:RainbowDelimitersToggle |
Toggle rainbow brackets |
:SmearCursorToggle |
Toggle the cursor smear animation |
| Command | Action |
|---|---|
:Lazy |
Open the Lazy plugin manager UI |
:Lazy sync |
Install missing + update existing plugins |
:Lazy update |
Update all plugins |
:Mason |
Open Mason (manage language servers / formatters) |
:MasonInstall <name> |
Install a tool via Mason |
:TSUpdate |
Update treesitter parsers |
| Shortcut | Action |
|---|---|
Space (then wait) |
which-key popup shows all shortcuts starting with Space |
:checkhealth |
Diagnose plugin/config issues |
:LspInfo |
See active language servers for current buffer |
:TransparentEnable / :TransparentDisable |
Toggle background transparency |
Each plugin lives in its own file under lua/plugins/.
| File | Plugin | What it does |
|---|---|---|
solarized-osaka.lua |
craftzdog/solarized-osaka.nvim | Active color theme |
catppuccin.lua |
catppuccin/nvim | Color theme (Mocha flavour), installed but lazy / inactive |
incline.lua |
b0o/incline.nvim | Floating filename label in the top-right of each split |
mini-bracketed.lua |
echasnovski/mini.bracketed | [/] jump motions for buffers, indents, undo, treesitter nodes, etc. |
inc-rename.lua |
smjonas/inc-rename.nvim | Live-preview LSP rename (replaces default <leader>rn behavior) |
dial.lua |
monaqa/dial.nvim | Smart Ctrl+a / Ctrl+x (numbers, dates, booleans, semver, let↔const) |
telescope.lua |
nvim-telescope/telescope.nvim | Fuzzy finder (with fzf-native + file-browser extensions) |
treesitter.lua |
nvim-treesitter/nvim-treesitter | Syntax highlighting |
neo-tree.lua |
nvim-neo-tree/neo-tree.nvim | File tree sidebar |
lsp-config.lua |
mason + mason-lspconfig + nvim-lspconfig | Language server setup |
completions.lua |
nvim-cmp + LuaSnip + friendly-snippets | Autocomplete + snippets |
formatting.lua |
stevearc/conform.nvim | Format on save |
lualine.lua |
nvim-lualine/lualine.nvim | Status bar |
gitsigns.lua |
lewis6991/gitsigns.nvim | Git markers + hunk operations |
which-key.lua |
folke/which-key.nvim | Leader-key cheat popup |
comment.lua |
numToStr/Comment.nvim | gcc to toggle comments |
autopairs.lua |
windwp/nvim-autopairs | Auto-close brackets/quotes |
surround.lua |
kylechui/nvim-surround | Surround motions |
biscuits.lua |
code-biscuits/nvim-biscuits | Inline labels at end of long blocks |
colorizer.lua |
catgoose/nvim-colorizer.lua | Color highlighting (hex/rgb/Tailwind) |
crates.lua |
saecki/crates.nvim | Cargo.toml version annotations |
bufferline.lua |
akinsho/bufferline.nvim | VS Code-style tabs |
transparent.lua |
xiyaowong/transparent.nvim | Transparent background |
smear-cursor.lua |
sphamba/smear-cursor.nvim | Animated cursor trail |
noice.lua |
folke/noice.nvim | Floating cmdline + pretty notifications |
mini-animate.lua |
echasnovski/mini.animate | Smooth scroll / window-resize animations |
indent-blankline.lua |
lukas-reineke/indent-blankline.nvim | Indent guide lines |
mini-indentscope.lua |
echasnovski/mini.indentscope | Animated current-scope highlight |
rainbow-delimiters.lua |
HiPhish/rainbow-delimiters.nvim | Colored matching brackets |
zen-mode.lua |
folke/zen-mode.nvim | Distraction-free buffer view |
todo-comments.lua |
folke/todo-comments.nvim | Highlight TODO / FIXME / HACK comments |
render-markdown.lua |
MeanderingProgrammer/render-markdown.nvim | Pretty in-buffer rendering for Markdown files |
cd ~/Projects/foo
nvim .
You see the file tree on the right. To open src/main.rs:
- Press
Ctrl+p(telescope fuzzy find) - Type
main.rs Enter
You're now in main.rs in normal mode. To edit:
- Move your cursor to where you want to type (
hjklor arrows or/searchterm) - Press
ito enter insert mode - Type your changes
Escto leave insert mode:w(orSpace+w) to save
If you've opened multiple files, switch between them with:
Shift+l/Shift+h(next/prev tab)- Or
Ctrl+p(filename) /Ctrl+f(search content) to find a different one
After saving a Rust file, clippy runs and you'll see error/warning markers:
]d— jump to the next diagnosticK— read the hover docs about why it's flaggedSpace + c + a— get suggested code actions (often "apply suggestion" auto-fixes it)Escto dismiss popups
- Move cursor onto the function name
Space + r + n- The current name is pre-filled in the command line. Edit it; every occurrence in the buffer updates live with each character you type.
Enterto commit (renames across all files via LSP), orEscto cancel and revert.
- Position cursor on the first line
V(visual line mode)- Move down to select more lines (
jor arrows) gc
To uncomment, do the same thing again.
You changed several things in a file but only want to commit one of them.
- In the file, jump to the hunk:
]c(next hunk) or[c(previous) Space + h + pto preview what that hunk isSpace + h + sto stage just that hunk- Repeat for any other hunks you want
- Commit normally from your terminal:
git commit
This used to be an open issue with nvim 0.12 + nvim-treesitter master, worked around by disabling treesitter on markdown buffers. The workaround was removed when render-markdown.nvim was added (it requires treesitter on markdown). If errors return, run :TSUpdate first; if they persist, re-add an autocmd in treesitter.lua:
vim.api.nvim_create_autocmd("FileType", {
pattern = "markdown",
callback = function() pcall(vim.treesitter.stop) end,
})— but note that render-markdown.nvim will stop working if you do.
Your terminal isn't using a Nerd Font. See the Terminal + Nerd Font section in Install — common gotcha: the font is installed but you didn't fully quit your terminal before searching for it in the font picker (font lists are cached on launch).
A plugin is calling an old nvim API. Run :checkhealth vim.deprecated to see which one. Usually fixed by :Lazy update once upstream ships a fix. Harmless until then.
Run :LspInfo in a .rs file. If rust_analyzer isn't attached, check that rust-analyzer is installed (rust-analyzer --version in your shell). Mason should auto-install it on first launch — if it didn't, run :Mason, find rust-analyzer, press i to install.
Check that the formatter for that filetype is on your PATH:
- Rust:
rustfmt --version(ships with rustup) - Lua:
stylua --version(Mason installs it;:MasonInstall stylua) - JS/TS/etc:
prettier --version(npm install -g prettier)
Run :Lazy sync. If a plugin is stuck on "Working", check :Lazy log for errors. Usually a network or git issue.
init.luadoes the bootstrap and loads everything viarequire("lazy").setup("plugins")— that auto-imports every.luafile underlua/plugins/.- Editor settings live in
lua/vim-options.lua. - Each plugin is one file. To add a plugin, drop a new
.luafile inlua/plugins/returning the spec — Lazy picks it up automatically. - Commit
lazy-lock.jsonso other machines reproduce the exact plugin versions.