vimtricks.wiki Concise Vim tricks, one at a time.

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: match text only when immediately preceded by pattern
  • \(pattern\)\@=text — positive lookahead: match text only when immediately followed by pattern (applied before consuming text)
  • \(pattern\)\@<!text — negative lookbehind: match text only when NOT preceded by pattern
  • \(pattern\)\@!text — negative lookahead: match text only when NOT followed by pattern

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/\ze which 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 between foo and bar
  • Very magic equivalent: /\v(foo)@<=\w+(bar)@=/

Next

How do I open just enough folds to see the current line without expanding everything?