Learn Practice Daily golf Reference
Guides · Learning Vim

Your Vim config, line by line

Your config is a plain text file your editor runs every time it starts: ~/.vimrc for Vim, ~/.config/nvim/init.lua for Neovim, settings.json for VS Code's Vim extension, ~/.ideavimrc for JetBrains IDEs. Open it from inside Vim with :e $MYVIMRC, add one line you understand, and reload with :source %. A dozen lines of your own beat a thousand copied ones.

The ninja in its home dojo at night, brushing a new glowing line onto a hanging scroll titled init.lua, a smaller .vimrc scroll beside it, a wall of toggle switches and a laptop showing line numbers.
Where each editor keeps its config (Vim 9, Neovim 0.10 and later, VSCodeVim, IdeaVim)
EditorThe fileWritten inOpen it
Vim on macOS and Linux ~/.vimrc (or ~/.vim/vimrc) Vim script :e $MYVIMRC
Vim on Windows $HOME/_vimrc (or $HOME/vimfiles/vimrc) Vim script :e $MYVIMRC
Neovim on macOS and Linux ~/.config/nvim/init.lua (or init.vim, never both) Lua (or Vim script) :e $MYVIMRC
Neovim on Windows ~/AppData/Local/nvim/init.lua Lua (or Vim script) :e $MYVIMRC
VS Code (the Vim extension) your user settings.json JSON Command Palette: Preferences: Open User Settings (JSON)
JetBrains IDEs (IdeaVim) ~/.ideavimrc (or ~/.config/ideavim/ideavimrc) Vim script, a subset The IdeaVim icon in the status bar: Open ~/.ideavimrc

Your dojo, your rules

All through the course you train in a dojo someone set up for you: numbers in the gutter, a few lines kept above the cursor, searches that ignore case. Your own editor is your own dojo, and the config is the list of rules on its wall. The editor reads it at every start and does what it says.

To open it, type :e $MYVIMRC in Vim or Neovim: $MYVIMRC holds the path of the config the editor loaded. No config yet? Then there's nothing to open, so make the file. In Vim, :e ~/.vimrc and :w. In Neovim, :exe 'edit' stdpath('config') .. '/init.lua', then :w ++p (the ++p makes the folder too). :echo $MYVIMRC tells you which file is in charge at any time.

  • Neovim: Lua or Vim script. init.lua and init.vim both work, one at a time. This guide shows both Vim script and Lua; the options are the same names in each.
  • Vim: one surprise. With no ~/.vimrc, Vim loads defaults.vim, which switches on syntax highlighting, incsearch, scrolloff=5 and the mouse. The moment your own file exists, it doesn't. Keep them with two lines at the top: unlet! skip_defaults_vim and source $VIMRUNTIME/defaults.vim.
  • Neovim starts friendlier. hlsearch, incsearch, the mouse and syntax highlighting are already on, undo files already have a folder of their own (undofile itself still needs switching on), and termguicolors switches itself on when your terminal can show true colour.

Build one, a switch at a time

The window below is Vim with an empty config, after a search for total. Each switch is one line in a config file: flip it and the window redraws the way that line would make it. Under the window, the file those switches add up to, in Vim script, Lua or VS Code's JSON, ready to copy.

Try number and then relativenumber, together and on their own, and watch what happens to your own line's number. Then flip ignorecase with hlsearch off and on.

Vim with an empty config. Switch an option on and watch the window.
line numbers
search
the view
" ~/.vimrc
" nothing yet: Vim's defaults

The options worth having, and why

Each line below earns its place by fixing something you'd otherwise notice every day. Vim script first, Lua after.

  • Where am I? set number relativenumber (vim.opt.number = true, vim.opt.relativenumber = true). Your line shows its real number, for :42 and error messages; every other line shows how far away it is, which is the count you type before j or k.
  • Searches that read your mind. set ignorecase smartcase. /total finds Total and TOTAL too; type a capital, /Total, and case counts again.
  • See every match. set incsearch hlsearch: matches light up while you type the search, and stay lit after. :nohlsearch (or :noh) puts them out until the next search; Neovim already does both, and its Ctrl-l clears them.
  • See where you're going. set scrolloff=8 keeps eight lines past the cursor in view, so you never edit on the window's last row.
  • Long lines. wrap is on by default: a long line folds onto the next screen row. set nowrap cuts it at the edge instead, which code readers often prefer.
  • Spaces, not tabs. set expandtab shiftwidth=2 tabstop=2: >> and the Tab key put in two spaces, and a real tab character shows two columns wide. Use 4 if that's your project's style.
  • Undo that survives. set undofile keeps undo history after you close the file. In Vim, also set undodir=~/.vim/undo// (make that folder first), or the undo files land next to your code; Neovim already keeps them in its own folder.
  • The system clipboard. set clipboard=unnamedplus (vim.opt.clipboard = "unnamedplus") makes y and p use the clipboard every other app uses. In Vim on macOS or Windows, use unnamed; Vim needs to be built with clipboard support (vim --version shows +clipboard).
  • The mouse. set mouse=a lets you click, select and scroll in a terminal Vim. Neovim has it on already.
  • Splits where you look. set splitbelow splitright: :split opens below and :vsplit to the right, as most apps do.
  • Real colours. set termguicolors in Vim, for colour schemes made for true colour terminals.

