patch 9.2.0455: 'findfunc' only allows extra info for cmdline completion

Commit: 
https://github.com/vim/vim/commit/9694ff58fe510bf3906a7b6817429e29d5975987
Author: zeertzjq <[email protected]>
Date:   Fri May 8 21:09:48 2026 +0000

    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:  #20163
    closes: #20164
    
    Signed-off-by: zeertzjq <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index e82cd3055..969a17c16 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -1,4 +1,4 @@
-*options.txt*  For Vim version 9.2.  Last change: 2026 May 07
+*options.txt*  For Vim version 9.2.  Last change: 2026 May 08
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -3983,9 +3983,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/src/cmdexpand.c b/src/cmdexpand.c
index a763456a1..3c660b5ea 100644
--- a/src/cmdexpand.c
+++ b/src/cmdexpand.c
@@ -4172,22 +4172,22 @@ expand_process_user_list(
     // Loop over the items in the list.
     FOR_ALL_LIST_ITEMS(retlist, li)
     {
+       typval_T *tv = &li->li_tv;
        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)
+       if (tv->v_type == VAR_STRING)
        {
-           if (li->li_tv.vval.v_string == NULL)
+           if (tv->vval.v_string == NULL)
                continue;  // Skip NULL 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)
+       else if (tv->v_type == VAR_DICT && tv->vval.v_dict != NULL)
        {
-           dict_T      *d = li->li_tv.vval.v_dict;
+           dict_T      *d = tv->vval.v_dict;
            char_u      *word = dict_get_string(d, "word", FALSE);
 
            if (word == NULL)
diff --git a/src/ex_docmd.c b/src/ex_docmd.c
index 7b31bed99..11ea59e3b 100644
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -7119,8 +7119,16 @@ findfunc_find_file(char_u *findarg, int findarg_len, int 
count)
        else
        {
            listitem_T *li = list_find(fname_list, count - 1);
-           if (li != NULL && li->li_tv.v_type == VAR_STRING)
-               ret_fname = vim_strsave(li->li_tv.vval.v_string);
+
+           if (li != NULL)
+           {
+               typval_T *tv = &li->li_tv;
+
+               if (tv->v_type == VAR_STRING && tv->vval.v_string != NULL)
+                   ret_fname = vim_strsave(tv->vval.v_string);
+               else if (tv->v_type == VAR_DICT && tv->vval.v_dict != NULL)
+                   ret_fname = dict_get_string(tv->vval.v_dict, "word", TRUE);
+           }
        }
     }
 
diff --git a/src/testdir/test_findfile.vim b/src/testdir/test_findfile.vim
index 42c3fb643..a5f1f6fdd 100644
--- a/src/testdir/test_findfile.vim
+++ b/src/testdir/test_findfile.vim
@@ -329,22 +329,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')
@@ -352,12 +352,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'},
+          \ test_null_dict(),
+          \ test_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']
diff --git a/src/version.c b/src/version.c
index 0103f023e..36f96c055 100644
--- a/src/version.c
+++ b/src/version.c
@@ -729,6 +729,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    455,
 /**/
     454,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1wLSXQ-00CfvL-LX%40256bit.org.

Raspunde prin e-mail lui