May 21, 2017

Vim Plugins I Like

UPDATED SEPTEMBER 2021

Vim gains much functionality through the inclusion of plugins.

This post contains a curated set of some my favourite Vim plugins. I fully acknowledge that there are numerous useful plugins beyond those discussed here, hence, I do encourage Vim users to explore, test and discuss those plugins they appreciate.

The full set of plugins and mappings I use are available in my dotfiles repository.

vim-plug

The first decision a Vim user, looking to enter the plugin world, must decide is which plugin manager to use. There are many to choose from: Vundle, Pathogen, vim-plug, Dein and Vim 8 native package management to name a few.

Each will do the job, but for simplicity, performance and features the vim-plug plugin manager is the one I use.

All snippets for the remainder of this post will use vim-plug notation.

vim-moonfly-colors

Plug 'bluz71/vim-moonfly-colors'

Self-advertising, I have created my own Vim colorscheme named moonfly.

The moonfly color scheme is a dark theme, but unlike all other dark themes this one is my dark theme. That means it has been tuned to my particular tastes. Whether those tastes match up with anyone else’s taste will be in the eye of the beholder.

vim-nightfly-guicolors

Plug 'bluz71/vim-nightfly-guicolors'

More recently, I have created another Vim colorscheme named nightfly.

The nightfly color scheme is also a dark theme, but unlike the charcoal-colored moonfly color scheme this one skews slightly toward blue. The nightfly color scheme is heavily inspired by the Visual Studio Code Night Owl theme.

vim-moonfly-statusline

Plug 'bluz71/vim-moonfly-statusline'

I am not a fan of heavy statusline plugins like powerline and airline.

Instead, I have created the simple, yet informative, moonfly-statusline plugin which by default uses the moonfly color palette. If the colors do not suit then they can easily be customized if desired.

Note, the newer nightfly color scheme will automatically adapt to moonfly-statusline.

:bulb: With let g:moonflyIgnoreDefaultColors = 1 set, it is quite simple to define a custom status color scheme for moonfly-statusline. For example this simple theme should work well with most existing Vim color schemes:

let g:moonflyIgnoreDefaultColors = 1

highlight! link User1 DiffText
highlight! link User2 DiffAdd
highlight! link User3 Search
highlight! link User4 IncSearch
highlight! link User5 StatusLine
highlight! link User6 StatusLine
highlight! link User7 StatusLine

indentLine

Plug 'Yggdroot/indentLine'
let g:indentLine_char       = '▏'
let g:indentLine_setConceal = 0

The indentLine plugin is used to display indentation guide markers as often seen in Sublime and Atom editors. This is a simple and useful visual aid, though indentLine is not quite as slick-looking as the guide markers in Sublime and Atom.

Plug 'nelstrom/vim-visual-star-search'

The vim-visual-star-search plugin allows * and # searches to occur on the current visual selection.

targets.vim

Plug 'wellle/targets.vim'

The Targets.vim plugin provides additional text objects. The highest compliment I can provide a plugin is to say that it feels like a natural part of Vim itself, this tremendous plugin exhibits that characteristic.

Vim’s text objects allow for easy selection and operation on regions of text.

Common text object operations, provided by default with Vim, include:

The Targets.vim plugin provides additional text object separators such as: *, |, =, and _ to name a few.

Examples of those separators:

The *-based text object is handy for changing emphasized text in Mardown files whilst _-based text objects are useful for language that use snake_case such as Ruby and Elixir.

I also appreciate the comma smarts this plugin provides.

For example, given this text with the cursor positioned inside second:

foobar(first, second, third)

The operation da, will delete the comma before second but not the one trailing it. Most of the time this is the preferred result when dealing with source code.

I highly recommend this excellent plugin to all Vim users.

clever-f

Plug 'rhysd/clever-f.vim'
let g:clever_f_across_no_line    = 1
let g:clever_f_fix_key_direction = 1

The clever-f plugin makes f, F, t and T movements more informative and convenient.

The more informative aspect is achieved by clever-f highlighting all the matches for the chosen movement.

The more convenient aspect is achieved by simply using the f and F characters to navigate forward and backward through the matches unlike Vim’s inconvenient and hard to remember defaults of ; and ,. In my case I map the leader key to , and I map ; as a duplicate of :, hence those repeat characters are not even available.

