TLDR
I have this text in a buffer:
a 'string' and 'another one' !
^
cursor is here
And I search for this regex in a /
command:
\%(.*\%#\)\@=\%('[^']*'\)\@>\%(\%#.\+\)\@<=
There is no match.
Now I remove \@>
from the regex:
\%(.*\%#\)\@='[^']*'\%(\%#.\+\)\@<=
And search for it again. This time ' and '
is matched.
What did \@>
do exactly in the first regex, which explains why ' and '
was not matched?
Additional information
Suppose I'm looking for a regex to match the single-quoted string under the cursor.
And I use this text to test the regex:
a 'string' and 'another one' !
I don't want any match while the cursor is positioned on any of these characters:
a 'string' and 'another one' !
^^ ^^^^^ ^^
I want 'string'
to be the match when the cursor is on any of these characters:
a 'string' and 'another one' !
^^^^^^^^
And I want 'another one'
to be the match when the cursor is on any of these characters:
a 'string' and 'another one' !
^^^^^^^^^^^^^
Here's one possible regex:
\%(.*\%#\)\@=\%('[^']*'\)\@>\%(\%#.\+\)\@<=
├───────────┘ ├─────┘ ├─────────────┘
│ │ └ the cursor must be before the closing quote
│ └ a quoted string
└ the cursor must be after the opening quote
I understand why it works when the cursor is inside a string, but I don't understand why it works when the cursor is outside. For example, assuming the cursor is on the n
of and
:
a 'string' and 'another one' !
^
the regex fails to match anything, which is what I want.
This is due to the atom \@>
– which is inspired from the atomic group (?>pattern)
in perl – because if I remove it, the regex becomes:
\%(.*\%#\)\@='[^']*'\%(\%#.\+\)\@<=
and when the cursor is on the n
of and
, this new regex matches ' and '
:
a 'string' and 'another one' !
^^^^^^^
If you think this last regex matches 'string'
instead of ' and '
, because that's the text which is highlighted by the Search
highlight group, run this command:
:s///c
When the substitution command is asking for your confirmation before replacing the pattern, you should see that the text highlighted by the IncSearch
highlight group is ' and '
, not 'string'
.
I know what the help says about \@>
. It prevents the regex engine from backtracking.
For example, let's assume you want to match all sequences of several uppercase characters not followed by a comma, nor by another uppercase character; and you test your regex against this text:
ABC,
DEF
GHI,
JKL
If you try this regex:
\u\{2,},\@!
It correctly matches DEF
and JKL
, but it also wrongly matches AB
and GH
.
This is because when the regex engine processed the token \u\{2,}
, it initially matched ABC
and GHI
(\{2,}
is greedy so it matches as many uppercase characters as it can); but then it realized that there was a comma, and that the negative lookahead assertion ,\@!
could not be satisfied. So it backtracked into \u\{2,}
again, and this time was a little less greedy; it matched only AB
and GH
. Since there is no comma after AB
and GH
, the negative lookahead assertion can be satisfied and there is a match.
Now if you apply \@>
to \u\{2,}
, then you prevent the regex engine from backtracking into \u\{2,}
. That is, once the regex engine finds some text matching \u\{2,}
, it never comes back into it again; even if some later part of the regex fails. This is why the following regex correctly finds DEF
, JKL
, and nothing else:
\%(\u\{2,}\)\@>,\@!
^^^ ^^^^^
Going back to the original text:
a 'string' and 'another one' !
^
cursor is here
The regex:
\%(.*\%#\)\@=\%('[^']*'\)\@>\%(\%#.\+\)\@<=
finds nothing, because of \@>
.
But how does preventing the regex engine from backtracking into '[^']*'
also prevents the ' and '
match?
The core of the regex is '[^']*'
– two consecutive quotes with a minimum amount of text in-between – for which there are 3 possible matches:
'string'
' and '
'another one'
I understand why the first match fails; it's because of the negative lookbehind, which asserts that the cursor is positioned before the last quote, while it's after when it's on the n
of and
.
But I don't understand why the second match fails. When the engine reaches the second quote on the line, it should find that ' and '
matches, even if it can't backtrack into '[^']*'
.
It can't give up any character inside and
anyway (if it did, it couldn't match the second quote anymore), so there is no submatch which \@>
can prevent.
The only explanation I could find was the following one:
because of \@>
, the regex engine is not allowed to start a match from any character in any previous text which was successfully matched by \@>
(even if the global regex failed to match).
\%('[^']*'\)\@>
successfully matched 'string'
– even though the overall regex failed – so now it can't start a match from any character inside 'string'
.
And to find ' and '
, the engine would need to start its match from the second quote in 'string'
. It can't; thus it doesn't find ' and '
.
But this feature of \@>
doesn't seem to be documented at :h /\@>
. Unless this is implied by:
Matches the preceding atom like matching a whole pattern.
If '[^']*'
matches a string like a whole pattern, it means that it doesn't matter whether the rest of the regex matches; it's processed as if it was alone (i.e. no backtracking).
But maybe it means something more; maybe it also implies that every character inside the text matched by '[^']*'
is ignored when searching for further matches.
Somewhat in the same way that A.*C\|B.*D
can't match BCD
in ABCD
because ABC
has already been matched, and the regex engine can't start a match inside a previous one (unless it's processing a multi-line regex).
Unfortunately, I don't think that can be the explanation. Consider this text:
ABCD
And this regex:
\%(A.*C\)\@>E\@=\|B.*D
A.*C
matches ABC
, but the regex fails because of the positive lookahead E\@=
which asserts that an E
follows immediately afterward. Nevertheless, ABC
is matched like a whole pattern thanks to \@>
. If that means that no further match can start inside, then BCD
should not match. In practice, BCD
does match; so the previous explanation is either wrong or incomplete.