Configuring (Neo)vim: A modular approach


We become what we behold. We shape our tools, and thereafter our tools shape us.

Marshall McLuhan

Couldn’t agree more to Marshall McLuhan’s words. When you delve deeper into programming and interacting with the shell becomes mundane, enhancing productivity in editing is an aspect which you cannot overlook. Before I began to cherish the fruits of transparency, freedom, and ownership that Linux(and Open Source) has to offer, I quickly found myself at a point where I had to choose my religion- Holy Church of Emacs or Cult of Vi. I don’t want to start a flame war by commenting on which one is better and why, but I eventually landed in the Vim as my Swiss army knife and living happily thereafter! At the end of 2017, I decided to move to neovim, reasons which are out of the scope of this post. After tinkering for a while, I arrived at a stable configuration and efficient way of organizing vimrc files. So if you are a novice to (Neo)vim space or consider yourself at an intermediate stage, this post will definitely be worth your time.

The Skeleton

After installing neovim using your preferred method, you will be in the need to customize init.vim file located in $XDG_CONFIG_HOME/nvim or ~/.config/nvim. Configuration files will soon start to get long and clunky, thereby posing difficulties in keeping track of things, debugging, etc. I constantly keep on adding/removing plugins, which leaves antiquated settings scattered in my vimrc. Therefore I came up with the following skeleton init.vim file(I use dein as my plugin manager, you could use your preferred one):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if &compatible
set nocompatible " Be iMproved
endif

" Required:
set runtimepath+=/path/to/dein.vim

" Required:
if dein#load_state('/path/to/dein')
call dein#begin('/path/to/dein')
call dein#add(...)
call dein#add(...)
" Required:
call dein#end()
call dein#save_state()
endif
"-------------------Load files-----------------
source ~/.config/nvim/base.vim
source ~/.config/nvim/mappings.vim
source ~/.config/nvim/plugins.vim

The last three lines, along with the order in which they appear are important. First, we source base.vim which holds configuration for vim, independent of any plugins. For example, you could set/unset variables, call autocmds on specific file_types, etc.

Next comes the mappings.vim file, as its name suggests, it holds custom mappings for neovim, again, independent of any plugins. These are mostly key bindings which you prefer and which suits your needs. Key bindings for custom commands can also be saved in this file, although I recommended against plugin specific commands.

The reason for these files being isolated from plugins is that you can take these files into any system, source them and feel(almost) at home without installing plugins on the target system. Especially useful if you frequently ssh into remote systems, but you want to keep the vim clean for others to use.

Next comes the plugins.vim file, which holds all configuration related to each and every plugin. This includes custom key mappings related to file_type plugins, declaring global variables, buffer-local variables, etc for every plugin. Each plugin configuration begins after the following line so that it is easier to locate and modify changes(you can customize this your way, just make sure it is visually differentiable):

1
"----------[github-plugin-name]--------

Another advantage of this segregation of configuration is that in case something goes wrong, you are now in a better position to debug, as you know the order of execution. Key binding not working as expected? quickly run :verbose nmap and the logs will show in the reverse order of our sourced files what happened when. Same goes for variables and other stuff. As a piece of advice, always comment out the settings of the plugin which you intend to uninstall. You never know when you will need them again 😉

Below, are some of my snippets for each file:

base.vim
1
2
3
4
5
6
syntax enable
set nu
set rnu
set smartcase
set incsearch
...
mappings.vim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
" Too lazy to reach the esc key
inoremap jk <esc>
" Set map-leader to space
let mapleader=" "

" Xmonad like navigation
nnoremap <C-J> <C-W><C-W>
nnoremap <C-K> <C-W>W
nnoremap <C-L> <C-W>>
nnoremap <C-H> <C-W><

" For terminal use
tnoremap <C-j><C-k> <C-\><C-N>
tnoremap <C-h> <C-\><C-N><C-w>h
tnoremap <C-j> <C-\><C-N><C-w>j
tnoremap <C-k> <C-\><C-N><C-w>k
tnoremap <C-l> <C-\><C-N><C-w>l
plugins.vim
1
2
3
4
5
6
7
8
9
10
" Colorscheme
colorscheme one
set background=light

" vim-one
let g:one_allow_italics = 1

" Startify
let g:startify_fortune_use_unicode = 1
...

You can check out the complete configuration files at dotfiles.

Installing plugins

Next comes the most important part, installing plugins. Default vim features are sufficient for majority use cases, but when it comes to programming, you need to install plugins to get IDE-like features such as autocompletion, goto definitions, documentation, etc. First, let’s start off with a completion engine as I’m sure most of you will feel numb without one!

Completion/Linting Engine:

For this example, I will be using deoplete for completion and ALE for linting. Also, install the specific language sources for the completor if any, or configure Language Server Protocol to work with it. Optionally, install a snippets plugin if you are into snippets like I do(here, neosnippet)

