From e932d6ff170fd1b2f89f3755734de6615616956a Mon Sep 17 00:00:00 2001 From: bfredl Date: Mon, 18 May 2026 15:05:49 +0200 Subject: [PATCH] fix(api): regressions with lua return values #39837 fixes #39834 fixes #39833 fixes #39851 --- src/nvim/api/buffer.c | 2 ++ src/nvim/lua/executor.c | 2 +- test/functional/api/buffer_spec.lua | 44 ++++++++++++++++++++++++++++- test/functional/api/window_spec.lua | 43 ++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 2 deletions(-) diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 78be5390d0..9194df1f49 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -278,6 +278,7 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, // return sentinel value if the buffer isn't loaded if (b->b_ml.ml_mfp == NULL) { + init_line_array(lstate, &rv, 0, arena); return rv; } @@ -715,6 +716,7 @@ ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buf, // return sentinel value if the buffer isn't loaded if (b->b_ml.ml_mfp == NULL) { + init_line_array(lstate, &rv, 0, arena); return rv; } diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index b6847f0492..1ab51d7386 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -1808,7 +1808,7 @@ static int mode_ret(LuaRetMode mode) Object nlua_call_ref_ctx(bool fast, LuaRef ref, const char *name, Array args, LuaRetMode mode, Arena *arena, Error *err) { - lua_State *const lstate = global_lstate; + lua_State *const lstate = active_lstate; int top = lua_gettop(lstate); nlua_pushref(lstate, ref); int nargs = (int)args.size; diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua index 749a9b4267..a63ef9a58d 100644 --- a/test/functional/api/buffer_spec.lua +++ b/test/functional/api/buffer_spec.lua @@ -188,7 +188,7 @@ describe('api/buf', function() eq(0, api.nvim_buf_line_count(bufnr)) end) - it('get_lines has defined behaviour for unloaded buffers', function() + it('get_lines and get_text has defined behaviour for unloaded buffers', function() -- we'll need to know our bufnr for when it gets unloaded local bufnr = api.nvim_get_current_buf() -- replace the buffer contents with these three lines @@ -202,6 +202,22 @@ describe('api/buf', function() eq({}, api.nvim_buf_get_lines(bufnr, 1, 3, true)) -- it's impossible to get out-of-bounds errors for an unloaded buffer eq({}, api.nvim_buf_get_lines(bufnr, 8888, 9999, true)) + eq({}, api.nvim_buf_get_text(bufnr, 8888, 1000, 9999, 2000, {})) + -- The Lua path (vim.api) must also return an empty table, not nil. #39833 #39851 + eq( + { 'table', {} }, + exec_lua(function() + local r = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + return { type(r), r } + end) + ) + eq( + { 'table', {} }, + exec_lua(function() + local r = vim.api.nvim_buf_get_text(bufnr, 0, 0, -1, 0, {}) + return { type(r), r } + end) + ) end) describe('handles topline', function() @@ -2498,6 +2514,32 @@ describe('api/buf', function() ) end) + it('propagates return values when called from a coroutine #39834', function() + local other = api.nvim_create_buf(false, true) + eq( + { other, vim.NIL, 42 }, + exec_lua(function() + local out + local co = coroutine.create(function() + local function pack(...) + local r = { ... } + for i = 1, select('#', ...) do + if r[i] == nil then + r[i] = vim.NIL + end + end + return r + end + out = pack(vim.api.nvim_buf_call(other, function() + return vim.api.nvim_get_current_buf(), nil, 42 + end)) + end) + assert(coroutine.resume(co)) + return out + end) + ) + end) + it('can access buf options', function() local buf1 = api.nvim_get_current_buf() local buf2 = exec_lua [[ diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index c69ce3a791..fa2f9ead36 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -3956,6 +3956,49 @@ describe('API/win', function() ) end) + it('propagates return values when called from a coroutine #39834', function() + local other = api.nvim_open_win(api.nvim_create_buf(false, true), false, { split = 'left' }) + -- Single return value. + eq( + { other }, + exec_lua(function() + local out + local co = coroutine.create(function() + out = { + vim.api.nvim_win_call(other, function() + return vim.api.nvim_get_current_win() + end), + } + end) + assert(coroutine.resume(co)) + return out + end) + ) + -- Multiple return values (including nil in the middle). + eq( + { 6, vim.NIL, 7 }, + exec_lua(function() + local out + local co = coroutine.create(function() + local function pack(...) + local r = { ... } + for i = 1, select('#', ...) do + if r[i] == nil then + r[i] = vim.NIL + end + end + return r + end + out = pack(vim.api.nvim_win_call(other, function() + return 6, nil, 7 + end)) + end) + assert(coroutine.resume(co)) + return out + end) + ) + end) + it('can access window options', function() command('vsplit') local win1 = api.nvim_get_current_win()