commit 73c4472d4a0addec9e52a0e379b3cb0651baf690
parent 4f6b3e5c15ff7b56184d167769229c8c485d6ea1
Author: Justin M. Keyes <justinkz@gmail.com>
Date: Tue, 17 Feb 2026 07:40:19 -0500
Merge #37913 prompt-bufffer, u_savecommon
Diffstat:
5 files changed, 41 insertions(+), 8 deletions(-)
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
@@ -1594,8 +1594,8 @@ static void init_prompt(int cmdchar_todo)
int prompt_len = (int)strlen(prompt);
// In case the mark is set to a nonexistent line.
- curbuf->b_prompt_start.mark.lnum = MIN(curbuf->b_prompt_start.mark.lnum,
- curbuf->b_ml.ml_line_count);
+ curbuf->b_prompt_start.mark.lnum = MAX(1, MIN(curbuf->b_prompt_start.mark.lnum,
+ curbuf->b_ml.ml_line_count));
curwin->w_cursor.lnum = MAX(curwin->w_cursor.lnum, curbuf->b_prompt_start.mark.lnum);
char *text = ml_get(curbuf->b_prompt_start.mark.lnum);
diff --git a/src/nvim/eval/buffer.c b/src/nvim/eval/buffer.c
@@ -772,9 +772,10 @@ void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
// Update the prompt-text and prompt-marks if a plugin calls prompt_setprompt()
// even while user is editing their input.
- if (bt_prompt(buf)) {
+ if (bt_prompt(buf) && buf->b_ml.ml_mfp != NULL) {
// In case the mark is set to a nonexistent line.
- buf->b_prompt_start.mark.lnum = MIN(buf->b_prompt_start.mark.lnum, buf->b_ml.ml_line_count);
+ buf->b_prompt_start.mark.lnum = MAX(1, MIN(buf->b_prompt_start.mark.lnum,
+ buf->b_ml.ml_line_count));
linenr_T prompt_lno = buf->b_prompt_start.mark.lnum;
char *old_prompt = buf_prompt_text(buf);
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
@@ -382,7 +382,7 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, bool r
u_entry_T *prev_uep;
linenr_T size = bot - top - 1;
- // If curbuf->b_u_synced == true make a new header.
+ // If buf->b_u_synced == true make a new header.
if (buf->b_u_synced) {
// Need to create new entry in b_changelist.
buf->b_new_change = true;
@@ -401,7 +401,7 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, bool r
}
// If we undid more than we redid, move the entry lists before and
- // including curbuf->b_u_curhead to an alternate branch.
+ // including buf->b_u_curhead to an alternate branch.
u_header_T *old_curhead = buf->b_u_curhead;
if (old_curhead != NULL) {
buf->b_u_newhead = old_curhead->uh_next.ptr;
@@ -608,7 +608,7 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, bool r
buf->b_u_newhead->uh_entry = uep;
if (reload) {
// buffer was reloaded, notify text change subscribers
- curbuf->b_u_newhead->uh_flags |= UH_RELOAD;
+ buf->b_u_newhead->uh_flags |= UH_RELOAD;
}
buf->b_u_synced = false;
undo_undoes = false;
diff --git a/test/functional/editor/undo_spec.lua b/test/functional/editor/undo_spec.lua
@@ -225,3 +225,22 @@ describe(':undo! command', function()
)
end)
end)
+
+describe('undo', function()
+ before_each(clear)
+
+ it('u_savecommon uses correct buffer with reload = true', function()
+ -- Easiest to repro in a prompt buffer. prompt_setprompt's buffer must not yet have an undo
+ -- header to trigger this. Will crash if it wrongly uses the unloaded curbuf in nvim_buf_call,
+ -- as that has no undo buffer.
+ eq(0, #fn.undotree().entries)
+ exec_lua(function()
+ local buf = vim.api.nvim_get_current_buf()
+ vim.bo.buftype = 'prompt'
+ vim.api.nvim_buf_call(vim.fn.bufadd(''), function()
+ vim.fn.prompt_setprompt(buf, 'hej > ')
+ end)
+ end)
+ eq('hej > ', fn.prompt_getprompt(''))
+ end)
+end)
diff --git a/test/functional/legacy/prompt_buffer_spec.lua b/test/functional/legacy/prompt_buffer_spec.lua
@@ -691,12 +691,19 @@ describe('prompt buffer', function()
eq(true, api.nvim_buf_set_mark(0, ':', fn('line', '.'), 999, {}))
eq({ 12, 6 }, api.nvim_buf_get_mark(0, ':'))
+ -- Clamps lnum to at least 1. Do in one event to repro the leak.
+ exec_lua(function()
+ vim.fn.setpos("':", { 0, 0, 0, 0 })
+ vim.fn.prompt_setprompt('', 'bar > ')
+ end)
+ eq({ 1, 6 }, api.nvim_buf_get_mark(0, ':'))
+
-- No ml_get error from invalid lnum.
command('set messagesopt+=wait:0 messagesopt-=hit-enter')
fn('setpos', "':", { 0, 999, 7, 0 })
eq('', api.nvim_get_vvar('errmsg'))
command('set messagesopt&')
- eq({ 12, 6 }, api.nvim_buf_get_mark(0, ':'))
+ eq({ 13, 6 }, api.nvim_buf_get_mark(0, ':'))
end)
describe('prompt_getinput', function()
@@ -952,5 +959,11 @@ describe('prompt buffer', function()
{1:~ }|*8
{5:-- INSERT --} |
]])
+
+ -- No leak if prompt_setprompt called for unloaded prompt buffer.
+ local unloaded_buf = fn('bufadd', '')
+ api.nvim_set_option_value('buftype', 'prompt', { buf = unloaded_buf })
+ fn('prompt_setprompt', unloaded_buf, 'hello unloaded! > ')
+ eq('hello unloaded! > ', fn('prompt_getprompt', unloaded_buf))
end)
end)