Vim Config for Rails

I’ve been an Emacs user for much longer than I’ve been using Vim but I find vim to be more productive and I do like it’s the unix philosophy a lot. That being said, emacs is a great tool that can do more than just text editing but for me it fails to be as productive as vim.

Even though I’ve switched to vim full-time now, I still go back to emacs from time to time because there are parts of it I miss in vim (like staging chunks with magit for example). But the more important part, and the reason I’m bringing up emacs is that it has influenced the way I use vim a lot.

The one thing that confused me most at first was that vim is meant to be used as just a text editor and not like an IDE. So for example, in emacs you would load a ton of files and keep them there not having to close the editor for days (even weeks) which is not how you use vim. In vim, whenever you have to edit something you fire up vim do your edit and get out. At least that’s probably how vim was used in the old days, I’m not sure if there are still people using it like that.

Vim owns the Rails community

Back when TextMate died, a few years ago, everyone was looking for something better to replace it. I’ve also used TextMate and liked it very much, mostly because of it’s simplicity while still keeping you very productive. That and the fact that it looked awesome on the mac.

The fact that there was no alternative to TextMate (and there still isn’t one), most people looked at the next best thing, which meant it had to support Rails and be very powerful but yet simple to use and probably open source so we don’t have to go through the same experience again. At that time, the only editor that had most of those qualities was vim with the plugins from Tim Pope that were making it play nice with Rails. I say most of it because the ease of use is not there, vim has a learning curve that very is hard to ignore.

Vim and Emacs both promise more power that you can ever handle and it’s true, up to a point. The problem with that power is that it’s raw power and you’re very likely never to use it. To use that raw power, you’ll have to learn a new programming language that is on the other side of beautiful compared to ruby. That requires a huge time investment which most of us don’t have.

The productivity side plays an important role in a developer’s choice and I think that’s the reason more people (including myself) choose vim over emacs.

Here goes nothing (aka. my .vimrc)

I’ve been passionate about text editors for a long time so expect my vim setup to contain a touch more than you would normally see in a regular, sane .vimrc file.

let mapleader = " "

I find that setting my leader to the space key is the best choice I’ve made so far. It’s very easy to hit and it’s not really used for anything else in normal mode.

autocmd!
set nocompatible
set noshowmode

The first option here, the autocmd! one, clears the stage for the rest of the file. It makes sure there are no surprises, no commands bound to any events so that I can later add just the ones I need.

The nocompatible option tells vim to act more like the modern vim as opposed to the old vi.

The noshowmode option is used to disable the label for the current mode. I don’t really need an indicator for the mode I’m currently in, I can figure that out by the shape of my cursor so there’s no point in having it.

filetype plugin on
filetype indent on

These two options tell vim to load any plugins it finds for the current file type. The indent option on the second line does the same thing but for the corresponding indent file.

set exrc

Whenever you are working on a project that requires different settings (for example 80 chars column, or indent with tabs) than you would normally use for the rest of your projects, you can create a .vimrc file relative to that project and vim will load it when you switch to that project and if you set the exrc option.

au BufWritePost .vimrc so $MYVIMRC

This is just a handy trick to automatically load the .vimrc file whenever you save it. I usually tweak something and I want it to take effect immediately, that’s why I have this setting.

" set the runtime path to include Vundle and initialize
set rtp+=~/.vim/bundle/vundle/
call vundle#begin()

" let Vundle manage Vundle, required
Plugin 'gmarik/vundle'

This chunk will enable Vundle, a plugin manager for vim. Now that most of the vim plugins are up on github, you can just grab the author/rep part of the url and paste it into your vim config and Vundle will do the rest. It also knows how to uninstall and update plugins so you don’t have to do that manually.

So here’s the huge list of plugins I’m currently using. I won’t go thru each one but I’m going to provide links to each one if you want to read more about them.

