int ctrl_x_mode_dictionary(void) { return ctrl_x_mode == CTRL_X_DICTIONARY; }

# define CTRL_X_DICTIONARY	(9 + 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_update_sequence_numbers()
{
    int		number = 0;
    compl_T	*match;

    if (compl_dir_forward())
    {
	// search backwards for the first valid (!= -1) number.
	// This should normally succeed already at the first loop
	// cycle, so it's fast!
	for (match = compl_curr_match->cp_prev; match != NULL
		&& !is_first_match(match); match = match->cp_prev)
	    if (match->cp_number != -1)
	    {
		number = match->cp_number;
		break;
	    }
	if (match != NULL)
	    // go up and assign all numbers which are not assigned
	    // yet
	    for (match = match->cp_next;
		    match != NULL && match->cp_number == -1;
					   match = match->cp_next)
		match->cp_number = ++number;
    }
    else // BACKWARD
    {
	// search forwards (upwards) for the first valid (!= -1)
	// number.  This should normally succeed already at the
	// first loop cycle, so it's fast!
	for (match = compl_curr_match->cp_next; match != NULL
		&& !is_first_match(match); match = match->cp_next)
	    if (match->cp_number != -1)
	    {
		number = match->cp_number;
		break;
	    }
	if (match != NULL)
	    // go down and assign all numbers which are not
	    // assigned yet
	    for (match = match->cp_prev; match
		    && match->cp_number == -1;
					   match = match->cp_prev)
		match->cp_number = ++number;
    }
}

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.

# 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

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;


ins_compl_key2dir(int c)
{
    if (c == Ctrl_P || c == Ctrl_L
	    || c == K_PAGEUP || c == K_KPAGEUP || c == K_S_UP || c == K_UP)
	return BACKWARD;
    return FORWARD;
}

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;


ins_compl_dictionaries(
    char_u	*dict_start,
    char_u	*pat,
    int		flags,		// DICT_FIRST and/or DICT_EXACT
    int		thesaurus)	// Thesaurus completion
{
    char_u	*dict = dict_start;
    char_u	*ptr;
    char_u	*buf;
    regmatch_T	regmatch;
    char_u	**files;
    int		count;
    int		save_p_scs;
    int		dir = compl_direction;

    if (*dict == NUL)
    {
#ifdef FEAT_SPELL
	// When 'dictionary' is empty and spell checking is enabled use
	// "spell".
	if (!thesaurus && curwin->w_p_spell)
	    dict = (char_u *)"spell";
	else
#endif
	    return;
    }

    buf = alloc(LSIZE);
    if (buf == NULL)
	return;
    regmatch.regprog = NULL;	// so that we can goto theend

    // If 'infercase' is set, don't use 'smartcase' here
    save_p_scs = p_scs;
    if (curbuf->b_p_inf)
	p_scs = FALSE;

    // When invoked to match whole lines for CTRL-X CTRL-L adjust the pattern
    // to only match at the start of a line.  Otherwise just match the
    // pattern. Also need to double backslashes.
    if (ctrl_x_mode_line_or_eval())
    {
	char_u *pat_esc = vim_strsave_escaped(pat, (char_u *)"\\");
	size_t len;

	if (pat_esc == NULL)
	    goto theend;
	len = STRLEN(pat_esc) + 10;
	ptr = alloc(len);
	if (ptr == NULL)
	{
	    vim_free(pat_esc);
	    goto theend;
	}
	vim_snprintf((char *)ptr, len, "^\\s*\\zs\\V%s", pat_esc);
	regmatch.regprog = vim_regcomp(ptr, RE_MAGIC);
	vim_free(pat_esc);
	vim_free(ptr);
    }
    else
    {
	regmatch.regprog = vim_regcomp(pat, magic_isset() ? RE_MAGIC : 0);
	if (regmatch.regprog == NULL)
	    goto theend;
    }

    // ignore case depends on 'ignorecase', 'smartcase' and "pat"
    regmatch.rm_ic = ignorecase(pat);
    while (*dict != NUL && !got_int && !compl_interrupted)
    {
	// copy one dictionary file name into buf
	if (flags == DICT_EXACT)
	{
	    count = 1;
	    files = &dict;
	}
	else
	{
	    // Expand wildcards in the dictionary name, but do not allow
	    // backticks (for security, the 'dict' option may have been set in
	    // a modeline).
	    copy_option_part(&dict, buf, LSIZE, ",");
# ifdef FEAT_SPELL
	    if (!thesaurus && STRCMP(buf, "spell") == 0)
		count = -1;
	    else
# endif
		if (vim_strchr(buf, '`') != NULL
		    || expand_wildcards(1, &buf, &count, &files,
						     EW_FILE|EW_SILENT) != OK)
		count = 0;
	}

# ifdef FEAT_SPELL
	if (count == -1)
	{
	    // Complete from active spelling.  Skip "\<" in the pattern, we
	    // don't use it as a RE.
	    if (pat[0] == '\\' && pat[1] == '<')
		ptr = pat + 2;
	    else
		ptr = pat;
	    spell_dump_compl(ptr, regmatch.rm_ic, &dir, 0);
	}
	else
# endif
	    if (count > 0)	// avoid warning for using "files" uninit
	{
	    ins_compl_files(count, files, thesaurus, flags,
							&regmatch, buf, &dir);
	    if (flags != DICT_EXACT)
		FreeWild(count, files);
	}
	if (flags != 0)
	    break;
    }

theend:
    p_scs = save_p_scs;
    vim_regfree(regmatch.regprog);
    vim_free(buf);
}

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

# 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

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


set_completefunc_option(void)
{
    int	retval;

    retval = option_set_callback_func(curbuf->b_p_cfu, &cfu_cb);
    if (retval == OK)
	set_buflocal_cfu_callback(curbuf);

    return retval;
}

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

static callback_T cfu_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;