vim-lion

Plug 'tommcdo/vim-lion'
let g:lion_squeeze_spaces = 1

The vim-lion plugin is used to align text around a chosen character.

I find it easiest to select a visual region and then invoke gl<character> to re-align text around a chosen character (which will often be equals).

For example, gl= will convert this:

i = 5;
username = 'tommcdo';
stuff = [1, 2, 3];

into this:

i        = 5;
username = 'tommcdo';
stuff    = [1, 2, 3];

The alternative vim-easy-align and tabular plugins can also align text.

vim-indent-object

Plug 'michaeljsmith/vim-indent-object'

The vim-indent-object plugin adds yet another text object to Vim, this one based on the indentation of the current cursor line. This new text object is invoked by either i or I. Some examples:

These indent based text objects are handy because they are language agnostic, they work just as well for Python code as they do for JavaScript code.

vim-wordmotion

Plug 'chaoren/vim-wordmotion'
nmap cw ce
nmap cW cE

The wordmotion plugin expands Vim’s definition of a word. This plugin will take into account programming-related camel and snake-case (and other unusal word definitions) and allow navigation, using w and b, within such words.

This plugin will not suit everyone, but for certain language, such as Ruby which uses both camel and snake-case, it has proven invaluable in practice.

Note, the cw / cW mapping will restore standard Vim behaviour, that being to preserve whitespace between words.

fzf.vim

Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
Plug 'junegunn/fzf.vim'

The fzf.vim plugin is a performant fuzzy finder.

Please refer to fuzzy finding in Vim with fzf for comprehensive details about fzf and the fzf.vim plugin.

fern.vim

Plug 'lambdalisue/fern.vim'
let g:fern#disable_default_mappings   = 1
let g:fern#disable_drawer_auto_quit   = 1
let g:fern#disable_viewer_hide_cursor = 1

The fern plugin is a simple, yet powerful, file explorer.

Historically, NERDTree has been the go-to file explorer for Vim; however, in my experience NERDTree does have certain performance issues that can make it frustrating to use. In contrast, fern is a modern asynchronous file explorer that has excellent performance.

Candidate fern launch mappings:

noremap <silent> <Leader>d :Fern . -drawer -width=35 -toggle<CR><C-w>=
noremap <silent> <Leader>f :Fern . -drawer -reveal=% -width=35<CR><C-w>=
noremap <silent> <Leader>. :Fern %:h -drawer -width=35<CR><C-w>=

Candidate fern operation mappings:

function! FernInit() abort
  nmap <buffer><expr>
        \ <Plug>(fern-my-open-expand-collapse)
        \ fern#smart#leaf(
        \   "\<Plug>(fern-action-open:select)",
        \   "\<Plug>(fern-action-expand)",
        \   "\<Plug>(fern-action-collapse)",
        \ )
  nmap <buffer> <CR> <Plug>(fern-my-open-expand-collapse)
  nmap <buffer> <2-LeftMouse> <Plug>(fern-my-open-expand-collapse)
  nmap <buffer> m <Plug>(fern-action-mark:toggle)j
  nmap <buffer> N <Plug>(fern-action-new-file)
  nmap <buffer> K <Plug>(fern-action-new-dir)
  nmap <buffer> D <Plug>(fern-action-remove)
  nmap <buffer> C <Plug>(fern-action-move)
  nmap <buffer> R <Plug>(fern-action-rename)
  nmap <buffer> s <Plug>(fern-action-open:split)
  nmap <buffer> v <Plug>(fern-action-open:vsplit)
  nmap <buffer> r <Plug>(fern-action-reload)
  nmap <buffer> <nowait> d <Plug>(fern-action-hidden:toggle)
  nmap <buffer> <nowait> < <Plug>(fern-action-leave)
  nmap <buffer> <nowait> > <Plug>(fern-action-enter)
endfunction

augroup FernEvents
  autocmd!
  autocmd FileType fern call FernInit()
augroup END

Candidate style settings for those who like a NERDTree-like presentation:

let g:fern#mark_symbol                       = '●'
let g:fern#renderer#default#collapsed_symbol = '▷ '
let g:fern#renderer#default#expanded_symbol  = '▼ '
let g:fern#renderer#default#leading          = ' '
let g:fern#renderer#default#leaf_symbol      = ' '
let g:fern#renderer#default#root_symbol      = '~ '

