From: Hirohito Higashi Date: Wed, 22 Jul 2026 08:49:26 +0000 (+0000) Subject: patch 9.2.0825: regexp: submatch in a look-behind is empty with the NFA engine X-Git-Tag: v9.2.0825^0 X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=ade577e4dc2a9f34d2d592cbe1c80b9fc89753bf;p=thirdparty%2Fvim.git patch 9.2.0825: regexp: submatch in a look-behind is empty with the NFA engine Problem: With the NFA engine a sub-expression inside a variable width look-behind, e.g. "\v(.)@<=", is empty for the first match on every line except the first one. The old engine is correct (Mukundan) Solution: The look-behind is retried from the previous line, because the width of "." is over-estimated. While scanning that line the start state is added at the end of the line, where it gets the position of the line break as its start position, even though the match actually starts on the next line. Use the position of the start of the next line in that case (Hirohito Higashi). fixes: #20802 closes: #20805 Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Hirohito Higashi Signed-off-by: Christian Brabandt --- diff --git a/src/regexp_nfa.c b/src/regexp_nfa.c index f47d0c8344..b224f9fd58 100644 --- a/src/regexp_nfa.c +++ b/src/regexp_nfa.c @@ -7269,7 +7269,32 @@ nfa_regmatch( } else { - if (addstate(nextlist, start, m, NULL, clen) == NULL) + char_u *save_line = rex.line; + char_u *save_input = rex.input; + linenr_T save_lnum = rex.lnum; + + // At the end of a line the match can only start on the next + // line, use that position instead of the line break. + if (REG_MULTI && clen == 0 && nfa_endp != NULL + && rex.lnum < nfa_endp->se_u.pos.lnum) + { + char_u *next_line = reg_getline(rex.lnum + 1); + + if (next_line != NULL) + { + rex.line = next_line; + rex.input = rex.line; + ++rex.lnum; + } + } + + r = addstate(nextlist, start, m, NULL, clen); + + rex.line = save_line; + rex.input = save_input; + rex.lnum = save_lnum; + + if (r == NULL) { nfa_match = NFA_TOO_EXPENSIVE; goto theend; diff --git a/src/testdir/test_regexp_utf8.vim b/src/testdir/test_regexp_utf8.vim index d33c03c421..6dea55668a 100644 --- a/src/testdir/test_regexp_utf8.vim +++ b/src/testdir/test_regexp_utf8.vim @@ -651,4 +651,29 @@ func Test_regex_collection_range_with_composing_crash() bwipe! endfunc +" A submatch in a look-behind must not start on the previous line. See +" issue #20802. +func Test_lookbehind_submatch_on_second_line() + new + for re in range(0, 2) + exe "set re=" .. re + call setline(1, ['testing', 'testing']) + %s/\v(.)@<=/[\1]/g + call assert_equal(['t[t]e[e]s[s]t[t]i[i]n[n]g', + \ 't[t]e[e]s[s]t[t]i[i]n[n]g'], getline(1, '$'), 're=' .. re) + %d _ + + " With "\_." the look-behind can match the line break, then the submatch + " does start on the previous line. + call setline(1, ['abc', 'def']) + %s/\v(\_.)@<=/[\1]/g + call assert_equal(['a[a]b[b]c[c]', '[]d[d]e[e]f[f]'], + \ getline(1, '$'), 're=' .. re) + %d _ + endfor + + set re& + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/version.c b/src/version.c index 80eae1f884..4195d04c79 100644 --- a/src/version.c +++ b/src/version.c @@ -759,6 +759,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 825, /**/ 824, /**/