/* vi:set sts=4 sw=4:
 *
 * VIM - Vi IMproved	by Bram Moolenaar
 *
 * Do ":help uganda"  in Vim to read copying and usage conditions.
 * Do ":help credits" in Vim to see a list of people who contributed.
 */

/*
 * syntax.c: code for syntax highlighting
 */

#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "option.h"

/*
 * Structure that stores information about a highlight group.
 * The ID of a highlight group is also called group ID.  It is the index in
 * the highlight_ga array PLUS ONE.
 */
struct hl_group
{
    char_u	    *sg_name;	    /* highlight group name */
/* for normal terminals */
    int		    sg_term;	    /* "term=" highlighting attributes */
    char_u	    *sg_start;	    /* terminal string for start highl */
    char_u	    *sg_stop;	    /* terminal string for stop highl */
    int		    sg_term_attr;   /* NextScreen attr for term mode */
/* for color terminals */
    int		    sg_cterm;	    /* "cterm=" highlighting attr */
    int		    sg_cterm_fg;    /* terminal fg color number + 1 */
    int		    sg_cterm_bg;    /* terminal bg color number + 1 */
    int		    sg_cterm_attr;  /* NextScreen attr for color term mode */
#ifdef USE_GUI
/* for when using the GUI */
    int		    sg_gui;	    /* "gui=" highlighting attributes */
    GuiColor	    sg_gui_fg;	    /* GUI foreground color handle + 1 */
    char_u	    *sg_gui_fg_name;/* GUI foreground color name */
    GuiColor	    sg_gui_bg;	    /* GUI background color handle + 1 */
    char_u	    *sg_gui_bg_name;/* GUI background color name */
    GuiFont	    sg_font;	    /* GUI font handle */
    char_u	    *sg_font_name;  /* GUI font name */
    int		    sg_gui_attr;    /* NextScreen attr for GUI mode */
#endif
    int		    sg_link;	    /* link to this highlight group ID */
};

static struct growarray highlight_ga;	    /* highlight groups for
					       'highlight' option */

#define HL_TABLE() ((struct hl_group *)((highlight_ga.ga_data)))

/*
 * The "term" argument can be any combination of the following
 * names, separated by commas (but no spaces!).
 * The "cterm" and the "gui" argument is the same, but for the
 * color terminal or the GUI.
 */
static char *(hl_name_table[]) =
    {"bold", "standout", "underline", "italic", "reverse", "inverse", "NONE"};
static int hl_attr_table[] =
    {HL_BOLD, HL_STANDOUT, HL_UNDERLINE, HL_ITALIC, HL_INVERSE, HL_INVERSE, 0};

static int get_attr_entry  __ARGS((struct growarray *table, struct attr_entry *aep));
static void highlight_list_one __ARGS((int id));
static int syn_namen2id __ARGS((char_u *linep, int len));
static void syn_unadd_group __ARGS((void));
static void set_hl_attr __ARGS((int idx));
static int highlight_list_arg __ARGS((int id, int didh, int type, int iarg, char_u *sarg, char *name));
static int syn_name2id __ARGS((char_u *name));
static int syn_add_group __ARGS((char_u *name));
static int syn_id2attr __ARGS((int hl_id));
static int syn_get_final_id __ARGS((int hl_id));
static void syn_list_header __ARGS((int did_header, int outlen, int id));

#ifdef USE_GUI
static GuiColor color_name2handle __ARGS((char_u *name));
static GuiFont font_name2handle __ARGS((char_u *name));
#endif

/*
 * An attribute number is the index in attr_table plus ATTR_OFF.
 */
#define ATTR_OFF (HL_ALL + 1)

#ifdef SYNTAX_HL

#define SYN_NAMELEN	50		/* maximum length of a syntax name */

/*
 * The patterns that are being searched for are stored in a syn_pattern.
 * A match item consists of one pattern.
 * A start/end item consists of n start patterns and m end patterns.
 * A start/skip/end item consists of n start patterns, one skip pattern and m
 * end patterns.
 * For the latter two the patterns are always consecutive: start-skip-end.
 *
 * A character offset can be given for the matched text (_m_start and _m_end)
 * and for the actually highlighted text (_h_start and _h_end).
 */
struct syn_pattern
{
    char		 sp_type;	    /* see SPTYPE_ defines below */
    char		 sp_syncing;	    /* this item used for syncing */
    char		 sp_flags;	    /* see HL_ defines below */
    char		 sp_off_flags;	    /* see SPO_ defines below */
    short		 sp_syn_id;	    /* highlight group ID of item */
    char_u		*sp_pattern;	    /* regexp to match, pattern */
    vim_regexp		*sp_prog;	    /* regexp to match, program */
    int			 sp_ic;		    /* ignore-case flag for sp_prog */
    int			 sp_off_h_start;    /* highl. offset from match start */
    int			 sp_off_h_end;	    /* highl. offset from match end */
    int			 sp_off_m_start;    /* match  offset from match start */
    int			 sp_off_m_end;	    /* match  offset from match end */
    int			*sp_id_list;	    /* contained highlight group IDs */
    int			 sp_sync_idx;	    /* sync item index (syncing only) */
};

#define SPO_H_START	0x01	/* has offset for highlighting start */
#define SPO_H_END	0x02	/* has offset for highlighting end */
#define SPO_M_START	0x04	/* has offset for matching text start */
#define SPO_M_END	0x08	/* has offset for matching text end */
#define SPO_FIRST	0x01
#define SPO_LAST	0x08

#define SPTYPE_MATCH	1	/* match keyword with this group ID */
#define SPTYPE_START	2	/* match a regexp, start of item */
#define SPTYPE_END	3	/* match a regexp, end of item */
#define SPTYPE_SKIP	4	/* match a regexp, skip within item */

#define HL_CONTAINED	0x01	/* not used on toplevel */
#define HL_TRANSP	0x02	/* has no highlighting	*/
#define HL_ONELINE	0x04	/* match within one line only */
#define HL_HAS_EOL	0x08	/* end pattern that matches with $ */
#define HL_SYNC_HERE	0x10	/* sync point after this item (syncing only) */
#define HL_SYNC_THERE	0x20	/* sync point at current line (syncing only) */

#define SYN_ITEMS(buf)	((struct syn_pattern *)((buf)->b_syn_patterns.ga_data))

#define NONE_IDX	-2	/* value of sp_sync_idx for "NONE" */

/*
 * Flags for b_syn_sync_flags:
 */
#define SF_CCOMMENT	0x01	/* sync on a C-style comment */
#define SF_MATCH	0x02	/* sync by matching a pattern */

#define SYN_STATE_P(ssp)    ((int *)((ssp)->ga_data))

#ifndef OLD_KEYW
/*
 * Settings for keyword hash table.  It uses a simplistic hash function: add
 * all characters together, modulo KHASH_SIZE.
 */
#define KHASH_SIZE	512
#define KHASH_MASK	(KHASH_SIZE - 1)
#define MAXKEYWLEN	80	    /* maximum length of a keyword */
#endif

/*
 * The attributes of the syntax item that has been recognized.
 */
static int current_attr = 0;	    /* attr of current syntax word */

/*
 * For the current state we need to remember more than just the idx.
 * When si_m_endcol is 0, the items other than si_idx are unknown.
 */
struct state_item
{
    int	    si_idx;		    /* index of syntax item */
    int	    si_m_lnum;		    /* lnum of the match */
    int	    si_m_startcol;	    /* starting column of the match */
    int	    si_m_endcol;	    /* ending column of the match */
    int	    si_h_startcol;	    /* starting column of the highlighting */
    int	    si_h_endcol;	    /* ending column of the highlighting */
    int	    si_ends;		    /* if match ends after si_m_endcol */
    int	    si_attr;		    /* attributes in this state */
    int	    si_flags;		    /* HL_HAS_EOL flag in this state */
    int	    *si_id_list;	    /* list of contained groups */
};

#define KEYWORD_IDX	-1	    /* value of si_idx for keywords */
#define CONTAINS_ALLBUT	9999	    /* value of id for contains ALLBUT */
#define ID_LIST_ALL	(int *)-1   /* valid of si_id_list for containing all
				       but contained groups */

/*
 * The next possible match for any pattern is remembered, to avoid having to
 * try for a match in each column.
 * If next_match_idx == -1, not tried (in this line) yet.
 * If next_match_col == MAXCOL, no match found in this line.
 */
static int next_match_col;	    /* column for start of next match */
static int next_match_m_endcol;	    /* column for end of next match */
static int next_match_h_startcol;   /* column for highl. start of next match */
static int next_match_h_endcol;	    /* column for highl. end of next match */
static int next_match_idx;	    /* index of matched item */
static int next_match_flags;	    /* flags for next match */

/*
 * A state stack is an array of integers or struct state_item, stored in a
 * struct growarray.  A state stack is invalid if it's itemsize entry is zero.
 */
#define INVALID_STATE(ssp)  ((ssp)->ga_itemsize == 0)
#define VALID_STATE(ssp)    ((ssp)->ga_itemsize != 0)

/*
 * The current state (within the line) of the recognition engine.
 */
static BUF	*syn_buf;		/* current buffer for highlighting */
static linenr_t current_lnum = 0;	/* lnum of current state */
static int	current_state_stored = 0; /* TRUE if stored current state
					   * after setting current_finished */
static colnr_t	current_col = 0;	/* column of current state */
static int	current_finished = 0;	/* current line has been finished */
static struct growarray current_state	/* current stack of state_items */
		= {0, 0, 0, 0, NULL};

#define CUR_STATE(idx)	((struct state_item *)(current_state.ga_data))[idx]

static void syn_sync __ARGS((WIN *wp, linenr_t lnum));
static int syn_match_linecont __ARGS((linenr_t lnum));
static void syn_start_line __ARGS((void));
static void syn_free_all_states __ARGS((BUF *buf));
static void syn_clear_states __ARGS((int start, int end));
static void store_current_state __ARGS((void));
static void invalidate_state __ARGS((struct growarray *sp));
static void invalidate_current_state __ARGS((void));
static void validate_state __ARGS((struct growarray *sp));
static void validate_current_state __ARGS((void));
static void copy_state_to_current __ARGS((struct growarray *from));
static void move_state __ARGS((int from, int to));
static int syn_finish_line __ARGS((int syncing));
static int syn_current_attr __ARGS((int syncing, char_u *line));
static int did_match_already __ARGS((int idx));
static struct state_item *push_next_match __ARGS((struct state_item *cur_si, char_u *line));
static void check_state_ends __ARGS((struct state_item *cur_si, char_u *line));
static void update_si_attr __ARGS((int idx));
static void update_si_end __ARGS((struct state_item *sip, char_u *line, int startcol));
static int in_item_list __ARGS((int *id_list, int id, int contained));
static int push_current __ARGS((int idx));
static void pop_current __ARGS((void));
static char_u *find_endp __ARGS((int idx, char_u *sstart, int at_bol, char_u **hl_endp, int *flagsp));
static int check_keyword_id __ARGS((char_u *line, int startcol, int *endcol, int *flags));
static void syn_cmd_case __ARGS((char_u *arg, char_u **nextcomm, int syncing));
static void syn_clear_pattern __ARGS((BUF *buf, int i));
static void syn_cmd_clear __ARGS((char_u *arg, char_u **nextcomm, int syncing));
static void syn_clear_one __ARGS((int id, int syncing));
static void syn_cmd_list __ARGS((char_u *arg, char_u **nextcomm, int syncing));
static void syn_lines_msg __ARGS((void));
static void syn_list_one __ARGS((int id, int syncing, int link_only));
static void put_pattern __ARGS((struct syn_pattern *spp));
#ifdef OLD_KEYW
static int syn_list_recur __ARGS((int id, struct kwordtab *ktab, int len, int did_header, int attr, int clear));
#else
static int syn_list_keywords __ARGS((int id, struct keyentry **ktabp, int did_header, int attr));
static void syn_clear_keyword __ARGS((int id, struct keyentry **ktabp));
#endif
#ifdef OLD_KEYW
static void free_kwordtab __ARGS((struct kwordtab *ktab));
static struct kwordtab *add_kwordtab_down __ARGS((struct kwordtab *ktab, int n));
static struct kwordtab *alloc_kwordtab __ARGS((void));
#else
static void free_keywtab __ARGS((struct keyentry **ktabp));
#endif
static void add_keyword __ARGS((char_u *name, int id, int flags));
static void syn_cmd_keyword __ARGS((char_u *arg, char_u **nextcomm, int syncing));
static int get_group_name __ARGS((char_u *arg, int *flagsp, char_u **name,
		       char_u **name_end, int *sync_idx, char_u **rest));
static void syn_cmd_match __ARGS((char_u *arg, char_u **nextcomm, int syncing));
static void syn_cmd_region __ARGS((char_u *arg, char_u **nextcomm, int syncing));
static void init_syn_patterns __ARGS((void));
static char_u *get_syn_pattern __ARGS((char_u *arg, struct syn_pattern	*ci));
static void syn_cmd_sync __ARGS((char_u *arg, char_u **nextcomm, int syncing));
static int *get_id_list __ARGS((char_u **arg));

/*
 * Start the syntax recognition for a line.  This function is normally called
 * from the screen updating, once for each consecutive line.
 * The buffer is remembered in syn_buf, because get_syntax_attr() doesn't get
 * it.	Careful: curbuf and curwin are likely to point to another buffer and
 * window.
 */
    void
syntax_start(wp, lnum)
    WIN		*wp;
    linenr_t	lnum;
{
    int			len;
    long		to, from;
    long		diff;
    struct growarray	*new_ss;
    int			idx;

    /*
     * After switching buffers, invalidate current_state.
     */
    if (syn_buf != wp->w_buffer)
    {
	invalidate_current_state();
	syn_buf = wp->w_buffer;
    }

    /*
     * If the screen height has changed, re-allocate b_syn_states[].
     * Use the screen height plus one, so the line above and below the window
     * can always be stored too.
     */
    if (syn_buf->b_syn_states_len != Rows + 1)
    {
	syn_free_all_states(syn_buf);
	len = (Rows + 1) * sizeof(struct growarray);
	new_ss = (struct growarray *)alloc_clear(len);
	syn_buf->b_syn_states = new_ss;
	if (new_ss == NULL)		    /* out of memory */
	{
	    syn_buf->b_syn_states_len = 0;
	    return;
	}
	syn_buf->b_syn_states_len = Rows + 1;
	syn_buf->b_syn_states_lnum = 0;
	syn_buf->b_syn_change_lnum = MAXLNUM;
    }

    /*
     * Remove items from b_syn_states[] that have changes in or before them.
     */
    if (syn_buf->b_syn_change_lnum != MAXLNUM)
    {
	/* if change is before the end of the array, something to clear */
	if (syn_buf->b_syn_change_lnum <
		   syn_buf->b_syn_states_lnum + syn_buf->b_syn_states_len - 1)
	{
	    /* line before the start changed: clear all entries */
	    if (syn_buf->b_syn_change_lnum < syn_buf->b_syn_states_lnum)
		idx = 0;
	    else
		idx = syn_buf->b_syn_change_lnum -
					       syn_buf->b_syn_states_lnum + 1;
	    syn_clear_states(idx, syn_buf->b_syn_states_len);
	}
	if (syn_buf->b_syn_change_lnum < current_lnum)
	    invalidate_current_state();
	syn_buf->b_syn_change_lnum = MAXLNUM;
    }

    /*
     * If the topline has changed out of range of b_syn_states[], move the
     * items in the array.
     */
    if (wp->w_topline < syn_buf->b_syn_states_lnum)
    {
	/*
	 * Topline is above the array: Move entries down.
	 * (w_topline - 1) is the new first line in * b_syn_states[].
	 */
	to = syn_buf->b_syn_states_len - 1;
	from = to - (syn_buf->b_syn_states_lnum - (wp->w_topline - 1));
	while (from >= 0)
	{
	    move_state((int)from, (int)to);
	    --from;
	    --to;
	}
	syn_clear_states(0, (int)(to + 1));
	syn_buf->b_syn_states_lnum = wp->w_topline - 1;
    }
    else if ((diff = (wp->w_topline + wp->w_height) -
		 (syn_buf->b_syn_states_lnum + syn_buf->b_syn_states_len)) > 0)
    {
	/*
	 * The last line in the window is below the array: Move entries up
	 * "diff" positions.
	 */
	to = 0;
	from = to + diff;
	while (from < syn_buf->b_syn_states_len)
	{
	    move_state((int)from, (int)to);
	    ++from;
	    ++to;
	}
	syn_clear_states((int)to, syn_buf->b_syn_states_len);
	syn_buf->b_syn_states_lnum += diff;
    }

    /*
     * If the state of the end of the previous line is useful, store it.
     */
    if (VALID_STATE(&current_state) && current_lnum < lnum &&
	    current_lnum >= syn_buf->b_syn_states_lnum &&
	    current_lnum <
		     syn_buf->b_syn_states_lnum + syn_buf->b_syn_states_len &&
	    current_lnum < syn_buf->b_ml.ml_line_count)
    {
	(void)syn_finish_line(FALSE);
	if (!current_state_stored)
	{
	    ++current_lnum;
	    store_current_state();
	}

	/*
	 * If the current_lnum is now the same as "lnum", keep the current
	 * state (this happens very often!).  Otherwise invalidate
	 * current_state and figure it out below.
	 */
	if (current_lnum != lnum)
	    invalidate_current_state();
    }
    else
	invalidate_current_state();


    /*
     * Try to synchronize from a saved state in b_syn_states[].
     * Only do this if lnum is not before and not to far beyond a saved state.
     */
    if (INVALID_STATE(&current_state))
    {
	diff = syn_buf->b_syn_sync_minlines;
	if (diff < Rows * 2)
	    diff = Rows * 2;	    /* parse less then two screenfulls extra */
	if (lnum >= syn_buf->b_syn_states_lnum &&
		lnum <= syn_buf->b_syn_states_lnum +
					     syn_buf->b_syn_states_len + diff)
	{
	    idx = lnum - syn_buf->b_syn_states_lnum;
	    if (idx >= syn_buf->b_syn_states_len)
		idx = syn_buf->b_syn_states_len - 1;
	    for ( ; idx >= 0; --idx)
	    {
		if (VALID_STATE(&syn_buf->b_syn_states[idx]))
		{
		    current_lnum = syn_buf->b_syn_states_lnum + idx;
		    copy_state_to_current(&(syn_buf->b_syn_states[idx]));
		    break;
		}
	    }
	}
    }

    /*
     * If "lnum" is before or far beyond a line with a saved state, need to
     * re-synchronize.
     */
    if (INVALID_STATE(&current_state))
	syn_sync(wp, lnum);

    /*
     * Advance from the sync point or saved state until the current line.
     */
    while (current_lnum < lnum)
    {
	syn_start_line();
	(void)syn_finish_line(FALSE);
	++current_lnum;
	store_current_state();
    }

    syn_start_line();
}

