To: vim_dev@googlegroups.com Subject: Patch 8.2.3937 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.3937 Problem: Insert mode completion function is too long. Solution: Refactor into multiple functions. (Yegappan Lakshmanan, closes #9423) Files: src/insexpand.c, src/testdir/test_ins_complete.vim *** ../vim-8.2.3936/src/insexpand.c 2021-12-27 19:28:34.005419704 +0000 --- src/insexpand.c 2021-12-29 17:35:37.517730719 +0000 *************** *** 2901,2906 **** --- 2901,3393 ---- } /* + * 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,2940 **** 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) { --- 3407,3419 ---- *************** *** 2965,3066 **** || 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) 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 complete() was called then compl_pattern has been reset. The --- 3444,3457 ---- || ctrl_x_mode_line_or_eval()) && (!compl_started || found_all)) { ! 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; ! if (status == INS_COMPL_CPT_CONT) ! continue; } // If complete() was called then compl_pattern has been reset. The *************** *** 3075,3165 **** #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); 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; 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; 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); ! } 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); break; #ifdef FEAT_COMPL_FUNC --- 3466,3491 ---- #ifdef FEAT_FIND_ID case CTRL_X_PATH_PATTERNS: case CTRL_X_PATH_DEFINES: ! get_next_include_file_completion(type); break; #endif case CTRL_X_DICTIONARY: case CTRL_X_THESAURUS: ! get_next_dict_tsr_completion(type, &dict, dict_f); break; case CTRL_X_TAGS: ! get_next_tag_completion(); break; case CTRL_X_FILES: ! get_next_filename_completion(); break; case CTRL_X_CMDLINE: case CTRL_X_CMDLINE_CTRL_X: ! get_next_cmdline_completion(); break; #ifdef FEAT_COMPL_FUNC *************** *** 3170,3349 **** #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 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; } // check if compl_curr_match has changed, (e.g. other type of --- 3496,3510 ---- #endif case CTRL_X_SPELL: ! get_next_spell_completion(first_match_pos.lnum); break; default: // normal ^P/^N and ^X^L ! 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,3852 **** 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_ADDING)) { --- 4006,4012 ---- 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_ADDING)) { *** ../vim-8.2.3936/src/testdir/test_ins_complete.vim 2021-12-27 19:28:34.005419704 +0000 --- src/testdir/test_ins_complete.vim 2021-12-29 17:32:09.761953444 +0000 *************** *** 47,53 **** exe "normal o\\\\\\\\\" call assert_equal('run1 run2', getline('.')) ! set cpt=.,w,i " i-add-expands and switches to local exe "normal OM\\\\\\\\\" call assert_equal("Makefile\tto\trun3", getline('.')) --- 47,53 ---- exe "normal o\\\\\\\\\" call assert_equal('run1 run2', getline('.')) ! set cpt=.,\ ,w,i " i-add-expands and switches to local exe "normal OM\\\\\\\\\" call assert_equal("Makefile\tto\trun3", getline('.')) *************** *** 748,753 **** --- 748,764 ---- 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,773 **** --- 779,817 ---- 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 *** ../vim-8.2.3936/src/version.c 2021-12-29 16:44:44.709780642 +0000 --- src/version.c 2021-12-29 17:38:20.885542880 +0000 *************** *** 751,752 **** --- 751,754 ---- { /* Add new patch number below this line */ + /**/ + 3937, /**/ -- hundred-and-one symptoms of being an internet addict: 143. You dream in pallettes of 216 websafe colors. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///