]> granicus.if.org Git - vim/commitdiff
patch 8.0.1394: cannot intercept a yank command v8.0.1394
authorBram Moolenaar <Bram@vim.org>
Sat, 16 Dec 2017 17:27:02 +0000 (18:27 +0100)
committerBram Moolenaar <Bram@vim.org>
Sat, 16 Dec 2017 17:27:02 +0000 (18:27 +0100)
Problem:    Cannot intercept a yank command.
Solution:   Add the TextYankPost autocommand event. (Philippe Vaucher et al.,
            closes #2333)

12 files changed:
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/version.c
src/vim.h

index 06931dfd32633606937099a181586b95dca0783f..a47050e0d4d28d5b0d5206d7270b70552233528d 100644 (file)
@@ -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 to the text in the
                                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
index 977ff718b2935d89173b87a0bfb6e008984d291f..09a66a6eed742f61fa832915a7b71a892fe8ea9c 100644 (file)
@@ -1554,6 +1554,12 @@ v:errors Errors found by assert functions, such as |assert_true()|.
 <              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|.
index c13e7a45f51de48a3cf58e1974831e1c2fff45f9..55069783f288781b8cb928d58ac65bb862d300f9 100644 (file)
@@ -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 *d)
  * 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 *rettv, int what)
     }
 }
 
+/*
+ * 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) */
index 1cced57ec0196c1eb1c60a4ddf16d44d747c275f..85f607cb77890e2597243fcc019e7ecb3596a151 100644 (file)
@@ -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);
@@ -6632,6 +6634,16 @@ get_vim_var_list(int idx)
     return vimvars[idx].vv_list;
 }
 
+/*
+ * 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".
  */
@@ -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);
     }
 }
 
index fb49f28f8165b896fa8523e65bab7902fdb86050..ba9ec9ec0a02587af118adab829389d10f3138d2 100644 (file)
@@ -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}
 };
 
@@ -9399,6 +9401,15 @@ has_funcundefined(void)
     return (first_autopat[(int)EVENT_FUNCUNDEFINED] != NULL);
 }
 
+/*
+ * 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.
index 0209334ea4f3df169932f4dd2b4450b2e61c661f..1ecc67714d0ac55774159c630efb9c1a336d487a 100644 (file)
--- 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, &reglen))
+    {
+       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 mess)
 # endif
 #endif
 
+#ifdef FEAT_AUTOCMD
+    if (!deleting && has_textyankpost())
+       yank_do_autocmd(oap, y_current);
+#endif
+
     return OK;
 
 fail:          /* free the allocated lines */
index 2a7626338e09bbeeebc46e469ea795f058cce78e..9db43b944b0d73d58880685316abfd8290c05e01 100644 (file)
@@ -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, char_u *action);
 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 : */
index 34e87a19e8b23e13d5f28138b9193761fbfa1cfa..e29f3f09931dda2811764ef2b84a363215b02473 100644 (file)
@@ -64,6 +64,7 @@ void set_vim_var_nr(int idx, varnumber_T val);
 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);
index 30582d4e1413be54e58d6c673021a8f29f89b8b0..757963113f4ec951c49b033e6fa8ac71b1dc5acc 100644 (file)
@@ -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);
index cadc013fb976d28eeab0b3f67f0edcb94da68895..bf106f33c7b65303ccb57c88cca388ed3cc785a6 100644 (file)
@@ -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("\<C-V>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
index 4b18f63ad28f313bff2cf24971a54a87cc140ef9..57c286dc1cbe2c3f075b654b435e1f0dfbe91605 100644 (file)
@@ -771,6 +771,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1394,
 /**/
     1393,
 /**/
index b43c210a68f146462cdc997ccc538911db06eec6..4765b783b5bf74d5d3e876902b38855549a25b71 100644 (file)
--- 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