get_normal_compl_info(char_u *line, int startcol, colnr_T curs_col)
{
    if ((compl_cont_status & CONT_SOL) || ctrl_x_mode_path_defines())
    {
	if (!compl_status_adding())
	{
	    while (--startcol >= 0 && vim_isIDc(line[startcol]))
		;
	    compl_col += ++startcol;
	    compl_length = curs_col - startcol;
	}
	if (p_ic)
	    compl_pattern = str_foldcase(line + compl_col,
		    compl_length, NULL, 0);
	else
	    compl_pattern = vim_strnsave(line + compl_col, compl_length);
	if (compl_pattern == NULL)
	    return FAIL;
    }
    else if (compl_status_adding())
    {
	char_u	    *prefix = (char_u *)"\\<";

	// we need up to 2 extra chars for the prefix
	compl_pattern = alloc(quote_meta(NULL, line + compl_col,
		    compl_length) + 2);
	if (compl_pattern == NULL)
	    return FAIL;
	if (!vim_iswordp(line + compl_col)
		|| (compl_col > 0
		    && (vim_iswordp(mb_prevptr(line, line + compl_col)))))
	    prefix = (char_u *)"";
	STRCPY((char *)compl_pattern, prefix);
	(void)quote_meta(compl_pattern + STRLEN(prefix),
		line + compl_col, compl_length);
    }
    else if (--startcol < 0
	    || !vim_iswordp(mb_prevptr(line, line + startcol + 1)))
    {
	// Match any word of at least two chars
	compl_pattern = vim_strsave((char_u *)"\\<\\k\\k");
	if (compl_pattern == NULL)
	    return FAIL;
	compl_col += curs_col;
	compl_length = 0;
    }
    else
    {
	// Search the point of change class of multibyte character
	// or not a word single byte character backward.
	if (has_mbyte)
	{
	    int base_class;
	    int head_off;

	    startcol -= (*mb_head_off)(line, line + startcol);
	    base_class = mb_get_class(line + startcol);
	    while (--startcol >= 0)
	    {
		head_off = (*mb_head_off)(line, line + startcol);
		if (base_class != mb_get_class(line + startcol
			    - head_off))
		    break;
		startcol -= head_off;
	    }
	}
	else
	    while (--startcol >= 0 && vim_iswordc(line[startcol]))
		;
	compl_col += ++startcol;
	compl_length = (int)curs_col - startcol;
	if (compl_length == 1)
	{
	    // Only match word with at least two chars -- webb
	    // there's no need to call quote_meta,
	    // alloc(7) is enough  -- Acevedo
	    compl_pattern = alloc(7);
	    if (compl_pattern == NULL)
		return FAIL;
	    STRCPY((char *)compl_pattern, "\\<");
	    (void)quote_meta(compl_pattern + 2, line + compl_col, 1);
	    STRCAT((char *)compl_pattern, "\\k");
	}
	else
	{
	    compl_pattern = alloc(quote_meta(NULL, line + compl_col,
			compl_length) + 2);
	    if (compl_pattern == NULL)
		return FAIL;
	    STRCPY((char *)compl_pattern, "\\<");
	    (void)quote_meta(compl_pattern + 2, line + compl_col,
		    compl_length);
	}
    }

    return OK;
}

# 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

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 colnr_T	  compl_col = 0;


int ctrl_x_mode_none(void) { return ctrl_x_mode == 0; }

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;

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;


completeopt_was_set(void)
{
    compl_no_insert = FALSE;
    compl_no_select = FALSE;
    if (strstr((char *)p_cot, "noselect") != NULL)
	compl_no_select = TRUE;
    if (strstr((char *)p_cot, "noinsert") != NULL)
	compl_no_insert = TRUE;
}

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 CP_EQUAL	    8	// ins_compl_equal() always returns TRUE

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_was_interrupted = FALSE;

static int	  compl_interrupted = FALSE;

static int	  compl_restarting = FALSE;

static int	  compl_started = FALSE;

static char_u	  *compl_pattern = NULL;

static char_u	  *compl_orig_text = NULL;

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

static pumitem_T *compl_match_array = NULL;


f_complete_info(typval_T *argvars, typval_T *rettv)
{
    list_T	*what_list = NULL;

    if (rettv_dict_alloc(rettv) == FAIL)
	return;

    if (in_vim9script() && check_for_opt_list_arg(argvars, 0) == FAIL)
	return;

    if (argvars[0].v_type != VAR_UNKNOWN)
    {
	if (argvars[0].v_type != VAR_LIST)
	{
	    emsg(_(e_list_required));
	    return;
	}
	what_list = argvars[0].vval.v_list;
    }
    get_complete_info(what_list, rettv->vval.v_dict);
}

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 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 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_add_list(list_T *list);

static pumitem_T *compl_match_array = NULL;


get_cmdline_compl_info(char_u *line, colnr_T curs_col)
{
    compl_pattern = vim_strnsave(line, curs_col);
    if (compl_pattern == NULL)
	return FAIL;
    set_cmd_context(&compl_xp, compl_pattern,
	    (int)STRLEN(compl_pattern), curs_col, FALSE);
    if (compl_xp.xp_context == EXPAND_UNSUCCESSFUL
	    || compl_xp.xp_context == EXPAND_NOTHING)
	// No completion possible, use an empty pattern to get a
	// "pattern not found" message.
	compl_col = curs_col;
    else
	compl_col = (int)(compl_xp.xp_pattern - compl_pattern);
    compl_length = curs_col - compl_col;

    return OK;
}

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

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

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

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;


int ctrl_x_mode_normal(void) { return ctrl_x_mode == CTRL_X_NORMAL; }

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

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
}

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


ins_compl_mode(void)
{
    if (ctrl_x_mode_not_defined_yet() || ctrl_x_mode_scroll() || compl_started)
	return (char_u *)ctrl_x_mode_names[ctrl_x_mode & ~CTRL_X_WANT_IDENT];

    return (char_u *)"";
}

# 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_MSG(i) ctrl_x_msgs[(i) & ~CTRL_X_WANT_IDENT]

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_u	  *compl_leader = NULL;

static int	  compl_started = FALSE;

static int	  ctrl_x_mode = CTRL_X_NORMAL;

static char_u	  *compl_pattern = NULL;

static char_u	  *compl_orig_text = NULL;

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

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 char_u *find_line_end(char_u *ptr);

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 unsigned  quote_meta(char_u *dest, char_u *str, int len);

static void spell_back_to_badword(void);


get_next_completion_match(int type, ins_compl_next_state_T *st, pos_T *ini)
{
    int	found_new_match = FALSE;

    switch (type)
    {
	case -1:
	    break;
#ifdef FEAT_FIND_ID
	case CTRL_X_PATH_PATTERNS:
	case CTRL_X_PATH_DEFINES:
	    get_next_include_file_completion(type);
	    break;
#endif

	case CTRL_X_DICTIONARY:
	case CTRL_X_THESAURUS:
	    get_next_dict_tsr_completion(type, st->dict, st->dict_f);
	    st->dict = NULL;
	    break;

	case CTRL_X_TAGS:
	    get_next_tag_completion();
	    break;

	case CTRL_X_FILES:
	    get_next_filename_completion();
	    break;

	case CTRL_X_CMDLINE:
	case CTRL_X_CMDLINE_CTRL_X:
	    get_next_cmdline_completion();
	    break;

#ifdef FEAT_COMPL_FUNC
	case CTRL_X_FUNCTION:
	case CTRL_X_OMNI:
	    expand_by_function(type, compl_pattern);
	    break;
#endif

	case CTRL_X_SPELL:
	    get_next_spell_completion(st->first_match_pos.lnum);
	    break;

	default:	// normal ^P/^N and ^X^L
	    found_new_match = get_next_default_completion(st, ini);
	    if (found_new_match == FAIL && st->ins_buf == curbuf)
		st->found_all = TRUE;
    }

    // check if compl_curr_match has changed, (e.g. other type of
    // expansion added something)
    if (type != 0 && compl_curr_match != compl_old_match)
	found_new_match = OK;

    return found_new_match;
}

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

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

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;


ins_compl_del_pum(void)
{
    if (compl_match_array == NULL)
	return;

    pum_undisplay();
    VIM_CLEAR(compl_match_array);
}

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",
};

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 char_u	  *compl_leader = NULL;

static char_u	  *compl_pattern = NULL;

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 pumitem_T *compl_match_array = NULL;


get_next_include_file_completion(int compl_type)
{
    find_pattern_in_path(compl_pattern, compl_direction,
	    (int)STRLEN(compl_pattern), FALSE, FALSE,
	    (compl_type == CTRL_X_PATH_DEFINES
	     && !(compl_cont_status & CONT_SOL))
	    ? FIND_DEFINE : FIND_ANY, 1L, ACTION_EXPAND,
	    (linenr_T)1, (linenr_T)MAXLNUM);
}

