From 871b4b164278702224acfe684915518ec48d46ed Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 11 May 2026 19:30:29 +0800 Subject: [PATCH 1/3] vim-patch:9.2.0470: No way to hook into put commands Problem: No way to hook into put commands (yochem) Solution: Introduce TextPutPre and TextPutPost autocommands (Foxe Chen). fixes: vim/vim#18701 closes: vim/vim#20144 https://github.com/vim/vim/commit/e0781bd5bfd1acc2c4d5d01ec60fecafc99ee3e2 Co-authored-by: Foxe Chen --- runtime/doc/autocmd.txt | 39 ++++++ runtime/doc/news.txt | 1 + runtime/lua/vim/_meta/api_keysets.gen.lua | 2 + src/nvim/auevents.lua | 2 + src/nvim/getchar.c | 5 + src/nvim/register.c | 156 ++++++++++++++++++++-- src/nvim/register.h | 8 +- test/old/testdir/test_autocmd.vim | 125 +++++++++++++++++ 8 files changed, 324 insertions(+), 14 deletions(-) diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index b7992336cd..fe49d0593e 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -1266,6 +1266,45 @@ TextChangedT After a change was made to the text in the current buffer in |Terminal-mode|. Otherwise the same as TextChanged. + *TextPutPost* +TextPutPost After text has been put in the current buffer. + The following values in |v:event| are mostly + the same as |TextYankPost|: + operator The operation performed, + either 'p' or 'P'. + regcontents Text that was put. For + |quote_=|, this is the result + of the expression. + regname Name of the register or empty + string for the unnamed + register. + regtype Type of the register, see + |getregtype()|. + visual True if the operation is + performed in |Visual| mode. + Not triggered when |quote_| is used nor when + called recursively. + It is not allowed to change the buffer text, + see |textlock|. + Note that for the |quote_.| register, since + the last inserted text is buffered into the + input buffer (buffer isn't modified directly), + this autocommand is called directly after + |TextPutPre|. + + *TextPutPre* +TextPutPre Before text has been put in the current buffer. + Same values as |TextPutPost| in |v:event|. It + is valid to call |setreg()| in this + autocommand, allowing you to process and + modify the text in "regcontents" before it is + put. However this does not apply to |quote_#|, + |quote_=|, |quote_%|, |quote_:|, |quote_/| or |quote_.|. + Not triggered when |quote_| is used nor when + called recursively. + It is not allowed to change the buffer text, + see |textlock|. + *TextYankPost* TextYankPost Just after a |yank| or |deleting| command, but not if the black hole register |quote_| is used nor diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 4d463f8a76..0734709274 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -144,6 +144,7 @@ EVENTS • |:delmarks| now triggers the |MarkSet| autocommand with line==col==0, same as |nvim_buf_del_mark()| +• |TextPutPre| and |TextPutPost| are triggered before/after putting text. HIGHLIGHTS diff --git a/runtime/lua/vim/_meta/api_keysets.gen.lua b/runtime/lua/vim/_meta/api_keysets.gen.lua index f5067142b8..5aa5a3e63f 100644 --- a/runtime/lua/vim/_meta/api_keysets.gen.lua +++ b/runtime/lua/vim/_meta/api_keysets.gen.lua @@ -207,6 +207,8 @@ error('Cannot require a meta file') --- |'TextChangedI' --- |'TextChangedP' --- |'TextChangedT' +--- |'TextPutPost' +--- |'TextPutPre' --- |'TextYankPost' --- |'UIEnter' --- |'UILeave' diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index 36731489b5..eca8256833 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -126,6 +126,8 @@ return { TextChangedI = true, -- text was modified in Insert mode(no popup) TextChangedP = true, -- text was modified in Insert mode(popup) TextChangedT = true, -- text was modified in Terminal mode + TextPutPost = true, -- after some text was put + TextPutPre = true, -- before some text was put TextYankPost = true, -- after a yank or delete was done (y, d, c) UIEnter = false, -- after UI attaches UILeave = false, -- after UI detaches diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 28a2977b78..9c0ba796e0 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -63,6 +63,7 @@ #include "nvim/os/os_defs.h" #include "nvim/plines.h" #include "nvim/pos_defs.h" +#include "nvim/register.h" #include "nvim/state.h" #include "nvim/state_defs.h" #include "nvim/strings.h" @@ -661,6 +662,10 @@ void stuffRedoReadbuff(const char *s) void stuffReadbuffLen(const char *s, ptrdiff_t len) { + if (add_last_insert == 1) { // Only add if this is the first call, for + // recursive calls, ignore. + ga_concat_len(&last_insert_ga, s, (size_t)len); + } add_buff(&readbuf1, s, len); } diff --git a/src/nvim/register.c b/src/nvim/register.c index 7df4e3c343..9c0d7b6237 100644 --- a/src/nvim/register.c +++ b/src/nvim/register.c @@ -1169,27 +1169,37 @@ void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) /// @param reg_width The width, only used if "reg_type" is kMTBlockWise. /// @param[out] buf Buffer to store formatted string. The allocated size should /// be at least NUMBUFLEN+2 to always fit the value. -/// @param buf_len The allocated size of the buffer. -void format_reg_type(MotionType reg_type, colnr_T reg_width, char *buf, size_t buf_len) +/// @param bufsize The allocated size of the buffer. +/// +/// @return The length of the register type string. +size_t format_reg_type(MotionType reg_type, colnr_T reg_width, char *buf, size_t bufsize) FUNC_ATTR_NONNULL_ALL { - assert(buf_len > 1); + assert(bufsize > 1); switch (reg_type) { case kMTLineWise: buf[0] = 'V'; buf[1] = NUL; - break; + return 1; case kMTCharWise: buf[0] = 'v'; buf[1] = NUL; - break; + return 1; case kMTBlockWise: - snprintf(buf, buf_len, CTRL_V_STR "%" PRIdCOLNR, reg_width + 1); - break; + return vim_snprintf_safelen(buf, bufsize, CTRL_V_STR "%" PRIdCOLNR, reg_width + 1); case kMTUnknown: buf[0] = NUL; - break; + return 0; } + abort(); +} + +static void add_regtype_to_dict(yankreg_T *reg, dict_T *dict, char *buf, size_t bufsize) +{ + // "reg" is NULL when pasting a special register, which is charwise. + size_t len = format_reg_type(reg != NULL ? reg->y_type : kMTCharWise, + reg != NULL ? reg->y_width : 0, buf, bufsize); + tv_dict_add_str_len(dict, S_LEN("regtype"), buf, (int)len); } /// Execute autocommands for TextYankPost. @@ -1222,8 +1232,7 @@ void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) // Register type. char buf[NUMBUFLEN + 2]; - format_reg_type(reg->y_type, reg->y_width, buf, ARRAY_SIZE(buf)); - tv_dict_add_str(dict, S_LEN("regtype"), buf); + add_regtype_to_dict(reg, dict, buf, ARRAY_SIZE(buf)); // Name of requested register, or empty string for unnamed operation. buf[0] = (char)oap->regname; @@ -1252,6 +1261,78 @@ void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) recursive = false; } +/// Trigger TextPutPre or TextPutPost autocommand. +/// +/// @param reg May be NULL, if special register +/// @param insert Not NULL if special register, except '.' +/// @param post If Post or Pre +/// @param dir BACKWARD for 'P', FORWARD for 'p' +static void put_do_autocmd(int regname, yankreg_T *reg, const String *insert, bool post, + Direction dir) +{ + static bool recursive = false; + + if (recursive || regname == '_') { + return; + } + + save_v_event_T save_v_event; + dict_T *v_event = get_v_event(&save_v_event); + + list_T *list = tv_list_alloc(reg != NULL ? (ptrdiff_t)reg->y_size : 1); + + if (regname == '.') { + if (last_insert_ga.ga_data != NULL) { + // Get the last inserted text to place in "regcontents" + tv_list_append_string(list, last_insert_ga.ga_data, last_insert_ga.ga_len); + } + } else if (insert != NULL) { + tv_list_append_string(list, insert->data, (ssize_t)insert->size); + } else { + assert(reg != NULL); + for (size_t n = 0; n < reg->y_size; n++) { + tv_list_append_string(list, reg->y_array[n].data, (ssize_t)reg->y_array[n].size); + } + } + + tv_list_set_lock(list, VAR_FIXED); + tv_dict_add_list(v_event, S_LEN("regcontents"), list); + + char buf[NUMBUFLEN + 2]; + + // register name or empty string for unnamed operation + buf[0] = (char)regname; + buf[1] = NUL; + size_t buflen = (buf[0] == NUL) ? 0 : 1; + tv_dict_add_str_len(v_event, S_LEN("regname"), buf, (int)buflen); + + // kind of operation (P, p) + buf[0] = dir == BACKWARD ? 'P' : 'p'; + buf[1] = NUL; + buflen = 1; + tv_dict_add_str_len(v_event, S_LEN("operator"), buf, (int)buflen); + + add_regtype_to_dict(reg, v_event, buf, sizeof(buf)); + + tv_dict_add_bool(v_event, S_LEN("visual"), VIsual_active); + + // Lock the dictionary and its keys + tv_dict_set_keys_readonly(v_event); + + recursive = true; + textlock++; + if (post) { + apply_autocmds(EVENT_TEXTPUTPOST, NULL, NULL, false, curbuf); + } else { + apply_autocmds(EVENT_TEXTPUTPRE, NULL, NULL, false, curbuf); + } + textlock--; + recursive = false; + + // Empty the dictionary, v:event is still valid + restore_v_event(v_event, &save_v_event); +} + /// Yanks the text between "oap->start" and "oap->end" into a yank register. /// If we are to append (uppercase register), we first yank into a new yank /// register and then concatenate the old and the new one. @@ -1320,9 +1401,22 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) ? 'c' : (flags & PUT_LINE ? 'i' : (dir == FORWARD ? 'a' : 'i')); + if (has_event(EVENT_TEXTPUTPRE) || has_event(EVENT_TEXTPUTPOST)) { + add_last_insert++; + } + // To avoid 'autoindent' on linewise puts, create a new line with `:put _`. if (flags & PUT_LINE) { - do_put('_', NULL, dir, 1, PUT_LINE); + const int save_add_last_insert = add_last_insert; + add_last_insert = 0; + stuffcharReadbuff(K_COMMAND); + if (dir == FORWARD) { + stuffReadbuffLen(S_LEN("put _")); + } else { + stuffReadbuffLen(S_LEN("put! _")); + } + stuffcharReadbuff(CAR); + add_last_insert = save_add_last_insert; } // If given a count when putting linewise, we stuff the readbuf with the @@ -1337,14 +1431,28 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) // back to the previous line in the case of 'noautoindent' and // 'backspace' includes "eol". So we insert a dummy space for Ctrl_U // to consume. - stuffReadbuff("\n "); - stuffcharReadbuff(Ctrl_U); + static const char s[] = { '\n', ' ', Ctrl_U, NUL }; + stuffReadbuffLen(S_LEN(s)); } } } else { stuff_inserted(command_start_char, count, false); } + // Since the text is not inserted into the buffer immediately, just call + // TextPutPost after TextPutPre. + if (has_event(EVENT_TEXTPUTPRE)) { + put_do_autocmd('.', NULL, NULL, false, dir); + } + + if (has_event(EVENT_TEXTPUTPOST)) { + put_do_autocmd('.', NULL, NULL, true, dir); + } + + if (--add_last_insert == 0) { + ga_clear(&last_insert_ga); + } + // Putting the text is done later, so can't move the cursor to the next // character. Simulate it with motion commands after the insert. if (flags & PUT_CURSEND) { @@ -1454,7 +1562,20 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) y_size = 1; // use fake one-line yank register y_array = &insert_string; } + if (has_event(EVENT_TEXTPUTPRE)) { + put_do_autocmd(regname, NULL, &insert_string, false, dir); + } } else { + if (has_event(EVENT_TEXTPUTPRE)) { + yankreg_T *const save_reg = reg; + if (reg == NULL) { + // Make sure to call this before we set the variables, as setreg() + // may be called and invalidate them. + reg = get_yank_register(regname, YREG_PASTE); + } + put_do_autocmd(regname, reg, NULL, false, dir); + reg = save_reg; + } // in case of replacing visually selected text // the yankreg might already have been saved to avoid // just restoring the deleted text. @@ -2069,6 +2190,15 @@ end: curbuf->b_op_start = orig_start; curbuf->b_op_end = orig_end; } + + if (has_event(EVENT_TEXTPUTPOST)) { + if (insert_string.data == NULL) { + put_do_autocmd(regname, reg, NULL, true, dir); + } else { + put_do_autocmd(regname, NULL, &insert_string, true, dir); + } + } + if (allocated) { xfree(insert_string.data); } diff --git a/src/nvim/register.h b/src/nvim/register.h index 7179c806cf..382566c84a 100644 --- a/src/nvim/register.h +++ b/src/nvim/register.h @@ -1,10 +1,16 @@ #pragma once #include "nvim/ascii_defs.h" -#include "nvim/ex_cmds_defs.h" +#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep #include "nvim/macros_defs.h" #include "nvim/register_defs.h" +/// Used by TextPutPost/TextPutPre autocommands for the '.' register. If +/// "add_last_insert" is == 1, then "stuff_inserted" will add the last inserted +/// text to "last_insert_ga". +EXTERN garray_T last_insert_ga INIT( = { 0, 0, 1, 64, NULL }); +EXTERN int add_last_insert INIT( = 0); + #include "register.h.generated.h" #include "register.h.inline.generated.h" diff --git a/test/old/testdir/test_autocmd.vim b/test/old/testdir/test_autocmd.vim index 2c4808434b..f1d262c535 100644 --- a/test/old/testdir/test_autocmd.vim +++ b/test/old/testdir/test_autocmd.vim @@ -5330,4 +5330,129 @@ func Test_SwapExists_b_nwindows() %bw! endfunc +func Test_TextPutX() + enew! + + let g:pre_event = [] + let g:post_event = [] + au TextPutPre * let g:pre_event = copy(v:event) + au TextPutPost * let g:post_event = copy(v:event) + + call setreg('a', ['foo'], 'v') + norm "ap + call assert_equal( + \ #{regcontents: ['foo'], regname: 'a', operator: 'p', + \ visual: v:false, regtype: 'v'}, + \ g:pre_event) + call assert_equal(g:pre_event, g:post_event) + + call setreg('', ['hello'], 'V') + norm P + call assert_equal( + \ #{regcontents: ['hello'], regname: '', operator: 'P', + \ visual: v:false, regtype: 'V'}, + \ g:pre_event) + call assert_equal(g:pre_event, g:post_event) + + call setreg('', ['maybe', 'true'], 'V') + norm Vp + call assert_equal( + \ #{regcontents: ['maybe', 'true'], regname: '', operator: 'P', + \ regtype: 'V', visual: v:true}, + \ g:pre_event) + call assert_equal(g:pre_event, g:post_event) + call assert_equal({}, v:event) + + call feedkeys("iinserted text\below\", 'x') + norm ".p + call assert_equal( + \ #{regcontents: ["inserted text\nbelow"], regname: '.', + \ operator: 'p', regtype: 'v', visual: v:false}, + \ g:pre_event) + call assert_equal(g:pre_event, g:post_event) + + call feedkeys("\"=201\p", 'x') + call assert_equal( + \ #{regcontents: ["201"], regname: '=', + \ operator: 'p', regtype: 'v', visual: v:false}, + \ g:pre_event) + call assert_equal(g:pre_event, g:post_event) + + vsplit some.txt + wincmd l + norm "#p + call assert_equal( + \ #{regcontents: ["some.txt"], regname: '#', + \ operator: 'p', regtype: 'v', visual: v:false}, + \ g:pre_event) + call assert_equal(g:pre_event, g:post_event) + wincmd h + bw! + + if has('clipboard_working') + let @+ = 'clipboard' + + norm "+p + call assert_equal( + \ #{regcontents: ["clipboard"], regname: '+', + \ operator: 'p', regtype: 'v', visual: v:false}, + \ g:pre_event) + call assert_equal(g:pre_event, g:post_event) + endif + + %delete " Clear buffer + + au! TextPutPre + au! TextPutPost + let g:pre_event = [] + let g:post_event = [] + + au TextPutPre * call setreg(v:event['regname'], + \ getreg('', 0, v:true) + ['!']) " Unnamed register should be same as regname + + call setreg('', ['hello', 'world']) + norm p + call assert_equal(['', 'hello', 'world', '!'], getline(1, '$')) + + au! TextPutPre + + " Test that special registers cannot be modified + %delete + au TextPutPre * call setreg('=', '"modified"') | let g:pre_event = copy(v:event) + + " Set up the expression register to evaluate to a known value. + call feedkeys("\"=\"original\"\p", 'x') + + " The original value is what got put, not the modified one. + call assert_equal(['original'], getline(1, '$')) + + " v:event still reports the original value. + call assert_equal(['original'], g:pre_event['regcontents']) + + au! TextPutPre + let g:pre_event = [] + + " Test that recursive ". register calls have the same contents for post and + " pre + au TextPutPre * put . | let g:pre_event = copy(v:event) + au TextPutPost * let g:post_event = copy(v:event) + + call feedkeys("iinserted\", 'x') + norm! ".p + + call assert_equal( + \ #{regcontents: ["inserted"], regname: '.', + \ operator: 'p', regtype: 'v', visual: v:false}, + \ g:pre_event) + call assert_equal(g:pre_event, g:post_event) + + au! TextPutPre + au! TextPutPost + + unlet g:post_event + unlet g:pre_event + bwipe! + +endfunc + " vim: shiftwidth=2 sts=2 expandtab From 58184d3fa39b6c33d0859c2d3480458740a5fafc Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 12 May 2026 07:31:58 +0800 Subject: [PATCH 2/3] vim-patch:9.2.0473: Pasting ". register without autocommands breaks TextPut* Problem: Pasting ". register without TextPut* autocommands breaks subsequent TextPut* autocommands (after 9.2.0470). Solution: Only decrement add_last_insert if it has been incremented (zeertzjq). closes: vim/vim#20192 https://github.com/vim/vim/commit/a70b7a85afad9dc8eeae2a97142dc744cf3205e6 --- src/nvim/register.c | 6 +++--- test/old/testdir/test_autocmd.vim | 31 ++++++++++++++++++------------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/nvim/register.c b/src/nvim/register.c index 9c0d7b6237..1df0f0a62a 100644 --- a/src/nvim/register.c +++ b/src/nvim/register.c @@ -1401,7 +1401,8 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) ? 'c' : (flags & PUT_LINE ? 'i' : (dir == FORWARD ? 'a' : 'i')); - if (has_event(EVENT_TEXTPUTPRE) || has_event(EVENT_TEXTPUTPOST)) { + bool has_textput_events = has_event(EVENT_TEXTPUTPRE) || has_event(EVENT_TEXTPUTPOST); + if (has_textput_events) { add_last_insert++; } @@ -1444,12 +1445,11 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) if (has_event(EVENT_TEXTPUTPRE)) { put_do_autocmd('.', NULL, NULL, false, dir); } - if (has_event(EVENT_TEXTPUTPOST)) { put_do_autocmd('.', NULL, NULL, true, dir); } - if (--add_last_insert == 0) { + if (has_textput_events && --add_last_insert == 0) { ga_clear(&last_insert_ga); } diff --git a/test/old/testdir/test_autocmd.vim b/test/old/testdir/test_autocmd.vim index f1d262c535..d4fcf98fb6 100644 --- a/test/old/testdir/test_autocmd.vim +++ b/test/old/testdir/test_autocmd.vim @@ -5432,22 +5432,27 @@ func Test_TextPutX() au! TextPutPre let g:pre_event = [] - " Test that recursive ". register calls have the same contents for post and - " pre - au TextPutPre * put . | let g:pre_event = copy(v:event) - au TextPutPost * let g:post_event = copy(v:event) + for round in range(2) + " Recursive ". register calls have the same contents for post and pre. + au TextPutPre * put . | let g:pre_event = copy(v:event) + au TextPutPost * let g:post_event = copy(v:event) - call feedkeys("iinserted\", 'x') - norm! ".p + call feedkeys("iinserted\", 'x') + norm! ".p - call assert_equal( - \ #{regcontents: ["inserted"], regname: '.', - \ operator: 'p', regtype: 'v', visual: v:false}, - \ g:pre_event) - call assert_equal(g:pre_event, g:post_event) + call assert_equal( + \ #{regcontents: ["inserted"], regname: '.', + \ operator: 'p', regtype: 'v', visual: v:false}, + \ g:pre_event) + call assert_equal(g:pre_event, g:post_event) - au! TextPutPre - au! TextPutPost + au! TextPutPre + au! TextPutPost + + " Pasting ". register without TextPutPre/TextPutPost autocommands should + " not interfere with these autocommands in the next round. + norm! ".p + endfor unlet g:post_event unlet g:pre_event From 986382069a14f122522865934400ed1e9c41d418 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 12 May 2026 15:42:10 +0800 Subject: [PATCH 3/3] refactor: make nvim_put() trigger TextPut* with '_' as regname --- runtime/doc/autocmd.txt | 3 ++- src/nvim/api/vim.c | 2 +- src/nvim/register.c | 2 +- test/functional/api/vim_spec.lua | 40 ++++++++++++++++++++++++++++---- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index fe49d0593e..d1db1a0936 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -1277,7 +1277,8 @@ TextPutPost After text has been put in the current buffer. of the expression. regname Name of the register or empty string for the unnamed - register. + register. For |nvim_put()| + this is set to '_'. regtype Type of the register, see |getregtype()|. visual True if the operation is diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index a425c52f9c..d57994f2f0 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1462,7 +1462,7 @@ void nvim_put(ArrayOf(String) lines, String type, Boolean after, Boolean follow, TRY_WRAP(err, { bool VIsual_was_active = VIsual_active; msg_silent++; // Avoid "N more lines" message. - do_put(0, reg, after ? FORWARD : BACKWARD, 1, follow ? PUT_CURSEND : 0); + do_put('_', reg, after ? FORWARD : BACKWARD, 1, follow ? PUT_CURSEND : 0); msg_silent--; VIsual_active = VIsual_was_active; }); diff --git a/src/nvim/register.c b/src/nvim/register.c index 1df0f0a62a..38755b67d5 100644 --- a/src/nvim/register.c +++ b/src/nvim/register.c @@ -1272,7 +1272,7 @@ static void put_do_autocmd(int regname, yankreg_T *reg, const String *insert, bo { static bool recursive = false; - if (recursive || regname == '_') { + if (recursive || (regname == '_' && reg == NULL)) { return; } diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index a59ac2d521..581aaaa301 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -1555,29 +1555,53 @@ describe('API', function() ) end) it('inserts text', function() + exec([[ + let g:pre_event = [] + let g:post_event = [] + au TextPutPre * let g:pre_event = copy(v:event) + au TextPutPost * let g:post_event = copy(v:event) + ]]) -- linewise - api.nvim_put({ 'line 1', 'line 2', 'line 3' }, 'l', true, true) + local lines = { 'line 1', 'line 2', 'line 3' } + local expected_event = { + regcontents = lines, + regname = '_', + operator = 'p', + regtype = 'V', + visual = false, + } + api.nvim_put(lines, 'l', true, true) expect([[ line 1 line 2 line 3]]) eq({ 0, 4, 1, 0 }, fn.getpos('.')) + eq(expected_event, api.nvim_get_var('pre_event')) + eq(expected_event, api.nvim_get_var('post_event')) command('%delete _') -- charwise + expected_event.regtype = 'v' api.nvim_put({ 'line 1', 'line 2', 'line 3' }, 'c', true, false) expect([[ line 1 line 2 line 3]]) eq({ 0, 1, 1, 0 }, fn.getpos('.')) -- follow=false + eq(expected_event, api.nvim_get_var('pre_event')) + eq(expected_event, api.nvim_get_var('post_event')) -- blockwise - api.nvim_put({ 'AA', 'BB' }, 'b', true, true) + lines = { 'AA', 'BB' } + expected_event.regcontents = lines + expected_event.regtype = '\0222' + api.nvim_put(lines, 'b', true, true) expect([[ lAAine 1 lBBine 2 line 3]]) eq({ 0, 2, 4, 0 }, fn.getpos('.')) + eq(expected_event, api.nvim_get_var('pre_event')) + eq(expected_event, api.nvim_get_var('post_event')) command('%delete _') -- Empty lines list. api.nvim_put({}, 'c', true, true) @@ -1590,19 +1614,27 @@ describe('API', function() ]]) api.nvim_put({ 'AB' }, 'c', true, true) -- after=false, follow=true - api.nvim_put({ 'line 1', 'line 2' }, 'c', false, true) + lines = { 'line 1', 'line 2' } + expected_event.regcontents = lines + expected_event.regtype = 'v' + expected_event.operator = 'P' + api.nvim_put(lines, 'c', false, true) expect([[ Aline 1 line 2B]]) eq({ 0, 2, 7, 0 }, fn.getpos('.')) + eq(expected_event, api.nvim_get_var('pre_event')) + eq(expected_event, api.nvim_get_var('post_event')) command('%delete _') api.nvim_put({ 'AB' }, 'c', true, true) -- after=false, follow=false - api.nvim_put({ 'line 1', 'line 2' }, 'c', false, false) + api.nvim_put(lines, 'c', false, false) expect([[ Aline 1 line 2B]]) eq({ 0, 1, 2, 0 }, fn.getpos('.')) + eq(expected_event, api.nvim_get_var('pre_event')) + eq(expected_event, api.nvim_get_var('post_event')) eq('', api.nvim_eval('v:errmsg')) end)