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.
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.
visual-star-search
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:
-
ciw
- change inside word -
yi)
- yank inside parenthesis -
vat
- visually select around tag -
di"
- delete inside double quotes
The Targets.vim plugin provides additional text object separators such as:
*
, |
, =
, and _
to name a few.
Examples of those separators:
-
ci*
- change inside star -
va|
- visually select around pipe -
ci_
- change inside underscore
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:
-
vii
- visually select inside code block using current indentation -
vaI
- visually select around code block using current indentation AND include trailing line (for example theend
delimiter in Ruby)
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>=
-
The
<Leader>d
mapping toggles a left-hand side project drawer whilst also equalizing existing splits -
The
<Leader>f
mapping opens fern and reveals the current buffer in the project tree -
The
<Leader>.
opens just the directory of sibling files of the current buffer
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
-
Enter and double-click open and collapse directories, or opens files. Note, if multiple splits exist fern will prompt the user asking which split to open the chosen file into thus avoiding the oil and vinegar problem.
-
m
marks entries for bulk operation such as deletion or opening -
N
andK
create new files and directories respectively -
D
removes content -
C
moves the content under the cursor, think ofC
for change -
R
renames content, including bulk renames for marked content -
s
andv
will open files in either horizontal or vertical splits -
r
reloads the current tree, use this to update fern with filesystem changes that occur outside Vim -
d
toggles dot files -
<
and>
will change up and down the directory hierarchy.
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
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:
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:
-
Square-brackets
g
navigates between hunks -
<Leader>p
displays the current hunk as a diff in the preview window -
<Leader>+
stages the curent hunk -
<Leader>-
reverts the curent hunk back toHEAD
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.
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:
-
[q
/]q
- navigate up and down through the quickfix list, for instance through vim-grepper results -
[l
/]l
- navigate up and down through the location list, for instance through ALE results -
[a
/]a
- navigate backward and forward through the file list -
[b
/]b
- navigate backward and forward through the buffer list -
[<Space>
/]<Space>
- add a blank line above or below the current line -
[e
/]e
- move the current line, or visual lines, up or down -
[p
/]p
- linewise paste above or below the current line
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.