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);
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
/*
# 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
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.
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;
}
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;
}
#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
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)
free_callback(&cfu_cb);
free_callback(&ofu_cb);
free_callback(&tsrfu_cb);
+ clear_cpt_callbacks(&cpt_cb, cpt_cb_count);
# endif
}
#endif
{
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;
}
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();
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
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')
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