commit c556972ae10d0e61337dbc804a4a0af4e390f98e
parent 2c2203c04005495b4a6272eb178a90ce06580e9d
Author: glepnir <glephunter@gmail.com>
Date: Wed, 21 Jan 2026 14:17:44 +0800
fix(api): auto-load buffers in nvim_buf_set_* operations (#35046)
fix(api): auto-load buffers in nvim_buf_set_* operations
Problem: Setting marks, lines, or text on unloaded buffers fails because
ml_line_count is not accurate until the buffer is loaded.
Solution: Add require_loaded_buffer() helper and use it in write operations
(nvim_buf_set_lines, nvim_buf_set_text, nvim_buf_set_mark). These now
auto-load buffers as needed.
Diffstat:
2 files changed, 35 insertions(+), 15 deletions(-)
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
@@ -288,6 +288,22 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id,
return rv;
}
+static buf_T *require_loaded_buffer(Buffer buffer, Error *err)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return NULL;
+ }
+
+ // Load buffer if necessary
+ if (buf->b_ml.ml_mfp == NULL && !buf_ensure_loaded(buf)) {
+ api_set_error(err, kErrorTypeException, "Failed to load buffer");
+ return NULL;
+ }
+
+ return buf;
+}
+
/// Sets (replaces) a line-range in the buffer.
///
/// Indexing is zero-based, end-exclusive. Negative indices are interpreted
@@ -315,18 +331,12 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
FUNC_API_SINCE(1)
FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
- buf_T *buf = find_buffer_by_handle(buffer, err);
+ buf_T *buf = require_loaded_buffer(buffer, err);
if (!buf) {
return;
}
- // Load buffer if necessary. #22670
- if (!buf_ensure_loaded(buf)) {
- api_set_error(err, kErrorTypeException, "Failed to load buffer");
- return;
- }
-
bool oob = false;
start = normalize_index(buf, start, true, &oob);
end = normalize_index(buf, end, true, &oob);
@@ -480,17 +490,11 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
replacement = scratch;
}
- buf_T *buf = find_buffer_by_handle(buffer, err);
+ buf_T *buf = require_loaded_buffer(buffer, err);
if (!buf) {
return;
}
- // Load buffer if necessary. #22670
- if (!buf_ensure_loaded(buf)) {
- api_set_error(err, kErrorTypeException, "Failed to load buffer");
- return;
- }
-
bool oob = false;
// check range is ordered and everything!
@@ -1112,7 +1116,7 @@ Boolean nvim_buf_set_mark(Buffer buffer, String name, Integer line, Integer col,
FUNC_API_SINCE(8)
{
bool res = false;
- buf_T *buf = find_buffer_by_handle(buffer, err);
+ buf_T *buf = require_loaded_buffer(buffer, err);
if (!buf) {
return res;
diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua
@@ -863,6 +863,7 @@ describe('api/buf', function()
local new_bufnr = fn.bufnr('set_lines', true)
lua_or_rpc.nvim_buf_set_lines(new_bufnr, 0, -1, false, {})
eq({ '' }, lua_or_rpc.nvim_buf_get_lines(new_bufnr, 0, -1, false))
+ eq(true, api.nvim_buf_is_loaded(new_bufnr))
end)
end)
@@ -1809,6 +1810,14 @@ describe('api/buf', function()
eq({ 'one', 'two' }, get_lines(0, 2, true))
end)
+ it('auto-loads unloaded buffer', function()
+ local new_bufnr = fn.bufnr('set_text', true)
+ eq(false, api.nvim_buf_is_loaded(new_bufnr))
+ api.nvim_buf_set_text(new_bufnr, 0, 0, 0, -1, { 'foo' })
+ eq(true, api.nvim_buf_is_loaded(new_bufnr))
+ eq({ 'foo' }, api.nvim_buf_get_lines(new_bufnr, 0, -1, false))
+ end)
+
describe('handles topline', function()
local screen
before_each(function()
@@ -2375,6 +2384,13 @@ describe('api/buf', function()
it('fails when invalid buffer number is used', function()
eq(false, pcall(api.nvim_buf_set_mark, 99, 'a', 1, 1, {}))
end)
+ it('auto-loads unloaded buffer', function()
+ local new_bufnr = fn.bufnr('set_mark', true)
+ eq(false, api.nvim_buf_is_loaded(new_bufnr))
+ eq(true, api.nvim_buf_set_mark(new_bufnr, 'A', 0, 0, {}))
+ eq(true, api.nvim_buf_is_loaded(new_bufnr))
+ eq({ 0, 0 }, api.nvim_buf_get_mark(new_bufnr, 'A'))
+ end)
end)
describe('nvim_buf_del_mark', function()