How do I match a pattern only when preceded or followed by another pattern using zero-width assertions?
Answer
\@= and \@<=
Explanation
Vim's regex engine supports zero-width lookahead and lookbehind assertions — \@= and \@<= — which let you match text based on surrounding context without including that context in the match. This is one of Vim's most powerful and underused regex features.
How it works
The assertion atoms are placed after the pattern they test, inside a group:
\(pattern\)\@<=text— positive lookbehind: matchtextonly when immediately preceded bypattern\(pattern\)\@=text— positive lookahead: matchtextonly when immediately followed bypattern(applied before consumingtext)\(pattern\)\@<!text— negative lookbehind: matchtextonly when NOT preceded bypattern\(pattern\)\@!text— negative lookahead: matchtextonly when NOT followed bypattern
In \v (very magic) mode, backslashes are dropped: (pattern)@<=text.
Example
Rename all function names after the def keyword in Python, leaving def itself untouched:
:%s/\(def \)\@<=\w\+/renamed/g
Given:
def calculate(x):
def render(x):
After substitution:
def renamed(x):
def renamed(x):
The \(def \)\@<= lookbehind requires def to precede the match, but def is not consumed or modified.
Tips
- Unlike
\zs/\zewhich shift the match boundaries,\@<=and\@=are true zero-width assertions that test a condition without consuming text - Vim's lookbehind (
\@<=) supports variable-length patterns, unlike many other regex engines that require fixed-width lookbehinds - Combine both:
\(foo\)\@<=\w\+\( bar\)\@=matches a word betweenfooandbar - Very magic equivalent:
/\v(foo)@<=\w+(bar)@=/