ftp.nice.ch/pub/next/unix/editor/vim-5.0f.s.tar.gz#/vim-5.0f/src/syntax.c

This is syntax.c in view mode; [Download] [Up]

/* vi:set ts=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)))

#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_u				 sp_syn_id;			/* highlight group ID of item */
	char				 sp_flags;			/* see HL_ defines below */
	char				 sp_off_flags;		/* see SPO_ defines below */
	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))

#define CHECK_ALL		NULL		/* check all items */
#define CHECK_KEYWORDS	1			/* check all keywords */

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

/*
 * 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_h_startcol;			/* starting column of the highlighting */
	int		si_m_endcol;			/* ending column of the match */
	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 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((int syncing));
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 struct state_item *push_next_match __ARGS((int syncing, struct state_item *cur_si, char_u *line));
static void check_state_ends __ARGS((int syncing, struct state_item *cur_si, char_u *line));
static void update_si_attr __ARGS((int syncing, int idx));
static void update_si_end __ARGS((int syncing, 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 syncing, 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_cmd_clear __ARGS((char_u *arg, char_u **nextcomm, int syncing));
static void syn_cmd_list __ARGS((char_u *arg, char_u **nextcomm, int syncing));
static void syn_list_one __ARGS((int id, int syncing, int link_only));
static void put_pattern __ARGS((struct syn_pattern *spp));
static int syn_list_recur __ARGS((int id, struct kwordtab *ktab, int len, int did_header, int attr));
static void syn_list_header __ARGS((int did_header, int outlen, int id));
static void free_kwordtab __ARGS((struct kwordtab *ktab));
static void add_kwordtab __ARGS((char_u *name, int id, int flags));
static struct kwordtab *add_kwordtab_down __ARGS((struct kwordtab *ktab, int n));
static struct kwordtab *alloc_kwordtab __ARGS((void));
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));
#ifdef USE_GUI
static GuiColor color_name2handle __ARGS((char_u *name));
#endif
static int *get_id_list __ARGS((char_u **arg));

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

static int get_attr_entry  __ARGS((struct growarray *table, struct attr_entry *aep));

static void highlight_list_one __ARGS((int id));
static void set_hl_attr __ARGS((int idx));
static int syn_name2id __ARGS((char_u *name));
static int syn_namen2id __ARGS((char_u *linep, int len));
static int syn_check_group __ARGS((char_u *name, int len));
static int syn_add_group __ARGS((char_u *name));
static void syn_unadd_group __ARGS((void));
static int syn_id2attr __ARGS((int hl_id));

/*
 * 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);
		++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))
	{
		if (syn_buf->b_syn_sync_lines != 0)
			diff = syn_buf->b_syn_sync_lines;
		else
			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(FALSE);
		(void)syn_finish_line(FALSE);
		++current_lnum;
		store_current_state();
	}

	syn_start_line(FALSE);
}

/*
 * 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();

	/*
	 * 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 */
		while (start_lnum > 1)
		{
			line = ml_get(start_lnum - 1);
			if (*line == NUL || *(line + STRLEN(line) - 1) != '\\')
				break;
			--start_lnum;
		}

		/* 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_lines.
		 */
		if (find_start_comment((int)syn_buf->b_syn_sync_lines) != 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(FALSE, current_state.ga_len - 1);
					break;
				}
		}

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

		/* always start parsing in "start_lnum" */
		current_lnum = start_lnum;
	}

	/*
	 * 2. Search backwards for given sync patterns.
	 */
	else if (syn_buf->b_syn_sync_flags & SF_MATCH)
	{
		if (syn_buf->b_syn_sync_lines && start_lnum > syn_buf->b_syn_sync_lines)
			break_lnum = start_lnum - syn_buf->b_syn_sync_lines;
		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(TRUE);
				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(FALSE, 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(FALSE, 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;
		}
	}

	/*
	 * 3. Simply start on a given number of lines above "lnum".
	 *
	 * Synchronize on a number of lines backwards, assuming that we
	 * are not in anything there, and then re-parsing to catch up with
	 * what is actually there.
	 */
	else if (syn_buf->b_syn_sync_lines)
	{
		if (start_lnum <= syn_buf->b_syn_sync_lines)
			current_lnum = 1;
		else
			current_lnum = start_lnum - syn_buf->b_syn_sync_lines;
	}
	else
		current_lnum = start_lnum;

	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(syncing)
	int		syncing;			/* called for syncing */
{
	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(syncing, cur_si, line, 0);
		check_state_ends(syncing, 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;
		}
	}
}

