From 3a10405214ace2ac4bf7a289bd789fa16e18c437 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Sun, 15 Feb 2026 16:22:47 +0000 Subject: [PATCH] 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. --- src/nvim/eval/buffer.c | 4 ++ test/functional/api/extmark_spec.lua | 41 ++++++++++++++++++++- test/functional/lua/buffer_updates_spec.lua | 34 +++++++++++++++++ 3 files changed, 77 insertions(+), 2 deletions(-) diff --git a/src/nvim/eval/buffer.c b/src/nvim/eval/buffer.c index dd731d0d26..b9070eb0dc 100644 --- a/src/nvim/eval/buffer.c +++ b/src/nvim/eval/buffer.c @@ -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; } diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index 42f3a8fe30..65a1f9ccd3 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -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') - 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() diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua index 6c142cd01e..e884499a98 100644 --- a/test/functional/lua/buffer_updates_spec.lua +++ b/test/functional/lua/buffer_updates_spec.lua @@ -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('supdood') + 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)