diff options
author | Bram Moolenaar <Bram@vim.org> | 2019-03-30 13:53:47 +0100 |
---|---|---|
committer | Bram Moolenaar <Bram@vim.org> | 2019-03-30 13:53:47 +0100 |
commit | 7591bb39d58ece38a5fef984a08ea9012616c1f9 (patch) | |
tree | e5e5b82ffd29f4f922ebfb5a97ea12a8b3624b7c /src/insexpand.c | |
parent | de5b3800427328170574f1950ae75776e020f4e7 (diff) |
patch 8.1.1076: file for Insert mode is much too bigv8.1.1076
Problem: File for Insert mode is much too big.
Solution: Split off the code for Insert completion. (Yegappan Lakshmanan,
closes #4044)
Diffstat (limited to 'src/insexpand.c')
-rw-r--r-- | src/insexpand.c | 3992 |
1 files changed, 3992 insertions, 0 deletions
diff --git a/src/insexpand.c b/src/insexpand.c new file mode 100644 index 0000000000..c646674a64 --- /dev/null +++ b/src/insexpand.c @@ -0,0 +1,3992 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * insexpand.c: functions for Insert mode completion + */ + +#include "vim.h" + +#ifdef FEAT_INS_EXPAND +/* + * Definitions used for CTRL-X submode. + * Note: If you change CTRL-X submode, you must also maintain ctrl_x_msgs[] and + * ctrl_x_mode_names[] below. + */ +# define CTRL_X_WANT_IDENT 0x100 + +# define CTRL_X_NORMAL 0 /* CTRL-N CTRL-P completion, default */ +# define CTRL_X_NOT_DEFINED_YET 1 +# define CTRL_X_SCROLL 2 +# define CTRL_X_WHOLE_LINE 3 +# define CTRL_X_FILES 4 +# define CTRL_X_TAGS (5 + CTRL_X_WANT_IDENT) +# define CTRL_X_PATH_PATTERNS (6 + CTRL_X_WANT_IDENT) +# define CTRL_X_PATH_DEFINES (7 + CTRL_X_WANT_IDENT) +# define CTRL_X_FINISHED 8 +# define CTRL_X_DICTIONARY (9 + CTRL_X_WANT_IDENT) +# define CTRL_X_THESAURUS (10 + CTRL_X_WANT_IDENT) +# define CTRL_X_CMDLINE 11 +# define CTRL_X_FUNCTION 12 +# define CTRL_X_OMNI 13 +# define CTRL_X_SPELL 14 +# define CTRL_X_LOCAL_MSG 15 /* only used in "ctrl_x_msgs" */ +# define CTRL_X_EVAL 16 /* for builtin function complete() */ + +# define CTRL_X_MSG(i) ctrl_x_msgs[(i) & ~CTRL_X_WANT_IDENT] + +// Message for CTRL-X mode, index is ctrl_x_mode. +static char *ctrl_x_msgs[] = +{ + N_(" Keyword completion (^N^P)"), // CTRL_X_NORMAL, ^P/^N compl. + N_(" ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)"), + NULL, // CTRL_X_SCROLL: depends on state + N_(" Whole line completion (^L^N^P)"), + N_(" File name completion (^F^N^P)"), + N_(" Tag completion (^]^N^P)"), + N_(" Path pattern completion (^N^P)"), + N_(" Definition completion (^D^N^P)"), + NULL, // CTRL_X_FINISHED + N_(" Dictionary completion (^K^N^P)"), + N_(" Thesaurus completion (^T^N^P)"), + N_(" Command-line completion (^V^N^P)"), + N_(" User defined completion (^U^N^P)"), + N_(" Omni completion (^O^N^P)"), + N_(" Spelling suggestion (s^N^P)"), + N_(" Keyword Local completion (^N^P)"), + NULL, // CTRL_X_EVAL doesn't use msg. +}; + +static char *ctrl_x_mode_names[] = { + "keyword", + "ctrl_x", + "unknown", // CTRL_X_SCROLL + "whole_line", + "files", + "tags", + "path_patterns", + "path_defines", + "unknown", // CTRL_X_FINISHED + "dictionary", + "thesaurus", + "cmdline", + "function", + "omni", + "spell", + NULL, // CTRL_X_LOCAL_MSG only used in "ctrl_x_msgs" + "eval" +}; + +/* + * Array indexes used for cp_text[]. + */ +#define CPT_ABBR 0 // "abbr" +#define CPT_MENU 1 // "menu" +#define CPT_KIND 2 // "kind" +#define CPT_INFO 3 // "info" +#define CPT_USER_DATA 4 // "user data" +#define CPT_COUNT 5 // Number of entries + +/* + * Structure used to store one match for insert completion. + */ +typedef struct compl_S compl_T; +struct compl_S +{ + compl_T *cp_next; + compl_T *cp_prev; + char_u *cp_str; /* matched text */ + char cp_icase; /* TRUE or FALSE: ignore case */ + char_u *(cp_text[CPT_COUNT]); /* text for the menu */ + char_u *cp_fname; /* file containing the match, allocated when + * cp_flags has FREE_FNAME */ + int cp_flags; /* ORIGINAL_TEXT, CONT_S_IPOS or FREE_FNAME */ + int cp_number; /* sequence number */ +}; + +# define ORIGINAL_TEXT (1) /* the original text when the expansion begun */ +# define FREE_FNAME (2) + +static char e_hitend[] = N_("Hit end of paragraph"); +# ifdef FEAT_COMPL_FUNC +static char e_complwin[] = N_("E839: Completion function changed window"); +static char e_compldel[] = N_("E840: Completion function deleted text"); +# endif + +/* + * All the current matches are stored in a list. + * "compl_first_match" points to the start of the list. + * "compl_curr_match" points to the currently selected entry. + * "compl_shown_match" is different from compl_curr_match during + * ins_compl_get_exp(). + */ +static compl_T *compl_first_match = NULL; +static compl_T *compl_curr_match = NULL; +static compl_T *compl_shown_match = NULL; +static compl_T *compl_old_match = NULL; + +// After using a cursor key <Enter> selects a match in the popup menu, +// otherwise it inserts a line break. +static int 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 int compl_get_longest = FALSE; // put longest common string + // in compl_leader + +static int compl_no_insert = FALSE; // FALSE: select & insert + // TRUE: noinsert +static int compl_no_select = FALSE; // FALSE: select & insert + // TRUE: noselect + +// Selected one of the matches. When FALSE the match was edited or using the +// longest common string. +static int compl_used_match; + +// didn't finish finding completions. +static int compl_was_interrupted = FALSE; + +// Set when character typed while looking for matches and it means we should +// stop looking for matches. +static int compl_interrupted = FALSE; + +static int compl_restarting = FALSE; // don't insert match + +// When the first completion is done "compl_started" is set. When it's +// FALSE the word to be completed must be located. +static int compl_started = FALSE; + +// Which Ctrl-X mode are we in? +static int ctrl_x_mode = CTRL_X_NORMAL; + +static int compl_matches = 0; +static char_u *compl_pattern = NULL; +static int compl_direction = FORWARD; +static int compl_shows_dir = FORWARD; +static int compl_pending = 0; // > 1 for postponed CTRL-N +static pos_T compl_startpos; +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 + // completion started +static int compl_cont_mode = 0; +static expand_T compl_xp; + +static int compl_opt_refresh_always = FALSE; +static int compl_opt_suppress_empty = FALSE; + +static int ins_compl_add(char_u *str, int len, int icase, char_u *fname, char_u **cptext, int cdir, int flags, int adup); +static void ins_compl_longest_match(compl_T *match); +static void ins_compl_del_pum(void); +static void ins_compl_files(int count, char_u **files, int thesaurus, int flags, regmatch_T *regmatch, char_u *buf, int *dir); +static char_u *find_line_end(char_u *ptr); +static void ins_compl_free(void); +static char_u *ins_compl_mode(void); +static int ins_compl_need_restart(void); +static void ins_compl_new_leader(void); +static int ins_compl_len(void); +static void ins_compl_restart(void); +static void ins_compl_set_original_text(char_u *str); +static void ins_compl_fixRedoBufForLeader(char_u *ptr_arg); +# if defined(FEAT_COMPL_FUNC) || defined(FEAT_EVAL) +static void ins_compl_add_list(list_T *list); +static void ins_compl_add_dict(dict_T *dict); +# endif +static int ins_compl_key2dir(int c); +static int ins_compl_pum_key(int c); +static int ins_compl_key2count(int c); +static void show_pum(int prev_w_wrow, int prev_w_leftcol); +static unsigned quote_meta(char_u *dest, char_u *str, int len); +#endif // FEAT_INS_EXPAND + +#ifdef FEAT_SPELL +static void spell_back_to_badword(void); +static int spell_bad_len = 0; // length of located bad word +#endif + +#if defined(FEAT_INS_EXPAND) || defined(PROTO) +/* + * CTRL-X pressed in Insert mode. + */ + void +ins_ctrl_x(void) +{ + // CTRL-X after CTRL-X CTRL-V doesn't do anything, so that CTRL-X + // CTRL-V works like CTRL-N + if (ctrl_x_mode != CTRL_X_CMDLINE) + { + // if the next ^X<> won't ADD nothing, then reset + // compl_cont_status + if (compl_cont_status & CONT_N_ADDS) + compl_cont_status |= CONT_INTRPT; + else + compl_cont_status = 0; + // 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_pre = NULL; + showmode(); + } +} + +/* + * Functions to check the current CTRL-X mode. + */ +int ctrl_x_mode_none(void) { return ctrl_x_mode == 0; } +int ctrl_x_mode_normal(void) { return ctrl_x_mode == CTRL_X_NORMAL; } +int ctrl_x_mode_scroll(void) { return ctrl_x_mode == CTRL_X_SCROLL; } +int ctrl_x_mode_whole_line(void) { return ctrl_x_mode == CTRL_X_WHOLE_LINE; } +int ctrl_x_mode_files(void) { return ctrl_x_mode == CTRL_X_FILES; } +int ctrl_x_mode_tags(void) { return ctrl_x_mode == CTRL_X_TAGS; } +int ctrl_x_mode_path_patterns(void) { + return ctrl_x_mode == CTRL_X_PATH_PATTERNS; } +int ctrl_x_mode_path_defines(void) { + return ctrl_x_mode == CTRL_X_PATH_DEFINES; } +int ctrl_x_mode_dictionary(void) { return ctrl_x_mode == CTRL_X_DICTIONARY; } +int ctrl_x_mode_thesaurus(void) { return ctrl_x_mode == CTRL_X_THESAURUS; } +int ctrl_x_mode_cmdline(void) { return ctrl_x_mode == CTRL_X_CMDLINE; } +int ctrl_x_mode_function(void) { return ctrl_x_mode == CTRL_X_FUNCTION; } +int ctrl_x_mode_omni(void) { return ctrl_x_mode == CTRL_X_OMNI; } +int ctrl_x_mode_spell(void) { return ctrl_x_mode == CTRL_X_SPELL; } +int ctrl_x_mode_line_or_eval(void) { + return ctrl_x_mode == CTRL_X_WHOLE_LINE || ctrl_x_mode == CTRL_X_EVAL; } + +/* + * Whether other than default completion has been selected. + */ + int +ctrl_x_mode_not_default(void) +{ + return ctrl_x_mode != CTRL_X_NORMAL; +} + +/* + * Whether CTRL-X was typed without a following character. + */ + int +ctrl_x_mode_not_defined_yet(void) +{ + return ctrl_x_mode == CTRL_X_NOT_DEFINED_YET; +} + +/* + * Return TRUE if the 'dict' or 'tsr' option can be used. + */ + int +has_compl_option(int dict_opt) +{ + if (dict_opt ? (*curbuf->b_p_dict == NUL && *p_dict == NUL +# ifdef FEAT_SPELL + && !curwin->w_p_spell +# endif + ) + : (*curbuf->b_p_tsr == NUL && *p_tsr == NUL)) + { + ctrl_x_mode = CTRL_X_NORMAL; + edit_submode = NULL; + msg_attr(dict_opt ? _("'dictionary' option is empty") + : _("'thesaurus' option is empty"), + HL_ATTR(HLF_E)); + if (emsg_silent == 0) + { + vim_beep(BO_COMPL); + setcursor(); + out_flush(); +#ifdef FEAT_EVAL + if (!get_vim_var_nr(VV_TESTING)) +#endif + ui_delay(2000L, FALSE); + } + return FALSE; + } + return TRUE; +} + +/* + * Is the character 'c' a valid key to go to or keep us in CTRL-X mode? + * This depends on the current mode. + */ + int +vim_is_ctrl_x_key(int c) +{ + // Always allow ^R - let its results then be checked + if (c == Ctrl_R) + return TRUE; + + // Accept <PageUp> and <PageDown> if the popup menu is visible. + if (ins_compl_pum_key(c)) + return TRUE; + + switch (ctrl_x_mode) + { + case 0: // Not in any CTRL-X mode + return (c == Ctrl_N || c == Ctrl_P || c == Ctrl_X); + case CTRL_X_NOT_DEFINED_YET: + return ( c == Ctrl_X || c == Ctrl_Y || c == Ctrl_E + || c == Ctrl_L || c == Ctrl_F || c == Ctrl_RSB + || c == Ctrl_I || c == Ctrl_D || c == Ctrl_P + || c == Ctrl_N || c == Ctrl_T || c == Ctrl_V + || c == Ctrl_Q || c == Ctrl_U || c == Ctrl_O + || c == Ctrl_S || c == Ctrl_K || c == 's'); + case CTRL_X_SCROLL: + return (c == Ctrl_Y || c == Ctrl_E); + case CTRL_X_WHOLE_LINE: + return (c == Ctrl_L || c == Ctrl_P || c == Ctrl_N); + case CTRL_X_FILES: + return (c == Ctrl_F || c == Ctrl_P || c == Ctrl_N); + case CTRL_X_DICTIONARY: + return (c == Ctrl_K || c == Ctrl_P || c == Ctrl_N); + case CTRL_X_THESAURUS: + return (c == Ctrl_T || c == Ctrl_P || c == Ctrl_N); + case CTRL_X_TAGS: + return (c == Ctrl_RSB || c == Ctrl_P || c == Ctrl_N); +#ifdef FEAT_FIND_ID + case CTRL_X_PATH_PATTERNS: + return (c == Ctrl_P || c == Ctrl_N); + case CTRL_X_PATH_DEFINES: + return (c == Ctrl_D || c == Ctrl_P || c == Ctrl_N); +#endif + case CTRL_X_CMDLINE: + return (c == Ctrl_V || c == Ctrl_Q || c == Ctrl_P || c == Ctrl_N + || c == Ctrl_X); +#ifdef FEAT_COMPL_FUNC + case CTRL_X_FUNCTION: + return (c == Ctrl_U || c == Ctrl_P || c == Ctrl_N); + case CTRL_X_OMNI: + return (c == Ctrl_O || c == Ctrl_P || c == Ctrl_N); +#endif + case CTRL_X_SPELL: + return (c == Ctrl_S || c == Ctrl_P || c == Ctrl_N); + case CTRL_X_EVAL: + return (c == Ctrl_P || c == Ctrl_N); + } + internal_error("vim_is_ctrl_x_key()"); + return FALSE; +} + +/* + * Return TRUE when character "c" is part of the item currently being + * completed. Used to decide whether to abandon complete mode when the menu + * is visible. + */ + int +ins_compl_accept_char(int c) +{ + if (ctrl_x_mode & CTRL_X_WANT_IDENT) + // When expanding an identifier only accept identifier chars. + return vim_isIDc(c); + + switch (ctrl_x_mode) + { + case CTRL_X_FILES: + // When expanding file name only accept file name chars. But not + // path separators, so that "proto/<Tab>" expands files in + // "proto", not "proto/" as a whole + return vim_isfilec(c) && !vim_ispathsep(c); + + case CTRL_X_CMDLINE: + case CTRL_X_OMNI: + // Command line and Omni completion can work with just about any + // printable character, but do stop at white space. + return vim_isprintc(c) && !VIM_ISWHITE(c); + + case CTRL_X_WHOLE_LINE: + // For while line completion a space can be part of the line. + return vim_isprintc(c); + } + return vim_iswordc(c); +} + +/* + * 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 + * the rest of the word to be in -- webb + */ + int +ins_compl_add_infercase( + char_u *str, + int len, + int icase, + char_u *fname, + int dir, + int flags) +{ + char_u *p; + int i, c; + int actual_len; // Take multi-byte characters + int actual_compl_length; // into account. + int min_len; + int *wca; // Wide character array. + int has_lower = FALSE; + int was_letter = FALSE; + + if (p_ic && curbuf->b_p_inf && len > 0) + { + // Infer case of completed part. + + // Find actual length of completion. + if (has_mbyte) + { + p = str; + actual_len = 0; + while (*p != NUL) + { + MB_PTR_ADV(p); + ++actual_len; + } + } + else + actual_len = len; + + // Find actual length of original text. + if (has_mbyte) + { + p = compl_orig_text; + actual_compl_length = 0; + while (*p != NUL) + { + MB_PTR_ADV(p); + ++actual_compl_length; + } + } + else + actual_compl_length = compl_length; + + // "actual_len" may be smaller than "actual_compl_length" 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. + wca = (int *)alloc((unsigned)(actual_len * sizeof(int))); + if (wca != NULL) + { + p = str; + for (i = 0; i < actual_len; ++i) + if (has_mbyte) + wca[i] = mb_ptr2char_adv(&p); + else + wca[i] = *(p++); + + // Rule 1: Were any chars converted to lower? + p = compl_orig_text; + for (i = 0; i < min_len; ++i) + { + if (has_mbyte) + c = mb_ptr2char_adv(&p); + else + c = *(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) + { + p = compl_orig_text; + for (i = 0; i < min_len; ++i) + { + if (has_mbyte) + c = mb_ptr2char_adv(&p); + else + c = *(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. + p = compl_orig_text; + for (i = 0; i < min_len; ++i) + { + if (has_mbyte) + c = mb_ptr2char_adv(&p); + else + c = *(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. + // 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. + p = IObuff; + i = 0; + while (i < actual_len && (p - IObuff + 6) < IOSIZE) + if (has_mbyte) + p += (*mb_char2bytes)(wca[i++], p); + else + *(p++) = wca[i++]; + *p = NUL; + + vim_free(wca); + } + + return ins_compl_add(IObuff, len, icase, fname, NULL, dir, + flags, FALSE); + } + return ins_compl_add(str, len, icase, fname, NULL, dir, flags, FALSE); +} + +/* + * Add a match to the list of matches. + * If the given string is already in the list of completions, then return + * NOTDONE, otherwise add it to the list and return OK. If there is an error, + * maybe because alloc() returns NULL, then FAIL is returned. + */ + static int +ins_compl_add( + char_u *str, + int len, + int icase, + char_u *fname, + char_u **cptext, // extra text for popup menu or NULL + int cdir, + int flags, + int adup) // accept duplicate match +{ + compl_T *match; + int dir = (cdir == 0 ? compl_direction : cdir); + + ui_breakcheck(); + if (got_int) + return FAIL; + if (len < 0) + 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 & ORIGINAL_TEXT) + && STRNCMP(match->cp_str, str, len) == 0 + && match->cp_str[len] == NUL) + return NOTDONE; + match = match->cp_next; + } while (match != NULL && match != compl_first_match); + } + + // Remove any popup menu before changing the list of matches. + ins_compl_del_pum(); + + // Allocate a new match structure. + // Copy the values to the new match structure. + match = (compl_T *)alloc_clear((unsigned)sizeof(compl_T)); + if (match == NULL) + return FAIL; + match->cp_number = -1; + if (flags & ORIGINAL_TEXT) + match->cp_number = 0; + if ((match->cp_str = vim_strnsave(str, len)) == NULL) + { + vim_free(match); + return FAIL; + } + match->cp_icase = icase; + + // match-fname is: + // - compl_curr_match->cp_fname if it is a string equal to fname. + // - a copy of fname, FREE_FNAME is set to free later THE allocated mem. + // - NULL otherwise. --Acevedo + if (fname != NULL + && compl_curr_match != NULL + && compl_curr_match->cp_fname != NULL + && 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); + flags |= FREE_FNAME; + } + else + match->cp_fname = NULL; + match->cp_flags = flags; + + if (cptext != NULL) + { + int i; + + for (i = 0; i < CPT_COUNT; ++i) + if (cptext[i] != NULL && *cptext[i] != NUL) + match->cp_text[i] = vim_strsave(cptext[i]); + } + + // Link the new match structure in the list of matches. + if (compl_first_match == NULL) + match->cp_next = match->cp_prev = NULL; + else if (dir == FORWARD) + { + match->cp_next = compl_curr_match->cp_next; + match->cp_prev = compl_curr_match; + } + else // BACKWARD + { + match->cp_next = compl_curr_match; + match->cp_prev = compl_curr_match->cp_prev; + } + if (match->cp_next) + match->cp_next->cp_prev = match; + if (match->cp_prev) + match->cp_prev->cp_next = match; + else // if there's nothing before, it is the first match + compl_first_match = match; + compl_curr_match = match; + + // Find the longest common string if still doing that. + if (compl_get_longest && (flags & ORIGINAL_TEXT) == 0) + ins_compl_longest_match(match); + + return OK; +} + +/* + * Return TRUE if "str[len]" matches with match->cp_str, considering + * match->cp_icase. + */ + static int +ins_compl_equal(compl_T *match, char_u *str, int len) +{ + if (match->cp_icase) + return STRNICMP(match->cp_str, str, (size_t)len) == 0; + return STRNCMP(match->cp_str, str, (size_t)len) == 0; +} + +/* + * Reduce the longest common string for match "match". + */ + static void +ins_compl_longest_match(compl_T *match) +{ + char_u *p, *s; + int c1, c2; + int had_match; + + if (compl_leader == NULL) + { + // First match, use it as a whole. + compl_leader = vim_strsave(match->cp_str); + if (compl_leader != NULL) + { + had_match = (curwin->w_cursor.col > compl_col); + ins_compl_delete(); + ins_bytes(compl_leader + ins_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; + } + } + else + { + // Reduce the text if this match differs from compl_leader. + p = compl_leader; + s = match->cp_str; + while (*p != NUL) + { + if (has_mbyte) + { + c1 = mb_ptr2char(p); + c2 = mb_ptr2char(s); + } + else + { + c1 = *p; + c2 = *s; + } + if (match->cp_icase ? (MB_TOLOWER(c1) != MB_TOLOWER(c2)) + : (c1 != c2)) + break; + if (has_mbyte) + { + MB_PTR_ADV(p); + MB_PTR_ADV(s); + } + else + { + ++p; + ++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 + ins_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. + * Frees matches[]. + */ + static void +ins_compl_add_matches( + int num_matches, + char_u **matches, + int icase) +{ + int i; + int add_r = OK; + int dir = compl_direction; + + for (i = 0; i < num_matches && add_r != FAIL; i++) + if ((add_r = ins_compl_add(matches[i], -1, icase, + NULL, NULL, dir, 0, FALSE)) == OK) + // if dir was BACKWARD then honor it just once + dir = FORWARD; + FreeWild(num_matches, matches); +} + +/* + * Make the completion list cyclic. + * 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) + { + // 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; + } + return count; +} + +/* + * Return whether there currently is a shown match. + */ + int +ins_compl_has_shown_match(void) +{ + return compl_shown_match == NULL + || compl_shown_match != compl_shown_match->cp_next; +} + +/* + * Return whether the shown match is long enough. + */ + int +ins_compl_long_shown_match(void) +{ + return (int)STRLEN(compl_shown_match->cp_str) + > curwin->w_cursor.col - compl_col; +} + +/* + * Set variables that store noselect and noinsert behavior from the + * 'completeopt' value. + */ + void +completeopt_was_set(void) +{ + compl_no_insert = FALSE; + compl_no_select = FALSE; + if (strstr((char *)p_cot, "noselect") != NULL) + compl_no_select = TRUE; + if (strstr((char *)p_cot, "noinsert") != NULL) + compl_no_insert = TRUE; +} + +/* + * Start completion for the complete() function. + * "startcol" is where the matched text starts (1 is first column). + * "list" is the list of matches. + */ + void +set_completion(colnr_T startcol, list_T *list) +{ + int save_w_wrow = curwin->w_wrow; + int save_w_leftcol = curwin->w_leftcol; + + // If already doing completions stop it. + if (ctrl_x_mode != CTRL_X_NORMAL) + ins_compl_prep(' '); + ins_compl_clear(); + ins_compl_free(); + + compl_direction = FORWARD; + if (startcol > curwin->w_cursor.col) + startcol = curwin->w_cursor.col; + compl_col = startcol; + compl_length = (int)curwin->w_cursor.col - (int)startcol; + // compl_pattern doesn't need to be set + compl_orig_text = vim_strnsave(ml_get_curline() + compl_col, compl_length); + if (compl_orig_text == NULL || ins_compl_add(compl_orig_text, + -1, p_ic, NULL, NULL, 0, ORIGINAL_TEXT, FALSE) != OK) + return; + + ctrl_x_mode = CTRL_X_EVAL; + + ins_compl_add_list(list); + compl_matches = ins_compl_make_cyclic(); + compl_started = TRUE; + compl_used_match = TRUE; + compl_cont_status = 0; + + compl_curr_match = compl_first_match; + if (compl_no_insert || compl_no_select) + { + ins_complete(K_DOWN, FALSE); + if (compl_no_select) + // Down/Up has no real effect. + ins_complete(K_UP, FALSE); + } + else + ins_complete(Ctrl_N, FALSE); + compl_enter_selects = compl_no_insert; + + // Lazily show the popup menu, unless we got interrupted. + if (!compl_interrupted) + show_pum(save_w_wrow, save_w_leftcol); + out_flush(); +} + + +// "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; + +/* + * Update the screen and when there is any scrolling remove the popup menu. + */ + static void +ins_compl_upd_pum(void) +{ + int h; + + if (compl_match_array != NULL) + { + h = curwin->w_cline_height; + // Update the screen later, before drawing the popup menu over it. + pum_call_update_screen(); + if (h != curwin->w_cline_height) + ins_compl_del_pum(); + } +} + +/* + * Remove any popup menu. + */ + static void +ins_compl_del_pum(void) +{ + if (compl_match_array != NULL) + { + pum_undisplay(); + VIM_CLEAR(compl_match_array); + } +} + +/* + * Return TRUE if the popup menu should be displayed. + */ + int +pum_wanted(void) +{ + // 'completeopt' must contain "menu" or "menuone" + if (vim_strchr(p_cot, 'm') == NULL) + return FALSE; + + // The display looks bad on a B&W display. + if (t_colors < 8 +#ifdef FEAT_GUI + && !gui.in_use +#endif + ) + return FALSE; + return TRUE; +} + +/* + * Return TRUE if there are two or more matches to be shown in the popup menu. + * One if 'completopt' contains "menuone". + */ + static int +pum_enough_matches(void) +{ + compl_T *compl; + int i; + + // Don't display the popup menu if there are no matches or there is only + // one (ignoring the original text). + compl = compl_first_match; + i = 0; + do + { + if (compl == NULL + || ((compl->cp_flags & ORIGINAL_TEXT) == 0 && ++i == 2)) + break; + compl = compl->cp_next; + } while (compl != compl_first_match); + + if (strstr((char *)p_cot, "menuone") != NULL) + return (i >= 1); + return (i >= 2); +} + +/* + * 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) +{ + compl_T *compl; + compl_T *shown_compl = NULL; + int did_find_shown_match = FALSE; + int shown_match_ok = FALSE; + int i; + int cur = -1; + colnr_T col; + int lead_len = 0; + + if (!pum_wanted() || !pum_enough_matches()) + return; + +#if defined(FEAT_EVAL) + // Dirty hard-coded hack: remove any matchparen highlighting. + do_cmdline_cmd((char_u *)"if exists('g:loaded_matchparen')|3match none|endif"); +#endif + + // Update the screen later, before drawing the popup menu over it. + pum_call_update_screen(); + + if (compl_match_array == NULL) + { + // Need to build the popup menu list. + compl_match_arraysize = 0; + compl = compl_first_match; + if (compl_leader != NULL) + lead_len = (int)STRLEN(compl_leader); + do + { + if ((compl->cp_flags & ORIGINAL_TEXT) == 0 + && (compl_leader == NULL + || ins_compl_equal(compl, compl_leader, lead_len))) + ++compl_match_arraysize; + compl = compl->cp_next; + } while (compl != NULL && compl != compl_first_match); + if (compl_match_arraysize == 0) + return; + compl_match_array = (pumitem_T *)alloc_clear( + (unsigned)(sizeof(pumitem_T) + * compl_match_arraysize)); + if (compl_match_array != NULL) + { + // 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 & ORIGINAL_TEXT) + shown_match_ok = TRUE; + + i = 0; + compl = compl_first_match; + do + { + if ((compl->cp_flags & ORIGINAL_TEXT) == 0 + && (compl_leader == NULL + || ins_compl_equal(compl, compl_leader, 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; + } + + 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]; + else + compl_match_array[i++].pum_extra = compl->cp_fname; + } + + if (compl == compl_shown_match) + { + did_find_shown_match = TRUE; + + // When the original text is the shown match don't set + // compl_shown_match. + if (compl->cp_flags & ORIGINAL_TEXT) + shown_match_ok = 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; + } + } + compl = compl->cp_next; + } while (compl != NULL && compl != compl_first_match); + + if (!shown_match_ok) // no displayed match at all + cur = -1; + } + } + 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]) + { + cur = i; + break; + } + } + + if (compl_match_array != NULL) + { + // 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; + curwin->w_cursor.col = compl_col; + pum_display(compl_match_array, compl_match_arraysize, cur); + curwin->w_cursor.col = col; + } +} + +#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. + */ + static void +ins_compl_dictionaries( + char_u *dict_start, + char_u *pat, + int flags, // DICT_FIRST and/or DICT_EXACT + int thesaurus) // Thesaurus completion +{ + char_u *dict = dict_start; + char_u *ptr; + char_u *buf; + regmatch_T regmatch; + char_u **files; + int count; + int save_p_scs; + int dir = compl_direction; + + if (*dict == NUL) + { +#ifdef FEAT_SPELL + // When 'dictionary' is empty and spell checking is enabled use + // "spell". + if (!thesaurus && curwin->w_p_spell) + |