/*
 * Try to find a synchronisation point for line "lnum".
 *
 * This sets current_lnum and the current state.  One of three methods is
 * used:
 * 1. Search backwards for the end of a C-comment.
 * 2. Search backwards for given sync patterns.
 * 3. Simply start on a given number of lines above "lnum".
 */
    static void
syn_sync(wp, start_lnum)
    WIN		*wp;
    linenr_t	start_lnum;
{
    BUF			*curbuf_save;
    WIN			*curwin_save;
    FPOS		cursor_save;
    int			idx;
    linenr_t		lnum;
    linenr_t		end_lnum;
    linenr_t		break_lnum;
    int			had_sync_point = FALSE;
    struct state_item	*cur_si;
    struct syn_pattern	*spp;
    char_u		*line;
    int			found_flags = 0;
    int			found_match_idx = 0;
    linenr_t		found_current_lnum = 0;
    int			found_current_col= 0;
    colnr_t		found_m_endcol = 0;

    /*
     * Clear any current state that might be hanging around.
     */
    invalidate_current_state();

    /*
     * Start at least "minlines" back.
     * Default starting point for parsing is there.
     */
    start_lnum -= syn_buf->b_syn_sync_minlines;
    if (start_lnum < 1)
	start_lnum = 1;
    current_lnum = start_lnum;

    /*
     * 1. Search backwards for the end of a C-style comment.
     */
    if (syn_buf->b_syn_sync_flags & SF_CCOMMENT)
    {
	/* need to make syn_buf the current buffer for a moment */
	curwin_save = curwin;
	curwin = wp;
	curbuf_save = curbuf;
	curbuf = syn_buf;

	/*
	 * Skip lines that end in a backslash.
	 */
	for ( ; start_lnum > 1; --start_lnum)
	{
	    line = ml_get(start_lnum - 1);
	    if (*line == NUL || *(line + STRLEN(line) - 1) != '\\')
		break;
	}

	/* set cursor to start of search */
	cursor_save = wp->w_cursor;
	wp->w_cursor.lnum = start_lnum;
	wp->w_cursor.col = 0;

	/*
	 * If the line is inside a comment, need to find the syntax item that
	 * defines the comment.
	 * Restrict the search for the end of a comment to b_syn_sync_maxlines.
	 */
	if (find_start_comment((int)syn_buf->b_syn_sync_maxlines) != NULL)
	{
	    for (idx = 0; idx < syn_buf->b_syn_patterns.ga_len; ++idx)
		if (SYN_ITEMS(syn_buf)[idx].sp_syn_id ==
						     syn_buf->b_syn_sync_id &&
			      SYN_ITEMS(syn_buf)[idx].sp_type == SPTYPE_START)
		{
		    validate_current_state();
		    if (push_current(idx) == OK)
			update_si_attr(current_state.ga_len - 1);
		    break;
		}
	}

	/* restore cursor and buffer */
	wp->w_cursor = cursor_save;
	curwin = curwin_save;
	curbuf = curbuf_save;
    }

    /*
     * 2. Search backwards for given sync patterns.
     */
    else if (syn_buf->b_syn_sync_flags & SF_MATCH)
    {
	if (syn_buf->b_syn_sync_maxlines
				 && start_lnum > syn_buf->b_syn_sync_maxlines)
	    break_lnum = start_lnum - syn_buf->b_syn_sync_maxlines;
	else
	    break_lnum = 0;

	end_lnum = start_lnum;
	lnum = start_lnum;
	while (--lnum > break_lnum)
	{
	    /*
	     * Check if the previous line has the line-continuation pattern.
	     */
	    if (lnum > 1 && syn_match_linecont(lnum - 1))
		continue;

	    /*
	     * Start with nothing on the state stack
	     */
	    validate_current_state();

	    for (current_lnum = lnum; current_lnum < end_lnum; ++current_lnum)
	    {
		syn_start_line();
		for (;;)
		{
		    had_sync_point = syn_finish_line(TRUE);
		    /*
		     * When a sync point has been found, remember where, and
		     * continue to look for another one, further on in the line.
		     */
		    if (had_sync_point && current_state.ga_len)
		    {
			cur_si = &CUR_STATE(current_state.ga_len - 1);
			spp = &(SYN_ITEMS(syn_buf)[cur_si->si_idx]);
			found_flags = spp->sp_flags;
			found_match_idx = spp->sp_sync_idx;
			found_current_lnum = current_lnum;
			found_current_col = current_col;
			found_m_endcol = cur_si->si_m_endcol;
			/*
			 * Continue after the match (be aware of a zero-length
			 * match).
			 */
			if (found_m_endcol > current_col)
			    current_col = found_m_endcol;
			else
			    ++current_col;
		    }
		    else
			break;
		}
	    }

	    /*
	     * If a sync point was encountered, break here.
	     */
	    if (found_flags)
	    {
		/*
		 * Put the item that was specified by the sync point on the
		 * state stack.  If there was no item specified, make the
		 * state stack empty.
		 */
		ga_clear(&current_state);
		if (found_match_idx >= 0 &&
			push_current(found_match_idx) == OK)
		    update_si_attr(current_state.ga_len - 1);

		/*
		 * When using "grouphere", continue from the sync point
		 * match, until the end of the line.  Parsing starts at
		 * the next line.
		 * For "groupthere" the parsing starts at start_lnum.
		 */
		if (had_sync_point & HL_SYNC_HERE)
		{
		    if (current_state.ga_len)
		    {
			cur_si = &CUR_STATE(current_state.ga_len - 1);
			cur_si->si_h_startcol = found_current_col;
			line = ml_get_buf(syn_buf, current_lnum, FALSE);
			update_si_end(cur_si, line, current_col);
		    }
		    current_col = found_m_endcol;
		    current_lnum = found_current_lnum;
		    (void)syn_finish_line(FALSE);
		    ++current_lnum;
		}
		else
		    current_lnum = start_lnum;

		break;
	    }

	    end_lnum = lnum;
	    invalidate_current_state();
	}

	/* Ran into start of the file or exceeded maximum number of lines */
	if (lnum <= break_lnum)
	{
	    invalidate_current_state();
	    current_lnum = break_lnum + 1;
	}
    }

    validate_current_state();
}

/*
 * Return TRUE if the line-continuation pattern matches in line "lnum".
 */
    static int
syn_match_linecont(lnum)
    linenr_t	    lnum;
{
    if (syn_buf->b_syn_linecont_prog != NULL)
    {
	reg_ic = syn_buf->b_syn_linecont_ic;
	return vim_regexec(syn_buf->b_syn_linecont_prog,
				      ml_get_buf(syn_buf, lnum, FALSE), TRUE);
    }
    return FALSE;
}

/*
 * Set the state for the start of a line.
 */
    static void
syn_start_line()
{
    char_u		*line;
    struct state_item	*cur_si;

    current_finished = FALSE;
    current_col = 0;

    /*
     * Need to update the end of a start/skip/end that continues from the
     * previous line.  And then it may end in column 0.
     */
    if (current_state.ga_len)
    {
	line = ml_get_buf(syn_buf, current_lnum, FALSE);
	cur_si = &CUR_STATE(current_state.ga_len - 1);
	cur_si->si_h_startcol = 0;	/* always start highl. in col 0 */
	update_si_end(cur_si, line, 0);
	check_state_ends(cur_si, line);
    }
    next_match_idx = -1;
}

/*
 * Free b_syn_states[] for buffer "buf".
 */
    static void
syn_free_all_states(buf)
    BUF	    *buf;
{
    int	    idx;

    if (buf->b_syn_states != NULL)
    {
	for (idx = 0; idx < buf->b_syn_states_len; ++idx)
	    ga_clear(&(buf->b_syn_states[idx]));
	vim_free(buf->b_syn_states);
	buf->b_syn_states = NULL;
	buf->b_syn_states_len = 0;
    }
}

/*
 * clear the entries in b_syn_states[] from "start" to (not including) "end"
 */
    static void
syn_clear_states(start, end)
    int	    start, end;
{
    int	    idx;

    for (idx = start; idx < end; ++idx)
	invalidate_state(&(syn_buf->b_syn_states[idx]));
}

/*
 * Try saving the current state in b_syn_states[].
 * The current state must be at the start of the current_lnum line!
 */
    static void
store_current_state()
{
    long		idx;
    int			i;
    struct growarray	*to;

    idx = current_lnum - syn_buf->b_syn_states_lnum;
    if (idx >= 0 && idx < syn_buf->b_syn_states_len)
    {
	to = &(syn_buf->b_syn_states[idx]);
	if (to->ga_data != NULL)
	    ga_clear(to);
	else if (INVALID_STATE(to))
	    validate_state(to);
	if (current_state.ga_len && ga_grow(to, current_state.ga_len) != FAIL)
	{
	    for (i = 0; i < current_state.ga_len; ++i)
		SYN_STATE_P(to)[i] = CUR_STATE(i).si_idx;
	    to->ga_len = current_state.ga_len;
	    to->ga_room -= to->ga_len;
	}
    }
    current_state_stored = TRUE;
}

/*
 * Copy a state stack from "from" in b_syn_states[] to current_state;
 */
    static void
copy_state_to_current(from)
    struct growarray *from;
{
    int	    i;

    ga_clear(&current_state);
    validate_current_state();
    if (from->ga_len && ga_grow(&current_state, from->ga_len) != FAIL)
    {
	for (i = 0; i < from->ga_len; ++i)
	{
	    CUR_STATE(i).si_idx = SYN_STATE_P(from)[i];
	    CUR_STATE(i).si_m_endcol = 0;
	    CUR_STATE(i).si_m_startcol = 0;
	    CUR_STATE(i).si_m_lnum = 0;
	    update_si_attr(i);
	}
	current_state.ga_len = from->ga_len;
	current_state.ga_room -= current_state.ga_len;
    }
}

/*
 * Invalidate a state stack by freeing it and setting ga_itemsize to zero.
 */
    static void
invalidate_state(sp)
    struct growarray *sp;
{
    ga_clear(sp);
    sp->ga_itemsize = 0;
}

    static void
invalidate_current_state()
{
    ga_clear(&current_state);
    current_state.ga_itemsize = 0;
}

/*
 * Validate a state stack by setting ga_itemsize and ga_growsize.
 */
    static void
validate_state(sp)
    struct growarray *sp;
{
    sp->ga_itemsize = sizeof(int);	/* for b_syn_states[] */
    sp->ga_growsize = 3;
}

    static void
validate_current_state()
{
    current_state.ga_itemsize = sizeof(struct state_item);
    current_state.ga_growsize = 3;
}

/*
 * Move a state stack from b_syn_states[from] to b_syn_states[to].
 */
    static void
move_state(from, to)
    int	    from, to;
{
    ga_clear(&(syn_buf->b_syn_states[to]));
    syn_buf->b_syn_states[to] = syn_buf->b_syn_states[from];
    ga_init(&(syn_buf->b_syn_states[from]));
    syn_buf->b_syn_states[from].ga_itemsize = 0;	/* invalid entry */
}

/*
 * Return TRUE if the syntax at start of lnum changed since last time.
 * This will only be called just after get_syntax_attr for the previous line,
 * to check if the next line needs to be redrawn too.
 */
    int
syntax_check_changed(lnum)
    linenr_t	lnum;
{
    struct growarray	*ssp;
    int			i;
    int			retval = TRUE;
    long		idx;

    /*
     * Check the state stack when:
     * - lnum is just below the previously syntaxed line.
     * - lnum is not before the lines with saved states.
     * - lnum is not past the lines with saved states.
     * - lnum is at or before the last changed line.
     */
    idx = lnum - syn_buf->b_syn_states_lnum;
    if (VALID_STATE(&current_state) && lnum == current_lnum + 1 &&
	    idx >= 0 && idx < syn_buf->b_syn_states_len &&
	    lnum < syn_buf->b_syn_change_lnum)
    {
	/*
	 * finish the previous line (needed when not all of the line was drawn)
	 */
	(void)syn_finish_line(FALSE);

	ssp = &(syn_buf->b_syn_states[idx]);
	if (VALID_STATE(ssp))	/* entry is valid */
	{
	    /*
	     * Compare the current state with the previously saved state of
	     * the line.
	     */
	    if (ssp->ga_len == current_state.ga_len)
	    {
		for (i = current_state.ga_len; --i >= 0; )
		    if (SYN_STATE_P(ssp)[i] != CUR_STATE(i).si_idx)
			break;
		/*
		 * If still the same state, return FALSE, syntax didn't change.
		 */
		if (i < 0)
		    retval = FALSE;
	    }
	}

	/*
	 * Store the current state in b_syn_states[] for later use.
	 */
	++current_lnum;
	store_current_state();
    }

    return retval;
}

/*
 * Finish the current line.
 * This doesn't return any attributes, it only gets the state at the end of
 * the line.  It can start anywhere in the line, as long as the current state
 * is valid.
 */
    static int
syn_finish_line(syncing)
    int	    syncing;		/* called for syncing */
{
    char_u	*line;

    if (!current_finished)
    {
	line = ml_get_buf(syn_buf, current_lnum, FALSE);
	while (!current_finished)
	{
	    (void)syn_current_attr(syncing, line);
	    /*
	     * Check for match with sync item.
	     */
	    if (syncing && current_state.ga_len &&
		    (SYN_ITEMS(syn_buf)[CUR_STATE(current_state.ga_len
			- 1).si_idx].sp_flags & (HL_SYNC_HERE|HL_SYNC_THERE)))
		return TRUE;
	    ++current_col;
	}
    }
    return FALSE;
}

/*
 * Return highlight attributes for next character.
 * This function is alwyas called from the screen updating, for each
 * consecutive character.  And only after syntax_start() has been called for
 * the current line.
 * Note that "col" doesn't start at 0, when win->w_leftcol is non-zero, and
 * doesn't continue until the last col when 'nowrap' is set.
 */
    int
get_syntax_attr(col, line)
    colnr_t	col;
    char_u	*line;
{
    int	    attr;

    /* check for out of memory situation */
    if (syn_buf->b_syn_states_len == 0)
	return 0;

    /* Make sure current_state is valid */
    if (INVALID_STATE(&current_state))
	validate_current_state();

    /*
     * Skip from the current column to "col".
     */
    while (current_col < col)
    {
	(void)syn_current_attr(FALSE, line);
	++current_col;
    }

    /*
     * Get the attributes for "col".
     */
    attr = syn_current_attr(FALSE, line);
    ++current_col;

    return attr;
}

/*
 * Get syntax attributes for current_lnum, current_col.
 */
    static int
