Consider the text:


First we highlight the regular expression /foobar/:

:syntax match Special /foobar/

enter image description here

Next we highlight the regular expression /foobarbaz/:

:syntax match Error /foobarbaz/

This match begins in the same position as our Special group, but it was defined later, and so it takes precedence. (See :help :syn-priority.)

enter image description here

Now let's clear that (:syntax clear Error), and instead try starting our highlighting at bar by using \zs to set the start of the match.

:syntax match Error /foo\zsbarbaz/

enter image description here

Okay, that didn't work, but perhaps it's because by altering the start of the match, this match now begins inside our earlier group. If that's the case, we can fix it with the containedin argument:

:syntax match Error /foo\zsbarbaz/ containedin=Special

enter image description here

That seems to have worked!

Now let's try starting our highlighting from the baz:

:syntax match Error /foobar\zsbaz/ containedin=Special

enter image description here

Huh. Now that we've moved the start of our Error match past the end of the Special match, it no longer works. (Note that I'm not using the contained argument, so even if the Error group starts outside of the Special group, it should still match: removing the containedin argument also has no effect.)

The sole mention of \zs in syntax.txt doesn't appear to be relevant (it regards multi-line patterns), and :help \zs also doesn't shed any light on this.

What's going on?

N.B. For this particular example, I could use an hs syntax pattern offset to achieve the desired highlighting, and with more complicated regular expressions I could use \@<= look-behind. But \zs is recommended over \@<= for performance reasons, and in any case, I'd still like to understand how the feature works.

  • 2
    Were you ever able to find anything else out about this? This has me very intrigued.
    – ZeroKnight
    Mar 25, 2018 at 11:07
  • 3
    @SakariCajanus 'containedin' allows an item to start inside another group; it does not require it. If there is no overlap, then the last example should work regardless of whether or not 'containedin' is set.
    – Rich
    Nov 13, 2018 at 11:07
  • 1
    I can't find the ref now, but I remember discovering that this was because \zs still "consumes" (from the point-of-view of the regex engine) the input prior to matching, while lookahead/behinds do not
    – D. Ben Knoble
    May 13, 2020 at 15:45
  • 1
    @D.BenKnoble Hmm. Can you be any more specific about what you mean by “consumed”? How does this explain the difference between the behaviours of the last two regular expressions in the question?
    – Rich
    May 13, 2020 at 19:16
  • 1
    When the engine “consumes” the text, it doesnt go back and look at it again—even if other things might match there. Its like the text is part of the match, even though we used \zs. I may not be explaining this well, and I havent read your q in detail
    – D. Ben Knoble
    May 13, 2020 at 20:50


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.