Lastly, if one prefers to automatically update the current tree upon entering the fern window then please add the following to the above FernEvents function (not the FernGroup):

augroup FernTypeGroup
    autocmd! * <buffer>
    autocmd BufEnter <buffer> silent execute "normal \<Plug>(fern-action-reload)"
augroup END

:exclamation: Some Vim elitists consider file explorers an anti-pattern. I primarily use fern as a project tree visualizer and occasional file manager. I do not recommend you use fern as your prime method to open files, alternatives such as: fzf, projectionist and the standard gf command are better and faster.

fern-git-status

Plug 'lambdalisue/fern-git-status.vim'
let g:fern_git_status#disable_ignored    = 1
let g:fern_git_status#disable_untracked  = 1
let g:fern_git_status#disable_submodules = 1

The fern-git-status plugin augments the above documented fern plugin with Git status badges when in a Git working tree.

A screenshot of fern-git-status in action:

fern-git-status

vim-gitgutter

Plug 'airblade/vim-gitgutter'
let g:gitgutter_grep                    = 'rg'
let g:gitgutter_map_keys                = 0
let g:gitgutter_sign_added              = '▎'
let g:gitgutter_sign_modified           = '▎'
let g:gitgutter_sign_modified_removed   = '▌'
let g:gitgutter_sign_removed            = '▎'
let g:gitgutter_sign_removed_first_line = '▎'
nmap [g <Plug>GitGutterPrevHunkzz
nmap ]g <Plug>GitGutterNextHunkzz
nmap <Leader>p <Plug>GitGutterPreviewHunk
nmap <Leader>+ <Plug>GitGutterStageHunk
nmap <Leader>- <Plug>GitGutterUndoHunk

The vim-gitgutter plugin highlights Git repository modifications via the signs column whilst also providing functionality to navigate, preview, stage and undo those modified Git chunks, aka hunks.

The speed with which signs appear and change is governed by Vim’s updatetime option. I suggest setting it to 100ms:

set updatetime=100

Candidate mappings:

This plugin shines when dealing with modified Git chunks; that being easy navigation and staging of those hunks.

Code Completion with VimCompletesMe and LSP-based LSC

Plug 'ajh17/VimCompletesMe'
Plug 'natebosch/vim-lsc'

The VimCompletesMe plugin uses the TAB character, whilst in insert mode, to carry out completions using Vim’s various built-in completions. The plugin itself usually determines the appropriate type of completion, be it keyword, file or omni completion, based on the current context.

Where language-aware intelligence is required the LSC plugin with appropriate LSP-compliant language servers is recommended. Please refer to the LSP in Vim with the LSC Plugin post for details about the Language Server Protocol (LSP) and the LSC plugin.

ALE

Plug 'w0rp/ale'
let g:ale_fixers = {
\  'css':        ['prettier'],
\  'javascript': ['prettier-standard'],
\  'json':       ['prettier'],
\  'ruby':       ['standardrb'],
\  'scss':       ['prettier'],
\  'yml':        ['prettier']
\}
let g:ale_linters = {
\  'css':        ['csslint'],
\  'javascript': ['standard'],
\  'json':       ['jsonlint'],
\  'ruby':       ['standardrb'],
\  'scss':       ['sasslint'],
\  'yaml':       ['yamllint']
\}
let g:ale_linters_explicit = 1
let g:ale_open_list        = 0

The ALE plugin is used to asynchronously run language linters and fixers within modern versions of Vim.

Note, a fixer can also formats code, hence there is no need to install code formatting plugins such as vim-prettier when using ALE.

ALE ships with configurations for most common languages, such as JavaScript and Ruby to name a few, hence little configuration is required. Note, the tools that ALE uses, such as eslint or standard, will need to be installed on the host, the ALE plugin will not install the underlying lint or fix tools.

One could define mappings for linting and for fixing, and run them manually, others however prefer to have ALE fix and lint code is it is being written. Candidate ALE settings:

let g:ale_fix_on_save              = 1
let g:ale_lint_on_enter            = 0
let g:ale_lint_on_filetype_changed = 0
let g:ale_lint_on_insert_leave     = 0
let g:ale_lint_on_save             = 1
let g:ale_lint_on_text_changed     = 'never'