syn_current_attr(syncing, line)
    int		syncing;	    /* called for syncing */
    char_u	*line;
{
    int			syn_id;
    char_u		*endp;
    char_u		*hl_endp = NULL;
    int			idx;
    struct syn_pattern	*spp;
    struct state_item	*cur_si;
    int			startcol;
    int			hl_startcol;
    int			endcol;
    int			flags;
    int			found_match;
    static int		try_next_column = FALSE;    /* must try in next col */

    /*
     * No character, no attributes!  Past end of line?
     * Do try matching with an empty line (could be the start of a region).
     */
    if (*(line + current_col) == NUL && current_col != 0)
    {
	/*
	 * If we found a match after the last column, use it.
	 */
	if (next_match_idx >= 0 && next_match_col >= (int)current_col &&
		next_match_col != MAXCOL)
	    (void)push_next_match(NULL, line);

	current_finished = TRUE;
	current_state_stored = FALSE;
	return 0;
    }

    /* if the next character is NUL, we will finish the line now */
    if (*(line + current_col) == NUL || *(line + current_col + 1) == NUL)
    {
	current_finished = TRUE;
	current_state_stored = FALSE;
    }

    /*
     * When in the previous column there was a match but it could not be used
     * (empty match or already matched in this column) need to try again in
     * the next column.
     */
    if (try_next_column)
    {
	next_match_idx = -1;
	try_next_column = FALSE;
    }

    /*
     * Repeat matching keywords and patterns, to find contained items at the
     * same column.  This stops when there are no extra matches at the current
     * column.
     */
    do
    {
	found_match = FALSE;
	syn_id = 0;

	/*
	 * 1. Check for a current state.
	 *    Only when there is no current state, or if the current state may
	 *    contain other things, we need to check for keywords and patterns.
	 */
	if (current_state.ga_len)
	    cur_si = &CUR_STATE(current_state.ga_len - 1);
	else
	    cur_si = NULL;

	if (cur_si == NULL || cur_si->si_id_list != NULL)
	{
	    /*
	     * 2. Check for keywords, if on a keyword char after a non-keyword
	     *	  char.  Don't do this when syncing.
	     */
	    if (       !syncing
#ifdef OLD_KEYW
		    && (syn_buf->b_first_ktab != NULL
			|| syn_buf->b_first_ktab_ic != NULL)
#else
		    && (syn_buf->b_keywtab != NULL
			|| syn_buf->b_keywtab_ic != NULL)
#endif
		    && iswordchar_buf(line[current_col], syn_buf)
		    && (current_col == 0
			|| !iswordchar_buf(line[current_col - 1], syn_buf)))
	    {
		syn_id = check_keyword_id(line, (int)current_col,
							     &endcol, &flags);
		if (syn_id)
		{
		    /*
		     * Ignore a contained keyword on toplevel, accept keywords
		     * at other levels only if in the contains list.
		     */
		    if (       (cur_si == NULL
				&& (flags & HL_CONTAINED))
			    || (cur_si != NULL
				&& !in_item_list(cur_si->si_id_list,
					       syn_id, flags & HL_CONTAINED)))
			syn_id = 0;
		    else
		    {
			if (push_current(KEYWORD_IDX) == OK)
			{
			    cur_si = &CUR_STATE(current_state.ga_len - 1);
			    cur_si->si_m_endcol = endcol;
			    cur_si->si_m_startcol = current_col;
			    cur_si->si_h_startcol = 0;	/* starts right away */
			    cur_si->si_h_endcol = endcol;
			    cur_si->si_ends = TRUE;
			    if (flags & HL_TRANSP)
			    {
				if (current_state.ga_len < 2)
				    cur_si->si_attr = 0;
				else
				    cur_si->si_attr = CUR_STATE(
					    current_state.ga_len - 2).si_attr;
			    }
			    else
				cur_si->si_attr = syn_id2attr(syn_id);
			    cur_si->si_id_list = NULL;
			}
		    }
		}
	    }

	    /*
	     * 3. Check for patterns (only if not found a keyword).
	     */
	    if (syn_id == 0 && syn_buf->b_syn_patterns.ga_len)
	    {
		/*
		 * If we didn't check for a match yet, or we are past it, check
		 * for any match with a pattern.
		 */
		if (next_match_idx < 0 || next_match_col < (int)current_col)
		{
		    next_match_idx = 0;		/* no match in this line yet */
		    next_match_col = MAXCOL;	/* no match in this line yet */
		    for (idx = 0; idx < syn_buf->b_syn_patterns.ga_len; ++idx)
		    {
			spp = &(SYN_ITEMS(syn_buf)[idx]);
			if (	   spp->sp_syncing == syncing
				&& (spp->sp_type == SPTYPE_MATCH
				    || spp->sp_type == SPTYPE_START)
				&& ((cur_si == NULL
					&& !(spp->sp_flags & HL_CONTAINED))
				    || (cur_si != NULL
					&& in_item_list(cur_si->si_id_list,
						spp->sp_syn_id,
						spp->sp_flags & HL_CONTAINED))))
			{
			    reg_ic = spp->sp_ic;
			    if (!vim_regexec(spp->sp_prog, line + current_col,
							   current_col == 0))
				continue;

			    /*
			     * Compute the first column of the match.
			     */
			    if ((spp->sp_off_flags & SPO_M_END)
				    && spp->sp_type == SPTYPE_START)
				startcol = (spp->sp_prog->endp[0] - line)
						      - 1 + spp->sp_off_m_end;
			    else
				startcol = (spp->sp_prog->startp[0] - line)
							+ spp->sp_off_m_start;
			    if (startcol < 0)
				startcol = 0;

			    /*
			     * If an existing match is better, skip this one.
			     */
			    if (startcol >= next_match_col)
				continue;
			    /*
			     * If we matched this pattern at this position
			     * before, skip it.  Must retry in the next
			     * column, because it may match from there..
			     */
			    if (did_match_already(idx))
			    {
				try_next_column = TRUE;
				continue;
			    }

			    endp = spp->sp_prog->endp[0];

			    /*
			     * Compute the highlight start now, before the
			     * start/end of the match is lost.
			     */
			    if ((spp->sp_off_flags & SPO_H_END)
				    && spp->sp_type == SPTYPE_START)
				hl_startcol = (endp - line) -
							1 + spp->sp_off_h_end;
			    else
				hl_startcol = (spp->sp_prog->startp[0] - line)
							+ spp->sp_off_h_start;

			    /*
			     * If this is a oneline, the end must be found
			     * too.
			     */
			    flags = 0;
			    if (spp->sp_type == SPTYPE_START
				    && (spp->sp_flags & HL_ONELINE))
				endp = find_endp(idx, endp,
					      endp == line, &hl_endp, &flags);

			    /*
			     * For a "match" the size must be > 0 after the
			     * end offset needs has been added.
			     */
			    else if (spp->sp_type == SPTYPE_MATCH)
			    {
				hl_endp = endp + spp->sp_off_h_end;
				endp += spp->sp_off_m_end;
				if (endp <= line + startcol)
				{
				    /*
				     * If an empty string is matched, may need
				     * to try matching again at next column.
				     */
				    if (spp->sp_prog->startp[0] ==
							spp->sp_prog->endp[0])
					try_next_column = TRUE;
				    continue;
				}
			    }

			    if (endp != NULL)
			    {
				if (hl_startcol < startcol)
				    hl_startcol = startcol;
				if (hl_endp == NULL || hl_endp > endp)
				    hl_endp = endp;

				next_match_idx = idx;
				next_match_col = startcol;
				next_match_m_endcol = endp - line;
				next_match_h_endcol = hl_endp - line;
				next_match_h_startcol = hl_startcol;
				next_match_flags = flags;
			    }
			}
		    }
		}

		/*
		 * If we found a match at the current column, use it.
		 */
		if (next_match_idx >= 0 && next_match_col == (int)current_col)
		{
		    cur_si = push_next_match(cur_si, line);
		    found_match = TRUE;
		}
	    }
	}
    } while (found_match);

    /*
     * Use attributes from the current state.
     */
    if (cur_si != NULL)
    {
	if ((int)current_col >= cur_si->si_h_startcol
				   && (int)current_col <= cur_si->si_h_endcol)
	    current_attr = cur_si->si_attr;
	else if (current_state.ga_len >= 2)
	    current_attr = CUR_STATE(current_state.ga_len - 2).si_attr;
	else
	    current_attr = 0;

	/*
	 * Check for end of current state (and the states before it) at the
	 * next column.
	 */
	++current_col;
	check_state_ends(cur_si, line);
	--current_col;
    }
    else
	current_attr = 0;

    return current_attr;
}

/*
 * Check if we already matched pattern "idx" at the current column.
 */
    static int
did_match_already(idx)
    int	    idx;
{
    int		i;

    for (i = current_state.ga_len; --i >= 0; )
    {
	if (CUR_STATE(i).si_m_startcol == (int)current_col
		&& CUR_STATE(i).si_m_lnum == (int)current_lnum
		&& CUR_STATE(i).si_idx == idx)
	    return TRUE;
    }
    return FALSE;
}

/*
 * Push the next match onto the stack.
 */
    static struct state_item *
push_next_match(cur_si, line)
    struct state_item	*cur_si;
    char_u		*line;
{
    struct syn_pattern	*spp;

    spp = &(SYN_ITEMS(syn_buf)[next_match_idx]);

    /*
     * Push this item in current_state stack;
     */
    if (push_current(next_match_idx) == OK)
    {
	/*
	 * If it's a start-skip-end type that crosses lines, figure out how
	 * much it continues in this line.  Otherwise just fill in the length.
	 */
	cur_si = &CUR_STATE(current_state.ga_len - 1);
	cur_si->si_h_startcol = next_match_h_startcol;
	cur_si->si_m_startcol = current_col;
	cur_si->si_m_lnum = current_lnum;
	if (spp->sp_type == SPTYPE_START && !(spp->sp_flags & HL_ONELINE))
	{
	    update_si_end(cur_si, line, next_match_m_endcol);
	}
	else
	{
	    cur_si->si_m_endcol = next_match_m_endcol - 1;
	    cur_si->si_h_endcol = next_match_h_endcol - 1;
	    cur_si->si_ends = TRUE;
	    cur_si->si_flags = next_match_flags;
	}
	update_si_attr(current_state.ga_len - 1);
    }

    next_match_idx = -1;	/* try other match next time */

    return cur_si;
}

/*
 * Check for end of current state (and the states before it).
 */
    static void
check_state_ends(cur_si, line)
    struct state_item	*cur_si;
    char_u		*line;
{
    int			flags;

    for (;;)
    {
	if (cur_si->si_m_endcol < (int)current_col && cur_si->si_ends)
	{
	    flags = cur_si->si_flags;
	    pop_current();
	    if (current_state.ga_len == 0)
		break;
	    cur_si = &CUR_STATE(current_state.ga_len - 1);

	    /*
	     * Only for a region the search for the end continues after the
	     * end of the contained item.  If the contained match included the
	     * end-of-line, break here, the region always continues.
	     */
	    if (SYN_ITEMS(syn_buf)[cur_si->si_idx].sp_type == SPTYPE_START)
	    {
		update_si_end(cur_si, line, (int)current_col);
		if (flags & HL_HAS_EOL)
		    break;
	    }
	}
	else
	    break;
    }
}

/*
 * Update an entry in the current_state stack for a match or start-skip-end
 * pattern.  This fills in si_attr and si_id_list.
 */
    static void
update_si_attr(idx)
    int	    idx;
{
    struct state_item	*sip = &CUR_STATE(idx);
    struct syn_pattern	*spp;

    spp = &(SYN_ITEMS(syn_buf)[sip->si_idx]);
    sip->si_attr = syn_id2attr(spp->sp_syn_id);
    sip->si_id_list = spp->sp_id_list;

    /*
     * For transparent items, take attr from outer item.
     * Also take id_list, if there is none.
     */
    if (spp->sp_flags & HL_TRANSP)
    {
	if (idx == 0)
	{
	    sip->si_attr = 0;
	    if (sip->si_id_list == NULL)
		sip->si_id_list = ID_LIST_ALL;
	}
	else
	{
	    sip->si_attr = CUR_STATE(idx - 1).si_attr;
	    if (sip->si_id_list == NULL)
		sip->si_id_list = CUR_STATE(idx - 1).si_id_list;
	}
    }
}

/*
 * Update an entry in the current_state stack for a start-skip-end pattern.
 * This finds the end of the current item, if it's in the current line.
 *
 * Return the flags for the matched END.
 */
    static void
update_si_end(sip, line, startcol)
    struct state_item	*sip;
    char_u		*line;
    int			startcol;   /* where to start searching for the end */
{
    char_u		*endp;
    char_u		*hl_endp;

    /*
     * We need to find the end of the match.  It may continue in the next
     * line.
     */
    endp = find_endp(sip->si_idx, line + startcol,
				   startcol == 0, &hl_endp, &(sip->si_flags));
    if (endp == NULL)
    {
	/* continues on next line */
	sip->si_m_endcol = STRLEN(line) - 1;
	sip->si_h_endcol = sip->si_m_endcol;
	if (SYN_ITEMS(syn_buf)[sip->si_idx].sp_flags & HL_ONELINE)
	    sip->si_ends = TRUE;
	else
	    sip->si_ends = FALSE;
    }
    else
    {
	/* match within one line */
	sip->si_m_endcol = endp - line - 1;
	sip->si_h_endcol = hl_endp - line - 1;
	sip->si_ends = TRUE;
    }
}

/*
 * Add a new state to the current state stack.
 * Return FAIL if it's not possible (out of memory).
 */
    static int
push_current(idx)
    int	    idx;
{
    if (ga_grow(&current_state, 1) == FAIL)
	return FAIL;
    CUR_STATE(current_state.ga_len).si_idx = idx;
    ++current_state.ga_len;
    --current_state.ga_room;
    return OK;
}

/*
 * Remove a state from the current_state stack.
 */
    static void
pop_current()
{
    if (current_state.ga_len)
    {
	--current_state.ga_len;
	++current_state.ga_room;
    }
    /* after the end of a pattern, try matching a keyword or pattern */
    next_match_idx = -1;
}

/*
 * Find the end of a start/skip/end pattern match.
 */
    static char_u *
find_endp(idx, sstart, at_bol, hl_endp, flagsp)
    int	    idx;	/* index of the pattern */
    char_u  *sstart;	/* where to start looking for an END match */
    int	    at_bol;	/* if sstart is at begin-of-line */
    char_u  **hl_endp;	/* end column for highlighting */
    int	    *flagsp;	/* flags of matching END */
{
    char_u		*endp;
    struct syn_pattern	*spp, *spp_skip;
    char_u		*p;
    int			start_idx;
    int			best_idx;
    char_u		*best_ptr;

    /*
     * Check for being called with a START pattern.
     * Can happen with a match that continues to the next line, because it
     * contained a region.
     */
    spp = &(SYN_ITEMS(syn_buf)[idx]);
    if (spp->sp_type != SPTYPE_START)
    {
	*hl_endp = sstart;
	return sstart;
    }

    /*
     * Find the SKIP or first END pattern after the last START pattern.
     */
    for (;;)
    {
	spp = &(SYN_ITEMS(syn_buf)[idx]);
	if (spp->sp_type != SPTYPE_START)
	    break;
	++idx;
    }

    /*
     *	Lookup the SKIP pattern (if present)
     */
    if (spp->sp_type == SPTYPE_SKIP)
    {
	spp_skip = spp;
	++idx;
    }
    else
	spp_skip = NULL;

    endp = sstart;	    /* start looking for a match at sstart */
    start_idx = idx;	    /* remember the first END pattern. */
    for (;;)
    {
	best_idx = -1;
	best_ptr = NULL;
	for (idx = start_idx; idx < syn_buf->b_syn_patterns.ga_len; ++idx)
	{
	    spp = &(SYN_ITEMS(syn_buf)[idx]);
	    if (spp->sp_type != SPTYPE_END)	/* past last END pattern */
		break;

	    reg_ic = spp->sp_ic;
	    if (vim_regexec(spp->sp_prog, endp, (at_bol && endp == sstart)))
	    {
		if (best_idx == -1 || spp->sp_prog->startp[0] < best_ptr)
		{
		    best_idx = idx;
		    best_ptr = spp->sp_prog->startp[0];
		}
	    }
	}

	/*
	 * If all end patterns have been tried, and there is no match, the
	 * item continues until end-of-line.
	 */
	if (best_idx == -1)
	    break;

	/*
	 * If the skip pattern matches before the end pattern,
	 * continue searching after the skip pattern.
	 */
	if (	   spp_skip != NULL
		&& (reg_ic = spp_skip->sp_ic,
			vim_regexec(spp_skip->sp_prog, endp,
						  (at_bol && endp == sstart)))
		&& spp_skip->sp_prog->startp[0] <= best_ptr)
	{
	    /* Add offset to skip pattern match */
	    if (spp_skip->sp_off_flags & SPO_M_START)
		p = spp_skip->sp_prog->startp[0] + spp_skip->sp_off_m_start + 1;
	    else
		p = spp_skip->sp_prog->endp[0] + spp_skip->sp_off_m_end;

	    /* take care of an empty match or negative offset */
	    if (p <= endp)
		++endp;
	    else if (p <= spp_skip->sp_prog->endp[0])
		endp = p;
	    else
		/* Be careful not to jump over the NUL at the end-of-line */
		for (endp = spp_skip->sp_prog->endp[0];
					     *endp != NUL && endp < p; ++endp)
		    ;

	    /* if skip pattern includes end-of-line, return here */
	    if (*endp == NUL)
		break;

	    continue;	    /* start with first end pattern again */
	}

	/*
	 * Match from start pattern to end pattern.
	 * Correct for match and highlight offset of end pattern.
	 */
	spp = &(SYN_ITEMS(syn_buf)[best_idx]);
	if (spp->sp_off_flags & SPO_M_START)
	    p = spp->sp_prog->startp[0] + spp->sp_off_m_start + 1;
	else
	    p = spp->sp_prog->endp[0] + spp->sp_off_m_end;
	if (p < sstart)
	    p = sstart;

	if (spp->sp_off_flags & SPO_H_START)
	    endp = spp->sp_prog->startp[0] +
					  spp->sp_off_h_start + 1;
	else
	    endp = spp->sp_prog->endp[0] + spp->sp_off_h_end;
	if (endp < sstart)
	    endp = sstart;
	if (endp > p)
	    endp = p;
	*hl_endp = endp;
	*flagsp = spp->sp_flags;

	return p;
    }

    return NULL;	/* no match for an END pattern in this line */
}

#ifdef OLD_KEYW
/*
 * Check one position in a line for a matching keyword.
 * The caller must check if a keyword can start at startcol.
 * Return it's ID if found, 0 otherwise.
 */
    static int
check_keyword_id(line, startcol, endcol, flags)
    char_u	*line;
    int		startcol;	/* position in line to check for keyword */
    int		*endcol;	/* end position of found keyword */
    int		*flags;		/* flags of matching keyword */
{
    struct kwordtab *ktab;
    struct kwordtab *prev_ktab;
    char_u	    *p;
    int		    round;
    int		    c;

    /*
     * Try twice:
     * 1. matching case
     * 2. ignoring case
     */
    for (round = 1; round <= 2; ++round)
    {
	if (round == 1)
	    ktab = syn_buf->b_first_ktab;
	else
	    ktab = syn_buf->b_first_ktab_ic;
	if (ktab == NULL)
	    continue;
	p = line + startcol;
	for (;;)
	{
	    if (round == 1)
		c = *p;
	    else
		c = TO_UPPER(*p);
	    prev_ktab = ktab;
	    ktab = ktab->table[(c >> 4) & 15];
	    if (ktab == NULL)
		break;
	    ktab = ktab->table[c & 15];
	    if (ktab == NULL)
		break;
	    ++p;
	}

	/*
	 * Found a keyword only when the next char is not a keyword char.
	 */
	if (iswordchar_buf(*p, syn_buf) || prev_ktab->syn_id == 0)
	    continue;

	*endcol = (int)(p - line - 1);
	*flags = prev_ktab->flags;
	return prev_ktab->syn_id;
    }
    return 0;
}
#else