# define CTRL_X_PATH_DEFINES	(7 + 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
}

# 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_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 int	  compl_cont_mode = 0;

static int	  compl_cont_status = 0;

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

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_longest_match(compl_T *match)
{
    char_u	*p, *s;
    int		c1, c2;
    int		had_match;

    if (compl_leader == NULL)
    {
	// First match, use it as a whole.
	compl_leader = vim_strsave(match->cp_str);
	if (compl_leader == NULL)
	    return;

	had_match = (curwin->w_cursor.col > compl_col);
	ins_compl_delete();
	ins_bytes(compl_leader + get_compl_len());
	ins_redraw(FALSE);

	// When the match isn't there (to avoid matching itself) remove it
	// again after redrawing.
	if (!had_match)
	    ins_compl_delete();
	compl_used_match = FALSE;

	return;
    }

    // Reduce the text if this match differs from compl_leader.
    p = compl_leader;
    s = match->cp_str;
    while (*p != NUL)
    {
	if (has_mbyte)
	{
	    c1 = mb_ptr2char(p);
	    c2 = mb_ptr2char(s);
	}
	else
	{
	    c1 = *p;
	    c2 = *s;
	}
	if ((match->cp_flags & CP_ICASE)
		? (MB_TOLOWER(c1) != MB_TOLOWER(c2)) : (c1 != c2))
	    break;
	if (has_mbyte)
	{
	    MB_PTR_ADV(p);
	    MB_PTR_ADV(s);
	}
	else
	{
	    ++p;
	    ++s;
	}
    }

    if (*p != NUL)
    {
	// Leader was shortened, need to change the inserted text.
	*p = NUL;
	had_match = (curwin->w_cursor.col > compl_col);
	ins_compl_delete();
	ins_bytes(compl_leader + get_compl_len());
	ins_redraw(FALSE);

	// When the match isn't there (to avoid matching itself) remove it
	// again after redrawing.
	if (!had_match)
	    ins_compl_delete();
    }

    compl_used_match = FALSE;
}

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_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	  compl_shows_dir = FORWARD;

static int	  compl_pending = 0;

static int	  compl_length = 0;


ins_compl_make_cyclic(void)
{
    compl_T *match;
    int	    count = 0;

    if (compl_first_match == NULL)
	return 0;

    // Find the end of the list.
    match = compl_first_match;
    // there's always an entry for the compl_orig_text, it doesn't count.
    while (match->cp_next != NULL && !is_first_match(match->cp_next))
    {
	match = match->cp_next;
	++count;
    }
    match->cp_next = compl_first_match;
    compl_first_match->cp_prev = match;

    return count;
}

# 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

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;


ins_compl_accept_char(int c)
{
    if (ctrl_x_mode & CTRL_X_WANT_IDENT)
	// When expanding an identifier only accept identifier chars.
	return vim_isIDc(c);

    switch (ctrl_x_mode)
    {
	case CTRL_X_FILES:
	    // When expanding file name only accept file name chars. But not
	    // path separators, so that "proto/<Tab>" expands files in
	    // "proto", not "proto/" as a whole
	    return vim_isfilec(c) && !vim_ispathsep(c);

	case CTRL_X_CMDLINE:
	case CTRL_X_CMDLINE_CTRL_X:
	case CTRL_X_OMNI:
	    // Command line and Omni completion can work with just about any
	    // printable character, but do stop at white space.
	    return vim_isprintc(c) && !VIM_ISWHITE(c);

	case CTRL_X_WHOLE_LINE:
	    // For while line completion a space can be part of the line.
	    return vim_isprintc(c);
    }
    return vim_iswordc(c);
}

# define CTRL_X_WANT_IDENT	0x100

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

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

# define CTRL_X_THESAURUS	(10 + CTRL_X_WANT_IDENT)

# define CTRL_X_CMDLINE		11

# define CTRL_X_OMNI		13

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

# 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",
};

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


int ctrl_x_mode_tags(void) { return ctrl_x_mode == CTRL_X_TAGS; }

# define CTRL_X_TAGS		(5 + 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;


copy_global_to_buflocal_cb(callback_T *globcb, callback_T *bufcb)
{
    free_callback(bufcb);
    if (globcb->cb_name != NULL && *globcb->cb_name != NUL)
	copy_callback(bufcb, globcb);
}

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",
};

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 char_u	  *compl_leader = NULL;

static char_u	  *compl_pattern = NULL;

static char_u	  *compl_orig_text = NULL;

static pumitem_T *compl_match_array = NULL;

static callback_T cfu_cb;

static callback_T ofu_cb;

static callback_T tsrfu_cb;


int ctrl_x_mode_files(void) { return ctrl_x_mode == CTRL_X_FILES; }

# define CTRL_X_FILES		4

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;


