Configuration: Writing a .vimrc You Won't Be Embarrassed By
This chapter walks through a lean .vimrc, the options worth flipping, mapping keys, and the autocommands that react to file events.
Where the Config Lives
~/.vimrc classic Vim config (Unix)
~/.vim/vimrc alternative location
$HOME\_vimrc Windows
~/.config/nvim/init.vim Neovim (vimscript)
~/.config/nvim/init.lua Neovim (Lua)
This chapter uses vimscript. Everything here works in Vim and Neovim. If you use Neovim and prefer Lua, convert by wrapping settings in vim.opt and mappings in vim.keymap.set.
Create the file if it doesn't exist:
vim ~/.vimrc
Vim reloads its config on startup. To reload without restarting:
:source $MYVIMRC
A Starter .vimrc
Start with the minimum. Add to it as you hit pain.
" General
set nocompatible " do not emulate vi
set encoding=utf-8
set hidden " allow switching away from unsaved buffers
set history=1000
set undofile " persistent undo across sessions
" Display
set number " show line numbers
set relativenumber " ...relative to the current line
set cursorline " highlight the current line
set ruler " position in the status line
set showcmd " show partial commands as you type
set wildmenu " tab-completion menu for ex commands
set scrolloff=4 " keep 4 lines visible above/below cursor
set sidescrolloff=8
" Search
set ignorecase
set smartcase
set incsearch " show matches as you type
set hlsearch " highlight all matches
" Indentation
set expandtab " spaces, not tabs
set tabstop=4 " a tab character displays as 4
set shiftwidth=4 " indent is 4 spaces
set softtabstop=4 " backspace removes a full indent
set autoindent " keep indent on new lines
filetype plugin indent on
" Files
set nobackup
set nowritebackup
set noswapfile " or set directory=~/.vim/swap//
" Syntax
syntax enable
" Leader
let mapleader = " " " space is a big, easy-to-reach leader
" Mappings
nnoremap <leader>w :w<CR>
nnoremap <leader>q :q<CR>
nnoremap <leader><space> :noh<CR> " clear search highlight
nnoremap <leader>b :ls<CR>:b<Space>
Paste this into ~/.vimrc, save, restart Vim. You now have a setup that most Vim users would recognise as reasonable.
Read on for the pieces and why.
:set and :setlocal
:set changes a global option. :setlocal changes it for the current buffer only.
:set number turn on line numbers globally
:set nonumber turn off
:set number! toggle
:set number? query current value
:setlocal textwidth=80 only for this buffer
In .vimrc, you almost always want set. Use setlocal inside autocommands (below).
The Options Worth Knowing
Line numbers
set number " show line numbers
set relativenumber " relative to cursor (makes 5j, 10k easier)
relativenumber is divisive. With it, every line shows its distance from the cursor, which makes {count}j style motion easy. Without it, the absolute number helps for jumping to a specific line. Some people set both, which shows the current line number and relative numbers elsewhere. Try it.
Search
set ignorecase
set smartcase " case-sensitive only if pattern has capitals
set incsearch " search as you type
set hlsearch " highlight matches
This combination is what you want. smartcase is the detail that elevates ignorecase from annoying to pleasant.
Indentation
set expandtab
set tabstop=4
set shiftwidth=4
set softtabstop=4
tabstop is how wide a tab character displays. shiftwidth is how many columns >> shifts. softtabstop is how many columns backspace deletes. Keep them equal. expandtab makes the Tab key insert spaces instead of a tab character. If your project uses tabs, flip it off per-filetype in an autocommand.
Mouse
set mouse=a " enable mouse in all modes
Lets you click to position the cursor and drag to resize splits. Some people find it handy; Vim purists disagree. Your call.
Persistent undo
set undofile
set undodir=~/.vim/undo
Undo history survives closing the file. Open a file tomorrow and u still works back to yesterday. Create the directory:
mkdir -p ~/.vim/undo
Colour scheme
syntax enable
set background=dark " or light
colorscheme desert " pick any shipped scheme
See available schemes in /usr/share/vim/vim*/colors/ or :colorscheme <Tab>. Popular external themes: gruvbox, dracula, onedark, catppuccin. Install via a plugin manager (chapter 10).
Status line
The default status line is fine. If you want more info:
set laststatus=2 " always show status line
set statusline=%f\ %y\ %m\ %r%=%l:%c\ %p%%
Or install vim-airline or lightline, which give you a pretty one without building it yourself.
Mappings
Mapping keys is how Vim becomes yours. The commands:
map, noremap, nmap, nnoremap, imap, inoremap, vmap, vnoremap, xmap, xnoremap, cmap, cnoremap
That's overwhelming. The rule:
- Always use
noremapvariants - Use the mode-specific version:
nnoremapfor normal mode,inoremapfor insert,vnoremapfor visual, etc.
noremap means "don't recursively interpret the right-hand side". If you map <leader>w to :w<CR> with nmap, and somewhere else you remap :, the mapping might do something unexpected. nnoremap does exactly what it says.
Examples
" Save with leader-w
nnoremap <leader>w :w<CR>
" Swap : and ; in normal mode (no shift to run ex commands)
nnoremap ; :
nnoremap : ;
" jk to leave insert mode
inoremap jk <Esc>
" Centre the screen after jumping to the next search match
nnoremap n nzz
nnoremap N Nzz
" Redo with U (not undo line, which nobody uses)
nnoremap U <C-r>
" Move lines up/down
nnoremap <A-j> :m .+1<CR>==
nnoremap <A-k> :m .-2<CR>==
Don't add all of these on day one. Add the ones you miss.
The leader key
<leader> is a prefix you define:
let mapleader = " " " space
let mapleader = "," " comma
let mapleader = "\\" " backslash (the default)
Set it before any mappings that use <leader>. Space is a popular choice because it's easy to hit and isn't used for much in normal mode.
Common mapping symbols
<CR> carriage return (Enter)
<Esc> escape
<Tab> tab
<Space> space
<BS> backspace
<C-a> Ctrl+A
<S-a> Shift+A (usually just A)
<A-a> Alt+A (sometimes <M-a>)
<leader> the leader key
<silent> suppress command-line echo
Use <silent> when your mapping runs an ex command that would otherwise flash the bottom line:
nnoremap <silent> <leader><space> :noh<CR>
Autocommands
Autocommands run on events: opening a file, saving, changing filetype, entering insert mode, and dozens more.
autocmd {event} {pattern} {command}
Examples:
" Strip trailing whitespace on save
autocmd BufWritePre * %s/\s\+$//e
" Highlight the current line only in the active window
autocmd WinEnter * set cursorline
autocmd WinLeave * set nocursorline
" Filetype-specific settings
autocmd FileType python setlocal expandtab tabstop=4 shiftwidth=4
autocmd FileType go setlocal noexpandtab tabstop=4 shiftwidth=4
autocmd FileType markdown setlocal wrap linebreak textwidth=80
" Jump to the last position you were at in a file
autocmd BufReadPost *
\ if line("'\"") > 1 && line("'\"") <= line("$") |
\ execute "normal! g`\"" |
\ endif
Autocommand groups
Autocommands accumulate. Reloading your .vimrc would add duplicates unless you group them. Convention:
augroup MyConfig
autocmd!
autocmd FileType python setlocal expandtab
autocmd BufWritePre * %s/\s\+$//e
augroup END
autocmd! inside the group clears previous autocmds in that group first. Wrap every autocmd you add in a named group. It is boilerplate, and it saves you hours of "why is this running three times".
Filetype-Specific Config
Autocommands are one way. The cleaner way is a file per filetype, under ~/.vim/after/ftplugin/:
" ~/.vim/after/ftplugin/python.vim
setlocal expandtab
setlocal tabstop=4
setlocal shiftwidth=4
setlocal textwidth=88
Vim loads this file whenever it detects a Python buffer. No autocommand boilerplate. Use the after/ variant so your settings override the system defaults.
Organising a Bigger .vimrc
Once your config exceeds ~100 lines, split it:
~/.vimrc top-level, loads everything else
~/.vim/plugin/ files here load automatically
~/.vim/after/plugin/ load after plugins (for overrides)
~/.vim/after/ftplugin/ filetype-specific settings
From .vimrc:
runtime plugin/mappings.vim
runtime plugin/options.vim
Or just dump everything in ~/.vim/plugin/*.vim and Vim loads them automatically.
Common Pitfalls
"I mapped a key and it does something weird." Use noremap. You probably used map, which recursed through another mapping. Almost every Vim config you see online uses the noremap variants for this reason.
"My autocmds run twice after sourcing .vimrc." Wrap them in augroup ... autocmd! ... augroup END. This is the #1 reason configs misbehave.
"My colorscheme is wrong after Vim starts." Some colorschemes require syntax enable and a terminal that supports true colour:
set termguicolors
syntax enable
colorscheme gruvbox
"My config works for Vim but not Neovim." Most of it does. The main differences: Neovim's config path, Lua alternatives, and a few deprecated vim options. :h vim-differences in Neovim lists them.
"I can't find the right keycode to remap." In insert mode, press <C-v> followed by the key. Vim types the code literally. Use that in your mapping.
Next Steps
Continue to 10-plugins.md to add the handful of plugins that actually earn their keep.