/*
 * Check one position in a line for a matching keyword.
 * The caller must check if a keyword can start at startcol.
 * Return it's ID if found, 0 otherwise.
 */
    static int
check_keyword_id(line, startcol, endcol, flags)
    char_u	*line;
    int		startcol;	/* position in line to check for keyword */
    int		*endcol;	/* last character of found keyword */
    int		*flags;		/* flags of matching keyword */
{
    struct keyentry *ktab;
    char_u	    *p;
    int		    round;
    int		    hash;
    int		    len;
    char_u	    keyword[MAXKEYWLEN + 1]; /* assume max. keyword len is 80 */

    /* Find first character after the keyword */
    p = line + startcol; 
    for (len = 1; iswordchar_buf(p[len], syn_buf); ++len)
	;
    if (len > MAXKEYWLEN)
	return 0;

    /*
     * Must make a copy of the keyword, so we can add a NUL and make it
     * uppercase.
     */
    STRNCPY(keyword, p, len);
    keyword[len] = NUL;

    /*
     * Try twice:
     * 1. matching case
     * 2. ignoring case
     */
    for (round = 1; round <= 2; ++round)
    {
	if ((round == 1 ? syn_buf->b_keywtab : syn_buf->b_keywtab_ic) == NULL)
	    continue;
	p = keyword;
	hash = 0;
	if (round == 1)	/* match case */
	{
	    while (*p)
		hash += *p++;
	    ktab = syn_buf->b_keywtab[hash & KHASH_MASK];
	}
	else /* round == 2, ignore case */
	{
	    while (*p)
	    {
		hash += (*p = TO_UPPER(*p));
		++p;
	    }
	    ktab = syn_buf->b_keywtab_ic[hash & KHASH_MASK];
	}

	for ( ; ktab != NULL; ktab = ktab->next)
	    if (STRCMP(keyword, ktab->keyword) == 0)
		break;

	if (ktab != NULL)
	{
	    *endcol = startcol + len - 1;
	    *flags = ktab->flags;
	    return ktab->syn_id;
	}
    }
    return 0;
}
#endif

/*
 * Handle ":syntax case" command.
 */
/* ARGSUSED */
    static void
syn_cmd_case(arg, nextcomm, syncing)
    char_u  *arg;
    char_u  **nextcomm;
    int	    syncing;	    /* not used */
{
    char_u	*next;

    next = skiptowhite(arg);
    if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5)
	curbuf->b_syn_ic = FALSE;
    else if (STRNICMP(arg, "ignore", 6) == 0 && next - arg == 6)
	curbuf->b_syn_ic = TRUE;
    else
	EMSG2("Illegal argument: %s", arg);

    *nextcomm = check_nextcomm(next);
}

/*
 * Clear all syntax info for one buffer.
 */
    void
syntax_clear(buf)
    BUF	    *buf;
{
    int i;

    curbuf->b_syn_ic = FALSE;	    /* Use case, by default */

    /* free the keywords */
#ifdef OLD_KEYW
    free_kwordtab(buf->b_first_ktab);
    buf->b_first_ktab = NULL;
    free_kwordtab(buf->b_first_ktab_ic);
    buf->b_first_ktab_ic = NULL;
#else
    free_keywtab(buf->b_keywtab);
    buf->b_keywtab = NULL;
    free_keywtab(buf->b_keywtab_ic);
    buf->b_keywtab_ic = NULL;
#endif

    /* free the syntax patterns */
    for (i = buf->b_syn_patterns.ga_len; --i >= 0; )
	syn_clear_pattern(buf, i);
    ga_clear(&buf->b_syn_patterns);

    buf->b_syn_sync_flags = 0;
    buf->b_syn_sync_minlines = 0;
    buf->b_syn_sync_maxlines = 0;

    vim_free(buf->b_syn_linecont_prog);
    buf->b_syn_linecont_prog = NULL;
    vim_free(buf->b_syn_linecont_pat);
    buf->b_syn_linecont_pat = NULL;

    /* free the stored states */
    syn_free_all_states(buf);
}

/*
 * Clear and free one syntax pattern.  When clearing all, must be called from
 * last to first!
 */
    static void
syn_clear_pattern(buf, i)
    BUF	    *buf;
    int	    i;
{
    vim_free(SYN_ITEMS(buf)[i].sp_pattern);
    vim_free(SYN_ITEMS(buf)[i].sp_prog);
    /* Only free sp_id_list of first start pattern of an item! */
    if (i == 0 || SYN_ITEMS(buf)[i - 1].sp_type != SPTYPE_START)
	vim_free(SYN_ITEMS(buf)[i].sp_id_list);
}

/*
 * Handle ":syntax clear" command.
 */
    static void
syn_cmd_clear(arg, nextcomm, syncing)
    char_u  *arg;
    char_u  **nextcomm;
    int	    syncing;
{
    char_u	*arg_end;
    int		id;

    if (ends_excmd(*arg))
    {
	/*
	 * No argument: Clear all syntax items.
	 */
	syntax_clear(curbuf);
    }
    else
    {
	/*
	 * Clear the group IDs that are in the argument.
	 */
	while (!ends_excmd(*arg))
	{
	    arg_end = skiptowhite(arg);
	    id = syn_namen2id(arg, (int)(arg_end - arg));
	    if (id == 0)
	    {
		EMSG2("No such highlight group name: %s", arg);
		break;
	    }
	    else
		syn_clear_one(id, syncing);
	    arg = skipwhite(arg_end);
	}
    }
    *nextcomm = check_nextcomm(arg);
    redraw_curbuf_later(NOT_VALID);
}

/*
 * Clear one syntax group for the current buffer.
 */
    static void
syn_clear_one(id, syncing)
    int	    id;
    int	    syncing;
{
    struct syn_pattern	*spp;
    int			idx;

    /* Clear keywords only when not ":syn sync clear group-name" */
    if (!syncing)
    {
#ifdef OLD_KEYW
	(void)syn_list_recur(id, curbuf->b_first_ktab, 0, FALSE, 0, TRUE);
	(void)syn_list_recur(id, curbuf->b_first_ktab_ic, 0, FALSE, 0, TRUE);
#else
	(void)syn_clear_keyword(id, curbuf->b_keywtab);
	(void)syn_clear_keyword(id, curbuf->b_keywtab_ic);
#endif
    }

    /* clear the patterns for "id" */
    for (idx = curbuf->b_syn_patterns.ga_len; --idx >= 0; )
    {
	spp = &(SYN_ITEMS(curbuf)[idx]);
	if (spp->sp_syn_id != id || spp->sp_syncing != syncing)
	    continue;
	syn_clear_pattern(curbuf, idx);
	vim_memmove(spp, spp + 1, sizeof(struct syn_pattern) *
				   (curbuf->b_syn_patterns.ga_len - idx - 1));
	--curbuf->b_syn_patterns.ga_len;
	--curbuf->b_syn_patterns.ga_room;
    }
}

/*
 * Handle ":syntax [list]" command: list current syntax words.
 */
    static void
syn_cmd_list(arg, nextcomm, syncing)
    char_u  *arg;
    char_u  **nextcomm;
    int	    syncing;	    /* when TRUE: list syncing items */
{
    int	    id;
    char_u  *arg_end;

    if (!syntax_present(curbuf))
    {
	MSG("No Syntax items defined for this buffer");
	return;
    }

    if (syncing)
    {
	if (curbuf->b_syn_sync_flags & SF_CCOMMENT)
	{
	    MSG_PUTS("syncing on C-style comments");
	    if (curbuf->b_syn_sync_minlines || curbuf->b_syn_sync_maxlines)
		syn_lines_msg();
	    return;
	}
	else if (!(curbuf->b_syn_sync_flags & SF_MATCH))
	{
	    MSG_PUTS("syncing starts ");
	    msg_outnum(curbuf->b_syn_sync_minlines);
	    MSG_PUTS(" lines before top line");
	    return;
	}
	MSG_PUTS_TITLE("\n--- Syntax sync items ---");
	if (curbuf->b_syn_sync_minlines || curbuf->b_syn_sync_maxlines)
	{
	    MSG_PUTS("\nsyncing on items");
	    syn_lines_msg();
	}
    }
    else
	MSG_PUTS_TITLE("\n--- Syntax items ---");
    if (ends_excmd(*arg))
    {
	/*
	 * No argument: List all group IDs.
	 */
	for (id = 1; id <= highlight_ga.ga_len && !got_int; ++id)
	    syn_list_one(id, syncing, FALSE);
    }
    else
    {
	/*
	 * List the group IDs that are in the argument.
	 */
	while (!ends_excmd(*arg) && !got_int)
	{
	    arg_end = skiptowhite(arg);
	    id = syn_namen2id(arg, (int)(arg_end - arg));
	    if (id == 0)
		EMSG2("No such highlight group name: %s", arg);
	    else
		syn_list_one(id, syncing, TRUE);
	    arg = skipwhite(arg_end);
	}
    }
    *nextcomm = check_nextcomm(arg);
}

    static void
syn_lines_msg()
{
    MSG_PUTS("; ");
    if (curbuf->b_syn_sync_minlines)
    {
	MSG_PUTS("minimal ");
	msg_outnum(curbuf->b_syn_sync_minlines);
	if (curbuf->b_syn_sync_maxlines)
	    MSG_PUTS(", ");
    }
    if (curbuf->b_syn_sync_maxlines)
    {
	MSG_PUTS("maximal ");
	msg_outnum(curbuf->b_syn_sync_maxlines);
    }
    MSG_PUTS(" lines before top line");
}

/*
 * List one syntax item, for ":syntax" or "syntax list syntax_name".
 */
    static void
syn_list_one(id, syncing, link_only)
    int	    id;
    int	    syncing;	    /* when TRUE: list syncing items */
    int	    link_only;	    /* when TRUE; list link-only too */
{
    int			attr;
    int			idx;
    int			did_header = FALSE;
    int			*p;
    struct syn_pattern	*spp;

    attr = highlight_attr[HLF_D];	    /* highlight like directories */

    /* list the keywords for "id" */
    if (!syncing)
    {
#ifdef OLD_KEYW
	did_header = syn_list_recur(id, curbuf->b_first_ktab, 0,
							  FALSE, attr, FALSE);
	did_header = syn_list_recur(id, curbuf->b_first_ktab_ic, 0,
						     did_header, attr, FALSE);
#else
	did_header = syn_list_keywords(id, curbuf->b_keywtab, FALSE, attr);
	did_header = syn_list_keywords(id, curbuf->b_keywtab_ic,
							    did_header, attr);
#endif
    }

    /* list the patterns for "id" */
    for (idx = 0; idx < curbuf->b_syn_patterns.ga_len; ++idx)
    {
	spp = &(SYN_ITEMS(curbuf)[idx]);
	if (spp->sp_syn_id != id || spp->sp_syncing != syncing)
	    continue;
	syn_list_header(did_header, 999, id);
	did_header = TRUE;
	if (spp->sp_flags & HL_CONTAINED)
	{
	    msg_puts_attr((char_u *)"contained", attr);
	    msg_putchar(' ');
	}
	if (spp->sp_flags & HL_ONELINE)
	{
	    msg_puts_attr((char_u *)"oneline", attr);
	    msg_putchar(' ');
	}
	if (spp->sp_flags & HL_TRANSP)
	{
	    msg_puts_attr((char_u *)"transparent", attr);
	    msg_putchar(' ');
	}
	if (spp->sp_flags & (HL_SYNC_HERE|HL_SYNC_THERE))
	{
	    if (spp->sp_flags & HL_SYNC_HERE)
		msg_puts_attr((char_u *)"grouphere", attr);
	    else
		msg_puts_attr((char_u *)"groupthere", attr);
	    msg_putchar(' ');
	    if (spp->sp_sync_idx >= 0)
		msg_outtrans(HL_TABLE()[SYN_ITEMS(curbuf)
				   [spp->sp_sync_idx].sp_syn_id - 1].sg_name);
	    else
		MSG_PUTS("NONE");
	    msg_putchar(' ');
	}
	if (spp->sp_type == SPTYPE_MATCH)
	{
	    msg_puts_attr((char_u *)"match", attr);
	    msg_putchar(' ');
	    put_pattern(spp);
	}
	else if (spp->sp_type == SPTYPE_START)
	{
	    while (SYN_ITEMS(curbuf)[idx].sp_type == SPTYPE_START)
	    {
		msg_puts_attr((char_u *)"start=", attr);
		put_pattern(&SYN_ITEMS(curbuf)[idx++]);
	    }
	    if (SYN_ITEMS(curbuf)[idx].sp_type == SPTYPE_SKIP)
	    {
		msg_puts_attr((char_u *)"skip=", attr);
		put_pattern(&SYN_ITEMS(curbuf)[idx++]);
	    }
	    while (idx < curbuf->b_syn_patterns.ga_len &&
		    SYN_ITEMS(curbuf)[idx].sp_type == SPTYPE_END)
	    {
		msg_puts_attr((char_u *)"end=", attr);
		put_pattern(&SYN_ITEMS(curbuf)[idx++]);
	    }
	    --idx;
	}
	if (spp->sp_id_list != NULL)
	{
	    msg_puts_attr((char_u *)"contains", attr);
	    msg_putchar(' ');
	    for (p = spp->sp_id_list; *p; ++p)
	    {
		if (*p == CONTAINS_ALLBUT)
		{
		    if (p[1])
			MSG_PUTS("ALLBUT");
		    else
			MSG_PUTS("ALL");
		}
		else
		    msg_outtrans(HL_TABLE()[*p - 1].sg_name);
		msg_putchar(' ');
	    }
	}
    }

    /* list the link, if there is one */
    if (HL_TABLE()[id - 1].sg_link && (did_header || link_only))
    {
	syn_list_header(did_header, 999, id);
	msg_puts_attr((char_u *)"links to", attr);
	msg_putchar(' ');
	msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name);
    }
}

    static void
put_pattern(spp)
    struct syn_pattern	*spp;
{
    long	n;
    int		mask;

    msg_outtrans(spp->sp_pattern);
    for (mask = SPO_FIRST; mask <= SPO_LAST; mask <<= 1)
    {
	if (spp->sp_off_flags & mask)
	{
	    switch (mask)
	    {
		case SPO_H_START:   msg_putchar('s');
				    n = spp->sp_off_h_start;
				    break;
		case SPO_H_END:	    msg_putchar('e');
				    n = spp->sp_off_h_end;
				    break;
		case SPO_M_START:   msg_putchar('S');
				    n = spp->sp_off_m_start;
				    break;
		default:	    msg_putchar('E');
				    n = spp->sp_off_m_end;
				    break;
	    }
	    if (n > 0)
		msg_putchar('+');
	    if (n)
		msg_outnum(n);
	}
    }
    msg_putchar(' ');
}

/*
 * List or clear the keywords for one syntax group.
 * Return TRUE if the header has been printed.
 */
#ifdef OLD_KEYW
    static int
syn_list_recur(id, ktab, len, did_header, attr, clear)
    int		    id;
    struct kwordtab *ktab;
    int		    len;
    int		    did_header;		/* header has already been printed */
    int		    attr;
    int		    clear;		/* when TRUE, clear this ID */
{
    int		    i, j;
    struct kwordtab *ktab2;
    static char_u   namebuf[SYN_NAMELEN];
    int		    outlen;

    if (len == SYN_NAMELEN || ktab == NULL)
	return did_header;

    if (ktab->syn_id == id)
    {
	if (clear)
	{
	    ktab->syn_id = 0;
	    /* TODO: free() this table when it has become empty. */
	}
	else
	{
	    namebuf[len] = NUL;
	    if (ktab->flags & HL_CONTAINED)
		outlen = len + 10;
	    else
		outlen = len;
	    syn_list_header(did_header, outlen, id);
	    did_header = TRUE;
	    if (ktab->flags & HL_CONTAINED)
	    {
		msg_puts_attr((char_u *)"contained", attr);
		msg_putchar(' ');
	    }
	    msg_outtrans(namebuf);
	}
    }

    for (i = 0; i < 16; ++i)
	if (ktab->table[i] != NULL)
	{
	    ktab2 = ktab->table[i];
	    for (j = 0; j < 16; ++j)
		if (ktab2->table[j] != NULL)
		{
		    namebuf[len] = (i << 4) + j;
		    did_header = syn_list_recur(id, ktab2->table[j],
					    len + 1, did_header, attr, clear);
		}
	}
    return did_header;
}

#else

    static int
syn_list_keywords(id, ktabp, did_header, attr)
    int		    id;
    struct keyentry **ktabp;
    int		    did_header;		/* header has already been printed */
    int		    attr;
{
    int		    i;
    int		    outlen;
    struct keyentry *ktab;

    if (ktabp == NULL)
	return did_header;

    /*
     * Unfortunately, this list of keywords is not sorted on alphabet but on
     * hash value.
     */
    for (i = 0; i < KHASH_SIZE; ++i)
    {
	for (ktab = ktabp[i]; ktab != NULL; ktab = ktab->next)
	{
	    if (ktab->syn_id == id)
	    {
		outlen = STRLEN(ktab->keyword);
		if (ktab->flags & HL_CONTAINED)
		    outlen += 10;
		syn_list_header(did_header, outlen, id);
		did_header = TRUE;
		if (ktab->flags & HL_CONTAINED)
		{
		    msg_puts_attr((char_u *)"contained", attr);
		    msg_putchar(' ');
		}
		msg_outtrans(ktab->keyword);
	    }
	}
    }

    return did_header;
}

    static void
syn_clear_keyword(id, ktabp)
    int		    id;
    struct keyentry **ktabp;
{
    int		    i;
    struct keyentry *ktab;
    struct keyentry *ktab_prev;
    struct keyentry *ktab_next;

