patch 8.1.1138: plugins don't get notified when the popup menu changes

Problem:    Plugins don't get notified when the popup menu changes.
Solution:   Add the CompleteChanged event. (Andy Massimino. closes #4176)
diff --git a/src/autocmd.c b/src/autocmd.c
index aa11143..2ea23cc 100644
--- a/src/autocmd.c
+++ b/src/autocmd.c
@@ -112,6 +112,7 @@
     {"CmdUndefined",	EVENT_CMDUNDEFINED},
     {"ColorScheme",	EVENT_COLORSCHEME},
     {"ColorSchemePre",	EVENT_COLORSCHEMEPRE},
+    {"CompleteChanged",	EVENT_COMPLETECHANGED},
     {"CompleteDone",	EVENT_COMPLETEDONE},
     {"CursorHold",	EVENT_CURSORHOLD},
     {"CursorHoldI",	EVENT_CURSORHOLDI},
@@ -1794,6 +1795,17 @@
 }
 #endif
 
+#if defined(FEAT_EVAL) || defined(PROTO)
+/*
+ * Return TRUE when there is a CompleteChanged autocommand defined.
+ */
+    int
+has_completechanged(void)
+{
+    return (first_autopat[(int)EVENT_COMPLETECHANGED] != NULL);
+}
+#endif
+
 /*
  * Execute autocommands for "event" and file name "fname".
  * Return TRUE if some commands were executed.
diff --git a/src/dict.c b/src/dict.c
index 0bb9dfa..7d49599 100644
--- a/src/dict.c
+++ b/src/dict.c
@@ -342,18 +342,18 @@
 }
 
 /*
- * Add a number entry to dictionary "d".
+ * Add a number or special entry to dictionary "d".
  * Returns FAIL when out of memory and when key already exists.
  */
-    int
-dict_add_number(dict_T *d, char *key, varnumber_T nr)
+    static int
+dict_add_number_special(dict_T *d, char *key, varnumber_T nr, int special)
 {
     dictitem_T	*item;
 
     item = dictitem_alloc((char_u *)key);
     if (item == NULL)
 	return FAIL;
-    item->di_tv.v_type = VAR_NUMBER;
+    item->di_tv.v_type = special ? VAR_SPECIAL : VAR_NUMBER;
     item->di_tv.vval.v_number = nr;
     if (dict_add(d, item) == FAIL)
     {
@@ -364,6 +364,26 @@
 }
 
 /*
+ * Add a number entry to dictionary "d".
+ * Returns FAIL when out of memory and when key already exists.
+ */
+    int
+dict_add_number(dict_T *d, char *key, varnumber_T nr)
+{
+    return dict_add_number_special(d, key, nr, FALSE);
+}
+
+/*
+ * Add a special entry to dictionary "d".
+ * Returns FAIL when out of memory and when key already exists.
+ */
+    int
+dict_add_special(dict_T *d, char *key, varnumber_T nr)
+{
+    return dict_add_number_special(d, key, nr, TRUE);
+}
+
+/*
  * Add a string entry to dictionary "d".
  * Returns FAIL when out of memory and when key already exists.
  */
diff --git a/src/insexpand.c b/src/insexpand.c
index ac7f532..ad95acc 100644
--- a/src/insexpand.c
+++ b/src/insexpand.c
@@ -203,6 +203,7 @@
 static void ins_compl_add_list(list_T *list);
 static void ins_compl_add_dict(dict_T *dict);
 # endif
+static dict_T *ins_compl_dict_alloc(compl_T *match);
 static int  ins_compl_key2dir(int c);
 static int  ins_compl_pum_key(int c);
 static int  ins_compl_key2count(int c);
@@ -994,6 +995,37 @@
     return (i >= 2);
 }
 