Plugin ‘gmarik/vundle’
Plugin ‘dockyard/vim-easydir’
Plugin ‘ecomba/vim-ruby-refactoring’
Plugin ‘SirVer/ultisnips’
Plugin ‘int3/vim-extradite’
Plugin ‘jelera/vim-javascript-syntax’
Plugin ‘kchmck/vim-coffee-script’
Plugin ‘pangloss/vim-javascript’
Plugin ‘rorymckinley/vim-rubyhash’
Plugin ‘tmhedberg/matchit’
Plugin ‘tpope/vim-commentary’
Plugin ‘tpope/vim-fugitive’
Plugin ‘tpope/vim-haml’
Plugin ‘tpope/vim-ragtag’
Plugin ‘tpope/vim-rails’
Plugin ‘tpope/vim-rake’
Plugin ‘tpope/vim-surround’
Plugin ‘tpope/vim-vinegar’
Plugin ‘tpope/vim-unimpaired’
Plugin ‘tpope/vim-speeddating’
Plugin ‘tpope/vim-rsi’
Plugin ‘tpope/vim-projectionist’
Plugin ‘stefanoverna/vim-i18n’
Plugin ‘szw/vim-tags’
Plugin ‘vim-ruby/vim-ruby’
Plugin ‘danchoi/ri.vim’
Plugin ‘kien/ctrlp.vim’
Plugin ‘d11wtq/ctrlp_bdelete.vim’
Plugin ‘tacahiroy/ctrlp-funky’
Plugin ‘mileszs/ack.vim’
Plugin ‘junegunn/vim-easy-align’
Plugin ‘chriskempson/vim-tomorrow-theme’
Plugin ‘christoomey/vim-tmux-runner’
Plugin ‘gabebw/vim-spec-runner’
Plugin ‘marcweber/vim-addon-mw-utils’
Plugin ‘tomtom/tlib_vim’
Plugin ‘wellle/targets.vim’
Plugin ‘vim-scripts/SyntaxAttr.vim’
Plugin ‘guns/xterm-color-table.vim’
Plugin ‘slim-template/vim-slim’
Plugin ‘nanotech/jellybeans.vim’
Plugin ‘jgdavey/vim-blockle.git’
Plugin ‘vim-scripts/closetag.vim’
Plugin ‘tommcdo/vim-exchange’
Plugin ‘AndrewRadev/switch.vim’
Plugin ‘mattn/gist-vim’
Plugin ‘mattn/webapi-vim’
Plugin ‘tpope/vim-abolish’
Plugin ‘tpope/vim-repeat’
Plugin ‘gregsexton/gitv’
Plugin ‘gabrielelana/vim-markdown’
Plugin ‘henrik/vim-indexed-search’
Plugin ‘vim-scripts/LargeFile’
Plugin ‘skwp/greplace.vim’
Plugin ‘chriskempson/base16-vim’
Plugin ‘AndrewRadev/splitjoin.vim’
Plugin ‘avakhov/vim-yaml’
Plugin ‘idanarye/vim-merginal’
Plugin ‘mustache/vim-mustache-handlebars’
Plugin ‘godlygeek/tabular’

" All of your Plugins must be added before the following line
call vundle#end()
hi def link CtrlPMatch CursorLine
let g:ctrlp_clear_cache_on_exit = 0
let g:ctrlp_switch_buffer = 'Et'
let g:ctrlp_custom_ignore = {
  \ 'dir':  '\.git\|node_modules\|bin\|\.hg\|\.svn\|build\|log\|resources\|coverage\|doc\|tmp\|public/assets\|vendor\|Android',
  \ 'file': '\.jpg$\|\.exe$\|\.so$\|tags$\|\.dll$'
  \ }
