]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.1.1603: completion: cannot use autoloaded funcs in 'complete' F{func} v9.1.1603
authorGirish Palya <girishji@gmail.com>
Fri, 8 Aug 2025 10:30:35 +0000 (12:30 +0200)
committerChristian Brabandt <cb@256bit.org>
Fri, 8 Aug 2025 10:30:35 +0000 (12:30 +0200)
Problem:  completion: cannot use autoloaded funcs in 'complete' F{func}
          (Maxim Kim)
Solution: Make it work (Girish Palya)

fixes: #17869
closes: #17885

Signed-off-by: Girish Palya <girishji@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
src/buffer.c
src/evalbuffer.c
src/insexpand.c
src/option.c
src/optionstr.c
src/proto/insexpand.pro
src/structs.h
src/testdir/test_ins_complete.vim
src/testdir/test_options.vim
src/version.c

index 5b66228255b3e7b8b1099bd1b8348394819165c9..5c8cd259b2efe040dc5c6244182694b346a556a6 100644 (file)
@@ -2509,6 +2509,8 @@ free_buf_options(
     free_callback(&buf->b_ofu_cb);
     clear_string_option(&buf->b_p_tsrfu);
     free_callback(&buf->b_tsrfu_cb);
+    clear_cpt_callbacks(&buf->b_p_cpt_cb, buf->b_p_cpt_count);
+    buf->b_p_cpt_count = 0;
 #endif
 #ifdef FEAT_QUICKFIX
     clear_string_option(&buf->b_p_gefm);
index cefd6428f6b2928ed3cd6eb653d4285ea3be6ce7..b6cc548ec5aaaf4735189a162f3b94fca477f994 100644 (file)
@@ -42,6 +42,9 @@ set_ref_in_buffers(int copyID)
            abort = abort || set_ref_in_callback(&bp->b_ofu_cb, copyID);
        if (!abort)
            abort = abort || set_ref_in_callback(&bp->b_tsrfu_cb, copyID);
+       if (!abort && bp->b_p_cpt_cb != NULL)
+           abort = abort || set_ref_in_cpt_callbacks(bp->b_p_cpt_cb,
+                   bp->b_p_cpt_count, copyID);
 #endif
        if (!abort)
            abort = abort || set_ref_in_callback(&bp->b_tfu_cb, copyID);
index 160020224c747a228f850a60f86dd9665546a757..03d946bb89672abaf3deb61598ee787c0a0aa298 100644 (file)
@@ -273,7 +273,7 @@ static void ins_compl_add_list(list_T *list);
 static void ins_compl_add_dict(dict_T *dict);
 static int get_userdefined_compl_info(colnr_T curs_col, callback_T *cb, int *startcol);
 static void get_cpt_func_completion_matches(callback_T *cb);
-static callback_T *get_callback_if_cpt_func(char_u *p);
+static callback_T *get_callback_if_cpt_func(char_u *p, int idx);
 # endif
 static int setup_cpt_sources(void);
 static int is_cpt_func_refresh_always(void);
@@ -3191,12 +3191,40 @@ ins_compl_next_buf(buf_T *buf, int flag)
     return buf;
 }
 
+/*
+ * Count the number of entries in the 'complete' option (curbuf->b_p_cpt).
+ * Each non-empty, comma-separated segment is counted as one entry.
+ */
+    static int
+get_cpt_sources_count(void)
+{
+    char_u  dummy[LSIZE];
+    int            count = 0;
+    char_u  *p;
+
+    for (p = curbuf->b_p_cpt; *p != NUL; )
+    {
+       while (*p == ',' || *p == ' ')
+           p++;  // Skip delimiters
+
+       if (*p != NUL)
+       {
+           (void)copy_option_part(&p, dummy, LSIZE, ",");  // Advance p
+           count++;
+       }
+    }
+
+    return count;
+}
+
 #ifdef FEAT_COMPL_FUNC
 
 # ifdef FEAT_EVAL
 static callback_T cfu_cb;          // 'completefunc' callback function
 static callback_T ofu_cb;          // 'omnifunc' callback function
 static callback_T tsrfu_cb;        // 'thesaurusfunc' callback function
