mirror of
https://github.com/vim/vim.git
synced 2026-05-28 00:21:37 +02:00
patch 9.2.0433: customlist completion cannot supply pum metadata
Problem: customlist completion cannot supply pum metadata
Solution: Allow each item returned by a customlist function to be
either a string or a Dict with keys "word", "abbr", "kind",
"menu" and "info" (Yasuhiro Matsumoto).
closes: #20100
Signed-off-by: Yasuhiro Matsumoto <mattn.jp@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
committed by
Christian Brabandt
parent
3bd25c63b4
commit
5c700152ae
+16
-2
@@ -1,4 +1,4 @@
|
||||
*map.txt* For Vim version 9.2. Last change: 2026 Feb 14
|
||||
*map.txt* For Vim version 9.2. Last change: 2026 May 02
|
||||
|
||||
|
||||
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||
@@ -1693,7 +1693,21 @@ For the "custom" argument, the function should return the completion
|
||||
candidates one per line in a newline separated string.
|
||||
*E1303*
|
||||
For the "customlist" argument, the function should return the completion
|
||||
candidates as a Vim List. Non-string items in the list are ignored.
|
||||
candidates as a Vim List. Each item may be either a string or a |Dictionary|.
|
||||
A Dictionary item may have the following keys:
|
||||
word (required) the text inserted into the command line when the
|
||||
item is selected
|
||||
abbr alternative text shown in the popup menu in place of "word",
|
||||
when 'wildoptions' contains "pum"; useful when the inserted
|
||||
text and the displayed text should differ
|
||||
kind short kind text (one or two characters), shown in the popup
|
||||
menu when 'wildoptions' contains "pum"
|
||||
menu extra text shown after the match in the popup menu
|
||||
info long description shown in the info popup; the |+popupwin|
|
||||
feature is required to display it
|
||||
Items that are neither a string nor a Dictionary, and Dictionary items without
|
||||
a "word" key, are ignored. When 'wildoptions' does not contain "pum", only
|
||||
"word" is shown.
|
||||
|
||||
The function arguments are:
|
||||
ArgLead the leading portion of the argument currently being
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
*version9.txt* For Vim version 9.2. Last change: 2026 May 01
|
||||
*version9.txt* For Vim version 9.2. Last change: 2026 May 02
|
||||
|
||||
|
||||
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||
@@ -52623,6 +52623,8 @@ Other ~
|
||||
'completeopt' option
|
||||
- Channel can handle |Blob| messages |channel-open-options|.
|
||||
- Added the "u" flag to 'shortmess' to silence undo/redo messages: |shm-u|
|
||||
- |:command-completion-customlist| can return a list of dictionaries with
|
||||
kind/menu/info/abbr for the popup menu.
|
||||
|
||||
Platform specific ~
|
||||
-----------------
|
||||
|
||||
+116
-13
@@ -412,10 +412,16 @@ cmdline_pum_create(
|
||||
compl_match_arraysize = numMatches;
|
||||
for (int i = 0; i < numMatches; i++)
|
||||
{
|
||||
compl_match_array[i].pum_text = SHOW_MATCH(i);
|
||||
compl_match_array[i].pum_info = NULL;
|
||||
compl_match_array[i].pum_extra = NULL;
|
||||
compl_match_array[i].pum_kind = NULL;
|
||||
compl_match_array[i].pum_text = (xp->xp_files_abbr != NULL
|
||||
&& xp->xp_files_abbr[i] != NULL)
|
||||
? xp->xp_files_abbr[i]
|
||||
: SHOW_MATCH(i);
|
||||
compl_match_array[i].pum_info = xp->xp_files_info != NULL
|
||||
? xp->xp_files_info[i] : NULL;
|
||||
compl_match_array[i].pum_extra = xp->xp_files_menu != NULL
|
||||
? xp->xp_files_menu[i] : NULL;
|
||||
compl_match_array[i].pum_kind = xp->xp_files_kind != NULL
|
||||
? xp->xp_files_kind[i] : NULL;
|
||||
compl_match_array[i].pum_user_abbr_hlattr = -1;
|
||||
compl_match_array[i].pum_user_kind_hlattr = -1;
|
||||
}
|
||||
@@ -1021,6 +1027,31 @@ find_longest_match(expand_T *xp, int options)
|
||||
return ss;
|
||||
}
|
||||
|
||||
static void
|
||||
free_xp_files_extra(expand_T *xp, int numfiles)
|
||||
{
|
||||
if (xp->xp_files_abbr != NULL)
|
||||
{
|
||||
FreeWild(numfiles, xp->xp_files_abbr);
|
||||
xp->xp_files_abbr = NULL;
|
||||
}
|
||||
if (xp->xp_files_kind != NULL)
|
||||
{
|
||||
FreeWild(numfiles, xp->xp_files_kind);
|
||||
xp->xp_files_kind = NULL;
|
||||
}
|
||||
if (xp->xp_files_menu != NULL)
|
||||
{
|
||||
FreeWild(numfiles, xp->xp_files_menu);
|
||||
xp->xp_files_menu = NULL;
|
||||
}
|
||||
if (xp->xp_files_info != NULL)
|
||||
{
|
||||
FreeWild(numfiles, xp->xp_files_info);
|
||||
xp->xp_files_info = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Do wildcard expansion on the string "str".
|
||||
* Chars that should not be expanded must be preceded with a backslash.
|
||||
@@ -1087,6 +1118,7 @@ ExpandOne(
|
||||
if (xp->xp_numfiles != -1 && mode != WILD_ALL && mode != WILD_LONGEST)
|
||||
{
|
||||
FreeWild(xp->xp_numfiles, xp->xp_files);
|
||||
free_xp_files_extra(xp, xp->xp_numfiles);
|
||||
xp->xp_numfiles = -1;
|
||||
VIM_CLEAR(xp->xp_orig);
|
||||
|
||||
@@ -1188,6 +1220,7 @@ ExpandCleanup(expand_T *xp)
|
||||
{
|
||||
if (xp->xp_numfiles >= 0)
|
||||
{
|
||||
free_xp_files_extra(xp, xp->xp_numfiles);
|
||||
FreeWild(xp->xp_numfiles, xp->xp_files);
|
||||
xp->xp_numfiles = -1;
|
||||
}
|
||||
@@ -1424,7 +1457,10 @@ showmatches(
|
||||
}
|
||||
|
||||
if (xp->xp_numfiles == -1)
|
||||
{
|
||||
FreeWild(numMatches, matches);
|
||||
free_xp_files_extra(xp, numMatches);
|
||||
}
|
||||
|
||||
return EXPAND_OK;
|
||||
}
|
||||
@@ -4124,6 +4160,12 @@ ExpandUserList(
|
||||
list_T *retlist;
|
||||
listitem_T *li;
|
||||
garray_T ga;
|
||||
garray_T ga_abbr;
|
||||
garray_T ga_kind;
|
||||
garray_T ga_menu;
|
||||
garray_T ga_info;
|
||||
int have_extra = FALSE;
|
||||
int i;
|
||||
|
||||
*matches = NULL;
|
||||
*numMatches = 0;
|
||||
@@ -4132,31 +4174,92 @@ ExpandUserList(
|
||||
return FAIL;
|
||||
|
||||
ga_init2(&ga, sizeof(char *), 3);
|
||||
ga_init2(&ga_abbr, sizeof(char *), 3);
|
||||
ga_init2(&ga_kind, sizeof(char *), 3);
|
||||
ga_init2(&ga_menu, sizeof(char *), 3);
|
||||
ga_init2(&ga_info, sizeof(char *), 3);
|
||||
// Loop over the items in the list.
|
||||
FOR_ALL_LIST_ITEMS(retlist, li)
|
||||
{
|
||||
char_u *p;
|
||||
char_u *p = NULL;
|
||||
char_u *abbr = NULL;
|
||||
char_u *kind = NULL;
|
||||
char_u *menu = NULL;
|
||||
char_u *info = NULL;
|
||||
|
||||
if (li->li_tv.v_type != VAR_STRING || li->li_tv.vval.v_string == NULL)
|
||||
continue; // Skip non-string items and empty strings
|
||||
if (li->li_tv.v_type == VAR_STRING)
|
||||
{
|
||||
if (li->li_tv.vval.v_string == NULL)
|
||||
continue; // Skip empty strings
|
||||
p = vim_strsave(li->li_tv.vval.v_string);
|
||||
}
|
||||
else if (li->li_tv.v_type == VAR_DICT
|
||||
&& li->li_tv.vval.v_dict != NULL)
|
||||
{
|
||||
dict_T *d = li->li_tv.vval.v_dict;
|
||||
char_u *word = dict_get_string(d, "word", FALSE);
|
||||
|
||||
p = vim_strsave(li->li_tv.vval.v_string);
|
||||
if (p == NULL)
|
||||
break;
|
||||
if (word == NULL)
|
||||
continue; // "word" is required
|
||||
p = vim_strsave(word);
|
||||
abbr = dict_get_string(d, "abbr", TRUE);
|
||||
kind = dict_get_string(d, "kind", TRUE);
|
||||
menu = dict_get_string(d, "menu", TRUE);
|
||||
info = dict_get_string(d, "info", TRUE);
|
||||
if (abbr != NULL || kind != NULL || menu != NULL || info != NULL)
|
||||
have_extra = TRUE;
|
||||
}
|
||||
else
|
||||
continue; // Skip other types
|
||||
|
||||
if (ga_grow(&ga, 1) == FAIL)
|
||||
if (p == NULL
|
||||
|| ga_grow(&ga, 1) == FAIL
|
||||
|| ga_grow(&ga_abbr, 1) == FAIL
|
||||
|| ga_grow(&ga_kind, 1) == FAIL
|
||||
|| ga_grow(&ga_menu, 1) == FAIL
|
||||
|| ga_grow(&ga_info, 1) == FAIL)
|
||||
{
|
||||
vim_free(p);
|
||||
vim_free(abbr);
|
||||
vim_free(kind);
|
||||
vim_free(menu);
|
||||
vim_free(info);
|
||||
break;
|
||||
}
|
||||
|
||||
((char_u **)ga.ga_data)[ga.ga_len] = p;
|
||||
++ga.ga_len;
|
||||
((char_u **)ga.ga_data)[ga.ga_len++] = p;
|
||||
((char_u **)ga_abbr.ga_data)[ga_abbr.ga_len++] = abbr;
|
||||
((char_u **)ga_kind.ga_data)[ga_kind.ga_len++] = kind;
|
||||
((char_u **)ga_menu.ga_data)[ga_menu.ga_len++] = menu;
|
||||
((char_u **)ga_info.ga_data)[ga_info.ga_len++] = info;
|
||||
}
|
||||
list_unref(retlist);
|
||||
|
||||
*matches = ga.ga_data;
|
||||
*numMatches = ga.ga_len;
|
||||
if (have_extra && ga.ga_len > 0)
|
||||
{
|
||||
xp->xp_files_abbr = (char_u **)ga_abbr.ga_data;
|
||||
xp->xp_files_kind = (char_u **)ga_kind.ga_data;
|
||||
xp->xp_files_menu = (char_u **)ga_menu.ga_data;
|
||||
xp->xp_files_info = (char_u **)ga_info.ga_data;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No extra info collected; free the placeholder NULL entries.
|
||||
for (i = 0; i < ga_abbr.ga_len; i++)
|
||||
vim_free(((char_u **)ga_abbr.ga_data)[i]);
|
||||
vim_free(ga_abbr.ga_data);
|
||||
for (i = 0; i < ga_kind.ga_len; i++)
|
||||
vim_free(((char_u **)ga_kind.ga_data)[i]);
|
||||
vim_free(ga_kind.ga_data);
|
||||
for (i = 0; i < ga_menu.ga_len; i++)
|
||||
vim_free(((char_u **)ga_menu.ga_data)[i]);
|
||||
vim_free(ga_menu.ga_data);
|
||||
for (i = 0; i < ga_info.ga_len; i++)
|
||||
vim_free(((char_u **)ga_info.ga_data)[i]);
|
||||
vim_free(ga_info.ga_data);
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -678,6 +678,17 @@ typedef struct expand
|
||||
int xp_selected; // selected index in completion
|
||||
char_u *xp_orig; // originally expanded string
|
||||
char_u **xp_files; // list of files
|
||||
char_u **xp_files_abbr; // optional parallel array of display
|
||||
// strings (override xp_files for the
|
||||
// pum text); NULL if unused
|
||||
char_u **xp_files_kind; // optional parallel array of "kind"
|
||||
// strings; NULL if unused
|
||||
char_u **xp_files_menu; // optional parallel array of "menu"
|
||||
// strings (shown after the match);
|
||||
// NULL if unused
|
||||
char_u **xp_files_info; // optional parallel array of "info"
|
||||
// strings (shown in info popup);
|
||||
// NULL if unused
|
||||
char_u *xp_line; // text being completed
|
||||
#define EXPAND_BUF_LEN 256
|
||||
char_u xp_buf[EXPAND_BUF_LEN]; // buffer for returned match
|
||||
|
||||
@@ -4594,6 +4594,53 @@ func Test_custom_completion()
|
||||
delfunc Check_customlist_completion
|
||||
endfunc
|
||||
|
||||
" Test that 'customlist' completion accepts dict items with extra info
|
||||
" (kind/menu/info) for display in the popup menu, and that string items still
|
||||
" work in the same list.
|
||||
func Test_customlist_dict_completion()
|
||||
func DictComp(A, L, P)
|
||||
return [
|
||||
\ {'word': 'apple', 'kind': 'f', 'menu': 'fruit', 'info': 'A red fruit'},
|
||||
\ {'word': 'banana', 'kind': 'f', 'menu': 'fruit', 'info': 'A yellow fruit'},
|
||||
\ {'word': 'carrot', 'kind': 'v', 'menu': 'vegetable', 'info': 'An orange vegetable'},
|
||||
\ 'plain',
|
||||
\ ]
|
||||
endfunc
|
||||
command -nargs=1 -complete=customlist,DictComp DictCmd echo <q-args>
|
||||
|
||||
" getcompletion() returns only the "word" of each item; string items pass
|
||||
" through unchanged.
|
||||
call assert_equal(['apple', 'banana', 'carrot', 'plain'],
|
||||
\ getcompletion('', 'customlist,DictComp'))
|
||||
|
||||
" Items missing a "word" key are silently skipped.
|
||||
func DictCompMissingWord(A, L, P)
|
||||
return [{'kind': 'x'}, {'word': 'ok'}]
|
||||
endfunc
|
||||
call assert_equal(['ok'],
|
||||
\ getcompletion('', 'customlist,DictCompMissingWord'))
|
||||
|
||||
" Tab completion still selects the word.
|
||||
call feedkeys(":DictCmd a\<Tab>\<C-B>\"\<CR>", 'xt')
|
||||
call assert_equal('"DictCmd apple', @:)
|
||||
|
||||
" "abbr" overrides display only; "word" is what gets inserted.
|
||||
func DictCompAbbr(A, L, P)
|
||||
return [{'word': 'apple', 'abbr': 'APPLE🍎'}]
|
||||
endfunc
|
||||
call assert_equal(['apple'],
|
||||
\ getcompletion('', 'customlist,DictCompAbbr'))
|
||||
command -nargs=1 -complete=customlist,DictCompAbbr DictAbbrCmd echo <q-args>
|
||||
call feedkeys(":DictAbbrCmd \<Tab>\<C-B>\"\<CR>", 'xt')
|
||||
call assert_equal('"DictAbbrCmd apple', @:)
|
||||
|
||||
delcommand DictAbbrCmd
|
||||
delcommand DictCmd
|
||||
delfunc DictComp
|
||||
delfunc DictCompMissingWord
|
||||
delfunc DictCompAbbr
|
||||
endfunc
|
||||
|
||||
func Test_custom_completion_with_glob()
|
||||
func TestGlobComplete(A, L, P)
|
||||
return split(glob('Xglob*'), "\n")
|
||||
|
||||
@@ -729,6 +729,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
433,
|
||||
/**/
|
||||
432,
|
||||
/**/
|
||||
|
||||
Reference in New Issue
Block a user