    if (ktabp == NULL)	    /* no keywords present */
	return;

    for (i = 0; i < KHASH_SIZE; ++i)
    {
	ktab_prev = NULL;
	for (ktab = ktabp[i]; ktab != NULL; )
	{
	    if (ktab->syn_id == id)
	    {
		ktab_next = ktab->next;
		if (ktab_prev == NULL)
		    ktabp[i] = ktab_next;
		else
		    ktab_prev->next = ktab_next;
		vim_free(ktab);
		ktab = ktab_next;
	    }
	    else
	    {
		ktab_prev = ktab;
		ktab = ktab->next;
	    }
	}
    }
}
#endif

/*
 * Recursive function to free() a branch of a kwordtab.
 */
#ifdef OLD_KEYW
    static void
free_kwordtab(ktab)
    struct kwordtab *ktab;
{
    int	    i;

    if (ktab != NULL)
    {
	for (i = 0; i < 16; ++i)
	    free_kwordtab(ktab->table[i]);
	vim_free(ktab);
    }
}
#else
    static void
free_keywtab(ktabp)
    struct keyentry **ktabp;
{
    int		    i;
    struct keyentry *ktab;
    struct keyentry *ktab_next;

    if (ktabp != NULL)
    {
	for (i = 0; i < KHASH_SIZE; ++i)
	    for (ktab = ktabp[i]; ktab != NULL; ktab = ktab_next)
	    {
		ktab_next = ktab->next;
		vim_free(ktab);
	    }
	vim_free(ktabp);
    }
}
#endif

/*
 * Add a keyword to the list of keywords.
 */
#ifdef OLD_KEYW
    static void
add_keyword(name, id, flags)
    char_u	*name;	    /* name of keyword */
    int		id;	    /* group ID for this keyword */
    int		flags;	    /* flags for this keyword */
{
    struct kwordtab	*ktab;
    struct kwordtab	**pp;
    int			c;

    if (curbuf->b_syn_ic)
	pp = &(curbuf->b_first_ktab_ic);
    else
	pp = &(curbuf->b_first_ktab);
    if (*pp == NULL)
    {
	*pp = alloc_kwordtab();
	if (*pp == NULL)
	    return;
    }

    ktab = *pp;
    while (*name)
    {
	if (curbuf->b_syn_ic)
	    c = TO_UPPER(*name);
	else
	    c = *name;
	ktab = add_kwordtab_down(ktab, ((c >> 4) & 15));
	if (ktab == NULL)
	    return;
	ktab = add_kwordtab_down(ktab, (c & 15));
	if (ktab == NULL)
	    return;
	++name;
    }
    ktab->syn_id = id;
    ktab->flags = flags;
}

    static struct kwordtab *
add_kwordtab_down(ktab, n)
    struct kwordtab *ktab;
    int		    n;
{
    struct kwordtab   *next_ktab;

    next_ktab = ktab->table[n];
    if (next_ktab == NULL)
    {
	next_ktab = alloc_kwordtab();
	if (next_ktab == NULL)
	    return NULL;
	ktab->table[n] = next_ktab;
    }
    return next_ktab;
}

    static struct kwordtab *
alloc_kwordtab()
{
    return (struct kwordtab *)alloc_clear((unsigned)sizeof(struct kwordtab));
}

#else

    static void
add_keyword(name, id, flags)
    char_u	*name;	    /* name of keyword */
    int		id;	    /* group ID for this keyword */
    int		flags;	    /* flags for this keyword */
{
    struct keyentry	*ktab;
    struct keyentry	***ktabpp;
    char_u		*p;
    int			hash;

    ktab = (struct keyentry *)alloc(
			       (int)(sizeof(struct keyentry) + STRLEN(name)));
    if (ktab == NULL)
	return;
    STRCPY(ktab->keyword, name);
    ktab->syn_id = id;
    ktab->flags = flags;

    if (curbuf->b_syn_ic)
    {
	for (p = ktab->keyword; *p; ++p)
	    *p = TO_UPPER(*p);
	ktabpp = &curbuf->b_keywtab_ic;
    }
    else
	ktabpp = &curbuf->b_keywtab;

    if (*ktabpp == NULL)
    {
	*ktabpp = (struct keyentry **)alloc_clear(
			       (int)(sizeof(struct keyentry *) * KHASH_SIZE));
	if (*ktabpp == NULL)
	    return;
    }

    hash = 0;
    for (p = ktab->keyword; *p; ++p)
	hash += *p;
    hash &= KHASH_MASK;

    ktab->next = (*ktabpp)[hash];
    (*ktabpp)[hash] = ktab;
}
#endif

/*
 * Handle ":syntax keyword" command.
 */
/* ARGSUSED */
    static void
syn_cmd_keyword(arg, nextcomm, syncing)
    char_u  *arg;
    char_u  **nextcomm;
    int	    syncing;	    /* not used */
{
    char_u	*group_name;
    char_u	*group_name_end;
    int		syn_id;
    char_u	*keyword;
    char_u	*keyword_copy;
    int		flags;
    char_u	*end;

    if (get_group_name(arg, &flags, &group_name,
				     &group_name_end, NULL, &keyword) == FAIL)
    {
	EMSG2("Not enough arguments: \":syntax keyword %s\"", arg);
	return;
    }

    syn_id = syn_check_group(group_name, (int)(group_name_end - group_name));

    keyword_copy = alloc((unsigned)STRLEN(keyword) + 1);
    if (keyword_copy != NULL)
    {
	/*
	 * Isolate each keyword and add an entry for it.
	 */
	while (!ends_excmd(*keyword))
	{
	    end = keyword_copy;
	    while (*keyword && !vim_iswhite(*keyword))
	    {
		if (*keyword == '\\' && keyword[1])
		    ++keyword;
		*end++ = *keyword++;
	    }
	    *end = NUL;
	    add_keyword(keyword_copy, syn_id, flags);
	    keyword = skipwhite(keyword);
	}
	vim_free(keyword_copy);
    }
    *nextcomm = check_nextcomm(keyword);
    redraw_curbuf_later(NOT_VALID);
}

/*
 * Get the start and end of the group name argument.
 * Check for the "contained", "oneline" and "transparent" arguments too.
 * Return FAIL if the end of the command was found instead of further args.
 */
    static int
get_group_name(arg, flagsp, name, name_end, sync_idx, rest)
    char_u	*arg;		/* start of the argument */
    int		*flagsp;	/* flags for contained and transpartent */
    char_u	**name;		/* pointer to start of the name */
    char_u	**name_end;	/* pointer to end of the name */
    int		*sync_idx;	/* syntax item for "grouphere" argument */
    char_u	**rest;		/* pointer to rest of the arguments */
{
    char_u	*p;
    int		flags = 0;
    char_u	*gname_start, *gname;
    int		syn_id;
    int		i;

    *name = arg;
    *name_end = skiptowhite(arg);
    p = skipwhite(*name_end);

    for (;;)
    {
	if (STRNICMP(p, "contained", 9) == 0 &&
					   (p[9] == NUL || vim_iswhite(p[9])))
	{
	    flags |= HL_CONTAINED;
	    p = skipwhite(p + 9);
	}
	else if (STRNICMP(p, "oneline", 7) == 0 &&
					   (p[7] == NUL || vim_iswhite(p[7])))
	{
	    flags |= HL_ONELINE;
	    p = skipwhite(p + 7);
	}
	else if (STRNICMP(p, "transparent", 11) == 0 &&
					 (p[11] == NUL || vim_iswhite(p[11])))
	{
	    flags |= HL_TRANSP;
	    p = skipwhite(p + 11);
	}
	else if ((STRNICMP(p, "grouphere", 9) == 0 &&
					(p[9] == NUL || vim_iswhite(p[9]))) ||
		 (STRNICMP(p, "groupthere", (size_t)10) == 0 &&
					(p[10] == NUL || vim_iswhite(p[10]))))
	{
	    if (sync_idx == NULL)
	    {
		EMSG2("argument not accepted here: %s", p);
		return FAIL;
	    }

	    if (TO_UPPER(p[5]) == 'H')
	    {
		flags |= HL_SYNC_HERE;
		p = skipwhite(p + 9);
	    }
	    else
	    {
		flags |= HL_SYNC_THERE;
		p = skipwhite(p + 10);
	    }
	    gname_start = p;
	    p = skiptowhite(p);
	    if (gname_start == p)
		return FAIL;
	    gname = vim_strnsave(gname_start, (int)(p - gname_start));
	    if (gname == NULL)
		return FAIL;
	    if (STRCMP(gname, "NONE") == 0)
		*sync_idx = NONE_IDX;
	    else
	    {
		syn_id = syn_name2id(gname);
		for (i = 0; i < curbuf->b_syn_patterns.ga_len; ++i)
		    if (SYN_ITEMS(curbuf)[i].sp_syn_id == syn_id &&
				 SYN_ITEMS(curbuf)[i].sp_type == SPTYPE_START)
		    {
			*sync_idx = i;
			break;
		    }
		if (i == curbuf->b_syn_patterns.ga_len)
		{
		    EMSG2("Didn't find region item for %s", gname);
		    vim_free(gname);
		    return FAIL;
		}
	    }

	    vim_free(gname);
	    p = skipwhite(p);
	}
	else
	    break;
    }

    *flagsp = flags;
    *rest = p;

    /*
     * Check if there are enough arguments.  *rest may be a pattern, where '|'
     * is allowed, so only check for NUL.
     */
    if (ends_excmd(*arg) || **rest == NUL)
	return FAIL;
    return OK;
}

/*
 * Handle ":syntax match {name} [contained] [transparent] {pattern}
 *	    [contains {group-name} ..]".
 *
 * Also ":syntax sync match {name} [[grouphere | groupthere] {group-name}] .."
 */
    static void
syn_cmd_match(arg, nextcomm, syncing)
    char_u  *arg;
    char_u  **nextcomm;
    int	    syncing;	    /* if TRUE: ":syntax sync match .. " */
{
    char_u		*group_name;
    char_u		*group_name_end;
    char_u		*rest;
    struct syn_pattern	item;		/* the item found in the line */
    int			*id_list = NULL;
    int			syn_id;
    int			idx;
    int			flags;
    int			sync_idx;

    if (get_group_name(arg, &flags, &group_name, &group_name_end,
			    syncing ? &sync_idx : NULL, &rest) == FAIL ||
							 (flags & HL_ONELINE))

    {
	EMSG("Usage: syntax match {syntax_name} [contained] [transparent] {pattern} [contains {group-name} ..]");
	return;
    }

    init_syn_patterns();
    vim_memset(&item, 0, sizeof(item));

    /*
     * get the pattern.
     */
    rest = get_syn_pattern(rest, &item);
    if (vim_regcomp_had_eol())
	flags |= HL_HAS_EOL;

    /*
     * Check for "contains {syn_name} .." argument.
     */
    id_list = get_id_list(&rest);

    if (rest != NULL)
    {
	/*
	 * Check for trailing command.
	 */
	*nextcomm = check_nextcomm(rest);

	if (!ends_excmd(*rest))
	    EMSG2("Illegal arguments: %s", arg);
	else if (ga_grow(&curbuf->b_syn_patterns, 1) != FAIL &&
		    (syn_id = syn_check_group(group_name,
				    (int)(group_name_end - group_name))) != 0)
	{
	    /*
	     * Store the pattern in the syn_items list
	     */
	    idx = curbuf->b_syn_patterns.ga_len;
	    SYN_ITEMS(curbuf)[idx] = item;
	    SYN_ITEMS(curbuf)[idx].sp_syncing = syncing;
	    SYN_ITEMS(curbuf)[idx].sp_type = SPTYPE_MATCH;
	    SYN_ITEMS(curbuf)[idx].sp_syn_id = syn_id;
	    SYN_ITEMS(curbuf)[idx].sp_id_list = id_list;
	    SYN_ITEMS(curbuf)[idx].sp_flags = flags;
	    SYN_ITEMS(curbuf)[idx].sp_sync_idx = sync_idx;
	    ++curbuf->b_syn_patterns.ga_len;
	    --curbuf->b_syn_patterns.ga_room;

	    /* remember that we found a match for syncing on */
	    if (flags & (HL_SYNC_HERE|HL_SYNC_THERE))
		curbuf->b_syn_sync_flags |= SF_MATCH;

	    redraw_curbuf_later(NOT_VALID);
	    return;	/* don't free the progs and patterns now */
	}
    }

    /*
     * Something failed, free the allocated memory.
     */
    vim_free(item.sp_prog);
    vim_free(item.sp_pattern);
    vim_free(id_list);
}

/*
 * Handle ":syntax region {group-name} [contained] [oneline] [transparent]
 *		start {start} .. [skip {skip}] end {end} ..
 *		[contains {syntax-ID} ..]".
 */
    static void
syn_cmd_region(arg, nextcomm, syncing)
    char_u  *arg;
    char_u  **nextcomm;
    int	    syncing;	    /* TRUE if ":syntax sync region .." */
{
    char_u		*group_name;
    char_u		*group_name_end;
    char_u		*rest;			/* next arg, NULL on error */
    char_u		*key_end;
    char_u		*key = NULL;
    int			item;
#define ITEM_START  0
#define ITEM_SKIP   1
#define ITEM_END    2
    struct pat_ptr
    {
	struct syn_pattern	*pp_synp;	/* pointer to syn_pattern */
	struct pat_ptr		*pp_next;	/* pointer to next pat_ptr */
    }			*(pat_ptrs[3]);
					    /* patterns found in the line */
    struct pat_ptr	*ppp, **pppp;
    struct pat_ptr	*ppp_next;
    int			pat_count = 0;	    /* number of syn_patterns found */
    int			*id_list = NULL;
    int			syn_id;
    int			not_enough = FALSE;	/* not enough arguments */
    int			illegal = FALSE;	/* illegal arguments */
    int			idx;
    int			flags;
    int			success = FALSE;

    if (get_group_name(arg, &flags, &group_name,
					&group_name_end, NULL, &rest) == FAIL)

    {
	EMSG("Usage: syntax region {group-name} [contained] [transparent] [oneline] start {start_pat} [skip {skip_pat}] end {end_pat} [contains {group-name} ..]");
	return;
    }

    pat_ptrs[0] = NULL;
    pat_ptrs[1] = NULL;
    pat_ptrs[2] = NULL;

    init_syn_patterns();

    /*
     * get the patterns.
     */
    while (rest != NULL && !ends_excmd(*rest))
    {
	key_end = rest;
	while (*key_end && !vim_iswhite(*key_end) && *key_end != '=')
	    ++key_end;
	vim_free(key);
	key = vim_strnsave(rest, (int)(key_end - rest));
	if (key == NULL)			/* out of memory */
	{
	    rest = NULL;
	    break;
	}
	if (STRICMP(key, "start") == 0)
	    item = ITEM_START;
	else if (STRICMP(key, "end") == 0)
	    item = ITEM_END;
	else if (STRICMP(key, "skip") == 0)
	{
	    if (pat_ptrs[ITEM_SKIP] != NULL)	/* one skip pattern allowed */
	    {
		illegal = TRUE;
		break;
	    }
	    item = ITEM_SKIP;
	}
	else
	    break;
	rest = skipwhite(key_end);
	if (*rest != '=')
	{
	    rest = NULL;
	    EMSG2("Missing '=': %s", arg);
	    break;
	}
	rest = skipwhite(rest + 1);
	if (*rest == NUL)
	{
	    not_enough = TRUE;
	    break;
	}

	/*
	 * Allocate room for a syn_pattern, and link it in the list of
	 * syn_patterns for this item, at the end.
	 */
	ppp = (struct pat_ptr *)alloc((unsigned)sizeof(struct pat_ptr));
	if (ppp == NULL)
	{
	    rest = NULL;
	    break;
	}
	for (pppp = &(pat_ptrs[item]); *pppp != NULL;
						   pppp = &((*pppp)->pp_next))
	    ;
	*pppp = ppp;
	ppp->pp_next = NULL;
	ppp->pp_synp = (struct syn_pattern *)alloc_clear(
					(unsigned)sizeof(struct syn_pattern));
	if (ppp->pp_synp == NULL)
	{
	    rest = NULL;
	    break;
	}

	/*
	 * Get the syntax pattern and the following offset(s).
	 */
	rest = get_syn_pattern(rest, ppp->pp_synp);
	if (item == ITEM_END && vim_regcomp_had_eol())
	    ppp->pp_synp->sp_flags |= HL_HAS_EOL;
	++pat_count;
    }
    vim_free(key);
    if (illegal || not_enough)
	rest = NULL;

    /*
     * Must have a "start" and "end" pattern.
     */
    if (rest != NULL && (pat_ptrs[ITEM_START] == NULL ||
						  pat_ptrs[ITEM_END] == NULL))
    {
	not_enough = TRUE;
	rest = NULL;
    }

    /*
     * Check for "contains {syn_name} .." argument.
     */
    id_list = get_id_list(&rest);

    if (rest != NULL)
    {
	/*
	 * Check for trailing garbage or command.
	 */
	if (!ends_excmd(*rest))
	    illegal = TRUE;
	else
	{
	    *nextcomm = check_nextcomm(rest);

	    if (ga_grow(&(curbuf->b_syn_patterns), pat_count) != FAIL &&
		    (syn_id = syn_check_group( group_name,
				    (int)(group_name_end - group_name))) != 0)
	    {
		/*
		 * Store the start/skip/end in the syn_items list
		 */
		idx = curbuf->b_syn_patterns.ga_len;
		for (item = ITEM_START; item <= ITEM_END; ++item)
		{
		    for (ppp = pat_ptrs[item]; ppp != NULL; ppp = ppp->pp_next)
		    {
			SYN_ITEMS(curbuf)[idx] = *(ppp->pp_synp);
			SYN_ITEMS(curbuf)[idx].sp_syncing = syncing;
			SYN_ITEMS(curbuf)[idx].sp_type =
			    (item == ITEM_START) ? SPTYPE_START :
			    (item == ITEM_SKIP) ? SPTYPE_SKIP : SPTYPE_END;
			SYN_ITEMS(curbuf)[idx].sp_flags |= flags;
			SYN_ITEMS(curbuf)[idx].sp_syn_id = syn_id;
			if (item == ITEM_START)
			    SYN_ITEMS(curbuf)[idx].sp_id_list = id_list;
			++curbuf->b_syn_patterns.ga_len;
			--curbuf->b_syn_patterns.ga_room;
			++idx;
		    }
		}

		redraw_curbuf_later(NOT_VALID);
		success = TRUE;	    /* don't free the progs and patterns now */
	    }
	}
    }

    /*
     * Free the allocated memory.
     */
    for (item = ITEM_START; item <= ITEM_END; ++item)
	for (ppp = pat_ptrs[item]; ppp != NULL; ppp = ppp_next)
	{
	    if (!success)
	    {
		vim_free(ppp->pp_synp->sp_prog);
		vim_free(ppp->pp_synp->sp_pattern);
	    }
	    vim_free(ppp->pp_synp);
	    ppp_next = ppp->pp_next;
	    vim_free(ppp);
	}

    if (!success)
    {
	vim_free(id_list);
	if (illegal)
	    EMSG2("Illegal arguments: syntax region %s", arg);
	if (not_enough)
	    EMSG2("Not enough arguments: syntax region %s", arg);
    }
}

