quote_meta(char_u *dest, char_u *src, int len)
{
    unsigned	m = (unsigned)len + 1;  // one extra for the NUL

    for ( ; --len >= 0; src++)
    {
	switch (*src)
	{
	    case '.':
	    case '*':
	    case '[':
		if (ctrl_x_mode_dictionary() || ctrl_x_mode_thesaurus())
		    break;
		// FALLTHROUGH
	    case '~':
		if (!magic_isset())	// quote these only if magic is set
		    break;
		// FALLTHROUGH
	    case '\\':
		if (ctrl_x_mode_dictionary() || ctrl_x_mode_thesaurus())
		    break;
		// FALLTHROUGH
	    case '^':		// currently it's not needed.
	    case '$':
		m++;
		if (dest != NULL)
		    *dest++ = '\\';
		break;
	}
	if (dest != NULL)
	    *dest++ = *src;
	// Copy remaining bytes of a multibyte character.
	if (has_mbyte)
	{
	    int i, mb_len;

	    mb_len = (*mb_ptr2len)(src) - 1;
	    if (mb_len > 0 && len >= mb_len)
		for (i = 0; i < mb_len; ++i)
		{
		    --len;
		    ++src;
		    if (dest != NULL)
			*dest++ = *src;
		}
	}
    }
    if (dest != NULL)
	*dest = NUL;

    return m;
}

# define CTRL_X_LOCAL_MSG	15	// only used in "ctrl_x_msgs"

# define CTRL_X_EVAL		16	// for builtin function complete()

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.
    N_(" Command-line completion (^V^N^P)"),
};

static char *ctrl_x_mode_names[] = {
	"keyword",
	"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",
	"cmdline",
};

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

# define CP_ORIGINAL_TEXT   1	// the original text when the expansion begun

# define CP_CONT_S_IPOS	    4	// use CONT_S_IPOS for compl_cont_status

# define CP_ICASE	    16	// ins_compl_equal() ignores case

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;

static int	  compl_enter_selects = FALSE;

static char_u	  *compl_leader = NULL;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_used_match;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

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;


find_line_end(char_u *ptr)
{
    char_u	*s;

    s = ptr + STRLEN(ptr);
    while (s > ptr && (s[-1] == CAR || s[-1] == NL))
	--s;
    return s;
}

patch 9.0.0102: reading past end of line with insert mode completion

Problem:    Reading past end of line with insert mode completion.
Solution:   Check text length.

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

static char_u	  *compl_leader = NULL;

static char_u	  *compl_pattern = NULL;

static char_u	  *compl_orig_text = NULL;

static int ins_compl_add(char_u *str, int len, char_u *fname, char_u **cptext, typval_T *user_data, int cdir, int flags, int adup);

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_set_original_text(char_u *str);

static void ins_compl_fixRedoBufForLeader(char_u *ptr_arg);

static unsigned  quote_meta(char_u *dest, char_u *str, int len);

typedef struct
{
    char_u	*e_cpt;			// current entry in 'complete'
    buf_T	*ins_buf;		// buffer being scanned
    pos_T	*cur_match_pos;			// current match position
    pos_T	prev_match_pos;		// previous match position
    int		set_match_pos;		// save first_match_pos/last_match_pos
    pos_T	first_match_pos;	// first match position
    pos_T	last_match_pos;		// last match position
    int		found_all;		// found all matches of a certain type.
    char_u	*dict;			// dictionary file to search
    int		dict_f;			// "dict" is an exact file name or not
} ins_compl_next_state_T;


get_filename_compl_info(char_u *line, int startcol, colnr_T curs_col)
{
    // Go back to just before the first filename character.
    if (startcol > 0)
    {
	char_u	*p = line + startcol;

	MB_PTR_BACK(line, p);
	while (p > line && vim_isfilec(PTR2CHAR(p)))
	    MB_PTR_BACK(line, p);
	if (p == line && vim_isfilec(PTR2CHAR(p)))
	    startcol = 0;
	else
	    startcol = (int)(p - line) + 1;
    }

    compl_col += startcol;
    compl_length = (int)curs_col - startcol;
    compl_pattern = addstar(line + compl_col, compl_length, EXPAND_FILES);
    if (compl_pattern == NULL)
	return FAIL;

    return OK;
}

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.
    N_(" Command-line completion (^V^N^P)"),
};

static char *ctrl_x_mode_names[] = {
	"keyword",
	"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",
	"cmdline",
};

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

# define CP_ORIGINAL_TEXT   1	// the original text when the expansion begun

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;

static int	  compl_enter_selects = FALSE;

static char_u	  *compl_leader = NULL;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_used_match;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

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;

static int	  compl_length = 0;

static colnr_T	  compl_col = 0;

static char_u	  *compl_orig_text = NULL;

static int	  compl_cont_mode = 0;

static int	  compl_cont_status = 0;

# define CONT_INTRPT	(2 + 4)	// a ^X interrupted the current expansion


ins_compl_insert(int in_compl_func)
{
    int compl_len = get_compl_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 (match_at_original_text(compl_shown_match))
	compl_used_match = FALSE;
    else
	compl_used_match = TRUE;
#ifdef FEAT_EVAL
    {
	dict_T *dict = ins_compl_dict_alloc(compl_shown_match);

	set_vim_var_dict(VV_COMPLETED_ITEM, dict);
    }
#endif
    if (!in_compl_func)
	compl_curr_match = compl_shown_match;
}

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

# define CP_ORIGINAL_TEXT   1	// the original text when the expansion begun

# define CP_EQUAL	    8	// ins_compl_equal() always returns TRUE

static char e_hitend[] = N_("Hit end of paragraph");

static compl_T    *compl_curr_match = NULL;

static compl_T    *compl_shown_match = NULL;

static int	  compl_enter_selects = FALSE;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_used_match;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

static int	  ctrl_x_mode = CTRL_X_NORMAL;

static int	  compl_matches = 0;

static int	  compl_direction = FORWARD;

static int	  compl_shows_dir = FORWARD;

static int	  compl_pending = 0;

static int	  compl_length = 0;

static int	  compl_cont_mode = 0;

static int	  compl_cont_status = 0;

# define CONT_INTRPT	(2 + 4)	// a ^X interrupted the current expansion

static int	  compl_opt_refresh_always = FALSE;

static int	  compl_opt_suppress_empty = FALSE;

static int ins_compl_add(char_u *str, int len, char_u *fname, char_u **cptext, typval_T *user_data, int cdir, int flags, int adup);

static void ins_compl_files(int count, char_u **files, int thesaurus, int flags, regmatch_T *regmatch, char_u *buf, int *dir);

static int  ins_compl_need_restart(void);

static int  get_compl_len(void);

static void ins_compl_add_dict(dict_T *dict);

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);

static int  spell_bad_len = 0;

static int compl_match_arraysize;

#define DICT_FIRST	(1)	// use just first element in "dict"

#define DICT_EXACT	(2)	// "dict" is the exact name of a file