set_buflocal_cfu_callback(buf_T *buf UNUSED)
{
# ifdef FEAT_EVAL
    copy_global_to_buflocal_cb(&cfu_cb, &buf->b_cfu_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 cfu_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;


ins_compl_len(void)
{
    return compl_length;
}

static int	  compl_length = 0;

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


ssize_t pcnet_receive(NetClientState *nc, const uint8_t *buf, size_t size_)
{
    PCNetState *s = qemu_get_nic_opaque(nc);
    int is_padr = 0, is_bcast = 0, is_ladr = 0;
    uint8_t buf1[60];
    int remaining;
    int crc_err = 0;
    int size = size_;

    if (CSR_DRX(s) || CSR_STOP(s) || CSR_SPND(s) || !size ||
        (CSR_LOOP(s) && !s->looptest)) {
        return -1;
    }
#ifdef PCNET_DEBUG
    printf("pcnet_receive size=%d\n", size);
#endif

    /* if too small buffer, then expand it */
    if (size < MIN_BUF_SIZE) {
        memcpy(buf1, buf, size);
        memset(buf1 + size, 0, MIN_BUF_SIZE - size);
        buf = buf1;
        size = MIN_BUF_SIZE;
    }

    if (CSR_PROM(s)
        || (is_padr=padr_match(s, buf, size))
        || (is_bcast=padr_bcast(s, buf, size))
        || (is_ladr=ladr_match(s, buf, size))) {

        pcnet_rdte_poll(s);

        if (!(CSR_CRST(s) & 0x8000) && s->rdra) {
            struct pcnet_RMD rmd;
            int rcvrc = CSR_RCVRC(s)-1,i;
            hwaddr nrda;
            for (i = CSR_RCVRL(s)-1; i > 0; i--, rcvrc--) {
                if (rcvrc <= 1)
                    rcvrc = CSR_RCVRL(s);
                nrda = s->rdra +
                    (CSR_RCVRL(s) - rcvrc) *
                    (BCR_SWSTYLE(s) ? 16 : 8 );
                RMDLOAD(&rmd, nrda);
                if (GET_FIELD(rmd.status, RMDS, OWN)) {
#ifdef PCNET_DEBUG_RMD
                    printf("pcnet - scan buffer: RCVRC=%d PREV_RCVRC=%d\n",
                                rcvrc, CSR_RCVRC(s));
#endif
                    CSR_RCVRC(s) = rcvrc;
                    pcnet_rdte_poll(s);
                    break;
                }
            }
        }

        if (!(CSR_CRST(s) & 0x8000)) {
#ifdef PCNET_DEBUG_RMD
            printf("pcnet - no buffer: RCVRC=%d\n", CSR_RCVRC(s));
#endif
            s->csr[0] |= 0x1000; /* Set MISS flag */
            CSR_MISSC(s)++;
        } else {
            uint8_t *src = s->buffer;
            hwaddr crda = CSR_CRDA(s);
            struct pcnet_RMD rmd;
            int pktcount = 0;

            if (!s->looptest) {
                memcpy(src, buf, size);
                /* no need to compute the CRC */
                src[size] = 0;
                src[size + 1] = 0;
                src[size + 2] = 0;
                src[size + 3] = 0;
                size += 4;
            } else if (s->looptest == PCNET_LOOPTEST_CRC ||
                       !CSR_DXMTFCS(s) || size < MIN_BUF_SIZE+4) {
                uint32_t fcs = ~0;
                uint8_t *p = src;

                while (p != &src[size])
                    CRC(fcs, *p++);
                *(uint32_t *)p = htonl(fcs);
                size += 4;
            } else {
                uint32_t fcs = ~0;
                uint8_t *p = src;

                while (p != &src[size])
                    CRC(fcs, *p++);
                crc_err = (*(uint32_t *)p != htonl(fcs));
            }

#ifdef PCNET_DEBUG_MATCH
            PRINT_PKTHDR(buf);
#endif

            RMDLOAD(&rmd, PHYSADDR(s,crda));
            /*if (!CSR_LAPPEN(s))*/
                SET_FIELD(&rmd.status, RMDS, STP, 1);

#define PCNET_RECV_STORE() do {                                 \
    int count = MIN(4096 - GET_FIELD(rmd.buf_length, RMDL, BCNT),remaining); \
    hwaddr rbadr = PHYSADDR(s, rmd.rbadr);          \
    s->phys_mem_write(s->dma_opaque, rbadr, src, count, CSR_BSWP(s)); \
    src += count; remaining -= count;                           \
    SET_FIELD(&rmd.status, RMDS, OWN, 0);                       \
    RMDSTORE(&rmd, PHYSADDR(s,crda));                           \
    pktcount++;                                                 \
} while (0)

            remaining = size;
            PCNET_RECV_STORE();
            if ((remaining > 0) && CSR_NRDA(s)) {
                hwaddr nrda = CSR_NRDA(s);
#ifdef PCNET_DEBUG_RMD
                PRINT_RMD(&rmd);
#endif
                RMDLOAD(&rmd, PHYSADDR(s,nrda));
                if (GET_FIELD(rmd.status, RMDS, OWN)) {
                    crda = nrda;
                    PCNET_RECV_STORE();
#ifdef PCNET_DEBUG_RMD
                    PRINT_RMD(&rmd);
#endif
                    if ((remaining > 0) && (nrda=CSR_NNRD(s))) {
                        RMDLOAD(&rmd, PHYSADDR(s,nrda));
                        if (GET_FIELD(rmd.status, RMDS, OWN)) {
                            crda = nrda;
                            PCNET_RECV_STORE();
                        }
                    }
                }
            }

#undef PCNET_RECV_STORE

            RMDLOAD(&rmd, PHYSADDR(s,crda));
            if (remaining == 0) {
                SET_FIELD(&rmd.msg_length, RMDM, MCNT, size);
                SET_FIELD(&rmd.status, RMDS, ENP, 1);
                SET_FIELD(&rmd.status, RMDS, PAM, !CSR_PROM(s) && is_padr);
                SET_FIELD(&rmd.status, RMDS, LFAM, !CSR_PROM(s) && is_ladr);
                SET_FIELD(&rmd.status, RMDS, BAM, !CSR_PROM(s) && is_bcast);
                if (crc_err) {
                    SET_FIELD(&rmd.status, RMDS, CRC, 1);
                    SET_FIELD(&rmd.status, RMDS, ERR, 1);
                }
            } else {
                SET_FIELD(&rmd.status, RMDS, OFLO, 1);
                SET_FIELD(&rmd.status, RMDS, BUFF, 1);
                SET_FIELD(&rmd.status, RMDS, ERR, 1);
            }
            RMDSTORE(&rmd, PHYSADDR(s,crda));
            s->csr[0] |= 0x0400;

#ifdef PCNET_DEBUG
            printf("RCVRC=%d CRDA=0x%08x BLKS=%d\n",
                CSR_RCVRC(s), PHYSADDR(s,CSR_CRDA(s)), pktcount);
#endif
#ifdef PCNET_DEBUG_RMD
            PRINT_RMD(&rmd);
#endif

            while (pktcount--) {
                if (CSR_RCVRC(s) <= 1)
                    CSR_RCVRC(s) = CSR_RCVRL(s);
                else
                    CSR_RCVRC(s)--;
            }

            pcnet_rdte_poll(s);

        }
    }

    pcnet_poll(s);
    pcnet_update_irq(s);

    return size_;
}

net: pcnet: add check to validate receive data size(CVE-2015-7504)

In loopback mode, pcnet_receive routine appends CRC code to the
receive buffer. If the data size given is same as the buffer size,
the appended CRC code overwrites 4 bytes after s->buffer. Added a
check to avoid that.

Reported by: Qinghao Tang <luodalongde@gmail.com>
Cc: qemu-stable@nongnu.org
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Prasad J Pandit <pjp@fedoraproject.org>
Signed-off-by: Jason Wang <jasowang@redhat.com>

struct qemu_ether_header {
    uint8_t ether_dhost[6];
    uint8_t ether_shost[6];
    uint16_t ether_type;
}

#define CSR_INIT(S)      !!(((S)->csr[0])&0x0001)

#define CSR_STRT(S)      !!(((S)->csr[0])&0x0002)

#define CSR_STOP(S)      !!(((S)->csr[0])&0x0004)

#define CSR_TDMD(S)      !!(((S)->csr[0])&0x0008)

#define CSR_TXON(S)      !!(((S)->csr[0])&0x0010)

#define CSR_RXON(S)      !!(((S)->csr[0])&0x0020)

#define CSR_INEA(S)      !!(((S)->csr[0])&0x0040)

#define CSR_BSWP(S)      !!(((S)->csr[3])&0x0004)

#define CSR_LAPPEN(S)    !!(((S)->csr[3])&0x0020)

#define CSR_DXSUFLO(S)   !!(((S)->csr[3])&0x0040)

#define CSR_ASTRP_RCV(S) !!(((S)->csr[4])&0x0800)

#define CSR_DPOLL(S)     !!(((S)->csr[4])&0x1000)

#define CSR_SPND(S)      !!(((S)->csr[5])&0x0001)

#define CSR_LTINTEN(S)   !!(((S)->csr[5])&0x4000)

#define CSR_TOKINTD(S)   !!(((S)->csr[5])&0x8000)

#define CSR_DRX(S)       !!(((S)->csr[15])&0x0001)

#define CSR_DTX(S)       !!(((S)->csr[15])&0x0002)

#define CSR_LOOP(S)      !!(((S)->csr[15])&0x0004)

#define CSR_DXMTFCS(S)   !!(((S)->csr[15])&0x0008)

#define CSR_INTL(S)      !!(((S)->csr[15])&0x0040)

#define CSR_DRCVPA(S)    !!(((S)->csr[15])&0x2000)

#define CSR_DRCVBC(S)    !!(((S)->csr[15])&0x4000)

#define CSR_PROM(S)      !!(((S)->csr[15])&0x8000)

#define CSR_CRBC(S)      ((S)->csr[40])

#define CSR_CRST(S)      ((S)->csr[41])

#define CSR_CXBC(S)      ((S)->csr[42])

#define CSR_CXST(S)      ((S)->csr[43])

#define CSR_NRBC(S)      ((S)->csr[44])

#define CSR_NRST(S)      ((S)->csr[45])

#define CSR_POLL(S)      ((S)->csr[46])

#define CSR_PINT(S)      ((S)->csr[47])

#define CSR_RCVRC(S)     ((S)->csr[72])

#define CSR_XMTRC(S)     ((S)->csr[74])

#define CSR_RCVRL(S)     ((S)->csr[76])

#define CSR_XMTRL(S)     ((S)->csr[78])

#define CSR_MISSC(S)     ((S)->csr[112])


reg_write(struct pcilynx *lynx, int offset, u32 data)
{
	writel(data, lynx->registers + offset);
}

firewire: nosy: Fix a use-after-free bug in nosy_ioctl()

For each device, the nosy driver allocates a pcilynx structure.
A use-after-free might happen in the following scenario:

 1. Open nosy device for the first time and call ioctl with command
    NOSY_IOC_START, then a new client A will be malloced and added to
    doubly linked list.
 2. Open nosy device for the second time and call ioctl with command
    NOSY_IOC_START, then a new client B will be malloced and added to
    doubly linked list.
 3. Call ioctl with command NOSY_IOC_START for client A, then client A
    will be readded to the doubly linked list. Now the doubly linked
    list is messed up.
 4. Close the first nosy device and nosy_release will be called. In
    nosy_release, client A will be unlinked and freed.
 5. Close the second nosy device, and client A will be referenced,
    resulting in UAF.

The root cause of this bug is that the element in the doubly linked list
is reentered into the list.

Fix this bug by adding a check before inserting a client.  If a client
is already in the linked list, don't insert it.

The following KASAN report reveals it:

   BUG: KASAN: use-after-free in nosy_release+0x1ea/0x210
   Write of size 8 at addr ffff888102ad7360 by task poc
   CPU: 3 PID: 337 Comm: poc Not tainted 5.12.0-rc5+ #6
   Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.12.0-59-gc9ba5276e321-prebuilt.qemu.org 04/01/2014
   Call Trace:
     nosy_release+0x1ea/0x210
     __fput+0x1e2/0x840
     task_work_run+0xe8/0x180
     exit_to_user_mode_prepare+0x114/0x120
     syscall_exit_to_user_mode+0x1d/0x40
     entry_SYSCALL_64_after_hwframe+0x44/0xae

   Allocated by task 337:
     nosy_open+0x154/0x4d0
     misc_open+0x2ec/0x410
     chrdev_open+0x20d/0x5a0
     do_dentry_open+0x40f/0xe80
     path_openat+0x1cf9/0x37b0
     do_filp_open+0x16d/0x390
     do_sys_openat2+0x11d/0x360
     __x64_sys_open+0xfd/0x1a0
     do_syscall_64+0x33/0x40
     entry_SYSCALL_64_after_hwframe+0x44/0xae

   Freed by task 337:
     kfree+0x8f/0x210
     nosy_release+0x158/0x210
     __fput+0x1e2/0x840
     task_work_run+0xe8/0x180
     exit_to_user_mode_prepare+0x114/0x120
     syscall_exit_to_user_mode+0x1d/0x40
     entry_SYSCALL_64_after_hwframe+0x44/0xae

   The buggy address belongs to the object at ffff888102ad7300 which belongs to the cache kmalloc-128 of size 128
   The buggy address is located 96 bytes inside of 128-byte region [ffff888102ad7300, ffff888102ad7380)

[ Modified to use 'list_empty()' inside proper lock  - Linus ]

Link: https://lore.kernel.org/lkml/1617433116-5930-1-git-send-email-zheyuma97@gmail.com/
Reported-and-tested-by: 马哲宇 (Zheyu Ma) <zheyuma97@gmail.com>
Signed-off-by: Zheyu Ma <zheyuma97@gmail.com>
Cc: Greg Kroah-Hartman <greg@kroah.com>
Cc: Stefan Richter <stefanr@s5r6.in-berlin.de>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

struct pcl {
	__le32 next;
	__le32 async_error_next;
	u32 user_data;
	__le32 pcl_status;
	__le32 remaining_transfer_count;
	__le32 next_data_buffer;
	struct {
		__le32 control;
		__le32 pointer;
	} buffer[13];
}

struct packet {
	unsigned int length;
	char data[];
}

struct packet_buffer {
	char *data;
	size_t capacity;
	long total_packet_count, lost_packet_count;
	atomic_t size;
	struct packet *head, *tail;
	wait_queue_head_t wait;
}

struct pcilynx {
	struct pci_dev *pci_device;
	__iomem char *registers;

	struct pcl *rcv_start_pcl, *rcv_pcl;
	__le32 *rcv_buffer;

	dma_addr_t rcv_start_pcl_bus, rcv_pcl_bus, rcv_buffer_bus;

	spinlock_t client_list_lock;
	struct list_head client_list;

	struct miscdevice misc;
	struct list_head link;
	struct kref kref;
}

struct client {
	struct pcilynx *lynx;
	u32 tcode_mask;
	struct packet_buffer buffer;
	struct list_head link;
}

static const struct file_operations nosy_ops = {
	.owner =		THIS_MODULE,
	.read =			nosy_read,
	.unlocked_ioctl =	nosy_ioctl,
	.poll =			nosy_poll,
	.open =			nosy_open,
	.release =		nosy_release,
};

static struct pci_device_id pci_table[] = {
	{
		.vendor =    PCI_VENDOR_ID_TI,
		.device =    PCI_DEVICE_ID_TI_PCILYNX,
		.subvendor = PCI_ANY_ID,
		.subdevice = PCI_ANY_ID,
	},
	{ }	/* Terminating entry */
};

static struct pci_driver lynx_pci_driver = {
	.name =		driver_name,
	.id_table =	pci_table,
	.probe =	add_card,
	.remove =	remove_card,
};


packet_buffer_get(struct client *client, char __user *data, size_t user_length)
{
	struct packet_buffer *buffer = &client->buffer;
	size_t length;
	char *end;

	if (wait_event_interruptible(buffer->wait,
				     atomic_read(&buffer->size) > 0) ||
				     list_empty(&client->lynx->link))
		return -ERESTARTSYS;

	if (atomic_read(&buffer->size) == 0)
		return -ENODEV;

	/* FIXME: Check length <= user_length. */

	end = buffer->data + buffer->capacity;
	length = buffer->head->length;

	if (&buffer->head->data[length] < end) {
		if (copy_to_user(data, buffer->head->data, length))
			return -EFAULT;
		buffer->head = (struct packet *) &buffer->head->data[length];
	} else {
		size_t split = end - buffer->head->data;

		if (copy_to_user(data, buffer->head->data, split))
			return -EFAULT;
		if (copy_to_user(data + split, buffer->data, length - split))
			return -EFAULT;
		buffer->head = (struct packet *) &buffer->data[length - split];
	}

	/*
	 * Decrease buffer->size as the last thing, since this is what
	 * keeps the interrupt from overwriting the packet we are
	 * retrieving from the buffer.
	 */
	atomic_sub(sizeof(struct packet) + length, &buffer->size);

	return length;
}

static char driver_name[] = KBUILD_MODNAME;

struct pcl {
	__le32 next;
	__le32 async_error_next;
	u32 user_data;
	__le32 pcl_status;
	__le32 remaining_transfer_count;
	__le32 next_data_buffer;
	struct {
		__le32 control;
		__le32 pointer;
	} buffer[13];
}

struct packet {
	unsigned int length;
	char data[];
}

struct packet_buffer {
	char *data;
	size_t capacity;
	long total_packet_count, lost_packet_count;
	atomic_t size;
	struct packet *head, *tail;
	wait_queue_head_t wait;
}

struct pcilynx {
	struct pci_dev *pci_device;
	__iomem char *registers;

	struct pcl *rcv_start_pcl, *rcv_pcl;
	__le32 *rcv_buffer;

	dma_addr_t rcv_start_pcl_bus, rcv_pcl_bus, rcv_buffer_bus;

	spinlock_t client_list_lock;
	struct list_head client_list;

	struct miscdevice misc;
	struct list_head link;
	struct kref kref;
}

struct client {
	struct pcilynx *lynx;
	u32 tcode_mask;
	struct packet_buffer buffer;
	struct list_head link;
}

static const struct file_operations nosy_ops = {
	.owner =		THIS_MODULE,
	.read =			nosy_read,
	.unlocked_ioctl =	nosy_ioctl,
	.poll =			nosy_poll,
	.open =			nosy_open,
	.release =		nosy_release,
};

static struct pci_device_id pci_table[] = {
	{
		.vendor =    PCI_VENDOR_ID_TI,
		.device =    PCI_DEVICE_ID_TI_PCILYNX,
		.subvendor = PCI_ANY_ID,
		.subdevice = PCI_ANY_ID,
	},
	{ }	/* Terminating entry */
};

static struct pci_driver lynx_pci_driver = {
	.name =		driver_name,
	.id_table =	pci_table,
	.probe =	add_card,
	.remove =	remove_card,
};


lynx_get(struct pcilynx *lynx)
{
	kref_get(&lynx->kref);

	return lynx;
}


set_phy_reg(struct pcilynx *lynx, int addr, int val)
{
	if (addr > 15) {
		dev_err(&lynx->pci_device->dev,
			"PHY register address %d out of range\n", addr);
		return -1;
	}
	if (val > 0xff) {
		dev_err(&lynx->pci_device->dev,
			"PHY register value %d out of range\n", val);
		return -1;
	}
	reg_write(lynx, LINK_PHY, LINK_PHY_WRITE |
		  LINK_PHY_ADDR(addr) | LINK_PHY_WDATA(val));

	return 0;
}


nosy_release(struct inode *inode, struct file *file)
{
	struct client *client = file->private_data;
	struct pcilynx *lynx = client->lynx;

	spin_lock_irq(&lynx->client_list_lock);
	list_del_init(&client->link);
	spin_unlock_irq(&lynx->client_list_lock);

	packet_buffer_destroy(&client->buffer);
	kfree(client);
	lynx_put(lynx);

	return 0;
}


reg_set_bits(struct pcilynx *lynx, int offset, u32 mask)
{
	reg_write(lynx, offset, (reg_read(lynx, offset) | mask));
}


run_pcl(struct pcilynx *lynx, dma_addr_t pcl_bus,
			   int dmachan)
{
	reg_write(lynx, DMA0_CURRENT_PCL + dmachan * 0x20, pcl_bus);
	reg_write(lynx, DMA0_CHAN_CTRL + dmachan * 0x20,
		  DMA_CHAN_CTRL_ENABLE | DMA_CHAN_CTRL_LINK);
}


packet_buffer_destroy(struct packet_buffer *buffer)
{
	kfree(buffer->data);
}


nosy_read(struct file *file, char __user *buffer, size_t count, loff_t *offset)
{
	struct client *client = file->private_data;

	return packet_buffer_get(client, buffer, count);
}


lynx_release(struct kref *kref)
{
	kfree(container_of(kref, struct pcilynx, kref));
}


packet_buffer_init(struct packet_buffer *buffer, size_t capacity)
{
	buffer->data = kmalloc(capacity, GFP_KERNEL);
	if (buffer->data == NULL)
		return -ENOMEM;
	buffer->head = (struct packet *) buffer->data;
	buffer->tail = (struct packet *) buffer->data;
	buffer->capacity = capacity;
	buffer->lost_packet_count = 0;
	atomic_set(&buffer->size, 0);
	init_waitqueue_head(&buffer->wait);

	return 0;
}


reg_read(struct pcilynx *lynx, int offset)
{
	return readl(lynx->registers + offset);
}


lynx_put(struct pcilynx *lynx)
{
	kref_put(&lynx->kref, lynx_release);
}


nosy_open(struct inode *inode, struct file *file)
{
	int minor = iminor(inode);
	struct client *client;
	struct pcilynx *tmp, *lynx = NULL;

	mutex_lock(&card_mutex);
	list_for_each_entry(tmp, &card_list, link)
		if (tmp->misc.minor == minor) {
			lynx = lynx_get(tmp);
			break;
		}
	mutex_unlock(&card_mutex);
	if (lynx == NULL)
		return -ENODEV;

	client = kmalloc(sizeof *client, GFP_KERNEL);
	if (client == NULL)
		goto fail;

	client->tcode_mask = ~0;
	client->lynx = lynx;
	INIT_LIST_HEAD(&client->link);

	if (packet_buffer_init(&client->buffer, 128 * 1024) < 0)
		goto fail;

	file->private_data = client;

	return stream_open(inode, file);
fail:
	kfree(client);
	lynx_put(lynx);

	return -ENOMEM;
}

struct pcl {
	__le32 next;
	__le32 async_error_next;
	u32 user_data;
	__le32 pcl_status;
	__le32 remaining_transfer_count;
	__le32 next_data_buffer;
	struct {
		__le32 control;
		__le32 pointer;
	} buffer[13];
}

struct packet {
	unsigned int length;
	char data[];
}

struct packet_buffer {
	char *data;
	size_t capacity;
	long total_packet_count, lost_packet_count;
	atomic_t size;
	struct packet *head, *tail;
	wait_queue_head_t wait;
}

struct pcilynx {
	struct pci_dev *pci_device;
	__iomem char *registers;

	struct pcl *rcv_start_pcl, *rcv_pcl;
	__le32 *rcv_buffer;

	dma_addr_t rcv_start_pcl_bus, rcv_pcl_bus, rcv_buffer_bus;

	spinlock_t client_list_lock;
	struct list_head client_list;

	struct miscdevice misc;
	struct list_head link;
	struct kref kref;
}

struct client {
	struct pcilynx *lynx;
	u32 tcode_mask;
	struct packet_buffer buffer;
	struct list_head link;
}

static DEFINE_MUTEX(card_mutex);

static LIST_HEAD(card_list);

static const struct file_operations nosy_ops = {
	.owner =		THIS_MODULE,
	.read =			nosy_read,
	.unlocked_ioctl =	nosy_ioctl,
	.poll =			nosy_poll,
	.open =			nosy_open,
	.release =		nosy_release,
};

static struct pci_device_id pci_table[] = {
	{
		.vendor =    PCI_VENDOR_ID_TI,
		.device =    PCI_DEVICE_ID_TI_PCILYNX,
		.subvendor = PCI_ANY_ID,
		.subdevice = PCI_ANY_ID,
	},
	{ }	/* Terminating entry */
};

static struct pci_driver lynx_pci_driver = {
	.name =		driver_name,
	.id_table =	pci_table,
	.probe =	add_card,
	.remove =	remove_card,
};


nosy_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	struct client *client = file->private_data;
	spinlock_t *client_list_lock = &client->lynx->client_list_lock;
	struct nosy_stats stats;
	int ret;

	switch (cmd) {
	case NOSY_IOC_GET_STATS:
		spin_lock_irq(client_list_lock);
		stats.total_packet_count = client->buffer.total_packet_count;
		stats.lost_packet_count  = client->buffer.lost_packet_count;
		spin_unlock_irq(client_list_lock);

		if (copy_to_user((void __user *) arg, &stats, sizeof stats))
			return -EFAULT;
		else
			return 0;

	case NOSY_IOC_START:
		ret = -EBUSY;
		spin_lock_irq(client_list_lock);
		if (list_empty(&client->link)) {
			list_add_tail(&client->link, &client->lynx->client_list);
			ret = 0;
		}
		spin_unlock_irq(client_list_lock);

		return ret;

	case NOSY_IOC_STOP:
		spin_lock_irq(client_list_lock);
		list_del_init(&client->link);
		spin_unlock_irq(client_list_lock);

		return 0;

	case NOSY_IOC_FILTER:
		spin_lock_irq(client_list_lock);
		client->tcode_mask = arg;
		spin_unlock_irq(client_list_lock);

		return 0;

	default:
		return -EINVAL;
		/* Flush buffer, configure filter. */
	}
}