+static callback_T *cpt_cb;         // Callback functions associated with F{func}
+static int       cpt_cb_count;     // Number of cpt callbacks
 # endif
 
 /*
@@ -3267,6 +3295,124 @@ set_buflocal_ofu_callback(buf_T *buf UNUSED)
 # endif
 }
 
+/*
+ * Free an array of 'complete' F{func} callbacks and set the pointer to NULL.
+ */
+    void
+clear_cpt_callbacks(callback_T **callbacks, int count)
+{
+    if (callbacks == NULL || *callbacks == NULL)
+       return;
+
+    for (int i = 0; i < count; i++)
+       free_callback(&(*callbacks)[i]);
+
+    VIM_CLEAR(*callbacks);
+}
+
+/*
+ * Copies a list of callback_T structs from src to *dest, clearing any existing
+ * entries and allocating memory for the destination.
+ */
+    static int
+copy_cpt_callbacks(callback_T **dest, int *dest_cnt, callback_T *src, int cnt)
+{
+    if (cnt == 0)
+       return OK;
+
+    clear_cpt_callbacks(dest, *dest_cnt);
+    *dest_cnt = 0;
+
+    *dest = ALLOC_CLEAR_MULT(callback_T, cnt);
+    if (*dest == NULL)
+       return FAIL;
+
+    *dest_cnt = cnt;
+
+    for (int i = 0; i < cnt; i++)
+       if (src[i].cb_name != NULL && *(src[i].cb_name) != NUL)
+           copy_callback(&(*dest)[i], &src[i]);
+
+    return OK;
+}
+
+/*
+ * Copy global 'complete' F{func} callbacks into the given buffer's local
+ * callback array. Clears any existing buffer-local callbacks first.
+ */
+    void
+set_buflocal_cpt_callbacks(buf_T *buf UNUSED)
+{
+#ifdef FEAT_EVAL
+    if (buf == NULL || cpt_cb_count == 0)
+       return;
+    (void)copy_cpt_callbacks(&buf->b_p_cpt_cb, &buf->b_p_cpt_count, cpt_cb,
+           cpt_cb_count);
+#endif
+}
+
+/*
+ * Parse 'complete' option and initialize F{func} callbacks.
+ * Frees any existing callbacks and allocates new ones.
+ * Only F{func} entries are processed; others are ignored.
+ */
+    int
+set_cpt_callbacks(optset_T *args)
+{
+    char_u  buf[LSIZE];
+    char_u  *p;
+    int            idx = 0;
+    int            slen;
+    int            count;
+    int            local = (args->os_flags & OPT_LOCAL) != 0;
+
+    if (curbuf == NULL)
+       return FAIL;
+
+    clear_cpt_callbacks(&curbuf->b_p_cpt_cb, curbuf->b_p_cpt_count);
+    curbuf->b_p_cpt_count = 0;
+
+    count = get_cpt_sources_count();
+    if (count == 0)
+       return OK;
+
+    curbuf->b_p_cpt_cb = ALLOC_CLEAR_MULT(callback_T, count);
+    if (curbuf->b_p_cpt_cb == NULL)
+       return FAIL;
+    curbuf->b_p_cpt_count = count;
+
+    for (p = curbuf->b_p_cpt; *p != NUL; )
+    {
+       while (*p == ',' || *p == ' ')
+           p++; // Skip delimiters
+
+       if (*p != NUL)
+       {
+           slen = copy_option_part(&p, buf, LSIZE, ","); // Advance p
+           if (slen > 0 && buf[0] == 'F' && buf[1] != NUL)
+           {
+               char_u  *caret;
+               caret = vim_strchr(buf, '^');
+               if (caret != NULL)
+                   *caret = NUL;
+
+               if (option_set_callback_func(buf + 1, &curbuf->b_p_cpt_cb[idx])
+                       != OK)
+                   curbuf->b_p_cpt_cb[idx].cb_name = NULL;
+           }
+           idx++;
+       }
+    }
+
+    if (!local) // ':set' used insted of ':setlocal'
+       // Cache the callback array
+       if (copy_cpt_callbacks(&cpt_cb, &cpt_cb_count, curbuf->b_p_cpt_cb,
+                   curbuf->b_p_cpt_count) != OK)
+           return FAIL;
+
+    return OK;
+}
+
 /*
  * Parse the 'thesaurusfunc' option value and set the callback function.
  * Invoked when the 'thesaurusfunc' option is set. The option value can be a
@@ -3294,6 +3440,23 @@ did_set_thesaurusfunc(optset_T *args UNUSED)
     return retval == FAIL ? e_invalid_argument : NULL;
 }
 
+/*
+ * Mark "copyID" references in an array of F{func} callbacks so that they are
+ * not garbage collected.
+ */
+    int
+set_ref_in_cpt_callbacks(callback_T *callbacks, int count, int copyID)
+{
+    int        abort = FALSE;
+
+    if (callbacks == NULL)
+       return FALSE;
+
+    for (int i = 0; i < count; i++)
+       abort = abort || set_ref_in_callback(&callbacks[i], copyID);
+    return abort;
+}
+
 /*
  * Mark the global 'completefunc' 'omnifunc' and 'thesaurusfunc' callbacks with
  * "copyID" so that they are not garbage collected.
@@ -3304,6 +3467,7 @@ set_ref_in_insexpand_funcs(int copyID)
     int abort = set_ref_in_callback(&cfu_cb, copyID);
     abort = abort || set_ref_in_callback(&ofu_cb, copyID);
     abort = abort || set_ref_in_callback(&tsrfu_cb, copyID);
+    abort = abort || set_ref_in_cpt_callbacks(cpt_cb, cpt_cb_count, copyID);
 
     return abort;
 }
@@ -4233,7 +4397,7 @@ process_next_cpt_value(
        else if (*st->e_cpt == 'F' || *st->e_cpt == 'o')
        {
            compl_type = CTRL_X_FUNCTION;
-           st->func_cb = get_callback_if_cpt_func(st->e_cpt);
+           st->func_cb = get_callback_if_cpt_func(st->e_cpt, cpt_sources_index);
            if (!st->func_cb)
                compl_type = -1;
        }
@@ -4915,31 +5079,28 @@ get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_pos)
 
 #ifdef FEAT_COMPL_FUNC
 /*
- * Return the callback function associated with "p" if it points to a
- * userfunc.
+ * Return the callback function associated with "p" if it refers to a
+ * user-defined function in the 'complete' option.
+ * The "idx" parameter is used for indexing callback entries.
  */
     static callback_T *
