neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

commit ead2b4f76999234fbe6280cfa92e78507f604e01
parent f8d59cfab9585a538760e404789029be743b61dc
Author: Justin M. Keyes <justinkz@gmail.com>
Date:   Sun, 15 Feb 2026 03:57:23 -0500

Merge #37870 from seandewar/prompt-crash


Diffstat:
Msrc/nvim/edit.c | 11++++++++---
Msrc/nvim/eval/buffer.c | 18++++++++----------
Msrc/nvim/ex_getln.c | 2+-
Mtest/functional/legacy/prompt_buffer_spec.lua | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
4 files changed, 74 insertions(+), 16 deletions(-)

diff --git a/src/nvim/edit.c b/src/nvim/edit.c @@ -1593,12 +1593,17 @@ static void init_prompt(int cmdchar_todo) char *prompt = prompt_text(); int prompt_len = (int)strlen(prompt); - if (curwin->w_cursor.lnum < curbuf->b_prompt_start.mark.lnum) { - curwin->w_cursor.lnum = curbuf->b_prompt_start.mark.lnum; - } + // 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); + + 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); + colnr_T text_len = ml_get_len(curbuf->b_prompt_start.mark.lnum); + if ((curbuf->b_prompt_start.mark.lnum == curwin->w_cursor.lnum && (curbuf->b_prompt_start.mark.col < prompt_len + || curbuf->b_prompt_start.mark.col > text_len || !strnequal(text + curbuf->b_prompt_start.mark.col - prompt_len, prompt, (size_t)prompt_len)))) { // prompt is missing, insert it or append a line with it diff --git a/src/nvim/eval/buffer.c b/src/nvim/eval/buffer.c @@ -712,7 +712,7 @@ void restore_buffer(bufref_T *save_curbuf) /// "prompt_setcallback({buffer}, {callback})" function void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - Callback prompt_callback = { .type = kCallbackNone }; + Callback prompt_callback = CALLBACK_INIT; if (check_secure()) { return; @@ -735,7 +735,7 @@ void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "prompt_setinterrupt({buffer}, {callback})" function void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - Callback interrupt_callback = { .type = kCallbackNone }; + Callback interrupt_callback = CALLBACK_INIT; if (check_secure()) { return; @@ -772,22 +772,20 @@ 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 (buf->b_prompt_start.mark.lnum > buf->b_ml.ml_line_count) { - // In case the mark is set to a nonexistent line. - buf->b_prompt_start.mark.lnum = buf->b_ml.ml_line_count; - } + // 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); linenr_T prompt_lno = buf->b_prompt_start.mark.lnum; char *old_prompt = buf_prompt_text(buf); char *old_line = ml_get_buf(buf, prompt_lno); - old_line = old_line != NULL ? old_line : ""; + colnr_T old_line_len = ml_get_buf_len(buf, prompt_lno); int old_prompt_len = (int)strlen(old_prompt); colnr_T cursor_col = curwin->w_cursor.col; if (buf->b_prompt_start.mark.col < old_prompt_len - || curbuf->b_prompt_start.mark.col < old_prompt_len - || !strnequal(old_prompt, old_line + curbuf->b_prompt_start.mark.col - old_prompt_len, + || buf->b_prompt_start.mark.col > old_line_len + || !strnequal(old_prompt, old_line + buf->b_prompt_start.mark.col - old_prompt_len, (size_t)old_prompt_len)) { // If for some odd reason the old prompt is missing, // replace prompt line with new-prompt (discards user-input). @@ -802,7 +800,7 @@ void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) cursor_col += new_prompt_len - old_prompt_len; } - if (curwin->w_buffer == buf) { + if (curwin->w_buffer == buf && curwin->w_cursor.lnum == prompt_lno) { coladvance(curwin, cursor_col); } changed_lines_redraw_buf(buf, prompt_lno, prompt_lno + 1, 0); diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c @@ -4883,7 +4883,7 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const typval_T *cancelreturn = NULL; typval_T cancelreturn_strarg2 = TV_INITIAL_VALUE; const char *xp_name = NULL; - Callback input_callback = { .type = kCallbackNone }; + Callback input_callback = CALLBACK_INIT; char prompt_buf[NUMBUFLEN]; char defstr_buf[NUMBUFLEN]; char cancelreturn_buf[NUMBUFLEN]; diff --git a/test/functional/legacy/prompt_buffer_spec.lua b/test/functional/legacy/prompt_buffer_spec.lua @@ -667,6 +667,17 @@ describe('prompt buffer', function() eq({ last_line, 6 }, api.nvim_buf_get_mark(0, ':')) eq(true, api.nvim_buf_set_mark(0, ':', 1, 5, {})) eq({ 1, 5 }, api.nvim_buf_get_mark(0, ':')) + + -- No crash from invalid col. + eq(true, api.nvim_buf_set_mark(0, ':', fn('line', '.'), 999, {})) + eq({ 12, 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, ':')) end) describe('prompt_getinput', function() @@ -798,8 +809,8 @@ describe('prompt buffer', function() api.nvim_set_option_value('buftype', 'prompt', { buf = 0 }) local buf = api.nvim_get_current_buf() - local function set_prompt(prompt) - fn('prompt_setprompt', buf, prompt) + local function set_prompt(prompt, b) + fn('prompt_setprompt', b or buf, prompt) end set_prompt('> ') @@ -846,5 +857,49 @@ describe('prompt buffer', function() {5:-- INSERT --} | ]]) eq({ 1, 13 }, api.nvim_buf_get_mark(0, ':')) + + -- Cursor not moved when not on the prompt line. + feed('<CR>user input<Esc>k') + screen:expect([[ + new-prompt > user inpu^t | + new-prompt > user input | + {1:~ }|*7 + | + ]]) + set_prompt('<>< ') + screen:expect([[ + new-prompt > user inpu^t | + <>< user input | + {1:~ }|*7 + | + ]]) + + -- No crash when setting shorter prompt than curbuf's in other buffer. + feed('i<C-O>zt') + command('new | setlocal buftype=prompt') + set_prompt('looooooooooooooooooooooooooooooooooooooooooooong > ', '') -- curbuf + set_prompt('foo > ') + screen:expect([[ + loooooooooooooooooooooooo| + ooooooooooooooooooooong >| + ^ | + {1:~ }| + {3:[Prompt] [+] }| + foo > user input | + {1:~ }|*3 + {5:-- INSERT --} | + ]]) + + -- No prompt_setprompt crash from invalid ': col. Must happen in the same event. + exec_lua(function() + vim.cmd 'bwipeout!' + vim.api.nvim_buf_set_mark(0, ':', vim.fn.line('.'), 999, {}) + vim.fn.prompt_setprompt('', 'new-prompt > ') + end) + screen:expect([[ + new-prompt > ^ | + {1:~ }|*8 + {5:-- INSERT --} | + ]]) end) end)