diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2025-02-05 23:09:29 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2025-02-05 23:09:29 +0000 |
commit | d5f194ce780c95821a855aca3c19426576d28ae0 (patch) | |
tree | d45f461b19f9118ad2bb1f440a7a08973ad18832 /src/nvim/insexpand.c | |
parent | c5d770d311841ea5230426cc4c868e8db27300a8 (diff) | |
parent | 44740e561fc93afe3ebecfd3618bda2d2abeafb0 (diff) | |
download | rneovim-rahm.tar.gz rneovim-rahm.tar.bz2 rneovim-rahm.zip |
Diffstat (limited to 'src/nvim/insexpand.c')
-rw-r--r-- | src/nvim/insexpand.c | 721 |
1 files changed, 430 insertions, 291 deletions
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index bd7ee55722..e21433f5ec 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -10,6 +10,7 @@ #include <string.h> #include "klib/kvec.h" +#include "nvim/api/private/helpers.h" #include "nvim/ascii_defs.h" #include "nvim/autocmd.h" #include "nvim/autocmd_defs.h" @@ -37,7 +38,6 @@ #include "nvim/getchar.h" #include "nvim/gettext_defs.h" #include "nvim/globals.h" -#include "nvim/highlight.h" #include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" #include "nvim/indent.h" @@ -156,7 +156,8 @@ typedef struct compl_S compl_T; struct compl_S { compl_T *cp_next; compl_T *cp_prev; - char *cp_str; ///< matched text + compl_T *cp_match_next; ///< matched next compl_T + String cp_str; ///< matched text char *(cp_text[CPT_COUNT]); ///< text for the menu typval_T cp_user_data; char *cp_fname; ///< file containing the match, allocated when @@ -164,6 +165,7 @@ struct compl_S { int cp_flags; ///< CP_ values int cp_number; ///< sequence number int cp_score; ///< fuzzy match score + bool cp_in_match_array; ///< collected by compl_match_array int cp_user_abbr_hlattr; ///< highlight attribute for abbr int cp_user_kind_hlattr; ///< highlight attribute for kind }; @@ -219,7 +221,7 @@ static bool compl_enter_selects = false; /// When "compl_leader" is not NULL only matches that start with this string /// are used. -static char *compl_leader = NULL; +static String compl_leader = STRING_INIT; static bool compl_get_longest = false; ///< put longest common string in compl_leader @@ -244,8 +246,7 @@ static bool compl_started = false; static int ctrl_x_mode = CTRL_X_NORMAL; static int compl_matches = 0; ///< number of completion matches -static char *compl_pattern = NULL; -static size_t compl_patternlen = 0; +static String compl_pattern = STRING_INIT; static Direction compl_direction = FORWARD; static Direction compl_shows_dir = FORWARD; static int compl_pending = 0; ///< > 1 for postponed CTRL-N @@ -255,8 +256,9 @@ static pos_T compl_startpos; static int compl_length = 0; static colnr_T compl_col = 0; ///< column where the text starts ///< that is being completed -static char *compl_orig_text = NULL; ///< text as it was before - ///< completion started +static colnr_T compl_ins_end_col = 0; +static String compl_orig_text = STRING_INIT; ///< text as it was before + ///< completion started /// Undo information to restore extmarks for original text. static extmark_undo_vec_t compl_orig_extmarks; static int compl_cont_mode = 0; @@ -281,6 +283,11 @@ static size_t spell_bad_len = 0; // length of located bad word static int compl_selected_item = -1; +// "compl_match_array" points the currently displayed list of entries in the +// popup menu. It is NULL when there is no popup menu. +static pumitem_T *compl_match_array = NULL; +static int compl_match_arraysize; + /// CTRL-X pressed in Insert mode. void ins_ctrl_x(void) { @@ -475,7 +482,7 @@ bool check_compl_option(bool dict_opt) msg((dict_opt ? _("'dictionary' option is empty") : _("'thesaurus' option is empty")), HLF_E); if (emsg_silent == 0 && !in_assert_fails) { - vim_beep(BO_COMPL); + vim_beep(kOptBoFlagComplete); setcursor(); ui_flush(); os_delay(2004, false); @@ -560,11 +567,19 @@ static bool is_first_match(const compl_T *const match) return match == compl_first_match; } -static void do_autocmd_completedone(int c) +static void do_autocmd_completedone(int c, int mode, char *word) { save_v_event_T save_v_event; dict_T *v_event = get_v_event(&save_v_event); + mode = mode & ~CTRL_X_WANT_IDENT; + char *mode_str = NULL; + if (ctrl_x_mode_names[mode]) { + mode_str = ctrl_x_mode_names[mode]; + } + tv_dict_add_str(v_event, S_LEN("complete_word"), word != NULL ? word : ""); + tv_dict_add_str(v_event, S_LEN("complete_type"), mode_str != NULL ? mode_str : ""); + tv_dict_add_str(v_event, S_LEN("reason"), (c == Ctrl_Y ? "accept" : "cancel")); tv_dict_set_keys_readonly(v_event); @@ -624,7 +639,7 @@ static char *ins_compl_infercase_gettext(const char *str, int char_len, int comp // Rule 1: Were any chars converted to lower? { - const char *p = compl_orig_text; + const char *p = compl_orig_text.data; for (int i = 0; i < min_len; i++) { const int c = mb_ptr2char_adv(&p); if (mb_islower(c)) { @@ -644,7 +659,7 @@ static char *ins_compl_infercase_gettext(const char *str, int char_len, int comp // upper case. if (!has_lower) { bool was_letter = false; - const char *p = compl_orig_text; + const char *p = compl_orig_text.data; 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])) { @@ -660,7 +675,7 @@ static char *ins_compl_infercase_gettext(const char *str, int char_len, int comp // Copy the original case of the part we typed. { - const char *p = compl_orig_text; + const char *p = compl_orig_text.data; for (int i = 0; i < min_len; i++) { const int c = mb_ptr2char_adv(&p); if (mb_islower(c)) { @@ -690,7 +705,7 @@ static char *ins_compl_infercase_gettext(const char *str, int char_len, int comp ga_grow(&gap, IOSIZE); *p = NUL; STRCPY(gap.ga_data, IObuff); - gap.ga_len = (int)strlen(IObuff); + gap.ga_len = (int)(p - IObuff); } else { p += utf_char2bytes(wca[i++], p); } @@ -737,7 +752,7 @@ int ins_compl_add_infercase(char *str_arg, int len, bool icase, char *fname, Dir // Find actual length of original text. { - const char *p = compl_orig_text; + const char *p = compl_orig_text.data; compl_char_len = 0; while (*p != NUL) { MB_PTR_ADV(p); @@ -747,7 +762,7 @@ int ins_compl_add_infercase(char *str_arg, int len, bool icase, char *fname, Dir // "char_len" may be smaller than "compl_char_len" when using // thesaurus, only use the minimum when comparing. - int min_len = char_len < compl_char_len ? char_len : compl_char_len; + int min_len = MIN(char_len, compl_char_len); str = ins_compl_infercase_gettext(str, char_len, compl_char_len, min_len, &tofree); } @@ -758,7 +773,7 @@ int ins_compl_add_infercase(char *str_arg, int len, bool icase, char *fname, Dir flags |= CP_ICASE; } - int res = ins_compl_add(str, len, fname, NULL, false, NULL, dir, flags, false, -1, -1); + int res = ins_compl_add(str, len, fname, NULL, false, NULL, dir, flags, false, NULL); xfree(tofree); return res; } @@ -789,6 +804,7 @@ static inline void free_cptext(char *const *const cptext) /// @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. +/// @param[in] user_hl list of extra highlight attributes for abbr kind. /// /// If "cdir" is FORWARD, then the match is added after the current match. /// Otherwise, it is added before the current match. @@ -798,7 +814,7 @@ static inline void free_cptext(char *const *const cptext) /// returned in case of error. 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, int user_abbr_hlattr, int user_kind_hlattr) + int flags_arg, const bool adup, const int *user_hl) FUNC_ATTR_NONNULL_ARG(1) { compl_T *match; @@ -825,8 +841,8 @@ static int ins_compl_add(char *const str, int len, char *const fname, char *cons match = compl_first_match; do { if (!match_at_original_text(match) - && strncmp(match->cp_str, str, (size_t)len) == 0 - && ((int)strlen(match->cp_str) <= len || match->cp_str[len] == NUL)) { + && strncmp(match->cp_str.data, str, (size_t)len) == 0 + && ((int)match->cp_str.size <= len || match->cp_str.data[len] == NUL)) { if (cptext_allocated) { free_cptext(cptext); } @@ -846,7 +862,7 @@ static int ins_compl_add(char *const str, int len, char *const fname, char *cons if (flags & CP_ORIGINAL_TEXT) { match->cp_number = 0; } - match->cp_str = xstrnsave(str, (size_t)len); + match->cp_str = cbuf_to_string(str, (size_t)len); // match-fname is: // - compl_curr_match->cp_fname if it is a string equal to fname. @@ -864,8 +880,8 @@ static int ins_compl_add(char *const str, int len, char *const fname, char *cons match->cp_fname = NULL; } match->cp_flags = flags; - match->cp_user_abbr_hlattr = user_abbr_hlattr; - match->cp_user_kind_hlattr = user_kind_hlattr; + match->cp_user_abbr_hlattr = user_hl ? user_hl[0] : -1; + match->cp_user_kind_hlattr = user_hl ? user_hl[1] : -1; if (cptext != NULL) { int i; @@ -928,27 +944,55 @@ static bool ins_compl_equal(compl_T *match, char *str, size_t len) return true; } if (match->cp_flags & CP_ICASE) { - return STRNICMP(match->cp_str, str, len) == 0; + return STRNICMP(match->cp_str.data, str, len) == 0; + } + return strncmp(match->cp_str.data, str, len) == 0; +} + +/// when len is -1 mean use whole length of p otherwise part of p +static void ins_compl_insert_bytes(char *p, int len) + FUNC_ATTR_NONNULL_ALL +{ + if (len == -1) { + len = (int)strlen(p); } - return strncmp(match->cp_str, str, len) == 0; + assert(len >= 0); + ins_bytes_len(p, (size_t)len); + compl_ins_end_col = curwin->w_cursor.col; +} + +/// Checks if the column is within the currently inserted completion text +/// column range. If it is, it returns a special highlight attribute. +/// -1 mean normal item. +int ins_compl_col_range_attr(int col) +{ + if (get_cot_flags() & kOptCotFlagFuzzy) { + return -1; + } + + if (col >= (compl_col + (int)ins_compl_leader_len()) && col < compl_ins_end_col) { + return syn_name2attr("ComplMatchIns"); + } + + return -1; } /// Reduce the longest common string for match "match". static void ins_compl_longest_match(compl_T *match) { - if (compl_leader == NULL) { + if (compl_leader.data == NULL) { // First match, use it as a whole. - compl_leader = xstrdup(match->cp_str); + compl_leader = copy_string(match->cp_str, NULL); bool had_match = (curwin->w_cursor.col > compl_col); - ins_compl_delete(); - ins_bytes(compl_leader + get_compl_len()); + ins_compl_delete(false); + ins_compl_insert_bytes(compl_leader.data + get_compl_len(), -1); ins_redraw(false); // When the match isn't there (to avoid matching itself) remove it // again after redrawing. if (!had_match) { - ins_compl_delete(); + ins_compl_delete(false); } compl_used_match = false; @@ -956,8 +1000,8 @@ static void ins_compl_longest_match(compl_T *match) } // Reduce the text if this match differs from compl_leader. - char *p = compl_leader; - char *s = match->cp_str; + char *p = compl_leader.data; + char *s = match->cp_str.data; while (*p != NUL) { int c1 = utf_ptr2char(p); int c2 = utf_ptr2char(s); @@ -974,15 +1018,17 @@ static void ins_compl_longest_match(compl_T *match) if (*p != NUL) { // Leader was shortened, need to change the inserted text. *p = NUL; + compl_leader.size = (size_t)(p - compl_leader.data); + bool had_match = (curwin->w_cursor.col > compl_col); - ins_compl_delete(); - ins_bytes(compl_leader + get_compl_len()); + ins_compl_delete(false); + ins_compl_insert_bytes(compl_leader.data + get_compl_len(), -1); ins_redraw(false); // When the match isn't there (to avoid matching itself) remove it // again after redrawing. if (!had_match) { - ins_compl_delete(); + ins_compl_delete(false); } } @@ -997,9 +1043,9 @@ 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(matches[i], -1, NULL, NULL, false, NULL, dir, - CP_FAST | (icase ? CP_ICASE : 0), - false, -1, -1)) == OK) { + add_r = ins_compl_add(matches[i], -1, NULL, NULL, false, NULL, dir, + CP_FAST | (icase ? CP_ICASE : 0), false, NULL); + if (add_r == OK) { // If dir was BACKWARD then honor it just once. dir = FORWARD; } @@ -1038,8 +1084,8 @@ bool ins_compl_has_shown_match(void) /// Return whether the shown match is long enough. 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; + return compl_shown_match != NULL && compl_shown_match->cp_str.data != NULL + && (colnr_T)compl_shown_match->cp_str.size > curwin->w_cursor.col - compl_col; } /// Get the local or global value of 'completeopt' flags. @@ -1048,11 +1094,6 @@ unsigned get_cot_flags(void) return curbuf->b_cot_flags != 0 ? curbuf->b_cot_flags : cot_flags; } -/// "compl_match_array" points the currently displayed list of entries in the -/// popup menu. It is NULL when there is no popup menu. -static pumitem_T *compl_match_array = NULL; -static int compl_match_arraysize; - /// Remove any popup menu. static void ins_compl_del_pum(void) { @@ -1069,7 +1110,7 @@ bool pum_wanted(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { // "completeopt" must contain "menu" or "menuone" - return (get_cot_flags() & COT_ANY_MENU) != 0; + return (get_cot_flags() & (kOptCotFlagMenu | kOptCotFlagMenuone)) != 0; } /// Check that there are two or more matches to be shown in the popup menu. @@ -1088,7 +1129,7 @@ static bool pum_enough_matches(void) comp = comp->cp_next; } while (!is_first_match(comp)); - if (get_cot_flags() & COT_MENUONE) { + if (get_cot_flags() & kOptCotFlagMenuone) { return i >= 1; } return i >= 2; @@ -1099,7 +1140,7 @@ static dict_T *ins_compl_dict_alloc(compl_T *match) { // { word, abbr, menu, kind, info } dict_T *dict = tv_dict_alloc_lock(VAR_FIXED); - tv_dict_add_str(dict, S_LEN("word"), match->cp_str); + tv_dict_add_str(dict, S_LEN("word"), match->cp_str.data); tv_dict_add_str(dict, S_LEN("abbr"), match->cp_text[CPT_ABBR]); tv_dict_add_str(dict, S_LEN("menu"), match->cp_text[CPT_MENU]); tv_dict_add_str(dict, S_LEN("kind"), match->cp_text[CPT_KIND]); @@ -1168,54 +1209,47 @@ static int ins_compl_build_pum(void) XFREE_CLEAR(compl_leader); } - const int lead_len = compl_leader != NULL ? (int)strlen(compl_leader) : 0; int max_fuzzy_score = 0; unsigned cur_cot_flags = get_cot_flags(); - bool compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0; - bool compl_fuzzy_match = (cur_cot_flags & COT_FUZZY) != 0; - - do { - // When 'completeopt' contains "fuzzy" and leader is not NULL or empty, - // set the cp_score for later comparisons. - if (compl_fuzzy_match && compl_leader != NULL && lead_len > 0) { - comp->cp_score = fuzzy_match_str(comp->cp_str, compl_leader); - } - - if (!match_at_original_text(comp) - && (compl_leader == NULL - || ins_compl_equal(comp, compl_leader, (size_t)lead_len) - || (compl_fuzzy_match && comp->cp_score > 0))) { - compl_match_arraysize++; - } - comp = comp->cp_next; - } while (comp != NULL && !is_first_match(comp)); - - if (compl_match_arraysize == 0) { - return -1; - } - - assert(compl_match_arraysize >= 0); - compl_match_array = xcalloc((size_t)compl_match_arraysize, sizeof(pumitem_T)); + bool compl_no_select = (cur_cot_flags & kOptCotFlagNoselect) != 0; + bool fuzzy_filter = (cur_cot_flags & kOptCotFlagFuzzy) != 0; + bool fuzzy_sort = fuzzy_filter && !(cur_cot_flags & kOptCotFlagNosort); + compl_T *match_head = NULL, *match_tail = NULL; // 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); - if (strequal(compl_leader, compl_orig_text) && !shown_match_ok) { + if (strequal(compl_leader.data, compl_orig_text.data) && !shown_match_ok) { compl_shown_match = compl_no_select ? compl_first_match : compl_first_match->cp_next; } - compl_T *shown_compl = NULL; bool did_find_shown_match = false; - int cur = -1; + compl_T *shown_compl = NULL; int i = 0; - comp = compl_first_match; + int cur = -1; + do { + comp->cp_in_match_array = false; + // When 'completeopt' contains "fuzzy" and leader is not NULL or empty, + // set the cp_score for later comparisons. + if (fuzzy_filter && compl_leader.data != NULL && compl_leader.size > 0) { + comp->cp_score = fuzzy_match_str(comp->cp_str.data, compl_leader.data); + } + if (!match_at_original_text(comp) - && (compl_leader == NULL - || ins_compl_equal(comp, compl_leader, (size_t)lead_len) - || (compl_fuzzy_match && comp->cp_score > 0))) { - if (!shown_match_ok && !compl_fuzzy_match) { + && (compl_leader.data == NULL + || ins_compl_equal(comp, compl_leader.data, compl_leader.size) + || (fuzzy_filter && comp->cp_score > 0))) { + compl_match_arraysize++; + comp->cp_in_match_array = true; + if (match_head == NULL) { + match_head = comp; + } else { + match_tail->cp_match_next = comp; + } + match_tail = comp; + if (!shown_match_ok && !fuzzy_filter) { if (comp == compl_shown_match || did_find_shown_match) { // This item is the shown match or this is the // first displayed item after the shown match. @@ -1228,64 +1262,36 @@ static int ins_compl_build_pum(void) shown_compl = comp; } cur = i; - } else if (compl_fuzzy_match) { + } else if (fuzzy_filter) { if (i == 0) { shown_compl = comp; } // Update the maximum fuzzy score and the shown match // if the current item's score is higher - if (comp->cp_score > max_fuzzy_score) { + if (fuzzy_sort && comp->cp_score > max_fuzzy_score) { did_find_shown_match = true; max_fuzzy_score = comp->cp_score; if (!compl_no_select) { compl_shown_match = comp; } + } else if (!fuzzy_sort && i == 0 && !compl_no_select) { + compl_shown_match = shown_compl; } - if (!shown_match_ok && comp == compl_shown_match && !compl_no_select) { cur = i; shown_match_ok = true; } - - // If there is no "no select" condition and the max fuzzy - // score is positive, or there is no completion leader or the - // leader length is zero, mark the shown match as valid and - // reset the current index. - if (!compl_no_select - && (max_fuzzy_score > 0 - || (compl_leader == NULL || lead_len == 0))) { - if (match_at_original_text(compl_shown_match)) { - compl_shown_match = shown_compl; - } - } - } - - if (comp->cp_text[CPT_ABBR] != NULL) { - compl_match_array[i].pum_text = comp->cp_text[CPT_ABBR]; - } else { - compl_match_array[i].pum_text = comp->cp_str; - } - compl_match_array[i].pum_kind = comp->cp_text[CPT_KIND]; - compl_match_array[i].pum_info = comp->cp_text[CPT_INFO]; - compl_match_array[i].pum_score = comp->cp_score; - compl_match_array[i].pum_user_abbr_hlattr = comp->cp_user_abbr_hlattr; - compl_match_array[i].pum_user_kind_hlattr = comp->cp_user_kind_hlattr; - if (comp->cp_text[CPT_MENU] != NULL) { - compl_match_array[i++].pum_extra = comp->cp_text[CPT_MENU]; - } else { - compl_match_array[i++].pum_extra = comp->cp_fname; } + i++; } - if (comp == compl_shown_match && !compl_fuzzy_match) { + if (comp == compl_shown_match && !fuzzy_filter) { did_find_shown_match = true; - // When the original text is the shown match don't set // compl_shown_match. if (match_at_original_text(comp)) { shown_match_ok = true; } - if (!shown_match_ok && shown_compl != NULL) { // The shown match isn't displayed, set it to the // previously displayed match. @@ -1296,7 +1302,31 @@ static int ins_compl_build_pum(void) comp = comp->cp_next; } while (comp != NULL && !is_first_match(comp)); - if (compl_fuzzy_match && compl_leader != NULL && lead_len > 0) { + if (compl_match_arraysize == 0) { + return -1; + } + + assert(compl_match_arraysize >= 0); + compl_match_array = xcalloc((size_t)compl_match_arraysize, sizeof(pumitem_T)); + + i = 0; + comp = match_head; + while (comp != NULL) { + compl_match_array[i].pum_text = comp->cp_text[CPT_ABBR] != NULL + ? comp->cp_text[CPT_ABBR] : comp->cp_str.data; + compl_match_array[i].pum_kind = comp->cp_text[CPT_KIND]; + compl_match_array[i].pum_info = comp->cp_text[CPT_INFO]; + compl_match_array[i].pum_score = comp->cp_score; + compl_match_array[i].pum_user_abbr_hlattr = comp->cp_user_abbr_hlattr; + compl_match_array[i].pum_user_kind_hlattr = comp->cp_user_kind_hlattr; + compl_match_array[i++].pum_extra = comp->cp_text[CPT_MENU] != NULL + ? comp->cp_text[CPT_MENU] : comp->cp_fname; + compl_T *match_next = comp->cp_match_next; + comp->cp_match_next = NULL; + comp = match_next; + } + + if (fuzzy_sort && compl_leader.data != NULL && compl_leader.size > 0) { for (i = 0; i < compl_match_arraysize; i++) { compl_match_array[i].pum_idx = i; } @@ -1334,7 +1364,7 @@ void ins_compl_show_pum(void) } else { // popup menu already exists, only need to find the current item. for (int i = 0; i < compl_match_arraysize; i++) { - if (compl_match_array[i].pum_text == compl_shown_match->cp_str + if (compl_match_array[i].pum_text == compl_shown_match->cp_str.data || compl_match_array[i].pum_text == compl_shown_match->cp_text[CPT_ABBR]) { cur = i; break; @@ -1403,7 +1433,13 @@ bool compl_match_curr_select(int selected) /// Get current completion leader char *ins_compl_leader(void) { - return compl_leader != NULL ? compl_leader : compl_orig_text; + return compl_leader.data != NULL ? compl_leader.data : compl_orig_text.data; +} + +/// Get current completion leader length +size_t ins_compl_leader_len(void) +{ + return compl_leader.data != NULL ? compl_leader.size : compl_orig_text.size; } /// Add any identifiers that match the given pattern "pat" in the list of @@ -1562,6 +1598,7 @@ static void ins_compl_files(int count, char **files, bool thesaurus, int flags, FILE *fp = os_fopen(files[i], "r"); // open dictionary file if (flags != DICT_EXACT && !shortmess(SHM_COMPLETIONSCAN)) { msg_hist_off = true; // reset in msg_trunc() + msg_ext_set_kind("completion"); vim_snprintf(IObuff, IOSIZE, _("Scanning dictionary: %s"), files[i]); msg_trunc(IObuff, true, HLF_R); @@ -1577,11 +1614,7 @@ static void ins_compl_files(int count, char **files, bool thesaurus, int flags, char *ptr = buf; while (vim_regexec(regmatch, buf, (colnr_T)(ptr - buf))) { ptr = regmatch->startp[0]; - if (ctrl_x_mode_line_or_eval()) { - ptr = find_line_end(ptr); - } else { - ptr = find_word_end(ptr); - } + ptr = ctrl_x_mode_line_or_eval() ? find_line_end(ptr) : find_word_end(ptr); int add_r = ins_compl_add_infercase(regmatch->startp[0], (int)(ptr - regmatch->startp[0]), p_ic, files[i], *dir, false); @@ -1652,9 +1685,8 @@ static char *find_line_end(char *ptr) /// Free the list of completions static void ins_compl_free(void) { - XFREE_CLEAR(compl_pattern); - compl_patternlen = 0; - XFREE_CLEAR(compl_leader); + API_CLEAR_STRING(compl_pattern); + API_CLEAR_STRING(compl_leader); if (compl_first_match == NULL) { return; @@ -1667,7 +1699,7 @@ static void ins_compl_free(void) do { compl_T *match = compl_curr_match; compl_curr_match = compl_curr_match->cp_next; - xfree(match->cp_str); + API_CLEAR_STRING(match->cp_str); // several entries may use the same fname, free it just once. if (match->cp_flags & CP_FREE_FNAME) { xfree(match->cp_fname); @@ -1687,12 +1719,12 @@ void ins_compl_clear(void) compl_cont_status = 0; compl_started = false; compl_matches = 0; - XFREE_CLEAR(compl_pattern); - compl_patternlen = 0; - XFREE_CLEAR(compl_leader); + compl_ins_end_col = 0; + API_CLEAR_STRING(compl_pattern); + API_CLEAR_STRING(compl_leader); edit_submode_extra = NULL; kv_destroy(compl_orig_extmarks); - XFREE_CLEAR(compl_orig_text); + API_CLEAR_STRING(compl_orig_text); compl_enter_selects = false; // clear v:completed_item set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED)); @@ -1705,6 +1737,12 @@ bool ins_compl_active(void) return compl_started; } +/// Return true when wp is the actual completion window +bool ins_compl_win_active(win_T *wp) +{ + return ins_compl_active() && !(wp->w_p_pvw || wp->w_float_is_info); +} + /// Selected one of the matches. When false the match was edited or using the /// longest common string. bool ins_compl_used_match(void) @@ -1743,12 +1781,33 @@ int ins_compl_len(void) return compl_length; } +/// Return true when preinsert is set otherwise FALSE. +static bool ins_compl_has_preinsert(void) +{ + return (get_cot_flags() & (kOptCotFlagFuzzy|kOptCotFlagPreinsert)) == kOptCotFlagPreinsert; +} + +/// Returns true if the pre-insert effect is valid and the cursor is within +/// the `compl_ins_end_col` range. +bool ins_compl_preinsert_effect(void) +{ + if (!ins_compl_has_preinsert()) { + return false; + } + + return curwin->w_cursor.col < compl_ins_end_col; +} + /// Delete one character before the cursor and show the subset of the matches /// that match the word that is now before the cursor. /// Returns the character to be used, NUL if the work is done and another char /// to be got from the user. int ins_compl_bs(void) { + if (ins_compl_preinsert_effect()) { + ins_compl_delete(false); + } + char *line = get_cursor_line_ptr(); char *p = line + curwin->w_cursor.col; MB_PTR_BACK(line, p); @@ -1776,8 +1835,9 @@ int ins_compl_bs(void) // TODO(bfredl): get rid of random update_screen() calls deep inside completion logic line = get_cursor_line_ptr(); - xfree(compl_leader); - compl_leader = xstrnsave(line + compl_col, (size_t)(p_off - (ptrdiff_t)compl_col)); + API_CLEAR_STRING(compl_leader); + compl_leader = cbuf_to_string(line + compl_col, + (size_t)(p_off - (ptrdiff_t)compl_col)); ins_compl_new_leader(); if (compl_shown_match != NULL) { @@ -1810,12 +1870,12 @@ static bool ins_compl_need_restart(void) static void ins_compl_new_leader(void) { ins_compl_del_pum(); - ins_compl_delete(); - ins_bytes(compl_leader + get_compl_len()); + ins_compl_delete(true); + ins_compl_insert_bytes(compl_leader.data + get_compl_len(), -1); compl_used_match = false; if (compl_started) { - ins_compl_set_original_text(compl_leader); + ins_compl_set_original_text(compl_leader.data, compl_leader.size); } else { spell_bad_len = 0; // need to redetect bad word // Matches were cleared, need to search for them now. @@ -1833,8 +1893,13 @@ static void ins_compl_new_leader(void) ins_compl_show_pum(); // Don't let Enter select the original text when there is no popup menu. + if (compl_match_array == NULL) { + compl_enter_selects = false; + } else if (ins_compl_has_preinsert() && compl_leader.size > 0) { + ins_compl_insert(false, true); + } // Don't let Enter select when use user function and refresh_always is set - if (compl_match_array == NULL || ins_compl_refresh_always()) { + if (ins_compl_refresh_always()) { compl_enter_selects = false; } } @@ -1857,6 +1922,10 @@ void ins_compl_addleader(int c) { int cc; + if (ins_compl_preinsert_effect()) { + ins_compl_delete(false); + } + if (stop_arrow() == FAIL) { return; } @@ -1875,9 +1944,9 @@ void ins_compl_addleader(int c) ins_compl_restart(); } - xfree(compl_leader); - compl_leader = xstrnsave(get_cursor_line_ptr() + compl_col, - (size_t)(curwin->w_cursor.col - compl_col)); + API_CLEAR_STRING(compl_leader); + compl_leader = cbuf_to_string(get_cursor_line_ptr() + compl_col, + (size_t)(curwin->w_cursor.col - compl_col)); ins_compl_new_leader(); } @@ -1897,19 +1966,19 @@ static void ins_compl_restart(void) } /// Set the first match, the original text. -static void ins_compl_set_original_text(char *str) +static void ins_compl_set_original_text(char *str, size_t len) FUNC_ATTR_NONNULL_ALL { // Replace the original text entry. // The CP_ORIGINAL_TEXT flag is either at the first item or might possibly // be at the last item for backward completion if (match_at_original_text(compl_first_match)) { // safety check - xfree(compl_first_match->cp_str); - compl_first_match->cp_str = xstrdup(str); + API_CLEAR_STRING(compl_first_match->cp_str); + compl_first_match->cp_str = cbuf_to_string(str, len); } else if (compl_first_match->cp_prev != NULL && match_at_original_text(compl_first_match->cp_prev)) { - xfree(compl_first_match->cp_prev->cp_str); - compl_first_match->cp_prev->cp_str = xstrdup(str); + API_CLEAR_STRING(compl_first_match->cp_prev->cp_str); + compl_first_match->cp_prev->cp_str = cbuf_to_string(str, len); } } @@ -1919,8 +1988,8 @@ void ins_compl_addfrommatch(void) { int len = (int)curwin->w_cursor.col - (int)compl_col; assert(compl_shown_match != NULL); - char *p = compl_shown_match->cp_str; - if ((int)strlen(p) <= len) { // the match is too short + char *p = compl_shown_match->cp_str.data; + if ((int)compl_shown_match->cp_str.size <= len) { // the match is too short // When still at the original match use the first entry that matches // the leader. if (!match_at_original_text(compl_shown_match)) { @@ -1928,15 +1997,17 @@ void ins_compl_addfrommatch(void) } p = NULL; + size_t plen = 0; for (compl_T *cp = compl_shown_match->cp_next; cp != NULL && !is_first_match(cp); cp = cp->cp_next) { - if (compl_leader == NULL - || ins_compl_equal(cp, compl_leader, strlen(compl_leader))) { - p = cp->cp_str; + if (compl_leader.data == NULL + || ins_compl_equal(cp, compl_leader.data, compl_leader.size)) { + p = cp->cp_str.data; + plen = cp->cp_str.size; break; } } - if (p == NULL || (int)strlen(p) <= len) { + if (p == NULL || (int)plen <= len) { return; } } @@ -2076,7 +2147,7 @@ static bool ins_compl_stop(const int c, const int prev_mode, bool retval) // Get here when we have finished typing a sequence of ^N and // ^P or other completion characters in CTRL-X mode. Free up // memory that was used, and make sure we can redo the insert. - if (compl_curr_match != NULL || compl_leader != NULL || c == Ctrl_E) { + if (compl_curr_match != NULL || compl_leader.data != NULL || c == Ctrl_E) { // If any of the original typed text has been changed, eg when // ignorecase is set, we must add back-spaces to the redo // buffer. We add as few as necessary to delete just the part @@ -2085,7 +2156,7 @@ static bool ins_compl_stop(const int c, const int prev_mode, bool retval) // CTRL-E then don't use the current match. char *ptr; if (compl_curr_match != NULL && compl_used_match && c != Ctrl_E) { - ptr = compl_curr_match->cp_str; + ptr = compl_curr_match->cp_str.data; } else { ptr = NULL; } @@ -2121,36 +2192,46 @@ static bool ins_compl_stop(const int c, const int prev_mode, bool retval) } } + char *word = NULL; // If the popup menu is displayed pressing CTRL-Y means accepting // the selection without inserting anything. When // compl_enter_selects is set the Enter key does the same. if ((c == Ctrl_Y || (compl_enter_selects && (c == CAR || c == K_KENTER || c == NL))) && pum_visible()) { + word = xstrdup(compl_shown_match->cp_str.data); retval = true; + // May need to remove ComplMatchIns highlight. + redrawWinline(curwin, curwin->w_cursor.lnum); } // CTRL-E means completion is Ended, go back to the typed text. // but only do this, if the Popup is still visible if (c == Ctrl_E) { - ins_compl_delete(); + ins_compl_delete(false); char *p = NULL; - if (compl_leader != NULL) { - p = compl_leader; + size_t plen = 0; + if (compl_leader.data != NULL) { + p = compl_leader.data; + plen = compl_leader.size; } else if (compl_first_match != NULL) { - p = compl_orig_text; + p = compl_orig_text.data; + plen = compl_orig_text.size; } if (p != NULL) { const int compl_len = get_compl_len(); - const int len = (int)strlen(p); - if (len > compl_len) { - ins_bytes_len(p + compl_len, (size_t)(len - compl_len)); + if ((int)plen > compl_len) { + ins_compl_insert_bytes(p + compl_len, (int)plen - compl_len); } } restore_orig_extmarks(); retval = true; } + if ((c == Ctrl_W || c == Ctrl_U) && ins_compl_preinsert_effect()) { + ins_compl_delete(false); + } + auto_format(false, true); // Trigger the CompleteDonePre event to give scripts a chance to @@ -2184,7 +2265,8 @@ static bool ins_compl_stop(const int c, const int prev_mode, bool retval) } // Trigger the CompleteDone event to give scripts a chance to act // upon the end of completion. - do_autocmd_completedone(c); + do_autocmd_completedone(c, prev_mode, word); + xfree(word); return retval; } @@ -2236,7 +2318,7 @@ bool ins_compl_prep(int c) // Set "compl_get_longest" when finding the first matches. if (ctrl_x_mode_not_defined_yet() || (ctrl_x_mode_normal() && !compl_started)) { - compl_get_longest = (get_cot_flags() & COT_LONGEST) != 0; + compl_get_longest = (get_cot_flags() & kOptCotFlagLongest) != 0; compl_used_match = true; } @@ -2273,7 +2355,7 @@ bool ins_compl_prep(int c) } else if (ctrl_x_mode == CTRL_X_LOCAL_MSG) { // Trigger the CompleteDone event to give scripts a chance to act // upon the (possibly failed) completion. - do_autocmd_completedone(c); + do_autocmd_completedone(c, ctrl_x_mode, NULL); } may_trigger_modechanged(); @@ -2293,18 +2375,18 @@ bool ins_compl_prep(int c) /// "ptr" is the known leader text or NUL. static void ins_compl_fixRedoBufForLeader(char *ptr_arg) { - int len; + int len = 0; char *ptr = ptr_arg; if (ptr == NULL) { - if (compl_leader != NULL) { - ptr = compl_leader; + if (compl_leader.data != NULL) { + ptr = compl_leader.data; } else { return; // nothing to do } } - if (compl_orig_text != NULL) { - char *p = compl_orig_text; + if (compl_orig_text.data != NULL) { + char *p = compl_orig_text.data; for (len = 0; p[len] != NUL && p[len] == ptr[len]; len++) {} if (len > 0) { len -= utf_head_off(p, p + len); @@ -2312,8 +2394,6 @@ static void ins_compl_fixRedoBufForLeader(char *ptr_arg) for (p += len; *p != NUL; MB_PTR_ADV(p)) { AppendCharToRedobuff(K_BS); } - } else { - len = 0; } AppendToRedobuffLit(ptr + len, -1); } @@ -2573,9 +2653,8 @@ static int ins_compl_add_tv(typval_T *const tv, const Direction dir, bool fast) int flags = fast ? CP_FAST : 0; char *(cptext[CPT_COUNT]); char *user_abbr_hlname = NULL; - int user_abbr_hlattr = -1; char *user_kind_hlname = NULL; - int user_kind_hlattr = -1; + int user_hl[2] = { -1, -1 }; typval_T user_data; user_data.v_type = VAR_UNKNOWN; @@ -2587,10 +2666,10 @@ static int ins_compl_add_tv(typval_T *const tv, const Direction dir, bool fast) cptext[CPT_INFO] = tv_dict_get_string(tv->vval.v_dict, "info", true); user_abbr_hlname = tv_dict_get_string(tv->vval.v_dict, "abbr_hlgroup", false); - user_abbr_hlattr = get_user_highlight_attr(user_abbr_hlname); + user_hl[0] = get_user_highlight_attr(user_abbr_hlname); user_kind_hlname = tv_dict_get_string(tv->vval.v_dict, "kind_hlgroup", false); - user_kind_hlattr = get_user_highlight_attr(user_kind_hlname); + user_hl[1] = get_user_highlight_attr(user_kind_hlname); tv_dict_get_tv(tv->vval.v_dict, "user_data", &user_data); @@ -2613,8 +2692,7 @@ static int ins_compl_add_tv(typval_T *const tv, const Direction dir, bool fast) return FAIL; } int status = ins_compl_add((char *)word, -1, NULL, cptext, true, - &user_data, dir, flags, dup, - user_abbr_hlattr, user_kind_hlattr); + &user_data, dir, flags, dup, user_hl); if (status != OK) { tv_clear(&user_data); } @@ -2682,9 +2760,9 @@ static void set_completion(colnr_T startcol, list_T *list) { int flags = CP_ORIGINAL_TEXT; unsigned cur_cot_flags = get_cot_flags(); - bool compl_longest = (cur_cot_flags & COT_LONGEST) != 0; - bool compl_no_insert = (cur_cot_flags & COT_NOINSERT) != 0; - bool compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0; + bool compl_longest = (cur_cot_flags & kOptCotFlagLongest) != 0; + bool compl_no_insert = (cur_cot_flags & kOptCotFlagNoinsert) != 0; + bool compl_no_select = (cur_cot_flags & kOptCotFlagNoselect) != 0; // If already doing completions stop it. if (ctrl_x_mode_not_default()) { @@ -2701,13 +2779,15 @@ static void set_completion(colnr_T startcol, list_T *list) compl_col = startcol; compl_length = curwin->w_cursor.col - startcol; // compl_pattern doesn't need to be set - compl_orig_text = xstrnsave(get_cursor_line_ptr() + compl_col, (size_t)compl_length); + compl_orig_text = cbuf_to_string(get_cursor_line_ptr() + compl_col, + (size_t)compl_length); save_orig_extmarks(); if (p_ic) { flags |= CP_ICASE; } - if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, NULL, 0, - flags | CP_FAST, false, -1, -1) != OK) { + if (ins_compl_add(compl_orig_text.data, (int)compl_orig_text.size, + NULL, NULL, false, NULL, 0, + flags | CP_FAST, false, NULL) != OK) { return; } @@ -2841,6 +2921,25 @@ static void ins_compl_update_sequence_numbers(void) } } +/// Fill the dict of complete_info +static void fill_complete_info_dict(dict_T *di, compl_T *match, bool add_match) +{ + tv_dict_add_str(di, S_LEN("word"), match->cp_str.data); + tv_dict_add_str(di, S_LEN("abbr"), match->cp_text[CPT_ABBR]); + tv_dict_add_str(di, S_LEN("menu"), match->cp_text[CPT_MENU]); + tv_dict_add_str(di, S_LEN("kind"), match->cp_text[CPT_KIND]); + tv_dict_add_str(di, S_LEN("info"), match->cp_text[CPT_INFO]); + if (add_match) { + tv_dict_add_bool(di, S_LEN("match"), match->cp_in_match_array); + } + if (match->cp_user_data.v_type == VAR_UNKNOWN) { + // Add an empty string for backwards compatibility + tv_dict_add_str(di, S_LEN("user_data"), ""); + } else { + tv_dict_add_tv(di, S_LEN("user_data"), &match->cp_user_data); + } +} + /// Get complete information static void get_complete_info(list_T *what_list, dict_T *retdict) { @@ -2848,12 +2947,13 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) #define CI_WHAT_PUM_VISIBLE 0x02 #define CI_WHAT_ITEMS 0x04 #define CI_WHAT_SELECTED 0x08 -#define CI_WHAT_INSERTED 0x10 +#define CI_WHAT_COMPLETED 0x10 +#define CI_WHAT_MATCHES 0x20 #define CI_WHAT_ALL 0xff int what_flag; if (what_list == NULL) { - what_flag = CI_WHAT_ALL; + what_flag = CI_WHAT_ALL & ~(CI_WHAT_MATCHES|CI_WHAT_COMPLETED); } else { what_flag = 0; for (listitem_T *item = tv_list_first(what_list) @@ -2869,8 +2969,10 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) what_flag |= CI_WHAT_ITEMS; } else if (strcmp(what, "selected") == 0) { what_flag |= CI_WHAT_SELECTED; - } else if (strcmp(what, "inserted") == 0) { - what_flag |= CI_WHAT_INSERTED; + } else if (strcmp(what, "completed") == 0) { + what_flag |= CI_WHAT_COMPLETED; + } else if (strcmp(what, "matches") == 0) { + what_flag |= CI_WHAT_MATCHES; } } } @@ -2884,12 +2986,17 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) ret = tv_dict_add_nr(retdict, S_LEN("pum_visible"), pum_visible()); } - if (ret == OK && (what_flag & CI_WHAT_ITEMS || what_flag & CI_WHAT_SELECTED)) { + if (ret == OK && (what_flag & (CI_WHAT_ITEMS|CI_WHAT_SELECTED + |CI_WHAT_MATCHES|CI_WHAT_COMPLETED))) { list_T *li = NULL; int selected_idx = -1; - if (what_flag & CI_WHAT_ITEMS) { + bool has_items = what_flag & CI_WHAT_ITEMS; + bool has_matches = what_flag & CI_WHAT_MATCHES; + bool has_completed = what_flag & CI_WHAT_COMPLETED; + if (has_items || has_matches) { li = tv_list_alloc(kListLenMayKnow); - ret = tv_dict_add_list(retdict, S_LEN("items"), li); + const char *key = (has_matches && !has_items) ? "matches" : "items"; + ret = tv_dict_add_list(retdict, key, strlen(key), li); } if (ret == OK && what_flag & CI_WHAT_SELECTED) { if (compl_curr_match != NULL && compl_curr_match->cp_number == -1) { @@ -2901,20 +3008,10 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) compl_T *match = compl_first_match; do { if (!match_at_original_text(match)) { - if (what_flag & CI_WHAT_ITEMS) { + if (has_items || (has_matches && match->cp_in_match_array)) { dict_T *di = tv_dict_alloc(); tv_list_append_dict(li, di); - tv_dict_add_str(di, S_LEN("word"), match->cp_str); - tv_dict_add_str(di, S_LEN("abbr"), match->cp_text[CPT_ABBR]); - tv_dict_add_str(di, S_LEN("menu"), match->cp_text[CPT_MENU]); - tv_dict_add_str(di, S_LEN("kind"), match->cp_text[CPT_KIND]); - tv_dict_add_str(di, S_LEN("info"), match->cp_text[CPT_INFO]); - if (match->cp_user_data.v_type == VAR_UNKNOWN) { - // Add an empty string for backwards compatibility - tv_dict_add_str(di, S_LEN("user_data"), ""); - } else { - tv_dict_add_tv(di, S_LEN("user_data"), &match->cp_user_data); - } + fill_complete_info_dict(di, match, has_matches && has_items); } if (compl_curr_match != NULL && compl_curr_match->cp_number == match->cp_number) { @@ -2933,11 +3030,14 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) tv_dict_add_nr(retdict, S_LEN("preview_bufnr"), wp->w_buffer->handle); } } + if (ret == OK && selected_idx != -1 && has_completed) { + dict_T *di = tv_dict_alloc(); + fill_complete_info_dict(di, compl_curr_match, false); + ret = tv_dict_add_dict(retdict, S_LEN("completed"), di); + } } (void)ret; - // TODO(vim): - // if (ret == OK && (what_flag & CI_WHAT_INSERTED)) } /// "complete_info()" function @@ -3040,6 +3140,7 @@ static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_ar } if (!shortmess(SHM_COMPLETIONSCAN)) { msg_hist_off = true; // reset in msg_trunc() + msg_ext_set_kind("completion"); vim_snprintf(IObuff, IOSIZE, _("Scanning: %s"), st->ins_buf->b_fname == NULL ? buf_spname(st->ins_buf) @@ -3072,6 +3173,7 @@ static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_ar } else if (*st->e_cpt == ']' || *st->e_cpt == 't') { compl_type = CTRL_X_TAGS; if (!shortmess(SHM_COMPLETIONSCAN)) { + msg_ext_set_kind("completion"); msg_hist_off = true; // reset in msg_trunc() vim_snprintf(IObuff, IOSIZE, "%s", _("Scanning tags.")); msg_trunc(IObuff, true, HLF_R); @@ -3096,8 +3198,8 @@ done: /// included files. static void get_next_include_file_completion(int compl_type) { - find_pattern_in_path(compl_pattern, compl_direction, - compl_patternlen, false, false, + find_pattern_in_path(compl_pattern.data, compl_direction, + compl_pattern.size, false, false, ((compl_type == CTRL_X_PATH_DEFINES && !(compl_cont_status & CONT_SOL)) ? FIND_DEFINE : FIND_ANY), @@ -3109,14 +3211,14 @@ static void get_next_include_file_completion(int compl_type) static void get_next_dict_tsr_completion(int compl_type, char *dict, int dict_f) { if (thesaurus_func_complete(compl_type)) { - expand_by_function(compl_type, compl_pattern); + expand_by_function(compl_type, compl_pattern.data); } else { 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, + compl_pattern.data, dict != NULL ? dict_f : 0, compl_type == CTRL_X_THESAURUS); } @@ -3127,14 +3229,14 @@ static void get_next_tag_completion(void) { // set p_ic according to p_ic, p_scs and pat for find_tags(). const int save_p_ic = p_ic; - p_ic = ignorecase(compl_pattern); + p_ic = ignorecase(compl_pattern.data); // 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; char **matches; int num_matches; - if (find_tags(compl_pattern, &num_matches, &matches, + if (find_tags(compl_pattern.data, &num_matches, &matches, TAG_REGEXP | TAG_NAMES | TAG_NOIC | TAG_INS_COMP | (ctrl_x_mode_not_default() ? TAG_VERBOSE : 0), TAG_MANY, curbuf->b_ffname) == OK && num_matches > 0) { @@ -3149,13 +3251,13 @@ static void get_next_filename_completion(void) { char **matches; int num_matches; - if (expand_wildcards(1, &compl_pattern, &num_matches, &matches, + if (expand_wildcards(1, &compl_pattern.data, &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); + tilde_replace(compl_pattern.data, num_matches, matches); #ifdef BACKSLASH_IN_FILENAME if (curbuf->b_p_csl[0] != NUL) { for (int i = 0; i < num_matches; i++) { @@ -3179,8 +3281,8 @@ static void get_next_cmdline_completion(void) { char **matches; int num_matches; - if (expand_cmdline(&compl_xp, compl_pattern, - (int)compl_patternlen, &num_matches, &matches) == EXPAND_OK) { + if (expand_cmdline(&compl_xp, compl_pattern.data, + (int)compl_pattern.size, &num_matches, &matches) == EXPAND_OK) { ins_compl_add_matches(num_matches, matches, false); } } @@ -3189,7 +3291,7 @@ static void get_next_cmdline_completion(void) static void get_next_spell_completion(linenr_T lnum) { char **matches; - int num_matches = expand_spelling(lnum, compl_pattern, &matches); + int num_matches = expand_spelling(lnum, compl_pattern.data, &matches); if (num_matches > 0) { ins_compl_add_matches(num_matches, matches, p_ic); } else { @@ -3208,22 +3310,24 @@ static char *ins_compl_get_next_word_or_line(buf_T *ins_buf, pos_T *cur_match_po { *match_len = 0; char *ptr = ml_get_buf(ins_buf, cur_match_pos->lnum) + cur_match_pos->col; - int len; + int len = ml_get_buf_len(ins_buf, cur_match_pos->lnum) - cur_match_pos->col; if (ctrl_x_mode_line_or_eval()) { if (compl_status_adding()) { if (cur_match_pos->lnum >= ins_buf->b_ml.ml_line_count) { return NULL; } ptr = ml_get_buf(ins_buf, cur_match_pos->lnum + 1); + len = ml_get_buf_len(ins_buf, cur_match_pos->lnum + 1); if (!p_paste) { - ptr = skipwhite(ptr); + char *tmp_ptr = ptr; + ptr = skipwhite(tmp_ptr); + len -= (int)(ptr - tmp_ptr); } } - len = (int)strlen(ptr); } else { char *tmp_ptr = ptr; - if (compl_status_adding() && compl_length <= (int)strlen(tmp_ptr)) { + if (compl_status_adding() && compl_length <= len) { tmp_ptr += compl_length; // Skip if already inside a word. if (vim_iswordp(tmp_ptr)) { @@ -3319,10 +3423,11 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_ // 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(st->ins_buf, st->cur_match_pos, - compl_direction, compl_pattern); + compl_direction, compl_pattern.data); } else { found_new_match = searchit(NULL, st->ins_buf, st->cur_match_pos, - NULL, compl_direction, compl_pattern, compl_patternlen, + NULL, compl_direction, compl_pattern.data, + compl_pattern.size, 1, SEARCH_KEEP + SEARCH_NFMSG, RE_LAST, NULL); } msg_silent--; @@ -3368,7 +3473,8 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_ int len; char *ptr = ins_compl_get_next_word_or_line(st->ins_buf, st->cur_match_pos, &len, &cont_s_ipos); - if (ptr == NULL) { + if (ptr == NULL + || (ins_compl_has_preinsert() && strcmp(ptr, compl_pattern.data) == 0)) { continue; } if (ins_compl_add_infercase(ptr, len, p_ic, @@ -3419,7 +3525,7 @@ static bool get_next_completion_match(int type, ins_compl_next_state_T *st, pos_ case CTRL_X_FUNCTION: case CTRL_X_OMNI: - expand_by_function(type, compl_pattern); + expand_by_function(type, compl_pattern.data); break; case CTRL_X_SPELL: @@ -3450,9 +3556,9 @@ static void get_next_bufname_token(void) FOR_ALL_BUFFERS(b) { if (b->b_p_bl && b->b_sfname != NULL) { char *tail = path_tail(b->b_sfname); - if (strncmp(tail, compl_orig_text, strlen(compl_orig_text)) == 0) { + if (strncmp(tail, compl_orig_text.data, compl_orig_text.size) == 0) { ins_compl_add(tail, (int)strlen(tail), NULL, NULL, false, NULL, 0, - p_ic ? CP_ICASE : 0, false, -1, -1); + p_ic ? CP_ICASE : 0, false, NULL); } } } @@ -3518,7 +3624,7 @@ static int ins_compl_get_exp(pos_T *ini) // If complete() was called then compl_pattern has been reset. // The following won't work then, bail out. - if (compl_pattern == NULL) { + if (compl_pattern.data == NULL) { break; } @@ -3586,7 +3692,7 @@ static int ins_compl_get_exp(pos_T *ini) static void ins_compl_update_shown_match(void) { while (!ins_compl_equal(compl_shown_match, - compl_leader, strlen(compl_leader)) + compl_leader.data, compl_leader.size) && compl_shown_match->cp_next != NULL && !is_first_match(compl_shown_match->cp_next)) { compl_shown_match = compl_shown_match->cp_next; @@ -3595,10 +3701,10 @@ static void ins_compl_update_shown_match(void) // If we didn't find it searching forward, and compl_shows_dir is // backward, find the last match. if (compl_shows_dir_backward() - && !ins_compl_equal(compl_shown_match, compl_leader, strlen(compl_leader)) + && !ins_compl_equal(compl_shown_match, compl_leader.data, compl_leader.size) && (compl_shown_match->cp_next == NULL || is_first_match(compl_shown_match->cp_next))) { - while (!ins_compl_equal(compl_shown_match, compl_leader, strlen(compl_leader)) + while (!ins_compl_equal(compl_shown_match, compl_leader.data, compl_leader.size) && compl_shown_match->cp_prev != NULL && !is_first_match(compl_shown_match->cp_prev)) { compl_shown_match = compl_shown_match->cp_prev; @@ -3607,16 +3713,36 @@ static void ins_compl_update_shown_match(void) } /// Delete the old text being completed. -void ins_compl_delete(void) +void ins_compl_delete(bool new_leader) { + // Avoid deleting text that will be reinserted when changing leader. This + // allows marks present on the original text to shrink/grow appropriately. + int orig_col = 0; + if (new_leader) { + char *orig = compl_orig_text.data; + char *leader = ins_compl_leader(); + while (*orig != NUL && utf_ptr2char(orig) == utf_ptr2char(leader)) { + leader += utf_ptr2len(leader); + orig += utf_ptr2len(orig); + } + orig_col = (int)(orig - compl_orig_text.data); + } + // In insert mode: Delete the typed part. // In replace mode: Put the old characters back, if any. - int col = compl_col + (compl_status_adding() ? compl_length : 0); + int col = compl_col + (compl_status_adding() ? compl_length : orig_col); + bool has_preinsert = ins_compl_preinsert_effect(); + if (has_preinsert) { + col += (int)ins_compl_leader_len(); + curwin->w_cursor.col = compl_ins_end_col; + } + if ((int)curwin->w_cursor.col > col) { if (stop_arrow() == FAIL) { return; } backspace_until_column(col); + compl_ins_end_col = curwin->w_cursor.col; } // TODO(vim): is this sufficient for redrawing? Redrawing everything @@ -3628,15 +3754,25 @@ void ins_compl_delete(void) /// Insert the new text being completed. /// "in_compl_func" is true when called from complete_check(). -void ins_compl_insert(bool in_compl_func) +/// "move_cursor" is used when 'completeopt' includes "preinsert" and when true +/// cursor needs to move back from the inserted text to the compl_leader. +void ins_compl_insert(bool in_compl_func, bool move_cursor) { int compl_len = get_compl_len(); + bool preinsert = ins_compl_has_preinsert(); + char *cp_str = compl_shown_match->cp_str.data; + size_t cp_str_len = compl_shown_match->cp_str.size; + size_t leader_len = ins_compl_leader_len(); + // Make sure we don't go over the end of the string, this can happen with // illegal bytes. - if (compl_len < (int)strlen(compl_shown_match->cp_str)) { - ins_bytes(compl_shown_match->cp_str + compl_len); + if (compl_len < (int)cp_str_len) { + ins_compl_insert_bytes(cp_str + compl_len, -1); + if (preinsert && move_cursor) { + curwin->w_cursor.col -= (colnr_T)(cp_str_len - leader_len); + } } - compl_used_match = !match_at_original_text(compl_shown_match); + compl_used_match = !(match_at_original_text(compl_shown_match) || preinsert); dict_T *dict = ins_compl_dict_alloc(compl_shown_match); set_vim_var_dict(VV_COMPLETED_ITEM, dict); @@ -3704,7 +3840,7 @@ static compl_T *find_comp_when_fuzzy(void) comp = compl_first_match; do { if (comp->cp_score == score - && (str == comp->cp_str || str == comp->cp_text[CPT_ABBR])) { + && (str == comp->cp_str.data || str == comp->cp_text[CPT_ABBR])) { return comp; } comp = comp->cp_next; @@ -3731,8 +3867,8 @@ static int find_next_completion_match(bool allow_get_expansion, int todo, bool a bool found_end = false; compl_T *found_compl = NULL; unsigned cur_cot_flags = get_cot_flags(); - bool compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0; - bool compl_fuzzy_match = (cur_cot_flags & COT_FUZZY) != 0; + bool compl_no_select = (cur_cot_flags & kOptCotFlagNoselect) != 0; + bool compl_fuzzy_match = (cur_cot_flags & kOptCotFlagFuzzy) != 0; while (--todo >= 0) { if (compl_shows_dir_forward() && compl_shown_match->cp_next != NULL) { @@ -3787,9 +3923,9 @@ static int find_next_completion_match(bool allow_get_expansion, int todo, bool a found_end = false; } if (!match_at_original_text(compl_shown_match) - && compl_leader != NULL + && compl_leader.data != NULL && !ins_compl_equal(compl_shown_match, - compl_leader, strlen(compl_leader)) + compl_leader.data, compl_leader.size) && !(compl_fuzzy_match && compl_shown_match->cp_score > 0)) { todo++; } else { @@ -3836,8 +3972,9 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match const bool started = compl_started; buf_T *const orig_curbuf = curbuf; unsigned cur_cot_flags = get_cot_flags(); - bool compl_no_insert = (cur_cot_flags & COT_NOINSERT) != 0; - bool compl_fuzzy_match = (cur_cot_flags & COT_FUZZY) != 0; + bool compl_no_insert = (cur_cot_flags & kOptCotFlagNoinsert) != 0; + bool compl_fuzzy_match = (cur_cot_flags & kOptCotFlagFuzzy) != 0; + bool compl_preinsert = ins_compl_has_preinsert(); // When user complete function return -1 for findstart which is next // time of 'always', compl_shown_match become NULL. @@ -3845,7 +3982,7 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match return -1; } - if (compl_leader != NULL + if (compl_leader.data != NULL && !match_at_original_text(compl_shown_match) && !compl_fuzzy_match) { // Update "compl_shown_match" to the actually shown match @@ -3855,7 +3992,7 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match if (allow_get_expansion && insert_match && (!compl_get_longest || compl_used_match)) { // Delete old text to be replaced - ins_compl_delete(); + ins_compl_delete(false); } // When finding the longest common text we stick at the original text, @@ -3882,17 +4019,18 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match } // Insert the text of the new completion, or the compl_leader. - if (compl_no_insert && !started) { - ins_bytes(compl_orig_text + get_compl_len()); + if (compl_no_insert && !started && !compl_preinsert) { + ins_compl_insert_bytes(compl_orig_text.data + get_compl_len(), -1); compl_used_match = false; restore_orig_extmarks(); } else if (insert_match) { if (!compl_get_longest || compl_used_match) { - ins_compl_insert(in_compl_func); + ins_compl_insert(in_compl_func, true); } else { - ins_bytes(compl_leader + get_compl_len()); + assert(compl_leader.data != NULL); + ins_compl_insert_bytes(compl_leader.data + get_compl_len(), -1); } - if (!strcmp(compl_curr_match->cp_str, compl_orig_text)) { + if (strequal(compl_curr_match->cp_str.data, compl_orig_text.data)) { restore_orig_extmarks(); } } else { @@ -3908,7 +4046,7 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match // Delete old text to be replaced, since we're still searching and // don't want to match ourselves! - ins_compl_delete(); + ins_compl_delete(false); } // Enter will select a match when the match wasn't inserted and the popup @@ -3976,7 +4114,7 @@ void ins_compl_check_keys(int frequency, bool in_compl_func) } } } - if (compl_pending != 0 && !got_int && !(cot_flags & COT_NOINSERT)) { + if (compl_pending != 0 && !got_int && !(cot_flags & kOptCotFlagNoinsert)) { int todo = compl_pending > 0 ? compl_pending : -compl_pending; compl_pending = 0; @@ -4056,8 +4194,7 @@ static bool ins_compl_use_match(int c) /// Get the pattern, column and length for normal completion (CTRL-N CTRL-P /// completion) -/// Sets the global variables: compl_col, compl_length, compl_pattern and -/// compl_patternlen. +/// Sets the global variables: compl_col, compl_length and compl_pattern. /// Uses the global variables: compl_cont_status and ctrl_x_mode static int get_normal_compl_info(char *line, int startcol, colnr_T curs_col) { @@ -4068,29 +4205,32 @@ static int get_normal_compl_info(char *line, int startcol, colnr_T curs_col) compl_length = curs_col - startcol; } if (p_ic) { - compl_pattern = str_foldcase(line + compl_col, compl_length, NULL, 0); + compl_pattern = cstr_as_string(str_foldcase(line + compl_col, + compl_length, NULL, 0)); } else { - compl_pattern = xstrnsave(line + compl_col, (size_t)compl_length); + compl_pattern = cbuf_to_string(line + compl_col, (size_t)compl_length); } } else if (compl_status_adding()) { char *prefix = "\\<"; size_t prefixlen = STRLEN_LITERAL("\\<"); - // we need up to 2 extra chars for the prefix - compl_pattern = xmalloc(quote_meta(NULL, line + compl_col, - compl_length) + prefixlen); if (!vim_iswordp(line + compl_col) || (compl_col > 0 && (vim_iswordp(mb_prevptr(line, line + compl_col))))) { prefix = ""; prefixlen = 0; } - STRCPY(compl_pattern, prefix); - quote_meta(compl_pattern + prefixlen, line + compl_col, compl_length); + + // we need up to 2 extra chars for the prefix + size_t n = quote_meta(NULL, line + compl_col, compl_length) + prefixlen; + compl_pattern.data = xmalloc(n); + STRCPY(compl_pattern.data, prefix); + quote_meta(compl_pattern.data + prefixlen, line + compl_col, compl_length); + compl_pattern.size = n - 1; } else if (--startcol < 0 || !vim_iswordp(mb_prevptr(line, line + startcol + 1))) { // Match any word of at least two chars - compl_pattern = xstrnsave(S_LEN("\\<\\k\\k")); + compl_pattern = cbuf_to_string(S_LEN("\\<\\k\\k")); compl_col += curs_col; compl_length = 0; } else { @@ -4111,19 +4251,20 @@ static int get_normal_compl_info(char *line, int startcol, colnr_T curs_col) // Only match word with at least two chars -- webb // there's no need to call quote_meta, // xmalloc(7) is enough -- Acevedo - compl_pattern = xmalloc(7); - STRCPY(compl_pattern, "\\<"); - quote_meta(compl_pattern + 2, line + compl_col, 1); - strcat(compl_pattern, "\\k"); + compl_pattern.data = xmalloc(7); + STRCPY(compl_pattern.data, "\\<"); + quote_meta(compl_pattern.data + 2, line + compl_col, 1); + strcat(compl_pattern.data, "\\k"); + compl_pattern.size = strlen(compl_pattern.data); } else { - compl_pattern = xmalloc(quote_meta(NULL, line + compl_col, compl_length) + 2); - STRCPY(compl_pattern, "\\<"); - quote_meta(compl_pattern + 2, line + compl_col, compl_length); + size_t n = quote_meta(NULL, line + compl_col, compl_length) + 2; + compl_pattern.data = xmalloc(n); + STRCPY(compl_pattern.data, "\\<"); + quote_meta(compl_pattern.data + 2, line + compl_col, compl_length); + compl_pattern.size = n - 1; } } - compl_patternlen = strlen(compl_pattern); - return OK; } @@ -4138,13 +4279,12 @@ static int get_wholeline_compl_info(char *line, colnr_T curs_col) compl_length = 0; } if (p_ic) { - compl_pattern = str_foldcase(line + compl_col, compl_length, NULL, 0); + compl_pattern = cstr_as_string(str_foldcase(line + compl_col, + compl_length, NULL, 0)); } else { - compl_pattern = xstrnsave(line + compl_col, (size_t)compl_length); + compl_pattern = cbuf_to_string(line + compl_col, (size_t)compl_length); } - compl_patternlen = strlen(compl_pattern); - return OK; } @@ -4169,8 +4309,8 @@ static int get_filename_compl_info(char *line, int startcol, colnr_T curs_col) compl_col += startcol; compl_length = (int)curs_col - startcol; - compl_pattern = addstar(line + compl_col, (size_t)compl_length, EXPAND_FILES); - compl_patternlen = strlen(compl_pattern); + compl_pattern = cstr_as_string(addstar(line + compl_col, + (size_t)compl_length, EXPAND_FILES)); return OK; } @@ -4179,9 +4319,9 @@ static int get_filename_compl_info(char *line, int startcol, colnr_T curs_col) /// Sets the global variables: compl_col, compl_length and compl_pattern. static int get_cmdline_compl_info(char *line, colnr_T curs_col) { - compl_pattern = xstrnsave(line, (size_t)curs_col); - compl_patternlen = (size_t)curs_col; - set_cmd_context(&compl_xp, compl_pattern, (int)compl_patternlen, curs_col, false); + compl_pattern = cbuf_to_string(line, (size_t)curs_col); + set_cmd_context(&compl_xp, compl_pattern.data, + (int)compl_pattern.size, curs_col, false); if (compl_xp.xp_context == EXPAND_LUA) { nlua_expand_pat(&compl_xp); } @@ -4191,7 +4331,7 @@ static int get_cmdline_compl_info(char *line, colnr_T curs_col) // "pattern not found" message. compl_col = curs_col; } else { - compl_col = (int)(compl_xp.xp_pattern - compl_pattern); + compl_col = (int)(compl_xp.xp_pattern - compl_pattern.data); } compl_length = curs_col - compl_col; @@ -4269,8 +4409,7 @@ static int get_userdefined_compl_info(colnr_T curs_col) // it may have become invalid. char *line = ml_get(curwin->w_cursor.lnum); compl_length = curs_col - compl_col; - compl_pattern = xstrnsave(line + compl_col, (size_t)compl_length); - compl_patternlen = (size_t)compl_length; + compl_pattern = cbuf_to_string(line + compl_col, (size_t)compl_length); return OK; } @@ -4295,8 +4434,7 @@ static int get_spell_compl_info(int startcol, colnr_T curs_col) } // Need to obtain "line" again, it may have become invalid. char *line = ml_get(curwin->w_cursor.lnum); - compl_pattern = xstrnsave(line + compl_col, (size_t)compl_length); - compl_patternlen = (size_t)compl_length; + compl_pattern = cbuf_to_string(line + compl_col, (size_t)compl_length); return OK; } @@ -4481,19 +4619,19 @@ static int ins_compl_start(void) ins_compl_fixRedoBufForLeader(NULL); // Always add completion for the original text. - xfree(compl_orig_text); + API_CLEAR_STRING(compl_orig_text); kv_destroy(compl_orig_extmarks); - compl_orig_text = xstrnsave(line + compl_col, (size_t)compl_length); + compl_orig_text = cbuf_to_string(line + compl_col, (size_t)compl_length); save_orig_extmarks(); int flags = CP_ORIGINAL_TEXT; if (p_ic) { flags |= CP_ICASE; } - if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, NULL, 0, - flags, false, -1, -1) != OK) { - XFREE_CLEAR(compl_pattern); - compl_patternlen = 0; - XFREE_CLEAR(compl_orig_text); + if (ins_compl_add(compl_orig_text.data, (int)compl_orig_text.size, + NULL, NULL, false, NULL, 0, + flags, false, NULL) != OK) { + API_CLEAR_STRING(compl_pattern); + API_CLEAR_STRING(compl_orig_text); kv_destroy(compl_orig_extmarks); return FAIL; } @@ -4567,6 +4705,7 @@ static void ins_compl_show_statusmsg(void) if (edit_submode_extra != NULL) { if (!p_smd) { msg_hist_off = true; + msg_ext_set_kind("completion"); msg(edit_submode_extra, (edit_submode_highl < HLF_COUNT ? (int)edit_submode_highl + 1 : 0)); msg_hist_off = false; @@ -4726,7 +4865,7 @@ static unsigned quote_meta(char *dest, char *src, int len) #if defined(EXITFREE) void free_insexpand_stuff(void) { - XFREE_CLEAR(compl_orig_text); + API_CLEAR_STRING(compl_orig_text); kv_destroy(compl_orig_extmarks); callback_free(&cfu_cb); callback_free(&ofu_cb); |