-get_callback_if_cpt_func(char_u *p)
+get_callback_if_cpt_func(char_u *p, int idx)
 {
-    static callback_T  cb;
-    char_u             buf[LSIZE];
-    int                        slen;
-
     if (*p == 'o')
        return &curbuf->b_ofu_cb;
+
     if (*p == 'F')
     {
        if (*++p != ',' && *p != NUL)
        {
-           free_callback(&cb);
-           slen = copy_option_part(&p, buf, LSIZE, ",");
-           if (slen > 0  && option_set_callback_func(buf, &cb))
-               return &cb;
-           return NULL;
+           // 'F{func}' case
+           return curbuf->b_p_cpt_cb[idx].cb_name != NULL
+               ? &curbuf->b_p_cpt_cb[idx] : NULL;
        }
        else
-           return &curbuf->b_cfu_cb;
+           return &curbuf->b_cfu_cb; // 'cfu'
     }
+
     return NULL;
 }
 #endif
@@ -5196,7 +5357,7 @@ prepare_cpt_compl_funcs(void)
        if (*p == NUL)
            break;
 
-       cb = get_callback_if_cpt_func(p);
+       cb = get_callback_if_cpt_func(p, idx);
        if (cb)
        {
            if (get_userdefined_compl_info(curwin->w_cursor.col, cb, &startcol)
@@ -7087,6 +7248,7 @@ free_insexpand_stuff(void)
     free_callback(&cfu_cb);
     free_callback(&ofu_cb);
     free_callback(&tsrfu_cb);
+    clear_cpt_callbacks(&cpt_cb, cpt_cb_count);
 # endif
 }
 #endif
@@ -7126,55 +7288,39 @@ setup_cpt_sources(void)
 {
     char_u  buf[LSIZE];
     int            slen;
-    int            count = 0, idx = 0;
-    char_u  *p, *cpt;
+    int            idx = 0;
+    int            count;
+    char_u  *p;
 
-    // Make a copy of 'cpt' in case the buffer gets wiped out
-    cpt = vim_strsave(curbuf->b_p_cpt);
-    if (cpt == NULL)
-       return FAIL;
+    cpt_sources_clear();
 
-    for (p = cpt; *p;)
-    {
-       while (*p == ',' || *p == ' ') // Skip delimiters
-           p++;
-       if (*p) // If not end of string, count this segment
-       {
-           (void)copy_option_part(&p, buf, LSIZE, ","); // Advance p
-           count++;
-       }
-    }
+    count = get_cpt_sources_count();
     if (count == 0)
-       goto theend;
+       return OK;
 
-    cpt_sources_clear();
-    cpt_sources_count = count;
     cpt_sources_array = ALLOC_CLEAR_MULT(cpt_source_T, count);
     if (cpt_sources_array == NULL)
-    {
-       cpt_sources_count = 0;
-       vim_free(cpt);
        return FAIL;
-    }
 
-    for (p = cpt; *p;)
+    for (p = curbuf->b_p_cpt; *p;)
     {
        while (*p == ',' || *p == ' ') // Skip delimiters
            p++;
        if (*p) // If not end of string, count this segment
        {
-           char_u *t;
-
-           vim_memset(buf, 0, LSIZE);
            slen = copy_option_part(&p, buf, LSIZE, ","); // Advance p
-           if (slen > 0 && (t = vim_strchr(buf, '^')) != NULL)
-               cpt_sources_array[idx].cs_max_matches = atoi((char *)t + 1);
+           if (slen > 0)
+           {
+               char_u  *caret = vim_strchr(buf, '^');
+               if (caret != NULL)
+                   cpt_sources_array[idx].cs_max_matches
+                       = atoi((char *)caret + 1);
+           }
            idx++;
        }
     }
 
-theend:
-    vim_free(cpt);
+    cpt_sources_count = count;
     return OK;
 }
 
@@ -7335,7 +7481,7 @@ cpt_compl_refresh(void)
 
        if (cpt_sources_array[cpt_sources_index].cs_refresh_always)
        {
-           cb = get_callback_if_cpt_func(p);
+           cb = get_callback_if_cpt_func(p, cpt_sources_index);
            if (cb)
            {
                compl_curr_match = remove_old_matches();
index 978fc0bf237ca617c9ae6956eff7477cc1cb5ab4..46a38d50053310ca2ca539ec74d4e30c4649c5ba 100644 (file)
@@ -7358,6 +7358,9 @@ buf_copy_options(buf_T *buf, int flags)
            }
            buf->b_p_cpt = vim_strsave(p_cpt);
            COPY_OPT_SCTX(buf, BV_CPT);
+#ifdef FEAT_COMPL_FUNC
+           set_buflocal_cpt_callbacks(buf);
+#endif
 #ifdef BACKSLASH_IN_FILENAME
            buf->b_p_csl = vim_strsave(p_csl);
            COPY_OPT_SCTX(buf, BV_CSL);
index 9061792542c7498800406771ba36fcafa2040613..06b655e317f251c11c038dfcd80753bd1b2a7d7c 100644 (file)
@@ -252,6 +252,16 @@ illegal_char(char *errbuf, size_t errbuflen, int c)
     return errbuf;
 }
 
+    static char *
+illegal_char_after_chr(char *errbuf, size_t errbuflen, int c)
+{
+    if (errbuf == NULL)
+       return "";
+    vim_snprintf(errbuf, errbuflen, _(e_illegal_character_after_chr),
+                   (char *)transchar(c));
+    return errbuf;
+}
+
 /*
  * Check string options in a buffer for NULL value.
  */
@@ -1644,19 +1654,18 @@ did_set_complete(optset_T *args)
            }
        }
        if (char_before != NUL)
