changeset 16268:0f65f2808470 v8.1.1138

patch 8.1.1138: plugins don't get notified when the popup menu changes commit https://github.com/vim/vim/commit/d7f246c68cfb97406bcd4b098a2df2d870b3ef92 Author: Bram Moolenaar <Bram@vim.org> Date: Mon Apr 8 18:15:41 2019 +0200 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 https://github.com/vim/vim/issues/4176)
author Bram Moolenaar <Bram@vim.org>
date Mon, 08 Apr 2019 18:30:06 +0200
parents b471858040bc
children 9d8d046fb47b
files runtime/doc/autocmd.txt src/autocmd.c src/dict.c src/insexpand.c src/popupmnu.c src/proto/autocmd.pro src/proto/dict.pro src/proto/popupmnu.pro src/testdir/test_popup.vim src/version.c src/vim.h
diffstat 11 files changed, 165 insertions(+), 18 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -367,6 +367,7 @@ Name			triggered by ~
 |SessionLoadPost|	after loading a session file
 
 |MenuPopup|		just before showing the popup menu
+|CompleteChanged|	after Insert mode completion menu changed
 |CompleteDone|		after Insert mode completion is done
 
 |User|			to be used in combination with ":doautocmd"
@@ -579,7 +580,22 @@ ColorScheme			After loading a color sche
 ColorSchemePre			Before loading a color scheme. |:colorscheme|
 				Useful to setup removing things added by a
 				color scheme, before another one is loaded.
+CompleteChanged 					*CompleteChanged*
+				After each time the Insert mode completion
+				menu changed.  Not fired on popup menu hide,
+				use |CompleteDone| for that.  Never triggered
+				recursively.
 
+				Sets these |v:event| keys:
+				    completed_item
+				    height		nr of items visible
+				    width		screen cells
+				    row			top screen row
+				    col			leftmost screen column
+				    size		total nr of items
+				    scrollbar		TRUE if visible
+
+				It is not allowed to change the text |textlock|.
 							*CompleteDone*
 CompleteDone			After Insert mode completion is done.  Either
 				when something was completed or abandoning
--- a/src/autocmd.c
+++ b/src/autocmd.c
@@ -112,6 +112,7 @@ static struct event_name
     {"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 @@ has_textyankpost(void)
 }
 #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.
--- a/src/dict.c
+++ b/src/dict.c
@@ -342,18 +342,18 @@ dict_add(dict_T *d, dictitem_T *item)
 }
 
 /*
- * 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 @@ dict_add_number(dict_T *d, char *key, va
 }
 
 /*
+ * 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.
  */
--- a/src/insexpand.c
+++ b/src/insexpand.c
@@ -203,6 +203,7 @@ static void ins_compl_fixRedoBufForLeade
 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 @@ pum_enough_matches(void)
     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 @@ ins_compl_show_pum(void)
 	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 @@ ins_compl_insert(int in_compl_func)
 	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
--- a/src/popupmnu.c
+++ b/src/popupmnu.c
@@ -923,6 +923,22 @@ pum_get_height(void)
     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)
--- a/src/proto/autocmd.pro
+++ b/src/proto/autocmd.pro
@@ -26,6 +26,7 @@ int has_insertcharpre(void);
 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);
--- a/src/proto/dict.pro
+++ b/src/proto/dict.pro
@@ -14,6 +14,7 @@ void dictitem_free(dictitem_T *item);
 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);
--- a/src/proto/popupmnu.pro
+++ b/src/proto/popupmnu.pro
@@ -8,6 +8,7 @@ void pum_clear(void);
 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);
--- a/src/testdir/test_popup.vim
+++ b/src/testdir/test_popup.vim
@@ -1029,4 +1029,38 @@ func Test_popup_complete_info_02()
   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
--- 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 */
 /**/
+    1138,
+/**/
     1137,
 /**/
     1136,
--- a/src/vim.h
+++ b/src/vim.h
@@ -1270,6 +1270,7 @@ enum auto_event
     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