/*
 * 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;
			update_si_attr(FALSE, 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];
	syn_buf->b_syn_states[from].ga_data = NULL;
	syn_buf->b_syn_states[from].ga_room = 0;
	syn_buf->b_syn_states[from].ga_len = 0;
	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 = 0;
	char_u				*endp;
	char_u				*hl_endp = NULL;
	int					idx;
	struct syn_pattern	*spp;
	struct state_item	*cur_si = NULL;
	int					startcol;
	int					hl_startcol;
	int					endcol;
	int					flags;

	/* no character, no attributes!  (empty line?) */
	if (*(line + current_col) == NUL)
	{
		/*
		 * 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(syncing, NULL, line);

		current_finished = TRUE;
		return 0;
	}

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

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

	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
				&& (syn_buf->b_first_ktab != NULL
					|| syn_buf->b_first_ktab_ic != NULL)
				&& 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_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.
		 */
		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,
								 vim_regexec(spp->sp_prog, line + current_col,
														   current_col == 0)))
					{
						/*
						 * 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 this is the best match so far, remember it.
						 */
						if (startcol < next_match_col)
						{
							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(syncing, idx + 1, 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)
								{
									endp = NULL;
									/*
									 * 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]
													   && next_match_idx == 0)
										next_match_idx = -1;
								}
							}

							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(syncing, cur_si, line);
		}
	}

	/*
	 * 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(syncing, cur_si, line);
		--current_col;
	}
	else
		current_attr = 0;

	return current_attr;
}

/*
 * Push the next match onto the stack.
 */
	static struct state_item *
push_next_match(syncing, cur_si, line)
	int					syncing;
	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;
		if (spp->sp_type == SPTYPE_START && !(spp->sp_flags & HL_ONELINE))
		{
			update_si_end(syncing, 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(syncing, 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(syncing, cur_si, line)
	int					syncing;		/* called for syncing */
	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(syncing, 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(syncing, idx)
	int		syncing;			/* called for syncing */
	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(syncing, sip, line, startcol)
	int					syncing;	/* called for syncing */
	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(syncing, sip->si_idx + 1, 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 */
	next_match_idx = -1;
}

/*
 * Find the end of a start/skip/end pattern match.
 */
	static char_u *
find_endp(syncing, idx, sstart, at_bol, hl_endp, flagsp)
	int		syncing;	/* called for syncing */
	int 	idx;		/* index of the pattern after the matching START patn */
	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;

	/*
	 * 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 */
}

/*
 * 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))
			continue;

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

/*
 * Handle ":syntax case" command.
 */
	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 */
	free_kwordtab(buf->b_first_ktab);
	buf->b_first_ktab = NULL;
	free_kwordtab(buf->b_first_ktab_ic);
	buf->b_first_ktab_ic = NULL;

	/* free the syntax patterns */
	for (i = buf->b_syn_patterns.ga_len; --i >= 0; )
	{
		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);
	}
	ga_clear(&buf->b_syn_patterns);

	buf->b_syn_sync_flags = 0;
	buf->b_syn_sync_lines = 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);
}

/*
 * Handle ":syntax clear" command.
 */
	static void
syn_cmd_clear(arg, nextcomm, syncing)
	char_u	*arg;
	char_u	**nextcomm;
	int		syncing;		/* not used */
{
	syntax_clear(curbuf);
	*nextcomm = check_nextcomm(arg);
	redraw_curbuf_later(NOT_VALID);
}

/*
 * 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_lines)
			{
				MSG_PUTS("; maximal ");
				msg_outnum(curbuf->b_syn_sync_lines);
				MSG_PUTS(" lines before top line");
			}
			return;
		}
		else if (!(curbuf->b_syn_sync_flags & SF_MATCH))
		{
			MSG_PUTS("syncing starts ");
			msg_outnum(curbuf->b_syn_sync_lines);
			MSG_PUTS(" lines before top line");
			return;
		}
		MSG_PUTS_TITLE("\n--- Syntax sync items ---");
		if (curbuf->b_syn_sync_lines)
		{
			MSG_PUTS("\nsyncing at maximal ");
			msg_outnum(curbuf->b_syn_sync_lines);
			MSG_PUTS(" lines before top line");
		}
	}
	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; ++id)
			syn_list_one(id, syncing, FALSE);
	}
	else
	{
		/*
		 * List 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);
			else
				syn_list_one(id, syncing, TRUE);
			arg = skipwhite(arg_end);
		}
	}
	*nextcomm = check_nextcomm(arg);
}

/*
 * 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)
	{
		did_header = syn_list_recur(id, curbuf->b_first_ktab, 0, FALSE, attr);
		did_header = syn_list_recur(id, curbuf->b_first_ktab_ic, 0,
															did_header, attr);
	}

	/* 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;
		while (msg_col < 15)
			msg_putchar(' ');
		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)
					MSG_PUTS("ALLBUT");
				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 the keywords for one syntax item.
 * Return TRUE if the header has been printed.
 */
	static int
syn_list_recur(id, ktab, len, did_header, attr)
	int				id;
	struct kwordtab *ktab;
	int				len;
	int				did_header;			/* header has already been printed */
	int				attr;
{
	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)
	{
		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);
				}
		}
	return did_header;
}