base.vim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
" Completion
call dein#add('Shougo/deoplete.nvim')
" Linting
call dein#add('w0rp/ale')
" Snippets
call dein#add('Shougo/neosnippet.vim')
call dein#add('Shougo/neosnippet-snippets')
call dein#add('honza/vim-snippets')
" For python
call dein#add('zchee/deoplete-jedi', {'on_ft': 'python'})
" For Rust
call dein#add('sebastianmarkow/deoplete-rust', {'on_ft': 'rust'})
" For C/C++
call dein#add('zchee/deoplete-clang')
...

Alternatives: ncm2(WIP), Neomake

Complementary Plugins:

I call these complementary because they enhance the productivity of our vanilla vim/neovim. It is a personal preference, some may blame them to be bloat while others may find it necessary. These include plugins which offer sane defaults, extra keybindings/motions/text objects, etc.

base.vim
1
2
3
4
5
6
7
8
9
10
...
" Damn you curly braces!
call dein#add('jiangmiao/auto-pairs')
" Turns keyboard into a gamepad
call dein#add('justinmk/vim-sneak')
" Why close vim to view file structure?
call dein#add('justinmk/vim-dirvish')
" Vim + Git = Magic
call dein#add('tpope/vim-fugitive', {'on_cmd': 'Gstatus', 'augroup': 'fugitive', 'on_source': 'gitv'})
...

Others: All tpope and Shougo plugins!

Language Specific:

Next, we move on towards configuring language specific plugins. It is impossible to cover all languages, but here, I will mention those programming languages which I use, at the time of writing this post. The purpose of this is to show, in general, how will you configure your vimrc/init.vim in a maintainable way as you learn new programming languages.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
" Python
call dein#add('davidhalter/jedi-vim', {'on_ft': 'python'})
" Rust
call dein#add('rust-lang/rust.vim', {'on_ft': 'rust'})
call dein#add('sebastianmarkow/deoplete-rust', {'on_ft': 'rust'})
call dein#add('mattn/webapi-vim', {'on_ft': 'rust'})
" Latex and General Writing
call dein#add('vim-pandoc/vim-pandoc', {'name': 'vim_pandoc', 'on_source': 'pandoc-after'})
call dein#add('dhruvasagar/vim-table-mode', {'on_cmd': 'TableModeToggle'})
call dein#add('lervag/vimtex', {'on_ft': ['plaintex', 'tex']})
call dein#add('vim-pandoc/vim-pandoc-syntax', {'depends':'vim_pandoc'})
call dein#add('vim-pandoc/vim-pandoc-after', {'depends': 'vim_pandoc'})
call dein#add('junegunn/goyo.vim', {'on_cmd': 'Goyo'})
call dein#add('ron89/thesaurus_query.vim',{'on_ft': ['pandoc', 'text']})
call dein#add('rhysd/vim-grammarous',{'on_ft':['pandoc', 'text']})
call dein#add('junegunn/limelight.vim', {'on_cmd': 'Limelight'})

Fuzzy Finder:

For me, vim is incomplete without a fuzzy finder, and I am not joking. Efficiency reaches to a next level when you have a decently configured fuzzy file finder. I had been a long time user of Ctrlp until I switched to fzf.vim. Couple it with ripgrep, it is the fastest finder setup in my opinion. It’s fast and snappier than most of the other fuzzy systems that I had given a spin.

1
2
call dein#add('junegunn/fzf', { 'build': './install --all', 'merged': 0 }) 
call dein#add('junegunn/fzf.vim', { 'depends': 'fzf' })

I have configured leaderkey bindings for various actions in fzf, which can be found in my dotfiles repository.

Eyecandy

Some people may prefer customizing the looks, whereas others prefer simple and clean. If you are into customizing the looks, airline is a good option. Lightline is also a very good alternative. So go ahead, and pick the one which you prefer. You can find some good vim color schemes at vimcolors.com. Be sure to check them out.

One notable plugin to mention is vim-diminactive, which greatly assists in differentiating between different splits. It’s a must-have if you find yourself wasting too much time to figure out which split has focus.

Handy Commands

These are some useful commands which you will find helpful for your daily use. To view additional information about a particular command, type :h [command].

  • ![command]: Execute shell command
  • messages: View the status/info/warn/error messages from statusbar.
  • cd/lcd/tcd: Change working directory/window local directory/tab local directory.
  • verbose: To find out what has happened during startup and exit.
  • namp/vmap/smap/imap/_etc: To view mappings for specified mode.
  • set [variable]?: Query the value of the variable, for example, ‘:set autochdir?’
  • echo has(_feature_): To check if a particular feature is enabled or not.
  • python [statement]: Execute the python statement and print it’s output in statusbar.
  • h vim_diff: List difference between vim and neovim(works only in neovim).
    and many others.

Hope this article was useful. Comments, suggestions and criticisms are welcomed.

ShareComments