patch 9.1.1422: scheduling of complete function can be improved Commit: https://github.com/vim/vim/commit/98c29dbfd1c0765cbe5a2fce71072a33ad629f34 Author: Girish Palya <giris...@gmail.com> Date: Sun Jun 1 19:40:00 2025 +0200
patch 9.1.1422: scheduling of complete function can be improved Problem: scheduling of complete function can be improved Solution: call user completion functions earlier when just determining the insertion column (Girish Palya) This change improves the scheduling behavior of async user-defined completion functions (such as `F{func}`, `F`, or `'o'` values in the `'complete'` option), particularly benefiting LSP clients. Currently, these user functions are invoked twice: 1. First with `findstart = 1` to determine the completion start position. 2. Then with `findstart = 0` to retrieve the actual matches. Previously, both calls were executed back-to-back. With this change, the first call (`findstart = 1`) is performed earlier—before any matches are gathered from other sources. This adjustment gives event-driven completion sources (e.g., LSP clients) more time to send their requests while Vim concurrently collects matches from other sources like the current buffer. Not sure about the real-world performance gains, but this approach should, in theory, improve responsiveness and reduce latency for asynchronous completions. To test, try using yegappan LSP client: ```vim set cpt+=o^10 autocmd VimEnter * g:LspOptionsSet({ autoComplete: false, omniComplete: true }) ``` If you prefer to use 'native' auto-completion (without plugins), try the following configuration: ```vim set cot=menuone,popup,noselect,nearest autocmd TextChangedI * InsComplete() def InsComplete() if getcharstr(1) == '' && getline('.')->strpart(0, col('.') - 1) =~ '\k$' SkipTextChangedI() feedkeys("\<c-n>", "n") endif enddef inoremap <silent> <c-e> <c-r>=<SID>SkipTextChangedI()<cr><c-e> inoremap <silent> <c-y> <c-r>=<SID>SkipTextChangedI()<cr><c-y> def SkipTextChangedI(): string set eventignore+=TextChangedI timer_start(1, (_) => { set eventignore-=TextChangedI }) return '' enddef inoremap <silent><expr> <tab> pumvisible() ? "\<c-n>" : "\<tab>" inoremap <silent><expr> <s-tab> pumvisible() ? "\<c-p>" : "\<s-tab>" ``` closes: #17396 Signed-off-by: Girish Palya <giris...@gmail.com> Signed-off-by: Christian Brabandt <c...@256bit.org> diff --git a/src/insexpand.c b/src/insexpand.c index 1de15c3ff..66db022c1 100644 --- a/src/insexpand.c +++ b/src/insexpand.c @@ -221,8 +221,9 @@ static int *compl_fuzzy_scores; // Define the structure for completion source (in 'cpt' option) information typedef struct cpt_source_T { - int refresh_always; // Flag array to indicate which 'cpt' functions have 'refresh:always' set - int max_matches; // Maximum number of items to display in the menu from the source + int cs_refresh_always; // Whether 'refresh:always' is set for func + int cs_startcol; // Start column returned by func + int cs_max_matches; // Max items to display from this source } cpt_source_T; static cpt_source_T *cpt_sources_array; // Pointer to the array of completion sources @@ -250,9 +251,9 @@ 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_cpt_func_callback(char_u *funcname); +static callback_T *get_callback_if_cpt_func(char_u *p); # endif -static int cpt_sources_init(void); +static int setup_cpt_sources(void); static int is_cpt_func_refresh_always(void); static void cpt_sources_clear(void); static void cpt_compl_refresh(void); @@ -1467,7 +1468,7 @@ trim_compl_match_array(void) // Calculate size of trimmed array, respecting max_matches per source. for (i = 0; i < cpt_sources_count; i++) { - limit = cpt_sources_array[i].max_matches; + limit = cpt_sources_array[i].cs_max_matches; new_size += (limit > 0 && match_counts[i] > limit) ? limit : match_counts[i]; } @@ -1485,7 +1486,7 @@ trim_compl_match_array(void) src_idx = compl_match_array[i].pum_cpt_source_idx; if (src_idx != -1) { - limit = cpt_sources_array[src_idx].max_matches; + limit = cpt_sources_array[src_idx].cs_max_matches; if (limit <= 0 || match_counts[src_idx] < limit) { trimmed[trimmed_idx++] = compl_match_array[i]; @@ -1581,7 +1582,7 @@ ins_compl_build_pum(void) } else if (cpt_sources_array != NULL && !max_matches_found) { - int max_matches = cpt_sources_array[cur_source].max_matches; + int max_matches = cpt_sources_array[cur_source].cs_max_matches; if (max_matches > 0 && match_count > max_matches) max_matches_found = TRUE; } @@ -4026,6 +4027,8 @@ may_advance_cpt_index(char_u *cpt) { char_u *p = cpt; + if (cpt_sources_index == -1) + return FALSE; while (*p == ',' || *p == ' ') // Skip delimiters p++; return (*p != NUL); @@ -4178,11 +4181,7 @@ process_next_cpt_value( else if (*st->e_cpt == 'F' || *st->e_cpt == 'o') { compl_type = CTRL_X_FUNCTION; - if (*st->e_cpt == 'o') - st->func_cb = &curbuf->b_ofu_cb; - else - st->func_cb = (*++st->e_cpt != ',' && *st->e_cpt != NUL) - ? get_cpt_func_callback(st->e_cpt) : &curbuf->b_cfu_cb; + st->func_cb = get_callback_if_cpt_func(st->e_cpt); if (!st->func_cb) compl_type = -1; } @@ -4846,20 +4845,33 @@ get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_pos) return found_new_match; } +#ifdef FEAT_COMPL_FUNC /* - * Return the callback function associated with "funcname". + * Return the callback function associated with "p" if it points to a + * userfunc. */ -#ifdef FEAT_COMPL_FUNC static callback_T * -get_cpt_func_callback(char_u *funcname) +get_callback_if_cpt_func(char_u *p) { static callback_T cb; char_u buf[LSIZE]; int slen; - slen = copy_option_part(&funcname, buf, LSIZE, ","); - if (slen > 0 && option_set_callback_func(buf, &cb)) - return &cb; + 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; + } + else + return &curbuf->b_cfu_cb; + } return NULL; } @@ -5061,6 +5073,55 @@ strip_caret_numbers_in_place(char_u *str) *write = ' } +/* + * Call functions specified in the 'cpt' option with findstart=1, + * and retrieve the startcol. + */ + static void +prepare_cpt_compl_funcs(void) +{ +#ifdef FEAT_COMPL_FUNC + char_u *cpt; + char_u *p; + callback_T *cb = NULL; + int idx = 0; + int startcol; + + // Make a copy of 'cpt' in case the buffer gets wiped out + cpt = vim_strsave(curbuf->b_p_cpt); + strip_caret_numbers_in_place(cpt); + + // Re-insert the text removed by ins_compl_delete(). + ins_compl_insert_bytes(compl_orig_text.string + get_compl_len(), -1); + + for (p = cpt; *p;) + { + while (*p == ',' || *p == ' ') // Skip delimiters + p++; + cb = get_callback_if_cpt_func(p); + if (cb) + { + if (get_userdefined_compl_info(curwin->w_cursor.col, cb, &startcol) + == FAIL) + { + if (startcol == -3) + cpt_sources_array[idx].cs_refresh_always = FALSE; + else + startcol = -2; + } + cpt_sources_array[idx].cs_startcol = startcol; + } + (void)copy_option_part(&p, IObuff, IOSIZE, ","); // Advance p + idx++; + } + + // Undo insertion + ins_compl_delete(); + + vim_free(cpt); +#endif +} + /* * Safely advance the cpt_sources_index by one. */ @@ -5117,10 +5178,6 @@ ins_compl_get_exp(pos_T *ini) strip_caret_numbers_in_place(st.e_cpt_copy); st.e_cpt = st.e_cpt_copy == NULL ? (char_u *)"" : st.e_cpt_copy; st.last_match_pos = st.first_match_pos = *ini; - - if ((ctrl_x_mode_normal() || ctrl_x_mode_line_or_eval()) - && !cpt_sources_init()) - return FAIL; } else if (st.ins_buf != curbuf && !buf_valid(st.ins_buf)) st.ins_buf = curbuf; // In case the buffer was wiped out. @@ -5129,8 +5186,20 @@ ins_compl_get_exp(pos_T *ini) st.cur_match_pos = (compl_dir_forward()) ? &st.last_match_pos : &st.first_match_pos; + if (ctrl_x_mode_normal() && !ctrl_x_mode_line_or_eval() && + !(compl_cont_status & CONT_LOCAL)) + { + // ^N completion, not ^X^L or complete() or ^X^N + if (!compl_started) // Before showing menu the first time + { + if (setup_cpt_sources() == FAIL) + return FAIL; + } + prepare_cpt_compl_funcs(); + cpt_sources_index = 0; + } + // For ^N/^P loop over all the flags/windows/buffers in 'complete'. - cpt_sources_index = 0; for (;;) { found_new_match = FAIL; @@ -6120,10 +6189,48 @@ get_cmdline_compl_info(char_u *line, colnr_T curs_col) return OK; } +#ifdef FEAT_COMPL_FUNC +/* + * Set global variables related to completion: + * compl_col, compl_length, compl_pattern, and cpt_compl_pattern. + */ + static int +set_compl_globals( + int startcol UNUSED, + colnr_T curs_col UNUSED, + int is_cpt_compl UNUSED) +{ + char_u *line = NULL; + string_T *pattern = NULL; + int len; + + if (startcol < 0 || startcol > curs_col) + startcol = curs_col; + len = curs_col - startcol; + + // Re-obtain line in case it has changed + line = ml_get(curwin->w_cursor.lnum); + + pattern = is_cpt_compl ? &cpt_compl_pattern : &compl_pattern; + pattern->string = vim_strnsave(line + startcol, (size_t)len); + if (pattern->string == NULL) + { + pattern->length = 0; + return FAIL; + } + pattern->length = (size_t)len; + if (!is_cpt_compl) + { + compl_col = startcol; + compl_length = len; + } + return OK; +} +#endif + /* * Get the pattern, column and length for user defined completion ('omnifunc', * 'completefunc' and 'thesaurusfunc') - * Sets the global variables: compl_col, compl_length and compl_pattern. * Uses the global variable: spell_bad_len * Callback function "cb" is set if triggered by a function in the 'cpt' * option; otherwise, it is NULL. @@ -6140,14 +6247,11 @@ get_userdefined_compl_info( #ifdef FEAT_COMPL_FUNC // Call user defined function 'completefunc' with "a:findstart" // set to 1 to obtain the length of text to use for completion. - char_u *line = NULL; typval_T args[3]; int col; char_u *funcname = NULL; pos_T pos; int save_State = State; - int len; - string_T *compl_pat = NULL; int is_cpt_function = (cb != NULL); if (!is_cpt_function) @@ -6210,28 +6314,7 @@ get_userdefined_compl_info( compl_opt_refresh_always = FALSE; compl_opt_suppress_empty = FALSE; - if (col < 0 || col > curs_col) - col = curs_col; - - // Setup variables for completion. Need to obtain "line" again, - // it may have become invalid. - line = ml_get(curwin->w_cursor.lnum); - len = curs_col - col; - compl_pat = is_cpt_function ? &cpt_compl_pattern : &compl_pattern; - compl_pat->string = vim_strnsave(line + col, (size_t)len); - if (compl_pat->string == NULL) - { - compl_pat->length = 0; - return FAIL; - } - compl_pat->length = (size_t)compl_length; - - if (!is_cpt_function) - { - compl_col = col; - compl_length = len; - } - ret = OK; + ret = !is_cpt_function ? set_compl_globals(col, curs_col, FALSE) : OK; #endif return ret; @@ -6813,14 +6896,14 @@ cpt_sources_clear(void) } /* - * Initialize the info associated with completion sources. + * Setup completion sources. */ static int -cpt_sources_init(void) +setup_cpt_sources(void) { char_u buf[LSIZE]; int slen; - int count = 0; + int count = 0, idx = 0; char_u *p; for (p = curbuf->b_p_cpt; *p;) @@ -6833,31 +6916,31 @@ cpt_sources_init(void) count++; } } + if (count == 0) + return OK; + cpt_sources_clear(); cpt_sources_count = count; - if (count > 0) + cpt_sources_array = ALLOC_CLEAR_MULT(cpt_source_T, count); + if (cpt_sources_array == NULL) { - cpt_sources_array = ALLOC_CLEAR_MULT(cpt_source_T, count); - if (cpt_sources_array == NULL) - { - cpt_sources_count = 0; - return FAIL; - } - count = 0; - for (p = curbuf->b_p_cpt; *p;) + cpt_sources_count = 0; + return FAIL; + } + + for (p = curbuf->b_p_cpt; *p;) + { + while (*p == ',' || *p == ' ') // Skip delimiters + p++; + if (*p) // If not end of string, count this segment { - while (*p == ',' || *p == ' ') // Skip delimiters - p++; - if (*p) // If not end of string, count this segment - { - char_u *t; + 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[count].max_matches = atoi((char *)t + 1); - count++; - } + 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); + idx++; } } return OK; @@ -6871,7 +6954,7 @@ is_cpt_func_refresh_always(void) { #ifdef FEAT_COMPL_FUNC for (int i = 0; i < cpt_sources_count; i++) - if (cpt_sources_array[i].refresh_always) + if (cpt_sources_array[i].cs_refresh_always) return TRUE; #endif return FALSE; @@ -6977,17 +7060,17 @@ remove_old_matches(void) static void get_cpt_func_completion_matches(callback_T *cb UNUSED) { - int ret; - int startcol; + int startcol = cpt_sources_array[cpt_sources_index].cs_startcol; VIM_CLEAR_STRING(cpt_compl_pattern); - ret = get_userdefined_compl_info(curwin->w_cursor.col, cb, &startcol); - if (ret == FAIL && startcol == -3) - cpt_sources_array[cpt_sources_index].refresh_always = FALSE; - else if (ret == OK) + + if (startcol == -2 || startcol == -3) + return; + + if (set_compl_globals(startcol, curwin->w_cursor.col, TRUE) == OK) { expand_by_function(0, cpt_compl_pattern.string, cb); - cpt_sources_array[cpt_sources_index].refresh_always = + cpt_sources_array[cpt_sources_index].cs_refresh_always = compl_opt_refresh_always; compl_opt_refresh_always = FALSE; } @@ -7005,6 +7088,7 @@ cpt_compl_refresh(void) char_u *cpt; char_u *p; callback_T *cb = NULL; + int startcol, ret; // Make the completion list linear (non-cyclic) ins_compl_make_linear(); @@ -7018,17 +7102,25 @@ cpt_compl_refresh(void) while (*p == ',' || *p == ' ') // Skip delimiters p++; - if (cpt_sources_array[cpt_sources_index].refresh_always) + if (cpt_sources_array[cpt_sources_index].cs_refresh_always) { - if (*p == 'o') - cb = &curbuf->b_ofu_cb; - else if (*p == 'F') - cb = (*(p + 1) != ',' && *(p + 1) != NUL) - ? get_cpt_func_callback(p + 1) : &curbuf->b_cfu_cb; + cb = get_callback_if_cpt_func(p); if (cb) { compl_curr_match = remove_old_matches(); - get_cpt_func_completion_matches(cb); + ret = get_userdefined_compl_info(curwin->w_cursor.col, cb, + &startcol); + if (ret == FAIL) + { + if (startcol == -3) + cpt_sources_array[cpt_sources_index].cs_refresh_always + = FALSE; + else + startcol = -2; + } + cpt_sources_array[cpt_sources_index].cs_startcol = startcol; + if (ret == OK) + get_cpt_func_completion_matches(cb); } } diff --git a/src/version.c b/src/version.c index 753012403..01c3050d1 100644 --- a/src/version.c +++ b/src/version.c @@ -709,6 +709,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1422, /**/ 1421, /**/ -- -- 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/E1uLmya-00BYKl-7I%40256bit.org.