+    static void
+trigger_complete_changed_event(int cur)
+{
+    dict_T	    *v_event;
+    dict_T	    *item;
+    static int	    recursive = FALSE;
+
+    if (recursive)
+	return;
+
+    v_event = get_vim_var_dict(VV_EVENT);
+    if (cur < 0)
+	item = dict_alloc();
+    else
+	item = ins_compl_dict_alloc(compl_curr_match);
+    if (item == NULL)
+	return;
+    dict_add_dict(v_event, "completed_item", item);
+    pum_set_event_info(v_event);
+    dict_set_items_ro(v_event);
+
+    recursive = TRUE;
+    textlock++;
+    apply_autocmds(EVENT_COMPLETECHANGED, NULL, NULL, FALSE, curbuf);
+    textlock--;
+    recursive = FALSE;
+
+    dict_free_contents(v_event);
+    hash_init(&v_event->dv_hashtab);
+}
+
 /*
  * Show the popup menu for the list of matches.
  * Also adjusts "compl_shown_match" to an entry that is actually displayed.
@@ -1136,6 +1168,9 @@
 	curwin->w_cursor.col = compl_col;
 	pum_display(compl_match_array, compl_match_arraysize, cur);
 	curwin->w_cursor.col = col;
+
+	if (has_completechanged())
+	    trigger_complete_changed_event(cur);
     }
 }
 
@@ -2899,26 +2934,34 @@
 	compl_used_match = FALSE;
     else
 	compl_used_match = TRUE;
-
-    // Set completed item.
-    // { word, abbr, menu, kind, info }
-    dict = dict_alloc_lock(VAR_FIXED);
-    if (dict != NULL)
-    {
-	dict_add_string(dict, "word", compl_shown_match->cp_str);
-	dict_add_string(dict, "abbr", compl_shown_match->cp_text[CPT_ABBR]);
-	dict_add_string(dict, "menu", compl_shown_match->cp_text[CPT_MENU]);
-	dict_add_string(dict, "kind", compl_shown_match->cp_text[CPT_KIND]);
-	dict_add_string(dict, "info", compl_shown_match->cp_text[CPT_INFO]);
-	dict_add_string(dict, "user_data",
-				 compl_shown_match->cp_text[CPT_USER_DATA]);
-    }
+    dict = ins_compl_dict_alloc(compl_shown_match);
     set_vim_var_dict(VV_COMPLETED_ITEM, dict);
     if (!in_compl_func)
 	compl_curr_match = compl_shown_match;
 }
 
 /*
+ * Allocate Dict for the completed item.
+ * { word, abbr, menu, kind, info }
+ */
+    static dict_T *
+ins_compl_dict_alloc(compl_T *match)
+{
+    dict_T *dict = dict_alloc_lock(VAR_FIXED);
+
+    if (dict != NULL)
+    {
+	dict_add_string(dict, "word", match->cp_str);
+	dict_add_string(dict, "abbr", match->cp_text[CPT_ABBR]);
+	dict_add_string(dict, "menu", match->cp_text[CPT_MENU]);
+	dict_add_string(dict, "kind", match->cp_text[CPT_KIND]);
+	dict_add_string(dict, "info", match->cp_text[CPT_INFO]);
+	dict_add_string(dict, "user_data", match->cp_text[CPT_USER_DATA]);
+    }
+    return dict;
+}
+
+/*
  * Fill in the next completion in the current direction.
  * If "allow_get_expansion" is TRUE, then we may call ins_compl_get_exp() to
  * get more completions.  If it is FALSE, then we just do nothing when there
diff --git a/src/popupmnu.c b/src/popupmnu.c
index 7be8a9b..ba9c7ad 100644
--- a/src/popupmnu.c
+++ b/src/popupmnu.c
@@ -923,6 +923,22 @@
     return pum_height;
 }
 
+/*
+ * Add size information about the pum to "dict".
+ */
+    void
+pum_set_event_info(dict_T *dict)
+{
+    if (!pum_visible())
+	return;
+    dict_add_number(dict, "height", pum_height);
+    dict_add_number(dict, "width", pum_width);
+    dict_add_number(dict, "row", pum_row);
+    dict_add_number(dict, "col", pum_col);
+    dict_add_number(dict, "size", pum_size);
+    dict_add_special(dict, "scrollbar", pum_scrollbar ? VVAL_TRUE : VVAL_FALSE);
+}
+
 # if defined(FEAT_BEVAL_TERM) || defined(FEAT_TERM_POPUP_MENU) || defined(PROTO)
     static void
 pum_position_at_mouse(int min_width)
diff --git a/src/proto/autocmd.pro b/src/proto/autocmd.pro
index 8c6d18f..a448dad 100644
--- a/src/proto/autocmd.pro
+++ b/src/proto/autocmd.pro
@@ -26,6 +26,7 @@
 int has_cmdundefined(void);
 int has_funcundefined(void);
 int has_textyankpost(void);
