# HG changeset patch # User Christian Brabandt # Date 1513445405 -3600 # Node ID 6e81a68d63a110fc8edd0537f1281860f848b258 # Parent 754780887de1211d60bfeecb3e131cd149ecb690 patch 8.0.1394: cannot intercept a yank command commit https://github.com/vim/vim/commit/7e1652c63c96585b9e2235c195a3c322b1f11595 Author: Bram Moolenaar Date: Sat Dec 16 18:27:02 2017 +0100 patch 8.0.1394: cannot intercept a yank command Problem: Cannot intercept a yank command. Solution: Add the TextYankPost autocommand event. (Philippe Vaucher et al., closes #2333) diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -330,6 +330,7 @@ Name triggered by ~ |TextChanged| after a change was made to the text in Normal mode |TextChangedI| after a change was made to the text in Insert mode +|TextYankPost| after text is yanked or deleted |ColorScheme| after loading a color scheme @@ -956,6 +957,26 @@ TextChangedI After a change was made t current buffer in Insert mode. Not triggered when the popup menu is visible. Otherwise the same as TextChanged. + |TextYankPost| +TextYankPost After text has been yanked or deleted in the + current buffer. The following values of + |v:event| can be used to determine the operation + that triggered this autocmd: + operator The operation performed. + regcontents Text that was stored in the + register, as a list of lines, + like with: > + getreg(r, 1, 1) +< regname Name of the |register| or + empty string for the unnamed + register. + regtype Type of the register, see + |getregtype()|. + Not triggered when |quote_| is used nor when + called recursively. + It is not allowed to change the buffer text, + see |textlock|. + *User* User Never executed automatically. To be used for autocommands that are only executed with diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1554,6 +1554,12 @@ v:errors Errors found by assert function < If v:errors is set to anything but a list it is made an empty list by the assert function. + *v:event* *event-variable* +v:event Dictionary containing information about the current + |autocommand|. The dictionary is emptied when the |autocommand| + finishes, please refer to |dict-identity| for how to get an + independent copy of it. + *v:exception* *exception-variable* v:exception The value of the exception most recently caught and not finished. See also |v:throwpoint| and |throw-variables|. diff --git a/src/dict.c b/src/dict.c --- a/src/dict.c +++ b/src/dict.c @@ -47,6 +47,16 @@ dict_alloc(void) return d; } + dict_T * +dict_alloc_lock(int lock) +{ + dict_T *d = dict_alloc(); + + if (d != NULL) + d->dv_lock = lock; + return d; +} + /* * Allocate an empty dict for a return value. * Returns OK or FAIL. @@ -54,13 +64,12 @@ dict_alloc(void) int rettv_dict_alloc(typval_T *rettv) { - dict_T *d = dict_alloc(); + dict_T *d = dict_alloc_lock(0); if (d == NULL) return FAIL; rettv_dict_set(rettv, d); - rettv->v_lock = 0; return OK; } @@ -80,7 +89,7 @@ rettv_dict_set(typval_T *rettv, dict_T * * Free a Dictionary, including all non-container items it contains. * Ignores the reference count. */ - static void + void dict_free_contents(dict_T *d) { int todo; @@ -102,6 +111,8 @@ dict_free_contents(dict_T *d) --todo; } } + + /* The hashtab is still locked, it has to be re-initialized anyway */ hash_clear(&d->dv_hashtab); } @@ -846,4 +857,23 @@ dict_list(typval_T *argvars, typval_T *r } } +/* + * Make each item in the dict readonly (not the value of the item). + */ + void +dict_set_items_ro(dict_T *di) +{ + int todo = (int)di->dv_hashtab.ht_used; + hashitem_T *hi; + + /* Set readonly */ + for (hi = di->dv_hashtab.ht_array; todo > 0 ; ++hi) + { + if (HASHITEM_EMPTY(hi)) + continue; + --todo; + HI2DI(hi)->di_flags |= DI_FLAGS_RO | DI_FLAGS_FIX; + } +} + #endif /* defined(FEAT_EVAL) */ diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -192,6 +192,7 @@ static struct vimvar {VV_NAME("termu7resp", VAR_STRING), VV_RO}, {VV_NAME("termstyleresp", VAR_STRING), VV_RO}, {VV_NAME("termblinkresp", VAR_STRING), VV_RO}, + {VV_NAME("event", VAR_DICT), VV_RO}, }; /* shorthand */ @@ -319,8 +320,9 @@ eval_init(void) set_vim_var_nr(VV_SEARCHFORWARD, 1L); set_vim_var_nr(VV_HLSEARCH, 1L); - set_vim_var_dict(VV_COMPLETED_ITEM, dict_alloc()); + set_vim_var_dict(VV_COMPLETED_ITEM, dict_alloc_lock(VAR_FIXED)); set_vim_var_list(VV_ERRORS, list_alloc()); + set_vim_var_dict(VV_EVENT, dict_alloc_lock(VAR_FIXED)); set_vim_var_nr(VV_FALSE, VVAL_FALSE); set_vim_var_nr(VV_TRUE, VVAL_TRUE); @@ -6633,6 +6635,16 @@ get_vim_var_list(int idx) } /* + * Get Dict v: variable value. Caller must take care of reference count when + * needed. + */ + dict_T * +get_vim_var_dict(int idx) +{ + return vimvars[idx].vv_dict; +} + +/* * Set v:char to character "c". */ void @@ -6706,25 +6718,13 @@ set_vim_var_list(int idx, list_T *val) void set_vim_var_dict(int idx, dict_T *val) { - int todo; - hashitem_T *hi; - clear_tv(&vimvars[idx].vv_di.di_tv); vimvars[idx].vv_type = VAR_DICT; vimvars[idx].vv_dict = val; if (val != NULL) { ++val->dv_refcount; - - /* Set readonly */ - todo = (int)val->dv_hashtab.ht_used; - for (hi = val->dv_hashtab.ht_array; todo > 0 ; ++hi) - { - if (HASHITEM_EMPTY(hi)) - continue; - --todo; - HI2DI(hi)->di_flags |= DI_FLAGS_RO | DI_FLAGS_FIX; - } + dict_set_items_ro(val); } } diff --git a/src/fileio.c b/src/fileio.c --- a/src/fileio.c +++ b/src/fileio.c @@ -6478,6 +6478,7 @@ buf_modname( /* * Like fgets(), but if the file line is too long, it is truncated and the * rest of the line is thrown away. Returns TRUE for end-of-file. + * If the line is truncated then buf[size - 2] will not be NUL. */ int vim_fgets(char_u *buf, int size, FILE *fp) @@ -7856,6 +7857,7 @@ static struct event_name {"WinEnter", EVENT_WINENTER}, {"WinLeave", EVENT_WINLEAVE}, {"VimResized", EVENT_VIMRESIZED}, + {"TextYankPost", EVENT_TEXTYANKPOST}, {NULL, (event_T)0} }; @@ -9400,6 +9402,15 @@ has_funcundefined(void) } /* + * Return TRUE when there is a TextYankPost autocommand defined. + */ + int +has_textyankpost(void) +{ + return (first_autopat[(int)EVENT_TEXTYANKPOST] != NULL); +} + +/* * Execute autocommands for "event" and file name "fname". * Return TRUE if some commands were executed. */ diff --git a/src/ops.c b/src/ops.c --- a/src/ops.c +++ b/src/ops.c @@ -1645,6 +1645,63 @@ shift_delete_registers() y_regs[1].y_array = NULL; /* set register one to empty */ } + static void +yank_do_autocmd(oparg_T *oap, yankreg_T *reg) +{ + static int recursive = FALSE; + dict_T *v_event; + list_T *list; + int n; + char_u buf[NUMBUFLEN + 2]; + long reglen = 0; + + if (recursive) + return; + + v_event = get_vim_var_dict(VV_EVENT); + + list = list_alloc(); + for (n = 0; n < reg->y_size; n++) + list_append_string(list, reg->y_array[n], -1); + list->lv_lock = VAR_FIXED; + dict_add_list(v_event, "regcontents", list); + + buf[0] = (char_u)oap->regname; + buf[1] = NUL; + dict_add_nr_str(v_event, "regname", 0, buf); + + buf[0] = get_op_char(oap->op_type); + buf[1] = get_extra_op_char(oap->op_type); + buf[2] = NUL; + dict_add_nr_str(v_event, "operator", 0, buf); + + buf[0] = NUL; + buf[1] = NUL; + switch (get_reg_type(oap->regname, ®len)) + { + case MLINE: buf[0] = 'V'; break; + case MCHAR: buf[0] = 'v'; break; + case MBLOCK: + vim_snprintf((char *)buf, sizeof(buf), "%c%ld", Ctrl_V, + reglen + 1); + break; + } + dict_add_nr_str(v_event, "regtype", 0, buf); + + /* Lock the dictionary and its keys */ + dict_set_items_ro(v_event); + + recursive = TRUE; + textlock++; + apply_autocmds(EVENT_TEXTYANKPOST, NULL, NULL, FALSE, curbuf); + textlock--; + recursive = FALSE; + + /* Empty the dictionary, v:event is still valid */ + dict_free_contents(v_event); + hash_init(&v_event->dv_hashtab); +} + /* * Handle a delete operation. * @@ -1798,6 +1855,11 @@ op_delete(oparg_T *oap) return FAIL; } } + +#ifdef FEAT_AUTOCMD + if (did_yank && has_textyankpost()) + yank_do_autocmd(oap, y_current); +#endif } /* @@ -3270,6 +3332,11 @@ op_yank(oparg_T *oap, int deleting, int # endif #endif +#ifdef FEAT_AUTOCMD + if (!deleting && has_textyankpost()) + yank_do_autocmd(oap, y_current); +#endif + return OK; fail: /* free the allocated lines */ diff --git a/src/proto/dict.pro b/src/proto/dict.pro --- a/src/proto/dict.pro +++ b/src/proto/dict.pro @@ -1,7 +1,9 @@ /* dict.c */ dict_T *dict_alloc(void); +dict_T *dict_alloc_lock(int lock); int rettv_dict_alloc(typval_T *rettv); void rettv_dict_set(typval_T *rettv, dict_T *d); +void dict_free_contents(dict_T *d); void dict_unref(dict_T *d); int dict_free_nonref(int copyID); void dict_free_items(int copyID); @@ -23,4 +25,5 @@ void dict_extend(dict_T *d1, dict_T *d2, dictitem_T *dict_lookup(hashitem_T *hi); int dict_equal(dict_T *d1, dict_T *d2, int ic, int recursive); void dict_list(typval_T *argvars, typval_T *rettv, int what); +void dict_set_items_ro(dict_T *di); /* vim: set ft=c : */ diff --git a/src/proto/eval.pro b/src/proto/eval.pro --- a/src/proto/eval.pro +++ b/src/proto/eval.pro @@ -64,6 +64,7 @@ void set_vim_var_nr(int idx, varnumber_T varnumber_T get_vim_var_nr(int idx); char_u *get_vim_var_str(int idx); list_T *get_vim_var_list(int idx); +dict_T * get_vim_var_dict(int idx); void set_vim_var_char(int c); void set_vcount(long count, long count1, int set_prevcount); void set_vim_var_string(int idx, char_u *val, int len); diff --git a/src/proto/fileio.pro b/src/proto/fileio.pro --- a/src/proto/fileio.pro +++ b/src/proto/fileio.pro @@ -51,6 +51,7 @@ int has_textchangedI(void); int has_insertcharpre(void); int has_cmdundefined(void); int has_funcundefined(void); +int has_textyankpost(void); void block_autocmds(void); void unblock_autocmds(void); int is_autocmd_blocked(void); diff --git a/src/testdir/test_autocmd.vim b/src/testdir/test_autocmd.vim --- a/src/testdir/test_autocmd.vim +++ b/src/testdir/test_autocmd.vim @@ -1124,3 +1124,42 @@ func Test_Filter_noshelltemp() let &shelltemp = shelltemp bwipe! endfunc + +func Test_TextYankPost() + enew! + call setline(1, ['foo']) + + let g:event = [] + au TextYankPost * let g:event = copy(v:event) + + call assert_equal({}, v:event) + call assert_fails('let v:event = {}', 'E46:') + call assert_fails('let v:event.mykey = 0', 'E742:') + + norm "ayiw + call assert_equal( + \{'regcontents': ['foo'], 'regname': 'a', 'operator': 'y', 'regtype': 'v'}, + \g:event) + norm y_ + call assert_equal( + \{'regcontents': ['foo'], 'regname': '', 'operator': 'y', 'regtype': 'V'}, + \g:event) + call feedkeys("\y", 'x') + call assert_equal( + \{'regcontents': ['f'], 'regname': '', 'operator': 'y', 'regtype': "\x161"}, + \g:event) + norm "xciwbar + call assert_equal( + \{'regcontents': ['foo'], 'regname': 'x', 'operator': 'c', 'regtype': 'v'}, + \g:event) + norm "bdiw + call assert_equal( + \{'regcontents': ['bar'], 'regname': 'b', 'operator': 'd', 'regtype': 'v'}, + \g:event) + + call assert_equal({}, v:event) + + au! TextYankPost + unlet g:event + bwipe! +endfunc diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -772,6 +772,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1394, +/**/ 1393, /**/ 1392, diff --git a/src/vim.h b/src/vim.h --- a/src/vim.h +++ b/src/vim.h @@ -1339,6 +1339,7 @@ enum auto_event EVENT_TEXTCHANGEDI, /* text was modified in Insert mode*/ EVENT_CMDUNDEFINED, /* command undefined */ EVENT_OPTIONSET, /* option was set */ + EVENT_TEXTYANKPOST, /* after some text was yanked */ NUM_EVENTS /* MUST be the last one */ }; @@ -1988,7 +1989,8 @@ typedef int sock_T; #define VV_TERMU7RESP 83 #define VV_TERMSTYLERESP 84 #define VV_TERMBLINKRESP 85 -#define VV_LEN 86 /* number of v: vars */ +#define VV_EVENT 86 +#define VV_LEN 87 /* number of v: vars */ /* used for v_number in VAR_SPECIAL */ #define VVAL_FALSE 0L