packet_buffer_put(struct packet_buffer *buffer, void *data, size_t length)
{
	char *end;

	buffer->total_packet_count++;

	if (buffer->capacity <
	    atomic_read(&buffer->size) + sizeof(struct packet) + length) {
		buffer->lost_packet_count++;
		return;
	}

	end = buffer->data + buffer->capacity;
	buffer->tail->length = length;

	if (&buffer->tail->data[length] < end) {
		memcpy(buffer->tail->data, data, length);
		buffer->tail = (struct packet *) &buffer->tail->data[length];
	} else {
		size_t split = end - buffer->tail->data;

		memcpy(buffer->tail->data, data, split);
		memcpy(buffer->data, data + split, length - split);
		buffer->tail = (struct packet *) &buffer->data[length - split];
	}

	/* Finally, adjust buffer size and wake up userspace reader. */

	atomic_add(sizeof(struct packet) + length, &buffer->size);
	wake_up_interruptible(&buffer->wait);
}


irq_handler(int irq, void *device)
{
	struct pcilynx *lynx = device;
	u32 pci_int_status;

	pci_int_status = reg_read(lynx, PCI_INT_STATUS);

	if (pci_int_status == ~0)
		/* Card was ejected. */
		return IRQ_NONE;

	if ((pci_int_status & PCI_INT_INT_PEND) == 0)
		/* Not our interrupt, bail out quickly. */
		return IRQ_NONE;

	if ((pci_int_status & PCI_INT_P1394_INT) != 0) {
		u32 link_int_status;

		link_int_status = reg_read(lynx, LINK_INT_STATUS);
		reg_write(lynx, LINK_INT_STATUS, link_int_status);

		if ((link_int_status & LINK_INT_PHY_BUSRESET) > 0)
			bus_reset_irq_handler(lynx);
	}

	/* Clear the PCI_INT_STATUS register only after clearing the
	 * LINK_INT_STATUS register; otherwise the PCI_INT_P1394 will
	 * be set again immediately. */

	reg_write(lynx, PCI_INT_STATUS, pci_int_status);

	if ((pci_int_status & PCI_INT_DMA0_HLT) > 0) {
		packet_irq_handler(lynx);
		run_pcl(lynx, lynx->rcv_start_pcl_bus, 0);
	}

	return IRQ_HANDLED;
}


