From 69e2fc26f0da27f384c66c738110952324c20e67 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 27 Jul 2026 07:01:54 -0400 Subject: [PATCH] fix(lua): vim.regex():match_str abort in Luv callback #41008 (cherry picked from commit 80cd48257721cb4261495131ea806020a309b9c4) --- runtime/doc/lua.txt | 6 ++++-- runtime/lua/vim/_meta/regex.lua | 5 +++-- src/nvim/lua/stdlib.c | 3 ++- test/functional/lua/vim_spec.lua | 23 +++++++++++++++++++++++ 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 22911a8aa7..878120fe90 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -4623,8 +4623,10 @@ vim.re.updatelocale() *vim.re.updatelocale()* ============================================================================== Lua module: vim.regex *vim.regex* -Vim regexes can be used directly from Lua. Currently they only allow matching -within a single line. +The `vim.regex` interface allows Lua code to use Vim regexes. +• Matches within a single line. +• Does not check for interrupts (CTRL-C), so can be used from an |api-fast| + context. *regex:match_line()* diff --git a/runtime/lua/vim/_meta/regex.lua b/runtime/lua/vim/_meta/regex.lua index f488ca5313..a4fbad996f 100644 --- a/runtime/lua/vim/_meta/regex.lua +++ b/runtime/lua/vim/_meta/regex.lua @@ -4,8 +4,9 @@ error('Cannot require a meta file') -- luacheck: no unused args ---- @brief Vim regexes can be used directly from Lua. Currently they only allow ---- matching within a single line. +--- @brief The `vim.regex` interface allows Lua code to use Vim regexes. +--- - Matches within a single line. +--- - Does not check for interrupts (CTRL-C), so can be used from an |api-fast| context. --- Parses the Vim regex `re` and returns a regex object. Regexes are "magic" and case-sensitive by --- default, regardless of 'magic' and 'ignorecase'. They can be controlled with flags, see |/magic| diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c index c2e8394154..9151f75b09 100644 --- a/src/nvim/lua/stdlib.c +++ b/src/nvim/lua/stdlib.c @@ -301,7 +301,8 @@ int nlua_regex(lua_State *lstate) regprog_T *prog = NULL; TRY_WRAP(&err, { - prog = vim_regcomp(text, RE_AUTO | RE_MAGIC | RE_STRICT); + // RE_NOBREAK: so vim.regex() works in api-fast context. #18111 + prog = vim_regcomp(text, RE_AUTO | RE_MAGIC | RE_STRICT | RE_NOBREAK); }); if (ERROR_SET(&err)) { diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index fa08079b3b..74b0ca72da 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -1818,6 +1818,29 @@ describe('lua stdlib', function() -- vim.regex() error inside :silent! should not crash. #20546 command([[silent! lua vim.regex('\\z')]]) assert_alive() + + -- match_str() in a fast (luv) callback does not abort. #18111 + eq( + { true, true }, + exec_lua(function() + local re = vim.regex('^.*\\.go$') + local res + local timer = assert(vim.uv.new_timer()) + timer:start(10, 0, function() + -- Enough matches to reach BREAKCHECK_SKIP, which would abort (reentrant event loop). + local ok = true + for _ = 1, 3000 do + ok = ok and re:match_str('internal/config/config.go') ~= nil + end + res = { ok, vim.in_fast_event() } + timer:close() + end) + vim.wait(1000, function() + return res ~= nil + end) + return res + end) + ) end) it('vim.defer_fn', function()