neovim

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

optionstr.c (73405B)


      1 #include <assert.h>
      2 #include <stdbool.h>
      3 #include <stdint.h>
      4 #include <string.h>
      5 
      6 #include "nvim/api/private/defs.h"
      7 #include "nvim/api/win_config.h"
      8 #include "nvim/ascii_defs.h"
      9 #include "nvim/autocmd.h"
     10 #include "nvim/buffer_defs.h"
     11 #include "nvim/charset.h"
     12 #include "nvim/cmdexpand.h"
     13 #include "nvim/cmdexpand_defs.h"
     14 #include "nvim/cursor.h"
     15 #include "nvim/cursor_shape.h"
     16 #include "nvim/decoration.h"
     17 #include "nvim/diff.h"
     18 #include "nvim/digraph.h"
     19 #include "nvim/drawscreen.h"
     20 #include "nvim/errors.h"
     21 #include "nvim/eval/userfunc.h"
     22 #include "nvim/eval/vars.h"
     23 #include "nvim/ex_getln.h"
     24 #include "nvim/fold.h"
     25 #include "nvim/gettext_defs.h"
     26 #include "nvim/globals.h"
     27 #include "nvim/grid.h"
     28 #include "nvim/highlight_group.h"
     29 #include "nvim/indent.h"
     30 #include "nvim/indent_c.h"
     31 #include "nvim/insexpand.h"
     32 #include "nvim/macros_defs.h"
     33 #include "nvim/mark.h"
     34 #include "nvim/mbyte.h"
     35 #include "nvim/memline.h"
     36 #include "nvim/memory.h"
     37 #include "nvim/message.h"
     38 #include "nvim/move.h"
     39 #include "nvim/option.h"
     40 #include "nvim/option_defs.h"
     41 #include "nvim/option_vars.h"
     42 #include "nvim/optionstr.h"
     43 #include "nvim/os/os.h"
     44 #include "nvim/pos_defs.h"
     45 #include "nvim/regexp.h"
     46 #include "nvim/regexp_defs.h"
     47 #include "nvim/shada.h"
     48 #include "nvim/spell.h"
     49 #include "nvim/spellfile.h"
     50 #include "nvim/spellsuggest.h"
     51 #include "nvim/strings.h"
     52 #include "nvim/tag.h"
     53 #include "nvim/terminal.h"
     54 #include "nvim/types_defs.h"
     55 #include "nvim/vim_defs.h"
     56 #include "nvim/window.h"
     57 #include "nvim/winfloat.h"
     58 
     59 #include "optionstr.c.generated.h"
     60 
     61 static const char e_illegal_character_after_chr[]
     62  = N_("E535: Illegal character after <%c>");
     63 static const char e_comma_required[]
     64  = N_("E536: Comma required");
     65 static const char e_unclosed_expression_sequence[]
     66  = N_("E540: Unclosed expression sequence");
     67 static const char e_unbalanced_groups[]
     68  = N_("E542: Unbalanced groups");
     69 static const char e_backupext_and_patchmode_are_equal[]
     70  = N_("E589: 'backupext' and 'patchmode' are equal");
     71 static const char e_showbreak_contains_unprintable_or_wide_character[]
     72  = N_("E595: 'showbreak' contains unprintable or wide character");
     73 static const char e_wrong_number_of_characters_for_field_str[]
     74  = N_("E1511: Wrong number of characters for field \"%s\"");
     75 static const char e_wrong_character_width_for_field_str[]
     76  = N_("E1512: Wrong character width for field \"%s\"");
     77 
     78 /// All possible flags for 'shm'.
     79 /// the literal chars before 0 are removed flags. these are safely ignored
     80 static char SHM_ALL[] = { SHM_RO, SHM_MOD, SHM_LINES,
     81                          SHM_WRI, SHM_ABBREVIATIONS, SHM_WRITE, SHM_TRUNC, SHM_TRUNCALL,
     82                          SHM_OVER, SHM_OVERALL, SHM_SEARCH, SHM_ATTENTION, SHM_INTRO,
     83                          SHM_COMPLETIONMENU, SHM_COMPLETIONSCAN, SHM_RECORDING, SHM_FILEINFO,
     84                          SHM_SEARCHCOUNT, 'n', 'f', 'x', 'i', 0, };
     85 
     86 /// After setting various option values: recompute variables that depend on
     87 /// option values.
     88 void didset_string_options(void)
     89 {
     90  check_str_opt(kOptCasemap, NULL);
     91  check_str_opt(kOptBackupcopy, NULL);
     92  check_str_opt(kOptBelloff, NULL);
     93  check_str_opt(kOptCompleteopt, NULL);
     94  check_str_opt(kOptSessionoptions, NULL);
     95  check_str_opt(kOptViewoptions, NULL);
     96  check_str_opt(kOptFoldopen, NULL);
     97  check_str_opt(kOptDisplay, NULL);
     98  check_str_opt(kOptJumpoptions, NULL);
     99  check_str_opt(kOptRedrawdebug, NULL);
    100  check_str_opt(kOptTagcase, NULL);
    101  check_str_opt(kOptTermpastefilter, NULL);
    102  check_str_opt(kOptVirtualedit, NULL);
    103  check_str_opt(kOptSwitchbuf, NULL);
    104  check_str_opt(kOptTabclose, NULL);
    105  check_str_opt(kOptWildoptions, NULL);
    106  check_str_opt(kOptClipboard, NULL);
    107 }
    108 
    109 char *illegal_char(char *errbuf, size_t errbuflen, int c)
    110 {
    111  if (errbuf == NULL) {
    112    return "";
    113  }
    114  vim_snprintf(errbuf, errbuflen, _("E539: Illegal character <%s>"),
    115               transchar(c));
    116  return errbuf;
    117 }
    118 
    119 static char *illegal_char_after_chr(char *errbuf, size_t errbuflen, int c)
    120 {
    121  if (errbuf == NULL) {
    122    return "";
    123  }
    124  vim_snprintf(errbuf, errbuflen, _(e_illegal_character_after_chr), c);
    125  return errbuf;
    126 }
    127 
    128 /// Check string options in a buffer for NULL value.
    129 void check_buf_options(buf_T *buf)
    130 {
    131  check_string_option(&buf->b_p_bh);
    132  check_string_option(&buf->b_p_bt);
    133  check_string_option(&buf->b_p_fenc);
    134  check_string_option(&buf->b_p_ff);
    135  check_string_option(&buf->b_p_def);
    136  check_string_option(&buf->b_p_inc);
    137  check_string_option(&buf->b_p_inex);
    138  check_string_option(&buf->b_p_inde);
    139  check_string_option(&buf->b_p_indk);
    140  check_string_option(&buf->b_p_fp);
    141  check_string_option(&buf->b_p_fex);
    142  check_string_option(&buf->b_p_kp);
    143  check_string_option(&buf->b_p_mps);
    144  check_string_option(&buf->b_p_fo);
    145  check_string_option(&buf->b_p_flp);
    146  check_string_option(&buf->b_p_isk);
    147  check_string_option(&buf->b_p_com);
    148  check_string_option(&buf->b_p_cms);
    149  check_string_option(&buf->b_p_nf);
    150  check_string_option(&buf->b_p_qe);
    151  check_string_option(&buf->b_p_syn);
    152  check_string_option(&buf->b_s.b_syn_isk);
    153  check_string_option(&buf->b_s.b_p_spc);
    154  check_string_option(&buf->b_s.b_p_spf);
    155  check_string_option(&buf->b_s.b_p_spl);
    156  check_string_option(&buf->b_s.b_p_spo);
    157  check_string_option(&buf->b_p_sua);
    158  check_string_option(&buf->b_p_cink);
    159  check_string_option(&buf->b_p_cino);
    160  parse_cino(buf);
    161  check_string_option(&buf->b_p_lop);
    162  check_string_option(&buf->b_p_ft);
    163  check_string_option(&buf->b_p_cinw);
    164  check_string_option(&buf->b_p_cinsd);
    165  check_string_option(&buf->b_p_cot);
    166  check_string_option(&buf->b_p_cpt);
    167  check_string_option(&buf->b_p_cfu);
    168  check_string_option(&buf->b_p_ofu);
    169  check_string_option(&buf->b_p_keymap);
    170  check_string_option(&buf->b_p_gefm);
    171  check_string_option(&buf->b_p_gp);
    172  check_string_option(&buf->b_p_mp);
    173  check_string_option(&buf->b_p_efm);
    174  check_string_option(&buf->b_p_ep);
    175  check_string_option(&buf->b_p_path);
    176  check_string_option(&buf->b_p_tags);
    177  check_string_option(&buf->b_p_ffu);
    178  check_string_option(&buf->b_p_tfu);
    179  check_string_option(&buf->b_p_tc);
    180  check_string_option(&buf->b_p_dict);
    181  check_string_option(&buf->b_p_dia);
    182  check_string_option(&buf->b_p_tsr);
    183  check_string_option(&buf->b_p_tsrfu);
    184  check_string_option(&buf->b_p_lw);
    185  check_string_option(&buf->b_p_bkc);
    186  check_string_option(&buf->b_p_menc);
    187  check_string_option(&buf->b_p_vsts);
    188  check_string_option(&buf->b_p_vts);
    189 }
    190 
    191 /// Free the string allocated for an option.
    192 /// Checks for the string being empty_string_option. This may happen if we're out of memory,
    193 /// xstrdup() returned NULL, which was replaced by empty_string_option by check_options().
    194 void free_string_option(char *p)
    195 {
    196  if (p != empty_string_option) {
    197    xfree(p);
    198  }
    199 }
    200 
    201 void clear_string_option(char **pp)
    202 {
    203  if (*pp != empty_string_option) {
    204    xfree(*pp);
    205  }
    206  *pp = empty_string_option;
    207 }
    208 
    209 void check_string_option(char **pp)
    210 {
    211  if (*pp == NULL) {
    212    *pp = empty_string_option;
    213  }
    214 }
    215 
    216 /// Return true if "val" is a valid 'filetype' name.
    217 /// Also used for 'syntax' and 'keymap'.
    218 static bool valid_filetype(const char *val)
    219  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
    220 {
    221  return valid_name(val, ".-_");
    222 }
    223 
    224 /// Handle setting 'signcolumn' for value 'val'. Store minimum and maximum width.
    225 ///
    226 /// @param wcl  when NULL: use "wp->w_p_scl"
    227 /// @param wp   when NULL: only parse "scl"
    228 ///
    229 /// @return OK when the value is valid, FAIL otherwise
    230 int check_signcolumn(char *scl, win_T *wp)
    231 {
    232  char *val = empty_string_option;
    233  if (scl != NULL) {
    234    val = scl;
    235  } else if (wp != NULL) {
    236    val = wp->w_p_scl;
    237  }
    238 
    239  if (*val == NUL) {
    240    return FAIL;
    241  }
    242 
    243  if (opt_strings_flags(val, opt_scl_values, NULL, false) == OK) {
    244    if (wp == NULL) {
    245      return OK;
    246    }
    247    if (!strncmp(val, "no", 2)) {  // no
    248      wp->w_minscwidth = wp->w_maxscwidth = SCL_NO;
    249    } else if (!strncmp(val, "nu", 2) && (wp->w_p_nu || wp->w_p_rnu)) {  // number
    250      wp->w_minscwidth = wp->w_maxscwidth = SCL_NUM;
    251    } else if (!strncmp(val, "yes:", 4)) {  // yes:<NUM>
    252      wp->w_minscwidth = wp->w_maxscwidth = val[4] - '0';
    253    } else if (*val == 'y') {  // yes
    254      wp->w_minscwidth = wp->w_maxscwidth = 1;
    255    } else if (!strncmp(val, "auto:", 5)) {  // auto:<NUM>
    256      wp->w_minscwidth = 0;
    257      wp->w_maxscwidth = val[5] - '0';
    258    } else {  // auto
    259      wp->w_minscwidth = 0;
    260      wp->w_maxscwidth = 1;
    261    }
    262  } else {
    263    if (strncmp(val, "auto:", 5) != 0
    264        || strlen(val) != 8
    265        || !ascii_isdigit(val[5])
    266        || val[6] != '-'
    267        || !ascii_isdigit(val[7])) {
    268      return FAIL;
    269    }
    270    // auto:<NUM>-<NUM>
    271    int min = val[5] - '0';
    272    int max = val[7] - '0';
    273    if (min < 1 || max < 2 || min > 8 || min >= max) {
    274      return FAIL;
    275    }
    276    if (wp == NULL) {
    277      return OK;
    278    }
    279    wp->w_minscwidth = min;
    280    wp->w_maxscwidth = max;
    281  }
    282 
    283  int scwidth = wp->w_minscwidth <= 0 ? 0 : MIN(wp->w_maxscwidth, wp->w_scwidth);
    284  wp->w_scwidth = MAX(wp->w_minscwidth, scwidth);
    285  return OK;
    286 }
    287 
    288 /// Check validity of options with the 'statusline' format.
    289 /// Return an untranslated error message or NULL.
    290 const char *check_stl_option(char *s)
    291 {
    292  int groupdepth = 0;
    293  static char errbuf[ERR_BUFLEN];
    294 
    295  while (*s) {
    296    // Check for valid keys after % sequences
    297    while (*s && *s != '%') {
    298      s++;
    299    }
    300    if (!*s) {
    301      break;
    302    }
    303    s++;
    304    if (*s == '%' || *s == STL_TRUNCMARK || *s == STL_SEPARATE) {
    305      s++;
    306      continue;
    307    }
    308    if (*s == ')') {
    309      s++;
    310      if (--groupdepth < 0) {
    311        break;
    312      }
    313      continue;
    314    }
    315    if (*s == '-') {
    316      s++;
    317    }
    318    while (ascii_isdigit(*s)) {
    319      s++;
    320    }
    321    if (*s == STL_USER_HL) {
    322      continue;
    323    }
    324    if (*s == '.') {
    325      s++;
    326      while (*s && ascii_isdigit(*s)) {
    327        s++;
    328      }
    329    }
    330    if (*s == '(') {
    331      groupdepth++;
    332      continue;
    333    }
    334    if (vim_strchr(STL_ALL, (uint8_t)(*s)) == NULL) {
    335      return illegal_char(errbuf, sizeof(errbuf), (uint8_t)(*s));
    336    }
    337    if (*s == '{') {
    338      bool reevaluate = (*++s == '%');
    339 
    340      if (reevaluate && *++s == '}') {
    341        // "}" is not allowed immediately after "%{%"
    342        return illegal_char(errbuf, sizeof(errbuf), '}');
    343      }
    344      while ((*s != '}' || (reevaluate && s[-1] != '%')) && *s) {
    345        s++;
    346      }
    347      if (*s != '}') {
    348        return e_unclosed_expression_sequence;
    349      }
    350    }
    351  }
    352  if (groupdepth != 0) {
    353    return e_unbalanced_groups;
    354  }
    355  return NULL;
    356 }
    357 
    358 /// Check for a "normal" directory or file name in some options.  Disallow a
    359 /// path separator (slash and/or backslash), wildcards and characters that are
    360 /// often illegal in a file name. Be more permissive if "secure" is off.
    361 bool check_illegal_path_names(char *val, uint32_t flags)
    362 {
    363  return (((flags & kOptFlagNFname)
    364           && strpbrk(val, (secure ? "/\\*?[|;&<>\r\n" : "/\\*?[<>\r\n")) != NULL)
    365          || ((flags & kOptFlagNDname)
    366              && strpbrk(val, "*?[|;&<>\r\n") != NULL));
    367 }
    368 
    369 /// An option that accepts a list of flags is changed.
    370 /// e.g. 'viewoptions', 'switchbuf', 'casemap', etc.
    371 static const char *did_set_opt_flags(char *val, const char **values, unsigned *flagp, bool list)
    372 {
    373  if (opt_strings_flags(val, values, flagp, list) != OK) {
    374    return e_invarg;
    375  }
    376  return NULL;
    377 }
    378 
    379 static const char **opt_values(OptIndex idx, size_t *values_len)
    380 {
    381  OptIndex idx1 = idx == kOptViewoptions ? kOptSessionoptions
    382                                         : idx == kOptFileformats ? kOptFileformat
    383                                                                  : idx;
    384 
    385  vimoption_T *opt = get_option(idx1);
    386  if (values_len != NULL) {
    387    *values_len = opt->values_len;
    388  }
    389  return opt->values;
    390 }
    391 
    392 static int check_str_opt(OptIndex idx, char **varp)
    393 {
    394  vimoption_T *opt = get_option(idx);
    395  if (varp == NULL) {
    396    varp = opt->var;
    397  }
    398  bool list = opt->flags & (kOptFlagComma | kOptFlagOneComma);
    399  const char **values = opt_values(idx, NULL);
    400  return opt_strings_flags(*varp, values, opt->flags_var, list);
    401 }
    402 
    403 int expand_set_str_generic(optexpand_T *args, int *numMatches, char ***matches)
    404 {
    405  size_t values_len;
    406  const char **values = opt_values(args->oe_idx, &values_len);
    407  return expand_set_opt_string(args, values, values_len, numMatches, matches);
    408 }
    409 
    410 const char *did_set_str_generic(optset_T *args)
    411 {
    412  return check_str_opt(args->os_idx, args->os_varp) != OK ? e_invarg : NULL;
    413 }
    414 
    415 /// An option which is a list of flags is set.  Valid values are in "flags".
    416 static const char *did_set_option_listflag(char *val, char *flags, char *errbuf, size_t errbuflen)
    417 {
    418  for (char *s = val; *s; s++) {
    419    if (vim_strchr(flags, (uint8_t)(*s)) == NULL) {
    420      return illegal_char(errbuf, errbuflen, (uint8_t)(*s));
    421    }
    422  }
    423 
    424  return NULL;
    425 }
    426 
    427 /// Expand an option that accepts a list of string values.
    428 static int expand_set_opt_string(optexpand_T *args, const char **values, size_t numValues,
    429                                 int *numMatches, char ***matches)
    430 {
    431  regmatch_T *regmatch = args->oe_regmatch;
    432  bool include_orig_val = args->oe_include_orig_val;
    433  char *option_val = args->oe_opt_value;
    434 
    435  // Assume numValues is small since they are fixed enums, so just allocate
    436  // upfront instead of needing two passes to calculate output size.
    437  *matches = xmalloc(sizeof(char *) * (numValues + 1));
    438 
    439  int count = 0;
    440 
    441  if (include_orig_val && *option_val != NUL) {
    442    (*matches)[count++] = xstrdup(option_val);
    443  }
    444 
    445  for (const char **val = values; *val != NULL; val++) {
    446    if (**val == NUL) {
    447      continue;  // Ignore empty
    448    } else if (include_orig_val && *option_val != NUL) {
    449      if (strcmp(*val, option_val) == 0) {
    450        continue;
    451      }
    452    }
    453    if (vim_regexec(regmatch, *val, 0)) {
    454      (*matches)[count++] = xstrdup(*val);
    455    }
    456  }
    457  if (count == 0) {
    458    XFREE_CLEAR(*matches);
    459    return FAIL;
    460  }
    461  *numMatches = count;
    462  return OK;
    463 }
    464 
    465 static char *set_opt_callback_orig_option = NULL;
    466 static char *((*set_opt_callback_func)(expand_T *, int));
    467 
    468 /// Callback used by expand_set_opt_generic to also include the original value.
    469 static char *expand_set_opt_callback(expand_T *xp, int idx)
    470 {
    471  if (idx == 0) {
    472    if (set_opt_callback_orig_option != NULL) {
    473      return set_opt_callback_orig_option;
    474    } else {
    475      return "";  // empty strings are ignored
    476    }
    477  }
    478  return set_opt_callback_func(xp, idx - 1);
    479 }
    480 
    481 /// Expand an option with a callback that iterates through a list of possible names.
    482 static int expand_set_opt_generic(optexpand_T *args, CompleteListItemGetter func, int *numMatches,
    483                                  char ***matches)
    484 {
    485  set_opt_callback_orig_option = args->oe_include_orig_val ? args->oe_opt_value : NULL;
    486  set_opt_callback_func = func;
    487 
    488  // not using fuzzy as currently EXPAND_STRING_SETTING doesn't use it
    489  ExpandGeneric("", args->oe_xp, args->oe_regmatch, matches, numMatches,
    490                expand_set_opt_callback, false);
    491 
    492  set_opt_callback_orig_option = NULL;
    493  set_opt_callback_func = NULL;
    494  return OK;
    495 }
    496 
    497 /// Expand an option which is a list of flags.
    498 static int expand_set_opt_listflag(optexpand_T *args, char *flags, int *numMatches, char ***matches)
    499 {
    500  char *option_val = args->oe_opt_value;
    501  char *cmdline_val = args->oe_set_arg;
    502  bool append = args->oe_append;
    503  bool include_orig_val = args->oe_include_orig_val && (*option_val != NUL);
    504 
    505  size_t num_flags = strlen(flags);
    506 
    507  // Assume we only have small number of flags, so just allocate max size.
    508  *matches = xmalloc(sizeof(char *) * (num_flags + 1));
    509 
    510  int count = 0;
    511 
    512  if (include_orig_val) {
    513    (*matches)[count++] = xstrdup(option_val);
    514  }
    515 
    516  for (char *flag = flags; *flag != NUL; flag++) {
    517    if (append && vim_strchr(option_val, *flag) != NULL) {
    518      continue;
    519    }
    520 
    521    if (vim_strchr(cmdline_val, *flag) == NULL) {
    522      if (include_orig_val && option_val[1] == NUL && *flag == option_val[0]) {
    523        // This value is already used as the first choice as it's the
    524        // existing flag. Just skip it to avoid duplicate.
    525        continue;
    526      }
    527      (*matches)[count++] = xmemdupz(flag, 1);
    528    }
    529  }
    530 
    531  if (count == 0) {
    532    XFREE_CLEAR(*matches);
    533    return FAIL;
    534  }
    535  *numMatches = count;
    536  return OK;
    537 }
    538 
    539 /// The 'ambiwidth' option is changed.
    540 const char *did_set_ambiwidth(optset_T *args)
    541 {
    542  const char *errmsg = did_set_str_generic(args);
    543  if (errmsg != NULL) {
    544    return errmsg;
    545  }
    546  return check_chars_options();
    547 }
    548 
    549 /// The 'emoji' option is changed.
    550 const char *did_set_emoji(optset_T *args)
    551 {
    552  if (check_str_opt(kOptAmbiwidth, NULL) != OK) {
    553    return e_invarg;
    554  }
    555  return check_chars_options();
    556 }
    557 
    558 /// The 'background' option is changed.
    559 const char *did_set_background(optset_T *args)
    560 {
    561  const char *errmsg = did_set_str_generic(args);
    562  if (errmsg != NULL) {
    563    return errmsg;
    564  }
    565 
    566  if (args->os_oldval.string.data[0] == *p_bg) {
    567    // Value was not changed
    568    return NULL;
    569  }
    570 
    571  int dark = (*p_bg == 'd');
    572 
    573  init_highlight(false, false);
    574 
    575  if (dark != (*p_bg == 'd') && get_var_value("g:colors_name") != NULL) {
    576    // The color scheme must have set 'background' back to another
    577    // value, that's not what we want here.  Disable the color
    578    // scheme and set the colors again.
    579    do_unlet(S_LEN("g:colors_name"), true);
    580    free_string_option(p_bg);
    581    p_bg = xstrdup((dark ? "dark" : "light"));
    582    check_string_option(&p_bg);
    583    init_highlight(false, false);
    584  }
    585 
    586  // Notify all terminal buffers that the background color changed so they can
    587  // send a theme update notification
    588  FOR_ALL_BUFFERS(buf) {
    589    if (buf->terminal) {
    590      terminal_notify_theme(buf->terminal, dark);
    591    }
    592  }
    593 
    594  return NULL;
    595 }
    596 
    597 /// The 'backspace' option is changed.
    598 const char *did_set_backspace(optset_T *args FUNC_ATTR_UNUSED)
    599 {
    600  if (ascii_isdigit(*p_bs)) {
    601    if (*p_bs != '2') {
    602      return e_invarg;
    603    }
    604    return NULL;
    605  }
    606  return did_set_str_generic(args);
    607 }
    608 
    609 /// The 'backupcopy' option is changed.
    610 const char *did_set_backupcopy(optset_T *args)
    611 {
    612  buf_T *buf = (buf_T *)args->os_buf;
    613  const char *oldval = args->os_oldval.string.data;
    614  int opt_flags = args->os_flags;
    615  char *bkc = p_bkc;
    616  unsigned *flags = &bkc_flags;
    617 
    618  if (opt_flags & OPT_LOCAL) {
    619    bkc = buf->b_p_bkc;
    620    flags = &buf->b_bkc_flags;
    621  } else if (!(opt_flags & OPT_GLOBAL)) {
    622    // When using :set, clear the local flags.
    623    buf->b_bkc_flags = 0;
    624  }
    625 
    626  if ((opt_flags & OPT_LOCAL) && *bkc == NUL) {
    627    // make the local value empty: use the global value
    628    *flags = 0;
    629  } else {
    630    if (opt_strings_flags(bkc, opt_bkc_values, flags, true) != OK) {
    631      return e_invarg;
    632    }
    633 
    634    if (((*flags & kOptBkcFlagAuto) != 0)
    635        + ((*flags & kOptBkcFlagYes) != 0)
    636        + ((*flags & kOptBkcFlagNo) != 0) != 1) {
    637      // Must have exactly one of "auto", "yes"  and "no".
    638      opt_strings_flags(oldval, opt_bkc_values, flags, true);
    639      return e_invarg;
    640    }
    641  }
    642 
    643  return NULL;
    644 }
    645 
    646 /// The 'backupext' or the 'patchmode' option is changed.
    647 const char *did_set_backupext_or_patchmode(optset_T *args FUNC_ATTR_UNUSED)
    648 {
    649  if (strcmp(*p_bex == '.' ? p_bex + 1 : p_bex,
    650             *p_pm == '.' ? p_pm + 1 : p_pm) == 0) {
    651    return e_backupext_and_patchmode_are_equal;
    652  }
    653 
    654  return NULL;
    655 }
    656 
    657 /// The 'breakat' option is changed.
    658 const char *did_set_breakat(optset_T *args FUNC_ATTR_UNUSED)
    659 {
    660  for (int i = 0; i < 256; i++) {
    661    breakat_flags[i] = false;
    662  }
    663 
    664  if (p_breakat != NULL) {
    665    for (char *p = p_breakat; *p; p++) {
    666      breakat_flags[(uint8_t)(*p)] = true;
    667    }
    668  }
    669 
    670  return NULL;
    671 }
    672 
    673 /// The 'breakindentopt' option is changed.
    674 const char *did_set_breakindentopt(optset_T *args)
    675 {
    676  win_T *win = (win_T *)args->os_win;
    677  char **varp = (char **)args->os_varp;
    678 
    679  if (briopt_check(*varp, varp == &win->w_p_briopt ? win : NULL) == FAIL) {
    680    return e_invarg;
    681  }
    682 
    683  // list setting requires a redraw
    684  if (varp == &win->w_p_briopt && win->w_briopt_list) {
    685    redraw_all_later(UPD_NOT_VALID);
    686  }
    687 
    688  return NULL;
    689 }
    690 
    691 /// The 'bufhidden' option is changed.
    692 const char *did_set_bufhidden(optset_T *args)
    693 {
    694  buf_T *buf = (buf_T *)args->os_buf;
    695  return did_set_opt_flags(buf->b_p_bh, opt_bh_values, NULL, false);
    696 }
    697 
    698 /// The 'buftype' option is changed.
    699 const char *did_set_buftype(optset_T *args)
    700 {
    701  buf_T *buf = (buf_T *)args->os_buf;
    702  win_T *win = (win_T *)args->os_win;
    703  // When 'buftype' is set, check for valid value.
    704  if ((buf->terminal && buf->b_p_bt[0] != 't')
    705      || (!buf->terminal && buf->b_p_bt[0] == 't')
    706      || opt_strings_flags(buf->b_p_bt, opt_bt_values, NULL, false) != OK) {
    707    return e_invarg;
    708  }
    709  // buftype=prompt:
    710  if (buf->b_p_bt[0] == 'p') {
    711    // Set default value for 'comments'
    712    set_option_direct(kOptComments, STATIC_CSTR_AS_OPTVAL(""), OPT_LOCAL, SID_NONE);
    713    // set the prompt start position to lastline.
    714    pos_T next_prompt = { .lnum = buf->b_ml.ml_line_count, .col = buf->b_prompt_start.mark.col,
    715                          .coladd = 0 };
    716    RESET_FMARK(&buf->b_prompt_start, next_prompt, 0, ((fmarkv_T)INIT_FMARKV));
    717  }
    718  if (win->w_status_height || global_stl_height()) {
    719    win->w_redr_status = true;
    720    redraw_later(win, UPD_VALID);
    721  }
    722  buf->b_help = (buf->b_p_bt[0] == 'h');
    723  redraw_titles();
    724  return NULL;
    725 }
    726 
    727 /// The global 'listchars' or 'fillchars' option is changed.
    728 static const char *did_set_global_chars_option(win_T *win, char *val, CharsOption what,
    729                                               int opt_flags, char *errbuf, size_t errbuflen)
    730 {
    731  const char *errmsg = NULL;
    732  char **local_ptr = (what == kListchars) ? &win->w_p_lcs : &win->w_p_fcs;
    733 
    734  // only apply the global value to "win" when it does not have a
    735  // local value
    736  errmsg = set_chars_option(win, val, what,
    737                            **local_ptr == NUL || !(opt_flags & OPT_GLOBAL),
    738                            errbuf, errbuflen);
    739  if (errmsg != NULL) {
    740    return errmsg;
    741  }
    742 
    743  // If the current window is set to use the global
    744  // 'listchars'/'fillchars' value, clear the window-local value.
    745  if (!(opt_flags & OPT_GLOBAL)) {
    746    clear_string_option(local_ptr);
    747  }
    748 
    749  FOR_ALL_TAB_WINDOWS(tp, wp) {
    750    // If the current window has a local value need to apply it
    751    // again, it was changed when setting the global value.
    752    // If no error was returned above, we don't expect an error
    753    // here, so ignore the return value.
    754    char *opt = (what == kListchars) ? wp->w_p_lcs : wp->w_p_fcs;
    755    if (*opt == NUL) {
    756      set_chars_option(wp, opt, what, true, errbuf, errbuflen);
    757    }
    758  }
    759 
    760  redraw_all_later(UPD_NOT_VALID);
    761 
    762  return NULL;
    763 }
    764 
    765 /// The 'fillchars' option or the 'listchars' option is changed.
    766 const char *did_set_chars_option(optset_T *args)
    767 {
    768  win_T *win = (win_T *)args->os_win;
    769  char **varp = (char **)args->os_varp;
    770  const char *errmsg = NULL;
    771 
    772  if (varp == &p_lcs) {      // global 'listchars'
    773    errmsg = did_set_global_chars_option(win, *varp, kListchars, args->os_flags,
    774                                         args->os_errbuf, args->os_errbuflen);
    775  } else if (varp == &p_fcs) {  // global 'fillchars'
    776    errmsg = did_set_global_chars_option(win, *varp, kFillchars, args->os_flags,
    777                                         args->os_errbuf, args->os_errbuflen);
    778  } else if (varp == &win->w_p_lcs) {  // local 'listchars'
    779    errmsg = set_chars_option(win, *varp, kListchars, true,
    780                              args->os_errbuf, args->os_errbuflen);
    781  } else if (varp == &win->w_p_fcs) {  // local 'fillchars'
    782    errmsg = set_chars_option(win, *varp, kFillchars, true,
    783                              args->os_errbuf, args->os_errbuflen);
    784  }
    785 
    786  return errmsg;
    787 }
    788 
    789 /// Expand 'fillchars' or 'listchars' option value.
    790 int expand_set_chars_option(optexpand_T *args, int *numMatches, char ***matches)
    791 {
    792  char **varp = (char **)args->oe_varp;
    793  bool is_lcs = (varp == &p_lcs || varp == &curwin->w_p_lcs);
    794  return expand_set_opt_generic(args,
    795                                is_lcs ? get_listchars_name : get_fillchars_name,
    796                                numMatches,
    797                                matches);
    798 }
    799 
    800 /// The 'cinoptions' option is changed.
    801 const char *did_set_cinoptions(optset_T *args)
    802 {
    803  buf_T *buf = (buf_T *)args->os_buf;
    804  // TODO(vim): recognize errors
    805  parse_cino(buf);
    806 
    807  return NULL;
    808 }
    809 
    810 /// The 'colorcolumn' option is changed.
    811 const char *did_set_colorcolumn(optset_T *args)
    812 {
    813  win_T *win = (win_T *)args->os_win;
    814  char **varp = (char **)args->os_varp;
    815  return check_colorcolumn(*varp, varp == &win->w_p_cc ? win : NULL);
    816 }
    817 
    818 /// The 'comments' option is changed.
    819 const char *did_set_comments(optset_T *args)
    820 {
    821  char **varp = (char **)args->os_varp;
    822  char *errmsg = NULL;
    823  for (char *s = *varp; *s;) {
    824    while (*s && *s != ':') {
    825      if (vim_strchr(COM_ALL, (uint8_t)(*s)) == NULL
    826          && !ascii_isdigit(*s) && *s != '-') {
    827        errmsg = illegal_char(args->os_errbuf, args->os_errbuflen, (uint8_t)(*s));
    828        break;
    829      }
    830      s++;
    831    }
    832    if (*s++ == NUL) {
    833      errmsg = N_("E524: Missing colon");
    834    } else if (*s == ',' || *s == NUL) {
    835      errmsg = N_("E525: Zero length string");
    836    }
    837    if (errmsg != NULL) {
    838      break;
    839    }
    840    while (*s && *s != ',') {
    841      if (*s == '\\' && s[1] != NUL) {
    842        s++;
    843      }
    844      s++;
    845    }
    846    s = skip_to_option_part(s);
    847  }
    848  return errmsg;
    849 }
    850 
    851 /// The 'commentstring' option is changed.
    852 const char *did_set_commentstring(optset_T *args)
    853 {
    854  char **varp = (char **)args->os_varp;
    855 
    856  if (**varp != NUL && strstr(*varp, "%s") == NULL) {
    857    return N_("E537: 'commentstring' must be empty or contain %s");
    858  }
    859  return NULL;
    860 }
    861 
    862 /// Check if value for 'complete' is valid when 'complete' option is changed.
    863 const char *did_set_complete(optset_T *args)
    864 {
    865  char **varp = (char **)args->os_varp;
    866  char buffer[LSIZE];
    867  uint8_t char_before = NUL;
    868 
    869  for (char *p = *varp; *p;) {
    870    memset(buffer, 0, LSIZE);
    871    char *buf_ptr = buffer;
    872    int escape = 0;
    873 
    874    // Extract substring while handling escaped commas
    875    while (*p && (*p != ',' || escape) && buf_ptr < (buffer + LSIZE - 1)) {
    876      if (*p == '\\' && *(p + 1) == ',') {
    877        escape = 1;  // Mark escape mode
    878        p++;         // Skip '\'
    879      } else {
    880        escape = 0;
    881        *buf_ptr++ = *p;
    882      }
    883      p++;
    884    }
    885    *buf_ptr = NUL;
    886 
    887    if (vim_strchr(".wbuksid]tUfFo", (uint8_t)(*buffer)) == NULL) {
    888      return illegal_char(args->os_errbuf, args->os_errbuflen, (uint8_t)(*buffer));
    889    }
    890 
    891    if (vim_strchr("ksF", (uint8_t)(*buffer)) == NULL && *(buffer + 1) != NUL
    892        && *(buffer + 1) != '^') {
    893      char_before = (uint8_t)(*buffer);
    894    } else {
    895      char *t;
    896      // Test for a number after '^'
    897      if ((t = vim_strchr(buffer, '^')) != NULL) {
    898        *t++ = NUL;
    899        if (!*t) {
    900          char_before = '^';
    901        } else {
    902          for (; *t; t++) {
    903            if (!ascii_isdigit(*t)) {
    904              char_before = '^';
    905              break;
    906            }
    907          }
    908        }
    909      }
    910    }
    911    if (char_before != NUL) {
    912      if (args->os_errbuf != NULL) {
    913        return illegal_char_after_chr(args->os_errbuf, args->os_errbuflen,
    914                                      char_before);
    915      }
    916      return NULL;
    917    }
    918    // Skip comma and spaces
    919    while (*p == ',' || *p == ' ') {
    920      p++;
    921    }
    922  }
    923 
    924  if (set_cpt_callbacks(args) != OK) {
    925    return illegal_char_after_chr(args->os_errbuf, args->os_errbuflen, 'F');
    926  }
    927  return NULL;
    928 }
    929 
    930 /// The 'completeitemalign' option is changed.
    931 const char *did_set_completeitemalign(optset_T *args)
    932 {
    933  char *p = p_cia;
    934  unsigned new_cia_flags = 0;
    935  bool seen[3] = { false, false, false };
    936  int count = 0;
    937  char buf[10];
    938  while (*p) {
    939    copy_option_part(&p, buf, sizeof(buf), ",");
    940    if (count >= 3) {
    941      return e_invarg;
    942    }
    943    if (strequal(buf, "abbr")) {
    944      if (seen[CPT_ABBR]) {
    945        return e_invarg;
    946      }
    947      new_cia_flags = new_cia_flags * 10 + CPT_ABBR;
    948      seen[CPT_ABBR] = true;
    949      count++;
    950    } else if (strequal(buf, "kind")) {
    951      if (seen[CPT_KIND]) {
    952        return e_invarg;
    953      }
    954      new_cia_flags = new_cia_flags * 10 + CPT_KIND;
    955      seen[CPT_KIND] = true;
    956      count++;
    957    } else if (strequal(buf, "menu")) {
    958      if (seen[CPT_MENU]) {
    959        return e_invarg;
    960      }
    961      new_cia_flags = new_cia_flags * 10 + CPT_MENU;
    962      seen[CPT_MENU] = true;
    963      count++;
    964    } else {
    965      return e_invarg;
    966    }
    967  }
    968  if (new_cia_flags == 0 || count != 3) {
    969    return e_invarg;
    970  }
    971  cia_flags = new_cia_flags;
    972  return NULL;
    973 }
    974 
    975 /// The 'completeopt' option is changed.
    976 const char *did_set_completeopt(optset_T *args FUNC_ATTR_UNUSED)
    977 {
    978  buf_T *buf = (buf_T *)args->os_buf;
    979  char *cot = p_cot;
    980  unsigned *flags = &cot_flags;
    981 
    982  if (args->os_flags & OPT_LOCAL) {
    983    cot = buf->b_p_cot;
    984    flags = &buf->b_cot_flags;
    985  } else if (!(args->os_flags & OPT_GLOBAL)) {
    986    // When using :set, clear the local flags.
    987    buf->b_cot_flags = 0;
    988  }
    989 
    990  if (opt_strings_flags(cot, opt_cot_values, flags, true) != OK) {
    991    return e_invarg;
    992  }
    993 
    994  return NULL;
    995 }
    996 
    997 #ifdef BACKSLASH_IN_FILENAME
    998 /// The 'completeslash' option is changed.
    999 const char *did_set_completeslash(optset_T *args)
   1000 {
   1001  buf_T *buf = (buf_T *)args->os_buf;
   1002  if (opt_strings_flags(p_csl, opt_csl_values, NULL, false) != OK
   1003      || opt_strings_flags(buf->b_p_csl, opt_csl_values, NULL, false) != OK) {
   1004    return e_invarg;
   1005  }
   1006  return NULL;
   1007 }
   1008 #endif
   1009 
   1010 /// The 'concealcursor' option is changed.
   1011 const char *did_set_concealcursor(optset_T *args)
   1012 {
   1013  char **varp = (char **)args->os_varp;
   1014 
   1015  return did_set_option_listflag(*varp, COCU_ALL, args->os_errbuf, args->os_errbuflen);
   1016 }
   1017 
   1018 int expand_set_concealcursor(optexpand_T *args, int *numMatches, char ***matches)
   1019 {
   1020  return expand_set_opt_listflag(args, COCU_ALL, numMatches, matches);
   1021 }
   1022 
   1023 /// The 'cpoptions' option is changed.
   1024 const char *did_set_cpoptions(optset_T *args)
   1025 {
   1026  char **varp = (char **)args->os_varp;
   1027 
   1028  return did_set_option_listflag(*varp, CPO_VI, args->os_errbuf, args->os_errbuflen);
   1029 }
   1030 
   1031 int expand_set_cpoptions(optexpand_T *args, int *numMatches, char ***matches)
   1032 {
   1033  return expand_set_opt_listflag(args, CPO_VI, numMatches, matches);
   1034 }
   1035 
   1036 /// The 'cursorlineopt' option is changed.
   1037 const char *did_set_cursorlineopt(optset_T *args)
   1038 {
   1039  win_T *win = (win_T *)args->os_win;
   1040  char **varp = (char **)args->os_varp;
   1041 
   1042  // This could be changed to use opt_strings_flags() instead.
   1043  if (**varp == NUL || fill_culopt_flags(*varp, win) != OK) {
   1044    return e_invarg;
   1045  }
   1046 
   1047  return NULL;
   1048 }
   1049 
   1050 /// The 'diffanchors' option is changed.
   1051 const char *did_set_diffanchors(optset_T *args)
   1052 {
   1053  if (diffanchors_changed(args->os_flags & OPT_LOCAL) == FAIL) {
   1054    return e_invarg;
   1055  }
   1056 
   1057  return NULL;
   1058 }
   1059 
   1060 /// The 'diffopt' option is changed.
   1061 const char *did_set_diffopt(optset_T *args FUNC_ATTR_UNUSED)
   1062 {
   1063  return diffopt_changed() == FAIL ? e_invarg : NULL;
   1064 }
   1065 
   1066 int expand_set_diffopt(optexpand_T *args, int *numMatches, char ***matches)
   1067 {
   1068  expand_T *xp = args->oe_xp;
   1069 
   1070  if (xp->xp_pattern > args->oe_set_arg && *(xp->xp_pattern - 1) == ':') {
   1071    // Within "algorithm:", we have a subgroup of possible options.
   1072    const size_t algo_len = strlen("algorithm:");
   1073    if (xp->xp_pattern - args->oe_set_arg >= (int)algo_len
   1074        && strncmp(xp->xp_pattern - algo_len, "algorithm:", algo_len) == 0) {
   1075      return expand_set_opt_string(args,
   1076                                   opt_dip_algorithm_values,
   1077                                   ARRAY_SIZE(opt_dip_algorithm_values) - 1,
   1078                                   numMatches,
   1079                                   matches);
   1080    }
   1081    // Within "inline:", we have a subgroup of possible options.
   1082    const size_t inline_len = strlen("inline:");
   1083    if (xp->xp_pattern - args->oe_set_arg >= (int)inline_len
   1084        && strncmp(xp->xp_pattern - inline_len, "inline:", inline_len) == 0) {
   1085      return expand_set_opt_string(args,
   1086                                   opt_dip_inline_values,
   1087                                   ARRAY_SIZE(opt_dip_inline_values) - 1,
   1088                                   numMatches,
   1089                                   matches);
   1090    }
   1091    return FAIL;
   1092  }
   1093 
   1094  return expand_set_str_generic(args, numMatches, matches);
   1095 }
   1096 
   1097 /// The 'display' option is changed.
   1098 const char *did_set_display(optset_T *args)
   1099 {
   1100  const char *errmsg = did_set_str_generic(args);
   1101  if (errmsg != NULL) {
   1102    return errmsg;
   1103  }
   1104  init_chartab();
   1105  msg_grid_validate();
   1106  return NULL;
   1107 }
   1108 
   1109 /// One of the 'encoding', 'fileencoding' or 'makeencoding'
   1110 /// options is changed.
   1111 const char *did_set_encoding(optset_T *args)
   1112 {
   1113  buf_T *buf = (buf_T *)args->os_buf;
   1114  char **varp = (char **)args->os_varp;
   1115  int opt_flags = args->os_flags;
   1116  // Get the global option to compare with, otherwise we would have to check
   1117  // two values for all local options.
   1118  char **gvarp = (char **)get_option_varp_scope_from(args->os_idx, OPT_GLOBAL, buf, NULL);
   1119 
   1120  if (gvarp == &p_fenc) {
   1121    if (!MODIFIABLE(buf) && opt_flags != OPT_GLOBAL) {
   1122      return e_modifiable;
   1123    }
   1124 
   1125    if (vim_strchr(*varp, ',') != NULL) {
   1126      // No comma allowed in 'fileencoding'; catches confusing it
   1127      // with 'fileencodings'.
   1128      return e_invarg;
   1129    }
   1130 
   1131    // May show a "+" in the title now.
   1132    redraw_titles();
   1133    // Add 'fileencoding' to the swap file.
   1134    ml_setflags(buf);
   1135  }
   1136 
   1137  // canonize the value, so that strcmp() can be used on it
   1138  char *p = enc_canonize(*varp);
   1139  xfree(*varp);
   1140  *varp = p;
   1141  if (varp == &p_enc) {
   1142    // only encoding=utf-8 allowed
   1143    if (strcmp(p_enc, "utf-8") != 0) {
   1144      return e_unsupportedoption;
   1145    }
   1146    spell_reload();
   1147  }
   1148  return NULL;
   1149 }
   1150 
   1151 int expand_set_encoding(optexpand_T *args, int *numMatches, char ***matches)
   1152 {
   1153  return expand_set_opt_generic(args, get_encoding_name, numMatches, matches);
   1154 }
   1155 
   1156 /// The 'eventignore(win)' option is changed.
   1157 const char *did_set_eventignore(optset_T *args)
   1158 {
   1159  char **varp = (char **)args->os_varp;
   1160 
   1161  if (check_ei(*varp) == FAIL) {
   1162    return e_invarg;
   1163  }
   1164  return NULL;
   1165 }
   1166 
   1167 static bool expand_eiw = false;
   1168 
   1169 static char *get_eventignore_name(expand_T *xp, int idx)
   1170 {
   1171  bool subtract = *xp->xp_pattern == '-';
   1172  // 'eventignore(win)' allows special keyword "all" in addition to
   1173  // all event names.
   1174  if (!subtract && idx == 0) {
   1175    return "all";
   1176  }
   1177 
   1178  char *name = get_event_name_no_group(xp, idx - 1 + subtract, expand_eiw);
   1179  if (name == NULL) {
   1180    return NULL;
   1181  }
   1182 
   1183  snprintf(IObuff, IOSIZE, "%s%s", subtract ? "-" : "", name);
   1184  return IObuff;
   1185 }
   1186 
   1187 int expand_set_eventignore(optexpand_T *args, int *numMatches, char ***matches)
   1188 {
   1189  expand_eiw = args->oe_varp != (char *)&p_ei;
   1190  return expand_set_opt_generic(args, get_eventignore_name, numMatches, matches);
   1191 }
   1192 
   1193 /// The 'fileformat' option is changed.
   1194 const char *did_set_fileformat(optset_T *args)
   1195 {
   1196  buf_T *buf = (buf_T *)args->os_buf;
   1197  const char *oldval = args->os_oldval.string.data;
   1198  int opt_flags = args->os_flags;
   1199  if (!MODIFIABLE(buf) && !(opt_flags & OPT_GLOBAL)) {
   1200    return e_modifiable;
   1201  }
   1202 
   1203  const char *errmsg = did_set_str_generic(args);
   1204  if (errmsg != NULL) {
   1205    return errmsg;
   1206  }
   1207 
   1208  redraw_titles();
   1209  // update flag in swap file
   1210  ml_setflags(buf);
   1211  // Redraw needed when switching to/from "mac": a CR in the text
   1212  // will be displayed differently.
   1213  if (get_fileformat(buf) == EOL_MAC || *oldval == 'm') {
   1214    redraw_buf_later(buf, UPD_NOT_VALID);
   1215  }
   1216  return NULL;
   1217 }
   1218 
   1219 /// Function given to ExpandGeneric() to obtain the possible arguments of the
   1220 /// fileformat options.
   1221 char *get_fileformat_name(expand_T *xp FUNC_ATTR_UNUSED, int idx)
   1222 {
   1223  if (idx >= (int)ARRAY_SIZE(opt_ff_values)) {
   1224    return NULL;
   1225  }
   1226 
   1227  return (char *)opt_ff_values[idx];
   1228 }
   1229 
   1230 /// The 'filetype' or the 'syntax' option is changed.
   1231 const char *did_set_filetype_or_syntax(optset_T *args)
   1232 {
   1233  char **varp = (char **)args->os_varp;
   1234 
   1235  if (!valid_filetype(*varp)) {
   1236    return e_invarg;
   1237  }
   1238 
   1239  args->os_value_changed = strcmp(args->os_oldval.string.data, *varp) != 0;
   1240 
   1241  // Since we check the value, there is no need to set kOptFlagInsecure,
   1242  // even when the value comes from a modeline.
   1243  args->os_value_checked = true;
   1244 
   1245  return NULL;
   1246 }
   1247 
   1248 /// The 'foldexpr' option is changed.
   1249 const char *did_set_foldexpr(optset_T *args)
   1250 {
   1251  win_T *win = (win_T *)args->os_win;
   1252  did_set_optexpr(args);
   1253  if (foldmethodIsExpr(win)) {
   1254    foldUpdateAll(win);
   1255  }
   1256  return NULL;
   1257 }
   1258 
   1259 /// The 'foldignore' option is changed.
   1260 const char *did_set_foldignore(optset_T *args)
   1261 {
   1262  win_T *win = (win_T *)args->os_win;
   1263  if (foldmethodIsIndent(win)) {
   1264    foldUpdateAll(win);
   1265  }
   1266  return NULL;
   1267 }
   1268 
   1269 /// The 'foldmarker' option is changed.
   1270 const char *did_set_foldmarker(optset_T *args)
   1271 {
   1272  win_T *win = (win_T *)args->os_win;
   1273  char **varp = (char **)args->os_varp;
   1274  char *p = vim_strchr(*varp, ',');
   1275 
   1276  if (p == NULL) {
   1277    return e_comma_required;
   1278  }
   1279 
   1280  if (p == *varp || p[1] == NUL) {
   1281    return e_invarg;
   1282  }
   1283 
   1284  if (foldmethodIsMarker(win)) {
   1285    foldUpdateAll(win);
   1286  }
   1287 
   1288  return NULL;
   1289 }
   1290 
   1291 /// The 'foldmethod' option is changed.
   1292 const char *did_set_foldmethod(optset_T *args)
   1293 {
   1294  const char *errmsg = did_set_str_generic(args);
   1295  if (errmsg != NULL) {
   1296    return errmsg;
   1297  }
   1298  win_T *win = (win_T *)args->os_win;
   1299  foldUpdateAll(win);
   1300  if (foldmethodIsDiff(win)) {
   1301    newFoldLevel();
   1302  }
   1303  return NULL;
   1304 }
   1305 
   1306 /// The 'formatoptions' option is changed.
   1307 const char *did_set_formatoptions(optset_T *args)
   1308 {
   1309  char **varp = (char **)args->os_varp;
   1310 
   1311  return did_set_option_listflag(*varp, FO_ALL, args->os_errbuf, args->os_errbuflen);
   1312 }
   1313 
   1314 int expand_set_formatoptions(optexpand_T *args, int *numMatches, char ***matches)
   1315 {
   1316  return expand_set_opt_listflag(args, FO_ALL, numMatches, matches);
   1317 }
   1318 
   1319 /// The 'guicursor' option is changed.
   1320 const char *did_set_guicursor(optset_T *args FUNC_ATTR_UNUSED)
   1321 {
   1322  const char *errmsg = parse_shape_opt(SHAPE_CURSOR);
   1323  if (errmsg != NULL) {
   1324    return errmsg;
   1325  }
   1326  if (VIsual_active) {
   1327    // In Visual mode cursor may be drawn differently.
   1328    redrawWinline(curwin, curwin->w_cursor.lnum);
   1329  }
   1330  return NULL;
   1331 }
   1332 
   1333 /// The 'helpfile' option is changed.
   1334 const char *did_set_helpfile(optset_T *args FUNC_ATTR_UNUSED)
   1335 {
   1336  // May compute new values for $VIM and $VIMRUNTIME
   1337  if (didset_vim) {
   1338    vim_unsetenv_ext("VIM");
   1339  }
   1340  if (didset_vimruntime) {
   1341    vim_unsetenv_ext("VIMRUNTIME");
   1342  }
   1343  return NULL;
   1344 }
   1345 
   1346 /// The 'helplang' option is changed.
   1347 const char *did_set_helplang(optset_T *args FUNC_ATTR_UNUSED)
   1348 {
   1349  // Check for "", "ab", "ab,cd", etc.
   1350  for (char *s = p_hlg; *s != NUL; s += 3) {
   1351    if (s[1] == NUL || ((s[2] != ',' || s[3] == NUL) && s[2] != NUL)) {
   1352      return e_invarg;
   1353    }
   1354    if (s[2] == NUL) {
   1355      break;
   1356    }
   1357  }
   1358  return NULL;
   1359 }
   1360 
   1361 /// The 'highlight' option is changed.
   1362 const char *did_set_highlight(optset_T *args)
   1363 {
   1364  char **varp = (char **)args->os_varp;
   1365 
   1366  if (strcmp(*varp, HIGHLIGHT_INIT) != 0) {
   1367    return e_unsupportedoption;
   1368  }
   1369  return NULL;
   1370 }
   1371 
   1372 /// The 'iconstring' option is changed.
   1373 const char *did_set_iconstring(optset_T *args)
   1374 {
   1375  return did_set_titleiconstring(args, STL_IN_ICON);
   1376 }
   1377 
   1378 /// The 'inccommand' option is changed.
   1379 const char *did_set_inccommand(optset_T *args FUNC_ATTR_UNUSED)
   1380 {
   1381  if (cmdpreview) {
   1382    return e_invarg;
   1383  }
   1384  return did_set_str_generic(args);
   1385 }
   1386 
   1387 /// The 'iskeyword' option is changed.
   1388 const char *did_set_iskeyword(optset_T *args)
   1389 {
   1390  char **varp = (char **)args->os_varp;
   1391 
   1392  if (varp == &p_isk) {       // only check for global-value
   1393    if (check_isopt(*varp) == FAIL) {
   1394      return e_invarg;
   1395    }
   1396  } else {                    // fallthrough for local-value
   1397    return did_set_isopt(args);
   1398  }
   1399 
   1400  return NULL;
   1401 }
   1402 
   1403 /// The 'isident' or the 'iskeyword' or the 'isprint' or the 'isfname' option is
   1404 /// changed.
   1405 const char *did_set_isopt(optset_T *args)
   1406 {
   1407  buf_T *buf = (buf_T *)args->os_buf;
   1408  // 'isident', 'iskeyword', 'isprint' or 'isfname' option: refill g_chartab[]
   1409  // If the new option is invalid, use old value.
   1410  // 'lisp' option: refill g_chartab[] for '-' char
   1411  if (buf_init_chartab(buf, true) == FAIL) {
   1412    args->os_restore_chartab = true;  // need to restore it below
   1413    return e_invarg;                  // error in value
   1414  }
   1415  return NULL;
   1416 }
   1417 
   1418 /// The 'keymap' option has changed.
   1419 const char *did_set_keymap(optset_T *args)
   1420 {
   1421  buf_T *buf = (buf_T *)args->os_buf;
   1422  char **varp = (char **)args->os_varp;
   1423  int opt_flags = args->os_flags;
   1424 
   1425  if (!valid_filetype(*varp)) {
   1426    return e_invarg;
   1427  }
   1428 
   1429  int secure_save = secure;
   1430 
   1431  // Reset the secure flag, since the value of 'keymap' has
   1432  // been checked to be safe.
   1433  secure = 0;
   1434 
   1435  // load or unload key mapping tables
   1436  const char *errmsg = keymap_init();
   1437 
   1438  secure = secure_save;
   1439 
   1440  // Since we check the value, there is no need to set kOptFlagInsecure,
   1441  // even when the value comes from a modeline.
   1442  args->os_value_checked = true;
   1443 
   1444  if (errmsg == NULL) {
   1445    if (*buf->b_p_keymap != NUL) {
   1446      // Installed a new keymap, switch on using it.
   1447      buf->b_p_iminsert = B_IMODE_LMAP;
   1448      if (buf->b_p_imsearch != B_IMODE_USE_INSERT) {
   1449        buf->b_p_imsearch = B_IMODE_LMAP;
   1450      }
   1451    } else {
   1452      // Cleared the keymap, may reset 'iminsert' and 'imsearch'.
   1453      if (buf->b_p_iminsert == B_IMODE_LMAP) {
   1454        buf->b_p_iminsert = B_IMODE_NONE;
   1455      }
   1456      if (buf->b_p_imsearch == B_IMODE_LMAP) {
   1457        buf->b_p_imsearch = B_IMODE_USE_INSERT;
   1458      }
   1459    }
   1460    if ((opt_flags & OPT_LOCAL) == 0) {
   1461      set_iminsert_global(buf);
   1462      set_imsearch_global(buf);
   1463    }
   1464    status_redraw_buf(buf);
   1465  }
   1466 
   1467  return errmsg;
   1468 }
   1469 
   1470 /// The 'keymodel' option is changed.
   1471 const char *did_set_keymodel(optset_T *args FUNC_ATTR_UNUSED)
   1472 {
   1473  const char *errmsg = did_set_str_generic(args);
   1474  if (errmsg != NULL) {
   1475    return errmsg;
   1476  }
   1477  km_stopsel = (vim_strchr(p_km, 'o') != NULL);
   1478  km_startsel = (vim_strchr(p_km, 'a') != NULL);
   1479  return NULL;
   1480 }
   1481 
   1482 /// The 'lispoptions' option is changed.
   1483 const char *did_set_lispoptions(optset_T *args)
   1484 {
   1485  char **varp = (char **)args->os_varp;
   1486 
   1487  if (**varp != NUL && strcmp(*varp, "expr:0") != 0 && strcmp(*varp, "expr:1") != 0) {
   1488    return e_invarg;
   1489  }
   1490  return NULL;
   1491 }
   1492 
   1493 /// The 'matchpairs' option is changed.
   1494 const char *did_set_matchpairs(optset_T *args)
   1495 {
   1496  char **varp = (char **)args->os_varp;
   1497 
   1498  for (char *p = *varp; *p != NUL; p++) {
   1499    int x2 = -1;
   1500    int x3 = -1;
   1501 
   1502    p += utfc_ptr2len(p);
   1503    if (*p != NUL) {
   1504      x2 = (unsigned char)(*p++);
   1505    }
   1506    if (*p != NUL) {
   1507      x3 = utf_ptr2char(p);
   1508      p += utfc_ptr2len(p);
   1509    }
   1510    if (x2 != ':' || x3 == -1 || (*p != NUL && *p != ',')) {
   1511      return e_invarg;
   1512    }
   1513    if (*p == NUL) {
   1514      break;
   1515    }
   1516  }
   1517  return NULL;
   1518 }
   1519 
   1520 /// Process the updated 'messagesopt' option value.
   1521 const char *did_set_messagesopt(optset_T *args FUNC_ATTR_UNUSED)
   1522 {
   1523  if (messagesopt_changed() == FAIL) {
   1524    return e_invarg;
   1525  }
   1526  return NULL;
   1527 }
   1528 
   1529 /// The 'mkspellmem' option is changed.
   1530 const char *did_set_mkspellmem(optset_T *args FUNC_ATTR_UNUSED)
   1531 {
   1532  if (spell_check_msm() != OK) {
   1533    return e_invarg;
   1534  }
   1535  return NULL;
   1536 }
   1537 
   1538 /// The 'mouse' option is changed.
   1539 const char *did_set_mouse(optset_T *args)
   1540 {
   1541  char **varp = (char **)args->os_varp;
   1542 
   1543  return did_set_option_listflag(*varp, MOUSE_ALL, args->os_errbuf, args->os_errbuflen);
   1544 }
   1545 
   1546 int expand_set_mouse(optexpand_T *args, int *numMatches, char ***matches)
   1547 {
   1548  return expand_set_opt_listflag(args, MOUSE_ALL, numMatches, matches);
   1549 }
   1550 
   1551 /// Handle setting 'mousescroll'.
   1552 /// @return error message, NULL if it's OK.
   1553 const char *did_set_mousescroll(optset_T *args FUNC_ATTR_UNUSED)
   1554 {
   1555  OptInt vertical = -1;
   1556  OptInt horizontal = -1;
   1557 
   1558  char *string = p_mousescroll;
   1559 
   1560  while (true) {
   1561    char *end = vim_strchr(string, ',');
   1562    size_t length = end ? (size_t)(end - string) : strlen(string);
   1563 
   1564    // Both "ver:" and "hor:" are 4 bytes long.
   1565    // They should be followed by at least one digit.
   1566    if (length <= 4) {
   1567      return e_invarg;
   1568    }
   1569 
   1570    OptInt *direction;
   1571 
   1572    if (memcmp(string, "ver:", 4) == 0) {
   1573      direction = &vertical;
   1574    } else if (memcmp(string, "hor:", 4) == 0) {
   1575      direction = &horizontal;
   1576    } else {
   1577      return e_invarg;
   1578    }
   1579 
   1580    // If the direction has already been set, this is a duplicate.
   1581    if (*direction != -1) {
   1582      return e_invarg;
   1583    }
   1584 
   1585    // Verify that only digits follow the colon.
   1586    for (size_t i = 4; i < length; i++) {
   1587      if (!ascii_isdigit(string[i])) {
   1588        return N_("E5080: Digit expected");
   1589      }
   1590    }
   1591 
   1592    string += 4;
   1593    *direction = getdigits_int(&string, false, -1);
   1594 
   1595    // Num options are generally kept within the signed int range.
   1596    // We know this number won't be negative because we've already checked for
   1597    // a minus sign. We'll allow 0 as a means of disabling mouse scrolling.
   1598    if (*direction == -1) {
   1599      return e_invarg;
   1600    }
   1601 
   1602    if (!end) {
   1603      break;
   1604    }
   1605 
   1606    string = end + 1;
   1607  }
   1608 
   1609  // If a direction wasn't set, fallback to the default value.
   1610  p_mousescroll_vert = (vertical == -1) ? MOUSESCROLL_VERT_DFLT : vertical;
   1611  p_mousescroll_hor = (horizontal == -1) ? MOUSESCROLL_HOR_DFLT : horizontal;
   1612 
   1613  return NULL;
   1614 }
   1615 
   1616 /// One of the '*expr' options is changed:, 'diffexpr', 'foldexpr', 'foldtext',
   1617 /// 'formatexpr', 'includeexpr', 'indentexpr', 'patchexpr' and 'charconvert'.
   1618 const char *did_set_optexpr(optset_T *args)
   1619 {
   1620  char **varp = (char **)args->os_varp;
   1621 
   1622  // If the option value starts with <SID> or s:, then replace that with
   1623  // the script identifier.
   1624  char *name = get_scriptlocal_funcname(*varp);
   1625  if (name != NULL) {
   1626    free_string_option(*varp);
   1627    *varp = name;
   1628  }
   1629  return NULL;
   1630 }
   1631 
   1632 /// The 'rulerformat' option is changed.
   1633 const char *did_set_rulerformat(optset_T *args)
   1634 {
   1635  return did_set_statustabline_rulerformat(args, true, false);
   1636 }
   1637 
   1638 /// The 'selection' option is changed.
   1639 const char *did_set_selection(optset_T *args FUNC_ATTR_UNUSED)
   1640 {
   1641  const char *errmsg = did_set_str_generic(args);
   1642  if (errmsg != NULL) {
   1643    return errmsg;
   1644  }
   1645  if (VIsual_active) {
   1646    // Visual selection may be drawn differently.
   1647    redraw_curbuf_later(UPD_INVERTED);
   1648  }
   1649  return NULL;
   1650 }
   1651 
   1652 /// The 'sessionoptions' option is changed.
   1653 const char *did_set_sessionoptions(optset_T *args)
   1654 {
   1655  const char *errmsg = did_set_str_generic(args);
   1656  if (errmsg != NULL) {
   1657    return errmsg;
   1658  }
   1659  if ((ssop_flags & kOptSsopFlagCurdir) && (ssop_flags & kOptSsopFlagSesdir)) {
   1660    // Don't allow both "sesdir" and "curdir".
   1661    const char *oldval = args->os_oldval.string.data;
   1662    opt_strings_flags(oldval, opt_ssop_values, &ssop_flags, true);
   1663    return e_invarg;
   1664  }
   1665  return NULL;
   1666 }
   1667 
   1668 const char *did_set_shada(optset_T *args)
   1669 {
   1670  char *errbuf = args->os_errbuf;
   1671  size_t errbuflen = args->os_errbuflen;
   1672 
   1673  for (char *s = p_shada; *s;) {
   1674    // Check it's a valid character
   1675    if (vim_strchr("!\"%'/:<@cfhnrs", (uint8_t)(*s)) == NULL) {
   1676      return illegal_char(errbuf, errbuflen, (uint8_t)(*s));
   1677    }
   1678    if (*s == 'n') {          // name is always last one
   1679      break;
   1680    } else if (*s == 'r') {  // skip until next ','
   1681      while (*++s && *s != ',') {}
   1682    } else if (*s == '%') {
   1683      // optional number
   1684      while (ascii_isdigit(*++s)) {}
   1685    } else if (*s == '!' || *s == 'h' || *s == 'c') {
   1686      s++;                    // no extra chars
   1687    } else {                    // must have a number
   1688      while (ascii_isdigit(*++s)) {}
   1689 
   1690      if (!ascii_isdigit(*(s - 1))) {
   1691        if (errbuf != NULL) {
   1692          vim_snprintf(errbuf, errbuflen,
   1693                       _("E526: Missing number after <%s>"),
   1694                       transchar_byte((uint8_t)(*(s - 1))));
   1695          return errbuf;
   1696        } else {
   1697          return "";
   1698        }
   1699      }
   1700    }
   1701    if (*s == ',') {
   1702      s++;
   1703    } else if (*s) {
   1704      if (errbuf != NULL) {
   1705        return N_("E527: Missing comma");
   1706      } else {
   1707        return "";
   1708      }
   1709    }
   1710  }
   1711  if (*p_shada && get_shada_parameter('\'') < 0) {
   1712    return N_("E528: Must specify a ' value");
   1713  }
   1714  return NULL;
   1715 }
   1716 
   1717 /// The 'shortmess' option is changed.
   1718 const char *did_set_shortmess(optset_T *args)
   1719 {
   1720  char **varp = (char **)args->os_varp;
   1721 
   1722  return did_set_option_listflag(*varp, SHM_ALL, args->os_errbuf, args->os_errbuflen);
   1723 }
   1724 
   1725 int expand_set_shortmess(optexpand_T *args, int *numMatches, char ***matches)
   1726 {
   1727  return expand_set_opt_listflag(args, SHM_ALL, numMatches, matches);
   1728 }
   1729 
   1730 /// The 'showbreak' option is changed.
   1731 const char *did_set_showbreak(optset_T *args)
   1732 {
   1733  char **varp = (char **)args->os_varp;
   1734 
   1735  for (char *s = *varp; *s;) {
   1736    if (ptr2cells(s) != 1) {
   1737      return e_showbreak_contains_unprintable_or_wide_character;
   1738    }
   1739    MB_PTR_ADV(s);
   1740  }
   1741  return NULL;
   1742 }
   1743 
   1744 /// The 'showcmdloc' option is changed.
   1745 const char *did_set_showcmdloc(optset_T *args FUNC_ATTR_UNUSED)
   1746 {
   1747  const char *errmsg = did_set_str_generic(args);
   1748 
   1749  if (errmsg == NULL) {
   1750    comp_col();
   1751  }
   1752 
   1753  return errmsg;
   1754 }
   1755 
   1756 /// The 'signcolumn' option is changed.
   1757 const char *did_set_signcolumn(optset_T *args)
   1758 {
   1759  win_T *win = (win_T *)args->os_win;
   1760  char **varp = (char **)args->os_varp;
   1761  const char *oldval = args->os_oldval.string.data;
   1762  if (check_signcolumn(*varp, varp == &win->w_p_scl ? win : NULL) != OK) {
   1763    return e_invarg;
   1764  }
   1765  // When changing the 'signcolumn' to or from 'number', recompute the
   1766  // width of the number column if 'number' or 'relativenumber' is set.
   1767  if ((*oldval == 'n' && *(oldval + 1) == 'u') || win->w_minscwidth == SCL_NUM) {
   1768    win->w_nrwidth_line_count = 0;
   1769  }
   1770  return NULL;
   1771 }
   1772 
   1773 /// The 'spellcapcheck' option is changed.
   1774 const char *did_set_spellcapcheck(optset_T *args)
   1775 {
   1776  win_T *win = (win_T *)args->os_win;
   1777  // When 'spellcapcheck' is set compile the regexp program.
   1778  return compile_cap_prog(win->w_s);
   1779 }
   1780 
   1781 /// The 'spellfile' option is changed.
   1782 const char *did_set_spellfile(optset_T *args)
   1783 {
   1784  char **varp = (char **)args->os_varp;
   1785 
   1786  // When there is a window for this buffer in which 'spell'
   1787  // is set load the wordlists.
   1788  if (!valid_spellfile(*varp)) {
   1789    return e_invarg;
   1790  }
   1791  return did_set_spell_option();
   1792 }
   1793 
   1794 /// The 'spelllang' option is changed.
   1795 const char *did_set_spelllang(optset_T *args)
   1796 {
   1797  char **varp = (char **)args->os_varp;
   1798 
   1799  // When there is a window for this buffer in which 'spell'
   1800  // is set load the wordlists.
   1801  if (!valid_spelllang(*varp)) {
   1802    return e_invarg;
   1803  }
   1804  return did_set_spell_option();
   1805 }
   1806 
   1807 /// The 'spelloptions' option is changed.
   1808 const char *did_set_spelloptions(optset_T *args)
   1809 {
   1810  win_T *win = (win_T *)args->os_win;
   1811  int opt_flags = args->os_flags;
   1812  const char *val = args->os_newval.string.data;
   1813 
   1814  if (!(opt_flags & OPT_LOCAL)
   1815      && opt_strings_flags(val, opt_spo_values, &spo_flags, true) != OK) {
   1816    return e_invarg;
   1817  }
   1818  if (!(opt_flags & OPT_GLOBAL)
   1819      && opt_strings_flags(val, opt_spo_values, &win->w_s->b_p_spo_flags, true) != OK) {
   1820    return e_invarg;
   1821  }
   1822  return NULL;
   1823 }
   1824 
   1825 /// The 'spellsuggest' option is changed.
   1826 const char *did_set_spellsuggest(optset_T *args FUNC_ATTR_UNUSED)
   1827 {
   1828  if (spell_check_sps() != OK) {
   1829    return e_invarg;
   1830  }
   1831  return NULL;
   1832 }
   1833 
   1834 /// The 'statuscolumn' option is changed.
   1835 const char *did_set_statuscolumn(optset_T *args)
   1836 {
   1837  return did_set_statustabline_rulerformat(args, false, true);
   1838 }
   1839 
   1840 /// The 'statusline' option is changed.
   1841 const char *did_set_statusline(optset_T *args)
   1842 {
   1843  return did_set_statustabline_rulerformat(args, false, false);
   1844 }
   1845 
   1846 /// The 'statusline', 'winbar', 'tabline', 'rulerformat' or 'statuscolumn' option is changed.
   1847 ///
   1848 /// @param rulerformat  true if the 'rulerformat' option is changed
   1849 /// @param statuscolumn  true if the 'statuscolumn' option is changed
   1850 static const char *did_set_statustabline_rulerformat(optset_T *args, bool rulerformat,
   1851                                                     bool statuscolumn)
   1852 {
   1853  win_T *win = (win_T *)args->os_win;
   1854  char **varp = (char **)args->os_varp;
   1855  if (rulerformat) {       // reset ru_wid first
   1856    ru_wid = 0;
   1857  } else if (statuscolumn) {
   1858    // reset 'statuscolumn' width
   1859    win->w_nrwidth_line_count = 0;
   1860  }
   1861  const char *errmsg = NULL;
   1862  char *s = *varp;
   1863  bool is_stl = args->os_idx == kOptStatusline;
   1864 
   1865  // reset statusline to default when setting global option and empty string is being set
   1866  if (is_stl
   1867      && ((args->os_flags & OPT_GLOBAL) || !(args->os_flags & OPT_LOCAL))
   1868      && s[0] == NUL) {
   1869    xfree(*varp);
   1870    *varp = xstrdup(get_option_default(args->os_idx, args->os_flags).data.string.data);
   1871    s = *varp;
   1872  }
   1873 
   1874  // handle floating window statusline changes
   1875  if (is_stl && win && win->w_floating) {
   1876    win_config_float(win, win->w_config);
   1877  }
   1878 
   1879  if (rulerformat && *s == '%') {
   1880    // set ru_wid if 'ruf' starts with "%99("
   1881    if (*++s == '-') {        // ignore a '-'
   1882      s++;
   1883    }
   1884    int wid = getdigits_int(&s, true, 0);
   1885    if (wid && *s == '(' && (errmsg = check_stl_option(p_ruf)) == NULL) {
   1886      ru_wid = wid;
   1887    } else {
   1888      // Validate the flags in 'rulerformat' only if it doesn't point to
   1889      // a custom function ("%!" flag).
   1890      if ((*varp)[1] != '!') {
   1891        errmsg = check_stl_option(p_ruf);
   1892      }
   1893    }
   1894  } else if (rulerformat || s[0] != '%' || s[1] != '!') {
   1895    // check 'statusline', 'winbar', 'tabline' or 'statuscolumn'
   1896    // only if it doesn't start with "%!"
   1897    errmsg = check_stl_option(s);
   1898  }
   1899  if (rulerformat && errmsg == NULL) {
   1900    comp_col();
   1901  }
   1902  return errmsg;
   1903 }
   1904 
   1905 /// The 'tabline' option is changed.
   1906 const char *did_set_tabline(optset_T *args)
   1907 {
   1908  return did_set_statustabline_rulerformat(args, false, false);
   1909 }
   1910 
   1911 /// The 'tagcase' option is changed.
   1912 const char *did_set_tagcase(optset_T *args)
   1913 {
   1914  buf_T *buf = (buf_T *)args->os_buf;
   1915  int opt_flags = args->os_flags;
   1916 
   1917  unsigned *flags;
   1918  char *p;
   1919 
   1920  if (opt_flags & OPT_LOCAL) {
   1921    p = buf->b_p_tc;
   1922    flags = &buf->b_tc_flags;
   1923  } else {
   1924    p = p_tc;
   1925    flags = &tc_flags;
   1926  }
   1927 
   1928  if ((opt_flags & OPT_LOCAL) && *p == NUL) {
   1929    // make the local value empty: use the global value
   1930    *flags = 0;
   1931  } else if (opt_strings_flags(p, opt_tc_values, flags, false) != OK) {
   1932    return e_invarg;
   1933  }
   1934  return NULL;
   1935 }
   1936 
   1937 /// The 'titlestring' or the 'iconstring' option is changed.
   1938 static const char *did_set_titleiconstring(optset_T *args, int flagval)
   1939 {
   1940  char **varp = (char **)args->os_varp;
   1941 
   1942  // NULL => statusline syntax
   1943  if (vim_strchr(*varp, '%') && check_stl_option(*varp) == NULL) {
   1944    stl_syntax |= flagval;
   1945  } else {
   1946    stl_syntax &= ~flagval;
   1947  }
   1948  did_set_title();
   1949 
   1950  return NULL;
   1951 }
   1952 
   1953 /// The 'titlestring' option is changed.
   1954 const char *did_set_titlestring(optset_T *args)
   1955 {
   1956  return did_set_titleiconstring(args, STL_IN_TITLE);
   1957 }
   1958 
   1959 /// The 'varsofttabstop' option is changed.
   1960 const char *did_set_varsofttabstop(optset_T *args)
   1961 {
   1962  buf_T *buf = (buf_T *)args->os_buf;
   1963  char **varp = (char **)args->os_varp;
   1964 
   1965  if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) {
   1966    XFREE_CLEAR(buf->b_p_vsts_array);
   1967    return NULL;
   1968  }
   1969 
   1970  for (char *cp = *varp; *cp; cp++) {
   1971    if (ascii_isdigit(*cp)) {
   1972      continue;
   1973    }
   1974    if (*cp == ',' && cp > *varp && *(cp - 1) != ',') {
   1975      continue;
   1976    }
   1977    return e_invarg;
   1978  }
   1979 
   1980  colnr_T *oldarray = buf->b_p_vsts_array;
   1981  if (tabstop_set(*varp, &(buf->b_p_vsts_array))) {
   1982    xfree(oldarray);
   1983  } else {
   1984    return e_invarg;
   1985  }
   1986  return NULL;
   1987 }
   1988 
   1989 /// The 'varstabstop' option is changed.
   1990 const char *did_set_vartabstop(optset_T *args)
   1991 {
   1992  buf_T *buf = (buf_T *)args->os_buf;
   1993  win_T *win = (win_T *)args->os_win;
   1994  char **varp = (char **)args->os_varp;
   1995 
   1996  if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) {
   1997    XFREE_CLEAR(buf->b_p_vts_array);
   1998    return NULL;
   1999  }
   2000 
   2001  for (char *cp = *varp; *cp; cp++) {
   2002    if (ascii_isdigit(*cp)) {
   2003      continue;
   2004    }
   2005    if (*cp == ',' && cp > *varp && *(cp - 1) != ',') {
   2006      continue;
   2007    }
   2008    return e_invarg;
   2009  }
   2010 
   2011  colnr_T *oldarray = buf->b_p_vts_array;
   2012  if (tabstop_set(*varp, &(buf->b_p_vts_array))) {
   2013    xfree(oldarray);
   2014    if (foldmethodIsIndent(win)) {
   2015      foldUpdateAll(win);
   2016    }
   2017  } else {
   2018    return e_invarg;
   2019  }
   2020  return NULL;
   2021 }
   2022 
   2023 /// The 'verbosefile' option is changed.
   2024 const char *did_set_verbosefile(optset_T *args)
   2025 {
   2026  verbose_stop();
   2027  if (*p_vfile != NUL && verbose_open() == FAIL) {
   2028    return (char *)e_invarg;
   2029  }
   2030  return NULL;
   2031 }
   2032 
   2033 /// The 'virtualedit' option is changed.
   2034 const char *did_set_virtualedit(optset_T *args)
   2035 {
   2036  win_T *win = (win_T *)args->os_win;
   2037 
   2038  char *ve = p_ve;
   2039  unsigned *flags = &ve_flags;
   2040 
   2041  if (args->os_flags & OPT_LOCAL) {
   2042    ve = win->w_p_ve;
   2043    flags = &win->w_ve_flags;
   2044  }
   2045 
   2046  if ((args->os_flags & OPT_LOCAL) && *ve == NUL) {
   2047    // make the local value empty: use the global value
   2048    *flags = 0;
   2049  } else {
   2050    if (opt_strings_flags(ve, opt_ve_values, flags, true) != OK) {
   2051      return e_invarg;
   2052    } else if (strcmp(ve, args->os_oldval.string.data) != 0) {
   2053      // Recompute cursor position in case the new 've' setting
   2054      // changes something.
   2055      validate_virtcol(win);
   2056      coladvance(win, win->w_virtcol);
   2057    }
   2058  }
   2059  return NULL;
   2060 }
   2061 
   2062 /// The 'whichwrap' option is changed.
   2063 const char *did_set_whichwrap(optset_T *args)
   2064 {
   2065  char **varp = (char **)args->os_varp;
   2066 
   2067  // Add ',' to the list flags because 'whichwrap' is a flag
   2068  // list that is comma-separated.
   2069  return did_set_option_listflag(*varp, WW_ALL ",", args->os_errbuf, args->os_errbuflen);
   2070 }
   2071 
   2072 int expand_set_whichwrap(optexpand_T *args, int *numMatches, char ***matches)
   2073 {
   2074  return expand_set_opt_listflag(args, WW_ALL, numMatches, matches);
   2075 }
   2076 
   2077 /// The 'wildmode' option is changed.
   2078 const char *did_set_wildmode(optset_T *args FUNC_ATTR_UNUSED)
   2079 {
   2080  if (check_opt_wim() == FAIL) {
   2081    return e_invarg;
   2082  }
   2083  return NULL;
   2084 }
   2085 
   2086 /// The 'winbar' option is changed.
   2087 const char *did_set_winbar(optset_T *args)
   2088 {
   2089  return did_set_statustabline_rulerformat(args, false, false);
   2090 }
   2091 
   2092 static bool parse_border_opt(char *border_opt)
   2093 {
   2094  WinConfig fconfig = WIN_CONFIG_INIT;
   2095  Error err = ERROR_INIT;
   2096  bool result = true;
   2097  if (!parse_winborder(&fconfig, border_opt, &err)) {
   2098    result = false;
   2099  }
   2100  api_clear_error(&err);
   2101  return result;
   2102 }
   2103 
   2104 /// The 'winborder' option is changed.
   2105 const char *did_set_winborder(optset_T *args)
   2106 {
   2107  if (!parse_border_opt(p_winborder)) {
   2108    return e_invarg;
   2109  }
   2110  return NULL;
   2111 }
   2112 
   2113 const char *did_set_pumborder(optset_T *args)
   2114 {
   2115  if (!parse_border_opt(p_pumborder)) {
   2116    return e_invarg;
   2117  }
   2118  return NULL;
   2119 }
   2120 
   2121 /// The 'winhighlight' option is changed.
   2122 const char *did_set_winhighlight(optset_T *args)
   2123 {
   2124  win_T *win = (win_T *)args->os_win;
   2125  char **varp = (char **)args->os_varp;
   2126  if (!parse_winhl_opt(*varp, varp == &win->w_p_winhl ? win : NULL)) {
   2127    return e_invarg;
   2128  }
   2129  return NULL;
   2130 }
   2131 
   2132 int expand_set_winhighlight(optexpand_T *args, int *numMatches, char ***matches)
   2133 {
   2134  return expand_set_opt_generic(args, get_highlight_name, numMatches, matches);
   2135 }
   2136 
   2137 /// Handle an option that can be a range of string values.
   2138 /// Set a flag in "*flagp" for each string present.
   2139 ///
   2140 /// @param val  new value
   2141 /// @param values  array of valid string values
   2142 /// @param list  when true: accept a list of values
   2143 ///
   2144 /// @return  OK for correct value, FAIL otherwise. Empty is always OK.
   2145 static int opt_strings_flags(const char *val, const char **values, unsigned *flagp, bool list)
   2146 {
   2147  unsigned new_flags = 0;
   2148 
   2149  // If not list and val is empty, then force one iteration of the while loop
   2150  bool iter_one = (*val == NUL) && !list;
   2151 
   2152  while (*val || iter_one) {
   2153    for (unsigned i = 0;; i++) {
   2154      if (values[i] == NULL) {          // val not found in values[]
   2155        return FAIL;
   2156      }
   2157 
   2158      size_t len = strlen(values[i]);
   2159      if (strncmp(values[i], val, len) == 0
   2160          && ((list && val[len] == ',') || val[len] == NUL)) {
   2161        val += len + (val[len] == ',');
   2162        assert(i < sizeof(new_flags) * 8);
   2163        new_flags |= (1U << i);
   2164        break;                  // check next item in val list
   2165      }
   2166    }
   2167    if (iter_one) {
   2168      break;
   2169    }
   2170  }
   2171  if (flagp != NULL) {
   2172    *flagp = new_flags;
   2173  }
   2174 
   2175  return OK;
   2176 }
   2177 
   2178 /// @return  OK if "p" is a valid fileformat name, FAIL otherwise.
   2179 int check_ff_value(char *p)
   2180 {
   2181  return opt_strings_flags(p, opt_ff_values, NULL, false);
   2182 }
   2183 
   2184 static const char e_conflicts_with_value_of_listchars[]
   2185  = N_("E834: Conflicts with value of 'listchars'");
   2186 static const char e_conflicts_with_value_of_fillchars[]
   2187  = N_("E835: Conflicts with value of 'fillchars'");
   2188 
   2189 /// Calls mb_cptr2char_adv(p) and returns the character.
   2190 /// If "p" starts with "\x", "\u" or "\U" the hex or unicode value is used.
   2191 /// Returns 0 for invalid hex or invalid UTF-8 byte.
   2192 static schar_T get_encoded_char_adv(const char **p)
   2193 {
   2194  const char *s = *p;
   2195 
   2196  if (s[0] == '\\' && (s[1] == 'x' || s[1] == 'u' || s[1] == 'U')) {
   2197    int64_t num = 0;
   2198    for (int bytes = s[1] == 'x' ? 1 : s[1] == 'u' ? 2 : 4; bytes > 0; bytes--) {
   2199      *p += 2;
   2200      int n = hexhex2nr(*p);
   2201      if (n < 0) {
   2202        return 0;
   2203      }
   2204      num = num * 256 + n;
   2205    }
   2206    *p += 2;
   2207    return (char2cells((int)num) > 1) ? 0 : schar_from_char((int)num);
   2208  }
   2209 
   2210  int clen = utfc_ptr2len(s);
   2211  int firstc;
   2212  schar_T c = utfc_ptr2schar(s, &firstc);
   2213  *p += clen;
   2214  // Invalid UTF-8 byte or doublewidth not allowed
   2215  return ((clen == 1 && firstc > 127) || char2cells(firstc) > 1) ? 0 : c;
   2216 }
   2217 
   2218 struct chars_tab {
   2219  schar_T *cp;           ///< char value
   2220  String name;           ///< char id
   2221  const char *def;       ///< default value
   2222  const char *fallback;  ///< default value when "def" isn't single-width
   2223 };
   2224 
   2225 #define CHARSTAB_ENTRY(cp, name, def, fallback) \
   2226  { (cp), { name, STRLEN_LITERAL(name) }, def, fallback }
   2227 
   2228 static fcs_chars_T fcs_chars;
   2229 static const struct chars_tab fcs_tab[] = {
   2230  CHARSTAB_ENTRY(&fcs_chars.stl,        "stl",       " ",  NULL),
   2231  CHARSTAB_ENTRY(&fcs_chars.stlnc,      "stlnc",     " ",  NULL),
   2232  CHARSTAB_ENTRY(&fcs_chars.wbr,        "wbr",       " ",  NULL),
   2233  CHARSTAB_ENTRY(&fcs_chars.horiz,      "horiz",     "─",  "-"),
   2234  CHARSTAB_ENTRY(&fcs_chars.horizup,    "horizup",   "â”´",  "-"),
   2235  CHARSTAB_ENTRY(&fcs_chars.horizdown,  "horizdown", "┬",  "-"),
   2236  CHARSTAB_ENTRY(&fcs_chars.vert,       "vert",      "│",  "|"),
   2237  CHARSTAB_ENTRY(&fcs_chars.vertleft,   "vertleft",  "┤",  "|"),
   2238  CHARSTAB_ENTRY(&fcs_chars.vertright,  "vertright", "├",  "|"),
   2239  CHARSTAB_ENTRY(&fcs_chars.verthoriz,  "verthoriz", "┼",  "+"),
   2240  CHARSTAB_ENTRY(&fcs_chars.fold,       "fold",      "·",  "-"),
   2241  CHARSTAB_ENTRY(&fcs_chars.foldopen,   "foldopen",  "-",  NULL),
   2242  CHARSTAB_ENTRY(&fcs_chars.foldclosed, "foldclose", "+",  NULL),
   2243  CHARSTAB_ENTRY(&fcs_chars.foldsep,    "foldsep",   "│",  "|"),
   2244  CHARSTAB_ENTRY(&fcs_chars.foldinner,  "foldinner", NULL, NULL),
   2245  CHARSTAB_ENTRY(&fcs_chars.diff,       "diff",      "-",  NULL),
   2246  CHARSTAB_ENTRY(&fcs_chars.msgsep,     "msgsep",    " ",  NULL),
   2247  CHARSTAB_ENTRY(&fcs_chars.eob,        "eob",       "~",  NULL),
   2248  CHARSTAB_ENTRY(&fcs_chars.lastline,   "lastline",  "@",  NULL),
   2249  CHARSTAB_ENTRY(&fcs_chars.trunc,      "trunc",     ">",  NULL),
   2250  CHARSTAB_ENTRY(&fcs_chars.truncrl,    "truncrl",   "<",  NULL),
   2251 };
   2252 
   2253 static lcs_chars_T lcs_chars;
   2254 static const struct chars_tab lcs_tab[] = {
   2255  CHARSTAB_ENTRY(&lcs_chars.eol,      "eol",            NULL, NULL),
   2256  CHARSTAB_ENTRY(&lcs_chars.ext,      "extends",        NULL, NULL),
   2257  CHARSTAB_ENTRY(&lcs_chars.nbsp,     "nbsp",           NULL, NULL),
   2258  CHARSTAB_ENTRY(&lcs_chars.prec,     "precedes",       NULL, NULL),
   2259  CHARSTAB_ENTRY(&lcs_chars.space,    "space",          NULL, NULL),
   2260  CHARSTAB_ENTRY(&lcs_chars.tab2,     "tab",            NULL, NULL),
   2261  CHARSTAB_ENTRY(&lcs_chars.leadtab2, "leadtab",        NULL, NULL),
   2262  CHARSTAB_ENTRY(&lcs_chars.lead,     "lead",           NULL, NULL),
   2263  CHARSTAB_ENTRY(&lcs_chars.trail,    "trail",          NULL, NULL),
   2264  CHARSTAB_ENTRY(&lcs_chars.conceal,  "conceal",        NULL, NULL),
   2265  CHARSTAB_ENTRY(NULL,                "multispace",     NULL, NULL),
   2266  CHARSTAB_ENTRY(NULL,                "leadmultispace", NULL, NULL),
   2267 };
   2268 
   2269 #undef CHARSTAB_ENTRY
   2270 
   2271 static char *field_value_err(char *errbuf, size_t errbuflen, const char *fmt, const char *field)
   2272 {
   2273  if (errbuf == NULL) {
   2274    return "";
   2275  }
   2276  vim_snprintf(errbuf, errbuflen, _(fmt), field);
   2277  return errbuf;
   2278 }
   2279 
   2280 /// Handle setting 'listchars' or 'fillchars'.
   2281 /// Assume monocell characters
   2282 ///
   2283 /// @param value      points to either the global or the window-local value.
   2284 /// @param what       kListchars or kFillchars
   2285 /// @param apply      if false, do not store the flags, only check for errors.
   2286 /// @param errbuf     buffer for error message, can be NULL if it won't be used.
   2287 /// @param errbuflen  size of error buffer.
   2288 ///
   2289 /// @return error message, NULL if it's OK.
   2290 const char *set_chars_option(win_T *wp, const char *value, CharsOption what, bool apply,
   2291                             char *errbuf, size_t errbuflen)
   2292 {
   2293  const char *last_multispace = NULL;   // Last occurrence of "multispace:"
   2294  const char *last_lmultispace = NULL;  // Last occurrence of "leadmultispace:"
   2295  int multispace_len = 0;           // Length of lcs-multispace string
   2296  int lead_multispace_len = 0;      // Length of lcs-leadmultispace string
   2297 
   2298  const struct chars_tab *tab;
   2299  int entries;
   2300  if (what == kListchars) {
   2301    tab = lcs_tab;
   2302    entries = ARRAY_SIZE(lcs_tab);
   2303    if (wp->w_p_lcs[0] == NUL) {
   2304      value = p_lcs;  // local value is empty, use the global value
   2305    }
   2306  } else {
   2307    tab = fcs_tab;
   2308    entries = ARRAY_SIZE(fcs_tab);
   2309    if (wp->w_p_fcs[0] == NUL) {
   2310      value = p_fcs;  // local value is empty, use the global value
   2311    }
   2312  }
   2313 
   2314  // first round: check for valid value, second round: assign values
   2315  for (int round = 0; round <= (apply ? 1 : 0); round++) {
   2316    if (round > 0) {
   2317      // After checking that the value is valid: set defaults
   2318      for (int i = 0; i < entries; i++) {
   2319        if (tab[i].cp != NULL) {
   2320          // XXX: Characters taking 2 columns is forbidden (TUI limitation?).
   2321          // Set old defaults in this case.
   2322          *(tab[i].cp) = schar_from_str((tab[i].def && ptr2cells(tab[i].def) == 1)
   2323                                        ? tab[i].def : tab[i].fallback);
   2324        }
   2325      }
   2326 
   2327      if (what == kListchars) {
   2328        lcs_chars.tab1 = NUL;
   2329        lcs_chars.tab3 = NUL;
   2330        lcs_chars.leadtab1 = NUL;
   2331        lcs_chars.leadtab3 = NUL;
   2332 
   2333        if (multispace_len > 0) {
   2334          lcs_chars.multispace = xmalloc(((size_t)multispace_len + 1) * sizeof(schar_T));
   2335          lcs_chars.multispace[multispace_len] = NUL;
   2336        } else {
   2337          lcs_chars.multispace = NULL;
   2338        }
   2339 
   2340        if (lead_multispace_len > 0) {
   2341          lcs_chars.leadmultispace = xmalloc(((size_t)lead_multispace_len + 1) * sizeof(schar_T));
   2342          lcs_chars.leadmultispace[lead_multispace_len] = NUL;
   2343        } else {
   2344          lcs_chars.leadmultispace = NULL;
   2345        }
   2346      }
   2347    }
   2348 
   2349    const char *p = value;
   2350    while (*p) {
   2351      int i;
   2352      for (i = 0; i < entries; i++) {
   2353        if (!(strncmp(p, tab[i].name.data,
   2354                      tab[i].name.size) == 0 && p[tab[i].name.size] == ':')) {
   2355          continue;
   2356        }
   2357 
   2358        const char *s = p + tab[i].name.size + 1;
   2359 
   2360        if (what == kListchars && strcmp(tab[i].name.data, "multispace") == 0) {
   2361          if (round == 0) {
   2362            // Get length of lcs-multispace string in the first round
   2363            last_multispace = p;
   2364            multispace_len = 0;
   2365            while (*s != NUL && *s != ',') {
   2366              schar_T c1 = get_encoded_char_adv(&s);
   2367              if (c1 == 0) {
   2368                return field_value_err(errbuf, errbuflen,
   2369                                       e_wrong_character_width_for_field_str,
   2370                                       tab[i].name.data);
   2371              }
   2372              multispace_len++;
   2373            }
   2374            if (multispace_len == 0) {
   2375              // lcs-multispace cannot be an empty string
   2376              return field_value_err(errbuf, errbuflen,
   2377                                     e_wrong_number_of_characters_for_field_str,
   2378                                     tab[i].name.data);
   2379            }
   2380          } else {
   2381            int multispace_pos = 0;
   2382            while (*s != NUL && *s != ',') {
   2383              schar_T c1 = get_encoded_char_adv(&s);
   2384              if (p == last_multispace) {
   2385                lcs_chars.multispace[multispace_pos++] = c1;
   2386              }
   2387            }
   2388          }
   2389          p = s;
   2390          break;
   2391        }
   2392 
   2393        if (what == kListchars && strcmp(tab[i].name.data, "leadmultispace") == 0) {
   2394          if (round == 0) {
   2395            // Get length of lcs-leadmultispace string in first round
   2396            last_lmultispace = p;
   2397            lead_multispace_len = 0;
   2398            while (*s != NUL && *s != ',') {
   2399              schar_T c1 = get_encoded_char_adv(&s);
   2400              if (c1 == 0) {
   2401                return field_value_err(errbuf, errbuflen,
   2402                                       e_wrong_character_width_for_field_str,
   2403                                       tab[i].name.data);
   2404              }
   2405              lead_multispace_len++;
   2406            }
   2407            if (lead_multispace_len == 0) {
   2408              // lcs-leadmultispace cannot be an empty string
   2409              return field_value_err(errbuf, errbuflen,
   2410                                     e_wrong_number_of_characters_for_field_str,
   2411                                     tab[i].name.data);
   2412            }
   2413          } else {
   2414            int multispace_pos = 0;
   2415            while (*s != NUL && *s != ',') {
   2416              schar_T c1 = get_encoded_char_adv(&s);
   2417              if (p == last_lmultispace) {
   2418                lcs_chars.leadmultispace[multispace_pos++] = c1;
   2419              }
   2420            }
   2421          }
   2422          p = s;
   2423          break;
   2424        }
   2425 
   2426        if (*s == NUL) {
   2427          return field_value_err(errbuf, errbuflen,
   2428                                 e_wrong_number_of_characters_for_field_str,
   2429                                 tab[i].name.data);
   2430        }
   2431        schar_T c1 = get_encoded_char_adv(&s);
   2432        if (c1 == 0) {
   2433          return field_value_err(errbuf, errbuflen,
   2434                                 e_wrong_character_width_for_field_str,
   2435                                 tab[i].name.data);
   2436        }
   2437        schar_T c2 = 0;
   2438        schar_T c3 = 0;
   2439        if (tab[i].cp == &lcs_chars.tab2 || tab[i].cp == &lcs_chars.leadtab2) {
   2440          if (*s == NUL) {
   2441            return field_value_err(errbuf, errbuflen,
   2442                                   e_wrong_number_of_characters_for_field_str,
   2443                                   tab[i].name.data);
   2444          }
   2445          c2 = get_encoded_char_adv(&s);
   2446          if (c2 == 0) {
   2447            return field_value_err(errbuf, errbuflen,
   2448                                   e_wrong_character_width_for_field_str,
   2449                                   tab[i].name.data);
   2450          }
   2451          if (!(*s == ',' || *s == NUL)) {
   2452            c3 = get_encoded_char_adv(&s);
   2453            if (c3 == 0) {
   2454              return field_value_err(errbuf, errbuflen,
   2455                                     e_wrong_character_width_for_field_str,
   2456                                     tab[i].name.data);
   2457            }
   2458          }
   2459        }
   2460 
   2461        if (*s == ',' || *s == NUL) {
   2462          if (round > 0) {
   2463            if (tab[i].cp == &lcs_chars.tab2) {
   2464              lcs_chars.tab1 = c1;
   2465              lcs_chars.tab2 = c2;
   2466              lcs_chars.tab3 = c3;
   2467            } else if (tab[i].cp == &lcs_chars.leadtab2) {
   2468              lcs_chars.leadtab1 = c1;
   2469              lcs_chars.leadtab2 = c2;
   2470              lcs_chars.leadtab3 = c3;
   2471            } else if (tab[i].cp != NULL) {
   2472              *(tab[i].cp) = c1;
   2473            }
   2474          }
   2475          p = s;
   2476          break;
   2477        } else {
   2478          return field_value_err(errbuf, errbuflen,
   2479                                 e_wrong_number_of_characters_for_field_str,
   2480                                 tab[i].name.data);
   2481        }
   2482      }
   2483 
   2484      if (i == entries) {
   2485        return e_invarg;
   2486      }
   2487 
   2488      if (*p == ',') {
   2489        p++;
   2490      }
   2491    }
   2492  }
   2493 
   2494  if (what == kListchars && lcs_chars.leadtab2 != NUL && lcs_chars.tab2 == NUL) {
   2495    return e_leadtab_requires_tab;
   2496  }
   2497 
   2498  if (apply) {
   2499    if (what == kListchars) {
   2500      xfree(wp->w_p_lcs_chars.multispace);
   2501      xfree(wp->w_p_lcs_chars.leadmultispace);
   2502      wp->w_p_lcs_chars = lcs_chars;
   2503    } else {
   2504      wp->w_p_fcs_chars = fcs_chars;
   2505    }
   2506  }
   2507 
   2508  return NULL;          // no error
   2509 }
   2510 
   2511 /// Function given to ExpandGeneric() to obtain possible arguments of the
   2512 /// 'fillchars' option.
   2513 char *get_fillchars_name(expand_T *xp FUNC_ATTR_UNUSED, int idx)
   2514 {
   2515  if (idx < 0 || idx >= (int)ARRAY_SIZE(fcs_tab)) {
   2516    return NULL;
   2517  }
   2518 
   2519  return fcs_tab[idx].name.data;
   2520 }
   2521 
   2522 /// Function given to ExpandGeneric() to obtain possible arguments of the
   2523 /// 'listchars' option.
   2524 char *get_listchars_name(expand_T *xp FUNC_ATTR_UNUSED, int idx)
   2525 {
   2526  if (idx < 0 || idx >= (int)ARRAY_SIZE(lcs_tab)) {
   2527    return NULL;
   2528  }
   2529 
   2530  return lcs_tab[idx].name.data;
   2531 }
   2532 
   2533 /// Check all global and local values of 'listchars' and 'fillchars'.
   2534 /// May set different defaults in case character widths change.
   2535 ///
   2536 /// @return  an untranslated error message if any of them is invalid, NULL otherwise.
   2537 const char *check_chars_options(void)
   2538 {
   2539  if (set_chars_option(curwin, p_lcs, kListchars, false, NULL, 0) != NULL) {
   2540    return e_conflicts_with_value_of_listchars;
   2541  }
   2542  if (set_chars_option(curwin, p_fcs, kFillchars, false, NULL, 0) != NULL) {
   2543    return e_conflicts_with_value_of_fillchars;
   2544  }
   2545  FOR_ALL_TAB_WINDOWS(tp, wp) {
   2546    if (set_chars_option(wp, wp->w_p_lcs, kListchars, true, NULL, 0) != NULL) {
   2547      return e_conflicts_with_value_of_listchars;
   2548    }
   2549    if (set_chars_option(wp, wp->w_p_fcs, kFillchars, true, NULL, 0) != NULL) {
   2550      return e_conflicts_with_value_of_fillchars;
   2551    }
   2552  }
   2553  return NULL;
   2554 }