To: vim_dev@googlegroups.com Subject: Patch 8.0.1394 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.0.1394 Problem: Cannot intercept a yank command. Solution: Add the TextYankPost autocommand event. (Philippe Vaucher et al., closes #2333) Files: runtime/doc/autocmd.txt, runtime/doc/eval.txt, src/dict.c, src/eval.c, src/fileio.c, src/ops.c, src/proto/dict.pro, src/proto/eval.pro, src/proto/fileio.pro, src/testdir/test_autocmd.vim, src/vim.h *** ../vim-8.0.1393/runtime/doc/autocmd.txt 2017-10-19 18:35:46.090557740 +0200 --- runtime/doc/autocmd.txt 2017-12-16 18:08:43.586241339 +0100 *************** *** 314,319 **** --- 330,336 ---- |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 *************** *** 935,940 **** --- 957,982 ---- 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 *** ../vim-8.0.1393/runtime/doc/eval.txt 2017-12-12 22:45:07.141808185 +0100 --- runtime/doc/eval.txt 2017-12-16 18:13:36.452281594 +0100 *************** *** 1554,1559 **** --- 1554,1565 ---- < 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|. *** ../vim-8.0.1393/src/dict.c 2017-04-30 20:12:53.370810715 +0200 --- src/dict.c 2017-12-16 18:21:39.013085280 +0100 *************** *** 47,52 **** --- 47,62 ---- 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,66 **** int rettv_dict_alloc(typval_T *rettv) { ! dict_T *d = dict_alloc(); if (d == NULL) return FAIL; rettv_dict_set(rettv, d); - rettv->v_lock = 0; return OK; } --- 64,75 ---- int rettv_dict_alloc(typval_T *rettv) { ! dict_T *d = dict_alloc_lock(0); if (d == NULL) return FAIL; rettv_dict_set(rettv, d); return OK; } *************** *** 80,86 **** * Free a Dictionary, including all non-container items it contains. * Ignores the reference count. */ ! static void dict_free_contents(dict_T *d) { int todo; --- 89,95 ---- * Free a Dictionary, including all non-container items it contains. * Ignores the reference count. */ ! void dict_free_contents(dict_T *d) { int todo; *************** *** 102,107 **** --- 111,118 ---- --todo; } } + + /* The hashtab is still locked, it has to be re-initialized anyway */ hash_clear(&d->dv_hashtab); } *************** *** 846,849 **** --- 857,879 ---- } } + /* + * 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) */ *** ../vim-8.0.1393/src/eval.c 2017-12-07 22:11:01.094438719 +0100 --- src/eval.c 2017-12-16 18:24:14.024063214 +0100 *************** *** 192,197 **** --- 192,198 ---- {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,326 **** 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_list(VV_ERRORS, list_alloc()); set_vim_var_nr(VV_FALSE, VVAL_FALSE); set_vim_var_nr(VV_TRUE, VVAL_TRUE); --- 320,328 ---- set_vim_var_nr(VV_SEARCHFORWARD, 1L); set_vim_var_nr(VV_HLSEARCH, 1L); ! 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,6638 **** --- 6635,6650 ---- } /* + * 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,6730 **** 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; ! } } } --- 6718,6730 ---- void set_vim_var_dict(int idx, dict_T *val) { 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; ! dict_set_items_ro(val); } } *** ../vim-8.0.1393/src/fileio.c 2017-11-18 14:55:19.315803253 +0100 --- src/fileio.c 2017-12-16 18:03:25.440404381 +0100 *************** *** 6478,6483 **** --- 6478,6484 ---- /* * 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,7861 **** --- 7857,7863 ---- {"WinEnter", EVENT_WINENTER}, {"WinLeave", EVENT_WINLEAVE}, {"VimResized", EVENT_VIMRESIZED}, + {"TextYankPost", EVENT_TEXTYANKPOST}, {NULL, (event_T)0} }; *************** *** 9400,9405 **** --- 9402,9416 ---- } /* + * 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. */ *** ../vim-8.0.1393/src/ops.c 2017-12-05 17:22:07.386721705 +0100 --- src/ops.c 2017-12-16 18:21:46.457036162 +0100 *************** *** 1645,1650 **** --- 1645,1707 ---- 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,1803 **** --- 1855,1865 ---- return FAIL; } } + + #ifdef FEAT_AUTOCMD + if (did_yank && has_textyankpost()) + yank_do_autocmd(oap, y_current); + #endif } /* *************** *** 3270,3275 **** --- 3332,3342 ---- # endif #endif + #ifdef FEAT_AUTOCMD + if (!deleting && has_textyankpost()) + yank_do_autocmd(oap, y_current); + #endif + return OK; fail: /* free the allocated lines */ *** ../vim-8.0.1393/src/proto/dict.pro 2017-04-30 20:12:53.378810666 +0200 --- src/proto/dict.pro 2017-12-16 18:21:52.764994543 +0100 *************** *** 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,26 **** --- 25,29 ---- 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 : */ *** ../vim-8.0.1393/src/proto/eval.pro 2017-10-30 21:48:36.482732724 +0100 --- src/proto/eval.pro 2017-12-16 18:03:25.440404381 +0100 *************** *** 64,69 **** --- 64,70 ---- 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); *** ../vim-8.0.1393/src/proto/fileio.pro 2016-09-12 13:04:04.000000000 +0200 --- src/proto/fileio.pro 2017-12-16 18:03:25.440404381 +0100 *************** *** 51,56 **** --- 51,57 ---- 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); *** ../vim-8.0.1393/src/testdir/test_autocmd.vim 2017-11-05 16:23:05.085838996 +0100 --- src/testdir/test_autocmd.vim 2017-12-16 18:03:25.440404381 +0100 *************** *** 1124,1126 **** --- 1124,1165 ---- 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 *** ../vim-8.0.1393/src/vim.h 2017-12-05 20:31:02.524899040 +0100 --- src/vim.h 2017-12-16 18:03:25.440404381 +0100 *************** *** 1339,1344 **** --- 1339,1345 ---- 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,1994 **** #define VV_TERMU7RESP 83 #define VV_TERMSTYLERESP 84 #define VV_TERMBLINKRESP 85 ! #define VV_LEN 86 /* number of v: vars */ /* used for v_number in VAR_SPECIAL */ #define VVAL_FALSE 0L --- 1989,1996 ---- #define VV_TERMU7RESP 83 #define VV_TERMSTYLERESP 84 #define VV_TERMBLINKRESP 85 ! #define VV_EVENT 86 ! #define VV_LEN 87 /* number of v: vars */ /* used for v_number in VAR_SPECIAL */ #define VVAL_FALSE 0L *** ../vim-8.0.1393/src/version.c 2017-12-16 16:33:39.703175875 +0100 --- src/version.c 2017-12-16 18:04:13.280075913 +0100 *************** *** 773,774 **** --- 773,776 ---- { /* Add new patch number below this line */ + /**/ + 1394, /**/ -- hundred-and-one symptoms of being an internet addict: 109. You actually read -- and enjoy -- lists like this. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///