changeset 19047:a3fce2763e83 v8.2.0084

patch 8.2.0084: complete item "user_data" can only be a string Commit: https://github.com/vim/vim/commit/0892832bb6c7e322fcae8560eaad5a8140ee4a06 Author: Bram Moolenaar <Bram@vim.org> Date: Sat Jan 4 14:32:48 2020 +0100 patch 8.2.0084: complete item "user_data" can only be a string Problem: Complete item "user_data" can only be a string. Solution: Accept any type of variable. (closes https://github.com/vim/vim/issues/5412)
author Bram Moolenaar <Bram@vim.org>
date Sat, 04 Jan 2020 14:45:04 +0100
parents 69e878ae5d4b
children 7b6a4712b11f
files runtime/doc/insert.txt src/dict.c src/eval.c src/insexpand.c src/proto/dict.pro src/testdir/test_ins_complete.vim src/version.c
diffstat 7 files changed, 99 insertions(+), 44 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/insert.txt
+++ b/runtime/doc/insert.txt
@@ -1108,7 +1108,8 @@ items:
 	empty		when non-zero this match will be added even when it is
 			an empty string
 	user_data 	custom data which is associated with the item and
-			available in |v:completed_item|
+			available in |v:completed_item|; it can be any type;
+			defaults to an empty string
 
 All of these except "icase", "equal", "dup" and "empty" must be a string.  If
 an item does not meet these requirements then an error message is given and
--- a/src/dict.c
+++ b/src/dict.c
@@ -448,6 +448,27 @@ dict_add_list(dict_T *d, char *key, list
 }
 
 /*
+ * Add a typval_T entry to dictionary "d".
+ * Returns FAIL when out of memory and when key already exists.
+ */
+    int
+dict_add_tv(dict_T *d, char *key, typval_T *tv)
+{
+    dictitem_T	*item;
+
+    item = dictitem_alloc((char_u *)key);
+    if (item == NULL)
+	return FAIL;
+    copy_tv(tv, &item->di_tv);
+    if (dict_add(d, item) == FAIL)
+    {
+	dictitem_free(item);
+	return FAIL;
+    }
+    return OK;
+}
+
+/*
  * Add a callback to dictionary "d".
  * Returns FAIL when out of memory and when key already exists.
  */
@@ -590,6 +611,23 @@ dict_find(dict_T *d, char_u *key, int le
 }
 
 /*
+ * Get a typval_T item from a dictionary and copy it into "rettv".
+ * Returns FAIL if the entry doesn't exist or out of memory.
+ */
+    int
+dict_get_tv(dict_T *d, char_u *key, typval_T *rettv)
+{
+    dictitem_T	*di;
+    char_u	*s;
+
+    di = dict_find(d, key, -1);
+    if (di == NULL)
+	return FAIL;
+    copy_tv(&di->di_tv, rettv);
+    return OK;
+}
+
+/*
  * Get a string item from a dictionary.
  * When "save" is TRUE allocate memory for it.
  * When FALSE a shared buffer is used, can only be used once!
@@ -745,7 +783,7 @@ get_literal_key(char_u **arg, typval_T *
  * Return OK or FAIL.  Returns NOTDONE for {expr}.
  */
     int
