How do I get improved % matching for language keywords like if/else/endif in Vim?
Answer
vim-matchup
Explanation
The vim-matchup plugin by Andy Massimino is a drop-in replacement for Vim's built-in matchit plugin that supercharges the % key to work with language-specific keyword pairs like if/else/endif, do/end, <div>/</div>, and many more. It also adds highlighting of matching pairs, text objects for matched groups, and motion commands for navigating between them.
The problem
Vim's built-in % only matches single characters: (, ), {, }, [, ]. The older matchit plugin extends this to language keywords but is slow, has limited highlighting, and lacks text objects. Vim-matchup replaces matchit entirely with a faster, more feature-rich implementation.
Enhanced % motion
With vim-matchup installed, % works on all of these:
if / elseif / else / endif (VimScript)
def / end (Ruby)
if / elif / else (Python — indentation-aware)
<div> / </div> (HTML/XML)
{ / } (C, Go, JavaScript, etc.)
do / end (Lua, Elixir)
#if / #elif / #else / #endif (C preprocessor)
begin / rescue / ensure / end (Ruby)
Pressing % cycles through all matching keywords in the group, not just between two endpoints. On an if, the first % goes to elseif, the next to else, the next to endif, and the next back to if.
Matching pair highlighting
Vim-matchup highlights the matching keyword pair when your cursor is on any member of the group. Place your cursor on else and both the corresponding if and else are highlighted, giving you instant visual context for which block you are inside.
Text objects
The plugin adds i% and a% text objects for operating on matched groups:
ci% " change inside the matched pair (e.g., between if and endif)
da% " delete around the matched pair (including the keywords)
vi% " visually select inside the matched pair
ya% " yank around the matched pair
This is incredibly powerful for languages like Ruby, Lua, and VimScript where blocks are delimited by keywords rather than braces.
Navigation commands
Beyond %, the plugin provides additional motions:
]% " go to the next outer closing keyword
[% " go to the previous outer opening keyword
z% " go to the nearest match (inside or outside)
g% " go to the previous matching keyword (reverse of %)
Off-screen match display
When the matching keyword is off-screen, vim-matchup shows it in the statusline or a popup window so you always know what block you are inside without scrolling. This is a huge quality-of-life improvement when editing deeply nested code.
let g:matchup_matchparen_offscreen = {'method': 'popup'}
Recommended configuration
" Enable deferred highlighting for better performance in large files
let g:matchup_matchparen_deferred = 1
" Show the matching pair in a popup when it's off-screen
let g:matchup_matchparen_offscreen = {'method': 'popup'}
" Disable matchit since matchup replaces it completely
let g:loaded_matchit = 1
Tips
- Vim-matchup works with treesitter in Neovim for even more accurate matching — install the treesitter integration module for your language
- The
i%text object respects the nesting of matched groups, soci%inside a nestedifonly changes the inner block - Works seamlessly with vim-surround:
ds%deletes the enclosing matched keywords - Highlighting performance is optimized with deferred matching — it only highlights after the cursor stops moving, so it never causes lag while scrolling
- Supports custom match words per filetype via
b:match_words(same format as matchit, so existing configurations carry over)