From 16bf7652b7b204bb67921004ef2a575cf0fd160d Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Mon, 16 Feb 2026 22:09:14 +0000 Subject: [PATCH] fix(prompt): prompt_setprompt with unloaded buffer, ': with lnum 0 Problem: prompt_setprompt memory leak/other issues when fixing prompt line for unloaded buffer, or when ': line number is zero. Solution: don't fix prompt line for unloaded buffer. Clamp ': lnum above zero. --- src/nvim/edit.c | 4 ++-- src/nvim/eval/buffer.c | 5 +++-- test/functional/legacy/prompt_buffer_spec.lua | 15 ++++++++++++++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 058b36b8f4..10accc4d74 100644 --- 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 index b9070eb0dc..c550b6b743 100644 --- 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/test/functional/legacy/prompt_buffer_spec.lua b/test/functional/legacy/prompt_buffer_spec.lua index 36535245ea..0e6b665310 100644 --- 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)