-dict_get_tv(char_u **arg, typval_T *rettv, int evaluate, int literal)
+eval_dict(char_u **arg, typval_T *rettv, int evaluate, int literal)
 {
     dict_T	*d = NULL;
     typval_T	tvkey;
--- a/src/eval.c
+++ b/src/eval.c
@@ -2656,7 +2656,7 @@ eval7(
     case '#':	if ((*arg)[1] == '{')
 		{
 		    ++*arg;
-		    ret = dict_get_tv(arg, rettv, evaluate, TRUE);
+		    ret = eval_dict(arg, rettv, evaluate, TRUE);
 		}
 		else
 		    ret = NOTDONE;
@@ -2668,7 +2668,7 @@ eval7(
      */
     case '{':	ret = get_lambda_tv(arg, rettv, evaluate);
 		if (ret == NOTDONE)
-		    ret = dict_get_tv(arg, rettv, evaluate, FALSE);
+		    ret = eval_dict(arg, rettv, evaluate, FALSE);
 		break;
 
     /*
--- a/src/insexpand.c
+++ b/src/insexpand.c
@@ -91,8 +91,7 @@ static char *ctrl_x_mode_names[] = {
 #define CPT_MENU	1	// "menu"
 #define CPT_KIND	2	// "kind"
 #define CPT_INFO	3	// "info"
-#define CPT_USER_DATA	4	// "user data"
-#define CPT_COUNT	5	// Number of entries
+#define CPT_COUNT	4	// Number of entries
 
 /*
  * Structure used to store one match for insert completion.
@@ -104,6 +103,7 @@ struct compl_S
     compl_T	*cp_prev;
     char_u	*cp_str;	// matched text
     char_u	*(cp_text[CPT_COUNT]);	// text for the menu
+    typval_T	cp_user_data;
     char_u	*cp_fname;	// file containing the match, allocated when
 				// cp_flags has CP_FREE_FNAME
     int		cp_flags;	// CP_ values
@@ -187,7 +187,7 @@ static expand_T	  compl_xp;
 static int	  compl_opt_refresh_always = FALSE;
 static int	  compl_opt_suppress_empty = FALSE;
 
-static int ins_compl_add(char_u *str, int len, char_u *fname, char_u **cptext, int cdir, int flags, int adup);
+static int ins_compl_add(char_u *str, int len, char_u *fname, char_u **cptext, typval_T *user_data, int cdir, int flags, int adup);
 static void ins_compl_longest_match(compl_T *match);
 static void ins_compl_del_pum(void);
 static void ins_compl_files(int count, char_u **files, int thesaurus, int flags, regmatch_T *regmatch, char_u *buf, int *dir);
@@ -560,7 +560,7 @@ ins_compl_add_infercase(
     if (icase)
 	flags |= CP_ICASE;
 
-    return ins_compl_add(str, len, fname, NULL, dir, flags, FALSE);
+    return ins_compl_add(str, len, fname, NULL, NULL, dir, flags, FALSE);
 }
 
 /*
@@ -574,10 +574,11 @@ ins_compl_add(
     char_u	*str,
     int		len,
     char_u	*fname,
-    char_u	**cptext,   // extra text for popup menu or NULL
+    char_u	**cptext,	// extra text for popup menu or NULL
+    typval_T	*user_data,	// "user_data" entry or NULL
     int		cdir,
     int		flags_arg,
-    int		adup)	    // accept duplicate match
+    int		adup)		// accept duplicate match
 {
     compl_T	*match;
     int		dir = (cdir == 0 ? compl_direction : cdir);
@@ -646,6 +647,8 @@ ins_compl_add(
 	    if (cptext[i] != NULL && *cptext[i] != NUL)
 		match->cp_text[i] = vim_strsave(cptext[i]);
     }
+    if (user_data != NULL)
+	match->cp_user_data = *user_data;
 
     // Link the new match structure in the list of matches.
     if (compl_first_match == NULL)
@@ -783,7 +786,7 @@ ins_compl_add_matches(
     int		dir = compl_direction;
 
     for (i = 0; i < num_matches && add_r != FAIL; i++)
-	if ((add_r = ins_compl_add(matches[i], -1, NULL, NULL, dir,
+	if ((add_r = ins_compl_add(matches[i], -1, NULL, NULL, NULL, dir,
 					   icase ? CP_ICASE : 0, FALSE)) == OK)
 	    // if dir was BACKWARD then honor it just once
 	    dir = FORWARD;
@@ -952,7 +955,10 @@ ins_compl_dict_alloc(compl_T *match)
 	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]);
+	if (match->cp_user_data.v_type == VAR_UNKNOWN)
+	    dict_add_string(dict, "user_data", (char_u *)"");
+	else
+	    dict_add_tv(dict, "user_data", &match->cp_user_data);
     }
     return dict;
 }
@@ -1453,6 +1459,7 @@ ins_compl_free(void)
 	    vim_free(match->cp_fname);
 	for (i = 0; i < CPT_COUNT; ++i)
 	    vim_free(match->cp_text[i]);
+	clear_tv(&match->cp_user_data);
 	vim_free(match);
     } while (compl_curr_match != NULL && compl_curr_match != compl_first_match);
     compl_first_match = compl_curr_match = NULL;
@@ -2264,7 +2271,9 @@ ins_compl_add_tv(typval_T *tv, int dir)
     int		empty = FALSE;
     int		flags = 0;
     char_u	*(cptext[CPT_COUNT]);
-
+    typval_T	user_data;
+
+    user_data.v_type = VAR_UNKNOWN;
     if (tv->v_type == VAR_DICT && tv->vval.v_dict != NULL)
     {
 	word = dict_get_string(tv->vval.v_dict, (char_u *)"word", FALSE);
@@ -2276,8 +2285,7 @@ ins_compl_add_tv(typval_T *tv, int dir)
 						     (char_u *)"kind", FALSE);
 	cptext[CPT_INFO] = dict_get_string(tv->vval.v_dict,
 						     (char_u *)"info", FALSE);
-	cptext[CPT_USER_DATA] = dict_get_string(tv->vval.v_dict,
-						 (char_u *)"user_data", FALSE);
+	dict_get_tv(tv->vval.v_dict, (char_u *)"user_data", &user_data);
 	if (dict_get_string(tv->vval.v_dict, (char_u *)"icase", FALSE) != NULL
 			&& dict_get_number(tv->vval.v_dict, (char_u *)"icase"))
 	    flags |= CP_ICASE;
@@ -2296,7 +2304,7 @@ ins_compl_add_tv(typval_T *tv, int dir)
     }
     if (word == NULL || (!empty && *word == NUL))
 	return FAIL;
-    return ins_compl_add(word, -1, NULL, cptext, dir, flags, dup);
+    return ins_compl_add(word, -1, NULL, cptext, &user_data, dir, flags, dup);
 }
 
 /*
@@ -2373,7 +2381,7 @@ set_completion(colnr_T startcol, list_T 
     if (p_ic)
 	flags |= CP_ICASE;
     if (compl_orig_text == NULL || ins_compl_add(compl_orig_text,
-					-1, NULL, NULL, 0, flags, FALSE) != OK)
+				  -1, NULL, NULL, NULL, 0, flags, FALSE) != OK)
 	return;
 
     ctrl_x_mode = CTRL_X_EVAL;
@@ -2541,8 +2549,11 @@ get_complete_info(list_T *what_list, dic
 		    dict_add_string(di, "menu", match->cp_text[CPT_MENU]);
 		    dict_add_string(di, "kind", match->cp_text[CPT_KIND]);
 		    dict_add_string(di, "info", match->cp_text[CPT_INFO]);
-		    dict_add_string(di, "user_data",
-					    match->cp_text[CPT_USER_DATA]);
+		    if (match->cp_user_data.v_type == VAR_UNKNOWN)
+			// Add an empty string for backwards compatibility
+			dict_add_string(di, "user_data", (char_u *)"");
+		    else
+			dict_add_tv(di, "user_data", &match->cp_user_data);
 		}
 		match = match->cp_next;
 	    }
@@ -3893,7 +3904,7 @@ ins_complete(int c, int enable_pum)
 	if (p_ic)
 	    flags |= CP_ICASE;
 	if (compl_orig_text == NULL || ins_compl_add(compl_orig_text,
-					-1, NULL, NULL, 0, flags, FALSE) != OK)
+				  -1, NULL, NULL, NULL, 0, flags, FALSE) != OK)
 	{
 	    VIM_CLEAR(compl_pattern);
 	    VIM_CLEAR(compl_orig_text);
--- a/src/proto/dict.pro
+++ b/src/proto/dict.pro
@@ -18,18 +18,20 @@ int dict_add_special(dict_T *d, char *ke
 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);
+int dict_add_tv(dict_T *d, char *key, typval_T *tv);
 int dict_add_callback(dict_T *d, char *key, callback_T *cb);
 void dict_iterate_start(typval_T *var, dict_iterator_T *iter);
 char_u *dict_iterate_next(dict_iterator_T *iter, typval_T **tv_result);
 int dict_add_dict(dict_T *d, char *key, dict_T *dict);
 long dict_len(dict_T *d);
 dictitem_T *dict_find(dict_T *d, char_u *key, int len);
+int dict_get_tv(dict_T *d, char_u *key, typval_T *rettv);
 char_u *dict_get_string(dict_T *d, char_u *key, int save);
 varnumber_T dict_get_number(dict_T *d, char_u *key);
 varnumber_T dict_get_number_def(dict_T *d, char_u *key, int def);
 varnumber_T dict_get_number_check(dict_T *d, char_u *key);
 char_u *dict2string(typval_T *tv, int copyID, int restore_copyID);
-int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate, int literal);
+int eval_dict(char_u **arg, typval_T *rettv, int evaluate, int literal);
 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);
--- a/src/testdir/test_ins_complete.vim
+++ b/src/testdir/test_ins_complete.vim
@@ -158,17 +158,17 @@ func s:CompleteDone_CompleteFuncDict( fi
   endif
 
   return {
-          \ 'words': [
-            \ {
-              \ 'word': 'aword',
-              \ 'abbr': 'wrd',
-              \ 'menu': 'extra text',
-              \ 'info': 'words are cool',
-              \ 'kind': 'W',
-              \ 'user_data': 'test'
-            \ }
-          \ ]
-        \ }
+	  \ 'words': [
+	    \ {
+	      \ 'word': 'aword',
+	      \ 'abbr': 'wrd',
+	      \ 'menu': 'extra text',
+	      \ 'info': 'words are cool',
+	      \ 'kind': 'W',
+	      \ 'user_data': 'test'
+	    \ }
+	  \ ]
+	\ }
 endfunc
 
 func s:CompleteDone_CheckCompletedItemNone()
@@ -222,16 +222,17 @@ func s:CompleteDone_CompleteFuncDictNoUs
   endif
 
   return {
-          \ 'words': [
-            \ {
-              \ 'word': 'aword',
-              \ 'abbr': 'wrd',
-              \ 'menu': 'extra text',
-              \ 'info': 'words are cool',
-              \ 'kind': 'W'
-            \ }
-          \ ]
-        \ }
+	  \ 'words': [
+	    \ {
+	      \ 'word': 'aword',
+	      \ 'abbr': 'wrd',
+	      \ 'menu': 'extra text',
+	      \ 'info': 'words are cool',
+	      \ 'kind': 'W',
+	      \ 'user_data': ['one', 'two'],
+	    \ }
+	  \ ]
+	\ }
 endfunc
 
 func s:CompleteDone_CheckCompletedItemDictNoUserData()
@@ -240,7 +241,7 @@ func s:CompleteDone_CheckCompletedItemDi
   call assert_equal( 'extra text',     v:completed_item[ 'menu' ] )
   call assert_equal( 'words are cool', v:completed_item[ 'info' ] )
   call assert_equal( 'W',              v:completed_item[ 'kind' ] )
-  call assert_equal( '',               v:completed_item[ 'user_data' ] )
+  call assert_equal( ['one', 'two'],   v:completed_item[ 'user_data' ] )
 
   let s:called_completedone = 1
 endfunc
@@ -252,7 +253,7 @@ func Test_CompleteDoneDictNoUserData()
   execute "normal a\<C-X>\<C-U>\<C-Y>"
   set completefunc&
 
-  call assert_equal('', v:completed_item[ 'user_data' ])
+  call assert_equal(['one', 'two'], v:completed_item[ 'user_data' ])
   call assert_true(s:called_completedone)
 
   let s:called_completedone = 0
--- a/src/version.c
+++ b/src/version.c
@@ -743,6 +743,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    84,
+/**/
     83,
 /**/
     82,