compl_get_info(char_u *line, int startcol, colnr_T curs_col, int *line_invalid)
{
    if (ctrl_x_mode_normal()
	    || (ctrl_x_mode & CTRL_X_WANT_IDENT
		&& !thesaurus_func_complete(ctrl_x_mode)))
    {
	return get_normal_compl_info(line, startcol, curs_col);
    }
    else if (ctrl_x_mode_line_or_eval())
    {
	return get_wholeline_compl_info(line, curs_col);
    }
    else if (ctrl_x_mode_files())
    {
	return get_filename_compl_info(line, startcol, curs_col);
    }
    else if (ctrl_x_mode == CTRL_X_CMDLINE)
    {
	return get_cmdline_compl_info(line, curs_col);
    }
    else if (ctrl_x_mode_function() || ctrl_x_mode_omni()
	    || thesaurus_func_complete(ctrl_x_mode))
    {
	if (get_userdefined_compl_info(curs_col) == FAIL)
	    return FAIL;
	*line_invalid = TRUE;	// "line" may have become invalid
    }
    else if (ctrl_x_mode_spell())
    {
	if (get_spell_compl_info(startcol, curs_col) == FAIL)
	    return FAIL;
	*line_invalid = TRUE;	// "line" may have become invalid
    }
    else
    {
	internal_error("ins_complete()");
	return FAIL;
    }

    return OK;
}

# define CTRL_X_WANT_IDENT	0x100

# 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_DICTIONARY	(9 + CTRL_X_WANT_IDENT)

# define CTRL_X_THESAURUS	(10 + CTRL_X_WANT_IDENT)

# define CTRL_X_CMDLINE		11

# define CTRL_X_CMDLINE_CTRL_X	17	// CTRL-X typed in CTRL_X_CMDLINE

# define CTRL_X_MSG(i) ctrl_x_msgs[(i) & ~CTRL_X_WANT_IDENT]

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.
    N_(" Command-line completion (^V^N^P)"),
};

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

# define CP_EQUAL	    8	// ins_compl_equal() always returns TRUE

static int	  compl_enter_selects = FALSE;

static char_u	  *compl_leader = NULL;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_used_match;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

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;

static int	  compl_length = 0;

static colnr_T	  compl_col = 0;

static char_u	  *compl_orig_text = NULL;

static int	  compl_cont_mode = 0;

static int	  compl_cont_status = 0;

# define CONT_SOL	16	// pattern includes start of line, just for


ins_compl_init_get_longest(void)
{
    compl_get_longest = FALSE;
}

static int	  compl_enter_selects = FALSE;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

static int	  compl_opt_refresh_always = FALSE;

static int	  compl_opt_suppress_empty = FALSE;

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 void ins_compl_free(void);

static int  ins_compl_need_restart(void);

static void ins_compl_new_leader(void);

static int  get_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);

static void ins_compl_add_list(list_T *list);

static void ins_compl_add_dict(dict_T *dict);

static void show_pum(int prev_w_wrow, int prev_w_leftcol);

static void spell_back_to_badword(void);


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 || (!match_at_original_text(compl) && ++i == 2))
	    break;
	compl = compl->cp_next;
    } while (!is_first_match(compl));

    if (strstr((char *)p_cot, "menuone") != NULL)
	return (i >= 1);
    return (i >= 2);
}

# define CTRL_X_LOCAL_MSG	15	// only used in "ctrl_x_msgs"

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.
    N_(" Command-line completion (^V^N^P)"),
};

static char *ctrl_x_mode_names[] = {
	"keyword",
	"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",
	"cmdline",
};

#define CPT_MENU	1	// "menu"

typedef struct compl_S compl_T;

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

# define CP_ORIGINAL_TEXT   1	// the original text when the expansion begun

static char e_hitend[] = N_("Hit end of paragraph");

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;

static int	  compl_enter_selects = FALSE;

static char_u	  *compl_leader = NULL;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_used_match;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

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;

static int	  compl_length = 0;

static char_u	  *compl_orig_text = NULL;

static int	  compl_cont_mode = 0;


int ctrl_x_mode_cmdline(void) {
	return ctrl_x_mode == CTRL_X_CMDLINE
		|| ctrl_x_mode == CTRL_X_CMDLINE_CTRL_X; }

# define CTRL_X_CMDLINE		11

# define CTRL_X_CMDLINE_CTRL_X	17	// CTRL-X typed in CTRL_X_CMDLINE

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

static int	  compl_enter_selects = FALSE;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_used_match;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

static int	  ctrl_x_mode = CTRL_X_NORMAL;

static int	  compl_matches = 0;

static int	  compl_direction = FORWARD;

static int	  compl_shows_dir = FORWARD;

static int	  compl_pending = 0;

static int	  compl_length = 0;

static int	  compl_cont_mode = 0;

static int	  compl_cont_status = 0;

# define CONT_LOCAL	32	// for ctrl_x_mode 0, ^X^P/^X^N do a local

static int	  compl_opt_refresh_always = FALSE;

static int	  compl_opt_suppress_empty = FALSE;

static int ins_compl_add(char_u *str, int len, char_u *fname, char_u **cptext, typval_T *user_data, 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 void ins_compl_free(void);

static int  ins_compl_need_restart(void);

static void ins_compl_new_leader(void);

static int  get_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);

static void ins_compl_add_list(list_T *list);

static void ins_compl_add_dict(dict_T *dict);

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);

static void spell_back_to_badword(void);

static int  spell_bad_len = 0;

static int compl_match_arraysize;


is_first_match(compl_T *match)
{
    return match == compl_first_match;
}

typedef struct compl_S compl_T;

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

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;

static void ins_compl_longest_match(compl_T *match);

typedef struct
{
    char_u	*e_cpt;			// current entry in 'complete'
    buf_T	*ins_buf;		// buffer being scanned
    pos_T	*cur_match_pos;			// current match position
    pos_T	prev_match_pos;		// previous match position
    int		set_match_pos;		// save first_match_pos/last_match_pos
    pos_T	first_match_pos;	// first match position
    pos_T	last_match_pos;		// last match position
    int		found_all;		// found all matches of a certain type.
    char_u	*dict;			// dictionary file to search
    int		dict_f;			// "dict" is an exact file name or not
} ins_compl_next_state_T;


compl_status_sol(void)
{
    return compl_cont_status & CONT_SOL;
}

# define CP_CONT_S_IPOS	    4	// use CONT_S_IPOS for compl_cont_status

static int	  compl_cont_status = 0;

# define CONT_SOL	16	// pattern includes start of line, just for

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 void ins_compl_free(void);

static int  ins_compl_need_restart(void);

static void ins_compl_new_leader(void);

static int  get_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);

static void ins_compl_add_list(list_T *list);

static void ins_compl_add_dict(dict_T *dict);

static void show_pum(int prev_w_wrow, int prev_w_leftcol);

static void spell_back_to_badword(void);


f_complete_add(typval_T *argvars, typval_T *rettv)
{
    if (in_vim9script() && check_for_string_or_dict_arg(argvars, 0) == FAIL)
	return;

    rettv->vval.v_number = ins_compl_add_tv(&argvars[0], 0, FALSE);
}

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

