Merge #37870 from seandewar/prompt-crash

This commit is contained in:
Justin M. Keyes
2026-02-15 03:57:23 -05:00
committed by GitHub
4 changed files with 74 additions and 16 deletions

View File

@@ -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

View File

@@ -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);

View File

@@ -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];

View File

@@ -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)