/*
 * 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 */
{
	if (!did_header)
	{
		msg_putchar('\n');
		msg_outtrans(HL_TABLE()[id - 1].sg_name);
	}
	else if (msg_col + outlen >= Columns)
		msg_putchar('\n');
	else
		msg_putchar(' ');
	while (msg_col < 15)
		msg_putchar(' ');
}

/*
 * Recursive function to free() a branch of a kwordtab.
 */
	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);
	}
}

/*
 * Add an entry to the kwordtab.
 */
	static void
add_kwordtab(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));
}

/*
 * Handle ":syntax keyword" command.
 */
	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 in kwordtab for it.
		 */
		while (!ends_excmd(*keyword))
		{
			end = keyword_copy;
			while (*keyword && !vim_iswhite(*keyword))
			{
				if (*keyword == '\\' && keyword[1])
					++keyword;
				*end++ = *keyword++;
			}
			*end = NUL;
			add_kwordtab(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;

	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);
				return;		/* don't free the progs and patterns now */
			}
		}
	}

	/*
	 * Something failed, free the allocated memory.
	 */
	for (item = ITEM_START; item <= ITEM_END; ++item)
		for (ppp = pat_ptrs[item]; ppp != NULL; ppp = ppp_next)
		{
			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);
		}

	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.
 */
	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 (STRICMP(key, "lines") == 0)
		{
			if (!isdigit(*next_arg))
			{
				illegal = TRUE;
				break;
			}
			curbuf->b_syn_sync_lines = getdigits(&next_arg);
			next_arg = skipwhite(next_arg);
		}
		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)
			{
				if (count != 0)
				{
					EMSG("ALLBUT must be first in contains list");
					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;
							++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 USE_GUI

/*
 * color stuff for the GUI
 */

struct color_entry
{
	/* char_u			*name;  not used, since it is not always unique */
	GuiColor		handle;
};

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

#define COLOR_ITEM(i)	((struct color_entry *)color_table.ga_data)[i]

/*
 * Return the handle for a color name.
 * When the color isn't used yet, it is added to the color table.
 * Returns 0 when failed.
 */
	static GuiColor
color_name2handle(name)
	char_u	*name;
{
	int				i;
	GuiColor		ch;

	if (STRCMP(name, "NONE") == 0)
		return 0;

	/*
	 * Init growarray, in case it wasn't yet.
	 */
	color_table.ga_itemsize = sizeof(struct color_entry);
	color_table.ga_growsize = 5;
	if (ga_grow(&color_table, 1) == FAIL)
		return 0;

	/*
	 * Find the handle for this color.
	 */
	ch = (GuiColor)gui_mch_get_color(name);
	if (ch == 0)			/* not found */
		return 0;

	/*
	 * Check if the color already exists.
	 */
	for (i = 0; i < color_table.ga_len; ++i)
		if (COLOR_ITEM(i).handle == ch)		/* exists already */
			return ch;

	/*
	 * Add a new color to the array.
	 */
	COLOR_ITEM(color_table.ga_len).handle = ch;
	++color_table.ga_len;
	--color_table.ga_room;
	return ch;
}

/*
 * Font stuff for GUI.
 */
struct font_entry
{
	/* char_u			*name; not used, because it's not unique */
	GuiFont		handle;
};

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

#define FONT_ITEM(i)	((struct font_entry *)font_table.ga_data)[i]

/*
 * Return the handle for a font name.
 * When the font isn't used yet, it is added to the font table.
 * Returns 0 when failed.
 */
	static GuiFont
font_name2handle(name)
	char_u	*name;
{
	int				i;
	GuiFont			fh;

	if (STRCMP(name, "NONE") == 0)
		return 0;

	/*
	 * Init growarray, in case it wasn't yet.
	 */
	font_table.ga_itemsize = sizeof(struct font_entry);
	font_table.ga_growsize = 5;
	if (ga_grow(&font_table, 1) == FAIL)
		return 0;

	/*
	 * Try to find the font.
	 */
	fh = gui_mch_get_font(name, TRUE);
	if (fh == 0)			/* not found */
		return 0;

	/*
	 * Check if the font already exists.
	 */
	for (i = 0; i < font_table.ga_len; ++i)
		if (gui_mch_same_font(FONT_ITEM(i).handle, fh))	/* already exists */
		{
			gui_mch_free_font(fh);
			return FONT_ITEM(i).handle;
		}

	/*
	 * Add a new font to the array.
	 */
	FONT_ITEM(font_table.ga_len).handle = fh;
	++font_table.ga_len;
	--font_table.ga_room;
	return fh;
}
#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));
}

	int
syntax_present(buf)
	BUF		*buf;
{
	return (buf->b_syn_patterns.ga_len != 0
			|| curbuf->b_first_ktab != NULL
			|| curbuf->b_first_ktab != NULL);
}

#endif /* SYNTAX_HL */


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

/*
 * Handle the ":highlight .." command.
 */
	void
do_highlight(line)
	char_u		*line;
{
	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;

	/*
	 * If no argument, list current highlighting.
	 */
	if (ends_excmd(*line))
	{
		for (i = 0; i < highlight_ga.ga_len; ++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)
			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 */

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

	  	/*
	  	 * Check for the equal sign.
	  	 */
	  	linep = skipwhite(linep);
	  	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, "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
	  	}
		else if (  STRICMP(key, "term") == 0
				|| STRICMP(key, "cterm") == 0
				|| STRICMP(key, "gui") == 0)
	  	{
			/*
			 * 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};

			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 */
	  		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[20]) = {
							"black", "blue", "green", "cyan",
							"red", "magenta", "brown", "gray", "grey",
							"lightgray", "lightgrey", "darkgray", "darkgrey",
							"lightblue", "lightgreen", "lightcyan", "lightred",
							"lightmagenta", "yellow", "white"};
				static char color_numbers[20] = {0, 1, 2, 3,
												 4, 5, 6, 7, 7,
												 7, 7, 8, 8,
												 9, 10, 11, 12,
												 13, 14, 15};

				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;
	  	}
	  	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 */
	  		HL_TABLE()[idx].sg_gui_fg = color_name2handle(arg) + 1;
	  		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 */
	  		HL_TABLE()[idx].sg_gui_bg = color_name2handle(arg) + 1;
	  		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, TRUE);
					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;
			}
	  	}
	  	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
	{
		set_hl_attr(idx);
		redraw_all_later(NOT_VALID);
	}
	vim_free(key);
	vim_free(arg);
	highlight_changed();				/* update highlight_attr[] */
}

	static void
highlight_list_one(id)
	int					id;
{
	MSG("Sorry, listing of highlighting not implemented yet");
}

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

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

/*
 * 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.
 */
	static 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;
	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)
		{
#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;
			break;
		}
		hl_id = sgp->sg_link;
	}

	return attr;
}

#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();
}
#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 < 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 < HLF_COUNT; ++hlf)
				if (flags[hlf] == *p)
					break;
			++p;
			if (hlf == 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_namen2id(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										  *
 **********************************************************************/

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.