In Lua, an on/off option is true or false, a number is a number, and a word is a string: vim.opt.clipboard = "unnamedplus". Neovim's own example config writes vim.o.number = true; vim.o and vim.opt both work for these.

Try it here

Write your first .vimrc

A config is a file like any other, edited with the same keys. Add set relativenumber on a new line below set number, change the tab width from 8 to 2, then add set expandtab shiftwidth=2 as the last line.

Stuck? Show the keyso opens a line below and starts Insert mode; $ goes to the end of the line and r replaces one character.
A new line below: o, type set relativenumber, Esc.vimrc
1" ~/.vimrc
2set number
+set relativenumber
1set tabstop=8
NORMALLoading the editor…0.0sLn 2, Col :1

This editor needs a keyboard. Read on, and play it at your computer.

Check yourself

What relative numbers are for

With relativenumber on, the gutter shows 4 beside the return line, and your cursor is on the tax line. Which keys take you to return?

BeforeJust a picture: pick an answer below (1, 2, 3, 4)
1 const tax = total * TAX_RATE;
1 const fee = rush ? 2 : 0;
2 const tip = total * 0.15;
3 const due = total + tax + fee;
4 return { total, tax, fee, tip, due };
NORMAL
Pick an answer (click it, or press its number), then watch the engine play the keys to check it.
Check yourself

smartcase, with a capital

Your config has set ignorecase smartcase. The cursor is on the first line, and you search /Total (capital T). Where does the cursor land?

BeforeJust a picture: pick an answer below (1, 2, 3, 4)
1let total = 0;
1// Total before tax
2const subtotal = total;
NORMAL
Pick an answer (click it, or press its number), then watch the engine play the keys to check it.

Set it, check it, reload it

Every option can be tried before it goes in the file: :set relativenumber switches it on in this editor until you quit. When it's what you want, write the same words in the config without the colon.

  • Is it on? :set number? answers number or nonumber; :set scrolloff? answers scrolloff=0.
  • Who set it? :verbose set scrolloff? also says which file and line last set it: the first thing to type when an option won't stay the way you wrote it (a plugin may have changed it).
  • Flip it. :set number! turns it on if it's off and off if it's on; :set nonumber turns it off.
  • Reload. :source % runs the file you're editing, so a change in your config takes effect at once (in Neovim, Lua files too). Deleting a line and reloading doesn't switch its option back off, though: restart for a clean slate.
  • Read about it. :help 'scrolloff' (the quotes find the option) explains any option; :options lists them all, with their current values.
  • When it breaks. vim --clean or nvim --clean starts with no config at all, so you can always get in and fix the file.
Practise the keys

Your config commands, from memory

Type each one as you would in Vim, Enter included, with option names in full.

Which keys do it?
Open the config file the editor loaded.
scroll here, then press it
1 of 5
Open the config file the editor loaded.

Vim script and Lua, side by side

Neovim reads Lua, Vim reads Vim script, and the option names are the same in both. Once you can read one column below you can write the other. When there's no Lua spelling to hand, vim.cmd("set number") runs any Vim script line from Lua.

ToVim script (.vimrc)Lua (init.lua)
Switch an option onset numbervim.opt.number = true
Switch it offset nonumbervim.opt.number = false
Give it a numberset scrolloff=8vim.opt.scrolloff = 8
Give it a wordset clipboard=unnamedplusvim.opt.clipboard = "unnamedplus"
Set the leader keylet mapleader = " "vim.g.mapleader = " "
Map a key in Normal modennoremap <leader>h :nohlsearch<CR>vim.keymap.set("n", "<leader>h", "<cmd>nohlsearch<CR>")
Map a key in Insert modeinoremap jk <Esc>vim.keymap.set("i", "jk", "<Esc>")
A comment" like this-- like this

The leader key and your own mappings

A mapping makes some keys stand for others. The shape is the same everywhere: the mode, the keys you'll press, and the keys they stand for. inoremap jk <Esc> (Lua: vim.keymap.set("i", "jk", "<Esc>")) means: in Insert mode, typing j k quickly does Esc. It's the most popular first mapping, because your fingers never leave the home row.