nnoremap <C-b> :CtrlPBuffer<cr>
" CtrlP Delete
call ctrlp_bdelete#init()
" CtrlP Funky
let g:ctrlp_extensions = [‘funky']
let g:ctrlp_funky_multi_buffers = 1

I’m a huge fan of CtrlP**. I use it every day and I’m not really sure if vim would be that useful without it. This is one of those features that made TextMate awesome back in the day. You can fuzzy find a file or a buffer and quickly switch to it. It’s a lot better than having to remember and type each path whenever you need to visit a new file or buffer. I highly recommend it.

CtrlP Funky** is bringing the same fuzzy find capability to the methods in all your opened buffers. It allows you to quickly jump to a method by name and not having to remember the file name it lives in.

" Disable tag generation on file save
let g:vim_tags_auto_generate = 0

On large projects, generating a tags file is slow so I don’t really want that to happen on every save. I’m totally fine doing it manually whenever I thing my tags file is out of date.

" Vim tmux runner
" I don't want the default key mappings
let g:VtrUseVtrMaps = 0
" Vim spec runner
let g:spec_runner_dispatcher = 'call VtrSendCommand("{command}")'
map <Leader>tf <Plug>RunCurrentSpecFile
map <Leader>tt <Plug>RunFocusedSpec
map <Leader>tl <Plug>RunMostRecentSpec

I use Rspec a lot and I also live in Tmux. Needless to say I need a way to remove the pain out of running my specs. Now, vim (this sucks, compared to emacs) needs to wait for whatever command you tell it to run before you can get back control of the editor. So because of that there are some workarounds which try to make vim more responsive and having it talk to Tmux is one of them. Basically I hand the spec running command to a separate Tmux pane and I can keep control of my editor while the spec is running. I’ve also bound some shortcuts to the commands that run my specs.

nnoremap <C-c> :bnext\|bdelete #<CR>

I like to get rid of buffers I don’t use. I don’t have a reason why I do that, it probably makes it easier to find the ones I need, I don’t really know. So for that to happen and not having to reopen my windows (because when you delete a buffer from vim it also closes it’s window) I’ve bound the two commands above to C-c in normal mode. So now when I want to get rid of a buffer, I just do C-c and move on.

There’s one tiny problem with that, the buffer that replaces the one you’ve just killed is not the one I expect, I’m not sure how vim choses which one to show but I would expect to be the previous buffer, the one I’ve visited before. So if anyone knows how to make that happen, please let me know in the comments.

let g:netrw_liststyle = 0

I’ve switched from NERDTree to the vim’s default file manager and I’m trying to make it work. The line tells netrw how to list your files.

I’m not entirely happy with it but I’m trying. The biggest advantage with the default file manager is that it’s intuitive, meaning you know in which window the file you’re about to open will open. With NERDTree that’s a bit more complicated but NERDTree has some nice features too, like having some intuitive shortcuts to create, rename, delete files.

" Don't map rubyhash keys
let g:rubyhash_map_keys = 0
" ruby path if you are using RVM
let g:ruby_path = system('rvm current')
" Disable mappings from vim-ruby-refactoring
let g:ruby_refactoring_map_keys=0
" Intent private methods
let g:ruby_indent_access_modifier_style = 'outdent'

The vim-rubyhash plugin is used to quickly toggle hash keys from string to symbol and convert them to the new ruby1.9 syntax. The rubyhash_map_keys setting there will prevent the rubyhash plugin to do its key mapping. I don’t really like this default behavior of plugins since they tend to mess up with your key mappings.

I use rvm for most of my projects so the second line is used to configure rvm’s ruby path.

The third line disables the key mappings from the vim-ruby-refactoring plugin. This plugin provides a few handy functions like :RExtractLet which will extract a variable into a let(:variable) in your specs. It also has a few more but this one is the one I use more often.

I like my private methods indented two spaces from the private keyword so that’s what `ruby_indent_access_modifier_style’ does.

let g:UltiSnipsSnippetDirectories=["UltiSnips"]

This one just tells the ultisnips plugin where it should look for my custom snippets.

set encoding=utf-8 nobomb
nnoremap Q <nop>

Here, I’m setting the default encoding to UTF8 and I’m disabling vim’s ex-mode because I’ve never used it and It confuses me whenever I type Q by accident.

" Invisible characters
set listchars=tab:▸\ ,trail:·,eol:¬,nbsp:_,extends:❯,precedes:❮

" Syntax coloring lines that are too long just slows down the world
set synmaxcol=1200

" Use only 1 space after "." when joining lines instead of 2
set nojoinspaces

" Don't reset cursor to start of line when moving around
set nostartofline

I’m pretty picky about white space so I want to see those invisible chars. That way I know when I’ve added extra white space and I can remove it.

The synmaxcol option tells vim how many characters on a each line should be syntax highlighted. Having this set to a smaller number makes vim more responsive. It’s sad that syntax colouring slows down such a powerful editor, but it does.

I also use the J key a lot to join lines and I only want one space (instead of two) between the joined lines and after the ‘.’.

" Autocomplete ids and classes in CSS
autocmd FileType css,scss set iskeyword=@,48-57,_,-,?,!,192-255
" Add the '-' as a keyword in erb files
autocmd FileType eruby set iskeyword=@,48-57,_,192-255,$,-

I’m adding more characters to the definition of a keyword but just for a few file types (erb, css and scss).

set background=dark
colorscheme railscasts

I currently like and use the railscasts theme so the two lines above make that happen.

" Make those debugger statements painfully obvious
au BufEnter *.rb syn match error contained "\<binding.pry\>"
au BufEnter *.rb syn match error contained "\<debugger\>"

I’m one of those people who use the debugger in Rails so I like to have the line where I place my debugger command to be highlighted. The commands above look for the binding.pry and debugger commands and highlight them in red so there are easy to see.

syntax enable
syntax sync fromstart

You need to tell vim to enable syntax highlighting, it doesn’t do it by default. Doh.

I also have a bunch of one-liner settings that are pretty self explanatory:

set hlsearch                    " highlight the search
set ls=2                        " show a status line even if there's only one window

" Improve vim's scrolling speed
set ttyfast
set ttyscroll=3
set lazyredraw

set wildmenu                    " show completion on the mode-line
set linespace=0                 " number of pixels between the lines
set splitright                  " open vertical splits on the right
set splitbelow                  " open the horizontal split below
set wrap                        " wrap long lines
set linebreak                   " break lines at word end
set nobackup                    " don't want no backup files
set nowritebackup               " don't make a backup before overwriting a file
set noswapfile                  " no swap files
set hidden                      " hide buffers when abandoned

" Time out on key codes but not mappings
set notimeout
set ttimeout
set ttimeoutlen=100

" Auto-reload buffers when files are changed on disk
set autoread

" Lines with equal indent form a fold.
set foldmethod=indent
set foldlevel=1
set foldnestmax=10
" Open all folds by default
set nofoldenable

set undofile                    " Save undo's after file closes
set undodir=~/.vim/undo         " where to save undo histories
set undolevels=1000             " How many undos
set undoreload=10000            " number of lines to save for undo

set vb                          " disable alert sound
set showcmd                     " display incomplete commands
set history=100                 " a ton of history

" Default shell and shell syntax and source ~/.bash_profile
set shell=/bin/bash\ --login
let g:is_bash=1

" Whitespace
set tabstop=2 shiftwidth=2      " a tab is two spaces
set expandtab                   " use spaces, not tabs
set backspace=indent,eol,start  " backspace through everything in insert mode

" Searching
set incsearch                   " incremental searching
set ignorecase                  " searches are case insensitive...
set smartcase                   " ... unless they contain at least one capital letter
set scrolloff=0                 " keep a 5 line padding when moving the cursor

set autoindent                  " indent on enter
set smartindent                 " do smart indenting when starting a new line
set shiftround                  " indent to the closest shiftwidth

set switchbuf=""                " do not move focus/cursor to where the buffer is already open
set tagbsearch                  " use binary searching for tags

" The "Press ENTER or type command to continue" prompt is jarring and usually unnecessary.
set shortmess=atI
" remove search highlighting
nnoremap <leader>h :noh<cr>

I use the incremental search quite a lot and I need a quick way to clear out the search highlighting. So I’ve bound a key to it <leader>h which seems to work pretty well.

" C-c send enter in insert mode
inoremap <C-c> <Esc>

This is awesome, I’ve aliased the <esc> key to C-c when I’m in insert mode. Instead of having to reach for the escape key (or even worse, for the C-[ key combo) I just do C-c and I’m back to normal mode.

cnoreabbrev W w
cnoreabbrev Q q

I’m aliasing w and q to their uppercase counterparts because I often have the shift key pressed and I type W instead of w.

" Expand %% to current directory
" http://vimcasts.org/e/14
cnoremap %% <C-R>=expand('%')<cr>

I’ve stolen this from the 14th Vimcasts episode. It’s a quick way of obtaining the current file path on the command line.

autocmd Filetype gitcommit setlocal spell textwidth=72

This one is also great, it forces you to type commit messages that are no longer than 72 characters. Which is a very good practice.

augroup vimrcEx
  " Clear all autocmds for the current group
  autocmd!

  " Jump to last cursor position unless it's invalid or in an event handler or
  " a git commit
  au BufReadPost *
    \ if &filetype !~ '^git\c' && line("'\"") > 0 && line("'\"") <= line("$") |
    \   exe "normal! g`\"" |
    \ endif

  " Some file types use real tabs
  au FileType {make,gitconfig} set noexpandtab sw=4

  " Treat JSON files like JavaScript
  au BufNewFile,BufRead *.json setf javascript

  " Make Python follow PEP8
  au FileType python set sts=4 ts=4 sw=4 tw=79

  " Make sure all markdown files have the correct filetype
  au BufRead,BufNewFile *.{md,markdown,mdown,mkd,mkdn,txt} setf markdown

  " MultiMarkdown requires 4-space tabs
  au FileType markdown set sts=4 ts=4 sw=4
augroup END

In that whole block above, I’m setting config options for each of the file types. So for example JSON files have the javascript mode enabled, markdown files have different spaces/tabs setting, and so on.

if has("statusline") && !&cp
  set statusline=%y\ %<%f\ %h%m%r%=%-16.(line\ %l\ of\ %L%)
  set statusline+=%#warningmsg#
  set statusline+=%*
endif

I’m still configuring my status line. I want a minimalistic one with just the file name and probably the line/col numbers but I’m not entirely sold on it, I know I’m gonna change it in the near future.

" Complete till longest common string.
" When more than one match exists, list them all.
set wildmode=longest,list

I use completion on the vim’s command line so this one enables that for me.

" Disable output and VCS files
set wildignore+=*.o,*.out,*.obj,.git,*.rbc,*.rbo,*.class,.svn,*.gem

" Ignore images and log files
set wildignore+=*.gif,*.jpg,*.png,*.log

" Disable archive files
set wildignore+=*.zip,*.tar.gz,*.tar.bz2,*.rar,*.tar.xz

" Ignore bundler and sass cache
set wildignore+=*/vendor/gems/*,*/vendor/cache/*,*/.bundle/*,*/.sass-cache/*

" Ignore rails temporary asset caches
set wildignore+=*/tmp/cache/assets/*/sprockets/*,*/tmp/cache/assets/*/sass/*

" Ignore custom folders
set wildignore+=*/resources/*

" Ignore node modules
set wildignore+=node_modules/*

" Disable temp and backup files
set wildignore+=*.swp,*~,._*

" Disable osx index files
set wildignore+=.DS_Store

A few files that I want vim to ignore whenever possible.

cnoremap w!! %!sudo tee > /dev/null %

Whenever you find yourself changing files that are only writable by the root user, this bit will come in handy. It allows you to save those files even if you’re not root (you need to be able to sudo though).

" Visually select the text that was last edited/pasted
nnoremap gV `[v`]
" selelct what you've just pasted
nnoremap gp `[v`]

" reselect visual block after indent/outdent
vnoremap < <gv
vnoremap > >gv

Very handy tricks for playing with visual selections. The first two help you with re-selecting and the last two with indenting visual selections.

nnoremap <C-h> <C-w>h
nnoremap <C-j> <C-w>j
nnoremap <C-k> <C-w>k
nnoremap <C-l> <C-w>l

Given you have your hands on the home row most of the time, these shortcuts will save you when you try to switch windows. And that happens a lot.

nnoremap <leader><leader> :b#<cr>
nnoremap <leader>V :e $MYVIMRC<cr>

Two of my favorite shortcuts. The first one allows me to type the spacebar twice and it will bring me back to the last buffer I was on. The <leader>V shortcut is also handy because I can get to my .vimrc file quickly.

autocmd BufReadPost fugitive://* set bufhidden=delete

I don’t use vim-fugitive too much but when I do I want those buffers it creates to be gone by the time I’m done with it. That’s what the line above does, it removes those fugitive buffers.

autocmd BufRead,BufNewFile *.hamljs set filetype=haml

This one tells vim that .hamljs files should be using the haml mode.

au FileType qf call AdjustWindowHeight(3, 15)
function! AdjustWindowHeight(minheight, maxheight)
  exe max([min([line("$"), a:maxheight]), a:minheight]) . "wincmd _"
endfunction

I’m not sure if this is very useful but what it does is it resizes the quickfix window to match it’s contents.

" tmux will only forward escape sequences to the terminal if surrounded by a DCS sequence
" http://sourceforge.net/mailarchive/forum.php?thread_name=AANLkTinkbdoZ8eNR1X2UobLTeww1jFrvfJxTMfKSq-L%2B%40mail.gmail.com&forum_name=tmux-users
if exists('$TMUX')
  let &t_SI = "\<Esc>Ptmux;\<Esc>\<Esc>]50;CursorShape=1\x7\<Esc>\\"
  let &t_EI = "\<Esc>Ptmux;\<Esc>\<Esc>]50;CursorShape=0\x7\<Esc>\\"
else
  let &t_SI = "\<Esc>]50;CursorShape=1\x7"
  let &t_EI = "\<Esc>]50;CursorShape=0\x7"
endif

Just some play nice with tmux helpers. I’ve copied them from somewhere but I can’t remember the exact problem they solve.

autocmd BufLeave,FocusLost * silent! update

This one will call update on the current file whenever you leave it. So that your files will always be saved.

" The custom :Qargs command sets the arglist to contain each of the files
" referenced by the quickfix list.
command! -nargs=0 -bar Qargs execute 'args' QuickfixFilenames()
function! QuickfixFilenames()
  " Building a hash ensures we get each buffer only once
  let buffer_numbers = {}
  for quickfix_item in getqflist()
    let buffer_numbers[quickfix_item['bufnr']] = bufname(quickfix_item['bufnr'])
  endfor
  return join(map(values(buffer_numbers), 'fnameescape(v:val)'))
endfunction

I’ve used this in the past to do a replace in project kind of operation. Unfortunately this is how ugly it looks in vim.

autocmd VimResized * :wincmd =

That’s the last one. I’m using vim in terminal inside tmux so I want my vim windows to resize themselves whenever I resize the terminal window.

For anyone interested in my .vimrc, you can find it on github.

Conclusion

This was a pretty long one but I really hope you’ve got something out of it. I’m always improving my vim config so keep watching my git repo if you’re interested to see my updates.

Please share this article if you’ve learned something new out of it.