Please refer to ALE help and setup per your preference.

Note, use the :ALEInfo command to display runtime information per the current file type, use it when you need to debug any ALE issues.

vim-vsnip

Plug 'hrsh7th/vim-vsnip'
imap <expr> <C-j> vsnip#available(1) ? "<Plug>(vsnip-expand-or-jump)" : "<C-j>"
imap <expr> <C-k> vsnip#jumpable(-1) ? "<Plug>(vsnip-jump-prev)"      : "<C-k>"

The vim-vsnip plugin allows easy insertion of predefined text segments, named snippets, in the current buffer.

The following Vimcasts are an excellent introduction to snippets, in this case via the older, but similar, UltiSnips plugin, please view:

The legacy UltiSnips plugin uses a different in snippet syntax compared with vim-vsnip, but the core concept of snippets are common to both plugins.

So why choose vim-vsnip in preference to UltiSnips? In my case it is mainly due to the Python dependency of the UltiSnips plugin. Python-based Vim plugins are fragile to system level Python updates. vim-vsnip on the otherhand is a pure VimScript plugin and is not prone to such breakages.

By default, I do not recommend the use of the TAB character to expand or navigate a snippet since that will conflict with the VimCompletesMe plugin. Instead, I recommend the use of Control-j, as defined above, to expand and then navigate forward through a snippet’s tabstops and Control-k to navigate back up through the tabstops.

The small set of snippets I use are declared here.

:gift: I also define the following <Control-s> insert mode mapping to list available snippets in the completion menu, this is useful for the times I can’t recall the name of a seldom used snippet:

inoremap <silent> <C-s> <C-r>=SnippetsComplete()<CR>

function! SnippetsComplete() abort
    let wordToComplete = matchstr(strpart(getline('.'), 0, col('.') - 1), '\S\+$')
    let fromWhere      = col('.') - len(wordToComplete)
    let containWord    = "stridx(v:val.word, wordToComplete)>=0"
    let candidates     = vsnip#get_complete_items(bufnr("%"))
    let matches        = map(filter(candidates, containWord),
                \  "{
                \      'word': v:val.word,
                \      'menu': v:val.kind,
                \      'dup' : 1,
                \   }")


    if !empty(matches)
        call complete(fromWhere, matches)
    endif

    return ""
endfunction

vim-test

Plug 'janko-m/vim-test'
nnoremap <silent> <Leader>tt :TestNearest<CR>
nnoremap <silent> <Leader>tf :TestFile<CR>
nnoremap <silent> <Leader>ts :TestSuite<CR>
nnoremap <silent> <Leader>tl :TestLast<CR>
if has("nvim")
    let test#strategy = "neovim"
else
    let test#strategy = "vimterminal"
endif

The vim-test plugin provides a universal interface to various back-end testing frameworks. This plugin allows one to agnostically run tests for different languages and their associated testing frameworks.

Since we are using modern versions of Vim, let’s use the terminal capabilities provided to run tests in a split terminal window.

Note, the following configuration is required to use vim-test with Create React App projects:

let test#javascript#jest#executable = 'CI=true yarn test --colors'

Undotree

Plug 'mbbill/undotree'
let g:undotree_HighlightChangedWithSign = 0
let g:undotree_WindowLayout             = 4
let g:undotree_SetFocusWhenToggle       = 1
nnoremap <Leader>u :UndotreeToggle<CR>

The Undotree plugin visualizes your undo history and provides easy navigation back and forth through that history. This plugin proves handy for certain kinds of non-linear edits that may prove unreachable via the normal u undo command.

vim-auto-save

Plug '907th/vim-auto-save'
let g:auto_save        = 1
let g:auto_save_silent = 1
let g:auto_save_events = ["InsertLeave", "TextChanged", "FocusLost"]

The vim-auto-save plugin automatically saves changes to disk without required manual :w invocations. I prefer to automatically save after: normal mode changes (TextChanged), exiting insert mode (InsertLeave) and when focussing away from Vim (FocusLost).

Cheat40

Plug 'lifepillar/vim-cheat40'

