summaryrefslogtreecommitdiffstats
path: root/src/insexpand.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/insexpand.c')
-rw-r--r--src/insexpand.c3992
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)
+ dict = (char_u *)"spell";
+ else
+#endif
+ return;
+ }
+
+ buf = alloc(LSIZE);
+ if (buf == NULL)
+ return;
+ regmatch.regprog = NULL; // so that we can goto theend
+
+ // If 'infercase' is set, don't use 'smartcase' here
+ save_p_scs = p_scs;
+ if (curbuf->b_p_inf)
+ p_scs = FALSE;
+
+ // When invoked to match whole lines for CTRL-X CTRL-L adjust the pattern
+ // to only match at the start of a line. Otherwise just match the
+ // pattern. Also need to double backslashes.
+ if (ctrl_x_mode_line_or_eval())
+ {
+ char_u *pat_esc = vim_strsave_escaped(pat, (char_u *)"\\");
+ size_t len;
+
+ if (pat_esc == NULL)
+ goto theend;