/*
 * On first call for current buffer: Init growing array.
 */
    static void
init_syn_patterns()
{
    curbuf->b_syn_patterns.ga_itemsize = sizeof(struct syn_pattern);
    curbuf->b_syn_patterns.ga_growsize = 10;
}

/*
 * Get one pattern for a ":syntax match" or ":syntax region" command.
 * Stores the pattern and program in a struct syn_pattern.
 * Returns a pointer to the next argument, or NULL in case of an error.
 */
    static char_u *
get_syn_pattern(arg, ci)
    char_u		*arg;
    struct syn_pattern	*ci;
{
    char_u	*end;
    int		*p;

    if (arg[1] == NUL || arg[2] == NUL)	    /* need at least three chars */
	return NULL;

    end = skip_regexp(arg + 1, *arg, TRUE);
    if (*end != *arg)			    /* end delimiter not found */
    {
	EMSG2("Pattern delimiter not found: %s", arg);
	return NULL;
    }
    /* store the pattern and compiled regexp program */
    if ((ci->sp_pattern = vim_strnsave(arg + 1, (int)(end - arg - 1))) == NULL)
	return NULL;
    if ((ci->sp_prog = vim_regcomp(ci->sp_pattern, TRUE)) == NULL)
	return NULL;
    ci->sp_ic = curbuf->b_syn_ic;

    /*
     * Check for a highlighting offset from 's'tart ('b'egin) and/or 'e'nd.
     * Check for a text match offset from 'S'tart ('B'egin) and/or 'E'nd.
     */
    ++end;
    while (vim_strchr((char_u *)"bseBSE", *end) != NULL)
    {
	switch (*end++)
	{
	    case 'b':
	    case 's':	p = &(ci->sp_off_h_start);  /* highl. off from start */
			ci->sp_off_flags |= SPO_H_START;
			break;
	    case 'e':	p = &(ci->sp_off_h_end);    /* highl. off from end */
			ci->sp_off_flags |= SPO_H_END;
			break;
	    case 'B':
	    case 'S':	p = &(ci->sp_off_m_start);  /* match off from start */
			ci->sp_off_flags |= SPO_M_START;
			break;
	    default:	p = &(ci->sp_off_m_end);    /* match off from end */
			ci->sp_off_flags |= SPO_M_END;
			break;
	}
	if (*end == '+')
	{
	    ++end;
	    *p = getdigits(&end);		/* positive offset */
	}
	else if (*end == '-')
	{
	    ++end;
	    *p = -getdigits(&end);		/* negative offset */
	}
    }

    if (!ends_excmd(*end) && !vim_iswhite(*end))
    {
	EMSG2("Garbage after pattern: %s", arg);
	return NULL;
    }
    return skipwhite(end);
}

/*
 * Handle ":syntax sync .." command.
 */
/* ARGSUSED */
    static void
syn_cmd_sync(arg_start, nextcomm, syncing)
    char_u  *arg_start;
    char_u  **nextcomm;
    int	    syncing;	    /* not used */
{
    char_u  *arg_end;
    char_u  *key = NULL;
    char_u  *next_arg;
    int	    illegal = FALSE;
    int	    finished = FALSE;

    if (ends_excmd(*arg_start))
    {
	syn_cmd_list(arg_start, nextcomm, TRUE);
	return;
    }

    while (!ends_excmd(*arg_start))
    {
	arg_end = skiptowhite(arg_start);
	next_arg = skipwhite(arg_end);
	vim_free(key);
	key = vim_strnsave(arg_start, (int)(arg_end - arg_start));
	if (STRICMP(key, "ccomment") == 0)
	{
	    curbuf->b_syn_sync_flags |= SF_CCOMMENT;
	    if (!ends_excmd(*next_arg))
	    {
		arg_end = skiptowhite(next_arg);
		curbuf->b_syn_sync_id = syn_check_group(next_arg,
						   (int)(arg_end - next_arg));
		next_arg = skipwhite(arg_end);
	    }
	    else
		curbuf->b_syn_sync_id = syn_name2id((char_u *)"Comment");
	}
	else if (  STRNICMP(key, "lines", 5) == 0
		|| STRNICMP(key, "minlines", 8) == 0
		|| STRNICMP(key, "maxlines", 8) == 0)
	{
	    if (TO_UPPER(key[0]) == 'L')
		arg_end = key + 6;
	    else
		arg_end = key + 9;
	    if (arg_end[-1] != '=' || !isdigit(*arg_end))
	    {
		illegal = TRUE;
		break;
	    }
	    if (TO_UPPER(key[1]) == 'A')
		curbuf->b_syn_sync_maxlines = getdigits(&arg_end);
	    else
		curbuf->b_syn_sync_minlines = getdigits(&arg_end);
	}
	else if (STRICMP(key, "linecont") == 0)
	{
	    if (curbuf->b_syn_linecont_pat != NULL)
	    {
		EMSG("syntax sync: line continuations pattern specified twice");
		finished = TRUE;
		break;
	    }
	    arg_end = skip_regexp(next_arg + 1, *next_arg, TRUE);
	    if (*arg_end != *next_arg)	    /* end delimiter not found */
	    {
		illegal = TRUE;
		break;
	    }

	    /* store the pattern and compiled regexp program */
	    if ((curbuf->b_syn_linecont_pat = vim_strnsave(next_arg + 1,
				      (int)(arg_end - next_arg - 1))) == NULL)
	    {
		finished = TRUE;
		break;
	    }
	    curbuf->b_syn_linecont_ic = curbuf->b_syn_ic;
	    if ((curbuf->b_syn_linecont_prog =
		       vim_regcomp(curbuf->b_syn_linecont_pat, TRUE)) == NULL)
	    {
		vim_free(curbuf->b_syn_linecont_pat);
		curbuf->b_syn_linecont_pat = NULL;
		finished = TRUE;
		break;
	    }
	    next_arg = skipwhite(arg_end + 1);
	}
	else if (STRICMP(key, "match") == 0)
	{
	    syn_cmd_match(next_arg, nextcomm, TRUE);
	    finished = TRUE;
	    break;
	}
	else if (STRICMP(key, "region") == 0)
	{
	    syn_cmd_region(next_arg, nextcomm, TRUE);
	    finished = TRUE;
	    break;
	}
	else
	{
	    illegal = TRUE;
	    break;
	}
	arg_start = next_arg;
    }
    vim_free(key);
    if (illegal)
	EMSG2("Illegal arguments: %s", arg_start);
    else if (!finished)
    {
	*nextcomm = check_nextcomm(arg_start);
	redraw_curbuf_later(NOT_VALID);
    }
}

/*
 * Convert a line of highlight group names into a list of group ID numbers.
 * "arg" should point to the "contains" keyword.
 * "arg" is moved to after the last name.
 */
    static int *
get_id_list(arg)
    char_u	**arg;
{
    char_u	*p;
    char_u	*end;
    int		round;
    int		count;
    int		*retval = NULL;
    char_u	*name;
    vim_regexp	*prog;
    int		id;
    int		i;
    int		failed = FALSE;

    if (*arg == NULL)		    /* detected some error already */
	return NULL;

    if (STRNICMP(*arg, "contains", 8) != 0 || !vim_iswhite((*arg)[8]))
	return NULL;

    /*
     * We parse the list twice:
     * round == 1: count the number of items, allocate the array.
     * round == 2: fill the array with the items.
     */
    for (round = 1; round <= 2; ++round)
    {
	/*
	 * skip "contains"
	 */
	p = skipwhite(*arg + 8);
	if (ends_excmd(*p))
	{
	    EMSG("Missing argument after \"contains\"");
	    break;
	}

	/*
	 * parse the arguments after "contains"
	 */
	count = 0;
	while (!ends_excmd(*p))
	{
	    end = skiptowhite(p);
	    name = alloc((int)(end - p + 3));	    /* leave room for "^$" */
	    if (name == NULL)
	    {
		failed = TRUE;
		break;
	    }
	    STRNCPY(name + 1, p, end - p);
	    name[end - p + 1] = NUL;
	    if (       STRCMP(name + 1, "ALLBUT") == 0
		    || STRCMP(name + 1, "ALL") == 0)
	    {
		if (count != 0)
		{
		    EMSG2("%s must be first in contains list", name + 1);
		    failed = TRUE;
		    break;
		}
		id = CONTAINS_ALLBUT;
	    }
	    else
	    {
		/*
		 * Handle full group name.
		 */
		if (vim_strpbrk(name + 1, (char_u *)"\\.*^$~[") == NULL)
		    id = syn_check_group(name + 1, (int)(end - p));
		else
		{
		    /*
		     * Handle match of regexp with group names.
		     */
		    *name = '^';
		    STRCAT(name, "$");
		    prog = vim_regcomp(name, TRUE);
		    if (prog == NULL)
		    {
			failed = TRUE;
			break;
		    }

		    reg_ic = TRUE;
		    id = 0;
		    for (i = highlight_ga.ga_len; --i >= 0; )
		    {
			if (vim_regexec(prog, HL_TABLE()[i].sg_name, TRUE))
			{
			    if (round == 2)
				retval[count] = i + 1;
			    ++count;
			    id = -1;	    /* remember that we found one */
			}
		    }
		    vim_free(prog);
		}
	    }
	    vim_free(name);
	    if (id == 0)
	    {
		EMSG2("Unknown group name: %s", p);
		failed = TRUE;
		break;
	    }
	    if (id > 0)
	    {
		if (round == 2)
		    retval[count] = id;
		++count;
	    }
	    p = skipwhite(end);
	}
	if (failed)
	    break;
	if (round == 1)
	{
	    retval = (int *)alloc((unsigned)((count + 1) * sizeof(int)));
	    if (retval == NULL)
		break;
	    retval[count] = 0;	    /* zero means end of the list */
	}
    }
    *arg = p;
    if (failed)
    {
	vim_free(retval);
	retval = NULL;
    }
    if (retval == NULL)
	*arg = NULL;

    return retval;
}

/*
 * Check if "id" is in the "contains" list of pattern "idx".
 */
    static int
in_item_list(id_list, id, contained)
    int	    *id_list;	    /* id list */
    int	    id;		    /* group id */
    int	    contained;	    /* group id is contained */
{
    /*
     * If id_list is ID_LIST_ALL, we are in a transparent item that isn't
     * inside anything.  Only allow not-contained groups.
     */
    if (id_list == ID_LIST_ALL)
	return !contained;

    /*
     * If the first item is "ALLBUT", return TRUE if id is NOT in the contains
     * list.
     */
    if (*id_list == CONTAINS_ALLBUT)
    {
	++id_list;
	while (*id_list)
	    if (*id_list++ == id)
		return FALSE;
	return TRUE;
    }

    /*
     * Return TRUE if id is in the contains list.
     */
    while (*id_list)
	if (*id_list++ == id)
	    return TRUE;
    return FALSE;
}

struct subcommand
{
    char    *name;					/* subcommand name */
    void    (*func)__ARGS((char_u *, char_u **, int));	/* function to call */
};

static struct subcommand subcommands[] =
{
    {"case",		syn_cmd_case},
    {"clear",		syn_cmd_clear},
    {"keyword",		syn_cmd_keyword},
    {"list",		syn_cmd_list},
    {"match",		syn_cmd_match},
    {"region",		syn_cmd_region},
    {"sync",		syn_cmd_sync},
    {"",		syn_cmd_list},
    {NULL, NULL}
};

/*
 * Handle the ":syntax" command.
 * This searches the subcommands[] table for the subcommand name, and calls a
 * syntax_subcommand() function to do the rest.
 */
    void
do_syntax(arg, nextcomm)
    char_u  *arg;
    char_u  **nextcomm;
{
    char_u  *subcmd_end;
    char_u  *subcmd_name;
    int	    i;

    if (ends_excmd(*arg))
	subcmd_end = arg;
    else
	subcmd_end = skiptowhite(arg);		/* isolate subcommand name */
    subcmd_name = vim_strnsave(arg, (int)(subcmd_end - arg));
    if (subcmd_name != NULL)
    {
	for (i = 0; ; ++i)
	{
	    if (subcommands[i].name == NULL)
	    {
		EMSG2("Invalid :syntax subcommand: %s", subcmd_name);
		break;
	    }
	    if (STRICMP(subcmd_name, (char_u *)subcommands[i].name) == 0)
	    {
		(subcommands[i].func)(skipwhite(subcmd_end), nextcomm, FALSE);
		break;
	    }
	}
	vim_free(subcmd_name);
    }
}

#ifdef OLD_KEYW
    int
syntax_present(buf)
    BUF	    *buf;
{
    return (buf->b_syn_patterns.ga_len != 0
	    || curbuf->b_first_ktab != NULL
	    || curbuf->b_first_ktab_ic != NULL);
}
#else
    int
syntax_present(buf)
    BUF	    *buf;
{
    return (buf->b_syn_patterns.ga_len != 0
	    || curbuf->b_keywtab != NULL
	    || curbuf->b_keywtab_ic != NULL);
}
#endif

#endif /* SYNTAX_HL */


/**********************************************************************
 *  Highlighting stuff						      *
 **********************************************************************/

/*
 * The default highlight groups.  Used in the 'highlight' and 'guicursor'
 * options default.
 */
char	*(highlight_init[]) =
	{
#ifdef USE_GUI_WIN32
	    "Normal guibg=White guifg=Black",
#else
	    "Normal gui=NONE",	    /* X11 gets the defaults from X resources */
#endif
#ifdef USE_GUI
	    "Cursor guibg=Green",
#endif
	    "SpecialKey term=bold ctermfg=Blue guifg=Blue",
	    "WinEnd term=bold cterm=bold guibg=LightGrey",
	    "Directory term=bold ctermfg=Blue guifg=Blue",
	    "ErrorMsg term=standout ctermbg=LightRed guibg=Orange",
	    "Search term=reverse ctermbg=Yellow guibg=Yellow",
	    "MoreMsg term=bold cterm=bold ctermfg=Green gui=bold guifg=SeaGreen",
	    "ModeMsg term=bold cterm=bold gui=bold",
	    "LineNr term=underline ctermfg=Brown guifg=Brown",
	    "Question term=standout cterm=bold ctermfg=Green gui=bold guifg=SeaGreen",
	    "StatusLine term=reverse cterm=reverse gui=reverse",
	    "Title term=bold cterm=bold ctermfg=Blue gui=bold guifg=Blue",
	    "Visual term=reverse cterm=reverse gui=reverse",
	    "WarningMsg term=standout ctermfg=Red guifg=Red",
	};

    void
init_highlight()
{
    int	    i;

    for (i = 0; i < sizeof(highlight_init) / sizeof(char *); ++i)
	do_highlight((char_u *)highlight_init[i], FALSE);
}

/*
 * Handle the ":highlight .." command.
 */
    void