add_card(struct pci_dev *dev, const struct pci_device_id *unused)
{
	struct pcilynx *lynx;
	u32 p, end;
	int ret, i;

	if (pci_set_dma_mask(dev, DMA_BIT_MASK(32))) {
		dev_err(&dev->dev,
		    "DMA address limits not supported for PCILynx hardware\n");
		return -ENXIO;
	}
	if (pci_enable_device(dev)) {
		dev_err(&dev->dev, "Failed to enable PCILynx hardware\n");
		return -ENXIO;
	}
	pci_set_master(dev);

	lynx = kzalloc(sizeof *lynx, GFP_KERNEL);
	if (lynx == NULL) {
		dev_err(&dev->dev, "Failed to allocate control structure\n");
		ret = -ENOMEM;
		goto fail_disable;
	}
	lynx->pci_device = dev;
	pci_set_drvdata(dev, lynx);

	spin_lock_init(&lynx->client_list_lock);
	INIT_LIST_HEAD(&lynx->client_list);
	kref_init(&lynx->kref);

	lynx->registers = ioremap(pci_resource_start(dev, 0),
					  PCILYNX_MAX_REGISTER);
	if (lynx->registers == NULL) {
		dev_err(&dev->dev, "Failed to map registers\n");
		ret = -ENOMEM;
		goto fail_deallocate_lynx;
	}

	lynx->rcv_start_pcl = pci_alloc_consistent(lynx->pci_device,
				sizeof(struct pcl), &lynx->rcv_start_pcl_bus);
	lynx->rcv_pcl = pci_alloc_consistent(lynx->pci_device,
				sizeof(struct pcl), &lynx->rcv_pcl_bus);
	lynx->rcv_buffer = pci_alloc_consistent(lynx->pci_device,
				RCV_BUFFER_SIZE, &lynx->rcv_buffer_bus);
	if (lynx->rcv_start_pcl == NULL ||
	    lynx->rcv_pcl == NULL ||
	    lynx->rcv_buffer == NULL) {
		dev_err(&dev->dev, "Failed to allocate receive buffer\n");
		ret = -ENOMEM;
		goto fail_deallocate_buffers;
	}
	lynx->rcv_start_pcl->next	= cpu_to_le32(lynx->rcv_pcl_bus);
	lynx->rcv_pcl->next		= cpu_to_le32(PCL_NEXT_INVALID);
	lynx->rcv_pcl->async_error_next	= cpu_to_le32(PCL_NEXT_INVALID);

	lynx->rcv_pcl->buffer[0].control =
			cpu_to_le32(PCL_CMD_RCV | PCL_BIGENDIAN | 2044);
	lynx->rcv_pcl->buffer[0].pointer =
			cpu_to_le32(lynx->rcv_buffer_bus + 4);
	p = lynx->rcv_buffer_bus + 2048;
	end = lynx->rcv_buffer_bus + RCV_BUFFER_SIZE;
	for (i = 1; p < end; i++, p += 2048) {
		lynx->rcv_pcl->buffer[i].control =
			cpu_to_le32(PCL_CMD_RCV | PCL_BIGENDIAN | 2048);
		lynx->rcv_pcl->buffer[i].pointer = cpu_to_le32(p);
	}
	lynx->rcv_pcl->buffer[i - 1].control |= cpu_to_le32(PCL_LAST_BUFF);

	reg_set_bits(lynx, MISC_CONTROL, MISC_CONTROL_SWRESET);
	/* Fix buggy cards with autoboot pin not tied low: */
	reg_write(lynx, DMA0_CHAN_CTRL, 0);
	reg_write(lynx, DMA_GLOBAL_REGISTER, 0x00 << 24);

#if 0
	/* now, looking for PHY register set */
	if ((get_phy_reg(lynx, 2) & 0xe0) == 0xe0) {
		lynx->phyic.reg_1394a = 1;
		PRINT(KERN_INFO, lynx->id,
		      "found 1394a conform PHY (using extended register set)");
		lynx->phyic.vendor = get_phy_vendorid(lynx);
		lynx->phyic.product = get_phy_productid(lynx);
	} else {
		lynx->phyic.reg_1394a = 0;
		PRINT(KERN_INFO, lynx->id, "found old 1394 PHY");
	}
#endif

	/* Setup the general receive FIFO max size. */
	reg_write(lynx, FIFO_SIZES, 255);

	reg_set_bits(lynx, PCI_INT_ENABLE, PCI_INT_DMA_ALL);

	reg_write(lynx, LINK_INT_ENABLE,
		  LINK_INT_PHY_TIME_OUT | LINK_INT_PHY_REG_RCVD |
		  LINK_INT_PHY_BUSRESET | LINK_INT_IT_STUCK |
		  LINK_INT_AT_STUCK | LINK_INT_SNTRJ |
		  LINK_INT_TC_ERR | LINK_INT_GRF_OVER_FLOW |
		  LINK_INT_ITF_UNDER_FLOW | LINK_INT_ATF_UNDER_FLOW);

	/* Disable the L flag in self ID packets. */
	set_phy_reg(lynx, 4, 0);

	/* Put this baby into snoop mode */
	reg_set_bits(lynx, LINK_CONTROL, LINK_CONTROL_SNOOP_ENABLE);

	run_pcl(lynx, lynx->rcv_start_pcl_bus, 0);

	if (request_irq(dev->irq, irq_handler, IRQF_SHARED,
			driver_name, lynx)) {
		dev_err(&dev->dev,
			"Failed to allocate shared interrupt %d\n", dev->irq);
		ret = -EIO;
		goto fail_deallocate_buffers;
	}

	lynx->misc.parent = &dev->dev;
	lynx->misc.minor = MISC_DYNAMIC_MINOR;
	lynx->misc.name = "nosy";
	lynx->misc.fops = &nosy_ops;

	mutex_lock(&card_mutex);
	ret = misc_register(&lynx->misc);
	if (ret) {
		dev_err(&dev->dev, "Failed to register misc char device\n");
		mutex_unlock(&card_mutex);
		goto fail_free_irq;
	}
	list_add_tail(&lynx->link, &card_list);
	mutex_unlock(&card_mutex);

	dev_info(&dev->dev,
		 "Initialized PCILynx IEEE1394 card, irq=%d\n", dev->irq);

	return 0;

fail_free_irq:
	reg_write(lynx, PCI_INT_ENABLE, 0);
	free_irq(lynx->pci_device->irq, lynx);

fail_deallocate_buffers:
	if (lynx->rcv_start_pcl)
		pci_free_consistent(lynx->pci_device, sizeof(struct pcl),
				lynx->rcv_start_pcl, lynx->rcv_start_pcl_bus);
	if (lynx->rcv_pcl)
		pci_free_consistent(lynx->pci_device, sizeof(struct pcl),
				lynx->rcv_pcl, lynx->rcv_pcl_bus);
	if (lynx->rcv_buffer)
		pci_free_consistent(lynx->pci_device, PAGE_SIZE,
				lynx->rcv_buffer, lynx->rcv_buffer_bus);
	iounmap(lynx->registers);

fail_deallocate_lynx:
	kfree(lynx);

fail_disable:
	pci_disable_device(dev);

	return ret;
}

