From 900975d30dfa74f325f0f5aa0cef089ae45ee71d Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 9 May 2026 07:56:08 +0800 Subject: [PATCH] vim-patch:9.2.0455: 'findfunc' only allows extra info for cmdline completion Problem: 'findfunc' only allows extra info for cmdline completion, not for actually finding files (Maxim Kim, after 9.2.0451). Solution: Handle returning a list of dicts when actually finding files. Also fix crash on NULL string (zeertzjq). fixes: vim/vim#20163 closes: vim/vim#20164 https://github.com/vim/vim/commit/9694ff58fe510bf3906a7b6817429e29d5975987 --- runtime/doc/options.txt | 5 ++-- runtime/lua/vim/_meta/options.gen.lua | 5 ++-- src/nvim/cmdexpand.c | 12 ++++---- src/nvim/ex_docmd.c | 11 +++++-- src/nvim/options.lua | 5 ++-- test/old/testdir/test_findfile.vim | 42 +++++++++++++++++++++++---- 6 files changed, 57 insertions(+), 23 deletions(-) diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 793b200c7e..6f0c3deb68 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -2935,9 +2935,8 @@ A jump table for the options with a short description can be found at |Q_op|. |String| and is the |:find| command argument. The second argument is a |Boolean| and is set to |v:true| when the function is called to get a List of command-line completion matches for the |:find| command. - The function should return a List of strings, or, in the command-line - completion case, whatever a |:command-completion-customlist| function - may return. + The function should return a List, which is handled similarly to the + return value of a |:command-completion-customlist| function. The function is called only once per |:find| command invocation. The function can process all the directories specified in 'path'. diff --git a/runtime/lua/vim/_meta/options.gen.lua b/runtime/lua/vim/_meta/options.gen.lua index e2d5b82957..732c8df891 100644 --- a/runtime/lua/vim/_meta/options.gen.lua +++ b/runtime/lua/vim/_meta/options.gen.lua @@ -2625,9 +2625,8 @@ vim.go.fcs = vim.go.fillchars --- `String` and is the `:find` command argument. The second argument is --- a `Boolean` and is set to `v:true` when the function is called to get --- a List of command-line completion matches for the `:find` command. ---- The function should return a List of strings, or, in the command-line ---- completion case, whatever a `:command-completion-customlist` function ---- may return. +--- The function should return a List, which is handled similarly to the +--- return value of a `:command-completion-customlist` function. --- --- The function is called only once per `:find` command invocation. --- The function can process all the directories specified in 'path'. diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index ac0d71218f..cf15e4dcef 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -3575,20 +3575,20 @@ void expand_process_user_list(list_T *retlist, char ***matches, int *numMatches, ga_init(&ga_info, sizeof(char *), 3); // Loop over the items in the list. TV_LIST_ITER_CONST(retlist, li, { + const typval_T *tv = TV_LIST_ITEM_TV(li); char *p = NULL; char *abbr = NULL; char *kind = NULL; char *menu = NULL; char *info = NULL; - if (TV_LIST_ITEM_TV(li)->v_type == VAR_STRING) { - if (TV_LIST_ITEM_TV(li)->vval.v_string == NULL) { + if (tv->v_type == VAR_STRING) { + if (tv->vval.v_string == NULL) { continue; // Skip NULL strings } - p = xstrdup(TV_LIST_ITEM_TV(li)->vval.v_string); - } else if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT - && TV_LIST_ITEM_TV(li)->vval.v_dict != NULL) { - dict_T *d = TV_LIST_ITEM_TV(li)->vval.v_dict; + p = xstrdup(tv->vval.v_string); + } else if (tv->v_type == VAR_DICT && tv->vval.v_dict != NULL) { + dict_T *d = tv->vval.v_dict; char *word = tv_dict_get_string(d, "word", false); if (word == NULL) { diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 0fcf52ba58..3e30fff410 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -5694,9 +5694,14 @@ static char *findfunc_find_file(char *findarg, size_t findarg_len, int count) if (count > fname_count) { semsg(_(e_no_more_file_str_found_in_path), findarg); } else { - listitem_T *li = tv_list_find(fname_list, count - 1); - if (li != NULL && TV_LIST_ITEM_TV(li)->v_type == VAR_STRING) { - ret_fname = TO_SLASH_SAVE(TV_LIST_ITEM_TV(li)->vval.v_string); + const listitem_T *li = tv_list_find(fname_list, count - 1); + if (li != NULL) { + const typval_T *tv = TV_LIST_ITEM_TV(li); + if (tv->v_type == VAR_STRING && tv->vval.v_string != NULL) { + ret_fname = xstrdup(tv->vval.v_string); + } else if (tv->v_type == VAR_DICT && tv->vval.v_dict != NULL) { + ret_fname = tv_dict_get_string(tv->vval.v_dict, "word", true); + } } } } diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 62fd3b2cae..48a455b55f 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -3297,9 +3297,8 @@ local options = { |String| and is the |:find| command argument. The second argument is a |Boolean| and is set to |v:true| when the function is called to get a List of command-line completion matches for the |:find| command. - The function should return a List of strings, or, in the command-line - completion case, whatever a |:command-completion-customlist| function - may return. + The function should return a List, which is handled similarly to the + return value of a |:command-completion-customlist| function. The function is called only once per |:find| command invocation. The function can process all the directories specified in 'path'. diff --git a/test/old/testdir/test_findfile.vim b/test/old/testdir/test_findfile.vim index deba064479..1c79a76100 100644 --- a/test/old/testdir/test_findfile.vim +++ b/test/old/testdir/test_findfile.vim @@ -335,22 +335,22 @@ func Test_findfunc() set findfunc=FindFuncBasic find Xfindfunc3 - call assert_match('Xfindfunc3.c', @%) + call assert_match('Xfindfunc3\.c', @%) bw! 2find Xfind - call assert_match('Xfindfunc2.c', @%) + call assert_match('Xfindfunc2\.c', @%) bw! call assert_fails('4find Xfind', 'E347: No more file "Xfind" found in path') call assert_fails('find foobar', 'E345: Can''t find file "foobar" in path') sfind Xfindfunc2.c - call assert_match('Xfindfunc2.c', @%) + call assert_match('Xfindfunc2\.c', @%) call assert_equal(2, winnr('$')) %bw! call assert_fails('sfind foobar', 'E345: Can''t find file "foobar" in path') tabfind Xfindfunc3.c - call assert_match('Xfindfunc3.c', @%) + call assert_match('Xfindfunc3\.c', @%) call assert_equal(2, tabpagenr()) %bw! call assert_fails('tabfind foobar', 'E345: Can''t find file "foobar" in path') @@ -358,12 +358,44 @@ func Test_findfunc() " Test garbage collection call test_garbagecollect_now() find Xfindfunc2 - call assert_match('Xfindfunc2.c', @%) + call assert_match('Xfindfunc2\.c', @%) bw! delfunc FindFuncBasic call test_garbagecollect_now() call assert_fails('find Xfindfunc2', 'E117: Unknown function: FindFuncBasic') + " 'findfunc' with dicts in the returned list + func FindFuncDict(pat, cmdcomplete) + return [ + \ #{word: 'Xfindfunc1.c', abbr: 'Xff1.c'}, + \ #{word: 'Xfindfunc2.c'}, + \ 'Xfindfunc3.c', + "\ invalid values + \ #{abbr: 'XXX'}, + \ v:_null_dict, + \ v:_null_string, + \ ] + endfunc + + set findfunc=FindFuncDict + find Xfind + call assert_match('Xfindfunc1\.c', @%) + bw! + 2find Xfind + call assert_match('Xfindfunc2\.c', @%) + bw! + 3find Xfind + call assert_match('Xfindfunc3\.c', @%) + bw! + " These invalid values should not crash + 4find Xfind + 5find Xfind + 6find Xfind + call assert_fails('7find Xfind', 'E347: No more file "Xfind" found in path') + call assert_equal('', @%) + %bw! + delfunc FindFuncDict + " Buffer-local option func GlobalFindFunc(pat, cmdcomplete) return ['global']