static int	  compl_enter_selects = FALSE;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

static int	  compl_opt_refresh_always = FALSE;

static int	  compl_opt_suppress_empty = FALSE;

static int ins_compl_add(char_u *str, int len, char_u *fname, char_u **cptext, typval_T *user_data, int cdir, int flags, int adup);


ins_compl_pum_key(int c)
{
    return pum_visible() && (c == K_PAGEUP || c == K_KPAGEUP || c == K_S_UP
		     || c == K_PAGEDOWN || c == K_KPAGEDOWN || c == K_S_DOWN
		     || c == K_UP || c == K_DOWN);
}

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

static int	  compl_enter_selects = FALSE;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_used_match;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

static int	  ctrl_x_mode = CTRL_X_NORMAL;

static int	  compl_matches = 0;

static int	  compl_direction = FORWARD;

static int	  compl_shows_dir = FORWARD;

static int	  compl_pending = 0;

static int	  compl_length = 0;

static int	  compl_cont_mode = 0;

static int	  compl_cont_status = 0;

static int	  compl_opt_refresh_always = FALSE;

static int	  compl_opt_suppress_empty = FALSE;

static int ins_compl_add(char_u *str, int len, char_u *fname, char_u **cptext, typval_T *user_data, int cdir, int flags, int adup);

static void ins_compl_files(int count, char_u **files, int thesaurus, int flags, regmatch_T *regmatch, char_u *buf, int *dir);

static int  ins_compl_need_restart(void);

static int  get_compl_len(void);

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);

static int  spell_bad_len = 0;

static int compl_match_arraysize;

typedef struct
{
    char_u	*e_cpt;			// current entry in 'complete'
    buf_T	*ins_buf;		// buffer being scanned
    pos_T	*cur_match_pos;			// current match position
    pos_T	prev_match_pos;		// previous match position
    int		set_match_pos;		// save first_match_pos/last_match_pos
    pos_T	first_match_pos;	// first match position
    pos_T	last_match_pos;		// last match position
    int		found_all;		// found all matches of a certain type.
    char_u	*dict;			// dictionary file to search
    int		dict_f;			// "dict" is an exact file name or not
} ins_compl_next_state_T;


thesaurus_func_complete(int type UNUSED)
{
#ifdef FEAT_COMPL_FUNC
    return type == CTRL_X_THESAURUS
		&& (*curbuf->b_p_tsrfu != NUL || *p_tsrfu != NUL);
#else
    return FALSE;
#endif
}

# define CTRL_X_THESAURUS	(10 + CTRL_X_WANT_IDENT)

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

static int	  compl_enter_selects = FALSE;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_used_match;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

static int	  ctrl_x_mode = CTRL_X_NORMAL;

static int	  compl_matches = 0;

static int	  compl_direction = FORWARD;

static int	  compl_shows_dir = FORWARD;

static int	  compl_pending = 0;

static int	  compl_length = 0;

static int	  compl_cont_mode = 0;

static int	  compl_cont_status = 0;

static int	  compl_opt_refresh_always = FALSE;

static int	  compl_opt_suppress_empty = FALSE;

static int ins_compl_add(char_u *str, int len, char_u *fname, char_u **cptext, typval_T *user_data, int cdir, int flags, int adup);

static void ins_compl_files(int count, char_u **files, int thesaurus, int flags, regmatch_T *regmatch, char_u *buf, int *dir);

static int  ins_compl_need_restart(void);

static int  get_compl_len(void);

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);

static int  spell_bad_len = 0;

static int compl_match_arraysize;

typedef struct
{
    char_u	*e_cpt;			// current entry in 'complete'
    buf_T	*ins_buf;		// buffer being scanned
    pos_T	*cur_match_pos;			// current match position
    pos_T	prev_match_pos;		// previous match position
    int		set_match_pos;		// save first_match_pos/last_match_pos
    pos_T	first_match_pos;	// first match position
    pos_T	last_match_pos;		// last match position
    int		found_all;		// found all matches of a certain type.
    char_u	*dict;			// dictionary file to search
    int		dict_f;			// "dict" is an exact file name or not
} ins_compl_next_state_T;


free_insexpand_stuff(void)
{
    VIM_CLEAR(compl_orig_text);
# ifdef FEAT_EVAL
    free_callback(&cfu_cb);
    free_callback(&ofu_cb);
    free_callback(&tsrfu_cb);
# endif
}

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

static char_u	  *compl_orig_text = NULL;

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 void ins_compl_free(void);

static int  ins_compl_need_restart(void);

static void ins_compl_new_leader(void);

static int  get_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);

static void ins_compl_add_list(list_T *list);

static void ins_compl_add_dict(dict_T *dict);

static void show_pum(int prev_w_wrow, int prev_w_leftcol);

static void spell_back_to_badword(void);

static callback_T cfu_cb;

static callback_T ofu_cb;

static callback_T tsrfu_cb;


compl_status_adding(void)
{
    return compl_cont_status & CONT_ADDING;
}

# define CP_CONT_S_IPOS	    4	// use CONT_S_IPOS for compl_cont_status

static int	  compl_cont_status = 0;

# define CONT_ADDING	1	// "normal" or "adding" expansion

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 void ins_compl_free(void);

static int  ins_compl_need_restart(void);

static void ins_compl_new_leader(void);

static int  get_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);

static void ins_compl_add_list(list_T *list);

static void ins_compl_add_dict(dict_T *dict);

static void show_pum(int prev_w_wrow, int prev_w_leftcol);

static void spell_back_to_badword(void);


compl_status_local(void)
{
    return compl_cont_status & CONT_LOCAL;
}

# define CP_CONT_S_IPOS	    4	// use CONT_S_IPOS for compl_cont_status

static int	  compl_cont_status = 0;

# define CONT_LOCAL	32	// for ctrl_x_mode 0, ^X^P/^X^N do a local

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 void ins_compl_free(void);

static int  ins_compl_need_restart(void);

static void ins_compl_new_leader(void);

static int  get_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);

static void ins_compl_add_list(list_T *list);

static void ins_compl_add_dict(dict_T *dict);

static void show_pum(int prev_w_wrow, int prev_w_leftcol);

static void spell_back_to_badword(void);


ins_compl_clear(void)
{
    compl_cont_status = 0;
    compl_started = FALSE;
    compl_matches = 0;
    VIM_CLEAR(compl_pattern);
    VIM_CLEAR(compl_leader);
    edit_submode_extra = NULL;
    VIM_CLEAR(compl_orig_text);
    compl_enter_selects = FALSE;
#ifdef FEAT_EVAL
    // clear v:completed_item
    set_vim_var_dict(VV_COMPLETED_ITEM, dict_alloc_lock(VAR_FIXED));
#endif
}

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.
    N_(" Command-line completion (^V^N^P)"),
};

static char *ctrl_x_mode_names[] = {
	"keyword",
	"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",
	"cmdline",
};

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