static char driver_name[] = KBUILD_MODNAME;

struct pcl {
	__le32 next;
	__le32 async_error_next;
	u32 user_data;
	__le32 pcl_status;
	__le32 remaining_transfer_count;
	__le32 next_data_buffer;
	struct {
		__le32 control;
		__le32 pointer;
	} buffer[13];
}

struct packet {
	unsigned int length;
	char data[];
}

struct packet_buffer {
	char *data;
	size_t capacity;
	long total_packet_count, lost_packet_count;
	atomic_t size;
	struct packet *head, *tail;
	wait_queue_head_t wait;
}

struct pcilynx {
	struct pci_dev *pci_device;
	__iomem char *registers;

	struct pcl *rcv_start_pcl, *rcv_pcl;
	__le32 *rcv_buffer;

	dma_addr_t rcv_start_pcl_bus, rcv_pcl_bus, rcv_buffer_bus;

	spinlock_t client_list_lock;
	struct list_head client_list;

	struct miscdevice misc;
	struct list_head link;
	struct kref kref;
}

struct client {
	struct pcilynx *lynx;
	u32 tcode_mask;
	struct packet_buffer buffer;
	struct list_head link;
}

static DEFINE_MUTEX(card_mutex);

static LIST_HEAD(card_list);

static const struct file_operations nosy_ops = {
	.owner =		THIS_MODULE,
	.read =			nosy_read,
	.unlocked_ioctl =	nosy_ioctl,
	.poll =			nosy_poll,
	.open =			nosy_open,
	.release =		nosy_release,
};

