A vimrc is an important file for us Vim users.
Edit: My Vimrc has changed since I began writing this article (it is now 7 lines long), but the principle is still the same. If you want to look at the state of the Vimrc at the time I wrote this article, check out this link.
If you're like me, you probably have spent an ungodly amount of time tinkering with it until it grows massive. At some point, it is a good idea to split your vimrc into multiple parts for maintainability. Plus, if you want to share your vimrc to the world, splitting it into an organized structure will help clarify with other devs who are looking at your vimrc.
In this article, I will share how I organize my vimrc. Everything here is a matter of personal preference. There is no objectively right or wrong way to organize a vimrc. I hope that by sharing what's inside my vimrc, you will gain insight into how you can organize your own vimrc to meet your needs. Don't put down any line in your vimrc that you don't understand.
Lastly, this article makes the assumption that your default vimrc file and Vim files directory are in the root directory, ~/.vimrc
and ~/.vim/
, respectively.
Source, The Secret Sauce
The secret sauce of a modular vimrc is the source
Vimrscript command. Think of it like the require
, include
, or import
command.
If you have this inside your ~/.vimrc
:
source '~/my-script.vim'
Inside ~/my-script.vim
:
echo "Hello from outside yo vimrc"
The next time you open Vim, it will output "Hello from outside yo vimrc". Great! This is how you can split your vimrc into multiple parts.
Dotfiles
My Vim configs are stored inside my dotfiles repository - this way I can access them when I'm away from my computer. So if I have to login from a foreign computer, all I need to do is symlink the Vim files from the dotfiles repository, install the dependencies (I have a script for that too, but that's for another time), and I'm set to go!
The Structure
This is the structure for my Vim-related files:
dotfiles/
├─ vim/
│ ├─ custom-functions/
│ │ ├─ function1.vim
│ │ ├─ function2.vim
│ │ ├─ function-N.vim
│ ├─ custom-plugins/
│ │ ├─ ale.vim
│ │ ├─ fzf.vim
│ │ ├─ plugin-N.vim
│ ├─ main/
│ │ ├─ settings.vim
│ │ ├─ themes.vim
│ │ ├─ keymaps.vim
│ ├─ UltiSnips/
│ │ ├─ javascript.snippets
│ │ ├─ ruby.snippets
│ ├─ vimspector.json
├─ vimrc
The important ones are the vimrc
file and the vim/
directory.
Note that I name them without the dot (vimrc
and vim/
as opposed to .vimrc
and .vim/
). This is so that when I clone it, they won't be invisible. It's easier to deal with visible files than invisible ones.
Inside the vim/
directory, I divide them into multiple sub-directories. The three important ones are:
custom-functions/
where I store my own custom functionscustom-plugins/
where I store configs specific for Vim pluginsmain/
where I store the main Vim configurations and basic keymaps
If you notice, I also have a vimspector.json
file and a UltiSnips/
directory. I use the Vimspector plugin and it requires a config file. I also use the ultisnips plugin. It usesUltiSnips/
directory to store custom snippets. If you don't use any of these plugins, just ignore them.
Vimrc
The vimrc file is called first whenever you launch Vim. Inside it, we have:
if empty(glob('~/.vim/autoload/plug.vim'))
silent !curl -fLo ~/.vim/autoload/plug.vim --create-dirs
\ https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
autocmd VimEnter * PlugInstall | source $MYVIMRC
endif
call plug#begin('~/.vim/plugged')
Plug 'iggredible/totitle-vim'
Plug 'tpope/vim-sensible'
Plug 'sjl/badwolf'
Plug 'itchyny/lightline.vim'
Plug 'junegunn/fzf.vim'
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
Plug 'ludovicchabant/vim-gutentags'
Plug 'tomtom/tcomment_vim'
Plug 'mattn/emmet-vim'
Plug 'sheerun/vim-polyglot'
Plug 'tpope/vim-fugitive'
Plug 'Yggdroot/indentLine'
Plug 'preservim/nerdtree'
Plug 'luochen1990/rainbow'
Plug 'tpope/vim-dispatch'
Plug 'junegunn/vim-peekaboo'
Plug 'machakann/vim-sandwich'
Plug 'simnalamburt/vim-mundo'
Plug 'tpope/vim-endwise'
Plug 'tpope/vim-unimpaired'
Plug 'godlygeek/tabular'
Plug 'mhinz/vim-signify', { 'branch': 'legacy' }
Plug 'ryanoasis/vim-devicons'
Plug 'dense-analysis/ale'
Plug 'szw/vim-maximizer'
Plug 't9md/vim-choosewin'
Plug 'Shougo/unite.vim'
Plug 'puremourning/vimspector'
Plug 'SirVer/ultisnips'
Plug 'honza/vim-snippets'
call plug#end()
let b:fileList = split(globpath('~/.vim/main', '*.vim'), '\n')
let b:fileList += split(globpath('~/.vim/custom-functions', '*.vim'), '\n')
let b:fileList += split(globpath('~/.vim/custom-plugins', '*.vim'), '\n')
for fpath in b:fileList
exe 'source' fpath
endfor
My vimrc is less than 50 lines long, including empty lines. Hey, that's pretty readable! Let's go over what they do.
Automatically Installing Vim Plug
I use vim-plug as my plugin manager. The code below checks for the existence of ~/.vim/autoload/plug.vim
when you start Vim. The plugin manager uses that file to read the plugin files. If plug.vim
doesn't already exist, then it will download the plug.vim
file and create the ~/.vim/autoload/plug.vim
directory.
if empty(glob('~/.vim/autoload/plug.vim'))
silent !curl -fLo ~/.vim/autoload/plug.vim --create-dirs
\ https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
autocmd VimEnter * PlugInstall | source $MYVIMRC
endif
You can also find the automatic install script above in the vim-plug tips page.
I haven't really looked at other plugin managers, but I'm confident that you can pull something similar with other plugin managers.
Plugins
The next line lists a list of plugins I use:
call plug#begin('~/.vim/plugged')
Plug 'iggredible/totitle-vim'
Plug 'tpope/vim-sensible'
Plug 'sjl/badwolf'
Plug 'itchyny/lightline.vim'
Plug 'junegunn/fzf.vim'
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
Plug 'ludovicchabant/vim-gutentags'
Plug 'tomtom/tcomment_vim'
Plug 'mattn/emmet-vim'
Plug 'sheerun/vim-polyglot'
Plug 'tpope/vim-fugitive'
Plug 'Yggdroot/indentLine'
Plug 'preservim/nerdtree'
Plug 'luochen1990/rainbow'
Plug 'tpope/vim-dispatch'
Plug 'junegunn/vim-peekaboo'
Plug 'machakann/vim-sandwich'
Plug 'simnalamburt/vim-mundo'
Plug 'tpope/vim-endwise'
Plug 'tpope/vim-unimpaired'
Plug 'godlygeek/tabular'
Plug 'mhinz/vim-signify', { 'branch': 'legacy' }
Plug 'ryanoasis/vim-devicons'
Plug 'dense-analysis/ale'
Plug 'szw/vim-maximizer'
Plug 't9md/vim-choosewin'
Plug 'Shougo/unite.vim'
" Plug 'puremourning/vimspector'
Plug 'SirVer/ultisnips'
Plug 'honza/vim-snippets'
call plug#end()
This part is totally up to you. Use whatever plugins that you need, not what you see other people use.
Sourcing Stuff
Here's what the next 3 lines look like:
let b:fileList = split(globpath('~/.vim/main', '*.vim'), '\n')
let b:fileList += split(globpath('~/.vim/custom-functions', '*.vim'), '\n')
let b:fileList += split(globpath('~/.vim/custom-plugins', '*.vim'), '\n')
We are creating an array named fileList
and appending it with the paths for all the files inside main/
, custom-functions/
, and custom-plugins/
.
Finally, we have this loop. This is where the magic happens.
for fpath in b:fileList
exe 'source' fpath
endfor
It iterates through the fileList
array and calls source
on each file. This keeps my vimrc to be concise and spark joy :D.
Next, let's explore some contents of my main/
, custom-functions/
, and custom-plugins/
the Main Directory
The main directory contains the base vimrc configs that don't depend on any custom plugin.
Inside, I sub-divide them into three files:
settings.vim
keymaps.vim
themes.vim
Main Settings
This is what's inside the settings.vim
file:
set clipboard=unnamed
set noswapfile
set relativenumber number
set tabstop=2
set shiftwidth=2
set expandtab
set ignorecase
set smartcase
set hlsearch
set confirm
set hidden
set shortmess-=S
let s:english_dict = "/usr/share/dict/words"
if filereadable(s:english_dict)
let &dictionary=s:english_dict
endif
This file primarily deals with base settings (if you're curious what they do, consult :help
). The last command with the english_dict
adds a dictionary autocompletion (CTRL-X CTRL-K
), if a dictionary exists.
Main Keymaps
The keymaps.vim
file contains key mappings independent from plugins. For plugin-specific maps, they are inside custom-plugins/
.
Inside my keymaps.vim
, I have:
let mapleader = "\<Space>"
nnoremap <Leader>vs :source ~/.vimrc<CR>
nnoremap <Leader>ve :vsplit ~/.vimrc<CR>
nnoremap <Esc><Esc> :noh<Return><Esc>
I like to use Space
as the leader - but you can use whatever leader key you feel comfortable with.
I have three custom maps. The first two, <Leader>vs
and <Leader>ve
, are to quickly open and source (vs mnemonic: Vimrc Source) Vimrc (mnemonics: Vimrc Edit and Vimrc Source). I've used this countless times.
I also have a <Esc><Esc>
mapped to :noh
(nohighlight). This is used to remove the highlights after searching for keywords.
Main Themes
The themes.vim
file contain configurations for the color schemes and various UI-related settings.
silent! colorscheme evening
silent! colorscheme badwolf
highlight CursorColumn guibg=#ecf0c1
highlight CursorLine guibg=#ecf0c1
set termguicolors
set background=dark
If you notice, I use two color schemes. What's up with that?
badwolf is a custom color scheme plugin. When I launch Vim for the first time on a new machine, I won't have the plugins installed yet, using badwolf
immediately will cause it to fail on that first Vim run. evening
is a built-in Vim color scheme. By putting silent! colorscheme evening
followed by silent! colorscheme badwolf
, if badwolf
isn't available, it will, in effect, falls back to evening
. silent!
will also omit the error message (otherwise you'll see an error message when Vim couldn't find badwolf
)
Custom Functions
The custom-functions/
directory is where all the custom functions live. These are user-specific functions. What I need might be different from yours. But I'll go over some of mine anyway. Feel free to take them, modify them, and steal them (I stole two of them somewhere sometime ago :P).
I have four custom functions. Here are some of them.
Custom Functions to Delete Buffers
function! DeleteAllBuffers()
let l:current_pos = getpos('.')
execute "%bd | e# | echo 'Buffers Deleted'"
call setpos('.', l:current_pos)
endfunc
function! DeleteMatchingBuffers(pattern)
let l:bufferList = filter(range(1, bufnr('$')), 'buflisted(v:val)')
let l:matchingBuffers = filter(bufferList, 'bufname(v:val) =~ a:pattern')
if len(l:matchingBuffers) < 1
echo 'No buffers found matching pattern ' . a:pattern
return
endif
exec 'bd ' . join(l:matchingBuffers, ' ')
endfunction
command! -nargs=1 DelBuf call DeleteMatchingBuffers('<args>')
nnoremap <silent> <Leader>bD :call DeleteAllBuffers()<CR>
nnoremap <silent> <Leader>bd :DelBuf<Space>
I won't go into how Vimscript works. If you want to learn Vimscript, check out my Learn Vim book (free to read).
The main point is, I have a target-delete <Leader>bd
to delete a specific buffer(s) and mass-delete <Leader>bD
to delete all buffers except the current one.
After coding for a few hours, my buffer list would usually balloon and get convoluted. I'd need to clear them up.
If you run <Leader>bD
, it will indiscriminately delete all items in the buffer list (:ls
).
If you run <Leader>bd
, it will ask for a pattern. Vim will delete all buffers matching the pattern. So if you have users_controller.rb
, packages_controller.rb
, user_model.rb
, and package_model.rb
and you enter "controller" as pattern, it will delete the controller buffers and keep the model buffers.
Custom Functions to Open Url Under Cursor
function! OpenURLUnderCursor()
let l:uri = expand('<cWORD>')
silent exec "!open '" . l:uri . "'"
redraw!
endfunction
nnoremap gx :call OpenURLUnderCursor()<CR>
This one is actually a native feature of Vim (NETRW actually - :help netrw-gx
). By pressing gx
while your cursor is inside a URL, Vim will open that document. However, in Mac, that behavior doesn't work.
Custom Functions to Toggle Cursor Display
function! ToggleCursor()
if(&cursorcolumn == 1)
set nocursorcolumn
elseif (&cursorcolumn == 0)
set cursorcolumn
endif
if(&cursorline == 1)
set nocursorline
elseif (&cursorline == 0)
set cursorline
endif
endfunction
nnoremap <leader>tc :call ToggleCursor()<CR>
You can toggle the horizontal cursor highlight with :set cursorline
/ :set nocursorline
and the vertical cursor highlight with :set cursorcolumn
/ :set nocursorcolumn
. This can be useful if you need a makeshift straight edge.
Custom Functions to Toggle Numbers
function! ToggleNumber()
if(&relativenumber == 1)
set norelativenumber
else
set relativenumber
endif
endfunc
nnoremap <leader>tn :call ToggleNumber()<CR>
I like using relativenumber
to see how far apart a text is from the cursor. However, there are times when I need to see the absolute line number instead. This allows me to quickly toggle the number setting.
Custom Plugins
Finally, the custom-plugins/
section is where the settings for each plugin is.
Since there are so many of them, I will only cover one or two in this article. If you're curious, feel free to check out my dotfiles repository and see what else I have.
Inside the custom-plugins/
directory, I have files named after the plugin itself (you can name them anything you want).
For example, inside ale.vim
(ale is a linting engine for Vim):
" -------------------------
" Configs
" -------------------------
let g:ale_linters = {
\ 'javascript': ['eslint'],
\ 'ruby': ['rubocop'],
\}
let g:ale_fixers = {
\ 'javascript': ['eslint'],
\ 'ruby': ['rubocop'],
\}
let g:ale_linters_explicit = 1 " Only run linters named in ale_linters settings.
let g:ale_sign_column_always = 1
" -------------------------
" Keymaps
" -------------------------
nmap <silent> <C-k> <Plug>(ale_previous_wrap)
nmap <silent> <C-j> <Plug>(ale_next_wrap)
nnoremap <Leader>at :ALEToggle<CR>
Here, I have ale configured for Javascript and Ruby (using eslint and rubocop) - because I mainly work with Ruby and Javascript. Finally, I also have some keymaps related to the ale plugin.
Many of the plugins that I use don't need any configurations, like emmet-vim
, tcomment_vim
, vim-sandwich
, etc. You'll find that you don't need as many custom-plugin files as you have plugins installed.
What Is Next?
This concludes this article. I hope that you learned a thing or two. Make your vimrc uniquely yours. Keep improving everyday.
For max usage, I strongly recommend you reading this in conjunction with my dotfiles article for a complete guide on portability.
If you're still hungry for more, I suggest looking up other people's Vimrcs. Here are a few that I can think of.
Resources
Some great resources to learn and steal (cough) Vimrcs (and dotfiles):