# define CP_CONT_S_IPOS	    4	// use CONT_S_IPOS for compl_cont_status

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;

static int	  compl_enter_selects = FALSE;

static char_u	  *compl_leader = NULL;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

static int	  compl_matches = 0;

static char_u	  *compl_pattern = NULL;

static char_u	  *compl_orig_text = NULL;

static int	  compl_cont_status = 0;

static int	  compl_opt_refresh_always = FALSE;

static int	  compl_opt_suppress_empty = FALSE;

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 void ins_compl_free(void);

static int  ins_compl_need_restart(void);


ins_compl_addfrommatch(void)
{
    char_u	*p;
    int		len = (int)curwin->w_cursor.col - (int)compl_col;
    int		c;
    compl_T	*cp;

    p = compl_shown_match->cp_str;
    if ((int)STRLEN(p) <= 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))
	    return;

	p = NULL;
	for (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,
			(int)STRLEN(compl_leader)))
	    {
		p = cp->cp_str;
		break;
	    }
	}
	if (p == NULL || (int)STRLEN(p) <= len)
	    return;
    }
    p += len;
    c = PTR2CHAR(p);
    ins_compl_addleader(c);
}

# define CTRL_X_EVAL		16	// for builtin function complete()

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.
    N_(" Command-line completion (^V^N^P)"),
};

static char *ctrl_x_mode_names[] = {
	"keyword",
	"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",
	"cmdline",
};

typedef struct compl_S compl_T;

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

# define CP_ORIGINAL_TEXT   1	// the original text when the expansion begun

# define CP_CONT_S_IPOS	    4	// use CONT_S_IPOS for compl_cont_status

# define CP_EQUAL	    8	// ins_compl_equal() always returns TRUE

# define CP_ICASE	    16	// ins_compl_equal() ignores case

# define CP_FAST	    32	// use fast_breakcheck instead of ui_breakcheck

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;

static int	  compl_enter_selects = FALSE;

static char_u	  *compl_leader = NULL;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_used_match;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

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 ctrl_x_mode_eval(void) { return ctrl_x_mode == CTRL_X_EVAL; }

# define CTRL_X_EVAL		16	// for builtin function complete()

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.
    N_(" Command-line completion (^V^N^P)"),
};

static char *ctrl_x_mode_names[] = {
	"keyword",
	"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",
	"cmdline",
};

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

static char e_hitend[] = N_("Hit end of paragraph");

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;

static int	  compl_enter_selects = FALSE;

static char_u	  *compl_leader = NULL;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_used_match;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

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;

static pos_T	  compl_startpos;

static int	  compl_length = 0;

static colnr_T	  compl_col = 0;

static char_u	  *compl_orig_text = NULL;

static int	  compl_cont_mode = 0;

static expand_T	  compl_xp;

static int	  compl_cont_status = 0;


ins_compl_continue_search(char_u *line)
{
    // it is a continued search
    compl_cont_status &= ~CONT_INTRPT;	// remove INTRPT
    if (ctrl_x_mode_normal() || ctrl_x_mode_path_patterns()
						|| ctrl_x_mode_path_defines())
    {
	if (compl_startpos.lnum != curwin->w_cursor.lnum)
	{
	    // line (probably) wrapped, set compl_startpos to the
	    // first non_blank in the line, if it is not a wordchar
	    // include it to get a better pattern, but then we don't
	    // want the "\\<" prefix, check it below
	    compl_col = (colnr_T)getwhitecols(line);
	    compl_startpos.col = compl_col;
	    compl_startpos.lnum = curwin->w_cursor.lnum;
	    compl_cont_status &= ~CONT_SOL;   // clear SOL if present
	}
	else
	{
	    // S_IPOS was set when we inserted a word that was at the
	    // beginning of the line, which means that we'll go to SOL
	    // mode but first we need to redefine compl_startpos
	    if (compl_cont_status & CONT_S_IPOS)
	    {
		compl_cont_status |= CONT_SOL;
		compl_startpos.col = (colnr_T)(skipwhite(
			    line + compl_length
			    + compl_startpos.col) - line);
	    }
	    compl_col = compl_startpos.col;
	}
	compl_length = curwin->w_cursor.col - (int)compl_col;
	// IObuff is used to add a "word from the next line" would we
	// have enough space?  just being paranoid
#define	MIN_SPACE 75
	if (compl_length > (IOSIZE - MIN_SPACE))
	{
	    compl_cont_status &= ~CONT_SOL;
	    compl_length = (IOSIZE - MIN_SPACE);
	    compl_col = curwin->w_cursor.col - compl_length;
	}
	compl_cont_status |= CONT_ADDING | CONT_N_ADDS;
	if (compl_length < 1)
	    compl_cont_status &= CONT_LOCAL;
    }
    else if (ctrl_x_mode_line_or_eval())
	compl_cont_status = CONT_ADDING | CONT_N_ADDS;
    else
	compl_cont_status = 0;
}

# 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_CMDLINE_CTRL_X	17	// CTRL-X typed in CTRL_X_CMDLINE

# define CTRL_X_MSG(i) ctrl_x_msgs[(i) & ~CTRL_X_WANT_IDENT]

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.
    N_(" Command-line completion (^V^N^P)"),
};

static char *ctrl_x_mode_names[] = {
	"keyword",
	"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",
	"cmdline",
};

#define CPT_ABBR	0	// "abbr"

#define CPT_MENU	1	// "menu"

#define CPT_KIND	2	// "kind"

#define CPT_INFO	3	// "info"

#define CPT_COUNT	4	// Number of entries


int ctrl_x_mode_function(void) { return ctrl_x_mode == CTRL_X_FUNCTION; }

# define CTRL_X_FUNCTION	12

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

static int	  compl_enter_selects = FALSE;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_used_match;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

static int	  ctrl_x_mode = CTRL_X_NORMAL;

static int	  compl_matches = 0;

static int	  compl_direction = FORWARD;

static int	  compl_shows_dir = FORWARD;

static int	  compl_pending = 0;

static int	  compl_length = 0;

static int	  compl_cont_mode = 0;

static int	  compl_cont_status = 0;

# define CONT_LOCAL	32	// for ctrl_x_mode 0, ^X^P/^X^N do a local

static int	  compl_opt_refresh_always = FALSE;

static int	  compl_opt_suppress_empty = FALSE;

static int ins_compl_add(char_u *str, int len, char_u *fname, char_u **cptext, typval_T *user_data, 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 void ins_compl_free(void);

static int  ins_compl_need_restart(void);

static void ins_compl_new_leader(void);

static int  get_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);

static void ins_compl_add_list(list_T *list);

static void ins_compl_add_dict(dict_T *dict);

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);

static void spell_back_to_badword(void);

static int  spell_bad_len = 0;

static int compl_match_arraysize;


ins_compl_use_match(int c)
{
    switch (c)
    {
	case K_UP:
	case K_DOWN:
	case K_PAGEDOWN:
	case K_KPAGEDOWN:
	case K_S_DOWN:
	case K_PAGEUP:
	case K_KPAGEUP:
	case K_S_UP:
	    return FALSE;
    }
    return TRUE;
}

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