+int has_completechanged(void);
 void block_autocmds(void);
 void unblock_autocmds(void);
 int is_autocmd_blocked(void);
diff --git a/src/proto/dict.pro b/src/proto/dict.pro
index b09a647..6d9ae19 100644
--- a/src/proto/dict.pro
+++ b/src/proto/dict.pro
@@ -14,6 +14,7 @@
 dict_T *dict_copy(dict_T *orig, int deep, int copyID);
 int dict_add(dict_T *d, dictitem_T *item);
 int dict_add_number(dict_T *d, char *key, varnumber_T nr);
+int dict_add_special(dict_T *d, char *key, varnumber_T nr);
 int dict_add_string(dict_T *d, char *key, char_u *str);
 int dict_add_string_len(dict_T *d, char *key, char_u *str, int len);
 int dict_add_list(dict_T *d, char *key, list_T *list);
diff --git a/src/proto/popupmnu.pro b/src/proto/popupmnu.pro
index e2ae92a..c019663 100644
--- a/src/proto/popupmnu.pro
+++ b/src/proto/popupmnu.pro
@@ -8,6 +8,7 @@
 int pum_visible(void);
 void pum_may_redraw(void);
 int pum_get_height(void);
+void pum_set_event_info(dict_T *dict);
 int split_message(char_u *mesg, pumitem_T **array);
 void ui_remove_balloon(void);
 void ui_post_balloon(char_u *mesg, list_T *list);
diff --git a/src/testdir/test_popup.vim b/src/testdir/test_popup.vim
index a841678..538dd22 100644
--- a/src/testdir/test_popup.vim
+++ b/src/testdir/test_popup.vim
@@ -1029,4 +1029,38 @@
   bwipe!
 endfunc
 
+func Test_CompleteChanged()
+  new
+  call setline(1, ['foo', 'bar', 'foobar', ''])
+  set complete=. completeopt=noinsert,noselect,menuone
+  function! OnPumChange()
+    let g:event = copy(v:event)
+    let g:item = get(v:event, 'completed_item', {})
+    let g:word = get(g:item, 'word', v:null)
+  endfunction
+  augroup AAAAA_Group
+    au!
+    autocmd CompleteChanged * :call OnPumChange()
+  augroup END
+  call cursor(4, 1)
+
+  call feedkeys("Sf\<C-N>", 'tx')
+  call assert_equal({'completed_item': {}, 'width': 15,
+        \ 'height': 2, 'size': 2,
+        \ 'col': 0, 'row': 4, 'scrollbar': v:false}, g:event)
+  call feedkeys("a\<C-N>\<C-N>\<C-E>", 'tx')
+  call assert_equal('foo', g:word)
+  call feedkeys("a\<C-N>\<C-N>\<C-N>\<C-E>", 'tx')
+  call assert_equal('foobar', g:word)
+  call feedkeys("a\<C-N>\<C-N>\<C-N>\<C-N>\<C-E>", 'tx')
+  call assert_equal(v:null, g:word)
+  call feedkeys("a\<C-N>\<C-N>\<C-N>\<C-N>\<C-P>", 'tx')
+  call assert_equal('foobar', g:word)
+
+  autocmd! AAAAA_Group
+  set complete& completeopt&
+  delfunc! OnPumchange
+  bw!
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index d2da9fc..dadb017 100644
--- a/src/version.c
+++ b/src/version.c
@@ -772,6 +772,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1138,
+/**/
     1137,
 /**/
     1136,
diff --git a/src/vim.h b/src/vim.h
index 01123e0..af779f3 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -1270,6 +1270,7 @@
     EVENT_CMDWINLEAVE,		// before leaving the cmdline window
     EVENT_COLORSCHEME,		// after loading a colorscheme
     EVENT_COLORSCHEMEPRE,	// before loading a colorscheme
+    EVENT_COMPLETECHANGED,	// after completion popup menu changed
     EVENT_COMPLETEDONE,		// after finishing insert complete
     EVENT_CURSORHOLD,		// cursor in same position for a while
     EVENT_CURSORHOLDI,		// idem, in Insert mode