diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 03ab2a76ad..058b36b8f4 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1609,14 +1609,16 @@ static void init_prompt(int cmdchar_todo) // prompt is missing, insert it or append a line with it if (*text == NUL) { ml_replace(curbuf->b_prompt_start.mark.lnum, prompt, true); + inserted_bytes(curbuf->b_prompt_start.mark.lnum, 0, 0, prompt_len); } else { - ml_append(curbuf->b_ml.ml_line_count, prompt, 0, false); + const linenr_T lnum = curbuf->b_ml.ml_line_count; + ml_append(lnum, prompt, 0, false); + appended_lines_mark(lnum, 1); curbuf->b_prompt_start.mark.lnum = curbuf->b_ml.ml_line_count; } curbuf->b_prompt_start.mark.col = prompt_len; curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; coladvance(curwin, MAXCOL); - inserted_bytes(curbuf->b_ml.ml_line_count, 0, 0, (colnr_T)prompt_len); } // Insert always starts after the prompt, allow editing text after it. diff --git a/src/nvim/eval/buffer.c b/src/nvim/eval/buffer.c index 80202a8cec..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,14 +799,15 @@ 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; } if (curwin->w_buffer == buf && curwin->w_cursor.lnum == prompt_lno) { - coladvance(curwin, cursor_col); + curwin->w_cursor.col = cursor_col; } - changed_lines_redraw_buf(buf, prompt_lno, prompt_lno + 1, 0); - redraw_buf_later(buf, UPD_INVERTED); + changed_lines(buf, prompt_lno, 0, prompt_lno + 1, 0, true); } // Clear old prompt text and replace with the new one diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index 2fe073eeb4..9492e498dd 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,54 @@ 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])) + + feed('hello') + eq({ 0, 15 }, get_extmark_range(marks[1])) + eq({ 0, 15, 1, 0 }, get_extmark_range(marks[5])) + -- init_prompt uses correct range for inserted_bytes when fixing empty prompt. + fn.setline('.', { '', 'last line' }) + eq({ 'discard > ', 'last line' }, api.nvim_buf_get_lines(0, 0, -1, true)) + eq({ 0, 10 }, get_extmark_range(marks[1])) + eq({ 0, 10, 1, 0 }, get_extmark_range(marks[5])) end) it('can get details', function() diff --git a/test/functional/legacy/prompt_buffer_spec.lua b/test/functional/legacy/prompt_buffer_spec.lua index 550853d06b..36535245ea 100644 --- a/test/functional/legacy/prompt_buffer_spec.lua +++ b/test/functional/legacy/prompt_buffer_spec.lua @@ -62,7 +62,7 @@ describe('prompt buffer', function() screen:expect([[ cmd: ^ | {1:~ }|*3 - {3:[Prompt] }| + {3:[Prompt] [+] }| other buffer | {1:~ }|*3 {5:-- INSERT --} | @@ -149,7 +149,7 @@ describe('prompt buffer', function() screen:expect([[ cmd: | {1:~ }|*3 - {2:[Prompt] }| + {2:[Prompt] [+] }| ^other buffer | {1:~ }|*3 | @@ -158,7 +158,7 @@ describe('prompt buffer', function() screen:expect([[ cmd: ^ | {1:~ }|*3 - {3:[Prompt] }| + {3:[Prompt] [+] }| other buffer | {1:~ }|*3 {5:-- INSERT --} | @@ -167,7 +167,7 @@ describe('prompt buffer', function() screen:expect([[ cmd:^ | {1:~ }|*3 - {3:[Prompt] }| + {3:[Prompt] [+] }| other buffer | {1:~ }|*3 | @@ -892,10 +892,42 @@ describe('prompt buffer', function() {1:~ }|*7 | ]]) + -- Correct col when prompt has multi-cell chars. + feed('i') + screen:expect([[ + new-prompt > user input | + <>< user inp^ut | + {1:~ }|*7 + {5:-- INSERT --} | + ]]) + set_prompt('\t > ') + screen:expect([[ + new-prompt > user input | + > user inp^ut | + {1:~ }|*7 + {5:-- INSERT --} | + ]]) + -- Works with 'virtualedit': coladd remains sensible. Cursor is redrawn correctly. + -- Tab size visually changes due to multiples of 'tabstop'. + command('set virtualedit=all') + feed('Sab3h') + screen:expect([[ + new-prompt > user input | + > a ^ b | + {1:~ }|*7 + {5:-- INSERT --} | + ]]) + set_prompt('😊 > ') + screen:expect([[ + new-prompt > user input | + 😊 > a ^ b | + {1:~ }|*7 + {5:-- INSERT --} | + ]]) -- No crash when setting shorter prompt than curbuf's in other buffer. - feed('izt') - command('new | setlocal buftype=prompt') + feed('zt') + command('set virtualedit& | new | setlocal buftype=prompt') set_prompt('looooooooooooooooooooooooooooooooooooooooooooong > ', '') -- curbuf set_prompt('foo > ') screen:expect([[ @@ -904,7 +936,7 @@ describe('prompt buffer', function() ^ | {1:~ }| {3:[Prompt] [+] }| - foo > user input | + foo > a b | {1:~ }|*3 {5:-- INSERT --} | ]]) diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua index dfca9f02f6..e87407f176 100644 --- a/test/functional/lua/buffer_updates_spec.lua +++ b/test/functional/lua/buffer_updates_spec.lua @@ -422,6 +422,56 @@ describe('lua: nvim_buf_attach on_lines', function() feed('I ') eq({ api.nvim_get_current_buf(), 0, 1, 1 }, exec_lua('return _G.res')) end) + + it('prompt buffer', function() + local check_events = setup_eventcheck(false, nil, {}) + api.nvim_set_option_value('buftype', 'prompt', {}) + feed('i') + check_events { + { 'test1', 'lines', 1, 4, 0, 1, 1, 1 }, + } + fn.prompt_setprompt('', 'foo > ') + check_events { + { 'test1', 'lines', 1, 5, 0, 1, 1, 3 }, + } + feed('hello') + check_events { + { 'test1', 'lines', 1, 6, 0, 1, 1, 7 }, + } + fn.prompt_setprompt('', 'super-foo > ') + check_events { + { 'test1', 'lines', 1, 7, 0, 1, 1, 12 }, + } + eq({ 'super-foo > hello' }, api.nvim_buf_get_lines(0, 0, -1, true)) + -- Do this in the same event. + exec_lua(function() + vim.fn.setpos("':", { 0, 1, 999, 0 }) + vim.fn.prompt_setprompt('', 'discard > ') + end) + check_events { + { 'test1', 'lines', 1, 8, 0, 1, 1, 18 }, + } + eq({ 'discard > ' }, api.nvim_buf_get_lines(0, 0, -1, true)) + feed('hellothere') + check_events { + { 'test1', 'lines', 1, 9, 0, 1, 1, 11 }, + { 'test1', 'lines', 1, 10, 0, 1, 2, 16 }, + { 'test1', 'lines', 1, 11, 1, 2, 2, 1 }, + } + fn.prompt_setprompt('', 'foo > ') + check_events { + { 'test1', 'lines', 1, 12, 0, 1, 1, 16 }, + } + eq({ 'foo > hello', 'there' }, api.nvim_buf_get_lines(0, 0, -1, true)) + + -- init_prompt uses appended_lines_mark when appending to fix prompt. + api.nvim_buf_set_lines(0, 0, -1, true, { 'hi' }) + eq({ 'hi', 'foo > ' }, api.nvim_buf_get_lines(0, 0, -1, true)) + check_events { + { 'test1', 'lines', 1, 13, 0, 2, 1, 18 }, + { 'test1', 'lines', 1, 14, 1, 1, 2, 0 }, + } + end) end) describe('lua: nvim_buf_attach on_bytes', function() @@ -1576,6 +1626,48 @@ 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)) + + -- init_prompt uses appended_lines_mark when appending to fix prompt. + api.nvim_buf_set_lines(0, 0, -1, true, { 'hi' }) + eq({ 'hi', 'cool > ' }, api.nvim_buf_get_lines(0, 0, -1, true)) + check_events { + { 'test1', 'bytes', 1, 16, 0, 0, 0, 4, 0, 22, 1, 0, 3 }, + { 'test1', 'bytes', 1, 17, 1, 0, 3, 0, 0, 0, 1, 0, 8 }, + } end) local function test_lockmarks(mode)