# define CP_EQUAL	    8	// ins_compl_equal() always returns TRUE

# define CP_ICASE	    16	// ins_compl_equal() ignores case

static int	  compl_enter_selects = FALSE;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_used_match;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

static int	  ctrl_x_mode = CTRL_X_NORMAL;

static int	  compl_matches = 0;

static int	  compl_direction = FORWARD;

static int	  compl_shows_dir = FORWARD;

static int	  compl_pending = 0;

static int	  compl_length = 0;

static int	  compl_cont_mode = 0;

static int	  compl_cont_status = 0;

static int	  compl_opt_refresh_always = FALSE;

static int	  compl_opt_suppress_empty = FALSE;

static int ins_compl_add(char_u *str, int len, char_u *fname, char_u **cptext, typval_T *user_data, int cdir, int flags, int adup);

static void ins_compl_files(int count, char_u **files, int thesaurus, int flags, regmatch_T *regmatch, char_u *buf, int *dir);

static int  ins_compl_need_restart(void);

static int  get_compl_len(void);

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);

static int  spell_bad_len = 0;

static int compl_match_arraysize;

typedef struct
{
    char_u	*e_cpt;			// current entry in 'complete'
    buf_T	*ins_buf;		// buffer being scanned
    pos_T	*cur_match_pos;			// current match position
    pos_T	prev_match_pos;		// previous match position
    int		set_match_pos;		// save first_match_pos/last_match_pos
    pos_T	first_match_pos;	// first match position
    pos_T	last_match_pos;		// last match position
    int		found_all;		// found all matches of a certain type.
    char_u	*dict;			// dictionary file to search
    int		dict_f;			// "dict" is an exact file name or not
} ins_compl_next_state_T;


ins_compl_active(void)
{
    return compl_started;
}

static int	  compl_started = FALSE;

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 void ins_compl_free(void);

static int  ins_compl_need_restart(void);

static void ins_compl_new_leader(void);

static int  get_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);

static void ins_compl_add_list(list_T *list);

static void ins_compl_add_dict(dict_T *dict);

static void show_pum(int prev_w_wrow, int prev_w_leftcol);

static void spell_back_to_badword(void);


int ctrl_x_mode_path_patterns(void) {
				  return ctrl_x_mode == CTRL_X_PATH_PATTERNS; }

# define CTRL_X_PATH_PATTERNS	(6 + CTRL_X_WANT_IDENT)

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

static int	  compl_enter_selects = FALSE;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_used_match;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

static int	  ctrl_x_mode = CTRL_X_NORMAL;

static int	  compl_matches = 0;

static int	  compl_direction = FORWARD;

static int	  compl_shows_dir = FORWARD;

static int	  compl_pending = 0;

static int	  compl_length = 0;

static int	  compl_cont_mode = 0;

static int	  compl_cont_status = 0;

# define CONT_LOCAL	32	// for ctrl_x_mode 0, ^X^P/^X^N do a local

static int	  compl_opt_refresh_always = FALSE;

static int	  compl_opt_suppress_empty = FALSE;

static int ins_compl_add(char_u *str, int len, char_u *fname, char_u **cptext, typval_T *user_data, 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 void ins_compl_free(void);

static int  ins_compl_need_restart(void);

static void ins_compl_new_leader(void);

static int  get_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);

static void ins_compl_add_list(list_T *list);

static void ins_compl_add_dict(dict_T *dict);

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);

static void spell_back_to_badword(void);

static int  spell_bad_len = 0;

static int compl_match_arraysize;


ins_compl_upd_pum(void)
{
    int		h;

    if (compl_match_array == NULL)
	return;

    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();
}

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.
    N_(" Command-line completion (^V^N^P)"),
};

static char *ctrl_x_mode_names[] = {
	"keyword",
	"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",
	"cmdline",
};

#define CPT_MENU	1	// "menu"

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

# define CP_ORIGINAL_TEXT   1	// the original text when the expansion begun

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;

static int	  compl_enter_selects = FALSE;

static char_u	  *compl_leader = NULL;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_used_match;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

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;

static int	  compl_length = 0;

static char_u	  *compl_orig_text = NULL;

static int	  compl_cont_mode = 0;

static int	  compl_cont_status = 0;

# define CONT_INTRPT	(2 + 4)	// a ^X interrupted the current expansion


thesarurs_add_words_in_line(
	char_u	*fname,
	char_u	**buf_arg,
	int	dir,
	char_u	*skip_word)
{
    int		status = OK;
    char_u	*ptr;
    char_u	*wstart;

    // Add the other matches on the line
    ptr = *buf_arg;
    while (!got_int)
    {
	// Find start of the next word.  Skip white
	// space and punctuation.
	ptr = find_word_start(ptr);
	if (*ptr == NUL || *ptr == NL)
	    break;
	wstart = ptr;

	// Find end of the word.
	if (has_mbyte)
	    // Japanese words may have characters in
	    // different classes, only separate words
	    // with single-byte non-word characters.
	    while (*ptr != NUL)
	    {
		int l = (*mb_ptr2len)(ptr);

		if (l < 2 && !vim_iswordc(*ptr))
		    break;
		ptr += l;
	    }
	else
	    ptr = find_word_end(ptr);

	// Add the word. Skip the regexp match.
	if (wstart != skip_word)
	{
	    status = ins_compl_add_infercase(wstart, (int)(ptr - wstart), p_ic,
							fname, dir, FALSE);
	    if (status == FAIL)
		break;
	}
    }

    *buf_arg = ptr;
    return status;
}

# define CTRL_X_LOCAL_MSG	15	// only used in "ctrl_x_msgs"

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.
    N_(" Command-line completion (^V^N^P)"),
};

static char *ctrl_x_mode_names[] = {
	"keyword",
	"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",
	"cmdline",
};

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

# define CP_ORIGINAL_TEXT   1	// the original text when the expansion begun

static char e_hitend[] = N_("Hit end of paragraph");

static int	  compl_enter_selects = FALSE;

static char_u	  *compl_leader = NULL;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_used_match;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

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;

static int	  compl_length = 0;

static char_u	  *compl_orig_text = NULL;

static int	  compl_cont_mode = 0;

static int	  compl_cont_status = 0;

# define CONT_INTRPT	(2 + 4)	// a ^X interrupted the current expansion

# define CONT_N_ADDS	4	// next ^X<> will add-new or expand-current


get_compl_len(void)
{
    int off = (int)curwin->w_cursor.col - (int)compl_col;

    if (off < 0)
	return 0;
    return off;
}

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

static int	  compl_enter_selects = FALSE;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_used_match;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

static int	  ctrl_x_mode = CTRL_X_NORMAL;

static int	  compl_matches = 0;

static int	  compl_direction = FORWARD;

static int	  compl_shows_dir = FORWARD;

static int	  compl_pending = 0;

