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.