-       {
-           if (args->os_errbuf)
-           {
-               vim_snprintf((char *)args->os_errbuf, args->os_errbuflen,
-                       _(e_illegal_character_after_chr), char_before);
-               return args->os_errbuf;
-           }
-           return NULL;
-       }
+           return illegal_char_after_chr(args->os_errbuf, args->os_errbuflen,
+                   char_before);
        // Skip comma and spaces
        while (*p == ',' || *p == ' ')
            p++;
     }
+
+#ifdef FEAT_COMPL_FUNC
+    if (set_cpt_callbacks(args) != OK)
+       return illegal_char_after_chr(args->os_errbuf, args->os_errbuflen,
+               'F');
+#endif
     return NULL;
 }
 
index 5c0b0e30b7a592960ece6b84c3348821d65dbb6b..5b57be988a3b1345ff48f17b9c9d3bbb99fdf2a1 100644 (file)
@@ -70,5 +70,8 @@ void free_insexpand_stuff(void);
 int ins_compl_cancel(void);
 void f_complete_match(typval_T *argvars, typval_T *rettv);
 int ins_compl_setup_autocompl(int c);
-// void ins_compl_disable_autocompl(void);
+void set_buflocal_cpt_callbacks(buf_T *buf);
+int set_cpt_callbacks(optset_T *args);
+void clear_cpt_callbacks(callback_T **cb, int count);
+int set_ref_in_cpt_callbacks(callback_T *callbacks, int count, int copyID);
 /* vim: set ft=c : */