static int	  compl_length = 0;

static colnr_T	  compl_col = 0;

static int	  compl_cont_mode = 0;

static int	  compl_cont_status = 0;

static int	  compl_opt_refresh_always = FALSE;

static int	  compl_opt_suppress_empty = FALSE;

static int ins_compl_add(char_u *str, int len, char_u *fname, char_u **cptext, typval_T *user_data, 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 void ins_compl_free(void);

static int  ins_compl_need_restart(void);

static void ins_compl_new_leader(void);

static int  get_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);

static void ins_compl_add_list(list_T *list);

static void ins_compl_add_dict(dict_T *dict);

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);

static void spell_back_to_badword(void);

static int  spell_bad_len = 0;

static int compl_match_arraysize;

typedef struct
{
    char_u	*e_cpt;			// current entry in 'complete'
    buf_T	*ins_buf;		// buffer being scanned
    pos_T	*cur_match_pos;			// current match position
    pos_T	prev_match_pos;		// previous match position
    int		set_match_pos;		// save first_match_pos/last_match_pos
    pos_T	first_match_pos;	// first match position
    pos_T	last_match_pos;		// last match position
    int		found_all;		// found all matches of a certain type.
    char_u	*dict;			// dictionary file to search
    int		dict_f;			// "dict" is an exact file name or not
} ins_compl_next_state_T;


ins_compl_update_shown_match(void)
{
    while (!ins_compl_equal(compl_shown_match,
		compl_leader, (int)STRLEN(compl_leader))
	    && compl_shown_match->cp_next != NULL
	    && !is_first_match(compl_shown_match->cp_next))
	compl_shown_match = compl_shown_match->cp_next;

    // 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, (int)STRLEN(compl_leader))
	    && (compl_shown_match->cp_next == NULL
		|| is_first_match(compl_shown_match->cp_next)))
    {
	while (!ins_compl_equal(compl_shown_match,
		    compl_leader, (int)STRLEN(compl_leader))
		&& compl_shown_match->cp_prev != NULL
		&& !is_first_match(compl_shown_match->cp_prev))
	    compl_shown_match = compl_shown_match->cp_prev;
    }
}

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.
    N_(" Command-line completion (^V^N^P)"),
};

static char *ctrl_x_mode_names[] = {
	"keyword",
	"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",
	"cmdline",
};

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

# define CP_ORIGINAL_TEXT   1	// the original text when the expansion begun

# define CP_EQUAL	    8	// ins_compl_equal() always returns TRUE

# define CP_ICASE	    16	// ins_compl_equal() ignores case

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;

static int	  compl_enter_selects = FALSE;

static char_u	  *compl_leader = NULL;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_used_match;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

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;

static int	  compl_length = 0;

static char_u	  *compl_orig_text = NULL;

static int	  compl_cont_mode = 0;

static int	  compl_cont_status = 0;


ins_compl_add_infercase(
    char_u	*str_arg,
    int		len,
    int		icase,
    char_u	*fname,
    int		dir,
    int		cont_s_ipos)  // next ^X<> will set initial_pos
{
    char_u	*str = str_arg;
    char_u	*p;
    int		char_len;		// count multi-byte characters
    int		compl_char_len;
    int		min_len;
    int		flags = 0;
    int		res;
    char_u	*tofree = NULL;

    if (p_ic && curbuf->b_p_inf && len > 0)
    {
	// Infer case of completed part.

	// Find actual length of completion.
	if (has_mbyte)
	{
	    p = str;
	    char_len = 0;
	    while (*p != NUL)
	    {
		MB_PTR_ADV(p);
		++char_len;
	    }
	}
	else
	    char_len = len;

	// Find actual length of original text.
	if (has_mbyte)
	{
	    p = compl_orig_text;
	    compl_char_len = 0;
	    while (*p != NUL)
	    {
		MB_PTR_ADV(p);
		++compl_char_len;
	    }
	}
	else
	    compl_char_len = compl_length;

	// "char_len" may be smaller than "compl_char_len" when using
	// thesaurus, only use the minimum when comparing.
	min_len = char_len < compl_char_len ? char_len : compl_char_len;

	str = ins_compl_infercase_gettext(str, char_len,
					  compl_char_len, min_len, &tofree);
    }
    if (cont_s_ipos)
	flags |= CP_CONT_S_IPOS;
    if (icase)
	flags |= CP_ICASE;

    res = ins_compl_add(str, len, fname, NULL, NULL, dir, flags, FALSE);
    vim_free(tofree);
    return res;
}

# define CTRL_X_NORMAL		0  // CTRL-N CTRL-P completion, default

# define CTRL_X_LOCAL_MSG	15	// only used in "ctrl_x_msgs"

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.
    N_(" Command-line completion (^V^N^P)"),
};

static char *ctrl_x_mode_names[] = {
	"keyword",
	"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",
	"cmdline",
};

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

# define CP_ORIGINAL_TEXT   1	// the original text when the expansion begun

# define CP_CONT_S_IPOS	    4	// use CONT_S_IPOS for compl_cont_status

# define CP_ICASE	    16	// ins_compl_equal() ignores case

# define CP_FAST	    32	// use fast_breakcheck instead of ui_breakcheck

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;

static int	  compl_enter_selects = FALSE;

static char_u	  *compl_leader = NULL;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_used_match;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

static int	  ctrl_x_mode = CTRL_X_NORMAL;

static int	  compl_matches = 0;

static char_u	  *compl_pattern = NULL;

static int	  compl_direction = FORWARD;


find_word_start(char_u *ptr)
{
    if (has_mbyte)
	while (*ptr != NUL && *ptr != '\n' && mb_get_class(ptr) <= 1)
	    ptr += (*mb_ptr2len)(ptr);
    else
	while (*ptr != NUL && *ptr != '\n' && !vim_iswordc(*ptr))
	    ++ptr;
    return ptr;
}

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

static char_u	  *compl_leader = NULL;

static char_u	  *compl_pattern = NULL;

static char_u	  *compl_orig_text = NULL;

static int ins_compl_add(char_u *str, int len, char_u *fname, char_u **cptext, typval_T *user_data, int cdir, int flags, int adup);

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_set_original_text(char_u *str);

static void ins_compl_fixRedoBufForLeader(char_u *ptr_arg);

static unsigned  quote_meta(char_u *dest, char_u *str, int len);

typedef struct
{
    char_u	*e_cpt;			// current entry in 'complete'
    buf_T	*ins_buf;		// buffer being scanned
    pos_T	*cur_match_pos;			// current match position
    pos_T	prev_match_pos;		// previous match position
    int		set_match_pos;		// save first_match_pos/last_match_pos
    pos_T	first_match_pos;	// first match position
    pos_T	last_match_pos;		// last match position
    int		found_all;		// found all matches of a certain type.
    char_u	*dict;			// dictionary file to search
    int		dict_f;			// "dict" is an exact file name or not
} ins_compl_next_state_T;


