What Is Inside My Vimrc

September 16, 2021

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 functions
  • custom-plugins/ where I store configs specific for Vim plugins
  • main/ 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:

  1. settings.vim
  2. keymaps.vim
  3. 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):