I’ve had an off/on relationship with Vim for the past many years.
Before, I never felt like we understood each other properly. I felt thatthe kind of programming I’m doing is not easily done without plugins andsome essential settings in.vimrc
, butfiddling with all the knobs and installing all the plugins that Ithought I needed was a process that in the end stretched out from fewhours to weeks, months even; and it the end it just caused frustrationinstead of making me a happier coder.
Recently, I decided to give Vim another shot. This time around it wasdifferent – something in my brain switched and now for the first time inmy life I’m proud of my knowledge of Vim. My philosophy of it has changedto “less is more”, my approach was more disciplined and my motivationstronger. And so you don’t spend as much time learning as I did, I amgoing to lay down some fundamentals.
Start with a basic setup – but not zero
I learned this the hard way: you shouldn’t start with a huge.vimrc
file that you copied from someone elsenor should you install every single plugin that seems useful at thatmoment.
This is a good starting point for your .vimrc
:
set nocompatible " choose no compatibility with legacy vi
syntax enable
set encoding=utf-8
set showcmd " display incomplete commands
filetype plugin indent on " load file type plugins + indentation
"" Whitespace
set nowrap " don't wrap lines
set tabstop=2 shiftwidth=2 " a tab is two spaces (or set this to 4)
set expandtab " use spaces, not tabs (optional)
set backspace=indent,eol,start " backspace through everything in insert mode
"" Searching
set hlsearch " highlight matches
set incsearch " incremental searching
set ignorecase " searches are case insensitive...
set smartcase " ... unless they contain at least one capital letter
Everything mentioned in this article is valid in vim running in aterminal as well as graphical (GUI) vim such as gvim or MacVim. Agraphical vim setting offers extra features, but I’m not covering anyof those here.
Don’t use Janus.It’s a community maintained vim configuration project that in theory soundsnice, but once you start using it it’s not all rainbows. The current version ofJanus installs plugins that can mess with your workflow and adds tons of opinionatedmappings and piles of hacks on top of one another. The “experimental” version ofJanus is a rewrite that’s meant to be more configurable, but in fact it’s justmore fragmented and harder to follow what’s going on. You should be in charge ofyour.vimrc
, and with Janus you’re not.
Guidelines for expanding your vim settings later on:
- Never copy something from other people unless you know exactly whatthat particular setting is doing and you recognize that you’ve neededit before.
- Don’t use too many plugins; start with 3-5 and add others as yourskill progresses. UsePathogen for managing them.
- Overly eager plugins like Syntastic can actually hurt yourdevelopment even when you’re not actively using them. Zap suchoffenders quickly.
- Discover your Vim heroes and periodically check how they have settheir editor up. A lot of people publish their dotfiles on GitHub.
You can view my personal vim configuration here.
Make it pretty
Aesthetics of your text editor are very important given the time you’respending in it. You should use a good programming font for your terminalemulator: for example DejaVu Sans Mono orInconsolata. This willaffect vim running in the terminal. Next is the color scheme: pick oneby cycling through available colors with:color
The quality of the color theme will depend severely on your terminalemulator. You should consider using a graphical Vim to reach the highestquality of typeface and color; a graphical environment can also offerextra options such as antialiasing and line spacing settings.
Find what are killer features for you
Right from the start, you’ll need to discover some power movesthat’ll keep you returning to Vim rather than your old editor.
Vim has two main modes you need to care about first: the normal mode(where you move, perform commands) and theinsert mode (where you typein text). The insert mode is on the surface nothing special whencompared to your old editor: you pressi, you’re in insertmode, now you type text as you normally would. Pressing
But it is how you enter insert mode that offers some advantage:
i | insert before character under cursor |
---|---|
a | insert after cursor |
I | insert at beginning of current line |
A | insert at end of the line |
o | starts insert mode in a new line below current one |
O | insert in a new line above current one |
You can also enter insert mode by replacing existing text at the sametime:
ciw | ("change inner word") change word under cursor |
---|---|
ci" | change double-quoted string (but keep the quotes) |
ci( | change text between matching parentheses, also works with brackets |
cc | change whole line |
These shortcuts are a very compelling argument why Vim is more efficient atediting code than most editors.
For more information, see :help change.txt.
Learn to move
In Vim, efficient movement is everything. After all, most of your timeis spentediting, not inserting text and code. Commands for selecting,changing, moving and transforming text all follow the same motionrules, thus making them fundamental in knowing Vim. For example, in theciw command mentioned previously, theiw is amotion that means “inner word”.
Since there are too many different motions to remember at once, you’llneed some sort of a mnemonic to help you while learning.
Knowing where you want to go
When you think about it, you always know where you want to position yourcursor before editing – if you can describe your intent to Vim, you canperform it faster than hitting cursor keys repeatedly or even using themouse. In general:
- If you repeatedly (more than 2-3 times in a row) hit cursor keys tonavigate, there is a better way.
- If you press backspace more than a couple of times to delete text,there is a better way.
- If you find yourself performing the same changes on several lines,there is a better way.
Here’s an overview of several useful methods for navigating yourdocument, with the 2nd column indicating the corresponding backwardsmovement:
→ | ← | description |
---|---|---|
/ | ? | search for a pattern of text, jump to it by hitting Enter ( |
* | # | search for the word under cursor |
n | N | jump to the next match for the previous search |
$ | ^ | position cursor at end of current line |
f | F | position cursor on the character in the same line that matches the next keystroke |
t | T | position cursor before the next character that matches the keystroke |
; | , | repeat the last f, F, t, or T |
w | b | move to start of next word |
W | B | move to start of next "WORD" (sequence of non-blank characters) |
} | { | move down one paragraph (block of text separated by blank lines) |
gg | jump to first line of document | |
G | jump to end of document |
To me, the killer ones on this list are “word”, “WORD” and paragraphmotions.
Pro tip: while in insert mode,
For in-depth documentation on motions, see :help motion.txt.
After the jump
Some of the above motions jump, and it is very useful to know how tobacktrack those jumps. From the:help jump-motionsdocumentation:
A “jump” is one of the following commands:
'
,`
,G
,/
,?
,n
,N
,%
,(
,)
,[[
,]]
,{
,}
,:s
,:tag
,L
,M
,H
and the commands that start editing a new file. If youmake the cursor “jump” with one of these commands, the position of thecursor before the jump is remembered.
Mastering jumps is insanely powerful. Suppose you edited one line,exited insert mode and now you are navigating the document to find outsomething. Now you want to continue editing from the same spot. The`. motion brings the cursorback on the exact place wherethe last change was made.
Another example: you are in the middle of a script and need to add somecode, but that needs adding an extrarequire
statement (or import
,include
, or similar) near the top of the file. You jump to the topwithgg, add the require
statement and jump back to beforethe previous jump with``.
If you did multiple jumps, you can backtrack with
Yanking: copy & paste
Once you know basic motions and navigating around jumps, you can beefficient with copy (“yank” in Vim terms) and paste.
Y | yank current line; prepend with number to yank that many lines |
---|---|
y} | yank until end of paragraph |
dd | delete current line and yank it too (think "cut") |
d3d | delete 3 lines starting from current one |
p | paste yanked text at cursor; prepend number to paste that many times |
P | paste before cursor |
What isn’t obvious at first, but you’re gonna notice soon, is that Vimdoesn’t use your OS clipboard by default for these operations; i.e. youcan’t paste text copied from Vim in other apps andp won’tpaste into Vim what you just copied from another program on yourcomputer.
Vim uses its registers as its internal clipboard. You can even saveyanked text into a named register of your choosing to ensure it is neveroverwritten and paste it later from that register if you need to pasteit multiple times in different spots. Commands for selecting registersstart with "
:
"aY | yank current line into register "a" |
---|---|
"ap | paste from register "a" |
"*Y | yank line into special register "*" which is the system clipboard |
"*p | paste from register "*": the system clipboard |
"_D | delete from cursor until the end of line, but don't yank |
The examples above show how you can explicitly opt-in to use the systemclipboard via"*
, showing that Vim does have access to thesystem clipboard, but doesn’t use it by default. The last example usesthe"_
(the “black hole”) register, which is a way to discard textwithout copying it and overriding the default register.
Pro tip: after you paste code, it might not be indented correctly in the new context. You can select just pasted lines and autoindent them withV`]=. This should fix the indentation in most cases. Breaking it down: uppercaseV
enters line-based visual mode, `]
is a motion that jumps to the end of just changed text, and=
performs auto-indentation.
You can inspect the current state of all registers with:registers. See:help registers.
Quickly navigate files
Real-world projects have more than one file to edit. Efficient switchingbetween files is just as important as motion commands.
Core Vim features
In the same session, Vim remembers previously open files. You can listthem with:buffers (shortcut: :ls). Without evenusing any plugins, you can jump to a buffer on this list by typing:b and a part of a buffer’s name. For example, if you’vepreviously openedlib/api_wrapper.rb
, you can return to it with:b api. Hit
To switch between the currently open buffer and the previous one, use
:
nnoremap <leader><leader> <c-^>
Since my
is set to comma, I just have to hit ,, toalternate between files, for instance tests and implementation.
Folks coming from TextMate, IDEs, or gedit will quickly find themselves cravingfor a directory tree side pane, and the community is going to unanimouslyrecommend NERD tree.Don’t use the NERD tree. It is clumsy, will hurt yoursplit windows workflow because of edge-case bugs and plugin incompatibilities,andyou never needed a file browser pane in the first place, anyway. If youneed to view the directory structure of a project, usetree
command-line tool(easily installed via package manager of choice, such as Homebrew on OS X) andpipe it toless
. If, on the other hand, you want to interactively explorethe file structure while in Vim, simply edit a directory and the built-in Netrwplugin will kick in; for instance, start in the current directory:e ..
ctags
A lot of the time you’re switching to another file to jump to a specific methodor class definition. Now imagine that you can do this with a keystrokewithoutknowing which file actually holds the method/class.
Using tags requires a bit of forethought, but boy is it worth it! It’s thesingle most useful feature for navigating source code that I’ve everexperienced. It just requires that you generate the tags file up front, butdon’t worry: there are clever ways in which you can automatize this step.
You need to install exuberant ctags first. On Mac OS X: brew install ctags
.
To generate the tags file for your Ruby project, for instance, do this on thecommand line:
ctags -R --languages=ruby --exclude=.git
You can do this within Vim too: just start the command with :!. Thiswill generate thetags
file, which is a search index ofyour code. For languages other than Ruby, seectags --list-languages
.
Now you can jump to a method or class definition with a single keystroke, or byspecifying its name with the following commands:
/ :tag foo — jump to tag under cursor/named - g
/ :tjump foo — choose from a list of matching tags
You’ll get hooked on this quickly, but you’ll grow tired of constantly havingto regenerate the tags file after changes or checkouts. Tim Pope hasanautomated solution using git hooks. And if you often open code ofinstalled ruby gems,he got you covered as well.
Fuzzy file searching
What you need for larger projects is a plugin for fuzzy searching of file names in aproject. I find theCommand-T plugin very capable and fast, butit requires ruby on the system and vim compiled with ruby support (MacVim is bydefault, so no worries there). If for some reason that’s a deal breaker for you,some people swear byctrlp.vim.
Here are my mappings for starting a file search with Command-T. Istart a project-wide search with,f and search the directoryof the current file with ,F:
" use comma as key instead of backslash
let mapleader=","
" double percentage sign in command mode is expanded
" to directory of current file - http://vimcasts.org/e/14
cnoremap %% <C-R>=expand('%:h').'/'<cr>
map <leader>f :CommandTFlush<cr>\|:CommandT<cr>
map <leader>F :CommandTFlush<cr>\|:CommandT %%<cr>
For this to be fast, ensure that you don’t have any other mappings thatstart with
, otherwise Vim will always force a slight delayafter keystrokes before resolving the mapping. (See all of the currentmappings in effect with:map.)
Split windows
You can split the current buffer horizontally or vertically. This isuseful, for instance, to view simultaneously the top and the bottom partof the same file. But it’s even more useful to view different files insplits; for instance tests and implementation.
where | horizontal | vertical |
---|---|---|
normal mode | :split | :vsplit |
:CommandT | ||
:Ack | n/a | v |
To switch cursor between these windows, use
" easier navigation between split windows
nnoremap <c-j> <c-w>j
nnoremap <c-k> <c-w>k
nnoremap <c-h> <c-w>h
nnoremap <c-l> <c-w>l
For more information, see :help windows.
Addendum: Essential plugins
During 6 months of using Vim after publishing this article, I now feel confidentenough to sharewhich plugins I find essential for daily work. This issubjective, but you should know that I’ve tried using core Vim features longenough to actually experience the pain while coding without these.
- Command-T
-
I still use Command-T every minute of the day. Readmore about it in the previous section.
- Ack
-
:Ack performs anultra-fast project-widesearch using
ack
command-line tool. Can’t refactor without it; e.g. Idon’t dare to rename a HTML classname without checking first whether it’s usedin CSS or JS. - Commentary
-
Commentary adds key bindings forcommenting & uncommenting code. At first I’ve used acombination of
(blockwise visual mode) and I(uppercase ‘i’) to insert a #
character at the start of multiplelines. However, doing that a lot is cumbersome, and uncommenting is not sostraightforward.With Commentary, select some lines. Press \\. You’redone.
- Tabular
-
If the style guidelines for your project require aligningassignments or other syntax vertically, useTabular.Don’t ever ever keep aligning stuff vertically without a plugin–I’vebeen there, and I feel bad about it. Simply select some lines, and:Tabularize assignment.
- Surround
-
At some point I realized that a significant part of editing codeinvolves manipulating pairs of matching quotes, parentheses, brackets,and tags. For that I started using theSurround plugin, andI’m loving it. For instance,ysiW] (“you surround inner WORD,bracket”) surrounds the current “WORD” with square brackets.
But the non-obvious, killer feature of Surround is the ability togenerate or wrap existing content in HTML tags: e.g.ysip
(“you surround inner paragraph, Ctrl-tag”) prompts you for a tag name and neststhe current paragraph in it. Need HTML attributes? No problem, just type themafter the tag name.Readmore tips for Surround.
Further resources for learning
- :help – may look daunting and unfriendly at first, but is actuallymore useful and in-depth than most resources found on the Internet.
- Coming home to Vim
- VimCasts
- Destroy All Software – there are some episodes on Vim
- VimGolf – time-consuming but rewarding game
- Walking Without Crutches – presentation slides by Drew Neil
- Practical Vim – book by Drew Neil
- My vim configuration and plugins
Thanks Gary Bernhardt, Drew Neil for tips and inspiration, and Tim Popefor all your work on plugins.
if v:lang =~ "utf8$" || v:lang =~ "UTF-8$"
set fileencodings=ucs-bom,utf-8,latin1
endif
set nocompatible " Use Vim defaults (much better!)
set bs=indent,eol,start " allow backspacing over everything in insert mode
"set ai " always set autoindenting on
"set backup " keep a backup file
set viminfo='20,\"50 " read/write a .viminfo file, don't store more
" than 50 lines of registers
set history=50 " keep 50 lines of command line history
set ruler " show the cursor position all the time
" Only do this part when compiled with support for autocommands
if has("autocmd")
augroup redhat
autocmd!
" In text files, always limit the width of text to 78 characters
autocmd BufRead *.txt set tw=78
" When editing a file, always jump to the last cursor position
autocmd BufReadPost *
\ if line("'\"") > 0 && line ("'\"") <= line("$") |
\ exe "normal! g'\"" |
\ endif
" don't write swapfile on most commonly used directories for NFS mounts or USB sticks
autocmd BufNewFile,BufReadPre /media/*,/mnt/* set directory=~/tmp,/var/tmp,/tmp
" start with spec file template
autocmd BufNewFile *.spec 0r /usr/share/vim/vimfiles/template.spec
augroup END
endif
if has("cscope") && filereadable("/usr/bin/cscope")
set csprg=/usr/bin/cscope
set csto=0
set cst
set nocsverb
" add any database in current directory
if filereadable("cscope.out")
cs add cscope.out
" else add database pointed to by environment
elseif $CSCOPE_DB != ""
cs add $CSCOPE_DB
endif
set csverb
endif
" Switch syntax highlighting on, when the terminal has colors
" Also switch on highlighting the last used search pattern.
if &t_Co > 2 || has("gui_running")
syntax on
set hlsearch
endif
filetype plugin on
if &term=="xterm"
set t_Co=8
set t_Sb=m
set t_Sf=m
endif
" Don't wake up system with blinking cursor:
" http://www.linuxpowertop.org/known.php
let &guicursor = &guicursor . ",a:blinkon0"
set autoindent
set cindent
set softtabstop=4
set shiftwidth=4
set expandtab
set nu