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/src/dict.c b/src/dict.c
index c13e7a4..5506978 100644
--- a/src/dict.c
+++ b/src/dict.c
@@ -47,6 +47,16 @@
     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 @@
     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 @@
  * 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 @@
 	    --todo;
 	}
     }
+
+    /* The hashtab is still locked, it has to be re-initialized anyway */
     hash_clear(&d->dv_hashtab);
 }
 
@@ -846,4 +857,23 @@
     }
 }
 
+/*
+ * 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
index 1cced57..85f607c 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -192,6 +192,7 @@
     {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 @@
 
     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 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 @@
     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
index fb49f28..ba9ec9e 100644
--- a/src/fileio.c
+++ b/src/fileio.c
@@ -6478,6 +6478,7 @@
 /*
  * 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 @@
     {"WinEnter",	EVENT_WINENTER},
     {"WinLeave",	EVENT_WINLEAVE},
     {"VimResized",	EVENT_VIMRESIZED},
+    {"TextYankPost",	EVENT_TEXTYANKPOST},
     {NULL,		(event_T)0}
 };
 
@@ -9400,6 +9402,15 @@
 }
 
 /*
+ * 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
index 0209334..1ecc677 100644
--- a/src/ops.c
+++ b/src/ops.c
@@ -1645,6 +1645,63 @@
     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 @@
 		return FAIL;
 	    }
 	}
+
+#ifdef FEAT_AUTOCMD
+	if (did_yank && has_textyankpost())
+	    yank_do_autocmd(oap, y_current);
+#endif
     }
 
     /*
@@ -3270,6 +3332,11 @@
 # 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
index 2a76263..9db43b9 100644
--- 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 @@
 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
index 34e87a1..e29f3f0 100644
--- a/src/proto/eval.pro
+++ b/src/proto/eval.pro
@@ -64,6 +64,7 @@
 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
index 30582d4..7579631 100644
--- a/src/proto/fileio.pro
+++ b/src/proto/fileio.pro
@@ -51,6 +51,7 @@
 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
index cadc013..bf106f3 100644
--- a/src/testdir/test_autocmd.vim
+++ b/src/testdir/test_autocmd.vim
@@ -1124,3 +1124,42 @@
   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
diff --git a/src/version.c b/src/version.c
index 4b18f63..57c286d 100644
--- a/src/version.c
+++ b/src/version.c
@@ -772,6 +772,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1394,
+/**/
     1393,
 /**/
     1392,
diff --git a/src/vim.h b/src/vim.h
index b43c210..4765b78 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -1339,6 +1339,7 @@
     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 @@
 #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