# HG changeset patch # User Bram Moolenaar # Date 1640799903 -3600 # Node ID a8ded20a59e632457bd643a0c6b715383cd13ed6 # Parent b83c4c0c748f465ca4cbf749fe2e57b551359cf8 patch 8.2.3937: Insert mode completion function is too long Commit: https://github.com/vim/vim/commit/edc6f103907a004b9e2265e232dc8be8bc594601 Author: Yegappan Lakshmanan Date: Wed Dec 29 17:38:46 2021 +0000 patch 8.2.3937: Insert mode completion function is too long Problem: Insert mode completion function is too long. Solution: Refactor into multiple functions. (Yegappan Lakshmanan, closes #9423) diff --git a/src/insexpand.c b/src/insexpand.c --- a/src/insexpand.c +++ b/src/insexpand.c @@ -2901,6 +2901,493 @@ thesaurus_func_complete(int type UNUSED) } /* + * Return value of process_next_cpt_value() + */ +enum +{ + INS_COMPL_CPT_OK = 1, + INS_COMPL_CPT_CONT, + INS_COMPL_CPT_END +}; + +/* + * Process the next 'complete' option value in "e_cpt_arg". + * + * If successful, the arguments are set as below: + * e_cpt_arg - pointer to the next option value in 'e_cpt_arg' + * compl_type_arg - type of insert mode completion to use + * found_all_arg - all matches of this type are found + * buf_arg - search for completions in this buffer + * first_match_pos - position of the first completion match + * last_match_pos - position of the last completion match + * set_match_pos - TRUE if the first match position should be saved to avoid + * loops after the search wraps around. + * dict - name of the dictionary or thesaurus file to search + * dict_f - flag specifying whether "dict" is an exact file name or not + * + * Returns INS_COMPL_CPT_OK if the next value is processed successfully. + * Returns INS_COMPL_CPT_CONT to skip the current value and process the next + * option value. + * Returns INS_COMPL_CPT_END if all the values in "e_cpt" are processed. + */ + static int +process_next_cpt_value( + char_u **e_cpt_arg, + int *compl_type_arg, + int *found_all_arg, + buf_T **buf_arg, + pos_T *start_match_pos, + pos_T *first_match_pos, + pos_T *last_match_pos, + int *set_match_pos, + char_u **dict_arg, + int *dict_flag) +{ + char_u *e_cpt = *e_cpt_arg; + int compl_type = -1; + int status = INS_COMPL_CPT_OK; + buf_T *buf = *buf_arg; + int found_all = FALSE; + char_u *dict = NULL; + int dict_f = 0; + + while (*e_cpt == ',' || *e_cpt == ' ') + e_cpt++; + + if (*e_cpt == '.' && !curbuf->b_scanned) + { + buf = curbuf; + *first_match_pos = *start_match_pos; + // Move the cursor back one character so that ^N can match the + // word immediately after the cursor. + if (ctrl_x_mode == CTRL_X_NORMAL && dec(first_match_pos) < 0) + { + // Move the cursor to after the last character in the + // buffer, so that word at start of buffer is found + // correctly. + first_match_pos->lnum = buf->b_ml.ml_line_count; + first_match_pos->col = + (colnr_T)STRLEN(ml_get(first_match_pos->lnum)); + } + *last_match_pos = *first_match_pos; + compl_type = 0; + + // Remember the first match so that the loop stops when we + // wrap and come back there a second time. + *set_match_pos = TRUE; + } + else if (vim_strchr((char_u *)"buwU", *e_cpt) != NULL + && (buf = ins_compl_next_buf(buf, *e_cpt)) != curbuf) + { + // Scan a buffer, but not the current one. + if (buf->b_ml.ml_mfp != NULL) // loaded buffer + { + compl_started = TRUE; + first_match_pos->col = last_match_pos->col = 0; + first_match_pos->lnum = buf->b_ml.ml_line_count + 1; + last_match_pos->lnum = 0; + compl_type = 0; + } + else // unloaded buffer, scan like dictionary + { + found_all = TRUE; + if (buf->b_fname == NULL) + { + status = INS_COMPL_CPT_CONT; + goto done; + } + compl_type = CTRL_X_DICTIONARY; + dict = buf->b_fname; + dict_f = DICT_EXACT; + } + msg_hist_off = TRUE; // reset in msg_trunc_attr() + vim_snprintf((char *)IObuff, IOSIZE, _("Scanning: %s"), + buf->b_fname == NULL + ? buf_spname(buf) + : buf->b_sfname == NULL + ? buf->b_fname + : buf->b_sfname); + (void)msg_trunc_attr((char *)IObuff, TRUE, HL_ATTR(HLF_R)); + } + else if (*e_cpt == NUL) + status = INS_COMPL_CPT_END; + else + { + if (ctrl_x_mode_line_or_eval()) + compl_type = -1; + else if (*e_cpt == 'k' || *e_cpt == 's') + { + if (*e_cpt == 'k') + compl_type = CTRL_X_DICTIONARY; + else + compl_type = CTRL_X_THESAURUS; + if (*++e_cpt != ',' && *e_cpt != NUL) + { + dict = e_cpt; + dict_f = DICT_FIRST; + } + } +#ifdef FEAT_FIND_ID + else if (*e_cpt == 'i') + compl_type = CTRL_X_PATH_PATTERNS; + else if (*e_cpt == 'd') + compl_type = CTRL_X_PATH_DEFINES; +#endif + else if (*e_cpt == ']' || *e_cpt == 't') + { + msg_hist_off = TRUE; // reset in msg_trunc_attr() + compl_type = CTRL_X_TAGS; + vim_snprintf((char *)IObuff, IOSIZE, _("Scanning tags.")); + (void)msg_trunc_attr((char *)IObuff, TRUE, HL_ATTR(HLF_R)); + } + else + compl_type = -1; + + // in any case e_cpt is advanced to the next entry + (void)copy_option_part(&e_cpt, IObuff, IOSIZE, ","); + + found_all = TRUE; + if (compl_type == -1) + status = INS_COMPL_CPT_CONT; + } + +done: + *e_cpt_arg = e_cpt; + *compl_type_arg = compl_type; + *found_all_arg = found_all; + *buf_arg = buf; + *dict_arg = dict; + *dict_flag = dict_f; + return status; +} + +#ifdef FEAT_FIND_ID +/* + * Get the next set of identifiers or defines matching "compl_pattern" in + * included files. + */ + static void +get_next_include_file_completion(int compl_type) +{ + find_pattern_in_path(compl_pattern, compl_direction, + (int)STRLEN(compl_pattern), FALSE, FALSE, + (compl_type == CTRL_X_PATH_DEFINES + && !(compl_cont_status & CONT_SOL)) + ? FIND_DEFINE : FIND_ANY, 1L, ACTION_EXPAND, + (linenr_T)1, (linenr_T)MAXLNUM); +} +#endif + +/* + * Get the next set of words matching "compl_pattern" in dictionary or + * thesaurus files. + */ + static void +get_next_dict_tsr_completion(int compl_type, char_u **dict, int dict_f) +{ +#ifdef FEAT_COMPL_FUNC + if (thesaurus_func_complete(compl_type)) + expand_by_function(compl_type, compl_pattern); + else +#endif + ins_compl_dictionaries( + *dict != NULL ? *dict + : (compl_type == CTRL_X_THESAURUS + ? (*curbuf->b_p_tsr == NUL ? p_tsr : curbuf->b_p_tsr) + : (*curbuf->b_p_dict == NUL ? p_dict : curbuf->b_p_dict)), + compl_pattern, + *dict != NULL ? dict_f : 0, + compl_type == CTRL_X_THESAURUS); + *dict = NULL; +} + +/* + * Get the next set of tag names matching "compl_pattern". + */ + static void +get_next_tag_completion(void) +{ + int save_p_ic; + char_u **matches; + int num_matches; + + // set p_ic according to p_ic, p_scs and pat for find_tags(). + save_p_ic = p_ic; + p_ic = ignorecase(compl_pattern); + + // Find up to TAG_MANY matches. Avoids that an enormous number + // of matches is found when compl_pattern is empty + g_tag_at_cursor = TRUE; + if (find_tags(compl_pattern, &num_matches, &matches, + TAG_REGEXP | TAG_NAMES | TAG_NOIC | TAG_INS_COMP + | (ctrl_x_mode != CTRL_X_NORMAL ? TAG_VERBOSE : 0), + TAG_MANY, curbuf->b_ffname) == OK && num_matches > 0) + ins_compl_add_matches(num_matches, matches, p_ic); + g_tag_at_cursor = FALSE; + p_ic = save_p_ic; +} + +/* + * Get the next set of filename matching "compl_pattern". + */ + static void +get_next_filename_completion(void) +{ + char_u **matches; + int num_matches; + + if (expand_wildcards(1, &compl_pattern, &num_matches, &matches, + EW_FILE|EW_DIR|EW_ADDSLASH|EW_SILENT) != OK) + return; + + // May change home directory back to "~". + tilde_replace(compl_pattern, num_matches, matches); +#ifdef BACKSLASH_IN_FILENAME + if (curbuf->b_p_csl[0] != NUL) + { + int i; + + for (i = 0; i < num_matches; ++i) + { + char_u *ptr = matches[i]; + + while (*ptr != NUL) + { + if (curbuf->b_p_csl[0] == 's' && *ptr == '\\') + *ptr = '/'; + else if (curbuf->b_p_csl[0] == 'b' && *ptr == '/') + *ptr = '\\'; + ptr += (*mb_ptr2len)(ptr); + } + } + } +#endif + ins_compl_add_matches(num_matches, matches, p_fic || p_wic); +} + +/* + * Get the next set of command-line completions matching "compl_pattern". + */ + static void +get_next_cmdline_completion() +{ + char_u **matches; + int num_matches; + + if (expand_cmdline(&compl_xp, compl_pattern, + (int)STRLEN(compl_pattern), + &num_matches, &matches) == EXPAND_OK) + ins_compl_add_matches(num_matches, matches, FALSE); +} + +/* + * Get the next set of spell suggestions matching "compl_pattern". + */ + static void +get_next_spell_completion(linenr_T lnum UNUSED) +{ +#ifdef FEAT_SPELL + char_u **matches; + int num_matches; + + num_matches = expand_spelling(lnum, compl_pattern, &matches); + if (num_matches > 0) + ins_compl_add_matches(num_matches, matches, p_ic); +#endif +} + +/* + * Get the next set of words matching "compl_pattern" for default completion(s) + * (normal ^P/^N and ^X^L). + * Search for "compl_pattern" in the buffer "ins_buf" starting from the + * position "start_pos" in the "compl_direction" direction. If "save_match_pos" + * is TRUE, then set the "first_match_pos" and "last_match_pos". + * Returns OK if a new next match is found, otherwise returns FAIL. + */ + static int +get_next_default_completion( + buf_T *ins_buf, // buffer being scanned + pos_T *start_pos, // search start position + pos_T *cur_match_pos, // current match position + pos_T *prev_match_pos, // previous match position + int *save_match_pos, // set first_match_pos/last_match_pos + pos_T *first_match_pos, // first match position + pos_T *last_match_pos, // last match position + int scan_curbuf) // scan current buffer for completion +{ + int found_new_match = FAIL; + int save_p_scs; + int save_p_ws; + int looped_around = FALSE; + char_u *ptr; + int len; + + // If 'infercase' is set, don't use 'smartcase' here + save_p_scs = p_scs; + if (ins_buf->b_p_inf) + p_scs = FALSE; + + // Buffers other than curbuf are scanned from the beginning or the + // end but never from the middle, thus setting nowrapscan in this + // buffer is a good idea, on the other hand, we always set + // wrapscan for curbuf to avoid missing matches -- Acevedo,Webb + save_p_ws = p_ws; + if (ins_buf != curbuf) + p_ws = FALSE; + else if (scan_curbuf) + p_ws = TRUE; + looped_around = FALSE; + for (;;) + { + int cont_s_ipos = FALSE; + + ++msg_silent; // Don't want messages for wrapscan. + + // ctrl_x_mode_line_or_eval() || word-wise search that + // has added a word that was at the beginning of the line + if (ctrl_x_mode_line_or_eval() + || (compl_cont_status & CONT_SOL)) + found_new_match = search_for_exact_line(ins_buf, cur_match_pos, + compl_direction, compl_pattern); + else + found_new_match = searchit(NULL, ins_buf, cur_match_pos, NULL, + compl_direction, + compl_pattern, 1L, SEARCH_KEEP + SEARCH_NFMSG, + RE_LAST, NULL); + --msg_silent; + if (!compl_started || *save_match_pos) + { + // set "compl_started" even on fail + compl_started = TRUE; + *first_match_pos = *cur_match_pos; + *last_match_pos = *cur_match_pos; + *save_match_pos = FALSE; + } + else if (first_match_pos->lnum == last_match_pos->lnum + && first_match_pos->col == last_match_pos->col) + { + found_new_match = FAIL; + } + else if ((compl_direction == FORWARD) + && (prev_match_pos->lnum > cur_match_pos->lnum + || (prev_match_pos->lnum == cur_match_pos->lnum + && prev_match_pos->col >= cur_match_pos->col))) + { + if (looped_around) + found_new_match = FAIL; + else + looped_around = TRUE; + } + else if ((compl_direction != FORWARD) + && (prev_match_pos->lnum < cur_match_pos->lnum + || (prev_match_pos->lnum == cur_match_pos->lnum + && prev_match_pos->col <= cur_match_pos->col))) + { + if (looped_around) + found_new_match = FAIL; + else + looped_around = TRUE; + } + *prev_match_pos = *cur_match_pos; + if (found_new_match == FAIL) + break; + + // when ADDING, the text before the cursor matches, skip it + if ((compl_cont_status & CONT_ADDING) && ins_buf == curbuf + && start_pos->lnum == cur_match_pos->lnum + && start_pos->col == cur_match_pos->col) + continue; + ptr = ml_get_buf(ins_buf, cur_match_pos->lnum, FALSE) + + cur_match_pos->col; + if (ctrl_x_mode_line_or_eval()) + { + if (compl_cont_status & CONT_ADDING) + { + if (cur_match_pos->lnum >= ins_buf->b_ml.ml_line_count) + continue; + ptr = ml_get_buf(ins_buf, cur_match_pos->lnum + 1, FALSE); + if (!p_paste) + ptr = skipwhite(ptr); + } + len = (int)STRLEN(ptr); + } + else + { + char_u *tmp_ptr = ptr; + + if (compl_cont_status & CONT_ADDING) + { + tmp_ptr += compl_length; + // Skip if already inside a word. + if (vim_iswordp(tmp_ptr)) + continue; + // Find start of next word. + tmp_ptr = find_word_start(tmp_ptr); + } + // Find end of this word. + tmp_ptr = find_word_end(tmp_ptr); + len = (int)(tmp_ptr - ptr); + + if ((compl_cont_status & CONT_ADDING) && len == compl_length) + { + if (cur_match_pos->lnum < ins_buf->b_ml.ml_line_count) + { + // Try next line, if any. the new word will be + // "join" as if the normal command "J" was used. + // IOSIZE is always greater than + // compl_length, so the next STRNCPY always + // works -- Acevedo + STRNCPY(IObuff, ptr, len); + ptr = ml_get_buf(ins_buf, cur_match_pos->lnum + 1, FALSE); + tmp_ptr = ptr = skipwhite(ptr); + // Find start of next word. + tmp_ptr = find_word_start(tmp_ptr); + // Find end of next word. + tmp_ptr = find_word_end(tmp_ptr); + if (tmp_ptr > ptr) + { + if (*ptr != ')' && IObuff[len - 1] != TAB) + { + if (IObuff[len - 1] != ' ') + IObuff[len++] = ' '; + // IObuf =~ "\k.* ", thus len >= 2 + if (p_js + && (IObuff[len - 2] == '.' + || (vim_strchr(p_cpo, CPO_JOINSP) + == NULL + && (IObuff[len - 2] == '?' + || IObuff[len - 2] == '!')))) + IObuff[len++] = ' '; + } + // copy as much as possible of the new word + if (tmp_ptr - ptr >= IOSIZE - len) + tmp_ptr = ptr + IOSIZE - len - 1; + STRNCPY(IObuff + len, ptr, tmp_ptr - ptr); + len += (int)(tmp_ptr - ptr); + cont_s_ipos = TRUE; + } + IObuff[len] = NUL; + ptr = IObuff; + } + if (len == compl_length) + continue; + } + } + if (ins_compl_add_infercase(ptr, len, p_ic, + ins_buf == curbuf ? NULL : ins_buf->b_sfname, + 0, cont_s_ipos) != NOTDONE) + { + found_new_match = OK; + break; + } + } + p_scs = save_p_scs; + p_ws = save_p_ws; + + return found_new_match; +} + +/* * Get the next expansion(s), using "compl_pattern". * The search starts at position "ini" in curbuf and in the direction * compl_direction. @@ -2920,21 +3407,13 @@ ins_compl_get_exp(pos_T *ini) static buf_T *ins_buf = NULL; // buffer being scanned pos_T *pos; - char_u **matches; - int save_p_scs; - int save_p_ws; - int save_p_ic; int i; - int num_matches; - int len; int found_new_match; int type = ctrl_x_mode; - char_u *ptr; char_u *dict = NULL; int dict_f = 0; int set_match_pos; pos_T prev_pos = {0, 0, 0}; - int looped_around = FALSE; if (!compl_started) { @@ -2965,102 +3444,14 @@ ins_compl_get_exp(pos_T *ini) || ctrl_x_mode_line_or_eval()) && (!compl_started || found_all)) { - found_all = FALSE; - while (*e_cpt == ',' || *e_cpt == ' ') - e_cpt++; - if (*e_cpt == '.' && !curbuf->b_scanned) - { - ins_buf = curbuf; - first_match_pos = *ini; - // Move the cursor back one character so that ^N can match the - // word immediately after the cursor. - if (ctrl_x_mode == CTRL_X_NORMAL && dec(&first_match_pos) < 0) - { - // Move the cursor to after the last character in the - // buffer, so that word at start of buffer is found - // correctly. - first_match_pos.lnum = ins_buf->b_ml.ml_line_count; - first_match_pos.col = - (colnr_T)STRLEN(ml_get(first_match_pos.lnum)); - } - last_match_pos = first_match_pos; - type = 0; - - // Remember the first match so that the loop stops when we - // wrap and come back there a second time. - set_match_pos = TRUE; - } - else if (vim_strchr((char_u *)"buwU", *e_cpt) != NULL - && (ins_buf = ins_compl_next_buf(ins_buf, *e_cpt)) != curbuf) - { - // Scan a buffer, but not the current one. - if (ins_buf->b_ml.ml_mfp != NULL) // loaded buffer - { - compl_started = TRUE; - first_match_pos.col = last_match_pos.col = 0; - first_match_pos.lnum = ins_buf->b_ml.ml_line_count + 1; - last_match_pos.lnum = 0; - type = 0; - } - else // unloaded buffer, scan like dictionary - { - found_all = TRUE; - if (ins_buf->b_fname == NULL) - continue; - type = CTRL_X_DICTIONARY; - dict = ins_buf->b_fname; - dict_f = DICT_EXACT; - } - msg_hist_off = TRUE; // reset in msg_trunc_attr() - vim_snprintf((char *)IObuff, IOSIZE, _("Scanning: %s"), - ins_buf->b_fname == NULL - ? buf_spname(ins_buf) - : ins_buf->b_sfname == NULL - ? ins_buf->b_fname - : ins_buf->b_sfname); - (void)msg_trunc_attr((char *)IObuff, TRUE, HL_ATTR(HLF_R)); - } - else if (*e_cpt == NUL) + int status = process_next_cpt_value(&e_cpt, &type, &found_all, + &ins_buf, ini, &first_match_pos, &last_match_pos, + &set_match_pos, &dict, &dict_f); + + if (status == INS_COMPL_CPT_END) break; - else - { - if (ctrl_x_mode_line_or_eval()) - type = -1; - else if (*e_cpt == 'k' || *e_cpt == 's') - { - if (*e_cpt == 'k') - type = CTRL_X_DICTIONARY; - else - type = CTRL_X_THESAURUS; - if (*++e_cpt != ',' && *e_cpt != NUL) - { - dict = e_cpt; - dict_f = DICT_FIRST; - } - } -#ifdef FEAT_FIND_ID - else if (*e_cpt == 'i') - type = CTRL_X_PATH_PATTERNS; - else if (*e_cpt == 'd') - type = CTRL_X_PATH_DEFINES; -#endif - else if (*e_cpt == ']' || *e_cpt == 't') - { - msg_hist_off = TRUE; // reset in msg_trunc_attr() - type = CTRL_X_TAGS; - vim_snprintf((char *)IObuff, IOSIZE, _("Scanning tags.")); - (void)msg_trunc_attr((char *)IObuff, TRUE, HL_ATTR(HLF_R)); - } - else - type = -1; - - // in any case e_cpt is advanced to the next entry - (void)copy_option_part(&e_cpt, IObuff, IOSIZE, ","); - - found_all = TRUE; - if (type == -1) - continue; - } + if (status == INS_COMPL_CPT_CONT) + continue; } // If complete() was called then compl_pattern has been reset. The @@ -3075,91 +3466,26 @@ ins_compl_get_exp(pos_T *ini) #ifdef FEAT_FIND_ID case CTRL_X_PATH_PATTERNS: case CTRL_X_PATH_DEFINES: - find_pattern_in_path(compl_pattern, compl_direction, - (int)STRLEN(compl_pattern), FALSE, FALSE, - (type == CTRL_X_PATH_DEFINES - && !(compl_cont_status & CONT_SOL)) - ? FIND_DEFINE : FIND_ANY, 1L, ACTION_EXPAND, - (linenr_T)1, (linenr_T)MAXLNUM); + get_next_include_file_completion(type); break; #endif case CTRL_X_DICTIONARY: case CTRL_X_THESAURUS: -#ifdef FEAT_COMPL_FUNC - if (thesaurus_func_complete(type)) - expand_by_function(type, compl_pattern); - else -#endif - ins_compl_dictionaries( - dict != NULL ? dict - : (type == CTRL_X_THESAURUS - ? (*curbuf->b_p_tsr == NUL - ? p_tsr - : curbuf->b_p_tsr) - : (*curbuf->b_p_dict == NUL - ? p_dict - : curbuf->b_p_dict)), - compl_pattern, - dict != NULL ? dict_f - : 0, type == CTRL_X_THESAURUS); - dict = NULL; + get_next_dict_tsr_completion(type, &dict, dict_f); break; case CTRL_X_TAGS: - // set p_ic according to p_ic, p_scs and pat for find_tags(). - save_p_ic = p_ic; - p_ic = ignorecase(compl_pattern); - - // Find up to TAG_MANY matches. Avoids that an enormous number - // of matches is found when compl_pattern is empty - g_tag_at_cursor = TRUE; - if (find_tags(compl_pattern, &num_matches, &matches, - TAG_REGEXP | TAG_NAMES | TAG_NOIC | TAG_INS_COMP - | (ctrl_x_mode != CTRL_X_NORMAL ? TAG_VERBOSE : 0), - TAG_MANY, curbuf->b_ffname) == OK && num_matches > 0) - ins_compl_add_matches(num_matches, matches, p_ic); - g_tag_at_cursor = FALSE; - p_ic = save_p_ic; + get_next_tag_completion(); break; case CTRL_X_FILES: - if (expand_wildcards(1, &compl_pattern, &num_matches, &matches, - EW_FILE|EW_DIR|EW_ADDSLASH|EW_SILENT) == OK) - { - - // May change home directory back to "~". - tilde_replace(compl_pattern, num_matches, matches); -#ifdef BACKSLASH_IN_FILENAME - if (curbuf->b_p_csl[0] != NUL) - { - int i; - - for (i = 0; i < num_matches; ++i) - { - char_u *ptr = matches[i]; - - while (*ptr != NUL) - { - if (curbuf->b_p_csl[0] == 's' && *ptr == '\\') - *ptr = '/'; - else if (curbuf->b_p_csl[0] == 'b' && *ptr == '/') - *ptr = '\\'; - ptr += (*mb_ptr2len)(ptr); - } - } - } -#endif - ins_compl_add_matches(num_matches, matches, p_fic || p_wic); - } + get_next_filename_completion(); break; case CTRL_X_CMDLINE: case CTRL_X_CMDLINE_CTRL_X: - if (expand_cmdline(&compl_xp, compl_pattern, - (int)STRLEN(compl_pattern), - &num_matches, &matches) == EXPAND_OK) - ins_compl_add_matches(num_matches, matches, FALSE); + get_next_cmdline_completion(); break; #ifdef FEAT_COMPL_FUNC @@ -3170,180 +3496,15 @@ ins_compl_get_exp(pos_T *ini) #endif case CTRL_X_SPELL: -#ifdef FEAT_SPELL - num_matches = expand_spelling(first_match_pos.lnum, - compl_pattern, &matches); - if (num_matches > 0) - ins_compl_add_matches(num_matches, matches, p_ic); -#endif + get_next_spell_completion(first_match_pos.lnum); break; default: // normal ^P/^N and ^X^L - // If 'infercase' is set, don't use 'smartcase' here - save_p_scs = p_scs; - if (ins_buf->b_p_inf) - p_scs = FALSE; - - // Buffers other than curbuf are scanned from the beginning or the - // end but never from the middle, thus setting nowrapscan in this - // buffer is a good idea, on the other hand, we always set - // wrapscan for curbuf to avoid missing matches -- Acevedo,Webb - save_p_ws = p_ws; - if (ins_buf != curbuf) - p_ws = FALSE; - else if (*e_cpt == '.') - p_ws = TRUE; - looped_around = FALSE; - for (;;) - { - int cont_s_ipos = FALSE; - - ++msg_silent; // Don't want messages for wrapscan. - - // ctrl_x_mode_line_or_eval() || word-wise search that - // has added a word that was at the beginning of the line - if (ctrl_x_mode_line_or_eval() - || (compl_cont_status & CONT_SOL)) - found_new_match = search_for_exact_line(ins_buf, pos, - compl_direction, compl_pattern); - else - found_new_match = searchit(NULL, ins_buf, pos, NULL, - compl_direction, - compl_pattern, 1L, SEARCH_KEEP + SEARCH_NFMSG, - RE_LAST, NULL); - --msg_silent; - if (!compl_started || set_match_pos) - { - // set "compl_started" even on fail - compl_started = TRUE; - first_match_pos = *pos; - last_match_pos = *pos; - set_match_pos = FALSE; - } - else if (first_match_pos.lnum == last_match_pos.lnum - && first_match_pos.col == last_match_pos.col) - { - found_new_match = FAIL; - } - else if ((compl_direction == FORWARD) - && (prev_pos.lnum > pos->lnum - || (prev_pos.lnum == pos->lnum - && prev_pos.col >= pos->col))) - { - if (looped_around) - found_new_match = FAIL; - else - looped_around = TRUE; - } - else if ((compl_direction != FORWARD) - && (prev_pos.lnum < pos->lnum - || (prev_pos.lnum == pos->lnum - && prev_pos.col <= pos->col))) - { - if (looped_around) - found_new_match = FAIL; - else - looped_around = TRUE; - } - prev_pos = *pos; - if (found_new_match == FAIL) - { - if (ins_buf == curbuf) - found_all = TRUE; - break; - } - - // when ADDING, the text before the cursor matches, skip it - if ( (compl_cont_status & CONT_ADDING) && ins_buf == curbuf - && ini->lnum == pos->lnum - && ini->col == pos->col) - continue; - ptr = ml_get_buf(ins_buf, pos->lnum, FALSE) + pos->col; - if (ctrl_x_mode_line_or_eval()) - { - if (compl_cont_status & CONT_ADDING) - { - if (pos->lnum >= ins_buf->b_ml.ml_line_count) - continue; - ptr = ml_get_buf(ins_buf, pos->lnum + 1, FALSE); - if (!p_paste) - ptr = skipwhite(ptr); - } - len = (int)STRLEN(ptr); - } - else - { - char_u *tmp_ptr = ptr; - - if (compl_cont_status & CONT_ADDING) - { - tmp_ptr += compl_length; - // Skip if already inside a word. - if (vim_iswordp(tmp_ptr)) - continue; - // Find start of next word. - tmp_ptr = find_word_start(tmp_ptr); - } - // Find end of this word. - tmp_ptr = find_word_end(tmp_ptr); - len = (int)(tmp_ptr - ptr); - - if ((compl_cont_status & CONT_ADDING) - && len == compl_length) - { - if (pos->lnum < ins_buf->b_ml.ml_line_count) - { - // Try next line, if any. the new word will be - // "join" as if the normal command "J" was used. - // IOSIZE is always greater than - // compl_length, so the next STRNCPY always - // works -- Acevedo - STRNCPY(IObuff, ptr, len); - ptr = ml_get_buf(ins_buf, pos->lnum + 1, FALSE); - tmp_ptr = ptr = skipwhite(ptr); - // Find start of next word. - tmp_ptr = find_word_start(tmp_ptr); - // Find end of next word. - tmp_ptr = find_word_end(tmp_ptr); - if (tmp_ptr > ptr) - { - if (*ptr != ')' && IObuff[len - 1] != TAB) - { - if (IObuff[len - 1] != ' ') - IObuff[len++] = ' '; - // IObuf =~ "\k.* ", thus len >= 2 - if (p_js - && (IObuff[len - 2] == '.' - || (vim_strchr(p_cpo, CPO_JOINSP) - == NULL - && (IObuff[len - 2] == '?' - || IObuff[len - 2] == '!')))) - IObuff[len++] = ' '; - } - // copy as much as possible of the new word - if (tmp_ptr - ptr >= IOSIZE - len) - tmp_ptr = ptr + IOSIZE - len - 1; - STRNCPY(IObuff + len, ptr, tmp_ptr - ptr); - len += (int)(tmp_ptr - ptr); - cont_s_ipos = TRUE; - } - IObuff[len] = NUL; - ptr = IObuff; - } - if (len == compl_length) - continue; - } - } - if (ins_compl_add_infercase(ptr, len, p_ic, - ins_buf == curbuf ? NULL : ins_buf->b_sfname, - 0, cont_s_ipos) != NOTDONE) - { - found_new_match = OK; - break; - } - } - p_scs = save_p_scs; - p_ws = save_p_ws; + found_new_match = get_next_default_completion(ins_buf, ini, pos, + &prev_pos, &set_match_pos, &first_match_pos, + &last_match_pos, (*e_cpt == '.')); + if (found_new_match == FAIL && ins_buf == curbuf) + found_all = TRUE; } // check if compl_curr_match has changed, (e.g. other type of @@ -3845,8 +4006,7 @@ ins_compl_use_match(int c) static int get_normal_compl_info(char_u *line, int startcol, colnr_T curs_col) { - if ((compl_cont_status & CONT_SOL) - || ctrl_x_mode == CTRL_X_PATH_DEFINES) + if ((compl_cont_status & CONT_SOL) || ctrl_x_mode == CTRL_X_PATH_DEFINES) { if (!(compl_cont_status & CONT_ADDING)) { diff --git a/src/testdir/test_ins_complete.vim b/src/testdir/test_ins_complete.vim --- a/src/testdir/test_ins_complete.vim +++ b/src/testdir/test_ins_complete.vim @@ -47,7 +47,7 @@ func Test_ins_complete() exe "normal o\\\\\\\\\" call assert_equal('run1 run2', getline('.')) - set cpt=.,w,i + set cpt=.,\ ,w,i " i-add-expands and switches to local exe "normal OM\\\\\\\\\" call assert_equal("Makefile\tto\trun3", getline('.')) @@ -748,6 +748,17 @@ func Test_complete_across_line() close! endfunc +" Test for completing words with a '.' at the end of a word. +func Test_complete_joinspaces() + new + call setline(1, ['one two.', 'three. four']) + set joinspaces + exe "normal Goon\\\\\\\\\" + call assert_equal("one two. three. four", getline(3)) + set joinspaces& + bw! +endfunc + " Test for using CTRL-L to add one character when completing matching func Test_complete_add_onechar() new @@ -768,6 +779,39 @@ func Test_complete_add_onechar() close! endfunc +" Test for using CTRL-X CTRL-L to complete whole lines lines +func Test_complete_wholeline() + new + " complete one-line + call setline(1, ['a1', 'a2']) + exe "normal ggoa\\" + call assert_equal(['a1', 'a1', 'a2'], getline(1, '$')) + " go to the next match (wrapping around the buffer) + exe "normal 2GCa\\\" + call assert_equal(['a1', 'a', 'a2'], getline(1, '$')) + " go to the next match + exe "normal 2GCa\\\\" + call assert_equal(['a1', 'a2', 'a2'], getline(1, '$')) + exe "normal 2GCa\\\\\" + call assert_equal(['a1', 'a1', 'a2'], getline(1, '$')) + " repeat the test using CTRL-L + " go to the next match (wrapping around the buffer) + exe "normal 2GCa\\\" + call assert_equal(['a1', 'a2', 'a2'], getline(1, '$')) + " go to the next match + exe "normal 2GCa\\\\" + call assert_equal(['a1', 'a', 'a2'], getline(1, '$')) + exe "normal 2GCa\\\\\" + call assert_equal(['a1', 'a1', 'a2'], getline(1, '$')) + %d + " use CTRL-X CTRL-L to add one more line + call setline(1, ['a1', 'b1']) + setlocal complete=. + exe "normal ggOa\\\\\\" + call assert_equal(['a1', 'b1', '', 'a1', 'b1'], getline(1, '$')) + bw! +endfunc + " Test insert completion with 'cindent' (adjust the indent) func Test_complete_with_cindent() new diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 3937, +/**/ 3936, /**/ 3935,