f_complete_check(typval_T *argvars UNUSED, typval_T *rettv)
{
    int		saved = RedrawingDisabled;

    RedrawingDisabled = 0;
    ins_compl_check_keys(0, TRUE);
    rettv->vval.v_number = ins_compl_interrupted();
    RedrawingDisabled = saved;
}

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

# define CP_EQUAL	    8	// ins_compl_equal() always returns TRUE

static int	  compl_enter_selects = FALSE;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_used_match;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

static int	  ctrl_x_mode = CTRL_X_NORMAL;

static int	  compl_matches = 0;

static int	  compl_direction = FORWARD;

static int	  compl_shows_dir = FORWARD;

static int	  compl_pending = 0;

static int	  compl_length = 0;

static int	  compl_cont_mode = 0;

static int	  compl_cont_status = 0;

static int	  compl_opt_refresh_always = FALSE;

static int	  compl_opt_suppress_empty = FALSE;

static int ins_compl_add(char_u *str, int len, char_u *fname, char_u **cptext, typval_T *user_data, int cdir, int flags, int adup);

static void ins_compl_files(int count, char_u **files, int thesaurus, int flags, regmatch_T *regmatch, char_u *buf, int *dir);

static int  ins_compl_need_restart(void);

static int  get_compl_len(void);

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);

static int  spell_bad_len = 0;

static int compl_match_arraysize;

typedef struct
{
    char_u	*e_cpt;			// current entry in 'complete'
    buf_T	*ins_buf;		// buffer being scanned
    pos_T	*cur_match_pos;			// current match position
    pos_T	prev_match_pos;		// previous match position
    int		set_match_pos;		// save first_match_pos/last_match_pos
    pos_T	first_match_pos;	// first match position
    pos_T	last_match_pos;		// last match position
    int		found_all;		// found all matches of a certain type.
    char_u	*dict;			// dictionary file to search
    int		dict_f;			// "dict" is an exact file name or not
} ins_compl_next_state_T;


compl_dir_forward(void)
{
    return compl_direction == FORWARD;
}

static int	  compl_direction = FORWARD;

static int	  compl_shows_dir = FORWARD;

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 void ins_compl_free(void);

static int  ins_compl_need_restart(void);

static void ins_compl_new_leader(void);

static int  get_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);

static void ins_compl_add_list(list_T *list);

static void ins_compl_add_dict(dict_T *dict);

static void show_pum(int prev_w_wrow, int prev_w_leftcol);

static void spell_back_to_badword(void);


find_word_end(char_u *ptr)
{
    int		start_class;

    if (has_mbyte)
    {
	start_class = mb_get_class(ptr);
	if (start_class > 1)
	    while (*ptr != NUL)
	    {
		ptr += (*mb_ptr2len)(ptr);
		if (mb_get_class(ptr) != start_class)
		    break;
	    }
    }
    else
	while (vim_iswordc(*ptr))
	    ++ptr;
    return ptr;
}

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

static int	  compl_enter_selects = FALSE;

static char_u	  *compl_leader = NULL;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_used_match;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

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;

static int	  compl_length = 0;

static char_u	  *compl_orig_text = NULL;

static int	  compl_cont_mode = 0;

static int	  compl_cont_status = 0;

static int	  compl_opt_refresh_always = FALSE;

static int	  compl_opt_suppress_empty = FALSE;

static int ins_compl_add(char_u *str, int len, char_u *fname, char_u **cptext, typval_T *user_data, int cdir, int flags, int adup);

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 int  ins_compl_need_restart(void);

static int  get_compl_len(void);

static void ins_compl_set_original_text(char_u *str);

static void ins_compl_fixRedoBufForLeader(char_u *ptr_arg);

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);

static int  spell_bad_len = 0;

static int compl_match_arraysize;

typedef struct
{
    char_u	*e_cpt;			// current entry in 'complete'
    buf_T	*ins_buf;		// buffer being scanned
    pos_T	*cur_match_pos;			// current match position
    pos_T	prev_match_pos;		// previous match position
    int		set_match_pos;		// save first_match_pos/last_match_pos
    pos_T	first_match_pos;	// first match position
    pos_T	last_match_pos;		// last match position
    int		found_all;		// found all matches of a certain type.
    char_u	*dict;			// dictionary file to search
    int		dict_f;			// "dict" is an exact file name or not
} ins_compl_next_state_T;


ins_compl_free(void)
{
    compl_T *match;
    int	    i;

    VIM_CLEAR(compl_pattern);
    VIM_CLEAR(compl_leader);

    if (compl_first_match == NULL)
	return;

    ins_compl_del_pum();
    pum_clear();

    compl_curr_match = compl_first_match;
    do
    {
	match = compl_curr_match;
	compl_curr_match = compl_curr_match->cp_next;
	vim_free(match->cp_str);
	// several entries may use the same fname, free it just once.
	if (match->cp_flags & CP_FREE_FNAME)
	    vim_free(match->cp_fname);
	for (i = 0; i < CPT_COUNT; ++i)
	    vim_free(match->cp_text[i]);
#ifdef FEAT_EVAL
	clear_tv(&match->cp_user_data);
#endif
	vim_free(match);
    } while (compl_curr_match != NULL && !is_first_match(compl_curr_match));
    compl_first_match = compl_curr_match = NULL;
    compl_shown_match = NULL;
    compl_old_match = NULL;
}

# define CTRL_X_EVAL		16	// for builtin function complete()

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.
    N_(" Command-line completion (^V^N^P)"),
};

static char *ctrl_x_mode_names[] = {
	"keyword",
	"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",
	"cmdline",
};

#define CPT_COUNT	4	// Number of entries

typedef struct compl_S compl_T;

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

# define CP_ORIGINAL_TEXT   1	// the original text when the expansion begun

# define CP_FREE_FNAME	    2	// cp_fname is allocated

# define CP_CONT_S_IPOS	    4	// use CONT_S_IPOS for compl_cont_status

# define CP_FAST	    32	// use fast_breakcheck instead of ui_breakcheck

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;

static int	  compl_enter_selects = FALSE;

static char_u	  *compl_leader = NULL;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_used_match;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

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;


ins_compl_restart(void)
{
    ins_compl_free();
    compl_started = FALSE;
    compl_matches = 0;
    compl_cont_status = 0;
    compl_cont_mode = 0;
}

# define CP_CONT_S_IPOS	    4	// use CONT_S_IPOS for compl_cont_status

static int	  compl_enter_selects = FALSE;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

static int	  compl_matches = 0;

static int	  compl_cont_mode = 0;

static int	  compl_cont_status = 0;

static int	  compl_opt_refresh_always = FALSE;

static int	  compl_opt_suppress_empty = FALSE;

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 void ins_compl_free(void);

static int  ins_compl_need_restart(void);

static void ins_compl_new_leader(void);

static int  get_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);

static void ins_compl_add_list(list_T *list);

static void ins_compl_add_dict(dict_T *dict);

static void show_pum(int prev_w_wrow, int prev_w_leftcol);

static void spell_back_to_badword(void);


