commit 0e4fa5520086a066ebd08cb18df774dff563b1ef
parent 4b14ba52581266049811c7d4b2d89f1acc0f9d5e
Author: Justin M. Keyes <justinkz@gmail.com>
Date: Wed, 18 Feb 2026 14:19:25 -0500
Merge #37928 prompt-buffer cursor, undo
Diffstat:
4 files changed, 89 insertions(+), 24 deletions(-)
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
@@ -1615,6 +1615,8 @@ static void init_prompt(int cmdchar_todo)
ml_append(lnum, prompt, 0, false);
appended_lines_mark(lnum, 1);
curbuf->b_prompt_start.mark.lnum = curbuf->b_ml.ml_line_count;
+ // Like submitting, undo history was relevant to the old prompt.
+ u_clearallandblockfree(curbuf);
}
curbuf->b_prompt_start.mark.col = prompt_len;
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
diff --git a/src/nvim/eval/buffer.c b/src/nvim/eval/buffer.c
@@ -792,7 +792,7 @@ void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
// If for some odd reason the old prompt is missing,
// replace prompt line with new-prompt (discards user-input).
ml_replace_buf(buf, prompt_lno, (char *)new_prompt, true, false);
- extmark_splice_cols(buf, prompt_lno - 1, 0, old_line_len, new_prompt_len, kExtmarkUndo);
+ extmark_splice_cols(buf, prompt_lno - 1, 0, old_line_len, new_prompt_len, kExtmarkNoUndo);
cursor_col = new_prompt_len;
} else {
// Replace prev-prompt + user-input with new-prompt + user-input
@@ -801,14 +801,17 @@ void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
xfree(new_line);
}
extmark_splice_cols(buf, prompt_lno - 1, 0, buf->b_prompt_start.mark.col, new_prompt_len,
- kExtmarkUndo);
- cursor_col += new_prompt_len - old_prompt_len;
+ kExtmarkNoUndo);
+ cursor_col += new_prompt_len - buf->b_prompt_start.mark.col;
}
if (curwin->w_buffer == buf && curwin->w_cursor.lnum == prompt_lno) {
curwin->w_cursor.col = cursor_col;
+ check_cursor_col(curwin);
}
changed_lines(buf, prompt_lno, 0, prompt_lno + 1, 0, true);
+ // Undo history contains the old prompt.
+ u_clearallandblockfree(buf);
}
// Clear old prompt text and replace with the new one
diff --git a/test/functional/editor/undo_spec.lua b/test/functional/editor/undo_spec.lua
@@ -225,22 +225,3 @@ 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
@@ -514,6 +514,60 @@ describe('prompt buffer', function()
{1:~ }|*3
1 line {MATCH:.*} |
]])
+
+ -- "S" does not clear undo
+ feed('ihello<Esc>S')
+ screen:expect([[
+ cmd: tests-initial |
+ Command: "tests-initial" |
+ cmd: ^ |
+ {1:~ }|
+ {3:[Prompt] [+] }|
+ other buffer |
+ {1:~ }|*3
+ {5:-- INSERT --} |
+ ]])
+ feed('<Esc>u')
+ screen:expect([[
+ cmd: tests-initial |
+ Command: "tests-initial" |
+ ^cmd: hello |
+ {1:~ }|
+ {3:[Prompt] [+] }|
+ other buffer |
+ {1:~ }|*3
+ 1 change; {MATCH:.*} |
+ ]])
+
+ -- undo cleared if prompt changes
+ -- (otherwise undoing would abort it and append a new prompt, which isn't useful)
+ fn('prompt_setprompt', '', 'cmd > ')
+ feed('u')
+ screen:expect([[
+ cmd: tests-initial |
+ Command: "tests-initial" |
+ c^md > hello |
+ {1:~ }|
+ {3:[Prompt] [+] }|
+ other buffer |
+ {1:~ }|*3
+ Already at oldest change |
+ ]])
+
+ -- new prompt line appended to fix missing prompt also clears undo
+ feed('A there')
+ fn('setpos', "':", { 0, fn('line', '.'), 99, 0 })
+ feed('<Esc>u')
+ screen:expect([[
+ cmd: tests-initial |
+ Command: "tests-initial" |
+ cmd > hello there |
+ cmd >^ |
+ {3:[Prompt] [+] }|
+ other buffer |
+ {1:~ }|*3
+ Already at oldest change |
+ ]])
end)
it('o/O can create new lines', function()
@@ -931,9 +985,34 @@ describe('prompt buffer', function()
{1:~ }|*7
{5:-- INSERT --} |
]])
+ -- Minimum col should be 1. Same event to avoid corrections from the state loop.
+ feed('<Esc>0')
+ local colnr = exec_lua(function()
+ vim.fn.prompt_setprompt('', '')
+ return vim.fn.col('.')
+ end)
+ eq(1, colnr)
+ -- Correct cursor adjustment when old ': col and old prompt length differs.
+ set_prompt('foo > ')
+ fn('setpos', "':", { 0, fn('line', '.'), 10, 0 })
+ fn('setline', '.', ' foo > hello')
+ feed('fh')
+ screen:expect([[
+ new-prompt > user input |
+ foo > ^hello |
+ {1:~ }|*7
+ |
+ ]])
+ set_prompt('bar > ')
+ screen:expect([[
+ new-prompt > user input |
+ bar > ^hello |
+ {1:~ }|*7
+ |
+ ]])
-- No crash when setting shorter prompt than curbuf's in other buffer.
- feed('<C-O>zt')
+ feed('ztA')
command('set virtualedit& | new | setlocal buftype=prompt')
set_prompt('looooooooooooooooooooooooooooooooooooooooooooong > ', '') -- curbuf
set_prompt('foo > ')
@@ -943,7 +1022,7 @@ describe('prompt buffer', function()
^ |
{1:~ }|
{3:[Prompt] [+] }|
- foo > a b |
+ foo > hello |
{1:~ }|*3
{5:-- INSERT --} |
]])