2

The string that I have is this aaabbbb. aaa must become red, bbbb must become blue.

I tried this:

" here xB is not matched
syntax match xA /^a\+/
syntax match xB /^a\+\zsb\+/
highlight xA ctermfg=red
highlight xB ctermfg=blue

" here xB is matched
syntax match xB /^a\+\zsb\+/
highlight xB ctermfg=blue

But it fails. 1st one only matches aaa into red, and misses bbbb.

The 2nd one matches bbbb into blue, but obviously leaves aaa uncolored.

Any idea what's going on?

3 Answers 3

3

This won't work because Vim won't scan the same text twice trying to match a different regex.

Even though the \zs makes the resulting match only include the bs, Vim needs to see all the as to be able to match that regex and it won't do so if it already matched the as to the other pattern.

You can do a small experiment to see this behavior, by doing a simple search for the two regexes:

/^a\+\|^a\+\zsb\+

You'll see that the search will match the as, but not the bs.

(On the other hand, replacing the a in the first regex with a different character matches both lines starting with that other character and lines with the bs following the as, which proves the regex otherwise works as expected.)

To work around this issue, use a zero-width lookup match, using \@<= to match the series of as at the start of the line preceding the bs.

You can use this match instead:

syntax match xB /\(^a\+\)\@<=b\+/

You'll notice this pattern also works correctly in the search experiment:

/^a\+\|\(^a\+\)\@<=b\+

The reason why this works is that Vim will first search for bs and then look behind to see if that other part of the regex also matches. So the pattern gets anchored on b rather than a and doesn't suffer from the issue of trying to start a match on a region that already matched another expression.

Please note that zero-width lookups such as this one can have performance side effects, if the regex is too generic Vim might end up going pretty far trying to look for the start of a match. See :help /\@<= for more details.

1
  • 1
    Thanks, that directly nails exactly at the center of where my issue was.
    – caveman
    Commented Jan 5, 2020 at 14:17
3

I ran into this once.

The short answer is even though \zs causes the match to start later, it « consumes » the as before it.

The workaround is to use lookahead/behind, I believe. I’m not a master of vim’s syntax for that though; see :help pattern.

I think you just need /\(^a\+\)\@<=b\+/

3
  • Nitpick: you're missing the ^ on the lookbehind.
    – filbranden
    Commented Jan 5, 2020 at 5:48
  • Should there also be \(...\)? I.e. \(^a\+\)\@<=?
    – caveman
    Commented Jan 5, 2020 at 14:22
  • @caveman yeah probably
    – D. Ben Knoble
    Commented Jan 5, 2020 at 15:13
3

There's an alternative and arguably cleaner method of achieving your goal.

By using Vim's syntax features to make xA a group that can only be found within xB, you can remove your \zs entirely, resulting in much simpler regular expressions:

syntax match xA /^a\+/ containedin=xB contained
syntax match xB /^a\+b\+/

containedin=XB means that xA can be found within xB, and contained means that it cannot be found anywhere else (or more precisely, at the top level).

It's a little unclear from your question, but if you want aaa to be red even when it's not followed by bbbb, you can simplify even further:

syntax match xA /^a\+/ nextgroup=xB
syntax match xB /b\+/ contained

nextgroup=xB means that after a match for xA, Vim will check to see if xB immediately follows. As in the previous solution, contained in the xB definition means that strings of b characters elsewhere in the file will not be matched.

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.