patch 9.1.1603: completion: cannot use autoloaded funcs in 'complete' F{func}
Commit: https://github.com/vim/vim/commit/1bfe86a7d37ea849078255f16f99ed69fbf205dc Author: Girish Palya <giris...@gmail.com> Date: Fri Aug 8 12:30:35 2025 +0200 patch 9.1.1603: completion: cannot use autoloaded funcs in 'complete' F{func} 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 <giris...@gmail.com> Signed-off-by: Christian Brabandt <c...@256bit.org> diff --git a/src/buffer.c b/src/buffer.c index 5b6622825..5c8cd259b 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -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); diff --git a/src/evalbuffer.c b/src/evalbuffer.c index cefd6428f..b6cc548ec 100644 --- a/src/evalbuffer.c +++ b/src/evalbuffer.c @@ -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); diff --git a/src/insexpand.c b/src/insexpand.c index 160020224..03d946bb8 100644 --- a/src/insexpand.c +++ b/src/insexpand.c @@ -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(); diff --git a/src/option.c b/src/option.c index 978fc0bf2..46a38d500 100644 --- a/src/option.c +++ b/src/option.c @@ -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); diff --git a/src/optionstr.c b/src/optionstr.c index 906179254..06b655e31 100644 --- a/src/optionstr.c +++ b/src/optionstr.c @@ -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; } diff --git a/src/proto/insexpand.pro b/src/proto/insexpand.pro index 5c0b0e30b..5b57be988 100644 --- a/src/proto/insexpand.pro +++ b/src/proto/insexpand.pro @@ -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 : */ diff --git a/src/structs.h b/src/structs.h index 725ec395b..7748d5756 100644 --- a/src/structs.h +++ b/src/structs.h @@ -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' diff --git a/src/testdir/test_ins_complete.vim b/src/testdir/test_ins_complete.vim index 3a92d5298..4ec1f357e 100644 --- a/src/testdir/test_ins_complete.vim +++ b/src/testdir/test_ins_complete.vim @@ -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 diff --git a/src/testdir/test_options.vim b/src/testdir/test_options.vim index 6ea8ff65f..7353208ab 100644 --- a/src/testdir/test_options.vim +++ b/src/testdir/test_options.vim @@ -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() diff --git a/src/version.c b/src/version.c index 721c90238..deb219172 100644 --- a/src/version.c +++ b/src/version.c @@ -719,6 +719,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1603, /**/ 1602, /**/ -- -- 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 vim_dev+unsubscr...@googlegroups.com. To view this discussion visit https://groups.google.com/d/msgid/vim_dev/E1ukKau-002Gqm-Sv%40256bit.org.