#define RCV_BUFFER_SIZE (16 * 1024)

static struct pci_device_id pci_table[] = {
	{
		.vendor =    PCI_VENDOR_ID_TI,
		.device =    PCI_DEVICE_ID_TI_PCILYNX,
		.subvendor = PCI_ANY_ID,
		.subdevice = PCI_ANY_ID,
	},
	{ }	/* Terminating entry */
};

static struct pci_driver lynx_pci_driver = {
	.name =		driver_name,
	.id_table =	pci_table,
	.probe =	add_card,
	.remove =	remove_card,
};


bus_reset_irq_handler(struct pcilynx *lynx)
{
	struct client *client;
	struct timespec64 ts64;
	u32    timestamp;

	ktime_get_real_ts64(&ts64);
	timestamp = ts64.tv_nsec / NSEC_PER_USEC;

	spin_lock(&lynx->client_list_lock);

	list_for_each_entry(client, &lynx->client_list, link)
		packet_buffer_put(&client->buffer, &timestamp, 4);

	spin_unlock(&lynx->client_list_lock);
}


nosy_poll(struct file *file, poll_table *pt)
{
	struct client *client = file->private_data;
	__poll_t ret = 0;

	poll_wait(file, &client->buffer.wait, pt);

	if (atomic_read(&client->buffer.size) > 0)
		ret = EPOLLIN | EPOLLRDNORM;

	if (list_empty(&client->lynx->link))
		ret |= EPOLLHUP;

	return ret;
}