do_highlight(line, forceit)
    char_u	*line;
    int		forceit;
{
    char_u	    *name_end;
    char_u	    *p;
    char_u	    *linep;
    char_u	    *key_start;
    char_u	    *arg_start;
    char_u	    *key = NULL, *arg = NULL;
    int		    i;
    int		    off;
    int		    len;
    int		    attr;
    int		    id;
    int		    idx;
    int		    error = FALSE;
    int		    color;
    int		    is_normal_group = FALSE;	/* "Normal" group */

    /*
     * If no argument, list current highlighting.
     */
    if (ends_excmd(*line))
    {
	for (i = 1; i <= highlight_ga.ga_len && !got_int; ++i)
	    /* TODO: only call when the group has attributes set */
	    highlight_list_one(i);
	return;
    }

    /*
     * Isolate the name.
     */
    name_end = skiptowhite(line);
    linep = skipwhite(name_end);

    /*
     * ":highlight {group-name}": list highlighting for one group.
     */
    if (ends_excmd(*linep))
    {
	id = syn_namen2id(line, (int)(name_end - line));
	if (id == 0)
	    EMSG2("highlight group not found: %s", line);
	else
	    highlight_list_one(id);
	return;
    }

    /*
     * Handle ":highlight link {from} {to}" command.
     */
    if (STRNCMP(line, "link", name_end - line) == 0)
    {
	char_u	    *from_start = linep;
	char_u	    *from_end;
	char_u	    *to_start;
	char_u	    *to_end;
	int	    from_id;
	int	    to_id;

	from_end = skiptowhite(from_start);
	to_start = skipwhite(from_end);
	to_end	 = skiptowhite(to_start);

	if (ends_excmd(*from_start) || ends_excmd(*to_start))
	{
	    EMSG2("Not enough arguments: \":highlight link %s\"", from_start);
	    return;
	}

	if (!ends_excmd(*skipwhite(to_end)))
	{
	    EMSG2("Too many arguments: \":highlight link %s\"", from_start);
	    return;
	}

	from_id = syn_check_group(from_start, (int)(from_end - from_start));
	if (STRNCMP(to_start, "NONE", 4) == 0)
	    to_id = 0;
	else
	    to_id = syn_check_group(to_start, (int)(to_end - to_start));

	if (from_id > 0)
	{
	    /*
	     * Don't allow a link when there already is some highlighting
	     * for the group, unless '!' is used
	     */
	    if (to_id > 0 && !forceit
		    &&	  (HL_TABLE()[from_id - 1].sg_term_attr != 0
			|| HL_TABLE()[from_id - 1].sg_cterm_attr != 0
#ifdef USE_GUI
			|| HL_TABLE()[from_id - 1].sg_gui_attr != 0
#endif
		       ))
	    {
		if (sourcing_name == NULL)
		    EMSG("group has settings, highlight link ignored");
	    }
	    else
		HL_TABLE()[from_id - 1].sg_link = to_id;
	}

	redraw_curbuf_later(NOT_VALID);
	return;
    }


    /*
     * Find the group name in the table.  If it does not exist yet, add it.
     */
    id = syn_check_group(line, (int)(name_end - line));
    if (id == 0)			/* failed (out of memory) */
	return;
    idx = id - 1;			/* index is ID minus one */
    if (STRICMP(HL_TABLE()[idx].sg_name, "Normal") == 0)
	is_normal_group = TRUE;

    while (!ends_excmd(*linep))
    {
	key_start = linep;
	if (*linep == '=')
	{
	    EMSG2("unexpected equal sign: %s", key_start);
	    error = TRUE;
	    break;
	}

	/*
	 * Isolate the key ("term", "ctermfg", "ctermbg", "font", "guifg" or
	 * "guibg").
	 */
	while (*linep && !vim_iswhite(*linep) && *linep != '=')
	    ++linep;
	vim_free(key);
	key = vim_strnsave(key_start, (int)(linep - key_start));
	if (key == NULL)
	{
	    error = TRUE;
	    break;
	}
	linep = skipwhite(linep);

	if (STRICMP(key, "NONE") == 0)
	{
	    HL_TABLE()[idx].sg_term = 0;
	    vim_free(HL_TABLE()[idx].sg_start);
	    HL_TABLE()[idx].sg_start = NULL;
	    vim_free(HL_TABLE()[idx].sg_stop);
	    HL_TABLE()[idx].sg_stop = NULL;
	    HL_TABLE()[idx].sg_term_attr = 0;
	    HL_TABLE()[idx].sg_cterm = 0;
	    HL_TABLE()[idx].sg_cterm_fg = 0;
	    HL_TABLE()[idx].sg_cterm_bg = 0;
	    HL_TABLE()[idx].sg_cterm_attr = 0;
#ifdef USE_GUI	    /* in non-GUI fonts are simply ignored */
	    HL_TABLE()[idx].sg_gui = 0;
	    HL_TABLE()[idx].sg_gui_fg = 0;
	    vim_free(HL_TABLE()[idx].sg_gui_fg_name);
	    HL_TABLE()[idx].sg_gui_fg_name = NULL;
	    HL_TABLE()[idx].sg_gui_bg = 0;
	    vim_free(HL_TABLE()[idx].sg_gui_bg_name);
	    HL_TABLE()[idx].sg_gui_bg_name = NULL;
	    HL_TABLE()[idx].sg_font = 0;
	    vim_free(HL_TABLE()[idx].sg_font_name);
	    HL_TABLE()[idx].sg_font_name = NULL;
	    HL_TABLE()[idx].sg_gui_attr = 0;
#endif
	    continue;
	}

	/*
	 * Check for the equal sign.
	 */
	if (*linep != '=')
	{
	    EMSG2("missing equal sign: %s", key_start);
	    error = TRUE;
	    break;
	}
	++linep;

	/*
	 * Isolate the argument.
	 */
	linep = skipwhite(linep);
	arg_start = linep;
	linep = skiptowhite(linep);
	if (linep == arg_start)
	{
	    EMSG2("missing argument: %s", key_start);
	    error = TRUE;
	    break;
	}
	vim_free(arg);
	arg = vim_strnsave(arg_start, (int)(linep - arg_start));
	if (arg == NULL)
	{
	    error = TRUE;
	    break;
	}

	/*
	 * Store the argument.
	 */
	if (  STRICMP(key, "term") == 0
		|| STRICMP(key, "cterm") == 0
		|| STRICMP(key, "gui") == 0)
	{
	    attr = 0;
	    off = 0;
	    while (arg[off] != NUL)
	    {
		for (i = sizeof(hl_attr_table) / sizeof(int); --i >= 0; )
		{
		    len = STRLEN(hl_name_table[i]);
		    if (STRNICMP(arg + off, hl_name_table[i], len) == 0)
		    {
			attr |= hl_attr_table[i];
			off += len;
			break;
		    }
		}
		if (i < 0)
		{
		    EMSG2("Illegal value: %s", arg);
		    error = TRUE;
		    break;
		}
		if (arg[off] == ',')		/* another one follows */
		    ++off;
	    }
	    if (error)
		break;
	    if (TO_UPPER(*key) == 'T')
		HL_TABLE()[idx].sg_term = attr;
	    else if (TO_UPPER(*key) == 'C')
		HL_TABLE()[idx].sg_cterm = attr;
#ifdef USE_GUI
	    else
		HL_TABLE()[idx].sg_gui = attr;
#endif
	}
	else if (STRICMP(key, "font") == 0)
	{
#ifdef USE_GUI	    /* in non-GUI fonts are simply ignored */
	    if (is_normal_group)
		EMSG("Use 'guifont' option to set normal font");
	    else
	    {
		HL_TABLE()[idx].sg_font = font_name2handle(arg);
		vim_free(HL_TABLE()[idx].sg_font_name);
		HL_TABLE()[idx].sg_font_name = vim_strsave(arg);
	    }
#endif
	}
	else if (STRICMP(key, "ctermfg") == 0 || STRICMP(key, "ctermbg") == 0)
	{
	    if (isdigit(*arg))
		color = atoi((char *)arg);
	    else
	    {
		static char *(color_names[26]) = {
			    "Black", "DarkBlue", "DarkGreen", "DarkCyan",
			    "DarkRed", "DarkMagenta", "Brown", "Gray", "Grey",
			    "LightGray", "LightGrey", "DarkGray", "DarkGrey",
			    "Blue", "LightBlue", "Green", "LightGreen",
			    "Cyan", "LightCyan", "Red", "LightRed", "Magenta",
			    "LightMagenta", "Yellow", "White", "NONE"};
		static int color_numbers[26] = {0, 1, 2, 3,
						 4, 5, 6, 7, 7,
						 7, 7, 8, 8,
						 9, 9, 10, 10,
						 11, 11, 12, 12, 13,
						 13, 14, 15, -1};

		for (i = (sizeof(color_names) / sizeof(char *)); --i >= 0; )
		    if (STRICMP(arg, color_names[i]) == 0)
			break;
		if (i < 0)
		{
		    EMSG2("Color name or number not recognized: %s", key_start);
		    error = TRUE;
		    break;
		}
		color = color_numbers[i];
	    }
	    /* Add one to the argument, to avoid zero */
	    if (TO_UPPER(key[5]) == 'F')
		HL_TABLE()[idx].sg_cterm_fg = color + 1;
	    else
		HL_TABLE()[idx].sg_cterm_bg = color + 1;
#if 0
	    if (is_normal_group)
	    /* TODO: set foreground/background colors for cterm */
#endif
	}
	else if (STRICMP(key, "guifg") == 0)
	{
#ifdef USE_GUI	    /* in non-GUI guifg colors are simply ignored */
	    /* Add one to the argument, to avoid zero */
	    i = color_name2handle(arg) + 1;
	    if (i > 0 || STRCMP(arg, "NONE") == 0 || !gui.in_use)
	    {
		HL_TABLE()[idx].sg_gui_fg = i;
		vim_free(HL_TABLE()[idx].sg_gui_fg_name);
		HL_TABLE()[idx].sg_gui_fg_name = vim_strsave(arg);
	    }
#endif
	}
	else if (STRICMP(key, "guibg") == 0)
	{
#ifdef USE_GUI	    /* in non-GUI guibg colors are simply ignored */
	    /* Add one to the argument, to avoid zero */
	    i = color_name2handle(arg) + 1;
	    if (i > 0 || STRCMP(arg, "NONE") == 0 || !gui.in_use)
	    {
		HL_TABLE()[idx].sg_gui_bg = i;
		vim_free(HL_TABLE()[idx].sg_gui_bg_name);
		HL_TABLE()[idx].sg_gui_bg_name = vim_strsave(arg);
	    }
#endif
	}
	else if (STRICMP(key, "start") == 0 || STRICMP(key, "stop") == 0)
	{
	    char_u	buf[100];
	    char_u	*tname;

	    /*
	     * The "start" and "stop"  arguments can be a literal escape
	     * sequence, or a comma seperated list of terminal codes.
	     */
	    if (STRNCMP(arg, "t_", 2) == 0)
	    {
		off = 0;
		buf[0] = 0;
		while (arg[off] != NUL)
		{
		    /* Isolate one termcap name */
		    for (len = 0; arg[off + len] &&
						 arg[off + len] != ','; ++len)
			;
		    tname = vim_strnsave(arg + off, len);
		    if (tname == NULL)		/* out of memory */
		    {
			error = TRUE;
			break;
		    }
		    /* lookup the escape sequence for the item */
		    p = get_term_code(tname);
		    vim_free(tname);
		    if (p == NULL)	    /* ignore non-existing things */
			p = (char_u *)"";

		    /* Append it to the already found stuff */
		    if ((int)(STRLEN(buf) + STRLEN(p)) >= 99)
		    {
			EMSG2("terminal code too long: %s", arg);
			error = TRUE;
			break;
		    }
		    STRCAT(buf, p);

		    /* Advance to the next item */
		    off += len;
		    if (arg[off] == ',')	    /* another one follows */
			++off;
		}
	    }
	    else
	    {
		/*
		 * Copy characters from arg[] to buf[], translating <> codes.
		 */
		for (p = arg, off = 0; off < 100 && *p; )
		{
		    len = trans_special(&p, buf + off);
		    if (len)		    /* recognized special char */
			off += len;
		    else		    /* copy as normal char */
			buf[off++] = *p++;
		}
		buf[off] = NUL;
	    }
	    if (error)
		break;

	    if (STRCMP(buf, "NONE") == 0)	/* resetting the value */
		p = NULL;
	    else
		p = vim_strsave(buf);
	    if (STRICMP(key, "start") == 0)
	    {
		vim_free(HL_TABLE()[idx].sg_start);
		HL_TABLE()[idx].sg_start = p;
	    }
	    else
	    {
		vim_free(HL_TABLE()[idx].sg_stop);
		HL_TABLE()[idx].sg_stop = p;
	    }
#if 0
	    if (is_normal_group)
	    /* TODO: set start/stop for term */
#endif
	}
	else
	{
	    EMSG2("Illegal argument: %s", key_start);
	    error = TRUE;
	    break;
	}

	/*
	 * When highlighting has been given for a group, don't link it.
	 */
	HL_TABLE()[idx].sg_link = 0;

	/*
	 * Continue with next argument.
	 */
	linep = skipwhite(linep);
    }

    /*
     * If there is an error, and it's a new entry, remove it from the table.
     */
    if (error && idx == highlight_ga.ga_len)
	syn_unadd_group();
    else
    {
	if (is_normal_group)
	{
	    HL_TABLE()[idx].sg_term_attr = 0;
	    HL_TABLE()[idx].sg_cterm_attr = 0;
#ifdef USE_GUI
	    HL_TABLE()[idx].sg_gui_attr = 0;
	    if (gui.in_use)
		set_normal_colors();
#endif
	}
	else
	    set_hl_attr(idx);
	redraw_all_later(NOT_VALID);
    }
    vim_free(key);
    vim_free(arg);
    highlight_changed();		/* update highlight_attr[] */
}

#ifdef USE_GUI
/*
 * Set the normal foreground and background colors according to the "Normal"
 * highlighighting group.
 */
    void
set_normal_colors()
{
    int		id;

    id = syn_name2id((char_u *)"Normal");
    if (id > 0)
    {
	if (HL_TABLE()[id - 1].sg_gui_fg > 0)
	    gui.norm_pixel = HL_TABLE()[id - 1].sg_gui_fg - 1;
	else
	    gui.norm_pixel = gui.def_norm_pixel;
	if (HL_TABLE()[id - 1].sg_gui_bg > 0)
	    gui.back_pixel = HL_TABLE()[id - 1].sg_gui_bg - 1;
	else
	    gui.back_pixel = gui.def_back_pixel;
	gui_mch_new_colors();
	redraw_all_later(CLEAR);
    }
}

/*
 * Set font for "Normal" group.  Called by gui_mch_init_font() when a font has
 * actually chosen to be used.
 */
    void
hl_set_font_name(font_name)
    char_u	*font_name;
{
    int	    id;

    id = syn_name2id((char_u *)"Normal");
    if (id > 0)
    {
	vim_free(HL_TABLE()[id - 1].sg_font_name);
	HL_TABLE()[id - 1].sg_font_name = vim_strsave(font_name);
    }
}

/*
 * Set background color for "Normal" group.  Called by gui_mch_init()
 * when the color is known.
 */
    void
hl_set_bg_color_name(name)
    char_u  *name;	    /* must have been allocated */
{
    int	    id;

    if (name != NULL)
    {
	id = syn_name2id((char_u *)"Normal");
	if (id > 0)
	{
	    vim_free(HL_TABLE()[id - 1].sg_gui_bg_name);
	    HL_TABLE()[id - 1].sg_gui_bg_name = name;
	}
    }
}

/*
 * Set foreground color for "Normal" group.  Called by gui_mch_init()
 * when the color is known.
 */
    void
hl_set_fg_color_name(name)
    char_u  *name;	    /* must have been allocated */
{
    int	    id;

    if (name != NULL)
    {
	id = syn_name2id((char_u *)"Normal");
	if (id > 0)
	{
	    vim_free(HL_TABLE()[id - 1].sg_gui_fg_name);
	    HL_TABLE()[id - 1].sg_gui_fg_name = name;
	}
    }
}

/*
 * Return the handle for a color name.
 * Returns -1 when failed.
 */
    static GuiColor
color_name2handle(name)
    char_u  *name;
{
    if (STRCMP(name, "NONE") == 0)
	return (GuiColor)-1;

    if (STRICMP(name, "fg") == 0 || STRICMP(name, "foreground") == 0)
	return gui.norm_pixel;
    if (STRICMP(name, "bg") == 0 || STRICMP(name, "background") == 0)
	return gui.back_pixel;

    return gui_mch_get_color(name);
}

/*
 * Return the handle for a font name.
 * Returns 0 when failed.
 */
    static GuiFont
font_name2handle(name)
    char_u  *name;
{
    if (STRCMP(name, "NONE") == 0)
	return (GuiFont)0;

    return gui_mch_get_font(name, TRUE);
}
#endif /* USE_GUI */

/*
 * Table with the specifications for an attribute number.
 * Note that this table is used by ALL buffers.  This is required because the
 * GUI can redraw at any time for any buffer.
 */
struct growarray    term_attr_table = {0, 0, 0, 0, NULL};

#define TERM_ATTR_ENTRY(idx) ((struct attr_entry *)term_attr_table.ga_data)[idx]

struct growarray    cterm_attr_table = {0, 0, 0, 0, NULL};

#define CTERM_ATTR_ENTRY(idx) ((struct attr_entry *)cterm_attr_table.ga_data)[idx]

#ifdef USE_GUI
struct growarray    gui_attr_table = {0, 0, 0, 0, NULL};

#define GUI_ATTR_ENTRY(idx) ((struct attr_entry *)gui_attr_table.ga_data)[idx]
#endif

/*
 * Return the attr number for a set of colors and font.
 * Add a new entry to the term_attr_table, cterm_attr_table or gui_attr_table
 * if the combination is new.
 */
    static int
get_attr_entry(table, aep)
    struct growarray	*table;
    struct attr_entry	*aep;
{
    int		i;
    struct attr_entry *gap;

    /*
     * Init the table, in case it wasn't done yet.
     */
    table->ga_itemsize = sizeof(struct attr_entry);
    table->ga_growsize = 7;

    /*
     * Try to find an entry with the same specifications.
     */
    for (i = 0; i < table->ga_len; ++i)
    {
	gap = &(((struct attr_entry *)table->ga_data)[i]);
	if (	   aep->ae_attr == gap->ae_attr
		&& (
#ifdef USE_GUI
		       (table == &gui_attr_table
			&& (aep->ae_u.gui.fg_color == gap->ae_u.gui.fg_color
			    && aep->ae_u.gui.bg_color == gap->ae_u.gui.bg_color
			    && aep->ae_u.gui.font == gap->ae_u.gui.font))
		    ||
#endif
		       (table == &term_attr_table
			&& (aep->ae_u.term.start == NULL) ==
						(gap->ae_u.term.start == NULL)
			&& (aep->ae_u.term.start == NULL
			    || STRCMP(aep->ae_u.term.start,
						   gap->ae_u.term.start) == 0)
			&& (aep->ae_u.term.stop == NULL) ==
						 (gap->ae_u.term.stop == NULL)
			&& (aep->ae_u.term.stop == NULL
			    || STRCMP(aep->ae_u.term.stop,
						   gap->ae_u.term.stop) == 0))
		    || (table == &cterm_attr_table
			&& aep->ae_u.cterm.fg_color == gap->ae_u.cterm.fg_color
			&& aep->ae_u.cterm.bg_color == gap->ae_u.cterm.bg_color)
		     ))

	return i + ATTR_OFF;
    }

