diff options
Diffstat (limited to 'src/nvim/insexpand.c')
| -rw-r--r-- | src/nvim/insexpand.c | 3005 |
1 files changed, 1707 insertions, 1298 deletions
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 938189f343..da1063f699 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -43,6 +43,7 @@ #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/tag.h" +#include "nvim/textformat.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -131,15 +132,30 @@ typedef struct compl_S compl_T; struct compl_S { compl_T *cp_next; compl_T *cp_prev; - char_u *cp_str; ///< matched text - char_u *(cp_text[CPT_COUNT]); ///< text for the menu + char *cp_str; ///< matched text + char *(cp_text[CPT_COUNT]); ///< text for the menu typval_T cp_user_data; - char_u *cp_fname; ///< file containing the match, allocated when + char *cp_fname; ///< file containing the match, allocated when ///< cp_flags has CP_FREE_FNAME int cp_flags; ///< CP_ values int cp_number; ///< sequence number }; +/// state information used for getting the next set of insert completion +/// matches. +typedef struct { + char *e_cpt; ///< current entry in 'complete' + buf_T *ins_buf; ///< buffer being scanned + pos_T *cur_match_pos; ///< current match position + pos_T prev_match_pos; ///< previous match position + bool set_match_pos; ///< save first_match_pos/last_match_pos + pos_T first_match_pos; ///< first match position + pos_T last_match_pos; ///< last match position + bool found_all; ///< found all matches of a certain type. + char_u *dict; ///< dictionary file to search + int dict_f; ///< "dict" is an exact file name or not +} ins_compl_next_state_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "insexpand.c.generated.h" #endif @@ -162,6 +178,7 @@ static char e_compldel[] = N_("E840: Completion function deleted text"); // "compl_curr_match" points to the currently selected entry. // "compl_shown_match" is different from compl_curr_match during // ins_compl_get_exp(). +// "compl_old_match" points to previous "compl_curr_match". static compl_T *compl_first_match = NULL; static compl_T *compl_curr_match = NULL; @@ -174,7 +191,7 @@ static bool compl_enter_selects = false; /// When "compl_leader" is not NULL only matches that start with this string /// are used. -static char_u *compl_leader = NULL; +static char *compl_leader = NULL; static bool compl_get_longest = false; ///< put longest common string in compl_leader @@ -182,6 +199,8 @@ static bool compl_no_insert = false; ///< false: select & insert ///< true: noinsert static bool compl_no_select = false; ///< false: select & insert ///< true: noselect +static bool compl_longest = false; ///< false: insert full match + ///< true: insert longest prefix /// Selected one of the matches. When false the match was edited or using the /// longest common string. @@ -203,19 +222,36 @@ static bool compl_started = false; ///< Which Ctrl-X mode are we in? static int ctrl_x_mode = CTRL_X_NORMAL; -static int compl_matches = 0; +static int compl_matches = 0; ///< number of completion matches static char *compl_pattern = NULL; static Direction compl_direction = FORWARD; static Direction compl_shows_dir = FORWARD; static int compl_pending = 0; ///< > 1 for postponed CTRL-N static pos_T compl_startpos; +/// Length in bytes of the text being completed (this is deleted to be replaced +/// by the match.) +static int compl_length = 0; static colnr_T compl_col = 0; ///< column where the text starts ///< that is being completed -static char_u *compl_orig_text = NULL; ///< text as it was before +static char *compl_orig_text = NULL; ///< text as it was before ///< completion started static int compl_cont_mode = 0; static expand_T compl_xp; +// List of flags for method of completion. +static int compl_cont_status = 0; + +#define CONT_ADDING 1 ///< "normal" or "adding" expansion +#define CONT_INTRPT (2 + 4) ///< a ^X interrupted the current expansion + ///< it's set only iff N_ADDS is set +#define CONT_N_ADDS 4 ///< next ^X<> will add-new or expand-current +#define CONT_S_IPOS 8 ///< next ^X<> will set initial_pos? + ///< if so, word-wise-expansion will set SOL +#define CONT_SOL 16 ///< pattern includes start of line, just for + ///< word-wise expansion, not set for ^X^L +#define CONT_LOCAL 32 ///< for ctrl_x_mode 0, ^X^P/^X^N do a local + ///< expansion, (eg use complete=.) + static bool compl_opt_refresh_always = false; static size_t spell_bad_len = 0; // length of located bad word @@ -234,7 +270,7 @@ void ins_ctrl_x(void) } // We're not sure which CTRL-X mode it will be yet ctrl_x_mode = CTRL_X_NOT_DEFINED_YET; - edit_submode = (char_u *)_(CTRL_X_MSG(ctrl_x_mode)); + edit_submode = _(CTRL_X_MSG(ctrl_x_mode)); edit_submode_pre = NULL; showmode(); } else { @@ -359,9 +395,52 @@ bool ctrl_x_mode_not_defined_yet(void) return ctrl_x_mode == CTRL_X_NOT_DEFINED_YET; } -/// Check that the "dict" or "tsr" option can be used. +/// @return true if currently in "normal" or "adding" insert completion matches state +bool compl_status_adding(void) +{ + return compl_cont_status & CONT_ADDING; +} + +/// @return true if the completion pattern includes start of line, just for +/// word-wise expansion. +bool compl_status_sol(void) +{ + return compl_cont_status & CONT_SOL; +} + +/// @return true if ^X^P/^X^N will do a local completion (i.e. use complete=.) +bool compl_status_local(void) +{ + return compl_cont_status & CONT_LOCAL; +} + +/// Clear the completion status flags +void compl_status_clear(void) +{ + compl_cont_status = 0; +} + +// @return true if completion is using the forward direction matches +static bool compl_dir_forward(void) +{ + return compl_direction == FORWARD; +} + +/// @return true if currently showing forward completion matches +static bool compl_shows_dir_forward(void) +{ + return compl_shows_dir == FORWARD; +} + +/// @return true if currently showing backward completion matches +static bool compl_shows_dir_backward(void) +{ + return compl_shows_dir == BACKWARD; +} + +/// Check that the 'dictionary' or 'thesaurus' option can be used. /// -/// @param dict_opt check "dict" when true, "tsr" when false. +/// @param dict_opt check 'dictionary' when true, 'thesaurus' when false. bool check_compl_option(bool dict_opt) { if (dict_opt @@ -445,6 +524,18 @@ bool vim_is_ctrl_x_key(int c) return false; } +/// @return true if "match" is the original text when the completion began. +static bool match_at_original_text(const compl_T *const match) +{ + return match->cp_flags & CP_ORIGINAL_TEXT; +} + +/// @return true if "match" is the first match in the completion list. +static bool is_first_match(const compl_T *const match) +{ + return match == compl_first_match; +} + /// Check that character "c" is part of the item currently being /// completed. Used to decide whether to abandon complete mode when the menu /// is visible. @@ -479,6 +570,106 @@ bool ins_compl_accept_char(int c) return vim_iswordc(c); } +/// Get the completed text by inferring the case of the originally typed text. +/// If the result is in allocated memory "tofree" is set to it. +static char_u *ins_compl_infercase_gettext(char_u *str, int char_len, int compl_char_len, + int min_len, char **tofree) +{ + bool has_lower = false; + bool was_letter = false; + + // Allocate wide character array for the completion and fill it. + int *const wca = xmalloc((size_t)char_len * sizeof(*wca)); + { + const char_u *p = str; + for (int i = 0; i < char_len; i++) { + wca[i] = mb_ptr2char_adv(&p); + } + } + + // Rule 1: Were any chars converted to lower? + { + const char_u *p = (char_u *)compl_orig_text; + for (int i = 0; i < min_len; i++) { + const int c = mb_ptr2char_adv(&p); + if (mb_islower(c)) { + has_lower = true; + if (mb_isupper(wca[i])) { + // Rule 1 is satisfied. + for (i = compl_char_len; i < char_len; i++) { + wca[i] = mb_tolower(wca[i]); + } + break; + } + } + } + } + + // Rule 2: No lower case, 2nd consecutive letter converted to + // upper case. + if (!has_lower) { + const char_u *p = (char_u *)compl_orig_text; + for (int i = 0; i < min_len; i++) { + const int c = mb_ptr2char_adv(&p); + if (was_letter && mb_isupper(c) && mb_islower(wca[i])) { + // Rule 2 is satisfied. + for (i = compl_char_len; i < char_len; i++) { + wca[i] = mb_toupper(wca[i]); + } + break; + } + was_letter = mb_islower(c) || mb_isupper(c); + } + } + + // Copy the original case of the part we typed. + { + const char_u *p = (char_u *)compl_orig_text; + for (int i = 0; i < min_len; i++) { + const int c = mb_ptr2char_adv(&p); + if (mb_islower(c)) { + wca[i] = mb_tolower(wca[i]); + } else if (mb_isupper(c)) { + wca[i] = mb_toupper(wca[i]); + } + } + } + + // Generate encoding specific output from wide character array. + garray_T gap; + char *p = (char *)IObuff; + int i = 0; + ga_init(&gap, 1, 500); + while (i < char_len) { + if (gap.ga_data != NULL) { + ga_grow(&gap, 10); + assert(gap.ga_data != NULL); // suppress clang "Dereference of NULL pointer" + p = (char *)gap.ga_data + gap.ga_len; + gap.ga_len += utf_char2bytes(wca[i++], p); + } else if ((p - (char *)IObuff) + 6 >= IOSIZE) { + // Multi-byte characters can occupy up to five bytes more than + // ASCII characters, and we also need one byte for NUL, so when + // getting to six bytes from the edge of IObuff switch to using a + // growarray. Add the character in the next round. + ga_grow(&gap, IOSIZE); + *p = NUL; + STRCPY(gap.ga_data, IObuff); + gap.ga_len = (int)strlen(IObuff); + } else { + p += utf_char2bytes(wca[i++], p); + } + } + xfree(wca); + + if (gap.ga_data != NULL) { + *tofree = gap.ga_data; + return gap.ga_data; + } + + *p = NUL; + return (char_u *)IObuff; +} + /// This is like ins_compl_add(), but if 'ic' and 'inf' are set, then the /// case of the originally typed text is used, and the case of the completed /// text is inferred, ie this tries to work out what case you probably wanted @@ -490,13 +681,10 @@ int ins_compl_add_infercase(char_u *str_arg, int len, bool icase, char_u *fname, FUNC_ATTR_NONNULL_ARG(1) { char_u *str = str_arg; - int i, c; - int actual_len; // Take multi-byte characters - int actual_compl_length; // into account. - int min_len; - bool has_lower = false; - bool was_letter = false; + int char_len; // count multi-byte characters + int compl_char_len; int flags = 0; + char *tofree = NULL; if (p_ic && curbuf->b_p_inf && len > 0) { // Infer case of completed part. @@ -504,101 +692,28 @@ int ins_compl_add_infercase(char_u *str_arg, int len, bool icase, char_u *fname, // Find actual length of completion. { const char_u *p = str; - actual_len = 0; + char_len = 0; while (*p != NUL) { MB_PTR_ADV(p); - actual_len++; + char_len++; } } // Find actual length of original text. { - const char_u *p = compl_orig_text; - actual_compl_length = 0; + const char_u *p = (char_u *)compl_orig_text; + compl_char_len = 0; while (*p != NUL) { MB_PTR_ADV(p); - actual_compl_length++; + compl_char_len++; } } - // "actual_len" may be smaller than "actual_compl_length" when using + // "char_len" may be smaller than "compl_char_len" when using // thesaurus, only use the minimum when comparing. - min_len = actual_len < actual_compl_length - ? actual_len : actual_compl_length; - - // Allocate wide character array for the completion and fill it. - int *const wca = xmalloc((size_t)actual_len * sizeof(*wca)); - { - const char_u *p = str; - for (i = 0; i < actual_len; i++) { - wca[i] = mb_ptr2char_adv(&p); - } - } - - // Rule 1: Were any chars converted to lower? - { - const char_u *p = compl_orig_text; - for (i = 0; i < min_len; i++) { - c = mb_ptr2char_adv(&p); - if (mb_islower(c)) { - has_lower = true; - if (mb_isupper(wca[i])) { - // Rule 1 is satisfied. - for (i = actual_compl_length; i < actual_len; i++) { - wca[i] = mb_tolower(wca[i]); - } - break; - } - } - } - } - - // Rule 2: No lower case, 2nd consecutive letter converted to - // upper case. - if (!has_lower) { - const char_u *p = compl_orig_text; - for (i = 0; i < min_len; i++) { - c = mb_ptr2char_adv(&p); - if (was_letter && mb_isupper(c) && mb_islower(wca[i])) { - // Rule 2 is satisfied. - for (i = actual_compl_length; i < actual_len; i++) { - wca[i] = mb_toupper(wca[i]); - } - break; - } - was_letter = mb_islower(c) || mb_isupper(c); - } - } - - // Copy the original case of the part we typed. - { - const char_u *p = compl_orig_text; - for (i = 0; i < min_len; i++) { - c = mb_ptr2char_adv(&p); - if (mb_islower(c)) { - wca[i] = mb_tolower(wca[i]); - } else if (mb_isupper(c)) { - wca[i] = mb_toupper(wca[i]); - } - } - } + int min_len = char_len < compl_char_len ? char_len : compl_char_len; - // Generate encoding specific output from wide character array. - // Multi-byte characters can occupy up to five bytes more than - // ASCII characters, and we also need one byte for NUL, so stay - // six bytes away from the edge of IObuff. - { - char_u *p = IObuff; - i = 0; - while (i < actual_len && (p - IObuff + 6) < IOSIZE) { - p += utf_char2bytes(wca[i++], (char *)p); - } - *p = NUL; - } - - xfree(wca); - - str = IObuff; + str = ins_compl_infercase_gettext(str, char_len, compl_char_len, min_len, &tofree); } if (cont_s_ipos) { flags |= CP_CONT_S_IPOS; @@ -607,29 +722,37 @@ int ins_compl_add_infercase(char_u *str_arg, int len, bool icase, char_u *fname, flags |= CP_ICASE; } - return ins_compl_add(str, len, fname, NULL, false, NULL, dir, flags, false); + int res = ins_compl_add((char *)str, len, (char *)fname, NULL, false, NULL, dir, flags, false); + xfree(tofree); + return res; } /// Add a match to the list of matches /// -/// @param[in] str Match to add. -/// @param[in] len Match length, -1 to use #STRLEN. -/// @param[in] fname File name match comes from. May be NULL. -/// @param[in] cptext Extra text for popup menu. May be NULL. If not NULL, -/// must have exactly #CPT_COUNT items. +/// @param[in] str text of the match to add +/// @param[in] len length of "str". If -1, then the length of "str" is computed. +/// @param[in] fname file name to associate with this match. May be NULL. +/// @param[in] cptext list of strings to use with this match (for abbr, menu, info +/// and kind). May be NULL. +/// If not NULL, must have exactly #CPT_COUNT items. /// @param[in] cptext_allocated If true, will not copy cptext strings. /// /// @note Will free strings in case of error. /// cptext itself will not be freed. -/// @param[in] cdir Completion direction. -/// @param[in] adup True if duplicate matches are to be accepted. +/// @param[in] user_data user supplied data (any vim type) for this match +/// @param[in] cdir match direction. If 0, use "compl_direction". +/// @param[in] flags_arg match flags (cp_flags) +/// @param[in] adup accept this match even if it is already present. +/// +/// If "cdir" is FORWARD, then the match is added after the current match. +/// Otherwise, it is added before the current match. /// /// @return NOTDONE if the given string is already in the list of completions, /// otherwise it is added to the list and OK is returned. FAIL will be /// returned in case of error. -static int ins_compl_add(char_u *const str, int len, char_u *const fname, - char_u *const *const cptext, const bool cptext_allocated, - typval_T *user_data, const Direction cdir, int flags_arg, const bool adup) +static int ins_compl_add(char *const str, int len, char *const fname, char *const *const cptext, + const bool cptext_allocated, typval_T *user_data, const Direction cdir, + int flags_arg, const bool adup) FUNC_ATTR_NONNULL_ARG(1) { compl_T *match; @@ -654,21 +777,21 @@ static int ins_compl_add(char_u *const str, int len, char_u *const fname, return FAIL; } if (len < 0) { - len = (int)STRLEN(str); + len = (int)strlen(str); } // If the same match is already present, don't add it. if (compl_first_match != NULL && !adup) { match = compl_first_match; do { - if (!(match->cp_flags & CP_ORIGINAL_TEXT) + if (!match_at_original_text(match) && STRNCMP(match->cp_str, str, len) == 0 - && match->cp_str[len] == NUL) { + && ((int)strlen(match->cp_str) <= len || match->cp_str[len] == NUL)) { FREE_CPTEXT(cptext, cptext_allocated); return NOTDONE; } match = match->cp_next; - } while (match != NULL && match != compl_first_match); + } while (match != NULL && !is_first_match(match)); } // Remove any popup menu before changing the list of matches. @@ -681,7 +804,7 @@ static int ins_compl_add(char_u *const str, int len, char_u *const fname, if (flags & CP_ORIGINAL_TEXT) { match->cp_number = 0; } - match->cp_str = vim_strnsave(str, (size_t)len); + match->cp_str = xstrnsave(str, (size_t)len); // match-fname is: // - compl_curr_match->cp_fname if it is a string equal to fname. @@ -690,10 +813,10 @@ static int ins_compl_add(char_u *const str, int len, char_u *const fname, if (fname != NULL && compl_curr_match != NULL && compl_curr_match->cp_fname != NULL - && STRCMP(fname, compl_curr_match->cp_fname) == 0) { + && strcmp(fname, compl_curr_match->cp_fname) == 0) { match->cp_fname = compl_curr_match->cp_fname; } else if (fname != NULL) { - match->cp_fname = vim_strsave(fname); + match->cp_fname = xstrdup(fname); flags |= CP_FREE_FNAME; } else { match->cp_fname = NULL; @@ -708,9 +831,7 @@ static int ins_compl_add(char_u *const str, int len, char_u *const fname, continue; } if (*cptext[i] != NUL) { - match->cp_text[i] = (cptext_allocated - ? cptext[i] - : (char_u *)xstrdup((char *)cptext[i])); + match->cp_text[i] = (cptext_allocated ? cptext[i] : xstrdup(cptext[i])); } else if (cptext_allocated) { xfree(cptext[i]); } @@ -721,7 +842,8 @@ static int ins_compl_add(char_u *const str, int len, char_u *const fname, match->cp_user_data = *user_data; } - // Link the new match structure in the list of matches. + // Link the new match structure after (FORWARD) or before (BACKWARD) the + // current match in the list of matches . if (compl_first_match == NULL) { match->cp_next = match->cp_prev = NULL; } else if (dir == FORWARD) { @@ -776,7 +898,8 @@ static void ins_compl_longest_match(compl_T *match) if (compl_leader == NULL) { // First match, use it as a whole. - compl_leader = vim_strsave(match->cp_str); + compl_leader = xstrdup(match->cp_str); + had_match = (curwin->w_cursor.col > compl_col); ins_compl_delete(); ins_bytes(compl_leader + get_compl_len()); @@ -788,40 +911,42 @@ static void ins_compl_longest_match(compl_T *match) ins_compl_delete(); } compl_used_match = false; - } else { - // Reduce the text if this match differs from compl_leader. - p = compl_leader; - s = match->cp_str; - while (*p != NUL) { - c1 = utf_ptr2char((char *)p); - c2 = utf_ptr2char((char *)s); - - if ((match->cp_flags & CP_ICASE) - ? (mb_tolower(c1) != mb_tolower(c2)) - : (c1 != c2)) { - break; - } - MB_PTR_ADV(p); - MB_PTR_ADV(s); - } - if (*p != NUL) { - // Leader was shortened, need to change the inserted text. - *p = NUL; - had_match = (curwin->w_cursor.col > compl_col); - ins_compl_delete(); - ins_bytes(compl_leader + get_compl_len()); - ins_redraw(false); + return; + } - // When the match isn't there (to avoid matching itself) remove it - // again after redrawing. - if (!had_match) { - ins_compl_delete(); - } + // Reduce the text if this match differs from compl_leader. + p = (char_u *)compl_leader; + s = (char_u *)match->cp_str; + while (*p != NUL) { + c1 = utf_ptr2char((char *)p); + c2 = utf_ptr2char((char *)s); + + if ((match->cp_flags & CP_ICASE) + ? (mb_tolower(c1) != mb_tolower(c2)) + : (c1 != c2)) { + break; } + MB_PTR_ADV(p); + MB_PTR_ADV(s); + } - compl_used_match = false; + if (*p != NUL) { + // Leader was shortened, need to change the inserted text. + *p = NUL; + had_match = (curwin->w_cursor.col > compl_col); + ins_compl_delete(); + ins_bytes(compl_leader + get_compl_len()); + ins_redraw(false); + + // When the match isn't there (to avoid matching itself) remove it + // again after redrawing. + if (!had_match) { + ins_compl_delete(); + } } + + compl_used_match = false; } /// Add an array of matches to the list of matches. @@ -832,7 +957,7 @@ static void ins_compl_add_matches(int num_matches, char **matches, int icase) Direction dir = compl_direction; for (int i = 0; i < num_matches && add_r != FAIL; i++) { - if ((add_r = ins_compl_add((char_u *)matches[i], -1, NULL, NULL, false, NULL, dir, + if ((add_r = ins_compl_add(matches[i], -1, NULL, NULL, false, NULL, dir, CP_FAST | (icase ? CP_ICASE : 0), false)) == OK) { // If dir was BACKWARD then honor it just once. @@ -846,20 +971,21 @@ static void ins_compl_add_matches(int num_matches, char **matches, int icase) /// Return the number of matches (excluding the original). static int ins_compl_make_cyclic(void) { - compl_T *match; - int count = 0; + if (compl_first_match == NULL) { + return 0; + } - if (compl_first_match != NULL) { - // Find the end of the list. - match = compl_first_match; - // there's always an entry for the compl_orig_text, it doesn't count. - while (match->cp_next != NULL && match->cp_next != compl_first_match) { - match = match->cp_next; - count++; - } - match->cp_next = compl_first_match; - compl_first_match->cp_prev = match; + // Find the end of the list. + compl_T *match = compl_first_match; + int count = 0; + // there's always an entry for the compl_orig_text, it doesn't count. + while (match->cp_next != NULL && !is_first_match(match->cp_next)) { + match = match->cp_next; + count++; } + match->cp_next = compl_first_match; + compl_first_match->cp_prev = match; + return count; } @@ -873,7 +999,7 @@ bool ins_compl_has_shown_match(void) bool ins_compl_long_shown_match(void) { return compl_shown_match != NULL && compl_shown_match->cp_str != NULL - && (colnr_T)STRLEN(compl_shown_match->cp_str) > curwin->w_cursor.col - compl_col; + && (colnr_T)strlen(compl_shown_match->cp_str) > curwin->w_cursor.col - compl_col; } /// Set variables that store noselect and noinsert behavior from the @@ -882,12 +1008,16 @@ void completeopt_was_set(void) { compl_no_insert = false; compl_no_select = false; - if (strstr((char *)p_cot, "noselect") != NULL) { + compl_longest = false; + if (strstr(p_cot, "noselect") != NULL) { compl_no_select = true; } - if (strstr((char *)p_cot, "noinsert") != NULL) { + if (strstr(p_cot, "noinsert") != NULL) { compl_no_insert = true; } + if (strstr(p_cot, "longest") != NULL) { + compl_longest = true; + } } /// "compl_match_array" points the currently displayed list of entries in the @@ -898,10 +1028,12 @@ static int compl_match_arraysize; /// Remove any popup menu. static void ins_compl_del_pum(void) { - if (compl_match_array != NULL) { - pum_undisplay(false); - XFREE_CLEAR(compl_match_array); + if (compl_match_array == NULL) { + return; } + + pum_undisplay(false); + XFREE_CLEAR(compl_match_array); } /// Check if the popup menu should be displayed. @@ -909,7 +1041,7 @@ bool pum_wanted(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { // "completeopt" must contain "menu" or "menuone" - return vim_strchr((char *)p_cot, 'm') != NULL; + return vim_strchr(p_cot, 'm') != NULL; } /// Check that there are two or more matches to be shown in the popup menu. @@ -919,17 +1051,16 @@ static bool pum_enough_matches(void) { // Don't display the popup menu if there are no matches or there is only // one (ignoring the original text). - compl_T *comp = compl_first_match; + compl_T *compl = compl_first_match; int i = 0; do { - if (comp == NULL - || ((comp->cp_flags & CP_ORIGINAL_TEXT) == 0 && ++i == 2)) { + if (compl == NULL || (!match_at_original_text(compl) && ++i == 2)) { break; } - comp = comp->cp_next; - } while (comp != compl_first_match); + compl = compl->cp_next; + } while (!is_first_match(compl)); - if (strstr((char *)p_cot, "menuone") != NULL) { + if (strstr(p_cot, "menuone") != NULL) { return i >= 1; } return i >= 2; @@ -953,6 +1084,8 @@ static dict_T *ins_compl_dict_alloc(compl_T *match) return dict; } +/// Trigger the CompleteChanged autocmd event. Invoked each time the Insert mode +/// completion menu is changed. static void trigger_complete_changed_event(int cur) { static bool recursive = false; @@ -981,141 +1114,151 @@ static void trigger_complete_changed_event(int cur) restore_v_event(v_event, &save_v_event); } -/// Show the popup menu for the list of matches. -/// Also adjusts "compl_shown_match" to an entry that is actually displayed. -void ins_compl_show_pum(void) +/// Build a popup menu to show the completion matches. +/// +/// @return the popup menu entry that should be selected, +/// -1 if nothing should be selected. +static int ins_compl_build_pum(void) { - compl_T *compl; - compl_T *shown_compl = NULL; - bool did_find_shown_match = false; - bool shown_match_ok = false; - int i; - int cur = -1; - colnr_T col; - int lead_len = 0; - bool array_changed = false; + // Need to build the popup menu list. + compl_match_arraysize = 0; + compl_T *compl = compl_first_match; - if (!pum_wanted() || !pum_enough_matches()) { - return; + // If it's user complete function and refresh_always, + // do not use "compl_leader" as prefix filter. + if (ins_compl_need_restart()) { + XFREE_CLEAR(compl_leader); } - // Dirty hard-coded hack: remove any matchparen highlighting. - do_cmdline_cmd("if exists('g:loaded_matchparen')|3match none|endif"); - - // Update the screen before drawing the popup menu over it. - update_screen(0); + const int lead_len = compl_leader != NULL ? (int)strlen(compl_leader) : 0; - if (compl_match_array == NULL) { - array_changed = true; - // Need to build the popup menu list. - compl_match_arraysize = 0; - compl = compl_first_match; - // If it's user complete function and refresh_always, - // do not use "compl_leader" as prefix filter. - if (ins_compl_need_restart()) { - XFREE_CLEAR(compl_leader); - } - if (compl_leader != NULL) { - lead_len = (int)STRLEN(compl_leader); - } - do { - if ((compl->cp_flags & CP_ORIGINAL_TEXT) == 0 - && (compl_leader == NULL - || ins_compl_equal(compl, compl_leader, (size_t)lead_len))) { - compl_match_arraysize++; - } - compl = compl->cp_next; - } while (compl != NULL && compl != compl_first_match); - if (compl_match_arraysize == 0) { - return; + do { + if (!match_at_original_text(compl) + && (compl_leader == NULL + || ins_compl_equal(compl, (char_u *)compl_leader, (size_t)lead_len))) { + compl_match_arraysize++; } + compl = compl->cp_next; + } while (compl != NULL && !is_first_match(compl)); - assert(compl_match_arraysize >= 0); - compl_match_array = xcalloc((size_t)compl_match_arraysize, sizeof(pumitem_T)); - // If the current match is the original text don't find the first - // match after it, don't highlight anything. - if (compl_shown_match->cp_flags & CP_ORIGINAL_TEXT) { - shown_match_ok = true; - } + if (compl_match_arraysize == 0) { + return -1; + } - i = 0; - compl = compl_first_match; - do { - if ((compl->cp_flags & CP_ORIGINAL_TEXT) == 0 - && (compl_leader == NULL - || ins_compl_equal(compl, compl_leader, (size_t)lead_len))) { - if (!shown_match_ok) { - if (compl == compl_shown_match || did_find_shown_match) { - // This item is the shown match or this is the - // first displayed item after the shown match. - compl_shown_match = compl; - did_find_shown_match = true; - shown_match_ok = true; - } else { - // Remember this displayed match for when the - // shown match is just below it. - shown_compl = compl; - } - cur = i; - } + assert(compl_match_arraysize >= 0); + compl_match_array = xcalloc((size_t)compl_match_arraysize, sizeof(pumitem_T)); - if (compl->cp_text[CPT_ABBR] != NULL) { - compl_match_array[i].pum_text = - compl->cp_text[CPT_ABBR]; - } else { - compl_match_array[i].pum_text = compl->cp_str; - } - compl_match_array[i].pum_kind = compl->cp_text[CPT_KIND]; - compl_match_array[i].pum_info = compl->cp_text[CPT_INFO]; - if (compl->cp_text[CPT_MENU] != NULL) { - compl_match_array[i++].pum_extra = - compl->cp_text[CPT_MENU]; + // If the current match is the original text don't find the first + // match after it, don't highlight anything. + bool shown_match_ok = match_at_original_text(compl_shown_match); + + compl_T *shown_compl = NULL; + bool did_find_shown_match = false; + int cur = -1; + int i = 0; + compl = compl_first_match; + do { + if (!match_at_original_text(compl) + && (compl_leader == NULL + || ins_compl_equal(compl, (char_u *)compl_leader, (size_t)lead_len))) { + if (!shown_match_ok) { + if (compl == compl_shown_match || did_find_shown_match) { + // This item is the shown match or this is the + // first displayed item after the shown match. + compl_shown_match = compl; + did_find_shown_match = true; + shown_match_ok = true; } else { - compl_match_array[i++].pum_extra = compl->cp_fname; + // Remember this displayed match for when the + // shown match is just below it. + shown_compl = compl; } + cur = i; } - if (compl == compl_shown_match) { - did_find_shown_match = true; + if (compl->cp_text[CPT_ABBR] != NULL) { + compl_match_array[i].pum_text = (char_u *)compl->cp_text[CPT_ABBR]; + } else { + compl_match_array[i].pum_text = (char_u *)compl->cp_str; + } + compl_match_array[i].pum_kind = (char_u *)compl->cp_text[CPT_KIND]; + compl_match_array[i].pum_info = (char_u *)compl->cp_text[CPT_INFO]; + if (compl->cp_text[CPT_MENU] != NULL) { + compl_match_array[i++].pum_extra = (char_u *)compl->cp_text[CPT_MENU]; + } else { + compl_match_array[i++].pum_extra = (char_u *)compl->cp_fname; + } + } - // When the original text is the shown match don't set - // compl_shown_match. - if (compl->cp_flags & CP_ORIGINAL_TEXT) { - shown_match_ok = true; - } + if (compl == compl_shown_match) { + did_find_shown_match = true; - if (!shown_match_ok && shown_compl != NULL) { - // The shown match isn't displayed, set it to the - // previously displayed match. - compl_shown_match = shown_compl; - shown_match_ok = true; - } + // When the original text is the shown match don't set + // compl_shown_match. + if (match_at_original_text(compl)) { + shown_match_ok = true; } - compl = compl->cp_next; - } while (compl != NULL && compl != compl_first_match); - if (!shown_match_ok) { // no displayed match at all - cur = -1; + if (!shown_match_ok && shown_compl != NULL) { + // The shown match isn't displayed, set it to the + // previously displayed match. + compl_shown_match = shown_compl; + shown_match_ok = true; + } } + compl = compl->cp_next; + } while (compl != NULL && !is_first_match(compl)); + + if (!shown_match_ok) { // no displayed match at all + cur = -1; + } + + return cur; +} + +/// Show the popup menu for the list of matches. +/// Also adjusts "compl_shown_match" to an entry that is actually displayed. +void ins_compl_show_pum(void) +{ + if (!pum_wanted() || !pum_enough_matches()) { + return; + } + + // Dirty hard-coded hack: remove any matchparen highlighting. + do_cmdline_cmd("if exists('g:loaded_matchparen')|3match none|endif"); + + // Update the screen before drawing the popup menu over it. + update_screen(); + + int cur = -1; + bool array_changed = false; + + if (compl_match_array == NULL) { + array_changed = true; + // Need to build the popup menu list. + cur = ins_compl_build_pum(); } else { // popup menu already exists, only need to find the current item. - for (i = 0; i < compl_match_arraysize; i++) { - if (compl_match_array[i].pum_text == compl_shown_match->cp_str - || compl_match_array[i].pum_text - == compl_shown_match->cp_text[CPT_ABBR]) { + for (int i = 0; i < compl_match_arraysize; i++) { + if (compl_match_array[i].pum_text == (char_u *)compl_shown_match->cp_str + || compl_match_array[i].pum_text == (char_u *)compl_shown_match->cp_text[CPT_ABBR]) { cur = i; break; } } } + if (compl_match_array == NULL) { + return; + } + // In Replace mode when a $ is displayed at the end of the line only // part of the screen would be updated. We do need to redraw here. dollar_vcol = -1; // Compute the screen column of the start of the completed text. // Use the cursor to get all wrapping and other settings right. - col = curwin->w_cursor.col; + const colnr_T col = curwin->w_cursor.col; curwin->w_cursor.col = compl_col; pum_selected_item = cur; pum_display(compl_match_array, compl_match_arraysize, cur, array_changed, 0); @@ -1129,8 +1272,8 @@ void ins_compl_show_pum(void) #define DICT_FIRST (1) ///< use just first element in "dict" #define DICT_EXACT (2) ///< "dict" is the exact name of a file -/// Add any identifiers that match the given pattern in the list of dictionary -/// files "dict_start" to the list of completions. +/// Add any identifiers that match the given pattern "pat" in the list of +/// dictionary files "dict_start" to the list of completions. /// /// @param fla |