index 725ec395bdbe717ec11781f0602138af24e1edbd..7748d57561573d61eccba2fec9f58e0d39d31658 100644 (file)
@@ -3316,6 +3316,8 @@ struct file_buffer
     char_u     *b_p_csl;       // 'completeslash'
 #endif
 #ifdef FEAT_COMPL_FUNC
+    callback_T *b_p_cpt_cb;    // F{func} in 'complete' callback
+    int                b_p_cpt_count;  // Count of values in 'complete'
     char_u     *b_p_cfu;       // 'completefunc'
     callback_T b_cfu_cb;       // 'completefunc' callback
     char_u     *b_p_ofu;       // 'omnifunc'
index 3a92d52982f20a47610b600d23a1b9723cc61eae..4ec1f357e2a3d7e3f31986927bbd197a70caac2f 100644 (file)
@@ -198,6 +198,10 @@ func Test_omni_autoload()
   setlocal omnifunc=omni#Func
   call feedkeys("i\<C-X>\<C-O>\<Esc>", 'xt')
 
+  setlocal complete=.,Fomni#Func
+  call feedkeys("S\<C-N>\<Esc>", 'xt')
+  setlocal complete&
+
   bwipe!
   set omnifunc=
   let &rtp = save_rtp
@@ -2692,6 +2696,15 @@ func Test_omnifunc_callback()
   call assert_equal([[1, ''], [0, 'script1']], g:OmniFunc3Args)
   bw!
 
+  set complete=Fs:OmniFunc3
+  new
+  call setline(1, 'script1')
+  let g:OmniFunc3Args = []
+  call feedkeys("A\<C-N>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'script1']], g:OmniFunc3Args)
+  bw!
+  set complete&
+
   let &omnifunc = 's:OmniFunc3'
   new
   call setline(1, 'script2')
@@ -5323,4 +5336,51 @@ func Test_autocomplete_timer()
   unlet g:CallCount
 endfunc
 
+func s:TestCompleteScriptLocal(findstart, base)
+  if a:findstart
+    return 1
+  else
+    return ['foo', 'foobar']
+  endif
+endfunc
+
+" Issue 17869
+func Test_scriplocal_autoload_func()
+  let save_rtp = &rtp
+  set rtp=Xruntime/some
+  let dir = 'Xruntime/some/autoload'
+  call mkdir(dir, 'pR')
+
+  let lines =<< trim END
+      vim9script
+      export def Func(findstart: bool, base: string): any
+          if findstart
+              return 1
+          else
+              return ['match', 'matchfoo']
+          endif
+      enddef
+  END
+  call writefile(lines, dir .. '/compl.vim')
+
+  call test_override("char_avail", 1)
+  new
+  inoremap <buffer> <F2> <Cmd>let b:matches = complete_info(["matches"]).matches<CR>
+  set autocomplete
+
+  setlocal complete=.,Fcompl#Func
+  call feedkeys("im\<F2>\<Esc>0", 'xt!')
+  call assert_equal(['match', 'matchfoo'], b:matches->mapnew('v:val.word'))
+
+  setlocal complete=.,F<SID>TestCompleteScriptLocal
+  call feedkeys("Sf\<F2>\<Esc>0", 'xt!')
+  call assert_equal(['foo', 'foobar'], b:matches->mapnew('v:val.word'))
+
+  setlocal complete&
+  set autocomplete&
+  bwipe!
+  call test_override("char_avail", 0)
+  let &rtp = save_rtp
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab nofoldenable
index 6ea8ff65f165451e2f4eaac00dcc2d13aa547a3a..7353208ab09461b2213f08b4b728d25db32b0d9a 100644 (file)
@@ -281,11 +281,17 @@ func Test_complete()
   set complete=.,w,b,u,k,s,i,d,],t,U,F,o
   set complete=.
   set complete=.^10,t^0
-  set complete+=Ffuncref('foo'\\,\ [10])
-  set complete=Ffuncref('foo'\\,\ [10])^10
+
+  func Foo(a, b)
+    return ''
+  endfunc
+
+  set complete+=Ffuncref('Foo'\\,\ [10])
+  set complete=Ffuncref('Foo'\\,\ [10])^10
   set complete&
-  set complete+=Ffunction('g:foo'\\,\ [10\\,\ 20])
+  set complete+=Ffunction('g:Foo'\\,\ [10\\,\ 20])
   set complete&
+  delfunc Foo
 endfun
 
 func Test_set_completion()
index 721c90238031c29b6bbb8ca0b9a520e493c87135..deb219172fda6cab884a354e063f45c66771a28c 100644 (file)
@@ -719,6 +719,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1603,
 /**/
     1602,
 /**/