    if (table->ga_len + ATTR_OFF == 256)
    {
	EMSG("Too many different highlighting attributes in use");
	return 0;
    }

    /*
     * This is a new combination of colors and font, add an entry.
     */
    if (ga_grow(table, 1) == FAIL)
	return 0;

    gap = &(((struct attr_entry *)table->ga_data)[table->ga_len]);
    vim_memset(gap, 0, sizeof(struct attr_entry));
    gap->ae_attr = aep->ae_attr;
#ifdef USE_GUI
    if (table == &gui_attr_table)
    {
	gap->ae_u.gui.fg_color = aep->ae_u.gui.fg_color;
	gap->ae_u.gui.bg_color = aep->ae_u.gui.bg_color;
	gap->ae_u.gui.font = aep->ae_u.gui.font;
    }
#endif
    if (table == &term_attr_table)
    {
	if (aep->ae_u.term.start == NULL)
	    gap->ae_u.term.start = NULL;
	else
	    gap->ae_u.term.start = vim_strsave(aep->ae_u.term.start);
	if (aep->ae_u.term.stop == NULL)
	    gap->ae_u.term.stop = NULL;
	else
	    gap->ae_u.term.stop = vim_strsave(aep->ae_u.term.stop);
    }
    else if (table == &cterm_attr_table)
    {
	gap->ae_u.cterm.fg_color = aep->ae_u.cterm.fg_color;
	gap->ae_u.cterm.bg_color = aep->ae_u.cterm.bg_color;
    }
    ++table->ga_len;
    --table->ga_room;
    return (table->ga_len - 1 + ATTR_OFF);
}

#ifdef USE_GUI

    struct attr_entry *
syn_gui_attr2entry(attr)
    int		    attr;
{
    attr -= ATTR_OFF;
    if (attr >= gui_attr_table.ga_len)	    /* did ":syntax clear" */
	return NULL;
    return &(GUI_ATTR_ENTRY(attr));
}

#endif /* USE_GUI */

    struct attr_entry *
syn_term_attr2entry(attr)
    int		    attr;
{
    attr -= ATTR_OFF;
    if (attr >= term_attr_table.ga_len)	    /* did ":syntax clear" */
	return NULL;
    return &(TERM_ATTR_ENTRY(attr));
}

    struct attr_entry *
syn_cterm_attr2entry(attr)
    int		    attr;
{
    attr -= ATTR_OFF;
    if (attr >= cterm_attr_table.ga_len)	/* did ":syntax clear" */
	return NULL;
    return &(CTERM_ATTR_ENTRY(attr));
}

#define LIST_ATTR   1
#define LIST_STRING 2
#define LIST_INT    3

    static void
highlight_list_one(id)
    int		id;
{
    struct hl_group	*sgp;
    int			didh = FALSE;

    sgp = &HL_TABLE()[id - 1];	    /* index is ID minus one */

    didh = highlight_list_arg(id, didh, LIST_ATTR,
				    sgp->sg_term, NULL, "term");
    didh = highlight_list_arg(id, didh, LIST_STRING,
				    0, sgp->sg_start, "start");
    didh = highlight_list_arg(id, didh, LIST_STRING,
				    0, sgp->sg_stop, "stop");

    didh = highlight_list_arg(id, didh, LIST_ATTR,
				    sgp->sg_cterm, NULL, "cterm");
    didh = highlight_list_arg(id, didh, LIST_INT,
				    sgp->sg_cterm_fg, NULL, "ctermfg");
    didh = highlight_list_arg(id, didh, LIST_INT,
				    sgp->sg_cterm_bg, NULL, "ctermbg");

#ifdef USE_GUI
    didh = highlight_list_arg(id, didh, LIST_ATTR,
				    sgp->sg_gui, NULL, "gui");
    didh = highlight_list_arg(id, didh, LIST_STRING,
				    0, sgp->sg_gui_fg_name, "guifg");
    didh = highlight_list_arg(id, didh, LIST_STRING,
				    0, sgp->sg_gui_bg_name, "guibg");
    didh = highlight_list_arg(id, didh, LIST_STRING,
				    0, sgp->sg_font_name, "font");
#endif

    if (sgp->sg_link)
    {
	syn_list_header(didh, 9999, id);
	msg_puts_attr((char_u *)"links to", highlight_attr[HLF_D]);
	msg_putchar(' ');
	msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name);
    }
}

    static int
highlight_list_arg(id, didh, type, iarg, sarg, name)
    int		id;
    int		didh;
    int		type;
    int		iarg;
    char_u	*sarg;
    char	*name;
{
    char_u	buf[100];
    char_u	*ts;
    int		i;

    if (type == LIST_STRING ? (sarg != NULL) : (iarg != 0))
    {
	ts = buf;
	if (type == LIST_INT)
	    sprintf((char *)buf, "%d", iarg - 1);
	else if (type == LIST_STRING)
	    ts = sarg;
	else /* type == LIST_ATTR */
	{
	    buf[0] = NUL;
	    for (i = 0; hl_attr_table[i] != 0; ++i)
	    {
		if (iarg & hl_attr_table[i])
		{
		    if (buf[0] != NUL)
			STRCAT(buf, ",");
		    STRCAT(buf, hl_name_table[i]);
		    iarg &= ~hl_attr_table[i];	    /* don't want "inverse" */
		}
	    }
	}

	syn_list_header(didh, (int)(strsize(ts) + STRLEN(name) + 1), id);
	didh = TRUE;

	MSG_PUTS_ATTR(name, highlight_attr[HLF_D]);
	MSG_PUTS_ATTR("=", highlight_attr[HLF_D]);
	msg_outtrans(ts);
    }
    return didh;
}

/*
 * Output the syntax list header.
 */
    static void
syn_list_header(did_header, outlen, id)
    int	    did_header;		/* did header already */
    int	    outlen;		/* length of string that comes */
    int	    id;			/* highlight group id */
{
    int	    endcol = 15;

    if (Columns <= 15)
	endcol = Columns - 1;

    if (!did_header)
    {
	msg_putchar('\n');
	msg_outtrans(HL_TABLE()[id - 1].sg_name);
    }
    else if (msg_col + outlen + 1 >= Columns)
	msg_putchar('\n');
    else
	msg_putchar(' ');
    while (msg_col < endcol)
	msg_putchar(' ');
}

/*
 * Set the attribute numbers for a highlight group.
 * Called after one of the attributes has changed.
 */
    static void
set_hl_attr(idx)
    int		    idx;	    /* index in array */
{
    struct attr_entry at_en;

    /* The "Normal" group doesn't need an attribute number */
    if (STRICMP(HL_TABLE()[idx].sg_name, "Normal") == 0)
	return;

#ifdef USE_GUI
    /*
     * For the GUI mode: If there are other than "normal" highlighting
     * attributes, need to allocate an attr number.
     */
    if (HL_TABLE()[idx].sg_gui_fg == 0
	    && HL_TABLE()[idx].sg_gui_bg == 0
	    && HL_TABLE()[idx].sg_font == 0)
    {
	HL_TABLE()[idx].sg_gui_attr = HL_TABLE()[idx].sg_gui;
    }
    else
    {
	at_en.ae_attr = HL_TABLE()[idx].sg_gui;
	at_en.ae_u.gui.fg_color = HL_TABLE()[idx].sg_gui_fg;
	at_en.ae_u.gui.bg_color = HL_TABLE()[idx].sg_gui_bg;
	at_en.ae_u.gui.font = HL_TABLE()[idx].sg_font;
	HL_TABLE()[idx].sg_gui_attr = get_attr_entry(&gui_attr_table, &at_en);
    }
#endif
    /*
     * For the term mode: If there are other than "normal" highlighting
     * attributes, need to allocate an attr number.
     */
    if (HL_TABLE()[idx].sg_start == NULL && HL_TABLE()[idx].sg_stop == NULL)
	HL_TABLE()[idx].sg_term_attr = HL_TABLE()[idx].sg_term;
    else
    {
	at_en.ae_attr = HL_TABLE()[idx].sg_term;
	at_en.ae_u.term.start = HL_TABLE()[idx].sg_start;
	at_en.ae_u.term.stop = HL_TABLE()[idx].sg_stop;
	HL_TABLE()[idx].sg_term_attr = get_attr_entry(&term_attr_table, &at_en);
    }

    /*
     * For the color term mode: If there are other than "normal"
     * highlighting attributes, need to allocate an attr number.
     */
    if (HL_TABLE()[idx].sg_cterm_fg == 0 && HL_TABLE()[idx].sg_cterm_bg == 0)
	HL_TABLE()[idx].sg_cterm_attr = HL_TABLE()[idx].sg_cterm;
    else
    {
	at_en.ae_attr = HL_TABLE()[idx].sg_cterm;
	at_en.ae_u.cterm.fg_color = HL_TABLE()[idx].sg_cterm_fg;
	at_en.ae_u.cterm.bg_color = HL_TABLE()[idx].sg_cterm_bg;
	HL_TABLE()[idx].sg_cterm_attr =
				    get_attr_entry(&cterm_attr_table, &at_en);
    }
}

/*
 * Lookup a highlight group name and return it's ID.
 * If it is not found, 0 is returned.
 */
    static int
syn_name2id(name)
    char_u	*name;
{
    int		i;

    for (i = highlight_ga.ga_len; --i >= 0; )
	if (STRICMP(name, HL_TABLE()[i].sg_name) == 0)
	    break;
    return i + 1;
}

/* 
 * Return TRUE if highlight group "name" exists.
 */
    int
highlight_exists(name)
    char_u	*name;
{
    return (syn_name2id(name) > 0);
}

/*
 * Like syn_name2id(), but take a pointer + length argument.
 */
    static int
syn_namen2id(linep, len)
    char_u  *linep;
    int	    len;
{
    char_u  *name;
    int	    id = 0;

    name = vim_strnsave(linep, len);
    if (name != NULL)
    {
	id = syn_name2id(name);
	vim_free(name);
    }
    return id;
}

/*
 * Find highlight group name in the table and return it's ID.
 * The argument is a pointer to the name and the length of the name.
 * If it doesn't exist yet, a new entry is created.
 * Return 0 for failure.
 */
    int
syn_check_group(pp, len)
    char_u		*pp;
    int			len;
{
    int	    id;
    char_u  *name;

    name = vim_strnsave(pp, len);
    if (name == NULL)
	return 0;

    id = syn_name2id(name);
    if (id == 0)			/* doesn't exist yet */
	id = syn_add_group(name);
    else
	vim_free(name);
    return id;
}

/*
 * Add new highlight group and return it's ID.
 * "name" must be an allocated string, it will be consumed.
 * Return 0 for failure.
 */
    static int
syn_add_group(name)
    char_u		*name;
{
    /*
     * First call for this growarray: init growing array.
     */
    if (highlight_ga.ga_data == NULL)
    {
	highlight_ga.ga_itemsize = sizeof(struct hl_group);
	highlight_ga.ga_growsize = 10;
    }

    /*
     * Make room for at least one other syntax_highlight entry.
     */
    if (ga_grow(&highlight_ga, 1) == FAIL)
    {
	vim_free(name);
	return 0;
    }

    vim_memset(&(HL_TABLE()[highlight_ga.ga_len]), 0, sizeof(struct hl_group));
    HL_TABLE()[highlight_ga.ga_len].sg_name = name;
    ++highlight_ga.ga_len;
    --highlight_ga.ga_room;

    return highlight_ga.ga_len;		    /* ID is index plus one */
}

/*
 * When, just after calling syn_add_group(), an error is discovered, this
 * function deletes the new name.
 */
    static void
syn_unadd_group()
{
    --highlight_ga.ga_len;
    ++highlight_ga.ga_room;
    vim_free(HL_TABLE()[highlight_ga.ga_len].sg_name);
}

/*
 * Translate a group ID to highlight attributes.
 */
    static int
syn_id2attr(hl_id)
    int			hl_id;
{
    int			attr = 0;
    struct hl_group	*sgp;

    hl_id = syn_get_final_id(hl_id);
    sgp = &HL_TABLE()[hl_id - 1];	    /* index is ID minus one */

#ifdef USE_GUI
    /*
     * Only use GUI attr when the GUI is being used.
     */
    if (gui.in_use)
	attr = sgp->sg_gui_attr;
    else
#endif
	if (*T_CCO)
	    attr = sgp->sg_cterm_attr;
	else
	    attr = sgp->sg_term_attr;

    return attr;
}

#ifdef USE_GUI
/*
 * Get the GUI colors and attributes for a group ID.
 * NOTE: the colors will be 0 when not set, the color plus one otherwise.
 */
    int
syn_id2colors(hl_id, fgp, bgp)
    int		hl_id;
    GuiColor	*fgp;
    GuiColor	*bgp;
{
    struct hl_group	*sgp;

    hl_id = syn_get_final_id(hl_id);
    sgp = &HL_TABLE()[hl_id - 1];	    /* index is ID minus one */

    *fgp = sgp->sg_gui_fg;
    *bgp = sgp->sg_gui_bg;
    return sgp->sg_gui;
}
#endif

/*
 * Translate a group ID to the final group ID (following links).
 */
    static int
syn_get_final_id(hl_id)
    int			hl_id;
{
    int			count;
    struct hl_group	*sgp;

    /*
     * Follow links until there is no more.
     * Look out for loops!  Break after 100 links.
     */
    for (count = 100; --count >= 0; )
    {
	sgp = &HL_TABLE()[hl_id - 1];	    /* index is ID minus one */
	if (sgp->sg_link == 0)
	    break;
	hl_id = sgp->sg_link;
    }

    return hl_id;
}

#ifdef USE_GUI
/*
 * Call this function just after the GUI has started.
 * It finds the font and color handles for the highlighting groups.
 */
    void
highlight_gui_started()
{
    int	    idx;
    int	    didit;

    for (idx = 0; idx < highlight_ga.ga_len; ++idx)
    {
	didit = FALSE;
	if (HL_TABLE()[idx].sg_font_name != NULL)
	{
	    HL_TABLE()[idx].sg_font =
		font_name2handle(HL_TABLE()[idx].sg_font_name);
	    didit = TRUE;
	}
	if (HL_TABLE()[idx].sg_gui_fg_name != NULL)
	{
	    HL_TABLE()[idx].sg_gui_fg =
		color_name2handle(HL_TABLE()[idx].sg_gui_fg_name) + 1;
	    didit = TRUE;
	}
	if (HL_TABLE()[idx].sg_gui_bg_name != NULL)
	{
	    HL_TABLE()[idx].sg_gui_bg =
		color_name2handle(HL_TABLE()[idx].sg_gui_bg_name) + 1;
	    didit = TRUE;
	}
	if (didit)	/* need to get a new attr number */
	    set_hl_attr(idx);
    }

    highlight_changed();

    /* get the colors from the "Normal" group, if set */
    set_normal_colors();
}
#endif

/*
 * Translate the 'highlight' option into attributes in highlight_attr[].
 * Called only when the 'highlight' option has been changed.
 * Return FAIL when an invalid flag is found.  OK otherwise.
 */
    int
highlight_changed()
{
    int		hlf;
    int		i;
    char_u	*p;
    int		attr;
    char_u	*end;
    int		id;

    /* Check the HLF_ enums, they must be in the same order! */
    static int flags[HLF_COUNT] = {'8', '@', 'd', 'e', 'h', 'l', 'm', 'M',
				   'n', 'r', 's', 't', 'v', 'w'};

    /*
     * Clear all attributes.
     */
    for (hlf = 0; hlf < (int)HLF_COUNT; ++hlf)
	highlight_attr[hlf] = 0;

    /*
     * First set all attributes to their default value.
     * Then use the attributes from the 'highlight' option.
     */
    for (i = 0; i < 2; ++i)
    {
	if (i)
	    p = p_hl;
	else
	    p = get_highlight_default();
	if (p == NULL)	    /* just in case */
	    continue;

	while (*p)
	{
	    for (hlf = 0; hlf < (int)HLF_COUNT; ++hlf)
		if (flags[hlf] == *p)
		    break;
	    ++p;
	    if (hlf == (int)HLF_COUNT || *p == NUL)
		return FAIL;

	    /*
	     * Allow several flags to be combined, like "bu" for
	     * bold-underlined.
	     */
	    attr = 0;
	    for ( ; *p && *p != ','; ++p)	    /* parse upto comma */
	    {
		if (vim_iswhite(*p))		    /* ignore white space */
		    continue;

		if (attr > HL_ALL)  /* Combination with ':' is not allowed. */
		    return FAIL;

		switch (*p)
		{
		    case 'b':	attr |= HL_BOLD;
				break;
		    case 'i':	attr |= HL_ITALIC;
				break;
		    case '-':
		    case 'n':			    /* no highlighting */
				break;
		    case 'r':	attr |= HL_INVERSE;
				break;
		    case 's':	attr |= HL_STANDOUT;
				break;
		    case 'u':	attr |= HL_UNDERLINE;
				break;
		    case ':':	++p;		    /* highlight group name */
				if (attr || *p == NUL)   /* no combinations */
				    return FAIL;
				end = vim_strchr(p, ',');
				if (end == NULL)
				    end = p + STRLEN(p);
				id = syn_check_group(p, (int)(end - p));
				if (id == 0)
				    return FAIL;
				attr = syn_id2attr(id);
				p = end - 1;
				break;
		    default:	return FAIL;
		}
	    }
	    highlight_attr[hlf] = attr;

	    p = skip_to_option_part(p);	    /* skip comma and spaces */
	}
    }

    return OK;
}

/**********************************************************************
 *  End of Highlighting stuff					      *
 **********************************************************************/
