fix(prompt): prompt_setprompt does not adjust extmarks, no on_bytes

Problem: prompt_setprompt does not adjust extmarks or trigger on_bytes
buffer-updates when fixing the prompt line.

Solution: adjust them, trigger on_bytes.

Notably, hides extmarks when replacing the entire line (and clearing user
input). Otherwise, when just replacing the prompt text, hides extmarks there,
but moves those after (in the user input area) to the correct spot.
This commit is contained in:
Sean Dewar
2026-02-15 16:22:47 +00:00
parent 7641177c5f
commit 3a10405214
3 changed files with 77 additions and 2 deletions

View File

@@ -20,6 +20,7 @@
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/window.h"
#include "nvim/ex_cmds.h"
#include "nvim/extmark.h"
#include "nvim/globals.h"
#include "nvim/macros_defs.h"
#include "nvim/memline.h"
@@ -790,6 +791,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);
cursor_col = new_prompt_len;
} else {
// Replace prev-prompt + user-input with new-prompt + user-input
@@ -797,6 +799,8 @@ void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (ml_replace_buf(buf, prompt_lno, new_line, false, false) != OK) {
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;
}

View File

@@ -11,7 +11,9 @@ local feed = n.feed
local clear = n.clear
local command = n.command
local exec = n.exec
local exec_lua = n.exec_lua
local api = n.api
local fn = n.fn
local assert_alive = n.assert_alive
local function expect(contents)
@@ -1562,10 +1564,45 @@ describe('API/extmarks', function()
it('in prompt buffer', function()
feed('dd')
local id = set_extmark(ns, marks[1], 0, 0, {})
set_extmark(ns, marks[1], 0, 0, {})
api.nvim_set_option_value('buftype', 'prompt', {})
feed('i<esc>')
eq({ { id, 0, 2 } }, get_extmarks(ns, 0, -1))
eq({ { marks[1], 0, 2 } }, get_extmarks(ns, 0, -1))
fn.prompt_setprompt('', 'foo > ')
eq({ { marks[1], 0, 6 } }, get_extmarks(ns, 0, -1))
feed('ihello')
eq({ { marks[1], 0, 11 } }, get_extmarks(ns, 0, -1))
local function get_extmark_range(id)
local rv = get_extmark_by_id(ns, id, { details = true })
return rv[3].invalid and 'invalid' or { rv[1], rv[2], rv[3].end_row, rv[3].end_col }
end
set_extmark(ns, marks[2], 0, 0, { invalidate = true, end_col = 6 })
set_extmark(ns, marks[3], 0, 6, { invalidate = true, end_col = 11 })
set_extmark(ns, marks[4], 0, 0, { invalidate = true, end_col = 11 })
set_extmark(ns, marks[5], 0, 0, { invalidate = true, end_row = 1 })
fn.prompt_setprompt('', 'floob > ')
eq({ 0, 13 }, get_extmark_range(marks[1]))
eq('invalid', get_extmark_range(marks[2])) -- extmark spanning old prompt invalidated
eq({ 0, 8, 0, 13 }, get_extmark_range(marks[3]))
eq({ 0, 8, 0, 13 }, get_extmark_range(marks[4]))
eq({ 0, 8, 1, 0 }, get_extmark_range(marks[5]))
set_extmark(ns, marks[2], 0, 0, { invalidate = true, end_col = 8 })
set_extmark(ns, marks[3], 0, 8, { invalidate = true, end_col = 13 })
set_extmark(ns, marks[4], 0, 0, { invalidate = true, end_col = 13 })
set_extmark(ns, marks[5], 0, 0, { invalidate = true, end_row = 1 })
-- Do this in the same event.
exec_lua(function()
vim.fn.setpos("':", { 0, 1, 999, 0 })
vim.fn.prompt_setprompt('', 'discard > ')
end)
eq({ 0, 10 }, get_extmark_range(marks[1]))
eq('invalid', get_extmark_range(marks[2])) -- all spans on line invalidated
eq('invalid', get_extmark_range(marks[3]))
eq('invalid', get_extmark_range(marks[4]))
eq({ 0, 10, 1, 0 }, get_extmark_range(marks[5]))
end)
it('can get details', function()

View File

@@ -1618,6 +1618,40 @@ describe('lua: nvim_buf_attach on_bytes', function()
{ 'test1', 'bytes', 1, 6, 2, 0, 6, 0, 0, 0, 1, 0, 1 },
{ 'test1', 'bytes', 1, 7, 2, 0, 6, 0, 0, 0, 0, 2, 2 },
}
fn.prompt_setprompt('', 'foo > ')
check_events {
{ 'test1', 'bytes', 1, 8, 2, 0, 6, 0, 2, 2, 0, 6, 6 },
}
feed('hello')
check_events {
{ 'test1', 'bytes', 1, 9, 2, 6, 12, 0, 0, 0, 0, 5, 5 },
}
fn.prompt_setprompt('', 'uber-foo > ')
check_events {
{ 'test1', 'bytes', 1, 10, 2, 0, 6, 0, 6, 6, 0, 11, 11 },
}
eq({ '% ', '% ', 'uber-foo > hello' }, api.nvim_buf_get_lines(0, 0, -1, true))
-- Do this in the same event.
exec_lua(function()
vim.fn.setpos("':", { 0, vim.fn.line('.'), 999, 0 })
vim.fn.prompt_setprompt('', 'discard > ')
end)
check_events {
{ 'test1', 'bytes', 1, 11, 2, 0, 6, 0, 16, 16, 0, 10, 10 },
}
eq({ '% ', '% ', 'discard > ' }, api.nvim_buf_get_lines(0, 0, -1, true))
feed('sup<S-CR>dood')
check_events {
{ 'test1', 'bytes', 1, 12, 2, 10, 16, 0, 0, 0, 0, 3, 3 },
{ 'test1', 'bytes', 1, 13, 2, 13, 19, 0, 0, 0, 1, 0, 1 },
{ 'test1', 'bytes', 1, 14, 3, 0, 20, 0, 0, 0, 0, 4, 4 },
}
eq({ '% ', '% ', 'discard > sup', 'dood' }, api.nvim_buf_get_lines(0, 0, -1, true))
fn.prompt_setprompt('', 'cool > ')
check_events {
{ 'test1', 'bytes', 1, 15, 2, 0, 6, 0, 10, 10, 0, 7, 7 },
}
eq({ '% ', '% ', 'cool > sup', 'dood' }, api.nvim_buf_get_lines(0, 0, -1, true))
end)
local function test_lockmarks(mode)