int ctrl_x_mode_thesaurus(void) { return ctrl_x_mode == CTRL_X_THESAURUS; }

# define CTRL_X_THESAURUS	(10 + CTRL_X_WANT_IDENT)

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

static int	  compl_enter_selects = FALSE;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_used_match;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

static int	  ctrl_x_mode = CTRL_X_NORMAL;

static int	  compl_matches = 0;

static int	  compl_direction = FORWARD;

static int	  compl_shows_dir = FORWARD;

static int	  compl_pending = 0;

static int	  compl_length = 0;

static int	  compl_cont_mode = 0;

static int	  compl_cont_status = 0;

# define CONT_LOCAL	32	// for ctrl_x_mode 0, ^X^P/^X^N do a local

static int	  compl_opt_refresh_always = FALSE;

static int	  compl_opt_suppress_empty = FALSE;

static int ins_compl_add(char_u *str, int len, char_u *fname, char_u **cptext, typval_T *user_data, 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 void ins_compl_free(void);

static int  ins_compl_need_restart(void);

static void ins_compl_new_leader(void);

static int  get_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);

static void ins_compl_add_list(list_T *list);

static void ins_compl_add_dict(dict_T *dict);

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);

static void spell_back_to_badword(void);

static int  spell_bad_len = 0;

static int compl_match_arraysize;


ins_compl_show_pum(void)
{
    int		i;
    int		cur = -1;
    colnr_T	col;

    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.
	cur = ins_compl_build_pum();
    else
    {
	// popup menu already exists, only need to find the current item.
	for (i = 0; i < compl_match_arraysize; ++i)
	    if (compl_match_array[i].pum_text == compl_shown_match->cp_str
		    || compl_match_array[i].pum_text
				      == compl_shown_match->cp_text[CPT_ABBR])
	    {
		cur = i;
		break;
	    }
    }

    if (compl_match_array == NULL)
	return;

    // In Replace mode when a $ is displayed at the end of the line only
    // part of the screen would be updated.  We do need to redraw here.
    dollar_vcol = -1;

    // Compute the screen column of the start of the completed text.
    // Use the cursor to get all wrapping and other settings right.
    col = curwin->w_cursor.col;
    curwin->w_cursor.col = compl_col;
    pum_display(compl_match_array, compl_match_arraysize, cur);
    curwin->w_cursor.col = col;

#ifdef FEAT_EVAL
    if (has_completechanged())
	trigger_complete_changed_event(cur);
#endif
}

# define CTRL_X_LOCAL_MSG	15	// only used in "ctrl_x_msgs"

# define CTRL_X_EVAL		16	// for builtin function complete()

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.
    N_(" Command-line completion (^V^N^P)"),
};

static char *ctrl_x_mode_names[] = {
	"keyword",
	"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",
	"cmdline",
};

#define CPT_ABBR	0	// "abbr"

#define CPT_MENU	1	// "menu"

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

# define CP_ORIGINAL_TEXT   1	// the original text when the expansion begun

# define CP_CONT_S_IPOS	    4	// use CONT_S_IPOS for compl_cont_status

static char e_hitend[] = N_("Hit end of paragraph");

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;

static int	  compl_enter_selects = FALSE;

static char_u	  *compl_leader = NULL;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_used_match;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

static int	  ctrl_x_mode = CTRL_X_NORMAL;

static int	  compl_matches = 0;

static char_u	  *compl_pattern = NULL;

static int	  compl_direction = FORWARD;


set_buflocal_ofu_callback(buf_T *buf UNUSED)
{
# ifdef FEAT_EVAL
    copy_global_to_buflocal_cb(&ofu_cb, &buf->b_ofu_cb);
# endif
}

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

static void ins_compl_files(int count, char_u **files, int thesaurus, int flags, regmatch_T *regmatch, char_u *buf, int *dir);

static callback_T ofu_cb;

typedef struct
{
    char_u	*e_cpt;			// current entry in 'complete'
    buf_T	*ins_buf;		// buffer being scanned
    pos_T	*cur_match_pos;			// current match position
    pos_T	prev_match_pos;		// previous match position
    int		set_match_pos;		// save first_match_pos/last_match_pos
    pos_T	first_match_pos;	// first match position
    pos_T	last_match_pos;		// last match position
    int		found_all;		// found all matches of a certain type.
    char_u	*dict;			// dictionary file to search
    int		dict_f;			// "dict" is an exact file name or not
} ins_compl_next_state_T;


set_ref_in_insexpand_funcs(int copyID)
{
    int abort = FALSE;

    abort = set_ref_in_callback(&cfu_cb, copyID);
    abort = abort || set_ref_in_callback(&ofu_cb, copyID);
    abort = abort || set_ref_in_callback(&tsrfu_cb, copyID);

    return abort;
}

struct compl_S
{
    compl_T	*cp_next;
    compl_T	*cp_prev;
    char_u	*cp_str;	// matched text
    char_u	*(cp_text[CPT_COUNT]);	// text for the menu
#ifdef FEAT_EVAL
    typval_T	cp_user_data;
#endif
    char_u	*cp_fname;	// file containing the match, allocated when
				// cp_flags has CP_FREE_FNAME
    int		cp_flags;	// CP_ values
    int		cp_number;	// sequence number
}

static int	  compl_enter_selects = FALSE;

static int	  compl_get_longest = FALSE;

static int	  compl_no_insert = FALSE;

static int	  compl_no_select = FALSE;

static int	  compl_used_match;

static int	  compl_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

static int	  ctrl_x_mode = CTRL_X_NORMAL;

static int	  compl_matches = 0;

static int	  compl_direction = FORWARD;

static int	  compl_shows_dir = FORWARD;

static int	  compl_pending = 0;

static int	  compl_length = 0;

static int	  compl_cont_mode = 0;

static int	  compl_cont_status = 0;

static int	  compl_opt_refresh_always = FALSE;

static int	  compl_opt_suppress_empty = FALSE;

static int ins_compl_add(char_u *str, int len, char_u *fname, char_u **cptext, typval_T *user_data, int cdir, int flags, int adup);

static void ins_compl_files(int count, char_u **files, int thesaurus, int flags, regmatch_T *regmatch, char_u *buf, int *dir);

static int  ins_compl_need_restart(void);

static int  get_compl_len(void);

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);

static int  spell_bad_len = 0;

static int compl_match_arraysize;

static callback_T cfu_cb;

static callback_T ofu_cb;

static callback_T tsrfu_cb;

typedef struct
{
    char_u	*e_cpt;			// current entry in 'complete'
    buf_T	*ins_buf;		// buffer being scanned
    pos_T	*cur_match_pos;			// current match position
    pos_T	prev_match_pos;		// previous match position
    int		set_match_pos;		// save first_match_pos/last_match_pos
    pos_T	first_match_pos;	// first match position
    pos_T	last_match_pos;		// last match position
    int		found_all;		// found all matches of a certain type.
    char_u	*dict;			// dictionary file to search
    int		dict_f;			// "dict" is an exact file name or not
} ins_compl_next_state_T;