neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

syntax.c (182289B)


      1 // syntax.c: code for syntax highlighting
      2 
      3 #include <assert.h>
      4 #include <inttypes.h>
      5 #include <stdbool.h>
      6 #include <stddef.h>
      7 #include <stdlib.h>
      8 #include <string.h>
      9 
     10 #include "nvim/ascii_defs.h"
     11 #include "nvim/autocmd.h"
     12 #include "nvim/autocmd_defs.h"
     13 #include "nvim/buffer.h"
     14 #include "nvim/buffer_defs.h"
     15 #include "nvim/charset.h"
     16 #include "nvim/cmdexpand_defs.h"
     17 #include "nvim/drawscreen.h"
     18 #include "nvim/errors.h"
     19 #include "nvim/eval/typval_defs.h"
     20 #include "nvim/eval/vars.h"
     21 #include "nvim/ex_cmds_defs.h"
     22 #include "nvim/ex_docmd.h"
     23 #include "nvim/fold.h"
     24 #include "nvim/garray.h"
     25 #include "nvim/garray_defs.h"
     26 #include "nvim/gettext_defs.h"
     27 #include "nvim/globals.h"
     28 #include "nvim/hashtab.h"
     29 #include "nvim/hashtab_defs.h"
     30 #include "nvim/highlight_defs.h"
     31 #include "nvim/highlight_group.h"
     32 #include "nvim/indent_c.h"
     33 #include "nvim/macros_defs.h"
     34 #include "nvim/mbyte.h"
     35 #include "nvim/memline.h"
     36 #include "nvim/memory.h"
     37 #include "nvim/message.h"
     38 #include "nvim/option_vars.h"
     39 #include "nvim/optionstr.h"
     40 #include "nvim/os/input.h"
     41 #include "nvim/path.h"
     42 #include "nvim/pos_defs.h"
     43 #include "nvim/profile.h"
     44 #include "nvim/regexp.h"
     45 #include "nvim/regexp_defs.h"
     46 #include "nvim/runtime.h"
     47 #include "nvim/strings.h"
     48 #include "nvim/syntax.h"
     49 #include "nvim/types_defs.h"
     50 #include "nvim/vim_defs.h"
     51 
     52 static bool did_syntax_onoff = false;
     53 
     54 // different types of offsets that are possible
     55 #define SPO_MS_OFF      0       // match  start offset
     56 #define SPO_ME_OFF      1       // match  end   offset
     57 #define SPO_HS_OFF      2       // highl. start offset
     58 #define SPO_HE_OFF      3       // highl. end   offset
     59 #define SPO_RS_OFF      4       // region start offset
     60 #define SPO_RE_OFF      5       // region end   offset
     61 #define SPO_LC_OFF      6       // leading context offset
     62 #define SPO_COUNT       7
     63 
     64 static const char e_illegal_arg[] = N_("E390: Illegal argument: %s");
     65 static const char e_contains_argument_not_accepted_here[]
     66  = N_("E395: Contains argument not accepted here");
     67 static const char e_invalid_cchar_value[]
     68  = N_("E844: Invalid cchar value");
     69 static const char e_trailing_char_after_rsb_str_str[]
     70  = N_("E890: Trailing char after ']': %s]%s");
     71 
     72 // The patterns that are being searched for are stored in a syn_pattern.
     73 // A match item consists of one pattern.
     74 // A start/end item consists of n start patterns and m end patterns.
     75 // A start/skip/end item consists of n start patterns, one skip pattern and m
     76 // end patterns.
     77 // For the latter two, the patterns are always consecutive: start-skip-end.
     78 //
     79 // A character offset can be given for the matched text (_m_start and _m_end)
     80 // and for the actually highlighted text (_h_start and _h_end).
     81 //
     82 // Note that ordering of members is optimized to reduce padding.
     83 typedef struct {
     84  char sp_type;                         // see SPTYPE_ defines below
     85  bool sp_syncing;                      // this item used for syncing
     86  int16_t sp_syn_match_id;              // highlight group ID of pattern
     87  int16_t sp_off_flags;                 // see below
     88  int sp_offsets[SPO_COUNT];            // offsets
     89  int sp_flags;                         // see HL_ defines below
     90  int sp_cchar;                         // conceal substitute character
     91  int sp_ic;                            // ignore-case flag for sp_prog
     92  int sp_sync_idx;                      // sync item index (syncing only)
     93  int sp_line_id;                       // ID of last line where tried
     94  int sp_startcol;                      // next match in sp_line_id line
     95  int16_t *sp_cont_list;                // cont. group IDs, if non-zero
     96  int16_t *sp_next_list;                // next group IDs, if non-zero
     97  struct sp_syn sp_syn;                 // struct passed to in_id_list()
     98  char *sp_pattern;                     // regexp to match, pattern
     99  regprog_T *sp_prog;                   // regexp to match, program
    100  syn_time_T sp_time;
    101 } synpat_T;
    102 
    103 typedef struct {
    104  char *scl_name;           // syntax cluster name
    105  char *scl_name_u;         // uppercase of scl_name
    106  int16_t *scl_list;        // IDs in this syntax cluster
    107 } syn_cluster_T;
    108 
    109 // For the current state we need to remember more than just the idx.
    110 // When si_m_endpos.lnum is 0, the items other than si_idx are unknown.
    111 // (The end positions have the column number of the next char)
    112 typedef struct {
    113  int si_idx;                           // index of syntax pattern or
    114                                        // KEYWORD_IDX
    115  int si_id;                            // highlight group ID for keywords
    116  int si_trans_id;                      // idem, transparency removed
    117  int si_m_lnum;                        // lnum of the match
    118  int si_m_startcol;                    // starting column of the match
    119  lpos_T si_m_endpos;                   // just after end posn of the match
    120  lpos_T si_h_startpos;                 // start position of the highlighting
    121  lpos_T si_h_endpos;                   // end position of the highlighting
    122  lpos_T si_eoe_pos;                    // end position of end pattern
    123  int si_end_idx;                       // group ID for end pattern or zero
    124  int si_ends;                          // if match ends before si_m_endpos
    125  int si_attr;                          // attributes in this state
    126  int si_flags;                         // HL_HAS_EOL flag in this state, and
    127                                        // HL_SKIP* for si_next_list
    128  int si_seqnr;                         // sequence number
    129  int si_cchar;                         // substitution character for conceal
    130  int16_t *si_cont_list;                // list of contained groups
    131  int16_t *si_next_list;                // nextgroup IDs after this item ends
    132  reg_extmatch_T *si_extmatch;          // \z(...\) matches from start
    133                                        // pattern
    134 } stateitem_T;
    135 
    136 // Struct to reduce the number of arguments to get_syn_options(), it's used
    137 // very often.
    138 typedef struct {
    139  int flags;                   // flags for contained and transparent
    140  bool keyword;                // true for ":syn keyword"
    141  int *sync_idx;               // syntax item for "grouphere" argument, NULL
    142                               // if not allowed
    143  bool has_cont_list;          // true if "cont_list" can be used
    144  int16_t *cont_list;          // group IDs for "contains" argument
    145  int16_t *cont_in_list;       // group IDs for "containedin" argument
    146  int16_t *next_list;          // group IDs for "nextgroup" argument
    147 } syn_opt_arg_T;
    148 
    149 typedef struct {
    150  proftime_T total;
    151  int count;
    152  int match;
    153  proftime_T slowest;
    154  proftime_T average;
    155  int id;
    156  char *pattern;
    157 } time_entry_T;
    158 
    159 #include "syntax.c.generated.h"
    160 
    161 static char *(spo_name_tab[SPO_COUNT]) =
    162 { "ms=", "me=", "hs=", "he=", "rs=", "re=", "lc=" };
    163 
    164 // The sp_off_flags are computed like this:
    165 // offset from the start of the matched text: (1 << SPO_XX_OFF)
    166 // offset from the end   of the matched text: (1 << (SPO_XX_OFF + SPO_COUNT))
    167 // When both are present, only one is used.
    168 
    169 #define SPTYPE_MATCH    1       // match keyword with this group ID
    170 #define SPTYPE_START    2       // match a regexp, start of item
    171 #define SPTYPE_END      3       // match a regexp, end of item
    172 #define SPTYPE_SKIP     4       // match a regexp, skip within item
    173 
    174 #define SYN_ITEMS(buf)  ((synpat_T *)((buf)->b_syn_patterns.ga_data))
    175 
    176 #define NONE_IDX        (-2)    // value of sp_sync_idx for "NONE"
    177 
    178 // Flags for b_syn_sync_flags:
    179 #define SF_CCOMMENT     0x01    // sync on a C-style comment
    180 #define SF_MATCH        0x02    // sync by matching a pattern
    181 
    182 #define SYN_STATE_P(ssp)    ((bufstate_T *)((ssp)->ga_data))
    183 
    184 #define MAXKEYWLEN      80          // maximum length of a keyword
    185 
    186 // The attributes of the syntax item that has been recognized.
    187 static int current_attr = 0;        // attr of current syntax word
    188 static int current_id = 0;          // ID of current char for syn_get_id()
    189 static int current_trans_id = 0;    // idem, transparency removed
    190 static int current_flags = 0;
    191 static int current_seqnr = 0;
    192 static int current_sub_char = 0;
    193 
    194 // Methods of combining two clusters
    195 #define CLUSTER_REPLACE     1   // replace first list with second
    196 #define CLUSTER_ADD         2   // add second list to first
    197 #define CLUSTER_SUBTRACT    3   // subtract second list from first
    198 
    199 #define SYN_CLSTR(buf)  ((syn_cluster_T *)((buf)->b_syn_clusters.ga_data))
    200 
    201 // Syntax group IDs have different types:
    202 //     0 - 19999  normal syntax groups
    203 // 20000 - 20999  ALLBUT indicator (current_syn_inc_tag added)
    204 // 21000 - 21999  TOP indicator (current_syn_inc_tag added)
    205 // 22000 - 22999  CONTAINED indicator (current_syn_inc_tag added)
    206 // 23000 - 32767  cluster IDs (subtract SYNID_CLUSTER for the cluster ID)
    207 #define SYNID_ALLBUT    MAX_HL_ID   // syntax group ID for contains=ALLBUT
    208 #define SYNID_TOP       21000       // syntax group ID for contains=TOP
    209 #define SYNID_CONTAINED 22000       // syntax group ID for contains=CONTAINED
    210 #define SYNID_CLUSTER   23000       // first syntax group ID for clusters
    211 
    212 #define MAX_SYN_INC_TAG 999         // maximum before the above overflow
    213 #define MAX_CLUSTER_ID  (32767 - SYNID_CLUSTER)
    214 
    215 // Annoying Hack(TM):  ":syn include" needs this pointer to pass to
    216 // expand_filename().  Most of the other syntax commands don't need it, so
    217 // instead of passing it to them, we stow it here.
    218 static char **syn_cmdlinep;
    219 
    220 // Another Annoying Hack(TM):  To prevent rules from other ":syn include"'d
    221 // files from leaking into ALLBUT lists, we assign a unique ID to the
    222 // rules in each ":syn include"'d file.
    223 static int current_syn_inc_tag = 0;
    224 static int running_syn_inc_tag = 0;
    225 
    226 // In a hashtable item "hi_key" points to "keyword" in a keyentry.
    227 // This avoids adding a pointer to the hashtable item.
    228 // KE2HIKEY() converts a var pointer to a hashitem key pointer.
    229 // HIKEY2KE() converts a hashitem key pointer to a var pointer.
    230 // HI2KE() converts a hashitem pointer to a var pointer.
    231 static keyentry_T dumkey;
    232 #define KE2HIKEY(kp)  ((kp)->keyword)
    233 #define HIKEY2KE(p)   ((keyentry_T *)((p) - (dumkey.keyword - (char *)&dumkey)))
    234 #define HI2KE(hi)      HIKEY2KE((hi)->hi_key)
    235 
    236 // To reduce the time spent in keepend(), remember at which level in the state
    237 // stack the first item with "keepend" is present.  When "-1", there is no
    238 // "keepend" on the stack.
    239 static int keepend_level = -1;
    240 
    241 static char msg_no_items[] = N_("No Syntax items defined for this buffer");
    242 
    243 // value of si_idx for keywords
    244 #define KEYWORD_IDX     (-1)
    245 // valid of si_cont_list for containing all but contained groups
    246 #define ID_LIST_ALL     ((int16_t *)-1)
    247 
    248 static int next_seqnr = 1;              // value to use for si_seqnr
    249 
    250 // The next possible match in the current line for any pattern is remembered,
    251 // to avoid having to try for a match in each column.
    252 // If next_match_idx == -1, not tried (in this line) yet.
    253 // If next_match_col == MAXCOL, no match found in this line.
    254 // (All end positions have the column of the char after the end)
    255 static int next_match_col;              // column for start of next match
    256 static lpos_T next_match_m_endpos;      // position for end of next match
    257 static lpos_T next_match_h_startpos;    // pos. for highl. start of next match
    258 static lpos_T next_match_h_endpos;      // pos. for highl. end of next match
    259 static int next_match_idx;              // index of matched item
    260 static int next_match_flags;            // flags for next match
    261 static lpos_T next_match_eos_pos;       // end of start pattn (start region)
    262 static lpos_T next_match_eoe_pos;       // pos. for end of end pattern
    263 static int next_match_end_idx;          // ID of group for end pattn or zero
    264 static reg_extmatch_T *next_match_extmatch = NULL;
    265 
    266 // A state stack is an array of integers or stateitem_T, stored in a
    267 // garray_T.  A state stack is invalid if its itemsize entry is zero.
    268 #define INVALID_STATE(ssp)  ((ssp)->ga_itemsize == 0)
    269 #define VALID_STATE(ssp)    ((ssp)->ga_itemsize != 0)
    270 
    271 // The current state (within the line) of the recognition engine.
    272 // When current_state.ga_itemsize is 0 the current state is invalid.
    273 static win_T *syn_win;                  // current window for highlighting
    274 static buf_T *syn_buf;                  // current buffer for highlighting
    275 static synblock_T *syn_block;              // current buffer for highlighting
    276 static proftime_T *syn_tm;                 // timeout limit
    277 static linenr_T current_lnum = 0;          // lnum of current state
    278 static colnr_T current_col = 0;            // column of current state
    279 static bool current_state_stored = false;  // true if stored current state
    280                                           // after setting current_finished
    281 static bool current_finished = false;      // current line has been finished
    282 static garray_T current_state              // current stack of state_items
    283  = GA_EMPTY_INIT_VALUE;
    284 static int16_t *current_next_list = NULL;  // when non-zero, nextgroup list
    285 static int current_next_flags = 0;         // flags for current_next_list
    286 static int current_line_id = 0;            // unique number for current line
    287 
    288 #define CUR_STATE(idx)  ((stateitem_T *)(current_state.ga_data))[idx]
    289 
    290 static bool syn_time_on = false;
    291 #define IF_SYN_TIME(p) (p)
    292 
    293 // Set the timeout used for syntax highlighting.
    294 // Use NULL to reset, no timeout.
    295 void syn_set_timeout(proftime_T *tm)
    296 {
    297  syn_tm = tm;
    298 }
    299 
    300 // Start the syntax recognition for a line.  This function is normally called
    301 // from the screen updating, once for each displayed line.
    302 // The buffer is remembered in syn_buf, because get_syntax_attr() doesn't get
    303 // it.  Careful: curbuf and curwin are likely to point to another buffer and
    304 // window.
    305 void syntax_start(win_T *wp, linenr_T lnum)
    306 {
    307  synstate_T *last_valid = NULL;
    308  synstate_T *last_min_valid = NULL;
    309  synstate_T *sp;
    310  synstate_T *prev = NULL;
    311  linenr_T first_stored;
    312  int dist;
    313  static varnumber_T changedtick = 0;  // remember the last change ID
    314 
    315  current_sub_char = NUL;
    316 
    317  // After switching buffers, invalidate current_state.
    318  // Also do this when a change was made, the current state may be invalid
    319  // then.
    320  if (syn_block != wp->w_s
    321      || syn_buf != wp->w_buffer
    322      || changedtick != buf_get_changedtick(syn_buf)) {
    323    invalidate_current_state();
    324    syn_buf = wp->w_buffer;
    325    syn_block = wp->w_s;
    326  }
    327  changedtick = buf_get_changedtick(syn_buf);
    328  syn_win = wp;
    329 
    330  // Allocate syntax stack when needed.
    331  syn_stack_alloc();
    332  if (syn_block->b_sst_array == NULL) {
    333    return;             // out of memory
    334  }
    335  syn_block->b_sst_lasttick = display_tick;
    336 
    337  // If the state of the end of the previous line is useful, store it.
    338  if (VALID_STATE(&current_state)
    339      && current_lnum < lnum
    340      && current_lnum < syn_buf->b_ml.ml_line_count) {
    341    syn_finish_line(false);
    342    if (!current_state_stored) {
    343      current_lnum++;
    344      store_current_state();
    345    }
    346 
    347    // If the current_lnum is now the same as "lnum", keep the current
    348    // state (this happens very often!).  Otherwise invalidate
    349    // current_state and figure it out below.
    350    if (current_lnum != lnum) {
    351      invalidate_current_state();
    352    }
    353  } else {
    354    invalidate_current_state();
    355  }
    356 
    357  // Try to synchronize from a saved state in b_sst_array[].
    358  // Only do this if lnum is not before and not to far beyond a saved state.
    359  if (INVALID_STATE(&current_state) && syn_block->b_sst_array != NULL) {
    360    // Find last valid saved state before start_lnum.
    361    for (synstate_T *p = syn_block->b_sst_first; p != NULL; p = p->sst_next) {
    362      if (p->sst_lnum > lnum) {
    363        break;
    364      }
    365      if (p->sst_change_lnum == 0) {
    366        last_valid = p;
    367        if (p->sst_lnum >= lnum - syn_block->b_syn_sync_minlines) {
    368          last_min_valid = p;
    369        }
    370      }
    371    }
    372    if (last_min_valid != NULL) {
    373      load_current_state(last_min_valid);
    374    }
    375  }
    376 
    377  // If "lnum" is before or far beyond a line with a saved state, need to
    378  // re-synchronize.
    379  if (INVALID_STATE(&current_state)) {
    380    syn_sync(wp, lnum, last_valid);
    381    if (current_lnum == 1) {
    382      // First line is always valid, no matter "minlines".
    383      first_stored = 1;
    384    } else {
    385      // Need to parse "minlines" lines before state can be considered
    386      // valid to store.
    387      first_stored = current_lnum + syn_block->b_syn_sync_minlines;
    388    }
    389  } else {
    390    first_stored = current_lnum;
    391  }
    392 
    393  // Advance from the sync point or saved state until the current line.
    394  // Save some entries for syncing with later on.
    395  if (syn_block->b_sst_len <= Rows) {
    396    dist = 999999;
    397  } else {
    398    dist = syn_buf->b_ml.ml_line_count / (syn_block->b_sst_len - Rows) + 1;
    399  }
    400  while (current_lnum < lnum) {
    401    syn_start_line();
    402    syn_finish_line(false);
    403    current_lnum++;
    404 
    405    // If we parsed at least "minlines" lines or started at a valid
    406    // state, the current state is considered valid.
    407    if (current_lnum >= first_stored) {
    408      // Check if the saved state entry is for the current line and is
    409      // equal to the current state.  If so, then validate all saved
    410      // states that depended on a change before the parsed line.
    411      if (prev == NULL) {
    412        prev = syn_stack_find_entry(current_lnum - 1);
    413      }
    414      if (prev == NULL) {
    415        sp = syn_block->b_sst_first;
    416      } else {
    417        sp = prev;
    418      }
    419      while (sp != NULL && sp->sst_lnum < current_lnum) {
    420        sp = sp->sst_next;
    421      }
    422      if (sp != NULL
    423          && sp->sst_lnum == current_lnum
    424          && syn_stack_equal(sp)) {
    425        linenr_T parsed_lnum = current_lnum;
    426        prev = sp;
    427        while (sp != NULL && sp->sst_change_lnum <= parsed_lnum) {
    428          if (sp->sst_lnum <= lnum) {
    429            // valid state before desired line, use this one
    430            prev = sp;
    431          } else if (sp->sst_change_lnum == 0) {
    432            // past saved states depending on change, break here.
    433            break;
    434          }
    435          sp->sst_change_lnum = 0;
    436          sp = sp->sst_next;
    437        }
    438        load_current_state(prev);
    439      } else if (prev == NULL
    440                 // Store the state at this line when it's the first one, the line
    441                 // where we start parsing, or some distance from the previously
    442                 // saved state.  But only when parsed at least 'minlines'.
    443                 || current_lnum == lnum
    444                 || current_lnum >= prev->sst_lnum + dist) {
    445        prev = store_current_state();
    446      }
    447    }
    448 
    449    // This can take a long time: break when CTRL-C pressed.  The current
    450    // state will be wrong then.
    451    line_breakcheck();
    452    if (got_int) {
    453      current_lnum = lnum;
    454      break;
    455    }
    456  }
    457 
    458  syn_start_line();
    459 }
    460 
    461 // We cannot simply discard growarrays full of state_items or buf_states; we
    462 // have to manually release their extmatch pointers first.
    463 static void clear_syn_state(synstate_T *p)
    464 {
    465  if (p->sst_stacksize > SST_FIX_STATES) {
    466 #define UNREF_BUFSTATE_EXTMATCH(bs) unref_extmatch((bs)->bs_extmatch)
    467    GA_DEEP_CLEAR(&(p->sst_union.sst_ga), bufstate_T, UNREF_BUFSTATE_EXTMATCH);
    468  } else {
    469    for (int i = 0; i < p->sst_stacksize; i++) {
    470      unref_extmatch(p->sst_union.sst_stack[i].bs_extmatch);
    471    }
    472  }
    473 }
    474 
    475 // Cleanup the current_state stack.
    476 static void clear_current_state(void)
    477 {
    478 #define UNREF_STATEITEM_EXTMATCH(si) unref_extmatch((si)->si_extmatch)
    479  GA_DEEP_CLEAR(&current_state, stateitem_T, UNREF_STATEITEM_EXTMATCH);
    480 }
    481 
    482 // Try to find a synchronisation point for line "lnum".
    483 //
    484 // This sets current_lnum and the current state.  One of three methods is
    485 // used:
    486 // 1. Search backwards for the end of a C-comment.
    487 // 2. Search backwards for given sync patterns.
    488 // 3. Simply start on a given number of lines above "lnum".
    489 static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid)
    490 {
    491  pos_T cursor_save;
    492  linenr_T lnum;
    493  linenr_T break_lnum;
    494  stateitem_T *cur_si;
    495  synpat_T *spp;
    496  int found_flags = 0;
    497  int found_match_idx = 0;
    498  linenr_T found_current_lnum = 0;
    499  int found_current_col = 0;
    500  lpos_T found_m_endpos;
    501 
    502  // Clear any current state that might be hanging around.
    503  invalidate_current_state();
    504 
    505  // Start at least "minlines" back.  Default starting point for parsing is
    506  // there.
    507  // Start further back, to avoid that scrolling backwards will result in
    508  // resyncing for every line.  Now it resyncs only one out of N lines,
    509  // where N is minlines * 1.5, or minlines * 2 if minlines is small.
    510  // Watch out for overflow when minlines is MAXLNUM.
    511  if (syn_block->b_syn_sync_minlines > start_lnum) {
    512    start_lnum = 1;
    513  } else {
    514    if (syn_block->b_syn_sync_minlines == 1) {
    515      lnum = 1;
    516    } else if (syn_block->b_syn_sync_minlines < 10) {
    517      lnum = syn_block->b_syn_sync_minlines * 2;
    518    } else {
    519      lnum = syn_block->b_syn_sync_minlines * 3 / 2;
    520    }
    521    if (syn_block->b_syn_sync_maxlines != 0
    522        && lnum > syn_block->b_syn_sync_maxlines) {
    523      lnum = syn_block->b_syn_sync_maxlines;
    524    }
    525    if (lnum >= start_lnum) {
    526      start_lnum = 1;
    527    } else {
    528      start_lnum -= lnum;
    529    }
    530  }
    531  current_lnum = start_lnum;
    532 
    533  // 1. Search backwards for the end of a C-style comment.
    534  if (syn_block->b_syn_sync_flags & SF_CCOMMENT) {
    535    // Need to make syn_buf the current buffer for a moment, to be able to
    536    // use find_start_comment().
    537    win_T *curwin_save = curwin;
    538    curwin = wp;
    539    buf_T *curbuf_save = curbuf;
    540    curbuf = syn_buf;
    541 
    542    // Skip lines that end in a backslash.
    543    for (; start_lnum > 1; start_lnum--) {
    544      char *l = ml_get(start_lnum - 1);
    545      if (*l == NUL || *(l + ml_get_len(start_lnum - 1) - 1) != '\\') {
    546        break;
    547      }
    548    }
    549    current_lnum = start_lnum;
    550 
    551    // set cursor to start of search
    552    cursor_save = wp->w_cursor;
    553    wp->w_cursor.lnum = start_lnum;
    554    wp->w_cursor.col = 0;
    555 
    556    // If the line is inside a comment, need to find the syntax item that
    557    // defines the comment.
    558    // Restrict the search for the end of a comment to b_syn_sync_maxlines.
    559    if (find_start_comment((int)syn_block->b_syn_sync_maxlines) != NULL) {
    560      for (int idx = syn_block->b_syn_patterns.ga_len; --idx >= 0;) {
    561        if (SYN_ITEMS(syn_block)[idx].sp_syn.id
    562            == syn_block->b_syn_sync_id
    563            && SYN_ITEMS(syn_block)[idx].sp_type == SPTYPE_START) {
    564          validate_current_state();
    565          push_current_state(idx);
    566          update_si_attr(current_state.ga_len - 1);
    567          break;
    568        }
    569      }
    570    }
    571 
    572    // restore cursor and buffer
    573    wp->w_cursor = cursor_save;
    574    curwin = curwin_save;
    575    curbuf = curbuf_save;
    576  } else if (syn_block->b_syn_sync_flags & SF_MATCH) {
    577    // 2. Search backwards for given sync patterns.
    578    if (syn_block->b_syn_sync_maxlines != 0
    579        && start_lnum > syn_block->b_syn_sync_maxlines) {
    580      break_lnum = start_lnum - syn_block->b_syn_sync_maxlines;
    581    } else {
    582      break_lnum = 0;
    583    }
    584 
    585    found_m_endpos.lnum = 0;
    586    found_m_endpos.col = 0;
    587    linenr_T end_lnum = start_lnum;
    588    lnum = start_lnum;
    589    while (--lnum > break_lnum) {
    590      // This can take a long time: break when CTRL-C pressed.
    591      line_breakcheck();
    592      if (got_int) {
    593        invalidate_current_state();
    594        current_lnum = start_lnum;
    595        break;
    596      }
    597 
    598      // Check if we have run into a valid saved state stack now.
    599      if (last_valid != NULL && lnum == last_valid->sst_lnum) {
    600        load_current_state(last_valid);
    601        break;
    602      }
    603 
    604      // Check if the previous line has the line-continuation pattern.
    605      if (lnum > 1 && syn_match_linecont(lnum - 1)) {
    606        continue;
    607      }
    608 
    609      // Start with nothing on the state stack
    610      validate_current_state();
    611 
    612      for (current_lnum = lnum; current_lnum < end_lnum; current_lnum++) {
    613        syn_start_line();
    614        while (true) {
    615          bool had_sync_point = syn_finish_line(true);
    616          // When a sync point has been found, remember where, and
    617          // continue to look for another one, further on in the line.
    618          if (had_sync_point && current_state.ga_len) {
    619            cur_si = &CUR_STATE(current_state.ga_len - 1);
    620            if (cur_si->si_m_endpos.lnum > start_lnum) {
    621              // ignore match that goes to after where started
    622              current_lnum = end_lnum;
    623              break;
    624            }
    625            if (cur_si->si_idx < 0) {
    626              // Cannot happen?
    627              found_flags = 0;
    628              found_match_idx = KEYWORD_IDX;
    629            } else {
    630              spp = &(SYN_ITEMS(syn_block)[cur_si->si_idx]);
    631              found_flags = spp->sp_flags;
    632              found_match_idx = spp->sp_sync_idx;
    633            }
    634            found_current_lnum = current_lnum;
    635            found_current_col = current_col;
    636            found_m_endpos = cur_si->si_m_endpos;
    637            // Continue after the match (be aware of a zero-length
    638            // match).
    639            if (found_m_endpos.lnum > current_lnum) {
    640              current_lnum = found_m_endpos.lnum;
    641              current_col = found_m_endpos.col;
    642              if (current_lnum >= end_lnum) {
    643                break;
    644              }
    645            } else if (found_m_endpos.col > current_col) {
    646              current_col = found_m_endpos.col;
    647            } else {
    648              current_col++;
    649            }
    650 
    651            // syn_current_attr() will have skipped the check for
    652            // an item that ends here, need to do that now.  Be
    653            // careful not to go past the NUL.
    654            colnr_T prev_current_col = current_col;
    655            if (syn_getcurline()[current_col] != NUL) {
    656              current_col++;
    657            }
    658            check_state_ends();
    659            current_col = prev_current_col;
    660          } else {
    661            break;
    662          }
    663        }
    664      }
    665 
    666      // If a sync point was encountered, break here.
    667      if (found_flags) {
    668        // Put the item that was specified by the sync point on the
    669        // state stack.  If there was no item specified, make the
    670        // state stack empty.
    671        clear_current_state();
    672        if (found_match_idx >= 0) {
    673          push_current_state(found_match_idx);
    674          update_si_attr(current_state.ga_len - 1);
    675        }
    676 
    677        // When using "grouphere", continue from the sync point
    678        // match, until the end of the line.  Parsing starts at
    679        // the next line.
    680        // For "groupthere" the parsing starts at start_lnum.
    681        if (found_flags & HL_SYNC_HERE) {
    682          if (!GA_EMPTY(&current_state)) {
    683            cur_si = &CUR_STATE(current_state.ga_len - 1);
    684            cur_si->si_h_startpos.lnum = found_current_lnum;
    685            cur_si->si_h_startpos.col = found_current_col;
    686            update_si_end(cur_si, (int)current_col, true);
    687            check_keepend();
    688          }
    689          current_col = found_m_endpos.col;
    690          current_lnum = found_m_endpos.lnum;
    691          syn_finish_line(false);
    692          current_lnum++;
    693        } else {
    694          current_lnum = start_lnum;
    695        }
    696 
    697        break;
    698      }
    699 
    700      end_lnum = lnum;
    701      invalidate_current_state();
    702    }
    703 
    704    // Ran into start of the file or exceeded maximum number of lines
    705    if (lnum <= break_lnum) {
    706      invalidate_current_state();
    707      current_lnum = break_lnum + 1;
    708    }
    709  }
    710 
    711  validate_current_state();
    712 }
    713 
    714 static void save_chartab(char *chartab)
    715 {
    716  if (syn_block->b_syn_isk == empty_string_option) {
    717    return;
    718  }
    719 
    720  memmove(chartab, syn_buf->b_chartab, (size_t)32);
    721  memmove(syn_buf->b_chartab, syn_win->w_s->b_syn_chartab, (size_t)32);
    722 }
    723 
    724 static void restore_chartab(char *chartab)
    725 {
    726  if (syn_win->w_s->b_syn_isk != empty_string_option) {
    727    memmove(syn_buf->b_chartab, chartab, (size_t)32);
    728  }
    729 }
    730 
    731 /// Return true if the line-continuation pattern matches in line "lnum".
    732 static int syn_match_linecont(linenr_T lnum)
    733 {
    734  if (syn_block->b_syn_linecont_prog == NULL) {
    735    return false;
    736  }
    737 
    738  regmmatch_T regmatch;
    739  // chartab array for syn iskeyword
    740  char buf_chartab[32];
    741  save_chartab(buf_chartab);
    742 
    743  regmatch.rmm_ic = syn_block->b_syn_linecont_ic;
    744  regmatch.regprog = syn_block->b_syn_linecont_prog;
    745  int r = syn_regexec(&regmatch, lnum, 0,
    746                      IF_SYN_TIME(&syn_block->b_syn_linecont_time));
    747  syn_block->b_syn_linecont_prog = regmatch.regprog;
    748 
    749  restore_chartab(buf_chartab);
    750  return r;
    751 }
    752 
    753 // Prepare the current state for the start of a line.
    754 static void syn_start_line(void)
    755 {
    756  current_finished = false;
    757  current_col = 0;
    758 
    759  // Need to update the end of a start/skip/end that continues from the
    760  // previous line and regions that have "keepend".
    761  if (!GA_EMPTY(&current_state)) {
    762    syn_update_ends(true);
    763    check_state_ends();
    764  }
    765 
    766  next_match_idx = -1;
    767  current_line_id++;
    768  next_seqnr = 1;
    769 }
    770 
    771 /// Check for items in the stack that need their end updated.
    772 ///
    773 /// @param startofline  if true the last item is always updated.
    774 ///                     if false the item with "keepend" is forcefully updated.
    775 static void syn_update_ends(bool startofline)
    776 {
    777  stateitem_T *cur_si;
    778 
    779  if (startofline) {
    780    // Check for a match carried over from a previous line with a
    781    // contained region.  The match ends as soon as the region ends.
    782    for (int i = 0; i < current_state.ga_len; i++) {
    783      cur_si = &CUR_STATE(i);
    784      if (cur_si->si_idx >= 0
    785          && (SYN_ITEMS(syn_block)[cur_si->si_idx]).sp_type
    786          == SPTYPE_MATCH
    787          && cur_si->si_m_endpos.lnum < current_lnum) {
    788        cur_si->si_flags |= HL_MATCHCONT;
    789        cur_si->si_m_endpos.lnum = 0;
    790        cur_si->si_m_endpos.col = 0;
    791        cur_si->si_h_endpos = cur_si->si_m_endpos;
    792        cur_si->si_ends = true;
    793      }
    794    }
    795  }
    796 
    797  // Need to update the end of a start/skip/end that continues from the
    798  // previous line.  And regions that have "keepend", because they may
    799  // influence contained items.  If we've just removed "extend"
    800  // (startofline == 0) then we should update ends of normal regions
    801  // contained inside "keepend" because "extend" could have extended
    802  // these "keepend" regions as well as contained normal regions.
    803  // Then check for items ending in column 0.
    804  int i = current_state.ga_len - 1;
    805  if (keepend_level >= 0) {
    806    for (; i > keepend_level; i--) {
    807      if (CUR_STATE(i).si_flags & HL_EXTEND) {
    808        break;
    809      }
    810    }
    811  }
    812 
    813  bool seen_keepend = false;
    814  for (; i < current_state.ga_len; i++) {
    815    cur_si = &CUR_STATE(i);
    816    if ((cur_si->si_flags & HL_KEEPEND)
    817        || (seen_keepend && !startofline)
    818        || (i == current_state.ga_len - 1 && startofline)) {
    819      cur_si->si_h_startpos.col = 0;            // start highl. in col 0
    820      cur_si->si_h_startpos.lnum = current_lnum;
    821 
    822      if (!(cur_si->si_flags & HL_MATCHCONT)) {
    823        update_si_end(cur_si, (int)current_col, !startofline);
    824      }
    825 
    826      if (!startofline && (cur_si->si_flags & HL_KEEPEND)) {
    827        seen_keepend = true;
    828      }
    829    }
    830  }
    831  check_keepend();
    832 }
    833 
    834 /////////////////////////////////////////
    835 // Handling of the state stack cache.
    836 
    837 // EXPLANATION OF THE SYNTAX STATE STACK CACHE
    838 //
    839 // To speed up syntax highlighting, the state stack for the start of some
    840 // lines is cached.  These entries can be used to start parsing at that point.
    841 //
    842 // The stack is kept in b_sst_array[] for each buffer.  There is a list of
    843 // valid entries.  b_sst_first points to the first one, then follow sst_next.
    844 // The entries are sorted on line number.  The first entry is often for line 2
    845 // (line 1 always starts with an empty stack).
    846 // There is also a list for free entries.  This construction is used to avoid
    847 // having to allocate and free memory blocks too often.
    848 //
    849 // When making changes to the buffer, this is logged in b_mod_*.  When calling
    850 // update_screen() to update the display, it will call
    851 // syn_stack_apply_changes() for each displayed buffer to adjust the cached
    852 // entries.  The entries which are inside the changed area are removed,
    853 // because they must be recomputed.  Entries below the changed have their line
    854 // number adjusted for deleted/inserted lines, and have their sst_change_lnum
    855 // set to indicate that a check must be made if the changed lines would change
    856 // the cached entry.
    857 //
    858 // When later displaying lines, an entry is stored for each line.  Displayed
    859 // lines are likely to be displayed again, in which case the state at the
    860 // start of the line is needed.
    861 // For not displayed lines, an entry is stored for every so many lines.  These
    862 // entries will be used e.g., when scrolling backwards.  The distance between
    863 // entries depends on the number of lines in the buffer.  For small buffers
    864 // the distance is fixed at SST_DIST, for large buffers there is a fixed
    865 // number of entries SST_MAX_ENTRIES, and the distance is computed.
    866 
    867 static void syn_stack_free_block(synblock_T *block)
    868 {
    869  if (block->b_sst_array == NULL) {
    870    return;
    871  }
    872 
    873  for (synstate_T *p = block->b_sst_first; p != NULL; p = p->sst_next) {
    874    clear_syn_state(p);
    875  }
    876  XFREE_CLEAR(block->b_sst_array);
    877  block->b_sst_first = NULL;
    878  block->b_sst_len = 0;
    879 }
    880 // Free b_sst_array[] for buffer "buf".
    881 // Used when syntax items changed to force resyncing everywhere.
    882 void syn_stack_free_all(synblock_T *block)
    883 {
    884  syn_stack_free_block(block);
    885 
    886  // When using "syntax" fold method, must update all folds.
    887  FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
    888    if (wp->w_s == block && foldmethodIsSyntax(wp)) {
    889      foldUpdateAll(wp);
    890    }
    891  }
    892 }
    893 
    894 // Allocate the syntax state stack for syn_buf when needed.
    895 // If the number of entries in b_sst_array[] is much too big or a bit too
    896 // small, reallocate it.
    897 // Also used to allocate b_sst_array[] for the first time.
    898 static void syn_stack_alloc(void)
    899 {
    900  int len = syn_buf->b_ml.ml_line_count / SST_DIST + Rows * 2;
    901  if (len < SST_MIN_ENTRIES) {
    902    len = SST_MIN_ENTRIES;
    903  } else if (len > SST_MAX_ENTRIES) {
    904    len = SST_MAX_ENTRIES;
    905  }
    906  if (syn_block->b_sst_len > len * 2 || syn_block->b_sst_len < len) {
    907    // Allocate 50% too much, to avoid reallocating too often.
    908    len = syn_buf->b_ml.ml_line_count;
    909    len = (len + len / 2) / SST_DIST + Rows * 2;
    910    if (len < SST_MIN_ENTRIES) {
    911      len = SST_MIN_ENTRIES;
    912    } else if (len > SST_MAX_ENTRIES) {
    913      len = SST_MAX_ENTRIES;
    914    }
    915 
    916    if (syn_block->b_sst_array != NULL) {
    917      // When shrinking the array, cleanup the existing stack.
    918      // Make sure that all valid entries fit in the new array.
    919      while (syn_block->b_sst_len - syn_block->b_sst_freecount + 2 > len
    920             && syn_stack_cleanup()) {}
    921      if (len < syn_block->b_sst_len - syn_block->b_sst_freecount + 2) {
    922        len = syn_block->b_sst_len - syn_block->b_sst_freecount + 2;
    923      }
    924    }
    925 
    926    assert(len >= 0);
    927    synstate_T *sstp = xcalloc((size_t)len, sizeof(synstate_T));
    928 
    929    synstate_T *to = sstp - 1;
    930    if (syn_block->b_sst_array != NULL) {
    931      // Move the states from the old array to the new one.
    932      for (synstate_T *from = syn_block->b_sst_first; from != NULL;
    933           from = from->sst_next) {
    934        to++;
    935        *to = *from;
    936        to->sst_next = to + 1;
    937      }
    938    }
    939    if (to != sstp - 1) {
    940      to->sst_next = NULL;
    941      syn_block->b_sst_first = sstp;
    942      syn_block->b_sst_freecount = len - (int)(to - sstp) - 1;
    943    } else {
    944      syn_block->b_sst_first = NULL;
    945      syn_block->b_sst_freecount = len;
    946    }
    947 
    948    // Create the list of free entries.
    949    syn_block->b_sst_firstfree = to + 1;
    950    while (++to < sstp + len) {
    951      to->sst_next = to + 1;
    952    }
    953    (sstp + len - 1)->sst_next = NULL;
    954 
    955    xfree(syn_block->b_sst_array);
    956    syn_block->b_sst_array = sstp;
    957    syn_block->b_sst_len = len;
    958  }
    959 }
    960 
    961 // Check for changes in a buffer to affect stored syntax states.  Uses the
    962 // b_mod_* fields.
    963 // Called from update_screen(), before screen is being updated, once for each
    964 // displayed buffer.
    965 void syn_stack_apply_changes(buf_T *buf)
    966 {
    967  syn_stack_apply_changes_block(&buf->b_s, buf);
    968 
    969  FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
    970    if ((wp->w_buffer == buf) && (wp->w_s != &buf->b_s)) {
    971      syn_stack_apply_changes_block(wp->w_s, buf);
    972    }
    973  }
    974 }
    975 
    976 static void syn_stack_apply_changes_block(synblock_T *block, buf_T *buf)
    977 {
    978  synstate_T *prev = NULL;
    979  for (synstate_T *p = block->b_sst_first; p != NULL;) {
    980    if (p->sst_lnum + block->b_syn_sync_linebreaks > buf->b_mod_top) {
    981      linenr_T n = p->sst_lnum + buf->b_mod_xlines;
    982      if (n <= buf->b_mod_bot) {
    983        // this state is inside the changed area, remove it
    984        synstate_T *np = p->sst_next;
    985        if (prev == NULL) {
    986          block->b_sst_first = np;
    987        } else {
    988          prev->sst_next = np;
    989        }
    990        syn_stack_free_entry(block, p);
    991        p = np;
    992        continue;
    993      }
    994      // This state is below the changed area.  Remember the line
    995      // that needs to be parsed before this entry can be made valid
    996      // again.
    997      if (p->sst_change_lnum != 0 && p->sst_change_lnum > buf->b_mod_top) {
    998        if (p->sst_change_lnum + buf->b_mod_xlines > buf->b_mod_top) {
    999          p->sst_change_lnum += buf->b_mod_xlines;
   1000        } else {
   1001          p->sst_change_lnum = buf->b_mod_top;
   1002        }
   1003      }
   1004      if (p->sst_change_lnum == 0
   1005          || p->sst_change_lnum < buf->b_mod_bot) {
   1006        p->sst_change_lnum = buf->b_mod_bot;
   1007      }
   1008 
   1009      p->sst_lnum = n;
   1010    }
   1011    prev = p;
   1012    p = p->sst_next;
   1013  }
   1014 }
   1015 
   1016 /// Reduce the number of entries in the state stack for syn_buf.
   1017 ///
   1018 /// @return  true if at least one entry was freed.
   1019 static bool syn_stack_cleanup(void)
   1020 {
   1021  synstate_T *prev;
   1022  disptick_T tick;
   1023  int dist;
   1024  bool retval = false;
   1025 
   1026  if (syn_block->b_sst_first == NULL) {
   1027    return retval;
   1028  }
   1029 
   1030  // Compute normal distance between non-displayed entries.
   1031  if (syn_block->b_sst_len <= Rows) {
   1032    dist = 999999;
   1033  } else {
   1034    dist = syn_buf->b_ml.ml_line_count / (syn_block->b_sst_len - Rows) + 1;
   1035  }
   1036 
   1037  // Go through the list to find the "tick" for the oldest entry that can
   1038  // be removed.  Set "above" when the "tick" for the oldest entry is above
   1039  // "b_sst_lasttick" (the display tick wraps around).
   1040  tick = syn_block->b_sst_lasttick;
   1041  bool above = false;
   1042  prev = syn_block->b_sst_first;
   1043  for (synstate_T *p = prev->sst_next; p != NULL; prev = p, p = p->sst_next) {
   1044    if (prev->sst_lnum + dist > p->sst_lnum) {
   1045      if (p->sst_tick > syn_block->b_sst_lasttick) {
   1046        if (!above || p->sst_tick < tick) {
   1047          tick = p->sst_tick;
   1048        }
   1049        above = true;
   1050      } else if (!above && p->sst_tick < tick) {
   1051        tick = p->sst_tick;
   1052      }
   1053    }
   1054  }
   1055 
   1056  // Go through the list to make the entries for the oldest tick at an
   1057  // interval of several lines.
   1058  prev = syn_block->b_sst_first;
   1059  for (synstate_T *p = prev->sst_next; p != NULL; prev = p, p = p->sst_next) {
   1060    if (p->sst_tick == tick && prev->sst_lnum + dist > p->sst_lnum) {
   1061      // Move this entry from used list to free list
   1062      prev->sst_next = p->sst_next;
   1063      syn_stack_free_entry(syn_block, p);
   1064      p = prev;
   1065      retval = true;
   1066    }
   1067  }
   1068  return retval;
   1069 }
   1070 
   1071 // Free the allocated memory for a syn_state item.
   1072 // Move the entry into the free list.
   1073 static void syn_stack_free_entry(synblock_T *block, synstate_T *p)
   1074 {
   1075  clear_syn_state(p);
   1076  p->sst_next = block->b_sst_firstfree;
   1077  block->b_sst_firstfree = p;
   1078  block->b_sst_freecount++;
   1079 }
   1080 
   1081 // Find an entry in the list of state stacks at or before "lnum".
   1082 // Returns NULL when there is no entry or the first entry is after "lnum".
   1083 static synstate_T *syn_stack_find_entry(linenr_T lnum)
   1084 {
   1085  synstate_T *prev = NULL;
   1086  for (synstate_T *p = syn_block->b_sst_first; p != NULL; prev = p, p = p->sst_next) {
   1087    if (p->sst_lnum == lnum) {
   1088      return p;
   1089    }
   1090    if (p->sst_lnum > lnum) {
   1091      break;
   1092    }
   1093  }
   1094  return prev;
   1095 }
   1096 
   1097 // Try saving the current state in b_sst_array[].
   1098 // The current state must be valid for the start of the current_lnum line!
   1099 static synstate_T *store_current_state(void)
   1100 {
   1101  int i;
   1102  synstate_T *p;
   1103  bufstate_T *bp;
   1104  stateitem_T *cur_si;
   1105  synstate_T *sp = syn_stack_find_entry(current_lnum);
   1106 
   1107  // If the current state contains a start or end pattern that continues
   1108  // from the previous line, we can't use it.  Don't store it then.
   1109  for (i = current_state.ga_len - 1; i >= 0; i--) {
   1110    cur_si = &CUR_STATE(i);
   1111    if (cur_si->si_h_startpos.lnum >= current_lnum
   1112        || cur_si->si_m_endpos.lnum >= current_lnum
   1113        || cur_si->si_h_endpos.lnum >= current_lnum
   1114        || (cur_si->si_end_idx
   1115            && cur_si->si_eoe_pos.lnum >= current_lnum)) {
   1116      break;
   1117    }
   1118  }
   1119  if (i >= 0) {
   1120    if (sp != NULL) {
   1121      // find "sp" in the list and remove it
   1122      if (syn_block->b_sst_first == sp) {
   1123        // it's the first entry
   1124        syn_block->b_sst_first = sp->sst_next;
   1125      } else {
   1126        // find the entry just before this one to adjust sst_next
   1127        for (p = syn_block->b_sst_first; p != NULL; p = p->sst_next) {
   1128          if (p->sst_next == sp) {
   1129            break;
   1130          }
   1131        }
   1132        if (p != NULL) {        // just in case
   1133          p->sst_next = sp->sst_next;
   1134        }
   1135      }
   1136      syn_stack_free_entry(syn_block, sp);
   1137      sp = NULL;
   1138    }
   1139  } else if (sp == NULL || sp->sst_lnum != current_lnum) {
   1140    // Add a new entry
   1141    // If no free items, cleanup the array first.
   1142    if (syn_block->b_sst_freecount == 0) {
   1143      syn_stack_cleanup();
   1144      // "sp" may have been moved to the freelist now
   1145      sp = syn_stack_find_entry(current_lnum);
   1146    }
   1147    // Still no free items?  Must be a strange problem...
   1148    if (syn_block->b_sst_freecount == 0) {
   1149      sp = NULL;
   1150    } else {
   1151      // Take the first item from the free list and put it in the used
   1152      // list, after *sp
   1153      p = syn_block->b_sst_firstfree;
   1154      syn_block->b_sst_firstfree = p->sst_next;
   1155      syn_block->b_sst_freecount--;
   1156      if (sp == NULL) {
   1157        // Insert in front of the list
   1158        p->sst_next = syn_block->b_sst_first;
   1159        syn_block->b_sst_first = p;
   1160      } else {
   1161        // insert in list after *sp
   1162        p->sst_next = sp->sst_next;
   1163        sp->sst_next = p;
   1164      }
   1165      sp = p;
   1166      sp->sst_stacksize = 0;
   1167      sp->sst_lnum = current_lnum;
   1168    }
   1169  }
   1170  if (sp != NULL) {
   1171    // When overwriting an existing state stack, clear it first
   1172    clear_syn_state(sp);
   1173    sp->sst_stacksize = current_state.ga_len;
   1174    if (current_state.ga_len > SST_FIX_STATES) {
   1175      // Need to clear it, might be something remaining from when the
   1176      // length was less than SST_FIX_STATES.
   1177      ga_init(&sp->sst_union.sst_ga, (int)sizeof(bufstate_T), 1);
   1178      ga_grow(&sp->sst_union.sst_ga, current_state.ga_len);
   1179      sp->sst_union.sst_ga.ga_len = current_state.ga_len;
   1180      bp = SYN_STATE_P(&(sp->sst_union.sst_ga));
   1181    } else {
   1182      bp = sp->sst_union.sst_stack;
   1183    }
   1184    for (i = 0; i < sp->sst_stacksize; i++) {
   1185      bp[i].bs_idx = CUR_STATE(i).si_idx;
   1186      bp[i].bs_flags = CUR_STATE(i).si_flags;
   1187      bp[i].bs_seqnr = CUR_STATE(i).si_seqnr;
   1188      bp[i].bs_cchar = CUR_STATE(i).si_cchar;
   1189      bp[i].bs_extmatch = ref_extmatch(CUR_STATE(i).si_extmatch);
   1190    }
   1191    sp->sst_next_flags = current_next_flags;
   1192    sp->sst_next_list = current_next_list;
   1193    sp->sst_tick = display_tick;
   1194    sp->sst_change_lnum = 0;
   1195  }
   1196  current_state_stored = true;
   1197  return sp;
   1198 }
   1199 
   1200 // Copy a state stack from "from" in b_sst_array[] to current_state;
   1201 static void load_current_state(synstate_T *from)
   1202 {
   1203  bufstate_T *bp;
   1204 
   1205  clear_current_state();
   1206  validate_current_state();
   1207  keepend_level = -1;
   1208  if (from->sst_stacksize) {
   1209    ga_grow(&current_state, from->sst_stacksize);
   1210    if (from->sst_stacksize > SST_FIX_STATES) {
   1211      bp = SYN_STATE_P(&(from->sst_union.sst_ga));
   1212    } else {
   1213      bp = from->sst_union.sst_stack;
   1214    }
   1215    for (int i = 0; i < from->sst_stacksize; i++) {
   1216      CUR_STATE(i).si_idx = bp[i].bs_idx;
   1217      CUR_STATE(i).si_flags = bp[i].bs_flags;
   1218      CUR_STATE(i).si_seqnr = bp[i].bs_seqnr;
   1219      CUR_STATE(i).si_cchar = bp[i].bs_cchar;
   1220      CUR_STATE(i).si_extmatch = ref_extmatch(bp[i].bs_extmatch);
   1221      if (keepend_level < 0 && (CUR_STATE(i).si_flags & HL_KEEPEND)) {
   1222        keepend_level = i;
   1223      }
   1224      CUR_STATE(i).si_ends = false;
   1225      CUR_STATE(i).si_m_lnum = 0;
   1226      if (CUR_STATE(i).si_idx >= 0) {
   1227        CUR_STATE(i).si_next_list =
   1228          (SYN_ITEMS(syn_block)[CUR_STATE(i).si_idx]).sp_next_list;
   1229      } else {
   1230        CUR_STATE(i).si_next_list = NULL;
   1231      }
   1232      update_si_attr(i);
   1233    }
   1234    current_state.ga_len = from->sst_stacksize;
   1235  }
   1236  current_next_list = from->sst_next_list;
   1237  current_next_flags = from->sst_next_flags;
   1238  current_lnum = from->sst_lnum;
   1239 }
   1240 
   1241 /// Compare saved state stack "*sp" with the current state.
   1242 ///
   1243 /// @return  true when they are equal.
   1244 static bool syn_stack_equal(synstate_T *sp)
   1245 {
   1246  bufstate_T *bp;
   1247 
   1248  // First a quick check if the stacks have the same size end nextlist.
   1249  if (sp->sst_stacksize != current_state.ga_len
   1250      || sp->sst_next_list != current_next_list) {
   1251    return false;
   1252  }
   1253 
   1254  // Need to compare all states on both stacks.
   1255  if (sp->sst_stacksize > SST_FIX_STATES) {
   1256    bp = SYN_STATE_P(&(sp->sst_union.sst_ga));
   1257  } else {
   1258    bp = sp->sst_union.sst_stack;
   1259  }
   1260 
   1261  int i;
   1262  for (i = current_state.ga_len; --i >= 0;) {
   1263    // If the item has another index the state is different.
   1264    if (bp[i].bs_idx != CUR_STATE(i).si_idx) {
   1265      break;
   1266    }
   1267    if (bp[i].bs_extmatch == CUR_STATE(i).si_extmatch) {
   1268      continue;
   1269    }
   1270    // When the extmatch pointers are different, the strings in them can
   1271    // still be the same.  Check if the extmatch references are equal.
   1272    reg_extmatch_T *bsx = bp[i].bs_extmatch;
   1273    reg_extmatch_T *six = CUR_STATE(i).si_extmatch;
   1274    // If one of the extmatch pointers is NULL the states are different.
   1275    if (bsx == NULL || six == NULL) {
   1276      break;
   1277    }
   1278    int j;
   1279    for (j = 0; j < NSUBEXP; j++) {
   1280      // Check each referenced match string. They must all be equal.
   1281      if (bsx->matches[j] != six->matches[j]) {
   1282        // If the pointer is different it can still be the same text.
   1283        // Compare the strings, ignore case when the start item has the
   1284        // sp_ic flag set.
   1285        if (bsx->matches[j] == NULL || six->matches[j] == NULL) {
   1286          break;
   1287        }
   1288        if (mb_strcmp_ic((SYN_ITEMS(syn_block)[CUR_STATE(i).si_idx]).sp_ic,
   1289                         (const char *)bsx->matches[j],
   1290                         (const char *)six->matches[j]) != 0) {
   1291          break;
   1292        }
   1293      }
   1294    }
   1295    if (j != NSUBEXP) {
   1296      break;
   1297    }
   1298  }
   1299  return i < 0 ? true : false;
   1300 }
   1301 
   1302 // We stop parsing syntax above line "lnum".  If the stored state at or below
   1303 // this line depended on a change before it, it now depends on the line below
   1304 // the last parsed line.
   1305 // The window looks like this:
   1306 //          line which changed
   1307 //          displayed line
   1308 //          displayed line
   1309 // lnum ->  line below window
   1310 void syntax_end_parsing(win_T *wp, linenr_T lnum)
   1311 {
   1312  synstate_T *sp;
   1313 
   1314  if (syn_block != wp->w_s) {
   1315    return;  // not the right window
   1316  }
   1317  sp = syn_stack_find_entry(lnum);
   1318  if (sp != NULL && sp->sst_lnum < lnum) {
   1319    sp = sp->sst_next;
   1320  }
   1321 
   1322  if (sp != NULL && sp->sst_change_lnum != 0) {
   1323    sp->sst_change_lnum = lnum;
   1324  }
   1325 }
   1326 
   1327 // End of handling of the state stack.
   1328 // **************************************
   1329 
   1330 static void invalidate_current_state(void)
   1331 {
   1332  clear_current_state();
   1333  current_state.ga_itemsize = 0;        // mark current_state invalid
   1334  current_next_list = NULL;
   1335  keepend_level = -1;
   1336 }
   1337 
   1338 static void validate_current_state(void)
   1339 {
   1340  current_state.ga_itemsize = sizeof(stateitem_T);
   1341  ga_set_growsize(&current_state, 3);
   1342 }
   1343 
   1344 /// This will only be called just after get_syntax_attr() for the previous
   1345 /// line, to check if the next line needs to be redrawn too.
   1346 ///
   1347 /// @return  true if the syntax at start of lnum changed since last time.
   1348 bool syntax_check_changed(linenr_T lnum)
   1349 {
   1350  bool retval = true;
   1351  synstate_T *sp;
   1352 
   1353  // Check the state stack when:
   1354  // - lnum is just below the previously syntaxed line.
   1355  // - lnum is not before the lines with saved states.
   1356  // - lnum is not past the lines with saved states.
   1357  // - lnum is at or before the last changed line.
   1358  if (VALID_STATE(&current_state) && lnum == current_lnum + 1) {
   1359    sp = syn_stack_find_entry(lnum);
   1360    if (sp != NULL && sp->sst_lnum == lnum) {
   1361      // finish the previous line (needed when not all of the line was
   1362      // drawn)
   1363      syn_finish_line(false);
   1364 
   1365      // Compare the current state with the previously saved state of
   1366      // the line.
   1367      if (syn_stack_equal(sp)) {
   1368        retval = false;
   1369      }
   1370 
   1371      // Store the current state in b_sst_array[] for later use.
   1372      current_lnum++;
   1373      store_current_state();
   1374    }
   1375  }
   1376 
   1377  return retval;
   1378 }
   1379 
   1380 /// Finish the current line.
   1381 /// This doesn't return any attributes, it only gets the state at the end of
   1382 /// the line.  It can start anywhere in the line, as long as the current state
   1383 /// is valid.
   1384 ///
   1385 /// @param syncing  called for syncing
   1386 static bool syn_finish_line(const bool syncing)
   1387 {
   1388  while (!current_finished) {
   1389    syn_current_attr(syncing, false, NULL, false);
   1390 
   1391    // When syncing, and found some item, need to check the item.
   1392    if (syncing && current_state.ga_len) {
   1393      // Check for match with sync item.
   1394      const stateitem_T *const cur_si = &CUR_STATE(current_state.ga_len - 1);
   1395      if (cur_si->si_idx >= 0
   1396          && (SYN_ITEMS(syn_block)[cur_si->si_idx].sp_flags
   1397              & (HL_SYNC_HERE|HL_SYNC_THERE))) {
   1398        return true;
   1399      }
   1400 
   1401      // syn_current_attr() will have skipped the check for an item
   1402      // that ends here, need to do that now.  Be careful not to go
   1403      // past the NUL.
   1404      const colnr_T prev_current_col = current_col;
   1405      if (syn_getcurline()[current_col] != NUL) {
   1406        current_col++;
   1407      }
   1408      check_state_ends();
   1409      current_col = prev_current_col;
   1410    }
   1411    current_col++;
   1412  }
   1413  return false;
   1414 }
   1415 
   1416 /// Gets highlight attributes for next character.
   1417 /// Must first call syntax_start() once for the line.
   1418 /// "col" is normally 0 for the first use in a line, and increments by one each
   1419 /// time.  It's allowed to skip characters and to stop before the end of the
   1420 /// line.  But only a "col" after a previously used column is allowed.
   1421 /// When "can_spell" is not NULL set it to true when spell-checking should be
   1422 /// done.
   1423 ///
   1424 /// @param keep_state  keep state of char at "col"
   1425 ///
   1426 /// @return            highlight attributes for next character.
   1427 int get_syntax_attr(const colnr_T col, bool *const can_spell, const bool keep_state)
   1428 {
   1429  int attr = 0;
   1430 
   1431  if (can_spell != NULL) {
   1432    // Default: Only do spelling when there is no @Spell cluster or when
   1433    // ":syn spell toplevel" was used.
   1434    *can_spell = syn_block->b_syn_spell == SYNSPL_DEFAULT
   1435                 ? (syn_block->b_spell_cluster_id == 0)
   1436                 : (syn_block->b_syn_spell == SYNSPL_TOP);
   1437  }
   1438 
   1439  // check for out of memory situation
   1440  if (syn_block->b_sst_array == NULL) {
   1441    return 0;
   1442  }
   1443 
   1444  // After 'synmaxcol' the attribute is always zero.
   1445  if (syn_buf->b_p_smc > 0 && col >= (colnr_T)syn_buf->b_p_smc) {
   1446    clear_current_state();
   1447    current_id = 0;
   1448    current_trans_id = 0;
   1449    current_flags = 0;
   1450    current_seqnr = 0;
   1451    return 0;
   1452  }
   1453 
   1454  // Make sure current_state is valid
   1455  if (INVALID_STATE(&current_state)) {
   1456    validate_current_state();
   1457  }
   1458 
   1459  // Skip from the current column to "col", get the attributes for "col".
   1460  while (current_col <= col) {
   1461    attr = syn_current_attr(false, true, can_spell,
   1462                            current_col == col ? keep_state : false);
   1463    current_col++;
   1464  }
   1465 
   1466  return attr;
   1467 }
   1468 
   1469 /// Get syntax attributes for current_lnum, current_col.
   1470 ///
   1471 /// @param syncing     When true: called for syncing
   1472 /// @param displaying  result will be displayed
   1473 /// @param can_spell   return: do spell checking
   1474 /// @param keep_state  keep syntax stack afterwards
   1475 static int syn_current_attr(const bool syncing, const bool displaying, bool *const can_spell,
   1476                            const bool keep_state)
   1477 {
   1478  lpos_T endpos;
   1479  lpos_T hl_startpos;
   1480  lpos_T hl_endpos;
   1481  lpos_T eos_pos;               // end-of-start match (start region)
   1482  lpos_T eoe_pos;               // end-of-end pattern
   1483  int end_idx;                  // group ID for end pattern
   1484  stateitem_T *cur_si;
   1485  stateitem_T *sip = NULL;
   1486  int startcol;
   1487  int endcol;
   1488  int flags;
   1489  int cchar;
   1490  int16_t *next_list;
   1491  bool found_match;                         // found usable match
   1492  static bool try_next_column = false;      // must try in next col
   1493  regmmatch_T regmatch;
   1494  lpos_T pos;
   1495  reg_extmatch_T *cur_extmatch = NULL;
   1496  char buf_chartab[32];    // chartab array for syn iskeyword
   1497  char *line;              // current line.  NOTE: becomes invalid after
   1498                           // looking for a pattern match!
   1499 
   1500  // variables for zero-width matches that have a "nextgroup" argument
   1501  bool keep_next_list;
   1502  bool zero_width_next_list = false;
   1503  garray_T zero_width_next_ga;
   1504 
   1505  // No character, no attributes!  Past end of line?
   1506  // Do try matching with an empty line (could be the start of a region).
   1507  line = syn_getcurline();
   1508  if (line[current_col] == NUL && current_col != 0) {
   1509    // If we found a match after the last column, use it.
   1510    if (next_match_idx >= 0 && next_match_col >= (int)current_col
   1511        && next_match_col != MAXCOL) {
   1512      push_next_match();
   1513    }
   1514 
   1515    current_finished = true;
   1516    current_state_stored = false;
   1517    return 0;
   1518  }
   1519 
   1520  // if the current or next character is NUL, we will finish the line now
   1521  if (line[current_col] == NUL || line[current_col + 1] == NUL) {
   1522    current_finished = true;
   1523    current_state_stored = false;
   1524  }
   1525 
   1526  // When in the previous column there was a match but it could not be used
   1527  // (empty match or already matched in this column) need to try again in
   1528  // the next column.
   1529  if (try_next_column) {
   1530    next_match_idx = -1;
   1531    try_next_column = false;
   1532  }
   1533 
   1534  // Only check for keywords when not syncing and there are some.
   1535  const bool do_keywords = !syncing
   1536                           && (syn_block->b_keywtab.ht_used > 0
   1537                               || syn_block->b_keywtab_ic.ht_used > 0);
   1538 
   1539  // Init the list of zero-width matches with a nextlist.  This is used to
   1540  // avoid matching the same item in the same position twice.
   1541  ga_init(&zero_width_next_ga, (int)sizeof(int), 10);
   1542 
   1543  // use syntax iskeyword option
   1544  save_chartab(buf_chartab);
   1545 
   1546  // Repeat matching keywords and patterns, to find contained items at the
   1547  // same column.  This stops when there are no extra matches at the current
   1548  // column.
   1549  do {
   1550    found_match = false;
   1551    keep_next_list = false;
   1552    int syn_id = 0;
   1553 
   1554    // 1. Check for a current state.
   1555    //    Only when there is no current state, or if the current state may
   1556    //    contain other things, we need to check for keywords and patterns.
   1557    //    Always need to check for contained items if some item has the
   1558    //    "containedin" argument (takes extra time!).
   1559    if (current_state.ga_len) {
   1560      cur_si = &CUR_STATE(current_state.ga_len - 1);
   1561    } else {
   1562      cur_si = NULL;
   1563    }
   1564 
   1565    if (syn_block->b_syn_containedin || cur_si == NULL
   1566        || cur_si->si_cont_list != NULL) {
   1567      // 2. Check for keywords, if on a keyword char after a non-keyword
   1568      //          char.  Don't do this when syncing.
   1569      if (do_keywords) {
   1570        line = syn_getcurline();
   1571        const char *cur_pos = line + current_col;
   1572        if (vim_iswordp_buf(cur_pos, syn_buf)
   1573            && (current_col == 0
   1574                || !vim_iswordp_buf(cur_pos - 1 -
   1575                                    utf_head_off(line, cur_pos - 1),
   1576                                    syn_buf))) {
   1577          syn_id = check_keyword_id(line, (int)current_col, &endcol, &flags,
   1578                                    &next_list, cur_si, &cchar);
   1579          if (syn_id != 0) {
   1580            push_current_state(KEYWORD_IDX);
   1581            {
   1582              cur_si = &CUR_STATE(current_state.ga_len - 1);
   1583              cur_si->si_m_startcol = current_col;
   1584              cur_si->si_h_startpos.lnum = current_lnum;
   1585              cur_si->si_h_startpos.col = 0;            // starts right away
   1586              cur_si->si_m_endpos.lnum = current_lnum;
   1587              cur_si->si_m_endpos.col = endcol;
   1588              cur_si->si_h_endpos.lnum = current_lnum;
   1589              cur_si->si_h_endpos.col = endcol;
   1590              cur_si->si_ends = true;
   1591              cur_si->si_end_idx = 0;
   1592              cur_si->si_flags = flags;
   1593              cur_si->si_seqnr = next_seqnr++;
   1594              cur_si->si_cchar = cchar;
   1595              if (current_state.ga_len > 1) {
   1596                cur_si->si_flags |=
   1597                  CUR_STATE(current_state.ga_len - 2).si_flags
   1598                  & HL_CONCEAL;
   1599              }
   1600              cur_si->si_id = syn_id;
   1601              cur_si->si_trans_id = syn_id;
   1602              if (flags & HL_TRANSP) {
   1603                if (current_state.ga_len < 2) {
   1604                  cur_si->si_attr = 0;
   1605                  cur_si->si_trans_id = 0;
   1606                } else {
   1607                  cur_si->si_attr = CUR_STATE(current_state.ga_len - 2).si_attr;
   1608                  cur_si->si_trans_id = CUR_STATE(current_state.ga_len - 2).si_trans_id;
   1609                }
   1610              } else {
   1611                cur_si->si_attr = syn_id2attr(syn_id);
   1612              }
   1613              cur_si->si_cont_list = NULL;
   1614              cur_si->si_next_list = next_list;
   1615              check_keepend();
   1616            }
   1617          }
   1618        }
   1619      }
   1620 
   1621      // 3. Check for patterns (only if no keyword found).
   1622      if (syn_id == 0 && syn_block->b_syn_patterns.ga_len) {
   1623        // If we didn't check for a match yet, or we are past it, check
   1624        // for any match with a pattern.
   1625        if (next_match_idx < 0 || next_match_col < (int)current_col) {
   1626          // Check all relevant patterns for a match at this
   1627          // position.  This is complicated, because matching with a
   1628          // pattern takes quite a bit of time, thus we want to
   1629          // avoid doing it when it's not needed.
   1630          next_match_idx = 0;                   // no match in this line yet
   1631          next_match_col = MAXCOL;
   1632          for (int idx = syn_block->b_syn_patterns.ga_len; --idx >= 0;) {
   1633            synpat_T *const spp = &(SYN_ITEMS(syn_block)[idx]);
   1634            if (spp->sp_syncing == syncing
   1635                && (displaying || !(spp->sp_flags & HL_DISPLAY))
   1636                && (spp->sp_type == SPTYPE_MATCH
   1637                    || spp->sp_type == SPTYPE_START)
   1638                && (current_next_list != NULL
   1639                    ? in_id_list(NULL, current_next_list, &spp->sp_syn, 0)
   1640                    : (cur_si == NULL
   1641                       ? !(spp->sp_flags & HL_CONTAINED)
   1642                       : in_id_list(cur_si,
   1643                                    cur_si->si_cont_list, &spp->sp_syn,
   1644                                    spp->sp_flags)))) {
   1645              // If we already tried matching in this line, and
   1646              // there isn't a match before next_match_col, skip
   1647              // this item.
   1648              if (spp->sp_line_id == current_line_id
   1649                  && spp->sp_startcol >= next_match_col) {
   1650                continue;
   1651              }
   1652              spp->sp_line_id = current_line_id;
   1653 
   1654              colnr_T lc_col = current_col - spp->sp_offsets[SPO_LC_OFF];
   1655              if (lc_col < 0) {
   1656                lc_col = 0;
   1657              }
   1658 
   1659              regmatch.rmm_ic = spp->sp_ic;
   1660              regmatch.regprog = spp->sp_prog;
   1661              int r = syn_regexec(&regmatch, current_lnum, lc_col,
   1662                                  IF_SYN_TIME(&spp->sp_time));
   1663              spp->sp_prog = regmatch.regprog;
   1664              if (!r) {
   1665                // no match in this line, try another one
   1666                spp->sp_startcol = MAXCOL;
   1667                continue;
   1668              }
   1669 
   1670              // Compute the first column of the match.
   1671              syn_add_start_off(&pos, &regmatch,
   1672                                spp, SPO_MS_OFF, -1);
   1673              if (pos.lnum > current_lnum) {
   1674                // must have used end of match in a next line,
   1675                // we can't handle that
   1676                spp->sp_startcol = MAXCOL;
   1677                continue;
   1678              }
   1679              startcol = pos.col;
   1680 
   1681              // remember the next column where this pattern
   1682              // matches in the current line
   1683              spp->sp_startcol = startcol;
   1684 
   1685              // If a previously found match starts at a lower
   1686              // column number, don't use this one.
   1687              if (startcol >= next_match_col) {
   1688                continue;
   1689              }
   1690 
   1691              // If we matched this pattern at this position
   1692              // before, skip it.  Must retry in the next
   1693              // column, because it may match from there.
   1694              if (did_match_already(idx, &zero_width_next_ga)) {
   1695                try_next_column = true;
   1696                continue;
   1697              }
   1698 
   1699              endpos.lnum = regmatch.endpos[0].lnum;
   1700              endpos.col = regmatch.endpos[0].col;
   1701 
   1702              // Compute the highlight start.
   1703              syn_add_start_off(&hl_startpos, &regmatch,
   1704                                spp, SPO_HS_OFF, -1);
   1705 
   1706              // Compute the region start.
   1707              // Default is to use the end of the match.
   1708              syn_add_end_off(&eos_pos, &regmatch,
   1709                              spp, SPO_RS_OFF, 0);
   1710 
   1711              // Grab the external submatches before they get
   1712              // overwritten.  Reference count doesn't change.
   1713              unref_extmatch(cur_extmatch);
   1714              cur_extmatch = re_extmatch_out;
   1715              re_extmatch_out = NULL;
   1716 
   1717              flags = 0;
   1718              eoe_pos.lnum = 0;                 // avoid warning
   1719              eoe_pos.col = 0;
   1720              end_idx = 0;
   1721              hl_endpos.lnum = 0;
   1722 
   1723              // For a "oneline" the end must be found in the
   1724              // same line too.  Search for it after the end of
   1725              // the match with the start pattern.  Set the
   1726              // resulting end positions at the same time.
   1727              if (spp->sp_type == SPTYPE_START
   1728                  && (spp->sp_flags & HL_ONELINE)) {
   1729                lpos_T startpos;
   1730 
   1731                startpos = endpos;
   1732                find_endpos(idx, &startpos, &endpos, &hl_endpos,
   1733                            &flags, &eoe_pos, &end_idx, cur_extmatch);
   1734                if (endpos.lnum == 0) {
   1735                  continue;                         // not found
   1736                }
   1737              } else if (spp->sp_type == SPTYPE_MATCH) {
   1738                // For a "match" the size must be > 0 after the
   1739                // end offset needs has been added.  Except when
   1740                // syncing.
   1741                syn_add_end_off(&hl_endpos, &regmatch, spp,
   1742                                SPO_HE_OFF, 0);
   1743                syn_add_end_off(&endpos, &regmatch, spp,
   1744                                SPO_ME_OFF, 0);
   1745                if (endpos.lnum == current_lnum
   1746                    && (int)endpos.col + syncing < startcol) {
   1747                  // If an empty string is matched, may need
   1748                  // to try matching again at next column.
   1749                  if (regmatch.startpos[0].col == regmatch.endpos[0].col) {
   1750                    try_next_column = true;
   1751                  }
   1752                  continue;
   1753                }
   1754              }
   1755 
   1756              // keep the best match so far in next_match_*
   1757 
   1758              // Highlighting must start after startpos and end
   1759              // before endpos.
   1760              if (hl_startpos.lnum == current_lnum
   1761                  && (int)hl_startpos.col < startcol) {
   1762                hl_startpos.col = startcol;
   1763              }
   1764              limit_pos_zero(&hl_endpos, &endpos);
   1765 
   1766              next_match_idx = idx;
   1767              next_match_col = startcol;
   1768              next_match_m_endpos = endpos;
   1769              next_match_h_endpos = hl_endpos;
   1770              next_match_h_startpos = hl_startpos;
   1771              next_match_flags = flags;
   1772              next_match_eos_pos = eos_pos;
   1773              next_match_eoe_pos = eoe_pos;
   1774              next_match_end_idx = end_idx;
   1775              unref_extmatch(next_match_extmatch);
   1776              next_match_extmatch = cur_extmatch;
   1777              cur_extmatch = NULL;
   1778            }
   1779          }
   1780        }
   1781 
   1782        // If we found a match at the current column, use it.
   1783        if (next_match_idx >= 0 && next_match_col == (int)current_col) {
   1784          synpat_T *lspp;
   1785 
   1786          // When a zero-width item matched which has a nextgroup,
   1787          // don't push the item but set nextgroup.
   1788          lspp = &(SYN_ITEMS(syn_block)[next_match_idx]);
   1789          if (next_match_m_endpos.lnum == current_lnum
   1790              && next_match_m_endpos.col == current_col
   1791              && lspp->sp_next_list != NULL) {
   1792            current_next_list = lspp->sp_next_list;
   1793            current_next_flags = lspp->sp_flags;
   1794            keep_next_list = true;
   1795            zero_width_next_list = true;
   1796 
   1797            // Add the index to a list, so that we can check
   1798            // later that we don't match it again (and cause an
   1799            // endless loop).
   1800            GA_APPEND(int, &zero_width_next_ga, next_match_idx);
   1801            next_match_idx = -1;
   1802          } else {
   1803            cur_si = push_next_match();
   1804          }
   1805          found_match = true;
   1806        }
   1807      }
   1808    }
   1809 
   1810    // Handle searching for nextgroup match.
   1811    if (current_next_list != NULL && !keep_next_list) {
   1812      // If a nextgroup was not found, continue looking for one if:
   1813      // - this is an empty line and the "skipempty" option was given
   1814      // - we are on white space and the "skipwhite" option was given
   1815      if (!found_match) {
   1816        line = syn_getcurline();
   1817        if (((current_next_flags & HL_SKIPWHITE)
   1818             && ascii_iswhite(line[current_col]))
   1819            || ((current_next_flags & HL_SKIPEMPTY)
   1820                && *line == NUL)) {
   1821          break;
   1822        }
   1823      }
   1824 
   1825      // If a nextgroup was found: Use it, and continue looking for
   1826      // contained matches.
   1827      // If a nextgroup was not found: Continue looking for a normal
   1828      // match.
   1829      // When did set current_next_list for a zero-width item and no
   1830      // match was found don't loop (would get stuck).
   1831      current_next_list = NULL;
   1832      next_match_idx = -1;
   1833      if (!zero_width_next_list) {
   1834        found_match = true;
   1835      }
   1836    }
   1837  } while (found_match);
   1838 
   1839  restore_chartab(buf_chartab);
   1840 
   1841  // Use attributes from the current state, if within its highlighting.
   1842  // If not, use attributes from the current-but-one state, etc.
   1843  current_attr = 0;
   1844  current_id = 0;
   1845  current_trans_id = 0;
   1846  current_flags = 0;
   1847  current_seqnr = 0;
   1848  if (cur_si != NULL) {
   1849    for (int idx = current_state.ga_len - 1; idx >= 0; idx--) {
   1850      sip = &CUR_STATE(idx);
   1851      if ((current_lnum > sip->si_h_startpos.lnum
   1852           || (current_lnum == sip->si_h_startpos.lnum
   1853               && current_col >= sip->si_h_startpos.col))
   1854          && (sip->si_h_endpos.lnum == 0
   1855              || current_lnum < sip->si_h_endpos.lnum
   1856              || (current_lnum == sip->si_h_endpos.lnum
   1857                  && current_col < sip->si_h_endpos.col))) {
   1858        current_attr = sip->si_attr;
   1859        current_id = sip->si_id;
   1860        current_trans_id = sip->si_trans_id;
   1861        current_flags = sip->si_flags;
   1862        current_seqnr = sip->si_seqnr;
   1863        current_sub_char = sip->si_cchar;
   1864        break;
   1865      }
   1866    }
   1867 
   1868    if (can_spell != NULL) {
   1869      struct sp_syn sps;
   1870 
   1871      // set "can_spell" to true if spell checking is supposed to be
   1872      // done in the current item.
   1873      if (syn_block->b_spell_cluster_id == 0) {
   1874        // There is no @Spell cluster: Do spelling for items without
   1875        // @NoSpell cluster.
   1876        if (syn_block->b_nospell_cluster_id == 0
   1877            || current_trans_id == 0) {
   1878          *can_spell = (syn_block->b_syn_spell != SYNSPL_NOTOP);
   1879        } else {
   1880          sps.inc_tag = 0;
   1881          sps.id = (int16_t)syn_block->b_nospell_cluster_id;
   1882          sps.cont_in_list = NULL;
   1883          *can_spell = !in_id_list(sip, sip->si_cont_list, &sps, 0);
   1884        }
   1885      } else {
   1886        // The @Spell cluster is defined: Do spelling in items with
   1887        // the @Spell cluster.  But not when @NoSpell is also there.
   1888        // At the toplevel only spell check when ":syn spell toplevel"
   1889        // was used.
   1890        if (current_trans_id == 0) {
   1891          *can_spell = (syn_block->b_syn_spell == SYNSPL_TOP);
   1892        } else {
   1893          sps.inc_tag = 0;
   1894          sps.id = (int16_t)syn_block->b_spell_cluster_id;
   1895          sps.cont_in_list = NULL;
   1896          *can_spell = in_id_list(sip, sip->si_cont_list, &sps, 0);
   1897 
   1898          if (syn_block->b_nospell_cluster_id != 0) {
   1899            sps.id = (int16_t)syn_block->b_nospell_cluster_id;
   1900            if (in_id_list(sip, sip->si_cont_list, &sps, 0)) {
   1901              *can_spell = false;
   1902            }
   1903          }
   1904        }
   1905      }
   1906    }
   1907 
   1908    // Check for end of current state (and the states before it) at the
   1909    // next column.  Don't do this for syncing, because we would miss a
   1910    // single character match.
   1911    // First check if the current state ends at the current column.  It
   1912    // may be for an empty match and a containing item might end in the
   1913    // current column.
   1914    if (!syncing && !keep_state) {
   1915      check_state_ends();
   1916      if (!GA_EMPTY(&current_state)
   1917          && syn_getcurline()[current_col] != NUL) {
   1918        current_col++;
   1919        check_state_ends();
   1920        current_col--;
   1921      }
   1922    }
   1923  } else if (can_spell != NULL) {
   1924    // Default: Only do spelling when there is no @Spell cluster or when
   1925    // ":syn spell toplevel" was used.
   1926    *can_spell = syn_block->b_syn_spell == SYNSPL_DEFAULT
   1927                 ? (syn_block->b_spell_cluster_id == 0)
   1928                 : (syn_block->b_syn_spell == SYNSPL_TOP);
   1929  }
   1930 
   1931  // nextgroup ends at end of line, unless "skipnl" or "skipempty" present
   1932  if (current_next_list != NULL
   1933      && (line = syn_getcurline())[current_col] != NUL
   1934      && line[current_col + 1] == NUL
   1935      && !(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY))) {
   1936    current_next_list = NULL;
   1937  }
   1938 
   1939  if (!GA_EMPTY(&zero_width_next_ga)) {
   1940    ga_clear(&zero_width_next_ga);
   1941  }
   1942 
   1943  // No longer need external matches.  But keep next_match_extmatch.
   1944  unref_extmatch(re_extmatch_out);
   1945  re_extmatch_out = NULL;
   1946  unref_extmatch(cur_extmatch);
   1947 
   1948  return current_attr;
   1949 }
   1950 
   1951 /// @return  true if we already matched pattern "idx" at the current column.
   1952 static bool did_match_already(int idx, garray_T *gap)
   1953 {
   1954  for (int i = current_state.ga_len; --i >= 0;) {
   1955    if (CUR_STATE(i).si_m_startcol == (int)current_col
   1956        && CUR_STATE(i).si_m_lnum == (int)current_lnum
   1957        && CUR_STATE(i).si_idx == idx) {
   1958      return true;
   1959    }
   1960  }
   1961 
   1962  // Zero-width matches with a nextgroup argument are not put on the syntax
   1963  // stack, and can only be matched once anyway.
   1964  for (int i = gap->ga_len; --i >= 0;) {
   1965    if (((int *)(gap->ga_data))[i] == idx) {
   1966      return true;
   1967    }
   1968  }
   1969 
   1970  return false;
   1971 }
   1972 
   1973 // Push the next match onto the stack.
   1974 static stateitem_T *push_next_match(void)
   1975 {
   1976  stateitem_T *cur_si;
   1977  synpat_T *spp;
   1978  int save_flags;
   1979 
   1980  spp = &(SYN_ITEMS(syn_block)[next_match_idx]);
   1981 
   1982  // Push the item in current_state stack;
   1983  push_current_state(next_match_idx);
   1984  {
   1985    // If it's a start-skip-end type that crosses lines, figure out how
   1986    // much it continues in this line.  Otherwise just fill in the length.
   1987    cur_si = &CUR_STATE(current_state.ga_len - 1);
   1988    cur_si->si_h_startpos = next_match_h_startpos;
   1989    cur_si->si_m_startcol = current_col;
   1990    cur_si->si_m_lnum = current_lnum;
   1991    cur_si->si_flags = spp->sp_flags;
   1992    cur_si->si_seqnr = next_seqnr++;
   1993    cur_si->si_cchar = spp->sp_cchar;
   1994    if (current_state.ga_len > 1) {
   1995      cur_si->si_flags |=
   1996        CUR_STATE(current_state.ga_len - 2).si_flags & HL_CONCEAL;
   1997    }
   1998    cur_si->si_next_list = spp->sp_next_list;
   1999    cur_si->si_extmatch = ref_extmatch(next_match_extmatch);
   2000    if (spp->sp_type == SPTYPE_START && !(spp->sp_flags & HL_ONELINE)) {
   2001      // Try to find the end pattern in the current line
   2002      update_si_end(cur_si, (int)(next_match_m_endpos.col), true);
   2003      check_keepend();
   2004    } else {
   2005      cur_si->si_m_endpos = next_match_m_endpos;
   2006      cur_si->si_h_endpos = next_match_h_endpos;
   2007      cur_si->si_ends = true;
   2008      cur_si->si_flags |= next_match_flags;
   2009      cur_si->si_eoe_pos = next_match_eoe_pos;
   2010      cur_si->si_end_idx = next_match_end_idx;
   2011    }
   2012    if (keepend_level < 0 && (cur_si->si_flags & HL_KEEPEND)) {
   2013      keepend_level = current_state.ga_len - 1;
   2014    }
   2015    check_keepend();
   2016    update_si_attr(current_state.ga_len - 1);
   2017 
   2018    save_flags = cur_si->si_flags & (HL_CONCEAL | HL_CONCEALENDS);
   2019    // If the start pattern has another highlight group, push another item
   2020    // on the stack for the start pattern.
   2021    if (spp->sp_type == SPTYPE_START && spp->sp_syn_match_id != 0) {
   2022      push_current_state(next_match_idx);
   2023      cur_si = &CUR_STATE(current_state.ga_len - 1);
   2024      cur_si->si_h_startpos = next_match_h_startpos;
   2025      cur_si->si_m_startcol = current_col;
   2026      cur_si->si_m_lnum = current_lnum;
   2027      cur_si->si_m_endpos = next_match_eos_pos;
   2028      cur_si->si_h_endpos = next_match_eos_pos;
   2029      cur_si->si_ends = true;
   2030      cur_si->si_end_idx = 0;
   2031      cur_si->si_flags = HL_MATCH;
   2032      cur_si->si_seqnr = next_seqnr++;
   2033      cur_si->si_flags |= save_flags;
   2034      if (cur_si->si_flags & HL_CONCEALENDS) {
   2035        cur_si->si_flags |= HL_CONCEAL;
   2036      }
   2037      cur_si->si_next_list = NULL;
   2038      check_keepend();
   2039      update_si_attr(current_state.ga_len - 1);
   2040    }
   2041  }
   2042 
   2043  next_match_idx = -1;          // try other match next time
   2044 
   2045  return cur_si;
   2046 }
   2047 
   2048 // Check for end of current state (and the states before it).
   2049 static void check_state_ends(void)
   2050 {
   2051  stateitem_T *cur_si;
   2052  int had_extend;
   2053 
   2054  cur_si = &CUR_STATE(current_state.ga_len - 1);
   2055  while (true) {
   2056    if (cur_si->si_ends
   2057        && (cur_si->si_m_endpos.lnum < current_lnum
   2058            || (cur_si->si_m_endpos.lnum == current_lnum
   2059                && cur_si->si_m_endpos.col <= current_col))) {
   2060      // If there is an end pattern group ID, highlight the end pattern
   2061      // now.  No need to pop the current item from the stack.
   2062      // Only do this if the end pattern continues beyond the current
   2063      // position.
   2064      if (cur_si->si_end_idx
   2065          && (cur_si->si_eoe_pos.lnum > current_lnum
   2066              || (cur_si->si_eoe_pos.lnum == current_lnum
   2067                  && cur_si->si_eoe_pos.col > current_col))) {
   2068        cur_si->si_idx = cur_si->si_end_idx;
   2069        cur_si->si_end_idx = 0;
   2070        cur_si->si_m_endpos = cur_si->si_eoe_pos;
   2071        cur_si->si_h_endpos = cur_si->si_eoe_pos;
   2072        cur_si->si_flags |= HL_MATCH;
   2073        cur_si->si_seqnr = next_seqnr++;
   2074        if (cur_si->si_flags & HL_CONCEALENDS) {
   2075          cur_si->si_flags |= HL_CONCEAL;
   2076        }
   2077        update_si_attr(current_state.ga_len - 1);
   2078 
   2079        // nextgroup= should not match in the end pattern
   2080        current_next_list = NULL;
   2081 
   2082        // what matches next may be different now, clear it
   2083        next_match_idx = 0;
   2084        next_match_col = MAXCOL;
   2085        break;
   2086      }
   2087 
   2088      // handle next_list, unless at end of line and no "skipnl" or
   2089      // "skipempty"
   2090      current_next_list = cur_si->si_next_list;
   2091      current_next_flags = cur_si->si_flags;
   2092      if (!(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY))
   2093          && syn_getcurline()[current_col] == NUL) {
   2094        current_next_list = NULL;
   2095      }
   2096 
   2097      // When the ended item has "extend", another item with
   2098      // "keepend" now needs to check for its end.
   2099      had_extend = (cur_si->si_flags & HL_EXTEND);
   2100 
   2101      pop_current_state();
   2102 
   2103      if (GA_EMPTY(&current_state)) {
   2104        break;
   2105      }
   2106 
   2107      if (had_extend && keepend_level >= 0) {
   2108        syn_update_ends(false);
   2109        if (GA_EMPTY(&current_state)) {
   2110          break;
   2111        }
   2112      }
   2113 
   2114      cur_si = &CUR_STATE(current_state.ga_len - 1);
   2115 
   2116      // Only for a region the search for the end continues after
   2117      // the end of the contained item.  If the contained match
   2118      // included the end-of-line, break here, the region continues.
   2119      // Don't do this when:
   2120      // - "keepend" is used for the contained item
   2121      // - not at the end of the line (could be end="x$"me=e-1).
   2122      // - "excludenl" is used (HL_HAS_EOL won't be set)
   2123      if (cur_si->si_idx >= 0
   2124          && SYN_ITEMS(syn_block)[cur_si->si_idx].sp_type == SPTYPE_START
   2125          && !(cur_si->si_flags & (HL_MATCH | HL_KEEPEND))) {
   2126        update_si_end(cur_si, (int)current_col, true);
   2127        check_keepend();
   2128        if ((current_next_flags & HL_HAS_EOL)
   2129            && keepend_level < 0
   2130            && syn_getcurline()[current_col] == NUL) {
   2131          break;
   2132        }
   2133      }
   2134    } else {
   2135      break;
   2136    }
   2137  }
   2138 }
   2139 
   2140 // Update an entry in the current_state stack for a match or region.  This
   2141 // fills in si_attr, si_next_list and si_cont_list.
   2142 static void update_si_attr(int idx)
   2143 {
   2144  stateitem_T *sip = &CUR_STATE(idx);
   2145  synpat_T *spp;
   2146 
   2147  // This should not happen...
   2148  if (sip->si_idx < 0) {
   2149    return;
   2150  }
   2151 
   2152  spp = &(SYN_ITEMS(syn_block)[sip->si_idx]);
   2153  if (sip->si_flags & HL_MATCH) {
   2154    sip->si_id = spp->sp_syn_match_id;
   2155  } else {
   2156    sip->si_id = spp->sp_syn.id;
   2157  }
   2158  sip->si_attr = syn_id2attr(sip->si_id);
   2159  sip->si_trans_id = sip->si_id;
   2160  if (sip->si_flags & HL_MATCH) {
   2161    sip->si_cont_list = NULL;
   2162  } else {
   2163    sip->si_cont_list = spp->sp_cont_list;
   2164  }
   2165 
   2166  // For transparent items, take attr from outer item.
   2167  // Also take cont_list, if there is none.
   2168  // Don't do this for the matchgroup of a start or end pattern.
   2169  if ((spp->sp_flags & HL_TRANSP) && !(sip->si_flags & HL_MATCH)) {
   2170    if (idx == 0) {
   2171      sip->si_attr = 0;
   2172      sip->si_trans_id = 0;
   2173      if (sip->si_cont_list == NULL) {
   2174        sip->si_cont_list = ID_LIST_ALL;
   2175      }
   2176    } else {
   2177      sip->si_attr = CUR_STATE(idx - 1).si_attr;
   2178      sip->si_trans_id = CUR_STATE(idx - 1).si_trans_id;
   2179      if (sip->si_cont_list == NULL) {
   2180        sip->si_flags |= HL_TRANS_CONT;
   2181        sip->si_cont_list = CUR_STATE(idx - 1).si_cont_list;
   2182      }
   2183    }
   2184  }
   2185 }
   2186 
   2187 // Check the current stack for patterns with "keepend" flag.
   2188 // Propagate the match-end to contained items, until a "skipend" item is found.
   2189 static void check_keepend(void)
   2190 {
   2191  int i;
   2192  lpos_T maxpos;
   2193  lpos_T maxpos_h;
   2194  stateitem_T *sip;
   2195 
   2196  // This check can consume a lot of time; only do it from the level where
   2197  // there really is a keepend.
   2198  if (keepend_level < 0) {
   2199    return;
   2200  }
   2201 
   2202  // Find the last index of an "extend" item.  "keepend" items before that
   2203  // won't do anything.  If there is no "extend" item "i" will be
   2204  // "keepend_level" and all "keepend" items will work normally.
   2205  for (i = current_state.ga_len - 1; i > keepend_level; i--) {
   2206    if (CUR_STATE(i).si_flags & HL_EXTEND) {
   2207      break;
   2208    }
   2209  }
   2210 
   2211  maxpos.lnum = 0;
   2212  maxpos.col = 0;
   2213  maxpos_h.lnum = 0;
   2214  maxpos_h.col = 0;
   2215  for (; i < current_state.ga_len; i++) {
   2216    sip = &CUR_STATE(i);
   2217    if (maxpos.lnum != 0) {
   2218      limit_pos_zero(&sip->si_m_endpos, &maxpos);
   2219      limit_pos_zero(&sip->si_h_endpos, &maxpos_h);
   2220      limit_pos_zero(&sip->si_eoe_pos, &maxpos);
   2221      sip->si_ends = true;
   2222    }
   2223    if (sip->si_ends && (sip->si_flags & HL_KEEPEND)) {
   2224      if (maxpos.lnum == 0
   2225          || maxpos.lnum > sip->si_m_endpos.lnum
   2226          || (maxpos.lnum == sip->si_m_endpos.lnum
   2227              && maxpos.col > sip->si_m_endpos.col)) {
   2228        maxpos = sip->si_m_endpos;
   2229      }
   2230      if (maxpos_h.lnum == 0
   2231          || maxpos_h.lnum > sip->si_h_endpos.lnum
   2232          || (maxpos_h.lnum == sip->si_h_endpos.lnum
   2233              && maxpos_h.col > sip->si_h_endpos.col)) {
   2234        maxpos_h = sip->si_h_endpos;
   2235      }
   2236    }
   2237  }
   2238 }
   2239 
   2240 /// Update an entry in the current_state stack for a start-skip-end pattern.
   2241 /// This finds the end of the current item, if it's in the current line.
   2242 ///
   2243 /// @param startcol  where to start searching for the end
   2244 /// @param force     when true overrule a previous end
   2245 ///
   2246 /// @return          the flags for the matched END.
   2247 static void update_si_end(stateitem_T *sip, int startcol, bool force)
   2248 {
   2249  lpos_T hl_endpos;
   2250  lpos_T end_endpos;
   2251 
   2252  // return quickly for a keyword
   2253  if (sip->si_idx < 0) {
   2254    return;
   2255  }
   2256 
   2257  // Don't update when it's already done.  Can be a match of an end pattern
   2258  // that started in a previous line.  Watch out: can also be a "keepend"
   2259  // from a containing item.
   2260  if (!force && sip->si_m_endpos.lnum >= current_lnum) {
   2261    return;
   2262  }
   2263 
   2264  // We need to find the end of the region.  It may continue in the next
   2265  // line.
   2266  int end_idx = 0;
   2267  lpos_T startpos = {
   2268    .lnum = current_lnum,
   2269    .col = startcol,
   2270  };
   2271  lpos_T endpos = { 0 };
   2272  find_endpos(sip->si_idx, &startpos, &endpos, &hl_endpos,
   2273              &(sip->si_flags), &end_endpos, &end_idx, sip->si_extmatch);
   2274 
   2275  if (endpos.lnum == 0) {
   2276    // No end pattern matched.
   2277    if (SYN_ITEMS(syn_block)[sip->si_idx].sp_flags & HL_ONELINE) {
   2278      // a "oneline" never continues in the next line
   2279      sip->si_ends = true;
   2280      sip->si_m_endpos.lnum = current_lnum;
   2281      sip->si_m_endpos.col = syn_getcurline_len();
   2282    } else {
   2283      // continues in the next line
   2284      sip->si_ends = false;
   2285      sip->si_m_endpos.lnum = 0;
   2286    }
   2287    sip->si_h_endpos = sip->si_m_endpos;
   2288  } else {
   2289    // match within this line
   2290    sip->si_m_endpos = endpos;
   2291    sip->si_h_endpos = hl_endpos;
   2292    sip->si_eoe_pos = end_endpos;
   2293    sip->si_ends = true;
   2294    sip->si_end_idx = end_idx;
   2295  }
   2296 }
   2297 
   2298 // Add a new state to the current state stack.
   2299 // It is cleared and the index set to "idx".
   2300 static void push_current_state(int idx)
   2301 {
   2302  stateitem_T *p = GA_APPEND_VIA_PTR(stateitem_T, &current_state);
   2303  CLEAR_POINTER(p);
   2304  p->si_idx = idx;
   2305 }
   2306 
   2307 // Remove a state from the current_state stack.
   2308 static void pop_current_state(void)
   2309 {
   2310  if (!GA_EMPTY(&current_state)) {
   2311    unref_extmatch(CUR_STATE(current_state.ga_len - 1).si_extmatch);
   2312    current_state.ga_len--;
   2313  }
   2314  // after the end of a pattern, try matching a keyword or pattern
   2315  next_match_idx = -1;
   2316 
   2317  // if first state with "keepend" is popped, reset keepend_level
   2318  if (keepend_level >= current_state.ga_len) {
   2319    keepend_level = -1;
   2320  }
   2321 }
   2322 
   2323 /// Find the end of a start/skip/end syntax region after "startpos".
   2324 /// Only checks one line.
   2325 /// Also handles a match item that continued from a previous line.
   2326 /// If not found, the syntax item continues in the next line.  m_endpos->lnum
   2327 /// will be 0.
   2328 /// If found, the end of the region and the end of the highlighting is
   2329 /// computed.
   2330 ///
   2331 /// @param idx         index of the pattern
   2332 /// @param startpos    where to start looking for an END match
   2333 /// @param m_endpos    return: end of match
   2334 /// @param hl_endpos   return: end of highlighting
   2335 /// @param flagsp      return: flags of matching END
   2336 /// @param end_endpos  return: end of end pattern match
   2337 /// @param end_idx     return: group ID for end pat. match, or 0
   2338 /// @param start_ext   submatches from the start pattern
   2339 static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_endpos, int *flagsp,
   2340                        lpos_T *end_endpos, int *end_idx, reg_extmatch_T *start_ext)
   2341 {
   2342  synpat_T *spp_skip;
   2343  int best_idx;
   2344  regmmatch_T regmatch;
   2345  regmmatch_T best_regmatch;        // startpos/endpos of best match
   2346  lpos_T pos;
   2347  bool had_match = false;
   2348  char buf_chartab[32];  // chartab array for syn option iskeyword
   2349 
   2350  // just in case we are invoked for a keyword
   2351  if (idx < 0) {
   2352    return;
   2353  }
   2354 
   2355  // Check for being called with a START pattern.
   2356  // Can happen with a match that continues to the next line, because it
   2357  // contained a region.
   2358  synpat_T *spp = &(SYN_ITEMS(syn_block)[idx]);
   2359  if (spp->sp_type != SPTYPE_START) {
   2360    *hl_endpos = *startpos;
   2361    return;
   2362  }
   2363 
   2364  // Find the SKIP or first END pattern after the last START pattern.
   2365  while (true) {
   2366    spp = &(SYN_ITEMS(syn_block)[idx]);
   2367    if (spp->sp_type != SPTYPE_START) {
   2368      break;
   2369    }
   2370    idx++;
   2371  }
   2372 
   2373  //    Lookup the SKIP pattern (if present)
   2374  if (spp->sp_type == SPTYPE_SKIP) {
   2375    spp_skip = spp;
   2376    idx++;
   2377  } else {
   2378    spp_skip = NULL;
   2379  }
   2380 
   2381  // Setup external matches for syn_regexec().
   2382  unref_extmatch(re_extmatch_in);
   2383  re_extmatch_in = ref_extmatch(start_ext);
   2384 
   2385  colnr_T matchcol = startpos->col;     // start looking for a match at sstart
   2386  int start_idx = idx;              // remember the first END pattern.
   2387  best_regmatch.startpos[0].col = 0;            // avoid compiler warning
   2388 
   2389  // use syntax iskeyword option
   2390  save_chartab(buf_chartab);
   2391 
   2392  while (true) {
   2393    // Find end pattern that matches first after "matchcol".
   2394    best_idx = -1;
   2395    for (idx = start_idx; idx < syn_block->b_syn_patterns.ga_len; idx++) {
   2396      int lc_col = matchcol;
   2397 
   2398      spp = &(SYN_ITEMS(syn_block)[idx]);
   2399      if (spp->sp_type != SPTYPE_END) {         // past last END pattern
   2400        break;
   2401      }
   2402      lc_col -= spp->sp_offsets[SPO_LC_OFF];
   2403      if (lc_col < 0) {
   2404        lc_col = 0;
   2405      }
   2406 
   2407      regmatch.rmm_ic = spp->sp_ic;
   2408      regmatch.regprog = spp->sp_prog;
   2409      bool r = syn_regexec(&regmatch, startpos->lnum, lc_col,
   2410                           IF_SYN_TIME(&spp->sp_time));
   2411      spp->sp_prog = regmatch.regprog;
   2412      if (r) {
   2413        if (best_idx == -1 || regmatch.startpos[0].col
   2414            < best_regmatch.startpos[0].col) {
   2415          best_idx = idx;
   2416          best_regmatch.startpos[0] = regmatch.startpos[0];
   2417          best_regmatch.endpos[0] = regmatch.endpos[0];
   2418        }
   2419      }
   2420    }
   2421 
   2422    // If all end patterns have been tried, and there is no match, the
   2423    // item continues until end-of-line.
   2424    if (best_idx == -1) {
   2425      break;
   2426    }
   2427 
   2428    // If the skip pattern matches before the end pattern,
   2429    // continue searching after the skip pattern.
   2430    if (spp_skip != NULL) {
   2431      int lc_col = matchcol - spp_skip->sp_offsets[SPO_LC_OFF];
   2432 
   2433      if (lc_col < 0) {
   2434        lc_col = 0;
   2435      }
   2436      regmatch.rmm_ic = spp_skip->sp_ic;
   2437      regmatch.regprog = spp_skip->sp_prog;
   2438      int r = syn_regexec(&regmatch, startpos->lnum, lc_col,
   2439                          IF_SYN_TIME(&spp_skip->sp_time));
   2440      spp_skip->sp_prog = regmatch.regprog;
   2441      if (r && regmatch.startpos[0].col <= best_regmatch.startpos[0].col) {
   2442        // Add offset to skip pattern match
   2443        syn_add_end_off(&pos, &regmatch, spp_skip, SPO_ME_OFF, 1);
   2444 
   2445        // If the skip pattern goes on to the next line, there is no
   2446        // match with an end pattern in this line.
   2447        if (pos.lnum > startpos->lnum) {
   2448          break;
   2449        }
   2450 
   2451        int line_len = ml_get_buf_len(syn_buf, startpos->lnum);
   2452 
   2453        // take care of an empty match or negative offset
   2454        if (pos.col <= matchcol) {
   2455          matchcol++;
   2456        } else if (pos.col <= regmatch.endpos[0].col) {
   2457          matchcol = pos.col;
   2458        } else {
   2459          // Be careful not to jump over the NUL at the end-of-line
   2460          for (matchcol = regmatch.endpos[0].col;
   2461               matchcol < line_len && matchcol < pos.col;
   2462               matchcol++) {}
   2463        }
   2464 
   2465        // if the skip pattern includes end-of-line, break here
   2466        if (matchcol >= line_len) {
   2467          break;
   2468        }
   2469 
   2470        continue;  // start with first end pattern again
   2471      }
   2472    }
   2473 
   2474    // Match from start pattern to end pattern.
   2475    // Correct for match and highlight offset of end pattern.
   2476    spp = &(SYN_ITEMS(syn_block)[best_idx]);
   2477    syn_add_end_off(m_endpos, &best_regmatch, spp, SPO_ME_OFF, 1);
   2478    // can't end before the start
   2479    if (m_endpos->lnum == startpos->lnum && m_endpos->col < startpos->col) {
   2480      m_endpos->col = startpos->col;
   2481    }
   2482 
   2483    syn_add_end_off(end_endpos, &best_regmatch, spp, SPO_HE_OFF, 1);
   2484    // can't end before the start
   2485    if (end_endpos->lnum == startpos->lnum
   2486        && end_endpos->col < startpos->col) {
   2487      end_endpos->col = startpos->col;
   2488    }
   2489    // can't end after the match
   2490    limit_pos(end_endpos, m_endpos);
   2491 
   2492    // If the end group is highlighted differently, adjust the pointers.
   2493    if (spp->sp_syn_match_id != spp->sp_syn.id && spp->sp_syn_match_id != 0) {
   2494      *end_idx = best_idx;
   2495      if (spp->sp_off_flags & (1 << (SPO_RE_OFF + SPO_COUNT))) {
   2496        hl_endpos->lnum = best_regmatch.endpos[0].lnum;
   2497        hl_endpos->col = best_regmatch.endpos[0].col;
   2498      } else {
   2499        hl_endpos->lnum = best_regmatch.startpos[0].lnum;
   2500        hl_endpos->col = best_regmatch.startpos[0].col;
   2501      }
   2502      hl_endpos->col += spp->sp_offsets[SPO_RE_OFF];
   2503 
   2504      // can't end before the start
   2505      if (hl_endpos->lnum == startpos->lnum
   2506          && hl_endpos->col < startpos->col) {
   2507        hl_endpos->col = startpos->col;
   2508      }
   2509      limit_pos(hl_endpos, m_endpos);
   2510 
   2511      // now the match ends where the highlighting ends, it is turned
   2512      // into the matchgroup for the end
   2513      *m_endpos = *hl_endpos;
   2514    } else {
   2515      *end_idx = 0;
   2516      *hl_endpos = *end_endpos;
   2517    }
   2518 
   2519    *flagsp = spp->sp_flags;
   2520 
   2521    had_match = true;
   2522    break;
   2523  }
   2524 
   2525  // no match for an END pattern in this line
   2526  if (!had_match) {
   2527    m_endpos->lnum = 0;
   2528  }
   2529 
   2530  restore_chartab(buf_chartab);
   2531 
   2532  // Remove external matches.
   2533  unref_extmatch(re_extmatch_in);
   2534  re_extmatch_in = NULL;
   2535 }
   2536 
   2537 // Limit "pos" not to be after "limit".
   2538 static void limit_pos(lpos_T *pos, lpos_T *limit)
   2539 {
   2540  if (pos->lnum > limit->lnum) {
   2541    *pos = *limit;
   2542  } else if (pos->lnum == limit->lnum && pos->col > limit->col) {
   2543    pos->col = limit->col;
   2544  }
   2545 }
   2546 
   2547 // Limit "pos" not to be after "limit", unless pos->lnum is zero.
   2548 static void limit_pos_zero(lpos_T *pos, lpos_T *limit)
   2549 {
   2550  if (pos->lnum == 0) {
   2551    *pos = *limit;
   2552  } else {
   2553    limit_pos(pos, limit);
   2554  }
   2555 }
   2556 
   2557 /// Add offset to matched text for end of match or highlight.
   2558 ///
   2559 /// @param result    returned position
   2560 /// @param regmatch  start/end of match
   2561 /// @param spp       matched pattern
   2562 /// @param idx       index of offset
   2563 /// @param extra     extra chars for offset to start
   2564 static void syn_add_end_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *spp, int idx,
   2565                            int extra)
   2566 {
   2567  int col;
   2568  int off;
   2569  char *base;
   2570  char *p;
   2571 
   2572  if (spp->sp_off_flags & (1 << idx)) {
   2573    result->lnum = regmatch->startpos[0].lnum;
   2574    col = regmatch->startpos[0].col;
   2575    off = spp->sp_offsets[idx] + extra;
   2576  } else {
   2577    result->lnum = regmatch->endpos[0].lnum;
   2578    col = regmatch->endpos[0].col;
   2579    off = spp->sp_offsets[idx];
   2580  }
   2581  // Don't go past the end of the line.  Matters for "rs=e+2" when there
   2582  // is a matchgroup. Watch out for match with last NL in the buffer.
   2583  if (result->lnum > syn_buf->b_ml.ml_line_count) {
   2584    col = 0;
   2585  } else if (off != 0) {
   2586    base = ml_get_buf(syn_buf, result->lnum);
   2587    p = base + col;
   2588    if (off > 0) {
   2589      while (off-- > 0 && *p != NUL) {
   2590        MB_PTR_ADV(p);
   2591      }
   2592    } else {
   2593      while (off++ < 0 && base < p) {
   2594        MB_PTR_BACK(base, p);
   2595      }
   2596    }
   2597    col = (int)(p - base);
   2598  }
   2599  result->col = col;
   2600 }
   2601 
   2602 /// Add offset to matched text for start of match or highlight.
   2603 /// Avoid resulting column to become negative.
   2604 ///
   2605 /// @param result    returned position
   2606 /// @param regmatch  start/end of match
   2607 /// @param extra     extra chars for offset to end
   2608 static void syn_add_start_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *spp, int idx,
   2609                              int extra)
   2610 {
   2611  int col;
   2612  int off;
   2613  char *base;
   2614  char *p;
   2615 
   2616  if (spp->sp_off_flags & (1 << (idx + SPO_COUNT))) {
   2617    result->lnum = regmatch->endpos[0].lnum;
   2618    col = regmatch->endpos[0].col;
   2619    off = spp->sp_offsets[idx] + extra;
   2620  } else {
   2621    result->lnum = regmatch->startpos[0].lnum;
   2622    col = regmatch->startpos[0].col;
   2623    off = spp->sp_offsets[idx];
   2624  }
   2625  if (result->lnum > syn_buf->b_ml.ml_line_count) {
   2626    // a "\n" at the end of the pattern may take us below the last line
   2627    result->lnum = syn_buf->b_ml.ml_line_count;
   2628    col = ml_get_buf_len(syn_buf, result->lnum);
   2629  }
   2630  if (off != 0) {
   2631    base = ml_get_buf(syn_buf, result->lnum);
   2632    p = base + col;
   2633    if (off > 0) {
   2634      while (off-- && *p != NUL) {
   2635        MB_PTR_ADV(p);
   2636      }
   2637    } else {
   2638      while (off++ && base < p) {
   2639        MB_PTR_BACK(base, p);
   2640      }
   2641    }
   2642    col = (int)(p - base);
   2643  }
   2644  result->col = col;
   2645 }
   2646 
   2647 /// Get current line in syntax buffer.
   2648 static char *syn_getcurline(void)
   2649 {
   2650  return ml_get_buf(syn_buf, current_lnum);
   2651 }
   2652 
   2653 /// Get length of current line in syntax buffer.
   2654 static colnr_T syn_getcurline_len(void)
   2655 {
   2656  return ml_get_buf_len(syn_buf, current_lnum);
   2657 }
   2658 
   2659 // Call vim_regexec() to find a match with "rmp" in "syn_buf".
   2660 // Returns true when there is a match.
   2661 static bool syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T *st)
   2662 {
   2663  int timed_out = 0;
   2664  proftime_T pt;
   2665  const bool l_syn_time_on = syn_time_on;
   2666 
   2667  if (l_syn_time_on) {
   2668    pt = profile_start();
   2669  }
   2670 
   2671  if (rmp->regprog == NULL) {
   2672    // This can happen if a previous call to vim_regexec_multi() tried to
   2673    // use the NFA engine, which resulted in NFA_TOO_EXPENSIVE, and
   2674    // compiling the pattern with the other engine fails.
   2675    return false;
   2676  }
   2677 
   2678  rmp->rmm_maxcol = (colnr_T)syn_buf->b_p_smc;
   2679  int r = vim_regexec_multi(rmp, syn_win, syn_buf, lnum, col, syn_tm, &timed_out);
   2680 
   2681  if (l_syn_time_on) {
   2682    pt = profile_end(pt);
   2683    st->total = profile_add(st->total, pt);
   2684    if (profile_cmp(pt, st->slowest) < 0) {
   2685      st->slowest = pt;
   2686    }
   2687    st->count++;
   2688    if (r > 0) {
   2689      st->match++;
   2690    }
   2691  }
   2692  if (timed_out && !syn_win->w_s->b_syn_slow) {
   2693    syn_win->w_s->b_syn_slow = true;
   2694    msg(_("'redrawtime' exceeded, syntax highlighting disabled"), 0);
   2695  }
   2696 
   2697  if (r > 0) {
   2698    rmp->startpos[0].lnum += lnum;
   2699    rmp->endpos[0].lnum += lnum;
   2700    return true;
   2701  }
   2702  return false;
   2703 }
   2704 
   2705 /// Check one position in a line for a matching keyword.
   2706 /// The caller must check if a keyword can start at startcol.
   2707 /// Return its ID if found, 0 otherwise.
   2708 ///
   2709 /// @param startcol    position in line to check for keyword
   2710 /// @param endcolp     return: character after found keyword
   2711 /// @param flagsp      return: flags of matching keyword
   2712 /// @param next_listp  return: next_list of matching keyword
   2713 /// @param cur_si      item at the top of the stack
   2714 /// @param ccharp      conceal substitution char
   2715 static int check_keyword_id(char *const line, const int startcol, int *const endcolp,
   2716                            int *const flagsp, int16_t **const next_listp,
   2717                            stateitem_T *const cur_si, int *const ccharp)
   2718 {
   2719  // Find first character after the keyword.  First character was already
   2720  // checked.
   2721  char *const kwp = line + startcol;
   2722  int kwlen = 0;
   2723  do {
   2724    kwlen += utfc_ptr2len(kwp + kwlen);
   2725  } while (vim_iswordp_buf(kwp + kwlen, syn_buf));
   2726 
   2727  if (kwlen > MAXKEYWLEN) {
   2728    return 0;
   2729  }
   2730 
   2731  // Must make a copy of the keyword, so we can add a NUL and make it
   2732  // lowercase.
   2733  char keyword[MAXKEYWLEN + 1];         // assume max. keyword len is 80
   2734  xmemcpyz(keyword, kwp, (size_t)kwlen);
   2735 
   2736  keyentry_T *kp = NULL;
   2737 
   2738  // matching case
   2739  if (syn_block->b_keywtab.ht_used != 0) {
   2740    kp = match_keyword(keyword, &syn_block->b_keywtab, cur_si);
   2741  }
   2742 
   2743  // ignoring case
   2744  if (kp == NULL && syn_block->b_keywtab_ic.ht_used != 0) {
   2745    str_foldcase(kwp, kwlen, keyword, MAXKEYWLEN + 1);
   2746    kp = match_keyword(keyword, &syn_block->b_keywtab_ic, cur_si);
   2747  }
   2748 
   2749  if (kp != NULL) {
   2750    *endcolp = startcol + kwlen;
   2751    *flagsp = kp->flags;
   2752    *next_listp = kp->next_list;
   2753    *ccharp = kp->k_char;
   2754    return kp->k_syn.id;
   2755  }
   2756 
   2757  return 0;
   2758 }
   2759 
   2760 /// Find keywords that match.  There can be several with different
   2761 /// attributes.
   2762 /// When current_next_list is non-zero accept only that group, otherwise:
   2763 ///  Accept a not-contained keyword at toplevel.
   2764 ///  Accept a keyword at other levels only if it is in the contains list.
   2765 static keyentry_T *match_keyword(char *keyword, hashtab_T *ht, stateitem_T *cur_si)
   2766 {
   2767  hashitem_T *hi = hash_find(ht, keyword);
   2768  if (!HASHITEM_EMPTY(hi)) {
   2769    for (keyentry_T *kp = HI2KE(hi); kp != NULL; kp = kp->ke_next) {
   2770      if (current_next_list != 0
   2771          ? in_id_list(NULL, current_next_list, &kp->k_syn, 0)
   2772          : (cur_si == NULL
   2773             ? !(kp->flags & HL_CONTAINED)
   2774             : in_id_list(cur_si, cur_si->si_cont_list,
   2775                          &kp->k_syn, kp->flags))) {
   2776        return kp;
   2777      }
   2778    }
   2779  }
   2780  return NULL;
   2781 }
   2782 
   2783 // Handle ":syntax conceal" command.
   2784 static void syn_cmd_conceal(exarg_T *eap, int syncing)
   2785 {
   2786  char *arg = eap->arg;
   2787  char *next;
   2788 
   2789  eap->nextcmd = find_nextcmd(arg);
   2790  if (eap->skip) {
   2791    return;
   2792  }
   2793 
   2794  next = skiptowhite(arg);
   2795  if (*arg == NUL) {
   2796    if (curwin->w_s->b_syn_conceal) {
   2797      msg("syntax conceal on", 0);
   2798    } else {
   2799      msg("syntax conceal off", 0);
   2800    }
   2801  } else if (STRNICMP(arg, "on", 2) == 0 && next - arg == 2) {
   2802    curwin->w_s->b_syn_conceal = true;
   2803  } else if (STRNICMP(arg, "off", 3) == 0 && next - arg == 3) {
   2804    curwin->w_s->b_syn_conceal = false;
   2805  } else {
   2806    semsg(_(e_illegal_arg), arg);
   2807  }
   2808 }
   2809 
   2810 /// Handle ":syntax case" command.
   2811 static void syn_cmd_case(exarg_T *eap, int syncing)
   2812 {
   2813  char *arg = eap->arg;
   2814  char *next;
   2815 
   2816  eap->nextcmd = find_nextcmd(arg);
   2817  if (eap->skip) {
   2818    return;
   2819  }
   2820 
   2821  next = skiptowhite(arg);
   2822  if (*arg == NUL) {
   2823    if (curwin->w_s->b_syn_ic) {
   2824      msg("syntax case ignore", 0);
   2825    } else {
   2826      msg("syntax case match", 0);
   2827    }
   2828  } else if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5) {
   2829    curwin->w_s->b_syn_ic = false;
   2830  } else if (STRNICMP(arg, "ignore", 6) == 0 && next - arg == 6) {
   2831    curwin->w_s->b_syn_ic = true;
   2832  } else {
   2833    semsg(_(e_illegal_arg), arg);
   2834  }
   2835 }
   2836 
   2837 /// Handle ":syntax foldlevel" command.
   2838 static void syn_cmd_foldlevel(exarg_T *eap, int syncing)
   2839 {
   2840  char *arg = eap->arg;
   2841  char *arg_end;
   2842 
   2843  eap->nextcmd = find_nextcmd(arg);
   2844  if (eap->skip) {
   2845    return;
   2846  }
   2847 
   2848  if (*arg == NUL) {
   2849    switch (curwin->w_s->b_syn_foldlevel) {
   2850    case SYNFLD_START:
   2851      msg("syntax foldlevel start", 0);   break;
   2852    case SYNFLD_MINIMUM:
   2853      msg("syntax foldlevel minimum", 0); break;
   2854    default:
   2855      break;
   2856    }
   2857    return;
   2858  }
   2859 
   2860  arg_end = skiptowhite(arg);
   2861  if (STRNICMP(arg, "start", 5) == 0 && arg_end - arg == 5) {
   2862    curwin->w_s->b_syn_foldlevel = SYNFLD_START;
   2863  } else if (STRNICMP(arg, "minimum", 7) == 0 && arg_end - arg == 7) {
   2864    curwin->w_s->b_syn_foldlevel = SYNFLD_MINIMUM;
   2865  } else {
   2866    semsg(_(e_illegal_arg), arg);
   2867    return;
   2868  }
   2869 
   2870  arg = skipwhite(arg_end);
   2871  if (*arg != NUL) {
   2872    semsg(_(e_illegal_arg), arg);
   2873  }
   2874 }
   2875 
   2876 /// Handle ":syntax spell" command.
   2877 static void syn_cmd_spell(exarg_T *eap, int syncing)
   2878 {
   2879  char *arg = eap->arg;
   2880  char *next;
   2881 
   2882  eap->nextcmd = find_nextcmd(arg);
   2883  if (eap->skip) {
   2884    return;
   2885  }
   2886 
   2887  next = skiptowhite(arg);
   2888  if (*arg == NUL) {
   2889    if (curwin->w_s->b_syn_spell == SYNSPL_TOP) {
   2890      msg("syntax spell toplevel", 0);
   2891    } else if (curwin->w_s->b_syn_spell == SYNSPL_NOTOP) {
   2892      msg("syntax spell notoplevel", 0);
   2893    } else {
   2894      msg("syntax spell default", 0);
   2895    }
   2896  } else if (STRNICMP(arg, "toplevel", 8) == 0 && next - arg == 8) {
   2897    curwin->w_s->b_syn_spell = SYNSPL_TOP;
   2898  } else if (STRNICMP(arg, "notoplevel", 10) == 0 && next - arg == 10) {
   2899    curwin->w_s->b_syn_spell = SYNSPL_NOTOP;
   2900  } else if (STRNICMP(arg, "default", 7) == 0 && next - arg == 7) {
   2901    curwin->w_s->b_syn_spell = SYNSPL_DEFAULT;
   2902  } else {
   2903    semsg(_(e_illegal_arg), arg);
   2904    return;
   2905  }
   2906 
   2907  // assume spell checking changed, force a redraw
   2908  redraw_later(curwin, UPD_NOT_VALID);
   2909 }
   2910 
   2911 /// Handle ":syntax iskeyword" command.
   2912 static void syn_cmd_iskeyword(exarg_T *eap, int syncing)
   2913 {
   2914  char *arg = eap->arg;
   2915  char save_chartab[32];
   2916  char *save_isk;
   2917 
   2918  if (eap->skip) {
   2919    return;
   2920  }
   2921 
   2922  arg = skipwhite(arg);
   2923  if (*arg == NUL) {
   2924    msg_puts("\n");
   2925    if (curwin->w_s->b_syn_isk != empty_string_option) {
   2926      msg_puts("syntax iskeyword ");
   2927      msg_outtrans(curwin->w_s->b_syn_isk, 0, false);
   2928    } else {
   2929      msg_outtrans(_("syntax iskeyword not set"), 0, false);
   2930    }
   2931  } else {
   2932    if (STRNICMP(arg, "clear", 5) == 0) {
   2933      memmove(curwin->w_s->b_syn_chartab, curbuf->b_chartab, (size_t)32);
   2934      clear_string_option(&curwin->w_s->b_syn_isk);
   2935    } else {
   2936      memmove(save_chartab, curbuf->b_chartab, (size_t)32);
   2937      save_isk = curbuf->b_p_isk;
   2938      curbuf->b_p_isk = xstrdup(arg);
   2939 
   2940      buf_init_chartab(curbuf, false);
   2941      memmove(curwin->w_s->b_syn_chartab, curbuf->b_chartab, (size_t)32);
   2942      memmove(curbuf->b_chartab, save_chartab, (size_t)32);
   2943      clear_string_option(&curwin->w_s->b_syn_isk);
   2944      curwin->w_s->b_syn_isk = curbuf->b_p_isk;
   2945      curbuf->b_p_isk = save_isk;
   2946    }
   2947  }
   2948  redraw_later(curwin, UPD_NOT_VALID);
   2949 }
   2950 
   2951 // Clear all syntax info for one buffer.
   2952 void syntax_clear(synblock_T *block)
   2953 {
   2954  block->b_syn_error = false;           // clear previous error
   2955  block->b_syn_slow = false;            // clear previous timeout
   2956  block->b_syn_ic = false;              // Use case, by default
   2957  block->b_syn_foldlevel = SYNFLD_START;
   2958  block->b_syn_spell = SYNSPL_DEFAULT;  // default spell checking
   2959  block->b_syn_containedin = false;
   2960  block->b_syn_conceal = false;
   2961 
   2962  // free the keywords
   2963  clear_keywtab(&block->b_keywtab);
   2964  clear_keywtab(&block->b_keywtab_ic);
   2965 
   2966  // free the syntax patterns
   2967  for (int i = block->b_syn_patterns.ga_len; --i >= 0;) {
   2968    syn_clear_pattern(block, i);
   2969  }
   2970  ga_clear(&block->b_syn_patterns);
   2971 
   2972  // free the syntax clusters
   2973  for (int i = block->b_syn_clusters.ga_len; --i >= 0;) {
   2974    syn_clear_cluster(block, i);
   2975  }
   2976  ga_clear(&block->b_syn_clusters);
   2977  block->b_spell_cluster_id = 0;
   2978  block->b_nospell_cluster_id = 0;
   2979 
   2980  block->b_syn_sync_flags = 0;
   2981  block->b_syn_sync_minlines = 0;
   2982  block->b_syn_sync_maxlines = 0;
   2983  block->b_syn_sync_linebreaks = 0;
   2984 
   2985  vim_regfree(block->b_syn_linecont_prog);
   2986  block->b_syn_linecont_prog = NULL;
   2987  XFREE_CLEAR(block->b_syn_linecont_pat);
   2988  block->b_syn_folditems = 0;
   2989  clear_string_option(&block->b_syn_isk);
   2990 
   2991  // free the stored states
   2992  syn_stack_free_all(block);
   2993  invalidate_current_state();
   2994 
   2995  // Reset the counter for ":syn include"
   2996  running_syn_inc_tag = 0;
   2997 }
   2998 
   2999 // Get rid of ownsyntax for window "wp".
   3000 void reset_synblock(win_T *wp)
   3001 {
   3002  if (wp->w_s != &wp->w_buffer->b_s) {
   3003    syntax_clear(wp->w_s);
   3004    xfree(wp->w_s);
   3005    wp->w_s = &wp->w_buffer->b_s;
   3006  }
   3007 }
   3008 
   3009 // Clear syncing info for one buffer.
   3010 static void syntax_sync_clear(void)
   3011 {
   3012  // free the syntax patterns
   3013  for (int i = curwin->w_s->b_syn_patterns.ga_len; --i >= 0;) {
   3014    if (SYN_ITEMS(curwin->w_s)[i].sp_syncing) {
   3015      syn_remove_pattern(curwin->w_s, i);
   3016    }
   3017  }
   3018 
   3019  curwin->w_s->b_syn_sync_flags = 0;
   3020  curwin->w_s->b_syn_sync_minlines = 0;
   3021  curwin->w_s->b_syn_sync_maxlines = 0;
   3022  curwin->w_s->b_syn_sync_linebreaks = 0;
   3023 
   3024  vim_regfree(curwin->w_s->b_syn_linecont_prog);
   3025  curwin->w_s->b_syn_linecont_prog = NULL;
   3026  XFREE_CLEAR(curwin->w_s->b_syn_linecont_pat);
   3027  clear_string_option(&curwin->w_s->b_syn_isk);
   3028 
   3029  syn_stack_free_all(curwin->w_s);              // Need to recompute all syntax.
   3030 }
   3031 
   3032 // Remove one pattern from the buffer's pattern list.
   3033 static void syn_remove_pattern(synblock_T *block, int idx)
   3034 {
   3035  synpat_T *spp;
   3036 
   3037  spp = &(SYN_ITEMS(block)[idx]);
   3038  if (spp->sp_flags & HL_FOLD) {
   3039    block->b_syn_folditems--;
   3040  }
   3041  syn_clear_pattern(block, idx);
   3042  memmove(spp, spp + 1, sizeof(synpat_T) * (size_t)(block->b_syn_patterns.ga_len - idx - 1));
   3043  block->b_syn_patterns.ga_len--;
   3044 }
   3045 
   3046 // Clear and free one syntax pattern.  When clearing all, must be called from
   3047 // last to first!
   3048 static void syn_clear_pattern(synblock_T *block, int i)
   3049 {
   3050  xfree(SYN_ITEMS(block)[i].sp_pattern);
   3051  vim_regfree(SYN_ITEMS(block)[i].sp_prog);
   3052  // Only free sp_cont_list and sp_next_list of first start pattern
   3053  if (i == 0 || SYN_ITEMS(block)[i - 1].sp_type != SPTYPE_START) {
   3054    xfree(SYN_ITEMS(block)[i].sp_cont_list);
   3055    xfree(SYN_ITEMS(block)[i].sp_next_list);
   3056    xfree(SYN_ITEMS(block)[i].sp_syn.cont_in_list);
   3057  }
   3058 }
   3059 
   3060 // Clear and free one syntax cluster.
   3061 static void syn_clear_cluster(synblock_T *block, int i)
   3062 {
   3063  xfree(SYN_CLSTR(block)[i].scl_name);
   3064  xfree(SYN_CLSTR(block)[i].scl_name_u);
   3065  xfree(SYN_CLSTR(block)[i].scl_list);
   3066 }
   3067 
   3068 /// Handle ":syntax clear" command.
   3069 static void syn_cmd_clear(exarg_T *eap, int syncing)
   3070 {
   3071  char *arg = eap->arg;
   3072  char *arg_end;
   3073  int id;
   3074 
   3075  eap->nextcmd = find_nextcmd(arg);
   3076  if (eap->skip) {
   3077    return;
   3078  }
   3079 
   3080  // We have to disable this within ":syn include @group filename",
   3081  // because otherwise @group would get deleted.
   3082  // Only required for Vim 5.x syntax files, 6.0 ones don't contain ":syn
   3083  // clear".
   3084  if (curwin->w_s->b_syn_topgrp != 0) {
   3085    return;
   3086  }
   3087 
   3088  if (ends_excmd(*arg)) {
   3089    // No argument: Clear all syntax items.
   3090    if (syncing) {
   3091      syntax_sync_clear();
   3092    } else {
   3093      syntax_clear(curwin->w_s);
   3094      if (curwin->w_s == &curwin->w_buffer->b_s) {
   3095        do_unlet(S_LEN("b:current_syntax"), true);
   3096      }
   3097      do_unlet(S_LEN("w:current_syntax"), true);
   3098    }
   3099  } else {
   3100    // Clear the group IDs that are in the argument.
   3101    while (!ends_excmd(*arg)) {
   3102      arg_end = skiptowhite(arg);
   3103      if (*arg == '@') {
   3104        id = syn_scl_namen2id(arg + 1, (int)(arg_end - arg - 1));
   3105        if (id == 0) {
   3106          semsg(_("E391: No such syntax cluster: %s"), arg);
   3107          break;
   3108        }
   3109        // We can't physically delete a cluster without changing
   3110        // the IDs of other clusters, so we do the next best thing
   3111        // and make it empty.
   3112        int scl_id = id - SYNID_CLUSTER;
   3113 
   3114        XFREE_CLEAR(SYN_CLSTR(curwin->w_s)[scl_id].scl_list);
   3115      } else {
   3116        id = syn_name2id_len(arg, (size_t)(arg_end - arg));
   3117        if (id == 0) {
   3118          semsg(_(e_nogroup), arg);
   3119          break;
   3120        }
   3121        syn_clear_one(id, syncing);
   3122      }
   3123      arg = skipwhite(arg_end);
   3124    }
   3125  }
   3126  redraw_curbuf_later(UPD_SOME_VALID);
   3127  syn_stack_free_all(curwin->w_s);              // Need to recompute all syntax.
   3128 }
   3129 
   3130 // Clear one syntax group for the current buffer.
   3131 static void syn_clear_one(const int id, const bool syncing)
   3132 {
   3133  synpat_T *spp;
   3134 
   3135  // Clear keywords only when not ":syn sync clear group-name"
   3136  if (!syncing) {
   3137    syn_clear_keyword(id, &curwin->w_s->b_keywtab);
   3138    syn_clear_keyword(id, &curwin->w_s->b_keywtab_ic);
   3139  }
   3140 
   3141  // clear the patterns for "id"
   3142  for (int idx = curwin->w_s->b_syn_patterns.ga_len; --idx >= 0;) {
   3143    spp = &(SYN_ITEMS(curwin->w_s)[idx]);
   3144    if (spp->sp_syn.id != id || spp->sp_syncing != syncing) {
   3145      continue;
   3146    }
   3147    syn_remove_pattern(curwin->w_s, idx);
   3148  }
   3149 }
   3150 
   3151 // Handle ":syntax on" command.
   3152 static void syn_cmd_on(exarg_T *eap, int syncing)
   3153 {
   3154  syn_cmd_onoff(eap, "syntax");
   3155 }
   3156 
   3157 // Handle ":syntax reset" command.
   3158 // It actually resets highlighting, not syntax.
   3159 static void syn_cmd_reset(exarg_T *eap, int syncing)
   3160 {
   3161  eap->nextcmd = check_nextcmd(eap->arg);
   3162  if (!eap->skip) {
   3163    init_highlight(true, true);
   3164  }
   3165 }
   3166 
   3167 // Handle ":syntax manual" command.
   3168 static void syn_cmd_manual(exarg_T *eap, int syncing)
   3169 {
   3170  syn_cmd_onoff(eap, "manual");
   3171 }
   3172 
   3173 // Handle ":syntax off" command.
   3174 static void syn_cmd_off(exarg_T *eap, int syncing)
   3175 {
   3176  syn_cmd_onoff(eap, "nosyntax");
   3177 }
   3178 
   3179 static void syn_cmd_onoff(exarg_T *eap, char *name)
   3180  FUNC_ATTR_NONNULL_ALL
   3181 {
   3182  eap->nextcmd = check_nextcmd(eap->arg);
   3183  if (!eap->skip) {
   3184    did_syntax_onoff = true;
   3185    char buf[100];
   3186    memcpy(buf, "so ", 4);
   3187    vim_snprintf(buf + 3, sizeof(buf) - 3, SYNTAX_FNAME, name);
   3188    do_cmdline_cmd(buf);
   3189  }
   3190 }
   3191 
   3192 void syn_maybe_enable(void)
   3193 {
   3194  if (!did_syntax_onoff) {
   3195    exarg_T ea;
   3196    ea.arg = "";
   3197    ea.skip = false;
   3198    syn_cmd_on(&ea, false);
   3199  }
   3200 }
   3201 
   3202 /// Handle ":syntax [list]" command: list current syntax words.
   3203 ///
   3204 /// @param syncing  when true: list syncing items
   3205 static void syn_cmd_list(exarg_T *eap, int syncing)
   3206 {
   3207  char *arg = eap->arg;
   3208  char *arg_end;
   3209 
   3210  eap->nextcmd = find_nextcmd(arg);
   3211  if (eap->skip) {
   3212    return;
   3213  }
   3214 
   3215  msg_ext_set_kind("list_cmd");
   3216  if (!syntax_present(curwin)) {
   3217    msg(_(msg_no_items), 0);
   3218    return;
   3219  }
   3220 
   3221  if (syncing) {
   3222    if (curwin->w_s->b_syn_sync_flags & SF_CCOMMENT) {
   3223      msg_puts(_("syncing on C-style comments"));
   3224      syn_lines_msg();
   3225      syn_match_msg();
   3226      return;
   3227    } else if (!(curwin->w_s->b_syn_sync_flags & SF_MATCH)) {
   3228      if (curwin->w_s->b_syn_sync_minlines == 0) {
   3229        msg_puts(_("no syncing"));
   3230      } else {
   3231        if (curwin->w_s->b_syn_sync_minlines == MAXLNUM) {
   3232          msg_puts(_("syncing starts at the first line"));
   3233        } else {
   3234          msg_puts(_("syncing starts "));
   3235          msg_outnum(curwin->w_s->b_syn_sync_minlines);
   3236          msg_puts(_(" lines before top line"));
   3237        }
   3238        syn_match_msg();
   3239      }
   3240      return;
   3241    }
   3242    msg_puts_title(_("\n--- Syntax sync items ---"));
   3243    if (curwin->w_s->b_syn_sync_minlines > 0
   3244        || curwin->w_s->b_syn_sync_maxlines > 0
   3245        || curwin->w_s->b_syn_sync_linebreaks > 0) {
   3246      msg_puts(_("\nsyncing on items"));
   3247      syn_lines_msg();
   3248      syn_match_msg();
   3249    }
   3250  } else {
   3251    msg_puts_title(_("\n--- Syntax items ---"));
   3252  }
   3253  if (ends_excmd(*arg)) {
   3254    // No argument: List all group IDs and all syntax clusters.
   3255    for (int id = 1; id <= highlight_num_groups() && !got_int; id++) {
   3256      syn_list_one(id, syncing, false);
   3257    }
   3258    for (int id = 0; id < curwin->w_s->b_syn_clusters.ga_len && !got_int; id++) {
   3259      syn_list_cluster(id);
   3260    }
   3261  } else {
   3262    // List the group IDs and syntax clusters that are in the argument.
   3263    while (!ends_excmd(*arg) && !got_int) {
   3264      arg_end = skiptowhite(arg);
   3265      if (*arg == '@') {
   3266        int id = syn_scl_namen2id(arg + 1, (int)(arg_end - arg - 1));
   3267        if (id == 0) {
   3268          semsg(_("E392: No such syntax cluster: %s"), arg);
   3269        } else {
   3270          syn_list_cluster(id - SYNID_CLUSTER);
   3271        }
   3272      } else {
   3273        int id = syn_name2id_len(arg, (size_t)(arg_end - arg));
   3274        if (id == 0) {
   3275          semsg(_(e_nogroup), arg);
   3276        } else {
   3277          syn_list_one(id, syncing, true);
   3278        }
   3279      }
   3280      arg = skipwhite(arg_end);
   3281    }
   3282  }
   3283  eap->nextcmd = check_nextcmd(arg);
   3284 }
   3285 
   3286 static void syn_lines_msg(void)
   3287 {
   3288  if (curwin->w_s->b_syn_sync_maxlines > 0
   3289      || curwin->w_s->b_syn_sync_minlines > 0) {
   3290    msg_puts("; ");
   3291    if (curwin->w_s->b_syn_sync_minlines == MAXLNUM) {
   3292      msg_puts(_("from the first line"));
   3293    } else {
   3294      if (curwin->w_s->b_syn_sync_minlines > 0) {
   3295        msg_puts(_("minimal "));
   3296        msg_outnum(curwin->w_s->b_syn_sync_minlines);
   3297        if (curwin->w_s->b_syn_sync_maxlines) {
   3298          msg_puts(", ");
   3299        }
   3300      }
   3301      if (curwin->w_s->b_syn_sync_maxlines > 0) {
   3302        msg_puts(_("maximal "));
   3303        msg_outnum(curwin->w_s->b_syn_sync_maxlines);
   3304      }
   3305      msg_puts(_(" lines before top line"));
   3306    }
   3307  }
   3308 }
   3309 
   3310 static void syn_match_msg(void)
   3311 {
   3312  if (curwin->w_s->b_syn_sync_linebreaks > 0) {
   3313    msg_puts(_("; match "));
   3314    msg_outnum(curwin->w_s->b_syn_sync_linebreaks);
   3315    msg_puts(_(" line breaks"));
   3316  }
   3317 }
   3318 
   3319 static int last_matchgroup;
   3320 
   3321 /// List one syntax item, for ":syntax" or "syntax list syntax_name".
   3322 ///
   3323 /// @param syncing    when true: list syncing items
   3324 /// @param link_only  when true; list link-only too
   3325 static void syn_list_one(const int id, const bool syncing, const bool link_only)
   3326 {
   3327  bool did_header = false;
   3328  static keyvalue_T namelist1[] = {
   3329    KEYVALUE_ENTRY(HL_DISPLAY, "display"),
   3330    KEYVALUE_ENTRY(HL_CONTAINED, "contained"),
   3331    KEYVALUE_ENTRY(HL_ONELINE, "oneline"),
   3332    KEYVALUE_ENTRY(HL_KEEPEND, "keepend"),
   3333    KEYVALUE_ENTRY(HL_EXTEND, "extend"),
   3334    KEYVALUE_ENTRY(HL_EXCLUDENL, "excludenl"),
   3335    KEYVALUE_ENTRY(HL_TRANSP, "transparent"),
   3336    KEYVALUE_ENTRY(HL_FOLD, "fold"),
   3337    KEYVALUE_ENTRY(HL_CONCEAL, "conceal"),
   3338    KEYVALUE_ENTRY(HL_CONCEALENDS, "concealends"),
   3339  };
   3340  static keyvalue_T namelist2[] = {
   3341    KEYVALUE_ENTRY(HL_SKIPWHITE, "skipwhite"),
   3342    KEYVALUE_ENTRY(HL_SKIPNL, "skipnl"),
   3343    KEYVALUE_ENTRY(HL_SKIPEMPTY, "skipempty"),
   3344  };
   3345 
   3346  const int hl_id = HLF_D;      // highlight like directories
   3347 
   3348  // list the keywords for "id"
   3349  if (!syncing) {
   3350    did_header = syn_list_keywords(id, &curwin->w_s->b_keywtab, false, hl_id);
   3351    did_header = syn_list_keywords(id, &curwin->w_s->b_keywtab_ic, did_header, hl_id);
   3352  }
   3353 
   3354  // list the patterns for "id"
   3355  for (int idx = 0;
   3356       idx < curwin->w_s->b_syn_patterns.ga_len && !got_int;
   3357       idx++) {
   3358    const synpat_T *const spp = &(SYN_ITEMS(curwin->w_s)[idx]);
   3359    if (spp->sp_syn.id != id || spp->sp_syncing != syncing) {
   3360      continue;
   3361    }
   3362 
   3363    syn_list_header(did_header, 0, id, true);
   3364    did_header = true;
   3365    last_matchgroup = 0;
   3366    if (spp->sp_type == SPTYPE_MATCH) {
   3367      put_pattern("match", ' ', spp, hl_id);
   3368      msg_putchar(' ');
   3369    } else if (spp->sp_type == SPTYPE_START) {
   3370      while (SYN_ITEMS(curwin->w_s)[idx].sp_type == SPTYPE_START) {
   3371        put_pattern("start", '=', &SYN_ITEMS(curwin->w_s)[idx++], hl_id);
   3372      }
   3373      if (SYN_ITEMS(curwin->w_s)[idx].sp_type == SPTYPE_SKIP) {
   3374        put_pattern("skip", '=', &SYN_ITEMS(curwin->w_s)[idx++], hl_id);
   3375      }
   3376      while (idx < curwin->w_s->b_syn_patterns.ga_len
   3377             && SYN_ITEMS(curwin->w_s)[idx].sp_type == SPTYPE_END) {
   3378        put_pattern("end", '=', &SYN_ITEMS(curwin->w_s)[idx++], hl_id);
   3379      }
   3380      idx--;
   3381      msg_putchar(' ');
   3382    }
   3383    syn_list_flags(namelist1, ARRAY_SIZE(namelist1), spp->sp_flags, hl_id);
   3384 
   3385    if (spp->sp_cont_list != NULL) {
   3386      put_id_list("contains", spp->sp_cont_list, hl_id);
   3387    }
   3388 
   3389    if (spp->sp_syn.cont_in_list != NULL) {
   3390      put_id_list("containedin", spp->sp_syn.cont_in_list, hl_id);
   3391    }
   3392 
   3393    if (spp->sp_next_list != NULL) {
   3394      put_id_list("nextgroup", spp->sp_next_list, hl_id);
   3395      syn_list_flags(namelist2, ARRAY_SIZE(namelist2), spp->sp_flags, hl_id);
   3396    }
   3397    if (spp->sp_flags & (HL_SYNC_HERE|HL_SYNC_THERE)) {
   3398      if (spp->sp_flags & HL_SYNC_HERE) {
   3399        msg_puts_hl("grouphere", hl_id, false);
   3400      } else {
   3401        msg_puts_hl("groupthere", hl_id, false);
   3402      }
   3403      msg_putchar(' ');
   3404      if (spp->sp_sync_idx >= 0) {
   3405        msg_outtrans(highlight_group_name(SYN_ITEMS(curwin->w_s)
   3406                                          [spp->sp_sync_idx].sp_syn.id - 1), 0, false);
   3407      } else {
   3408        msg_puts("NONE");
   3409      }
   3410      msg_putchar(' ');
   3411    }
   3412  }
   3413 
   3414  // list the link, if there is one
   3415  if (highlight_link_id(id - 1) && (did_header || link_only) && !got_int) {
   3416    syn_list_header(did_header, 0, id, true);
   3417    msg_puts_hl("links to", hl_id, false);
   3418    msg_putchar(' ');
   3419    msg_outtrans(highlight_group_name(highlight_link_id(id - 1) - 1), 0, false);
   3420  }
   3421 }
   3422 
   3423 static void syn_list_flags(keyvalue_T *nlist, size_t nr_entries, int flags, int hl_id)
   3424 {
   3425  for (size_t i = 0; i < nr_entries; i++) {
   3426    if (flags & nlist[i].key) {
   3427      msg_puts_hl(nlist[i].value, hl_id, false);
   3428      msg_putchar(' ');
   3429    }
   3430  }
   3431 }
   3432 
   3433 // List one syntax cluster, for ":syntax" or "syntax list syntax_name".
   3434 static void syn_list_cluster(int id)
   3435 {
   3436  int endcol = 15;
   3437 
   3438  // slight hack:  roughly duplicate the guts of syn_list_header()
   3439  msg_putchar('\n');
   3440  msg_outtrans(SYN_CLSTR(curwin->w_s)[id].scl_name, 0, false);
   3441 
   3442  if (msg_col >= endcol) {      // output at least one space
   3443    endcol = msg_col + 1;
   3444  }
   3445  if (Columns <= endcol) {      // avoid hang for tiny window
   3446    endcol = Columns - 1;
   3447  }
   3448 
   3449  msg_advance(endcol);
   3450  if (SYN_CLSTR(curwin->w_s)[id].scl_list != NULL) {
   3451    put_id_list("cluster", SYN_CLSTR(curwin->w_s)[id].scl_list, HLF_D);
   3452  } else {
   3453    msg_puts_hl("cluster", HLF_D, false);
   3454    msg_puts("=NONE");
   3455  }
   3456 }
   3457 
   3458 static void put_id_list(const char *const name, const int16_t *const list, const int hl_id)
   3459 {
   3460  msg_puts_hl(name, hl_id, false);
   3461  msg_putchar('=');
   3462  for (const int16_t *p = list; *p; p++) {
   3463    if (*p >= SYNID_ALLBUT && *p < SYNID_TOP) {
   3464      if (p[1]) {
   3465        msg_puts("ALLBUT");
   3466      } else {
   3467        msg_puts("ALL");
   3468      }
   3469    } else if (*p >= SYNID_TOP && *p < SYNID_CONTAINED) {
   3470      msg_puts("TOP");
   3471    } else if (*p >= SYNID_CONTAINED && *p < SYNID_CLUSTER) {
   3472      msg_puts("CONTAINED");
   3473    } else if (*p >= SYNID_CLUSTER) {
   3474      int scl_id = *p - SYNID_CLUSTER;
   3475 
   3476      msg_putchar('@');
   3477      msg_outtrans(SYN_CLSTR(curwin->w_s)[scl_id].scl_name, 0, false);
   3478    } else {
   3479      msg_outtrans(highlight_group_name(*p - 1), 0, false);
   3480    }
   3481    if (p[1]) {
   3482      msg_putchar(',');
   3483    }
   3484  }
   3485  msg_putchar(' ');
   3486 }
   3487 
   3488 static void put_pattern(const char *const s, const int c, const synpat_T *const spp,
   3489                        const int hl_id)
   3490 {
   3491  static const char *const sepchars = "/+=-#@\"|'^&";
   3492  int i;
   3493 
   3494  // May have to write "matchgroup=group"
   3495  if (last_matchgroup != spp->sp_syn_match_id) {
   3496    last_matchgroup = spp->sp_syn_match_id;
   3497    msg_puts_hl("matchgroup", hl_id, false);
   3498    msg_putchar('=');
   3499    if (last_matchgroup == 0) {
   3500      msg_outtrans("NONE", 0, false);
   3501    } else {
   3502      msg_outtrans(highlight_group_name(last_matchgroup - 1), 0, false);
   3503    }
   3504    msg_putchar(' ');
   3505  }
   3506 
   3507  // Output the name of the pattern and an '=' or ' '.
   3508  msg_puts_hl(s, hl_id, false);
   3509  msg_putchar(c);
   3510 
   3511  // output the pattern, in between a char that is not in the pattern
   3512  for (i = 0; vim_strchr(spp->sp_pattern, (uint8_t)sepchars[i]) != NULL;) {
   3513    if (sepchars[++i] == NUL) {
   3514      i = 0;            // no good char found, just use the first one
   3515      break;
   3516    }
   3517  }
   3518  msg_putchar(sepchars[i]);
   3519  msg_outtrans(spp->sp_pattern, 0, false);
   3520  msg_putchar(sepchars[i]);
   3521 
   3522  // output any pattern options
   3523  bool first = true;
   3524  for (i = 0; i < SPO_COUNT; i++) {
   3525    const int mask = (1 << i);
   3526    if (!(spp->sp_off_flags & (mask + (mask << SPO_COUNT)))) {
   3527      continue;
   3528    }
   3529    if (!first) {
   3530      msg_putchar(',');  // Separate with commas.
   3531    }
   3532    msg_puts(spo_name_tab[i]);
   3533    const int n = spp->sp_offsets[i];
   3534    if (i != SPO_LC_OFF) {
   3535      if (spp->sp_off_flags & mask) {
   3536        msg_putchar('s');
   3537      } else {
   3538        msg_putchar('e');
   3539      }
   3540      if (n > 0) {
   3541        msg_putchar('+');
   3542      }
   3543    }
   3544    if (n || i == SPO_LC_OFF) {
   3545      msg_outnum(n);
   3546    }
   3547    first = false;
   3548  }
   3549  msg_putchar(' ');
   3550 }
   3551 
   3552 /// List or clear the keywords for one syntax group.
   3553 ///
   3554 /// @param did_header  header has already been printed
   3555 ///
   3556 /// @return            true if the header has been printed.
   3557 static bool syn_list_keywords(const int id, const hashtab_T *const ht, bool did_header,
   3558                              const int hl_id)
   3559 {
   3560  int prev_contained = 0;
   3561  const int16_t *prev_next_list = NULL;
   3562  const int16_t *prev_cont_in_list = NULL;
   3563  int prev_skipnl = 0;
   3564  int prev_skipwhite = 0;
   3565  int prev_skipempty = 0;
   3566 
   3567  // Unfortunately, this list of keywords is not sorted on alphabet but on
   3568  // hash value...
   3569  size_t todo = ht->ht_used;
   3570  for (const hashitem_T *hi = ht->ht_array; todo > 0 && !got_int; hi++) {
   3571    if (HASHITEM_EMPTY(hi)) {
   3572      continue;
   3573    }
   3574    todo--;
   3575    for (keyentry_T *kp = HI2KE(hi); kp != NULL && !got_int; kp = kp->ke_next) {
   3576      if (kp->k_syn.id == id) {
   3577        int outlen = 0;
   3578        bool force_newline = false;
   3579        if (prev_contained != (kp->flags & HL_CONTAINED)
   3580            || prev_skipnl != (kp->flags & HL_SKIPNL)
   3581            || prev_skipwhite != (kp->flags & HL_SKIPWHITE)
   3582            || prev_skipempty != (kp->flags & HL_SKIPEMPTY)
   3583            || prev_cont_in_list != kp->k_syn.cont_in_list
   3584            || prev_next_list != kp->next_list) {
   3585          force_newline = true;
   3586        } else {
   3587          outlen = (int)strlen(kp->keyword);
   3588        }
   3589        // output "contained" and "nextgroup" on each line
   3590        if (syn_list_header(did_header, outlen, id, force_newline)) {
   3591          prev_contained = 0;
   3592          prev_next_list = NULL;
   3593          prev_cont_in_list = NULL;
   3594          prev_skipnl = 0;
   3595          prev_skipwhite = 0;
   3596          prev_skipempty = 0;
   3597        }
   3598        did_header = true;
   3599        if (prev_contained != (kp->flags & HL_CONTAINED)) {
   3600          msg_puts_hl("contained", hl_id, false);
   3601          msg_putchar(' ');
   3602          prev_contained = (kp->flags & HL_CONTAINED);
   3603        }
   3604        if (kp->k_syn.cont_in_list != prev_cont_in_list) {
   3605          put_id_list("containedin", kp->k_syn.cont_in_list, hl_id);
   3606          msg_putchar(' ');
   3607          prev_cont_in_list = kp->k_syn.cont_in_list;
   3608        }
   3609        if (kp->next_list != prev_next_list) {
   3610          put_id_list("nextgroup", kp->next_list, hl_id);
   3611          msg_putchar(' ');
   3612          prev_next_list = kp->next_list;
   3613          if (kp->flags & HL_SKIPNL) {
   3614            msg_puts_hl("skipnl", hl_id, false);
   3615            msg_putchar(' ');
   3616            prev_skipnl = (kp->flags & HL_SKIPNL);
   3617          }
   3618          if (kp->flags & HL_SKIPWHITE) {
   3619            msg_puts_hl("skipwhite", hl_id, false);
   3620            msg_putchar(' ');
   3621            prev_skipwhite = (kp->flags & HL_SKIPWHITE);
   3622          }
   3623          if (kp->flags & HL_SKIPEMPTY) {
   3624            msg_puts_hl("skipempty", hl_id, false);
   3625            msg_putchar(' ');
   3626            prev_skipempty = (kp->flags & HL_SKIPEMPTY);
   3627          }
   3628        }
   3629        msg_outtrans(kp->keyword, 0, false);
   3630      }
   3631    }
   3632  }
   3633 
   3634  return did_header;
   3635 }
   3636 
   3637 static void syn_clear_keyword(int id, hashtab_T *ht)
   3638 {
   3639  hash_lock(ht);
   3640  int todo = (int)ht->ht_used;
   3641  for (hashitem_T *hi = ht->ht_array; todo > 0; hi++) {
   3642    if (HASHITEM_EMPTY(hi)) {
   3643      continue;
   3644    }
   3645    todo--;
   3646    keyentry_T *kp_prev = NULL;
   3647    for (keyentry_T *kp = HI2KE(hi); kp != NULL;) {
   3648      if (kp->k_syn.id == id) {
   3649        keyentry_T *kp_next = kp->ke_next;
   3650        if (kp_prev == NULL) {
   3651          if (kp_next == NULL) {
   3652            hash_remove(ht, hi);
   3653          } else {
   3654            hi->hi_key = KE2HIKEY(kp_next);
   3655          }
   3656        } else {
   3657          kp_prev->ke_next = kp_next;
   3658        }
   3659        xfree(kp->next_list);
   3660        xfree(kp->k_syn.cont_in_list);
   3661        xfree(kp);
   3662        kp = kp_next;
   3663      } else {
   3664        kp_prev = kp;
   3665        kp = kp->ke_next;
   3666      }
   3667    }
   3668  }
   3669  hash_unlock(ht);
   3670 }
   3671 
   3672 // Clear a whole keyword table.
   3673 static void clear_keywtab(hashtab_T *ht)
   3674 {
   3675  keyentry_T *kp_next;
   3676 
   3677  int todo = (int)ht->ht_used;
   3678  for (hashitem_T *hi = ht->ht_array; todo > 0; hi++) {
   3679    if (!HASHITEM_EMPTY(hi)) {
   3680      todo--;
   3681      for (keyentry_T *kp = HI2KE(hi); kp != NULL; kp = kp_next) {
   3682        kp_next = kp->ke_next;
   3683        xfree(kp->next_list);
   3684        xfree(kp->k_syn.cont_in_list);
   3685        xfree(kp);
   3686      }
   3687    }
   3688  }
   3689  hash_clear(ht);
   3690  hash_init(ht);
   3691 }
   3692 
   3693 /// Add a keyword to the list of keywords.
   3694 ///
   3695 /// @param name name of keyword
   3696 /// @param id group ID for this keyword
   3697 /// @param flags flags for this keyword
   3698 /// @param cont_in_list containedin for this keyword
   3699 /// @param next_list nextgroup for this keyword
   3700 static void add_keyword(char *const name, size_t namelen, const int id, const int flags,
   3701                        int16_t *const cont_in_list, int16_t *const next_list,
   3702                        const int conceal_char)
   3703 {
   3704  char name_folded[MAXKEYWLEN + 1];
   3705  const char *name_ic;
   3706  size_t name_iclen;
   3707  if (curwin->w_s->b_syn_ic) {
   3708    name_ic = str_foldcase(name, (int)namelen, name_folded, MAXKEYWLEN + 1);
   3709    name_iclen = strlen(name_ic);
   3710  } else {
   3711    name_ic = name;
   3712    name_iclen = namelen;
   3713  }
   3714 
   3715  keyentry_T *const kp = xmalloc(offsetof(keyentry_T, keyword) + name_iclen + 1);
   3716  STRCPY(kp->keyword, name_ic);
   3717  kp->k_syn.id = (int16_t)id;
   3718  kp->k_syn.inc_tag = current_syn_inc_tag;
   3719  kp->flags = flags;
   3720  kp->k_char = conceal_char;
   3721  kp->k_syn.cont_in_list = copy_id_list(cont_in_list);
   3722  if (cont_in_list != NULL) {
   3723    curwin->w_s->b_syn_containedin = true;
   3724  }
   3725  kp->next_list = copy_id_list(next_list);
   3726 
   3727  const hash_T hash = hash_hash(kp->keyword);
   3728  hashtab_T *const ht = (curwin->w_s->b_syn_ic)
   3729                        ? &curwin->w_s->b_keywtab_ic
   3730                        : &curwin->w_s->b_keywtab;
   3731  hashitem_T *const hi = hash_lookup(ht, kp->keyword,
   3732                                     strlen(kp->keyword), hash);
   3733 
   3734  // even though it looks like only the kp->keyword member is
   3735  // being used here, vim uses some pointer trickery to get the original
   3736  // struct again later by using knowledge of the offset of the keyword
   3737  // field in the struct. See the definition of the HI2KE macro.
   3738  if (HASHITEM_EMPTY(hi)) {
   3739    // new keyword, add to hashtable
   3740    kp->ke_next = NULL;
   3741    hash_add_item(ht, hi, kp->keyword, hash);
   3742  } else {
   3743    // keyword already exists, prepend to list
   3744    kp->ke_next = HI2KE(hi);
   3745    hi->hi_key = KE2HIKEY(kp);
   3746  }
   3747 }
   3748 
   3749 /// Get the start and end of the group name argument.
   3750 ///
   3751 /// @param arg       start of the argument
   3752 /// @param name_end  pointer to end of the name
   3753 ///
   3754 /// @return          a pointer to the first argument.
   3755 ///                  Return NULL if the end of the command was found instead of further args.
   3756 static char *get_group_name(char *arg, char **name_end)
   3757 {
   3758  *name_end = skiptowhite(arg);
   3759  char *rest = skipwhite(*name_end);
   3760 
   3761  // Check if there are enough arguments.  The first argument may be a
   3762  // pattern, where '|' is allowed, so only check for NUL.
   3763  if (ends_excmd(*arg) || *rest == NUL) {
   3764    return NULL;
   3765  }
   3766  return rest;
   3767 }
   3768 
   3769 /// Check for syntax command option arguments.
   3770 /// This can be called at any place in the list of arguments, and just picks
   3771 /// out the arguments that are known.  Can be called several times in a row to
   3772 /// collect all options in between other arguments.
   3773 ///
   3774 /// @param arg   next argument to be checked
   3775 /// @param opt   various things
   3776 /// @param skip  true if skipping over command
   3777 ///
   3778 /// @return      a pointer to the next argument (which isn't an option).
   3779 ///              Return NULL for any error;
   3780 static char *get_syn_options(char *arg, syn_opt_arg_T *opt, int *conceal_char, int skip)
   3781 {
   3782  int len = 0;
   3783  int fidx;
   3784  static const struct flag {
   3785    char *name;
   3786    int argtype;
   3787    int flags;
   3788  } flagtab[] = { { "cCoOnNtTaAiInNeEdD",      0,      HL_CONTAINED },
   3789                  { "oOnNeElLiInNeE",          0,      HL_ONELINE },
   3790                  { "kKeEeEpPeEnNdD",          0,      HL_KEEPEND },
   3791                  { "eExXtTeEnNdD",            0,      HL_EXTEND },
   3792                  { "eExXcClLuUdDeEnNlL",      0,      HL_EXCLUDENL },
   3793                  { "tTrRaAnNsSpPaArReEnNtT",  0,      HL_TRANSP },
   3794                  { "sSkKiIpPnNlL",            0,      HL_SKIPNL },
   3795                  { "sSkKiIpPwWhHiItTeE",      0,      HL_SKIPWHITE },
   3796                  { "sSkKiIpPeEmMpPtTyY",      0,      HL_SKIPEMPTY },
   3797                  { "gGrRoOuUpPhHeErReE",      0,      HL_SYNC_HERE },
   3798                  { "gGrRoOuUpPtThHeErReE",    0,      HL_SYNC_THERE },
   3799                  { "dDiIsSpPlLaAyY",          0,      HL_DISPLAY },
   3800                  { "fFoOlLdD",                0,      HL_FOLD },
   3801                  { "cCoOnNcCeEaAlL",          0,      HL_CONCEAL },
   3802                  { "cCoOnNcCeEaAlLeEnNdDsS",  0,      HL_CONCEALENDS },
   3803                  { "cCcChHaArR",              11,     0 },
   3804                  { "cCoOnNtTaAiInNsS",        1,      0 },
   3805                  { "cCoOnNtTaAiInNeEdDiInN",  2,      0 },
   3806                  { "nNeExXtTgGrRoOuUpP",      3,      0 }, };
   3807  static const char *const first_letters = "cCoOkKeEtTsSgGdDfFnN";
   3808 
   3809  if (arg == NULL) {            // already detected error
   3810    return NULL;
   3811  }
   3812 
   3813  if (curwin->w_s->b_syn_conceal) {
   3814    opt->flags |= HL_CONCEAL;
   3815  }
   3816 
   3817  while (true) {
   3818    // This is used very often when a large number of keywords is defined.
   3819    // Need to skip quickly when no option name is found.
   3820    // Also avoid tolower(), it's slow.
   3821    if (strchr(first_letters, *arg) == NULL) {
   3822      break;
   3823    }
   3824 
   3825    for (fidx = ARRAY_SIZE(flagtab); --fidx >= 0;) {
   3826      char *p = flagtab[fidx].name;
   3827      int i;
   3828      for (i = 0, len = 0; p[i] != NUL; i += 2, len++) {
   3829        if (arg[len] != p[i] && arg[len] != p[i + 1]) {
   3830          break;
   3831        }
   3832      }
   3833      if (p[i] == NUL && (ascii_iswhite(arg[len])
   3834                          || (flagtab[fidx].argtype > 0
   3835                              ? arg[len] == '='
   3836                              : ends_excmd(arg[len])))) {
   3837        if (opt->keyword
   3838            && (flagtab[fidx].flags == HL_DISPLAY
   3839                || flagtab[fidx].flags == HL_FOLD
   3840                || flagtab[fidx].flags == HL_EXTEND)) {
   3841          // treat "display", "fold" and "extend" as a keyword
   3842          fidx = -1;
   3843        }
   3844        break;
   3845      }
   3846    }
   3847    if (fidx < 0) {         // no match found
   3848      break;
   3849    }
   3850 
   3851    if (flagtab[fidx].argtype == 1) {
   3852      if (!opt->has_cont_list) {
   3853        emsg(_(e_contains_argument_not_accepted_here));
   3854        return NULL;
   3855      }
   3856      if (get_id_list(&arg, 8, &opt->cont_list, skip) == FAIL) {
   3857        return NULL;
   3858      }
   3859    } else if (flagtab[fidx].argtype == 2) {
   3860      if (get_id_list(&arg, 11, &opt->cont_in_list, skip) == FAIL) {
   3861        return NULL;
   3862      }
   3863    } else if (flagtab[fidx].argtype == 3) {
   3864      if (get_id_list(&arg, 9, &opt->next_list, skip) == FAIL) {
   3865        return NULL;
   3866      }
   3867    } else if (flagtab[fidx].argtype == 11 && arg[5] == '=') {
   3868      // cchar=?
   3869      *conceal_char = utf_ptr2char(arg + 6);
   3870      arg += utfc_ptr2len(arg + 6) - 1;
   3871      if (!vim_isprintc(*conceal_char)) {
   3872        emsg(_(e_invalid_cchar_value));
   3873        return NULL;
   3874      }
   3875      arg = skipwhite(arg + 7);
   3876    } else {
   3877      opt->flags |= flagtab[fidx].flags;
   3878      arg = skipwhite(arg + len);
   3879 
   3880      if (flagtab[fidx].flags == HL_SYNC_HERE
   3881          || flagtab[fidx].flags == HL_SYNC_THERE) {
   3882        if (opt->sync_idx == NULL) {
   3883          emsg(_("E393: group[t]here not accepted here"));
   3884          return NULL;
   3885        }
   3886        char *gname_start = arg;
   3887        arg = skiptowhite(arg);
   3888        if (gname_start == arg) {
   3889          return NULL;
   3890        }
   3891        char *gname = xstrnsave(gname_start, (size_t)(arg - gname_start));
   3892        if (strcmp(gname, "NONE") == 0) {
   3893          *opt->sync_idx = NONE_IDX;
   3894        } else {
   3895          int syn_id = syn_name2id(gname);
   3896          int i;
   3897          for (i = curwin->w_s->b_syn_patterns.ga_len; --i >= 0;) {
   3898            if (SYN_ITEMS(curwin->w_s)[i].sp_syn.id == syn_id
   3899                && SYN_ITEMS(curwin->w_s)[i].sp_type == SPTYPE_START) {
   3900              *opt->sync_idx = i;
   3901              break;
   3902            }
   3903          }
   3904          if (i < 0) {
   3905            semsg(_("E394: Didn't find region item for %s"), gname);
   3906            xfree(gname);
   3907            return NULL;
   3908          }
   3909        }
   3910 
   3911        xfree(gname);
   3912        arg = skipwhite(arg);
   3913      } else if (flagtab[fidx].flags == HL_FOLD
   3914                 && foldmethodIsSyntax(curwin)) {
   3915        // Need to update folds later.
   3916        foldUpdateAll(curwin);
   3917      }
   3918    }
   3919  }
   3920 
   3921  return arg;
   3922 }
   3923 
   3924 // Adjustments to syntax item when declared in a ":syn include"'d file.
   3925 // Set the contained flag, and if the item is not already contained, add it
   3926 // to the specified top-level group, if any.
   3927 static void syn_incl_toplevel(int id, int *flagsp)
   3928 {
   3929  if ((*flagsp & HL_CONTAINED) || curwin->w_s->b_syn_topgrp == 0) {
   3930    return;
   3931  }
   3932  *flagsp |= HL_CONTAINED | HL_INCLUDED_TOPLEVEL;
   3933  if (curwin->w_s->b_syn_topgrp >= SYNID_CLUSTER) {
   3934    // We have to alloc this, because syn_combine_list() will free it.
   3935    int16_t *grp_list = xmalloc(2 * sizeof(*grp_list));
   3936    int tlg_id = curwin->w_s->b_syn_topgrp - SYNID_CLUSTER;
   3937 
   3938    grp_list[0] = (int16_t)id;
   3939    grp_list[1] = 0;
   3940    syn_combine_list(&SYN_CLSTR(curwin->w_s)[tlg_id].scl_list, &grp_list,
   3941                     CLUSTER_ADD);
   3942  }
   3943 }
   3944 
   3945 // Handle ":syntax include [@{group-name}] filename" command.
   3946 static void syn_cmd_include(exarg_T *eap, int syncing)
   3947 {
   3948  char *arg = eap->arg;
   3949  int sgl_id = 1;
   3950  char *group_name_end;
   3951  const char *errormsg = NULL;
   3952  bool source = false;
   3953 
   3954  eap->nextcmd = find_nextcmd(arg);
   3955  if (eap->skip) {
   3956    return;
   3957  }
   3958 
   3959  if (arg[0] == '@') {
   3960    arg++;
   3961    char *rest = get_group_name(arg, &group_name_end);
   3962    if (rest == NULL) {
   3963      emsg(_("E397: Filename required"));
   3964      return;
   3965    }
   3966    sgl_id = syn_check_cluster(arg, (int)(group_name_end - arg));
   3967    if (sgl_id == 0) {
   3968      return;
   3969    }
   3970    // separate_nextcmd() and expand_filename() depend on this
   3971    eap->arg = rest;
   3972  }
   3973 
   3974  // Everything that's left, up to the next command, should be the
   3975  // filename to include.
   3976  eap->argt |= (EX_XFILE | EX_NOSPC);
   3977  separate_nextcmd(eap);
   3978  if (*eap->arg == '<' || *eap->arg == '$' || path_is_absolute(eap->arg)) {
   3979    // For an absolute path, "$VIM/..." or "<sfile>.." we ":source" the
   3980    // file.  Need to expand the file name first.  In other cases
   3981    // ":runtime!" is used.
   3982    source = true;
   3983    if (expand_filename(eap, syn_cmdlinep, &errormsg) == FAIL) {
   3984      if (errormsg != NULL) {
   3985        emsg(errormsg);
   3986      }
   3987      return;
   3988    }
   3989  }
   3990 
   3991  // Save and restore the existing top-level grouplist id and ":syn
   3992  // include" tag around the actual inclusion.
   3993  if (running_syn_inc_tag >= MAX_SYN_INC_TAG) {
   3994    emsg(_("E847: Too many syntax includes"));
   3995    return;
   3996  }
   3997  int prev_syn_inc_tag = current_syn_inc_tag;
   3998  current_syn_inc_tag = ++running_syn_inc_tag;
   3999  int prev_toplvl_grp = curwin->w_s->b_syn_topgrp;
   4000  curwin->w_s->b_syn_topgrp = sgl_id;
   4001  if (source
   4002      ? do_source(eap->arg, false, DOSO_NONE, NULL) == FAIL
   4003      : source_runtime(eap->arg, DIP_ALL) == FAIL) {
   4004    semsg(_(e_notopen), eap->arg);
   4005  }
   4006  curwin->w_s->b_syn_topgrp = prev_toplvl_grp;
   4007  current_syn_inc_tag = prev_syn_inc_tag;
   4008 }
   4009 
   4010 // Handle ":syntax keyword {group-name} [{option}] keyword .." command.
   4011 static void syn_cmd_keyword(exarg_T *eap, int syncing)
   4012 {
   4013  char *arg = eap->arg;
   4014  char *group_name_end;
   4015  int syn_id;
   4016  char *keyword_copy = NULL;
   4017  syn_opt_arg_T syn_opt_arg;
   4018  int conceal_char = NUL;
   4019 
   4020  char *rest = get_group_name(arg, &group_name_end);
   4021 
   4022  if (rest != NULL) {
   4023    if (eap->skip) {
   4024      syn_id = -1;
   4025    } else {
   4026      syn_id = syn_check_group(arg, (size_t)(group_name_end - arg));
   4027    }
   4028    if (syn_id != 0) {
   4029      // Allocate a buffer, for removing backslashes in the keyword.
   4030      keyword_copy = xmalloc(strlen(rest) + 1);
   4031    }
   4032    if (keyword_copy != NULL) {
   4033      syn_opt_arg.flags = 0;
   4034      syn_opt_arg.keyword = true;
   4035      syn_opt_arg.sync_idx = NULL;
   4036      syn_opt_arg.has_cont_list = false;
   4037      syn_opt_arg.cont_in_list = NULL;
   4038      syn_opt_arg.next_list = NULL;
   4039 
   4040      // The options given apply to ALL keywords, so all options must be
   4041      // found before keywords can be created.
   4042      // 1: collect the options and copy the keywords to keyword_copy.
   4043      int cnt = 0;
   4044      char *p = keyword_copy;
   4045      for (; rest != NULL && !ends_excmd(*rest); rest = skipwhite(rest)) {
   4046        rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip);
   4047        if (rest == NULL || ends_excmd(*rest)) {
   4048          break;
   4049        }
   4050        // Copy the keyword, removing backslashes, and add a NUL.
   4051        while (*rest != NUL && !ascii_iswhite(*rest)) {
   4052          if (*rest == '\\' && rest[1] != NUL) {
   4053            rest++;
   4054          }
   4055          *p++ = *rest++;
   4056        }
   4057        *p++ = NUL;
   4058        cnt++;
   4059      }
   4060 
   4061      if (!eap->skip) {
   4062        // Adjust flags for use of ":syn include".
   4063        syn_incl_toplevel(syn_id, &syn_opt_arg.flags);
   4064 
   4065        // 2: Add an entry for each keyword.
   4066        size_t kwlen = 0;
   4067        for (char *kw = keyword_copy; --cnt >= 0; kw += kwlen + 1) {
   4068          for (p = vim_strchr(kw, '[');;) {
   4069            if (p == NULL) {
   4070              kwlen = strlen(kw);
   4071            } else {
   4072              *p = NUL;
   4073              kwlen = (size_t)(p - kw);
   4074            }
   4075            add_keyword(kw, kwlen, syn_id, syn_opt_arg.flags,
   4076                        syn_opt_arg.cont_in_list,
   4077                        syn_opt_arg.next_list, conceal_char);
   4078            if (p == NULL) {
   4079              break;
   4080            }
   4081            if (p[1] == NUL) {
   4082              semsg(_("E789: Missing ']': %s"), kw);
   4083              goto error;
   4084            }
   4085            if (p[1] == ']') {
   4086              if (p[2] != NUL) {
   4087                semsg(_(e_trailing_char_after_rsb_str_str), kw, &p[2]);
   4088                goto error;
   4089              }
   4090              kw = p + 1;
   4091              kwlen = 1;
   4092              break;   // skip over the "]"
   4093            }
   4094            const int l = utfc_ptr2len(p + 1);
   4095 
   4096            memmove(p, p + 1, (size_t)l);
   4097            p += l;
   4098          }
   4099        }
   4100      }
   4101 
   4102 error:
   4103      xfree(keyword_copy);
   4104      xfree(syn_opt_arg.cont_in_list);
   4105      xfree(syn_opt_arg.next_list);
   4106    }
   4107  }
   4108 
   4109  if (rest != NULL) {
   4110    eap->nextcmd = check_nextcmd(rest);
   4111  } else {
   4112    semsg(_(e_invarg2), arg);
   4113  }
   4114 
   4115  redraw_curbuf_later(UPD_SOME_VALID);
   4116  syn_stack_free_all(curwin->w_s);              // Need to recompute all syntax.
   4117 }
   4118 
   4119 /// Handle ":syntax match {name} [{options}] {pattern} [{options}]".
   4120 ///
   4121 /// Also ":syntax sync match {name} [[grouphere | groupthere] {group-name}] .."
   4122 ///
   4123 /// @param syncing  true for ":syntax sync match .. "
   4124 static void syn_cmd_match(exarg_T *eap, int syncing)
   4125 {
   4126  char *arg = eap->arg;
   4127  char *group_name_end;
   4128  synpat_T item;                // the item found in the line
   4129  int syn_id;
   4130  syn_opt_arg_T syn_opt_arg;
   4131  int sync_idx = 0;
   4132  int conceal_char = NUL;
   4133 
   4134  // Isolate the group name, check for validity
   4135  char *rest = get_group_name(arg, &group_name_end);
   4136 
   4137  // Get options before the pattern
   4138  syn_opt_arg.flags = 0;
   4139  syn_opt_arg.keyword = false;
   4140  syn_opt_arg.sync_idx = syncing ? &sync_idx : NULL;
   4141  syn_opt_arg.has_cont_list = true;
   4142  syn_opt_arg.cont_list = NULL;
   4143  syn_opt_arg.cont_in_list = NULL;
   4144  syn_opt_arg.next_list = NULL;
   4145  rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip);
   4146 
   4147  // get the pattern.
   4148  init_syn_patterns();
   4149  CLEAR_FIELD(item);
   4150  rest = get_syn_pattern(rest, &item);
   4151  if (vim_regcomp_had_eol() && !(syn_opt_arg.flags & HL_EXCLUDENL)) {
   4152    syn_opt_arg.flags |= HL_HAS_EOL;
   4153  }
   4154 
   4155  // Get options after the pattern
   4156  rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip);
   4157 
   4158  if (rest != NULL) {           // all arguments are valid
   4159    // Check for trailing command and illegal trailing arguments.
   4160    eap->nextcmd = check_nextcmd(rest);
   4161    if (!ends_excmd(*rest) || eap->skip) {
   4162      rest = NULL;
   4163    } else {
   4164      if ((syn_id = syn_check_group(arg, (size_t)(group_name_end - arg))) != 0) {
   4165        syn_incl_toplevel(syn_id, &syn_opt_arg.flags);
   4166        // Store the pattern in the syn_items list
   4167        synpat_T *spp = GA_APPEND_VIA_PTR(synpat_T,
   4168                                          &curwin->w_s->b_syn_patterns);
   4169        *spp = item;
   4170        spp->sp_syncing = syncing;
   4171        spp->sp_type = SPTYPE_MATCH;
   4172        spp->sp_syn.id = (int16_t)syn_id;
   4173        spp->sp_syn.inc_tag = current_syn_inc_tag;
   4174        spp->sp_flags = syn_opt_arg.flags;
   4175        spp->sp_sync_idx = sync_idx;
   4176        spp->sp_cont_list = syn_opt_arg.cont_list;
   4177        spp->sp_syn.cont_in_list = syn_opt_arg.cont_in_list;
   4178        spp->sp_cchar = conceal_char;
   4179        if (syn_opt_arg.cont_in_list != NULL) {
   4180          curwin->w_s->b_syn_containedin = true;
   4181        }
   4182        spp->sp_next_list = syn_opt_arg.next_list;
   4183 
   4184        // remember that we found a match for syncing on
   4185        if (syn_opt_arg.flags & (HL_SYNC_HERE|HL_SYNC_THERE)) {
   4186          curwin->w_s->b_syn_sync_flags |= SF_MATCH;
   4187        }
   4188        if (syn_opt_arg.flags & HL_FOLD) {
   4189          curwin->w_s->b_syn_folditems++;
   4190        }
   4191 
   4192        redraw_curbuf_later(UPD_SOME_VALID);
   4193        syn_stack_free_all(curwin->w_s);          // Need to recompute all syntax.
   4194        return;           // don't free the progs and patterns now
   4195      }
   4196    }
   4197  }
   4198 
   4199  // Something failed, free the allocated memory.
   4200  vim_regfree(item.sp_prog);
   4201  xfree(item.sp_pattern);
   4202  xfree(syn_opt_arg.cont_list);
   4203  xfree(syn_opt_arg.cont_in_list);
   4204  xfree(syn_opt_arg.next_list);
   4205 
   4206  if (rest == NULL) {
   4207    semsg(_(e_invarg2), arg);
   4208  }
   4209 }
   4210 
   4211 /// Handle ":syntax region {group-name} [matchgroup={group-name}]
   4212 ///              start {start} .. [skip {skip}] end {end} .. [{options}]".
   4213 ///
   4214 /// @param syncing  true for ":syntax sync region .."
   4215 static void syn_cmd_region(exarg_T *eap, int syncing)
   4216 {
   4217  char *arg = eap->arg;
   4218  char *group_name_end;
   4219  char *rest;                    // next arg, NULL on error
   4220  char *key_end;
   4221  char *key = NULL;
   4222  int item;
   4223 #define ITEM_START          0
   4224 #define ITEM_SKIP           1
   4225 #define ITEM_END            2
   4226 #define ITEM_MATCHGROUP     3
   4227  struct pat_ptr {
   4228    synpat_T *pp_synp;                   // pointer to syn_pattern
   4229    int pp_matchgroup_id;                       // matchgroup ID
   4230    struct pat_ptr *pp_next;                   // pointer to next pat_ptr
   4231  }                   *(pat_ptrs[3]);
   4232  // patterns found in the line
   4233  struct pat_ptr *ppp;
   4234  struct pat_ptr *ppp_next;
   4235  int pat_count = 0;                            // nr of syn_patterns found
   4236  int syn_id;
   4237  int matchgroup_id = 0;
   4238  bool not_enough = false;                      // not enough arguments
   4239  bool illegal = false;                         // illegal arguments
   4240  bool success = false;
   4241  syn_opt_arg_T syn_opt_arg;
   4242  int conceal_char = NUL;
   4243 
   4244  // Isolate the group name, check for validity
   4245  rest = get_group_name(arg, &group_name_end);
   4246 
   4247  pat_ptrs[0] = NULL;
   4248  pat_ptrs[1] = NULL;
   4249  pat_ptrs[2] = NULL;
   4250 
   4251  init_syn_patterns();
   4252 
   4253  syn_opt_arg.flags = 0;
   4254  syn_opt_arg.keyword = false;
   4255  syn_opt_arg.sync_idx = NULL;
   4256  syn_opt_arg.has_cont_list = true;
   4257  syn_opt_arg.cont_list = NULL;
   4258  syn_opt_arg.cont_in_list = NULL;
   4259  syn_opt_arg.next_list = NULL;
   4260 
   4261  // get the options, patterns and matchgroup.
   4262  while (rest != NULL && !ends_excmd(*rest)) {
   4263    // Check for option arguments
   4264    rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip);
   4265    if (rest == NULL || ends_excmd(*rest)) {
   4266      break;
   4267    }
   4268 
   4269    // must be a pattern or matchgroup then
   4270    key_end = rest;
   4271    while (*key_end && !ascii_iswhite(*key_end) && *key_end != '=') {
   4272      key_end++;
   4273    }
   4274    xfree(key);
   4275    key = vim_strnsave_up(rest, (size_t)(key_end - rest));
   4276    if (strcmp(key, "MATCHGROUP") == 0) {
   4277      item = ITEM_MATCHGROUP;
   4278    } else if (strcmp(key, "START") == 0) {
   4279      item = ITEM_START;
   4280    } else if (strcmp(key, "END") == 0) {
   4281      item = ITEM_END;
   4282    } else if (strcmp(key, "SKIP") == 0) {
   4283      if (pat_ptrs[ITEM_SKIP] != NULL) {  // One skip pattern allowed.
   4284        illegal = true;
   4285        break;
   4286      }
   4287      item = ITEM_SKIP;
   4288    } else {
   4289      break;
   4290    }
   4291    rest = skipwhite(key_end);
   4292    if (*rest != '=') {
   4293      rest = NULL;
   4294      semsg(_("E398: Missing '=': %s"), arg);
   4295      break;
   4296    }
   4297    rest = skipwhite(rest + 1);
   4298    if (*rest == NUL) {
   4299      not_enough = true;
   4300      break;
   4301    }
   4302 
   4303    if (item == ITEM_MATCHGROUP) {
   4304      char *p = skiptowhite(rest);
   4305      if ((p - rest == 4 && strncmp(rest, "NONE", 4) == 0) || eap->skip) {
   4306        matchgroup_id = 0;
   4307      } else {
   4308        matchgroup_id = syn_check_group(rest, (size_t)(p - rest));
   4309        if (matchgroup_id == 0) {
   4310          illegal = true;
   4311          break;
   4312        }
   4313      }
   4314      rest = skipwhite(p);
   4315    } else {
   4316      // Allocate room for a syn_pattern, and link it in the list of
   4317      // syn_patterns for this item, at the start (because the list is
   4318      // used from end to start).
   4319      ppp = xmalloc(sizeof(struct pat_ptr));
   4320      ppp->pp_next = pat_ptrs[item];
   4321      pat_ptrs[item] = ppp;
   4322      ppp->pp_synp = xcalloc(1, sizeof(synpat_T));
   4323 
   4324      // Get the syntax pattern and the following offset(s).
   4325 
   4326      // Enable the appropriate \z specials.
   4327      if (item == ITEM_START) {
   4328        reg_do_extmatch = REX_SET;
   4329      } else {
   4330        assert(item == ITEM_SKIP || item == ITEM_END);
   4331        reg_do_extmatch = REX_USE;
   4332      }
   4333      rest = get_syn_pattern(rest, ppp->pp_synp);
   4334      reg_do_extmatch = 0;
   4335      if (item == ITEM_END && vim_regcomp_had_eol()
   4336          && !(syn_opt_arg.flags & HL_EXCLUDENL)) {
   4337        ppp->pp_synp->sp_flags |= HL_HAS_EOL;
   4338      }
   4339      ppp->pp_matchgroup_id = matchgroup_id;
   4340      pat_count++;
   4341    }
   4342  }
   4343  xfree(key);
   4344  if (illegal || not_enough) {
   4345    rest = NULL;
   4346  }
   4347 
   4348  // Must have a "start" and "end" pattern.
   4349  if (rest != NULL && (pat_ptrs[ITEM_START] == NULL
   4350                       || pat_ptrs[ITEM_END] == NULL)) {
   4351    not_enough = true;
   4352    rest = NULL;
   4353  }
   4354 
   4355  if (rest != NULL) {
   4356    // Check for trailing garbage or command.
   4357    // If OK, add the item.
   4358    eap->nextcmd = check_nextcmd(rest);
   4359    if (!ends_excmd(*rest) || eap->skip) {
   4360      rest = NULL;
   4361    } else {
   4362      ga_grow(&(curwin->w_s->b_syn_patterns), pat_count);
   4363      if ((syn_id = syn_check_group(arg, (size_t)(group_name_end - arg))) != 0) {
   4364        syn_incl_toplevel(syn_id, &syn_opt_arg.flags);
   4365        // Store the start/skip/end in the syn_items list
   4366        int idx = curwin->w_s->b_syn_patterns.ga_len;
   4367        for (item = ITEM_START; item <= ITEM_END; item++) {
   4368          for (ppp = pat_ptrs[item]; ppp != NULL; ppp = ppp->pp_next) {
   4369            SYN_ITEMS(curwin->w_s)[idx] = *(ppp->pp_synp);
   4370            SYN_ITEMS(curwin->w_s)[idx].sp_syncing = syncing;
   4371            SYN_ITEMS(curwin->w_s)[idx].sp_type =
   4372              (item == ITEM_START) ? SPTYPE_START
   4373                                   : (item == ITEM_SKIP) ? SPTYPE_SKIP : SPTYPE_END;
   4374            SYN_ITEMS(curwin->w_s)[idx].sp_flags |= syn_opt_arg.flags;
   4375            SYN_ITEMS(curwin->w_s)[idx].sp_syn.id = (int16_t)syn_id;
   4376            SYN_ITEMS(curwin->w_s)[idx].sp_syn.inc_tag =
   4377              current_syn_inc_tag;
   4378            SYN_ITEMS(curwin->w_s)[idx].sp_syn_match_id = (int16_t)ppp->pp_matchgroup_id;
   4379            SYN_ITEMS(curwin->w_s)[idx].sp_cchar = conceal_char;
   4380            if (item == ITEM_START) {
   4381              SYN_ITEMS(curwin->w_s)[idx].sp_cont_list =
   4382                syn_opt_arg.cont_list;
   4383              SYN_ITEMS(curwin->w_s)[idx].sp_syn.cont_in_list =
   4384                syn_opt_arg.cont_in_list;
   4385              if (syn_opt_arg.cont_in_list != NULL) {
   4386                curwin->w_s->b_syn_containedin = true;
   4387              }
   4388              SYN_ITEMS(curwin->w_s)[idx].sp_next_list =
   4389                syn_opt_arg.next_list;
   4390            }
   4391            curwin->w_s->b_syn_patterns.ga_len++;
   4392            idx++;
   4393            if (syn_opt_arg.flags & HL_FOLD) {
   4394              curwin->w_s->b_syn_folditems++;
   4395            }
   4396          }
   4397        }
   4398 
   4399        redraw_curbuf_later(UPD_SOME_VALID);
   4400        syn_stack_free_all(curwin->w_s);  // Need to recompute all syntax.
   4401        success = true;                   // don't free the progs and patterns now
   4402      }
   4403    }
   4404  }
   4405 
   4406  // Free the allocated memory.
   4407  for (item = ITEM_START; item <= ITEM_END; item++) {
   4408    for (ppp = pat_ptrs[item]; ppp != NULL; ppp = ppp_next) {
   4409      if (!success && ppp->pp_synp != NULL) {
   4410        vim_regfree(ppp->pp_synp->sp_prog);
   4411        xfree(ppp->pp_synp->sp_pattern);
   4412      }
   4413      xfree(ppp->pp_synp);
   4414      ppp_next = ppp->pp_next;
   4415      xfree(ppp);
   4416    }
   4417  }
   4418 
   4419  if (!success) {
   4420    xfree(syn_opt_arg.cont_list);
   4421    xfree(syn_opt_arg.cont_in_list);
   4422    xfree(syn_opt_arg.next_list);
   4423    if (not_enough) {
   4424      semsg(_("E399: Not enough arguments: syntax region %s"), arg);
   4425    } else if (illegal || rest == NULL) {
   4426      semsg(_(e_invarg2), arg);
   4427    }
   4428  }
   4429 }
   4430 
   4431 // A simple syntax group ID comparison function suitable for use in qsort()
   4432 static int syn_compare_stub(const void *const v1, const void *const v2)
   4433 {
   4434  const int16_t *const s1 = v1;
   4435  const int16_t *const s2 = v2;
   4436 
   4437  return *s1 > *s2 ? 1 : *s1 < *s2 ? -1 : 0;
   4438 }
   4439 
   4440 // Combines lists of syntax clusters.
   4441 // *clstr1 and *clstr2 must both be allocated memory; they will be consumed.
   4442 static void syn_combine_list(int16_t **const clstr1, int16_t **const clstr2, const int list_op)
   4443 {
   4444  size_t count1 = 0;
   4445  size_t count2 = 0;
   4446  const int16_t *g1;
   4447  const int16_t *g2;
   4448  int16_t *clstr = NULL;
   4449 
   4450  // Handle degenerate cases.
   4451  if (*clstr2 == NULL) {
   4452    return;
   4453  }
   4454  if (*clstr1 == NULL || list_op == CLUSTER_REPLACE) {
   4455    if (list_op == CLUSTER_REPLACE) {
   4456      xfree(*clstr1);
   4457    }
   4458    if (list_op == CLUSTER_REPLACE || list_op == CLUSTER_ADD) {
   4459      *clstr1 = *clstr2;
   4460    } else {
   4461      xfree(*clstr2);
   4462    }
   4463    return;
   4464  }
   4465 
   4466  for (g1 = *clstr1; *g1; g1++) {
   4467    count1++;
   4468  }
   4469  for (g2 = *clstr2; *g2; g2++) {
   4470    count2++;
   4471  }
   4472 
   4473  // For speed purposes, sort both lists.
   4474  qsort(*clstr1, count1, sizeof(**clstr1), syn_compare_stub);
   4475  qsort(*clstr2, count2, sizeof(**clstr2), syn_compare_stub);
   4476 
   4477  // We proceed in two passes; in round 1, we count the elements to place
   4478  // in the new list, and in round 2, we allocate and populate the new
   4479  // list.  For speed, we use a mergesort-like method, adding the smaller
   4480  // of the current elements in each list to the new list.
   4481  for (int round = 1; round <= 2; round++) {
   4482    g1 = *clstr1;
   4483    g2 = *clstr2;
   4484    int count = 0;
   4485 
   4486    // First, loop through the lists until one of them is empty.
   4487    while (*g1 && *g2) {
   4488      // We always want to add from the first list.
   4489      if (*g1 < *g2) {
   4490        if (round == 2) {
   4491          clstr[count] = *g1;
   4492        }
   4493        count++;
   4494        g1++;
   4495        continue;
   4496      }
   4497      // We only want to add from the second list if we're adding the
   4498      // lists.
   4499      if (list_op == CLUSTER_ADD) {
   4500        if (round == 2) {
   4501          clstr[count] = *g2;
   4502        }
   4503        count++;
   4504      }
   4505      if (*g1 == *g2) {
   4506        g1++;
   4507      }
   4508      g2++;
   4509    }
   4510 
   4511    // Now add the leftovers from whichever list didn't get finished
   4512    // first.  As before, we only want to add from the second list if
   4513    // we're adding the lists.
   4514    for (; *g1; g1++, count++) {
   4515      if (round == 2) {
   4516        clstr[count] = *g1;
   4517      }
   4518    }
   4519    if (list_op == CLUSTER_ADD) {
   4520      for (; *g2; g2++, count++) {
   4521        if (round == 2) {
   4522          clstr[count] = *g2;
   4523        }
   4524      }
   4525    }
   4526 
   4527    if (round == 1) {
   4528      // If the group ended up empty, we don't need to allocate any
   4529      // space for it.
   4530      if (count == 0) {
   4531        clstr = NULL;
   4532        break;
   4533      }
   4534      clstr = xmalloc(((size_t)count + 1) * sizeof(*clstr));
   4535      clstr[count] = 0;
   4536    }
   4537  }
   4538 
   4539  // Finally, put the new list in place.
   4540  xfree(*clstr1);
   4541  xfree(*clstr2);
   4542  *clstr1 = clstr;
   4543 }
   4544 
   4545 /// Lookup a syntax cluster name and return its ID.
   4546 /// If it is not found, 0 is returned.
   4547 static int syn_scl_name2id(char *name)
   4548 {
   4549  // Avoid using stricmp() too much, it's slow on some systems
   4550  char *name_u = vim_strsave_up(name);
   4551  int i;
   4552  for (i = curwin->w_s->b_syn_clusters.ga_len; --i >= 0;) {
   4553    if (SYN_CLSTR(curwin->w_s)[i].scl_name_u != NULL
   4554        && strcmp(name_u, SYN_CLSTR(curwin->w_s)[i].scl_name_u) == 0) {
   4555      break;
   4556    }
   4557  }
   4558  xfree(name_u);
   4559  return i < 0 ? 0 : i + SYNID_CLUSTER;
   4560 }
   4561 
   4562 /// Like syn_scl_name2id(), but take a pointer + length argument.
   4563 static int syn_scl_namen2id(char *linep, int len)
   4564 {
   4565  char *name = xstrnsave(linep, (size_t)len);
   4566  int id = syn_scl_name2id(name);
   4567  xfree(name);
   4568 
   4569  return id;
   4570 }
   4571 
   4572 /// Find syntax cluster name in the table and return its ID.
   4573 /// The argument is a pointer to the name and the length of the name.
   4574 /// If it doesn't exist yet, a new entry is created.
   4575 ///
   4576 /// @return  0 for failure.
   4577 static int syn_check_cluster(char *pp, int len)
   4578 {
   4579  char *name = xstrnsave(pp, (size_t)len);
   4580  int id = syn_scl_name2id(name);
   4581  if (id == 0) {                        // doesn't exist yet
   4582    id = syn_add_cluster(name);
   4583  } else {
   4584    xfree(name);
   4585  }
   4586  return id;
   4587 }
   4588 
   4589 /// Add new syntax cluster and return its ID.
   4590 /// "name" must be an allocated string, it will be consumed.
   4591 ///
   4592 /// @return  0 for failure.
   4593 static int syn_add_cluster(char *name)
   4594 {
   4595  // First call for this growarray: init growing array.
   4596  if (curwin->w_s->b_syn_clusters.ga_data == NULL) {
   4597    curwin->w_s->b_syn_clusters.ga_itemsize = sizeof(syn_cluster_T);
   4598    ga_set_growsize(&curwin->w_s->b_syn_clusters, 10);
   4599  }
   4600 
   4601  int len = curwin->w_s->b_syn_clusters.ga_len;
   4602  if (len >= MAX_CLUSTER_ID) {
   4603    emsg(_("E848: Too many syntax clusters"));
   4604    xfree(name);
   4605    return 0;
   4606  }
   4607 
   4608  syn_cluster_T *scp = GA_APPEND_VIA_PTR(syn_cluster_T,
   4609                                         &curwin->w_s->b_syn_clusters);
   4610  CLEAR_POINTER(scp);
   4611  scp->scl_name = name;
   4612  scp->scl_name_u = vim_strsave_up(name);
   4613  scp->scl_list = NULL;
   4614 
   4615  if (STRICMP(name, "Spell") == 0) {
   4616    curwin->w_s->b_spell_cluster_id = len + SYNID_CLUSTER;
   4617  }
   4618  if (STRICMP(name, "NoSpell") == 0) {
   4619    curwin->w_s->b_nospell_cluster_id = len + SYNID_CLUSTER;
   4620  }
   4621 
   4622  return len + SYNID_CLUSTER;
   4623 }
   4624 
   4625 // Handle ":syntax cluster {cluster-name} [contains={groupname},..]
   4626 //              [add={groupname},..] [remove={groupname},..]".
   4627 static void syn_cmd_cluster(exarg_T *eap, int syncing)
   4628 {
   4629  char *arg = eap->arg;
   4630  char *group_name_end;
   4631  bool got_clstr = false;
   4632  int opt_len;
   4633  int list_op;
   4634 
   4635  eap->nextcmd = find_nextcmd(arg);
   4636  if (eap->skip) {
   4637    return;
   4638  }
   4639 
   4640  char *rest = get_group_name(arg, &group_name_end);
   4641 
   4642  if (rest != NULL) {
   4643    int scl_id = syn_check_cluster(arg, (int)(group_name_end - arg));
   4644    if (scl_id == 0) {
   4645      return;
   4646    }
   4647    scl_id -= SYNID_CLUSTER;
   4648 
   4649    while (true) {
   4650      if (STRNICMP(rest, "add", 3) == 0
   4651          && (ascii_iswhite(rest[3]) || rest[3] == '=')) {
   4652        opt_len = 3;
   4653        list_op = CLUSTER_ADD;
   4654      } else if (STRNICMP(rest, "remove", 6) == 0
   4655                 && (ascii_iswhite(rest[6]) || rest[6] == '=')) {
   4656        opt_len = 6;
   4657        list_op = CLUSTER_SUBTRACT;
   4658      } else if (STRNICMP(rest, "contains", 8) == 0
   4659                 && (ascii_iswhite(rest[8]) || rest[8] == '=')) {
   4660        opt_len = 8;
   4661        list_op = CLUSTER_REPLACE;
   4662      } else {
   4663        break;
   4664      }
   4665 
   4666      int16_t *clstr_list = NULL;
   4667      if (get_id_list(&rest, opt_len, &clstr_list, eap->skip) == FAIL) {
   4668        semsg(_(e_invarg2), rest);
   4669        break;
   4670      }
   4671      if (scl_id >= 0) {
   4672        syn_combine_list(&SYN_CLSTR(curwin->w_s)[scl_id].scl_list,
   4673                         &clstr_list, list_op);
   4674      } else {
   4675        xfree(clstr_list);
   4676      }
   4677      got_clstr = true;
   4678    }
   4679 
   4680    if (got_clstr) {
   4681      redraw_curbuf_later(UPD_SOME_VALID);
   4682      syn_stack_free_all(curwin->w_s);          // Need to recompute all.
   4683    }
   4684  }
   4685 
   4686  if (!got_clstr) {
   4687    emsg(_("E400: No cluster specified"));
   4688  }
   4689  if (rest == NULL || !ends_excmd(*rest)) {
   4690    semsg(_(e_invarg2), arg);
   4691  }
   4692 }
   4693 
   4694 // On first call for current buffer: Init growing array.
   4695 static void init_syn_patterns(void)
   4696 {
   4697  curwin->w_s->b_syn_patterns.ga_itemsize = sizeof(synpat_T);
   4698  ga_set_growsize(&curwin->w_s->b_syn_patterns, 10);
   4699 }
   4700 
   4701 /// Get one pattern for a ":syntax match" or ":syntax region" command.
   4702 /// Stores the pattern and program in a synpat_T.
   4703 ///
   4704 /// @return  a pointer to the next argument, or NULL in case of an error.
   4705 static char *get_syn_pattern(char *arg, synpat_T *ci)
   4706 {
   4707  int idx;
   4708 
   4709  // need at least three chars
   4710  if (arg == NULL || arg[0] == NUL || arg[1] == NUL || arg[2] == NUL) {
   4711    return NULL;
   4712  }
   4713 
   4714  char *end = skip_regexp(arg + 1, *arg, true);
   4715  if (*end != *arg) {                       // end delimiter not found
   4716    semsg(_("E401: Pattern delimiter not found: %s"), arg);
   4717    return NULL;
   4718  }
   4719  // store the pattern and compiled regexp program
   4720  ci->sp_pattern = xstrnsave(arg + 1, (size_t)(end - arg) - 1);
   4721 
   4722  // Make 'cpoptions' empty, to avoid the 'l' flag
   4723  char *cpo_save = p_cpo;
   4724  p_cpo = empty_string_option;
   4725  ci->sp_prog = vim_regcomp(ci->sp_pattern, RE_MAGIC);
   4726  p_cpo = cpo_save;
   4727 
   4728  if (ci->sp_prog == NULL) {
   4729    return NULL;
   4730  }
   4731  ci->sp_ic = curwin->w_s->b_syn_ic;
   4732  syn_clear_time(&ci->sp_time);
   4733 
   4734  // Check for a match, highlight or region offset.
   4735  end++;
   4736  do {
   4737    for (idx = SPO_COUNT; --idx >= 0;) {
   4738      if (strncmp(end, spo_name_tab[idx], 3) == 0) {
   4739        break;
   4740      }
   4741    }
   4742    if (idx >= 0) {
   4743      int *p = &(ci->sp_offsets[idx]);
   4744      if (idx != SPO_LC_OFF) {
   4745        switch (end[3]) {
   4746        case 's':
   4747          break;
   4748        case 'b':
   4749          break;
   4750        case 'e':
   4751          idx += SPO_COUNT; break;
   4752        default:
   4753          idx = -1; break;
   4754        }
   4755      }
   4756      if (idx >= 0) {
   4757        ci->sp_off_flags |= (int16_t)(1 << idx);
   4758        if (idx == SPO_LC_OFF) {            // lc=99
   4759          end += 3;
   4760          *p = getdigits_int(&end, true, 0);
   4761 
   4762          // "lc=" offset automatically sets "ms=" offset
   4763          if (!(ci->sp_off_flags & (1 << SPO_MS_OFF))) {
   4764            ci->sp_off_flags |= (1 << SPO_MS_OFF);
   4765            ci->sp_offsets[SPO_MS_OFF] = *p;
   4766          }
   4767        } else {                          // yy=x+99
   4768          end += 4;
   4769          if (*end == '+') {
   4770            end++;
   4771            *p = getdigits_int(&end, true, 0);    // positive offset
   4772          } else if (*end == '-') {
   4773            end++;
   4774            *p = -getdigits_int(&end, true, 0);   // negative offset
   4775          }
   4776        }
   4777        if (*end != ',') {
   4778          break;
   4779        }
   4780        end++;
   4781      }
   4782    }
   4783  } while (idx >= 0);
   4784 
   4785  if (!ends_excmd(*end) && !ascii_iswhite(*end)) {
   4786    semsg(_("E402: Garbage after pattern: %s"), arg);
   4787    return NULL;
   4788  }
   4789  return skipwhite(end);
   4790 }
   4791 
   4792 /// Handle ":syntax sync .." command.
   4793 static void syn_cmd_sync(exarg_T *eap, int syncing)
   4794 {
   4795  char *arg_start = eap->arg;
   4796  char *key = NULL;
   4797  bool illegal = false;
   4798  bool finished = false;
   4799 
   4800  if (ends_excmd(*arg_start)) {
   4801    syn_cmd_list(eap, true);
   4802    return;
   4803  }
   4804 
   4805  while (!ends_excmd(*arg_start)) {
   4806    char *arg_end = skiptowhite(arg_start);
   4807    char *next_arg = skipwhite(arg_end);
   4808    xfree(key);
   4809    key = vim_strnsave_up(arg_start, (size_t)(arg_end - arg_start));
   4810    if (strcmp(key, "CCOMMENT") == 0) {
   4811      if (!eap->skip) {
   4812        curwin->w_s->b_syn_sync_flags |= SF_CCOMMENT;
   4813      }
   4814      if (!ends_excmd(*next_arg)) {
   4815        arg_end = skiptowhite(next_arg);
   4816        if (!eap->skip) {
   4817          curwin->w_s->b_syn_sync_id =
   4818            (int16_t)syn_check_group(next_arg, (size_t)(arg_end - next_arg));
   4819        }
   4820        next_arg = skipwhite(arg_end);
   4821      } else if (!eap->skip) {
   4822        curwin->w_s->b_syn_sync_id = (int16_t)syn_name2id("Comment");
   4823      }
   4824    } else if (strncmp(key, "LINES", 5) == 0
   4825               || strncmp(key, "MINLINES", 8) == 0
   4826               || strncmp(key, "MAXLINES", 8) == 0
   4827               || strncmp(key, "LINEBREAKS", 10) == 0) {
   4828      if (key[4] == 'S') {
   4829        arg_end = key + 6;
   4830      } else if (key[0] == 'L') {
   4831        arg_end = key + 11;
   4832      } else {
   4833        arg_end = key + 9;
   4834      }
   4835      if (arg_end[-1] != '=' || !ascii_isdigit(*arg_end)) {
   4836        illegal = true;
   4837        break;
   4838      }
   4839      linenr_T n = getdigits_int32(&arg_end, false, 0);
   4840      if (!eap->skip) {
   4841        if (key[4] == 'B') {
   4842          curwin->w_s->b_syn_sync_linebreaks = n;
   4843        } else if (key[1] == 'A') {
   4844          curwin->w_s->b_syn_sync_maxlines = n;
   4845        } else {
   4846          curwin->w_s->b_syn_sync_minlines = n;
   4847        }
   4848      }
   4849    } else if (strcmp(key, "FROMSTART") == 0) {
   4850      if (!eap->skip) {
   4851        curwin->w_s->b_syn_sync_minlines = MAXLNUM;
   4852        curwin->w_s->b_syn_sync_maxlines = 0;
   4853      }
   4854    } else if (strcmp(key, "LINECONT") == 0) {
   4855      if (*next_arg == NUL) {  // missing pattern
   4856        illegal = true;
   4857        break;
   4858      }
   4859      if (curwin->w_s->b_syn_linecont_pat != NULL) {
   4860        emsg(_("E403: syntax sync: line continuations pattern specified twice"));
   4861        finished = true;
   4862        break;
   4863      }
   4864      arg_end = skip_regexp(next_arg + 1, *next_arg, true);
   4865      if (*arg_end != *next_arg) {          // end delimiter not found
   4866        illegal = true;
   4867        break;
   4868      }
   4869 
   4870      if (!eap->skip) {
   4871        // store the pattern and compiled regexp program
   4872        curwin->w_s->b_syn_linecont_pat =
   4873          xstrnsave(next_arg + 1, (size_t)(arg_end - next_arg) - 1);
   4874        curwin->w_s->b_syn_linecont_ic = curwin->w_s->b_syn_ic;
   4875 
   4876        // Make 'cpoptions' empty, to avoid the 'l' flag
   4877        char *cpo_save = p_cpo;
   4878        p_cpo = empty_string_option;
   4879        curwin->w_s->b_syn_linecont_prog =
   4880          vim_regcomp(curwin->w_s->b_syn_linecont_pat, RE_MAGIC);
   4881        p_cpo = cpo_save;
   4882        syn_clear_time(&curwin->w_s->b_syn_linecont_time);
   4883 
   4884        if (curwin->w_s->b_syn_linecont_prog == NULL) {
   4885          XFREE_CLEAR(curwin->w_s->b_syn_linecont_pat);
   4886          finished = true;
   4887          break;
   4888        }
   4889      }
   4890      next_arg = skipwhite(arg_end + 1);
   4891    } else {
   4892      eap->arg = next_arg;
   4893      if (strcmp(key, "MATCH") == 0) {
   4894        syn_cmd_match(eap, true);
   4895      } else if (strcmp(key, "REGION") == 0) {
   4896        syn_cmd_region(eap, true);
   4897      } else if (strcmp(key, "CLEAR") == 0) {
   4898        syn_cmd_clear(eap, true);
   4899      } else {
   4900        illegal = true;
   4901      }
   4902      finished = true;
   4903      break;
   4904    }
   4905    arg_start = next_arg;
   4906  }
   4907  xfree(key);
   4908  if (illegal) {
   4909    semsg(_("E404: Illegal arguments: %s"), arg_start);
   4910  } else if (!finished) {
   4911    eap->nextcmd = check_nextcmd(arg_start);
   4912    redraw_curbuf_later(UPD_SOME_VALID);
   4913    syn_stack_free_all(curwin->w_s);            // Need to recompute all syntax.
   4914  }
   4915 }
   4916 
   4917 /// Convert a line of highlight group names into a list of group ID numbers.
   4918 /// "arg" should point to the "contains" or "nextgroup" keyword.
   4919 /// "arg" is advanced to after the last group name.
   4920 /// Careful: the argument is modified (NULs added).
   4921 ///
   4922 /// @param keylen  length of keyword
   4923 /// @param list    where to store the resulting list, if not NULL, the list is silently skipped!
   4924 ///
   4925 /// @return        FAIL for some error, OK for success.
   4926 static int get_id_list(char **const arg, const int keylen, int16_t **const list, const bool skip)
   4927 {
   4928  char *p = NULL;
   4929  char *end;
   4930  int total_count = 0;
   4931  int16_t *retval = NULL;
   4932  regmatch_T regmatch;
   4933  int id;
   4934  bool failed = false;
   4935 
   4936  // We parse the list twice:
   4937  // round == 1: count the number of items, allocate the array.
   4938  // round == 2: fill the array with the items.
   4939  // In round 1 new groups may be added, causing the number of items to
   4940  // grow when a regexp is used.  In that case round 1 is done once again.
   4941  for (int round = 1; round <= 2; round++) {
   4942    // skip "contains"
   4943    p = skipwhite(*arg + keylen);
   4944    if (*p != '=') {
   4945      semsg(_("E405: Missing equal sign: %s"), *arg);
   4946      break;
   4947    }
   4948    p = skipwhite(p + 1);
   4949    if (ends_excmd(*p)) {
   4950      semsg(_("E406: Empty argument: %s"), *arg);
   4951      break;
   4952    }
   4953 
   4954    // parse the arguments after "contains"
   4955    int count = 0;
   4956    do {
   4957      for (end = p; *end && !ascii_iswhite(*end) && *end != ','; end++) {}
   4958      char *const name = xmalloc((size_t)(end - p) + 3);   // leave room for "^$"
   4959      xmemcpyz(name + 1, p, (size_t)(end - p));
   4960      if (strcmp(name + 1, "ALLBUT") == 0
   4961          || strcmp(name + 1, "ALL") == 0
   4962          || strcmp(name + 1, "TOP") == 0
   4963          || strcmp(name + 1, "CONTAINED") == 0) {
   4964        if (TOUPPER_ASC(**arg) != 'C') {
   4965          semsg(_("E407: %s not allowed here"), name + 1);
   4966          failed = true;
   4967          xfree(name);
   4968          break;
   4969        }
   4970        if (count != 0) {
   4971          semsg(_("E408: %s must be first in contains list"),
   4972                name + 1);
   4973          failed = true;
   4974          xfree(name);
   4975          break;
   4976        }
   4977        if (name[1] == 'A') {
   4978          id = SYNID_ALLBUT;
   4979        } else if (name[1] == 'T') {
   4980          id = SYNID_TOP;
   4981        } else {
   4982          id = SYNID_CONTAINED;
   4983        }
   4984        id += current_syn_inc_tag;
   4985      } else if (name[1] == '@') {
   4986        if (skip) {
   4987          id = -1;
   4988        } else {
   4989          id = syn_check_cluster(name + 2, (int)(end - p - 1));
   4990        }
   4991      } else {
   4992        // Handle full group name.
   4993        if (strpbrk(name + 1, "\\.*^$~[") == NULL) {
   4994          id = syn_check_group((name + 1), (size_t)(end - p));
   4995        } else {
   4996          // Handle match of regexp with group names.
   4997          *name = '^';
   4998          strcat(name, "$");
   4999          regmatch.regprog = vim_regcomp(name, RE_MAGIC);
   5000          if (regmatch.regprog == NULL) {
   5001            failed = true;
   5002            xfree(name);
   5003            break;
   5004          }
   5005 
   5006          regmatch.rm_ic = true;
   5007          id = 0;
   5008          for (int i = highlight_num_groups(); --i >= 0;) {
   5009            if (vim_regexec(&regmatch, highlight_group_name(i), 0)) {
   5010              if (round == 2) {
   5011                // Got more items than expected; can happen
   5012                // when adding items that match:
   5013                // "contains=a.*b,axb".
   5014                // Go back to first round.
   5015                if (count >= total_count) {
   5016                  xfree(retval);
   5017                  round = 1;
   5018                } else {
   5019                  retval[count] = (int16_t)(i + 1);
   5020                }
   5021              }
   5022              count++;
   5023              id = -1;  // Remember that we found one.
   5024            }
   5025          }
   5026          vim_regfree(regmatch.regprog);
   5027        }
   5028      }
   5029      xfree(name);
   5030      if (id == 0) {
   5031        semsg(_("E409: Unknown group name: %s"), p);
   5032        failed = true;
   5033        break;
   5034      }
   5035      if (id > 0) {
   5036        if (round == 2) {
   5037          // Got more items than expected, go back to first round.
   5038          if (count >= total_count) {
   5039            xfree(retval);
   5040            round = 1;
   5041          } else {
   5042            retval[count] = (int16_t)id;
   5043          }
   5044        }
   5045        count++;
   5046      }
   5047      p = skipwhite(end);
   5048      if (*p != ',') {
   5049        break;
   5050      }
   5051      p = skipwhite(p + 1);             // skip comma in between arguments
   5052    } while (!ends_excmd(*p));
   5053    if (failed) {
   5054      break;
   5055    }
   5056    if (round == 1) {
   5057      retval = xmalloc(((size_t)count + 1) * sizeof(*retval));
   5058      retval[count] = 0;            // zero means end of the list
   5059      total_count = count;
   5060    }
   5061  }
   5062 
   5063  *arg = p;
   5064  if (failed || retval == NULL) {
   5065    xfree(retval);
   5066    return FAIL;
   5067  }
   5068 
   5069  if (*list == NULL) {
   5070    *list = retval;
   5071  } else {
   5072    xfree(retval);           // list already found, don't overwrite it
   5073  }
   5074  return OK;
   5075 }
   5076 
   5077 // Make a copy of an ID list.
   5078 static int16_t *copy_id_list(const int16_t *const list)
   5079 {
   5080  if (list == NULL) {
   5081    return NULL;
   5082  }
   5083 
   5084  int count;
   5085  for (count = 0; list[count]; count++) {}
   5086  const size_t len = ((size_t)count + 1) * sizeof(int16_t);
   5087  int16_t *const retval = xmalloc(len);
   5088  memmove(retval, list, len);
   5089 
   5090  return retval;
   5091 }
   5092 
   5093 /// Check if syntax group "ssp" is in the ID list "list" of "cur_si".
   5094 /// "cur_si" can be NULL if not checking the "containedin" list.
   5095 /// Used to check if a syntax item is in the "contains" or "nextgroup" list of
   5096 /// the current item.
   5097 /// This function is called very often, keep it fast!!
   5098 ///
   5099 /// @param cur_si     current item or NULL
   5100 /// @param list       id list
   5101 /// @param ssp        group id and ":syn include" tag of group
   5102 /// @param flags      group flags
   5103 static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, int flags)
   5104 {
   5105  int retval;
   5106  int16_t id = ssp->id;
   5107  static int depth = 0;
   5108 
   5109  // If ssp has a "containedin" list and "cur_si" is in it, return true.
   5110  if (cur_si != NULL && ssp->cont_in_list != NULL
   5111      && !(cur_si->si_flags & HL_MATCH)) {
   5112    // Ignore transparent items without a contains argument.  Double check
   5113    // that we don't go back past the first one.
   5114    while ((cur_si->si_flags & HL_TRANS_CONT)
   5115           && cur_si > (stateitem_T *)(current_state.ga_data)) {
   5116      cur_si--;
   5117    }
   5118    // cur_si->si_idx is -1 for keywords, these never contain anything.
   5119    if (cur_si->si_idx >= 0 && in_id_list(NULL, ssp->cont_in_list,
   5120                                          &(SYN_ITEMS(syn_block)[cur_si->si_idx].sp_syn),
   5121                                          SYN_ITEMS(syn_block)[cur_si->si_idx].sp_flags)) {
   5122      return true;
   5123    }
   5124  }
   5125 
   5126  if (list == NULL) {
   5127    return false;
   5128  }
   5129 
   5130  // If list is ID_LIST_ALL, we are in a transparent item that isn't
   5131  // inside anything.  Only allow not-contained groups.
   5132  if (list == ID_LIST_ALL) {
   5133    return !(flags & HL_CONTAINED);
   5134  }
   5135 
   5136  // Is this top-level (i.e. not 'contained') in the file it was declared in?
   5137  // For included files, this is different from HL_CONTAINED, which is set
   5138  // unconditionally.
   5139  bool toplevel = !(flags & HL_CONTAINED) || (flags & HL_INCLUDED_TOPLEVEL);
   5140 
   5141  // If the first item is "ALLBUT", return true if "id" is NOT in the
   5142  // contains list.  We also require that "id" is at the same ":syn include"
   5143  // level as the list.
   5144  int16_t item = *list;
   5145  if (item >= SYNID_ALLBUT && item < SYNID_CLUSTER) {
   5146    if (item < SYNID_TOP) {
   5147      // ALL or ALLBUT: accept all groups in the same file
   5148      if (item - SYNID_ALLBUT != ssp->inc_tag) {
   5149        return false;
   5150      }
   5151    } else if (item < SYNID_CONTAINED) {
   5152      // TOP: accept all not-contained groups in the same file
   5153      if (item - SYNID_TOP != ssp->inc_tag || !toplevel) {
   5154        return false;
   5155      }
   5156    } else {
   5157      // CONTAINED: accept all contained groups in the same file
   5158      if (item - SYNID_CONTAINED != ssp->inc_tag || toplevel) {
   5159        return false;
   5160      }
   5161    }
   5162    item = *++list;
   5163    retval = false;
   5164  } else {
   5165    retval = true;
   5166  }
   5167 
   5168  // Return "retval" if id is in the contains list.
   5169  while (item != 0) {
   5170    if (item == id) {
   5171      return retval;
   5172    }
   5173    if (item >= SYNID_CLUSTER) {
   5174      int16_t *scl_list = SYN_CLSTR(syn_block)[item - SYNID_CLUSTER].scl_list;
   5175      // restrict recursiveness to 30 to avoid an endless loop for a
   5176      // cluster that includes itself (indirectly)
   5177      if (scl_list != NULL && depth < 30) {
   5178        depth++;
   5179        int r = in_id_list(NULL, scl_list, ssp, flags);
   5180        depth--;
   5181        if (r) {
   5182          return retval;
   5183        }
   5184      }
   5185    }
   5186    item = *++list;
   5187  }
   5188  return !retval;
   5189 }
   5190 
   5191 struct subcommand {
   5192  char *name;                                // subcommand name
   5193  void (*func)(exarg_T *, int);              // function to call
   5194 };
   5195 
   5196 static struct subcommand subcommands[] = {
   5197  { "case",      syn_cmd_case },
   5198  { "clear",     syn_cmd_clear },
   5199  { "cluster",   syn_cmd_cluster },
   5200  { "conceal",   syn_cmd_conceal },
   5201  { "enable",    syn_cmd_on },
   5202  { "foldlevel", syn_cmd_foldlevel },
   5203  { "include",   syn_cmd_include },
   5204  { "iskeyword", syn_cmd_iskeyword },
   5205  { "keyword",   syn_cmd_keyword },
   5206  { "list",      syn_cmd_list },
   5207  { "manual",    syn_cmd_manual },
   5208  { "match",     syn_cmd_match },
   5209  { "on",        syn_cmd_on },
   5210  { "off",       syn_cmd_off },
   5211  { "region",    syn_cmd_region },
   5212  { "reset",     syn_cmd_reset },
   5213  { "spell",     syn_cmd_spell },
   5214  { "sync",      syn_cmd_sync },
   5215  { "",          syn_cmd_list },
   5216 };
   5217 
   5218 /// ":syntax".
   5219 /// This searches the subcommands[] table for the subcommand name, and calls a
   5220 /// syntax_subcommand() function to do the rest.
   5221 void ex_syntax(exarg_T *eap)
   5222 {
   5223  char *arg = eap->arg;
   5224  char *subcmd_end;
   5225 
   5226  syn_cmdlinep = eap->cmdlinep;
   5227 
   5228  // isolate subcommand name
   5229  for (subcmd_end = arg; ASCII_ISALPHA(*subcmd_end); subcmd_end++) {}
   5230  char *const subcmd_name = xstrnsave(arg, (size_t)(subcmd_end - arg));
   5231  if (eap->skip) {  // skip error messages for all subcommands
   5232    emsg_skip++;
   5233  }
   5234  size_t i;
   5235  for (i = 0; i < ARRAY_SIZE(subcommands); i++) {
   5236    if (strcmp(subcmd_name, subcommands[i].name) == 0) {
   5237      eap->arg = skipwhite(subcmd_end);
   5238      (subcommands[i].func)(eap, false);
   5239      break;
   5240    }
   5241  }
   5242 
   5243  if (i == ARRAY_SIZE(subcommands)) {
   5244    semsg(_("E410: Invalid :syntax subcommand: %s"), subcmd_name);
   5245  }
   5246 
   5247  xfree(subcmd_name);
   5248  if (eap->skip) {
   5249    emsg_skip--;
   5250  }
   5251 }
   5252 
   5253 /// @deprecated
   5254 void ex_ownsyntax(exarg_T *eap)
   5255 {
   5256  if (curwin->w_s == &curwin->w_buffer->b_s) {
   5257    curwin->w_s = xcalloc(1, sizeof(synblock_T));
   5258    hash_init(&curwin->w_s->b_keywtab);
   5259    hash_init(&curwin->w_s->b_keywtab_ic);
   5260    // TODO(vim): Keep the spell checking as it was.
   5261    curwin->w_p_spell = false;  // No spell checking
   5262    // make sure option values are "empty_string_option" instead of NULL
   5263    clear_string_option(&curwin->w_s->b_p_spc);
   5264    clear_string_option(&curwin->w_s->b_p_spf);
   5265    clear_string_option(&curwin->w_s->b_p_spl);
   5266    clear_string_option(&curwin->w_s->b_p_spo);
   5267    clear_string_option(&curwin->w_s->b_syn_isk);
   5268  }
   5269 
   5270  // Save value of b:current_syntax.
   5271  char *old_value = get_var_value("b:current_syntax");
   5272  if (old_value != NULL) {
   5273    old_value = xstrdup(old_value);
   5274  }
   5275 
   5276  // Apply the "syntax" autocommand event, this finds and loads the syntax file.
   5277  apply_autocmds(EVENT_SYNTAX, eap->arg, curbuf->b_fname, true, curbuf);
   5278 
   5279  // Move value of b:current_syntax to w:current_syntax.
   5280  char *new_value = get_var_value("b:current_syntax");
   5281  if (new_value != NULL) {
   5282    set_internal_string_var("w:current_syntax", new_value);
   5283  }
   5284 
   5285  // Restore value of b:current_syntax.
   5286  if (old_value == NULL) {
   5287    do_unlet(S_LEN("b:current_syntax"), true);
   5288  } else {
   5289    set_internal_string_var("b:current_syntax", old_value);
   5290    xfree(old_value);
   5291  }
   5292 }
   5293 
   5294 bool syntax_present(win_T *win)
   5295 {
   5296  return win->w_s->b_syn_patterns.ga_len != 0
   5297         || win->w_s->b_syn_clusters.ga_len != 0
   5298         || win->w_s->b_keywtab.ht_used > 0
   5299         || win->w_s->b_keywtab_ic.ht_used > 0;
   5300 }
   5301 
   5302 static enum {
   5303  EXP_SUBCMD,       // expand ":syn" sub-commands
   5304  EXP_CASE,         // expand ":syn case" arguments
   5305  EXP_SPELL,        // expand ":syn spell" arguments
   5306  EXP_SYNC,         // expand ":syn sync" arguments
   5307  EXP_CLUSTER,      // expand ":syn list @cluster" arguments
   5308 } expand_what;
   5309 
   5310 // Reset include_link, include_default, include_none to 0.
   5311 // Called when we are done expanding.
   5312 void reset_expand_highlight(void)
   5313 {
   5314  include_link = include_default = include_none = 0;
   5315 }
   5316 
   5317 // Handle command line completion for :match and :echohl command: Add "None"
   5318 // as highlight group.
   5319 void set_context_in_echohl_cmd(expand_T *xp, const char *arg)
   5320 {
   5321  xp->xp_context = EXPAND_HIGHLIGHT;
   5322  xp->xp_pattern = (char *)arg;
   5323  include_none = 1;
   5324 }
   5325 
   5326 // Handle command line completion for :syntax command.
   5327 void set_context_in_syntax_cmd(expand_T *xp, const char *arg)
   5328 {
   5329  // Default: expand subcommands.
   5330  xp->xp_context = EXPAND_SYNTAX;
   5331  expand_what = EXP_SUBCMD;
   5332  xp->xp_pattern = (char *)arg;
   5333  include_link = 0;
   5334  include_default = 0;
   5335 
   5336  if (*arg == NUL) {
   5337    return;
   5338  }
   5339 
   5340  // (part of) subcommand already typed
   5341  const char *p = skiptowhite(arg);
   5342  if (*p == NUL) {
   5343    return;
   5344  }
   5345 
   5346  // past first world
   5347  xp->xp_pattern = skipwhite(p);
   5348  if (*skiptowhite(xp->xp_pattern) != NUL) {
   5349    xp->xp_context = EXPAND_NOTHING;
   5350  } else if (STRNICMP(arg, "case", p - arg) == 0) {
   5351    expand_what = EXP_CASE;
   5352  } else if (STRNICMP(arg, "spell", p - arg) == 0) {
   5353    expand_what = EXP_SPELL;
   5354  } else if (STRNICMP(arg, "sync", p - arg) == 0) {
   5355    expand_what = EXP_SYNC;
   5356  } else if (STRNICMP(arg, "list", p - arg) == 0) {
   5357    p = skipwhite(p);
   5358    if (*p == '@') {
   5359      expand_what = EXP_CLUSTER;
   5360    } else {
   5361      xp->xp_context = EXPAND_HIGHLIGHT;
   5362    }
   5363  } else if (STRNICMP(arg, "keyword", p - arg) == 0
   5364             || STRNICMP(arg, "region", p - arg) == 0
   5365             || STRNICMP(arg, "match", p - arg) == 0) {
   5366    xp->xp_context = EXPAND_HIGHLIGHT;
   5367  } else {
   5368    xp->xp_context = EXPAND_NOTHING;
   5369  }
   5370 }
   5371 
   5372 // Function given to ExpandGeneric() to obtain the list syntax names for
   5373 // expansion.
   5374 char *get_syntax_name(expand_T *xp, int idx)
   5375 {
   5376  switch (expand_what) {
   5377  case EXP_SUBCMD:
   5378    if (idx < 0 || idx >= (int)ARRAY_SIZE(subcommands)) {
   5379      return NULL;
   5380    }
   5381    return subcommands[idx].name;
   5382  case EXP_CASE: {
   5383    static char *case_args[] = { "match", "ignore", NULL };
   5384    return case_args[idx];
   5385  }
   5386  case EXP_SPELL: {
   5387    static char *spell_args[] =
   5388    { "toplevel", "notoplevel", "default", NULL };
   5389    return spell_args[idx];
   5390  }
   5391  case EXP_SYNC: {
   5392    static char *sync_args[] =
   5393    { "ccomment", "clear", "fromstart",
   5394      "linebreaks=", "linecont", "lines=", "match",
   5395      "maxlines=", "minlines=", "region", NULL };
   5396    return sync_args[idx];
   5397  }
   5398  case EXP_CLUSTER:
   5399    if (idx < curwin->w_s->b_syn_clusters.ga_len) {
   5400      vim_snprintf(xp->xp_buf, EXPAND_BUF_LEN, "@%s",
   5401                   SYN_CLSTR(curwin->w_s)[idx].scl_name);
   5402      return xp->xp_buf;
   5403    } else {
   5404      return NULL;
   5405    }
   5406  }
   5407  return NULL;
   5408 }
   5409 
   5410 /// Function called for expression evaluation: get syntax ID at file position.
   5411 ///
   5412 /// @param trans       remove transparency
   5413 /// @param spellp      return: can do spell checking
   5414 /// @param keep_state  keep state of char at "col"
   5415 int syn_get_id(win_T *wp, linenr_T lnum, colnr_T col, int trans, bool *spellp, int keep_state)
   5416 {
   5417  // When the position is not after the current position and in the same
   5418  // line of the same window with the same buffer, need to restart parsing.
   5419  if (wp != syn_win || wp->w_buffer != syn_buf || lnum != current_lnum || col < current_col) {
   5420    syntax_start(wp, lnum);
   5421  } else if (col > current_col) {
   5422    // next_match may not be correct when moving around, e.g. with the
   5423    // "skip" expression in searchpair()
   5424    next_match_idx = -1;
   5425  }
   5426 
   5427  get_syntax_attr(col, spellp, keep_state);
   5428 
   5429  return trans ? current_trans_id : current_id;
   5430 }
   5431 
   5432 // Get extra information about the syntax item.  Must be called right after
   5433 // get_syntax_attr().
   5434 // Stores the current item sequence nr in "*seqnrp".
   5435 // Returns the current flags.
   5436 int get_syntax_info(int *seqnrp)
   5437 {
   5438  *seqnrp = current_seqnr;
   5439  return current_flags;
   5440 }
   5441 
   5442 /// Get the sequence number of the concealed file position.
   5443 ///
   5444 /// @return seqnr if the file position is concealed, 0 otherwise.
   5445 int syn_get_concealed_id(win_T *wp, linenr_T lnum, colnr_T col)
   5446 {
   5447  int seqnr;
   5448 
   5449  syn_get_id(wp, lnum, col, false, NULL, false);
   5450  int syntax_flags = get_syntax_info(&seqnr);
   5451 
   5452  if (syntax_flags & HL_CONCEAL) {
   5453    return seqnr;
   5454  }
   5455  return 0;
   5456 }
   5457 
   5458 // Return conceal substitution character
   5459 int syn_get_sub_char(void)
   5460 {
   5461  return current_sub_char;
   5462 }
   5463 
   5464 // Return the syntax ID at position "i" in the current stack.
   5465 // The caller must have called syn_get_id() before to fill the stack.
   5466 // Returns -1 when "i" is out of range.
   5467 int syn_get_stack_item(int i)
   5468 {
   5469  if (i >= current_state.ga_len) {
   5470    // Need to invalidate the state, because we didn't properly finish it
   5471    // for the last character, "keep_state" was true.
   5472    invalidate_current_state();
   5473    current_col = MAXCOL;
   5474    return -1;
   5475  }
   5476  return CUR_STATE(i).si_id;
   5477 }
   5478 
   5479 static int syn_cur_foldlevel(void)
   5480 {
   5481  int level = 0;
   5482  for (int i = 0; i < current_state.ga_len; i++) {
   5483    if (CUR_STATE(i).si_flags & HL_FOLD) {
   5484      level++;
   5485    }
   5486  }
   5487  return level;
   5488 }
   5489 
   5490 /// Function called to get folding level for line "lnum" in window "wp".
   5491 int syn_get_foldlevel(win_T *wp, linenr_T lnum)
   5492 {
   5493  int level = 0;
   5494 
   5495  // Return quickly when there are no fold items at all.
   5496  if (wp->w_s->b_syn_folditems != 0
   5497      && !wp->w_s->b_syn_error
   5498      && !wp->w_s->b_syn_slow) {
   5499    syntax_start(wp, lnum);
   5500 
   5501    // Start with the fold level at the start of the line.
   5502    level = syn_cur_foldlevel();
   5503 
   5504    if (wp->w_s->b_syn_foldlevel == SYNFLD_MINIMUM) {
   5505      // Find the lowest fold level that is followed by a higher one.
   5506      int cur_level = level;
   5507      int low_level = cur_level;
   5508      while (!current_finished) {
   5509        syn_current_attr(false, false, NULL, false);
   5510        cur_level = syn_cur_foldlevel();
   5511        if (cur_level < low_level) {
   5512          low_level = cur_level;
   5513        } else if (cur_level > low_level) {
   5514          level = low_level;
   5515        }
   5516        current_col++;
   5517      }
   5518    }
   5519  }
   5520  if (level > wp->w_p_fdn) {
   5521    level = (int)wp->w_p_fdn;
   5522    if (level < 0) {
   5523      level = 0;
   5524    }
   5525  }
   5526  return level;
   5527 }
   5528 
   5529 // ":syntime".
   5530 void ex_syntime(exarg_T *eap)
   5531 {
   5532  if (strcmp(eap->arg, "on") == 0) {
   5533    syn_time_on = true;
   5534  } else if (strcmp(eap->arg, "off") == 0) {
   5535    syn_time_on = false;
   5536  } else if (strcmp(eap->arg, "clear") == 0) {
   5537    syntime_clear();
   5538  } else if (strcmp(eap->arg, "report") == 0) {
   5539    syntime_report();
   5540  } else {
   5541    semsg(_(e_invarg2), eap->arg);
   5542  }
   5543 }
   5544 
   5545 static void syn_clear_time(syn_time_T *st)
   5546 {
   5547  st->total = profile_zero();
   5548  st->slowest = profile_zero();
   5549  st->count = 0;
   5550  st->match = 0;
   5551 }
   5552 
   5553 // Clear the syntax timing for the current buffer.
   5554 static void syntime_clear(void)
   5555 {
   5556  synpat_T *spp;
   5557 
   5558  if (!syntax_present(curwin)) {
   5559    msg(_(msg_no_items), 0);
   5560    return;
   5561  }
   5562  for (int idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; idx++) {
   5563    spp = &(SYN_ITEMS(curwin->w_s)[idx]);
   5564    syn_clear_time(&spp->sp_time);
   5565  }
   5566 }
   5567 
   5568 // Function given to ExpandGeneric() to obtain the possible arguments of the
   5569 // ":syntime {on,off,clear,report}" command.
   5570 char *get_syntime_arg(expand_T *xp, int idx)
   5571 {
   5572  switch (idx) {
   5573  case 0:
   5574    return "on";
   5575  case 1:
   5576    return "off";
   5577  case 2:
   5578    return "clear";
   5579  case 3:
   5580    return "report";
   5581  }
   5582  return NULL;
   5583 }
   5584 
   5585 static int syn_compare_syntime(const void *v1, const void *v2)
   5586 {
   5587  const time_entry_T *s1 = v1;
   5588  const time_entry_T *s2 = v2;
   5589 
   5590  return profile_cmp(s1->total, s2->total);
   5591 }
   5592 
   5593 // Clear the syntax timing for the current buffer.
   5594 static void syntime_report(void)
   5595 {
   5596  if (!syntax_present(curwin)) {
   5597    msg(_(msg_no_items), 0);
   5598    return;
   5599  }
   5600 
   5601  garray_T ga;
   5602  ga_init(&ga, sizeof(time_entry_T), 50);
   5603 
   5604  proftime_T total_total = profile_zero();
   5605  int total_count = 0;
   5606  time_entry_T *p;
   5607  for (int idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; idx++) {
   5608    synpat_T *spp = &(SYN_ITEMS(curwin->w_s)[idx]);
   5609    if (spp->sp_time.count > 0) {
   5610      p = GA_APPEND_VIA_PTR(time_entry_T, &ga);
   5611      p->total = spp->sp_time.total;
   5612      total_total = profile_add(total_total, spp->sp_time.total);
   5613      p->count = spp->sp_time.count;
   5614      p->match = spp->sp_time.match;
   5615      total_count += spp->sp_time.count;
   5616      p->slowest = spp->sp_time.slowest;
   5617      proftime_T tm = profile_divide(spp->sp_time.total, spp->sp_time.count);
   5618      p->average = tm;
   5619      p->id = spp->sp_syn.id;
   5620      p->pattern = spp->sp_pattern;
   5621    }
   5622  }
   5623 
   5624  // Sort on total time. Skip if there are no items to avoid passing NULL
   5625  // pointer to qsort().
   5626  if (ga.ga_len > 1) {
   5627    qsort(ga.ga_data, (size_t)ga.ga_len, sizeof(time_entry_T),
   5628          syn_compare_syntime);
   5629  }
   5630 
   5631  msg_puts_title(_("  TOTAL      COUNT  MATCH   SLOWEST     AVERAGE   NAME               PATTERN"));
   5632  msg_puts("\n");
   5633  for (int idx = 0; idx < ga.ga_len && !got_int; idx++) {
   5634    p = ((time_entry_T *)ga.ga_data) + idx;
   5635 
   5636    msg_puts(profile_msg(p->total));
   5637    msg_puts(" ");     // make sure there is always a separating space
   5638    msg_advance(13);
   5639    msg_outnum(p->count);
   5640    msg_puts(" ");
   5641    msg_advance(20);
   5642    msg_outnum(p->match);
   5643    msg_puts(" ");
   5644    msg_advance(26);
   5645    msg_puts(profile_msg(p->slowest));
   5646    msg_puts(" ");
   5647    msg_advance(38);
   5648    msg_puts(profile_msg(p->average));
   5649    msg_puts(" ");
   5650    msg_advance(50);
   5651    msg_outtrans(highlight_group_name(p->id - 1), 0, false);
   5652    msg_puts(" ");
   5653 
   5654    msg_advance(69);
   5655    int len;
   5656    if (Columns < 80) {
   5657      len = 20;       // will wrap anyway
   5658    } else {
   5659      len = Columns - 70;
   5660    }
   5661    int patlen = (int)strlen(p->pattern);
   5662    len = MIN(len, patlen);
   5663    msg_outtrans_len(p->pattern, len, 0, false);
   5664    msg_puts("\n");
   5665  }
   5666  ga_clear(&ga);
   5667  if (!got_int) {
   5668    msg_puts("\n");
   5669    msg_puts(profile_msg(total_total));
   5670    msg_advance(13);
   5671    msg_outnum(total_count);
   5672    msg_puts("\n");
   5673  }
   5674 }