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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_state);
validate_current_state();
if (from->ga_len && ga_grow(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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.