The Cheat40 plugin is a convenient, and customizable, 40 character wide cheat sheet. Cheat40 can display a cheat sheet of your key mappings or exotic Vim commands for instant recall. For example, if you keep forgetting seldom used mappings, just add them to your cheat sheet and then display the cheat sheet when required.

Use the <leader>? mapping, or bind your own key to :Cheat40, to toggle the right-hand side full height cheat sheet. Note, sections will usually be folded to avoid needless scrolling; just go to the section of interest and unfold it.

The Cheat40 plugin provides a default cheat sheet, however, I strongly recommend creating your own cheat sheet.

To disable the default cheat sheet provided by the plugin:

let g:cheat40_use_default = 0

Your cheat sheet should reside at ~/.vim/cheat40.txt.

As an example, here is my cheat sheet. Note, formatting is important, please adhere to the 40 character column layout.

If you find yourself often looking up your ~/.vimrc file for a particular mapping, please instead take the time to craft a cheat sheet; that will involve a little bit of upfront work, but the payoff will be worth it in the long run.

Tim Pope Plugins

A special mention should be given to Tim Pope who has crafted some of Vim’s most useful plugins. He deserves a place in the Vim hall of fame alongside Bram Moolenaar himself.

Commentry

Plug 'tpope/vim-commentary'

The vim-commentary plugin is a simple language agnostic commenter. I usually use it with a visual line selection to comment out or uncomment out a block of code with the gc command the plugin provides.

No need to remember what the comment characters are for a certain language, is it // or # or , just gc it.

Surround

Plug 'tpope/vim-surround'

The vim-surround plugin allows one to add, change or delete surrounding pairs.

What is a surrounding pair? It may be the quote characters or <div> tags or anything else that surrounds some text.

To delete a surrounding pair use d. Here are some examples, the first example will delete double quotes, the second will delete a tag (like <div>) and the third will delete * :

ds"
dst
ds*

To change a surrounding pair use c. Note, you must provide the old and new surround:

cs'"
cs*<div>

To add a surround pair one can visually select the candidate text and enter S followed by the surround character(s) of choice.

This plugin is a little harder to explain than it is to use, however once you get it you can’t imagine life without it.

Repeat

Plug 'tpope/vim-repeat'

The vim-repeat enhances the . operator to work as one would expect with a number of Vim plugins, most notably the vim-surround plugin noted above.

Unimpaired

Plug 'tpope/vim-unimpaired'

The vim-unimpaired plugin provides a set of mappings for many operations that have natural pairings. A pairing may be: up and down, or forward and backward, set or unset or above and below.

Of the mappings provided by this plugin these are the mappings I use most often:

The full set of mappings is documented here.

The vim-unimpaired plugin negates the need to provide your own custom set of mappings for these types of operation.

Projectionist

The vim-projectionist plugin, primarily, provides infrastructure to navigate projects. This plugin is effectively the core of the vim-rails plugin extracted into a standalone plugin.

Here is a simple configuration for create-react-app projects:

Plug 'tpope/vim-projectionist'
if filereadable('src/App.js')
    " This looks like a React app.
    let g:projectionist_heuristics = {
    \  'src/App.js': {
    \    'src/components/*.js': {
    \      'type': 'component',
    \      'alternate': 'src/__tests__/components/{}.test.js'
    \    },
    \    'src/__tests__/components/*.test.js': {
    \      'type': 'test',
    \      'alternate': 'src/components/{}.js'
    \    },
    \    'src/styles/*.css': {
    \      'type': 'stylesheet',
    \      'alternate': 'src/components/{}.js'
    \    }
    \  }
    \}
    nnoremap <Leader>ec :Ecomponent<Space>
    nnoremap <Leader>es :Estylesheet<Space>
    nnoremap <leader>et :Etest<Space>
    nnoremap <Leader>a  :A<CR>
endif

The above configuration will result in the following commands being created: Ecomponent, Estylesheet and Etest. Those commands are then mapped for quick access. Hence, <Leader>ec <TAB> will list all available components, in the status line if wildmenu and wildmode are set appropriately, allowing a developer to quickly go to the component they wish. The <Leader>a mapping provides quick switching to an alternate file, which will usually be the associated test suite for the current component file.

Set up does require upfront work, but once done, you will really appreciate the navigation capabilities this plugin provides.