# HG changeset patch # User Bram Moolenaar # Date 1578145504 -3600 # Node ID a3fce2763e8371ba14fdd0a898a7f6e001b9669e # Parent 69e878ae5d4bedfc044b16a8cdf8aa9c8ed05560 patch 8.2.0084: complete item "user_data" can only be a string Commit: https://github.com/vim/vim/commit/0892832bb6c7e322fcae8560eaad5a8140ee4a06 Author: Bram Moolenaar 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) diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt --- 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 diff --git a/src/dict.c b/src/dict.c --- 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; diff --git a/src/eval.c b/src/eval.c --- 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; /* diff --git a/src/insexpand.c b/src/insexpand.c --- 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); diff --git a/src/proto/dict.pro b/src/proto/dict.pro --- 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); diff --git a/src/testdir/test_ins_complete.vim b/src/testdir/test_ins_complete.vim --- 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\\\" 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 diff --git a/src/version.c b/src/version.c --- 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,