Why `noremap`? The nore means the right-hand side is pressed as Vim's own keys, not through your other mappings. Swap ; and : with nmap ; : and nmap : ;, and each mapping feeds the other forever; with nnoremap they swap cleanly. Always use the noremap forms (nnoremap, inoremap, vnoremap) unless you mean to chain mappings. vim.keymap.set is non-recursive already.

The leader is a key that starts commands of your own, so they never land on a key Vim already uses. It's a backslash until you choose; Space is popular, since in Normal mode it only moves one character right. Set it first, let mapleader = " ", then map: nnoremap <leader>h :nohlsearch<CR> makes ␣ h put out the search highlights. Set the leader before the mappings that use it: a mapping reads the leader when it's made.

Try it here

jk, switched on

This editor has inoremap jk <Esc> in its config. Each line needs a ; at the end: A, type ;, then j k instead of Esc. Then the next line.

Stuck? Show the keysType j and k one right after the other: Vim waits a moment after the j to see whether a k follows.
A, type ;, then jk to leave Insert modeconfig.ts
1const TAX_RATE = 0.0875;
1const OPEN_HOUR = 11
2const CLOSE_HOUR = 23
NORMALLoading the editor…0.0sLn 1, Col :1

This editor needs a keyboard. Read on, and play it at your computer.

In VS Code and JetBrains IDEs

The Vim plugins for other editors read their own config, so the same ideas travel in a different spelling.

  • VS Code (VSCodeVim). Settings go in your user settings.json. Some are the extension's, under vim.: "vim.hlsearch": true, "vim.leader": "<space>", "vim.useSystemClipboard": true. Others are VS Code's own, because VS Code draws the screen: "editor.lineNumbers": "relative" for relative numbers, "editor.cursorSurroundingLines": 8 for scrolloff, "editor.wordWrap" for wrapping. vim.ignorecase and vim.smartcase are already on. Mappings are lists of before and after keys, per mode. The extension can read a .vimrc (vim.vimrc.enable), but only its mappings, and it's marked experimental.
  • JetBrains IDEs (IdeaVim). ~/.ideavimrc reads like a .vimrc: set number relativenumber, set ignorecase smartcase, set scrolloff=8, inoremap jk <Esc>. It understands a subset of Vim's options, and adds its own for IDE actions. After an edit, IdeaVim offers a reload button in the editor, or type :source ~/.ideavimrc.

More on each: Vim in VS Code and Vim in JetBrains IDEs.

Start small

The fastest way to a config you hate is to copy someone's thousand lines on day one: plugins you didn't choose, mappings that move keys the tutorials assume, and no idea which line did what. Learn the stock editor first, then add options as you find a need.

  • Start with nothing, or the few lines below. Use the editor for a week.
  • Add a line when something bugs you twice. Try it with :set first, then write it down.
  • One line, one reason. A comment above a line you might forget (" keep 8 lines in view) is worth it.
  • Keep it in git. A dotfiles repo means a new laptop is one clone away, and every change has a date and a reason.
  • Starting points worth reading, not pasting. Neovim 0.11 ships $VIMRUNTIME/example_init.lua, a short, commented config: :read $VIMRUNTIME/example_init.lua puts it in your file to read line by line.

A year from now your config will be a record of how you work, and every line in it will mean something to you. Practising the moves themselves is what the course is for.

Questions

Where is my vimrc?
Type :echo $MYVIMRC inside Vim or Neovim: it prints the path of the config it loaded, or nothing if there isn't one. Vim looks for ~/.vimrc (on Windows $HOME/_vimrc), Neovim for ~/.config/nvim/init.lua or init.vim.
Should I write init.lua or init.vim?
Either works in Neovim, one at a time. Lua is what most Neovim plugins and their setup instructions use, so init.lua is the usual choice for a new config; init.vim is handy if you share lines with a Vim .vimrc.
Can Neovim use my existing .vimrc?
Yes. Create init.vim with set runtimepath^=~/.vim runtimepath+=~/.vim/after, let &packpath = &runtimepath and source ~/.vimrc, as Neovim's help (:help nvim-from-vim) suggests, and restart.
Why did syntax highlighting stop when I made a .vimrc?
Without a .vimrc, Vim loads defaults.vim, which turns on syntax highlighting and more. Your own file stops that. Add unlet! skip_defaults_vim and source $VIMRUNTIME/defaults.vim at the top, or syntax on and filetype plugin indent on.
Should I start from a distribution like LazyVim?
It gives you a full IDE at once, but also hundreds of choices you didn't make. You'll do better learning the stock editor and adding options as you need them; read a short example such as $VIMRUNTIME/example_init.lua instead of pasting a big one.