patch 9.0.1958: cannot complete option values

Problem:  cannot complete option values
Solution: Add completion functions for several options

Add cmdline tab-completion for setting string options

Add tab-completion for setting string options on the cmdline using
`:set=` (along with `:set+=` and `:set-=`).

The existing tab completion for setting options currently only works
when nothing is typed yet, and it only fills in with the existing value,
e.g. when the user does `:set diffopt=<Tab>` it will be completed to
`set diffopt=internal,filler,closeoff` and nothing else. This isn't too
useful as a user usually wants auto-complete to suggest all the possible
values, such as 'iblank', or 'algorithm:patience'.

For set= and set+=, this adds a new optional callback function for each
option that can be invoked when doing completion. This allows for each
option to have control over how completion works. For example, in
'diffopt', it will suggest the default enumeration, but if `algorithm:`
is selected, it will further suggest different algorithm types like
'meyers' and 'patience'. When using set=, the existing option value will
be filled in as the first choice to preserve the existing behavior. When
using set+= this won't happen as it doesn't make sense.

For flag list options (e.g. 'mouse' and 'guioptions'), completion will
take into account existing typed values (and in the case of set+=, the
existing option value) to make sure it doesn't suggest duplicates.

For set-=, there is a new `ExpandSettingSubtract` function which will
handle flag list and comma-separated options smartly, by only suggesting
values that currently exist in the option.

Note that Vim has some existing code that adds special handling for
'filetype', 'syntax', and misc dir options like 'backupdir'. This change
preserves them as they already work, instead of converting to the new
callback API for each option.

closes: #13182

Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Yee Cheng Chin <ychin.git@gmail.com>
diff --git a/src/optionstr.c b/src/optionstr.c
index 9b4464d..010bec7 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -24,8 +24,21 @@
 				 "hangul", "insertmode", "lang", "mess",
 				 "showmatch", "operator", "register", "shell",
 				 "spell", "term", "wildmode", NULL};
+#if defined(FEAT_LINEBREAK)
+// Note: Keep this in sync with briopt_check()
+static char *(p_briopt_values[]) = {"shift:", "min:", "sbr", "list:", "column:", NULL};
+#endif
+#if defined(FEAT_DIFF)
+// Note: Keep this in sync with diffopt_changed()
+static char *(p_dip_values[]) = {"filler", "context:", "iblank", "icase", "iwhite", "iwhiteall", "iwhiteeol", "horizontal", "vertical", "closeoff", "hiddenoff", "foldcolumn:", "followwrap", "internal", "indent-heuristic", "algorithm:", NULL};
+static char *(p_dip_algorithm_values[]) = {"myers", "minimal", "patience", "histogram", NULL};
+#endif
 static char *(p_nf_values[]) = {"bin", "octal", "hex", "alpha", "unsigned", NULL};
 static char *(p_ff_values[]) = {FF_UNIX, FF_DOS, FF_MAC, NULL};
+#ifdef FEAT_CLIPBOARD
+// Note: Keep this in sync with did_set_clipboard()
+static char *(p_cb_values[]) = {"unnamed", "unnamedplus", "autoselect", "autoselectplus", "autoselectml", "html", "exclude:", NULL};
+#endif
 #ifdef FEAT_CRYPT
 static char *(p_cm_values[]) = {"zip", "blowfish", "blowfish2",
  # ifdef FEAT_SODIUM
@@ -34,6 +47,10 @@
     NULL};
 #endif
 static char *(p_cmp_values[]) = {"internal", "keepascii", NULL};
+#ifdef FEAT_SYN_HL
+// Note: Keep this in sync with fill_culopt_flags()
+static char *(p_culopt_values[]) = {"line", "screenline", "number", "both", NULL};
+#endif
 static char *(p_dy_values[]) = {"lastline", "truncate", "uhex", NULL};
 static char *(p_jop_values[]) = {"stack", NULL};
 #ifdef FEAT_FOLDING
@@ -41,6 +58,18 @@
 				 "quickfix", "search", "tag", "insert",
 				 "undo", "jump", NULL};
 #endif
+// Note: Keep this in sync with match_keyprotocol()
+static char *(p_kpc_protocol_values[]) = {"none", "mok2", "kitty", NULL};
+#ifdef FEAT_PROP_POPUP
+// Note: Keep this in sync with parse_popup_option()
+static char *(p_popup_option_values[]) = {"height:", "width:", "highlight:", "border:", "align:", NULL};
+static char *(p_popup_option_border_values[]) = {"on", "off", NULL};
+static char *(p_popup_option_align_values[]) = {"item", "menu", NULL};
+#endif
+#if defined(FEAT_SPELL)
+// Note: Keep this in sync with spell_check_sps()
+static char *(p_sps_values[]) = {"best", "fast", "double", "expr:", "file:", "timeout:", NULL};
+#endif
 #ifdef FEAT_SESSION
 // Also used for 'viewoptions'!  Keep in sync with SSOP_ flags.
 static char *(p_ssop_values[]) = {"buffers", "winpos", "resize", "winsize",
@@ -62,6 +91,8 @@
 static char *(p_ttym_values[]) = {"xterm", "xterm2", "dec", "netterm", "jsbterm", "pterm", "urxvt", "sgr", NULL};
 #endif
 static char *(p_ve_values[]) = {"block", "insert", "all", "onemore", "none", "NONE", NULL};
+// Note: Keep this in sync with check_opt_wim()
+static char *(p_wim_values[]) = {"full", "longest", "list", "lastused", NULL};
 static char *(p_wop_values[]) = {"fuzzy", "tagfile", "pum", NULL};
 #ifdef FEAT_WAK
 static char *(p_wak_values[]) = {"yes", "menu", "no", NULL};
@@ -700,6 +731,198 @@
 }
 
 /*
+ * Expand an option that accepts a list of string values.
+ */
+    int
+expand_set_opt_string(
+	optexpand_T *args,
+	char **values,
+	int numValues,
+	int *numMatches,
+	char_u ***matches)
+{
+    char_u	*p;
+    regmatch_T	*regmatch = args->oe_regmatch;
+    int		include_orig_val = args->oe_include_orig_val;
+    char_u	*option_val = args->oe_opt_value;
+
+    // Assume numValues is small since they are fixed enums, so just allocate
+    // upfront instead of needing two passes to calculate output size.
+    *matches = ALLOC_MULT(char_u *, numValues + 1);
+    if (*matches == NULL)
+	return FAIL;
+
+    int count = 0;
+
+    if (include_orig_val && *option_val != NUL)
+    {
+	p = vim_strsave(option_val);
+	if (p == NULL)
+	{
+	    VIM_CLEAR(*matches);
+	    return FAIL;
+	}
+	(*matches)[count++] = p;
+    }
+
+    for (char **val = values; *val != NULL; val++)
+    {
+	if (include_orig_val && *option_val != NUL)
+	{
+	    if (STRCMP((char_u*)*val, option_val) == 0)
+		continue;
+	}
+	if (vim_regexec(regmatch, (char_u*)(*val), (colnr_T)0))
+	{
+	    p = vim_strsave((char_u*)*val);
+	    if (p == NULL)
+	    {
+		if (count == 0)
+		{
+		    VIM_CLEAR(*matches);
+		    return FAIL;
+		}
+		else
+		    break;
+	    }
+	    (*matches)[count++] = p;
+	}
+    }
+    if (count == 0)
+    {
+	VIM_CLEAR(*matches);
+	return FAIL;
+    }
+    *numMatches = count;
+    return OK;
+}
+
+static char_u *set_opt_callback_orig_option = NULL;
+static char_u *((*set_opt_callback_func)(expand_T *, int));
+
+/*
+ * Callback used by expand_set_opt_generic to also include the original value.
+ */
+    static char_u *
+expand_set_opt_callback(expand_T *xp, int idx)
+{
+    if (idx == 0)
+    {
+	if (set_opt_callback_orig_option != NULL)
+	    return set_opt_callback_orig_option;
+	else
+	    return (char_u *)""; // empty strings are ignored
+    }
+    return set_opt_callback_func(xp, idx - 1);
+}
+
+/*
+ * Expand an option with a callback that iterates through a list of possible names.
+ */
+    int
+expand_set_opt_generic(
+	optexpand_T *args,
+	char_u *((*func)(expand_T *, int)),
+	int *numMatches,
+	char_u ***matches)
+{
+    int ret;
+
+    set_opt_callback_orig_option = args->oe_include_orig_val ?
+	args->oe_opt_value : NULL;
+    set_opt_callback_func = func;
+
+    ret = ExpandGeneric(
+	    (char_u*)"", // not using fuzzy as currently EXPAND_STRING_SETTING doesn't use it
+	    args->oe_xp,
+	    args->oe_regmatch,
+	    matches,
+	    numMatches,
+	    expand_set_opt_callback,
+	    FALSE);
+
+    set_opt_callback_orig_option = NULL;
+    set_opt_callback_func = NULL;
+    return ret;
+}
+
+
+/*
+ * Expand an option which is a list of flags.
+ */
+    int
+expand_set_opt_listflag(
+	optexpand_T *args,
+	char_u *flags,
+	int *numMatches,
+	char_u ***matches)
+{
+    char_u  *p;
+    char_u  *option_val = args->oe_opt_value;
+    char_u  *cmdline_val = args->oe_set_arg;
+    int	    append = args->oe_append;
+    int	    include_orig_val = args->oe_include_orig_val && (*option_val != NUL);
+
+    int num_flags = STRLEN(flags);
+
+    // Assume we only have small number of flags, so just allocate max size.
+    *matches = ALLOC_MULT(char_u *, num_flags + 1);
+    if (*matches == NULL)
+	return FAIL;
+
+    int count = 0;
+
+    if (include_orig_val)
+    {
+	p = vim_strsave(option_val);
+	if (p == NULL)
+	{
+	    VIM_CLEAR(*matches);
+	    return FAIL;
+	}
+	(*matches)[count++] = p;
+    }
+
+    for (char_u *flag = flags; *flag != NUL; flag++)
+    {
+	if (append && vim_strchr(option_val, *flag) != NULL)
+	    continue;
+
+	if (vim_strchr(cmdline_val, *flag) == NULL)
+	{
+	    if (include_orig_val
+		    && option_val[1] == NUL
+		    && *flag == option_val[0])
+	    {
+		// This value is already used as the first choice as it's the
+		// existing flag. Just skip it to avoid duplicate.
+		continue;
+	    }
+	    p = vim_strnsave(flag, 1);
+	    if (p == NULL)
+	    {
+		if (count == 0)
+		{
+		    VIM_CLEAR(*matches);
+		    return FAIL;
+		}
+		else
+		    break;
+	    }
+	    (*matches)[count++] = p;
+	}
+    }
+
+    if (count == 0)
+    {
+	VIM_CLEAR(*matches);
+	return FAIL;
+    }
+    *numMatches = count;
+    return OK;
+}
+
+/*
  * The 'ambiwidth' option is changed.
  */
     char *
@@ -711,6 +934,17 @@
     return check_chars_options();
 }
 
+    int
+expand_set_ambiwidth(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_ambw_values,
+	    sizeof(p_ambw_values) / sizeof(p_ambw_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 /*
  * The 'background' option is changed.
  */
@@ -747,6 +981,17 @@
     return NULL;
 }
 
+    int
+expand_set_background(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_bg_values,
+	    sizeof(p_bg_values) / sizeof(p_bg_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 /*
  * The 'backspace' option is changed.
  */
@@ -764,6 +1009,17 @@
     return NULL;
 }
 
+    int
+expand_set_backspace(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_bs_values,
+	    sizeof(p_bs_values) / sizeof(p_bs_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 /*
  * The 'backupcopy' option is changed.
  */
@@ -801,6 +1057,17 @@
     return errmsg;
 }
 
+    int
+expand_set_backupcopy(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_bkc_values,
+	    sizeof(p_bkc_values) / sizeof(p_bkc_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 /*
  * The 'backupext' or the 'patchmode' option is changed.
  */
@@ -823,6 +1090,17 @@
     return did_set_opt_flags(p_bo, p_bo_values, &bo_flags, TRUE);
 }
 
+    int
+expand_set_belloff(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_bo_values,
+	    sizeof(p_bo_values) / sizeof(p_bo_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 #if defined(FEAT_LINEBREAK) || defined(PROTO)
 /*
  * The 'breakindentopt' option is changed.
@@ -840,6 +1118,17 @@
 
     return errmsg;
 }
+
+    int
+expand_set_breakindentopt(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_briopt_values,
+	    sizeof(p_briopt_values) / sizeof(p_briopt_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
 #endif
 
 #if defined(FEAT_BROWSE) || defined(PROTO)
@@ -855,6 +1144,17 @@
 
     return NULL;
 }
+
+    int
+expand_set_browsedir(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_bsdir_values,
+	    sizeof(p_bsdir_values) / sizeof(p_bsdir_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
 #endif
 
 /*
@@ -866,6 +1166,17 @@
     return did_set_opt_strings(curbuf->b_p_bh, p_bufhidden_values, FALSE);
 }
 
+    int
+expand_set_bufhidden(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_bufhidden_values,
+	    sizeof(p_bufhidden_values) / sizeof(p_bufhidden_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 /*
  * The 'buftype' option is changed.
  */
@@ -886,6 +1197,17 @@
     return NULL;
 }
 
+    int
+expand_set_buftype(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_buftype_values,
+	    sizeof(p_buftype_values) / sizeof(p_buftype_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 /*
  * The 'casemap' option is changed.
  */
@@ -895,6 +1217,30 @@
     return did_set_opt_flags(p_cmp, p_cmp_values, &cmp_flags, TRUE);
 }
 
+    int
+expand_set_casemap(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_cmp_values,
+	    sizeof(p_cmp_values) / sizeof(p_cmp_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
+#if defined(FEAT_CLIPBOARD) || defined(PROTO)
+    int
+expand_set_clipboard(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_cb_values,
+	    sizeof(p_cb_values) / sizeof(p_cb_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+#endif
+
 /*
  * The global 'listchars' or 'fillchars' option is changed.
  */
@@ -967,6 +1313,21 @@
 }
 
 /*
+ * Expand 'fillchars' or 'listchars' option value.
+ */
+    int
+expand_set_chars_option(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    char_u **varp = (char_u **)args->oe_varp;
+    int is_lcs = (varp == &p_lcs || varp == &curwin->w_p_lcs);
+    return expand_set_opt_generic(
+	    args,
+	    is_lcs ? get_listchars_name : get_fillchars_name,
+	    numMatches,
+	    matches);
+}
+
+/*
  * The 'cinoptions' option is changed.
  */
     char *
@@ -1091,6 +1452,20 @@
     return NULL;
 }
 
+    int
+expand_set_complete(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    static char *(p_cpt_values[]) = {
+	".", "w", "b", "u", "k", "kspell", "s", "i", "d", "]", "t", "U",
+	NULL};
+    return expand_set_opt_string(
+	    args,
+	    p_cpt_values,
+	    sizeof(p_cpt_values) / sizeof(p_cpt_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 /*
  * The 'completeopt' option is changed.
  */
@@ -1104,6 +1479,17 @@
     return NULL;
 }
 
+    int
+expand_set_completeopt(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_cot_values,
+	    sizeof(p_cot_values) / sizeof(p_cot_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 #if (defined(FEAT_PROP_POPUP) && defined(FEAT_QUICKFIX)) || defined(PROTO)
 /*
  * The 'completepopup' option is changed.
@@ -1132,6 +1518,17 @@
 
     return NULL;
 }
+
+    int
+expand_set_completeslash(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_csl_values,
+	    sizeof(p_csl_values) / sizeof(p_csl_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
 #endif
 
 #if defined(FEAT_CONCEAL) || defined(PROTO)
@@ -1145,6 +1542,12 @@
 
     return did_set_option_listflag(*varp, (char_u *)COCU_ALL, args->os_errbuf);
 }
+
+    int
+expand_set_concealcursor(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_listflag(args, (char_u*)COCU_ALL, numMatches, matches);
+}
 #endif
 
 /*
@@ -1158,6 +1561,12 @@
     return did_set_option_listflag(*varp, (char_u *)CPO_ALL, args->os_errbuf);
 }
 
+    int
+expand_set_cpoptions(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_listflag(args, (char_u*)CPO_ALL, numMatches, matches);
+}
+
 #if defined(FEAT_CRYPT) || defined(PROTO)
 /*
  * The 'cryptkey' option is changed.
@@ -1244,6 +1653,17 @@
     }
     return NULL;
 }
+
+    int
+expand_set_cryptmethod(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_cm_values,
+	    sizeof(p_cm_values) / sizeof(p_cm_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
 #endif
 
 #if (defined(FEAT_CSCOPE) && defined(FEAT_QUICKFIX)) || defined(PROTO)
@@ -1285,11 +1705,23 @@
 {
     char_u	**varp = (char_u **)args->os_varp;
 
+    // This could be changed to use opt_strings_flags() instead.
     if (**varp == NUL || fill_culopt_flags(*varp, curwin) != OK)
 	return e_invalid_argument;
 
     return NULL;
 }
+
+    int
+expand_set_cursorlineopt(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_culopt_values,
+	    sizeof(p_culopt_values) / sizeof(p_culopt_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
 #endif
 
 /*
@@ -1301,6 +1733,17 @@
     return did_set_opt_strings(p_debug, p_debug_values, TRUE);
 }
 
+    int
+expand_set_debug(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_debug_values,
+	    sizeof(p_debug_values) / sizeof(p_debug_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 #if defined(FEAT_DIFF) || defined(PROTO)
 /*
  * The 'diffopt' option is changed.
@@ -1313,6 +1756,36 @@
 
     return NULL;
 }
+
+    int
+expand_set_diffopt(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    expand_T *xp = args->oe_xp;
+
+    if (xp->xp_pattern > args->oe_set_arg && *(xp->xp_pattern-1) == ':')
+    {
+	// Within "algorithm:", we have a subgroup of possible options.
+	int algo_len = STRLEN("algorithm:");
+	if (xp->xp_pattern - args->oe_set_arg >= algo_len &&
+		STRNCMP(xp->xp_pattern - algo_len, "algorithm:", algo_len) == 0)
+	{
+	    return expand_set_opt_string(
+		    args,
+		    p_dip_algorithm_values,
+		    sizeof(p_dip_algorithm_values) / sizeof(p_dip_algorithm_values[0]) - 1,
+		    numMatches,
+		    matches);
+	}
+	return FAIL;
+    }
+
+    return expand_set_opt_string(
+	    args,
+	    p_dip_values,
+	    sizeof(p_dip_values) / sizeof(p_dip_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
 #endif
 
 /*
@@ -1328,6 +1801,17 @@
     return NULL;
 }
 
+    int
+expand_set_display(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_dy_values,
+	    sizeof(p_dy_values) / sizeof(p_dy_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 /*
  * The 'eadirection' option is changed.
  */
@@ -1337,6 +1821,17 @@
     return did_set_opt_strings(p_ead, p_ead_values, FALSE);
 }
 
+    int
+expand_set_eadirection(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_ead_values,
+	    sizeof(p_ead_values) / sizeof(p_ead_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 /*
  * One of the 'encoding', 'fileencoding', 'termencoding' or 'makeencoding'
  * options is changed.
@@ -1430,6 +1925,16 @@
     return errmsg;
 }
 
+    int
+expand_set_encoding(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_generic(
+	    args,
+	    get_encoding_name,
+	    numMatches,
+	    matches);
+}
+
 /*
  * The 'eventignore' option is changed.
  */
@@ -1441,6 +1946,26 @@
     return NULL;
 }
 
+    static char_u *
+get_eventignore_name(expand_T *xp, int idx)
+{
+    // 'eventignore' allows special keyword "all" in addition to
+    // all event names.
+    if (idx == 0)
+	return (char_u *)"all";
+    return get_event_name_no_group(xp, idx - 1);
+}
+
+    int
+expand_set_eventignore(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_generic(
+	    args,
+	    get_eventignore_name,
+	    numMatches,
+	    matches);
+}
+
 /*
  * The 'fileformat' option is changed.
  */
@@ -1470,6 +1995,17 @@
     return NULL;
 }
 
+    int
+expand_set_fileformat(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_ff_values,
+	    sizeof(p_ff_values) / sizeof(p_ff_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 /*
  * The 'fileformats' option is changed.
  */
@@ -1517,6 +2053,17 @@
 {
     return did_set_opt_strings(p_fcl, p_fcl_values, TRUE);
 }
+
+    int
+expand_set_foldclose(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_fcl_values,
+	    sizeof(p_fcl_values) / sizeof(p_fcl_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
 #endif
 
 #if (defined(FEAT_EVAL) && defined(FEAT_FOLDING)) || defined(PROTO)
@@ -1583,6 +2130,17 @@
     return NULL;
 }
 
+    int
+expand_set_foldmethod(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_fdm_values,
+	    sizeof(p_fdm_values) / sizeof(p_fdm_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 /*
  * The 'foldopen' option is changed.
  */
@@ -1591,6 +2149,17 @@
 {
     return did_set_opt_flags(p_fdo, p_fdo_values, &fdo_flags, TRUE);
 }
+
+    int
+expand_set_foldopen(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_fdo_values,
+	    sizeof(p_fdo_values) / sizeof(p_fdo_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
 #endif
 
 /*
@@ -1604,6 +2173,12 @@
     return did_set_option_listflag(*varp, (char_u *)FO_ALL, args->os_errbuf);
 }
 
+    int
+expand_set_formatoptions(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_listflag(args, (char_u*)FO_ALL, numMatches, matches);
+}
+
 #if defined(CURSOR_SHAPE) || defined(PROTO)
 /*
  * The 'guicursor' option is changed.
@@ -1722,6 +2297,12 @@
     gui_init_which_components(args->os_oldval.string);
     return NULL;
 }
+
+    int
+expand_set_guioptions(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_listflag(args, (char_u*)GO_ALL, numMatches, matches);
+}
 #endif
 
 #if defined(FEAT_GUI_TABLINE) || defined(PROTO)
@@ -1788,6 +2369,125 @@
 }
 
 /*
+ * Expand 'highlight' option.
+ */
+    int
+expand_set_highlight(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    char_u	    *p;
+    expand_T	    *xp = args->oe_xp;
+    static char_u   hl_flags[HLF_COUNT] = HL_FLAGS;
+    int		    i;
+    int		    count = 0;
+
+    if (xp->xp_pattern > args->oe_set_arg && *(xp->xp_pattern-1) == ':')
+    {
+	// Right after a ':', meaning we just return all highlight names.
+	return expand_set_opt_generic(
+		args,
+		get_highlight_name,
+		numMatches,
+		matches);
+    }
+
+    if (*xp->xp_pattern == NUL)
+    {
+	// At beginning of a comma-separated list. Return the specific list of
+	// supported occasions.
+	*matches = ALLOC_MULT(char_u *, HLF_COUNT + 1);
+	if (*matches == NULL)
+	    return FAIL;
+
+	// We still want to return the full option if it's requested.
+	if (args->oe_include_orig_val)
+	{
+	    p = vim_strsave(args->oe_opt_value);
+	    if (p == NULL)
+	    {
+		VIM_CLEAR(*matches);
+		return FAIL;
+	    }
+	    (*matches)[count++] = p;
+	}
+
+	for (i = 0; i < HLF_COUNT; i++)
+	{
+	    p = vim_strnsave(&hl_flags[i], 1);
+	    if (p == NULL)
+	    {
+		if (count == 0)
+		{
+		    VIM_CLEAR(*matches);
+		    return FAIL;
+		}
+		else
+		    break;
+	    }
+	    (*matches)[count++] = p;
+	}
+
+	if (count == 0)
+	{
+	    VIM_CLEAR(*matches);
+	    return FAIL;
+	}
+	*numMatches = count;
+	return OK;
+    }
+
+    // We are after the initial character (which indicates the occasion). We
+    // already made sure we are not matching after a ':' above, so now we want
+    // to match against display mode modifiers.
+    // Since the xp_pattern starts from the beginning, we need to include it in
+    // the returned match.
+
+    // Note: Keep this in sync with highlight_changed()
+    static char p_hl_mode_values[] =
+	{':', 'b', 'i', '-', 'n', 'r', 's', 'u', 'c', '2', 'd', '=', 't'};
+    int num_hl_modes = sizeof(p_hl_mode_values) / sizeof(p_hl_mode_values[0]);
+
+    *matches = ALLOC_MULT(char_u *, num_hl_modes);
+    if (*matches == NULL)
+	return FAIL;
+
+    int pattern_len = STRLEN(xp->xp_pattern);
+
+    for (i = 0; i < num_hl_modes; i++)
+    {
+	// Don't allow duplicates as these are unique flags
+	if (vim_strchr(xp->xp_pattern + 1, p_hl_mode_values[i]) != NULL)
+	    continue;
+
+	// ':' only works by itself, not with other flags.
+	if (pattern_len > 1 && p_hl_mode_values[i] == ':')
+	    continue;
+
+	p = vim_strnsave(xp->xp_pattern, pattern_len + 1);
+	if (p == NULL)
+	{
+	    if (i == 0)
+	    {
+		VIM_CLEAR(*matches);
+		return FAIL;
+	    }
+	    else
+		break;
+	}
+	p[pattern_len] = p_hl_mode_values[i];
+	p[pattern_len + 1] = NUL;
+	(*matches)[count++] = p;
+    }
+    if (count == 0)
+    {
+	VIM_CLEAR(*matches);
+	return FAIL;
+    }
+    *numMatches = count;
+
+    return OK;
+}
+
+/*
  * The 'titlestring' or the 'iconstring' option is changed.
  */
     static char *
@@ -1866,6 +2566,17 @@
     return NULL;
 }
 
+    int
+expand_set_jumpoptions(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_jop_values,
+	    sizeof(p_jop_values) / sizeof(p_jop_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 #if defined(FEAT_KEYMAP) || defined(PROTO)
 /*
  * The 'keymap' option is changed.
@@ -1939,6 +2650,17 @@
     return NULL;
 }
 
+    int
+expand_set_keymodel(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_km_values,
+	    sizeof(p_km_values) / sizeof(p_km_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 /*
  * The 'keyprotocol' option is changed.
  */
@@ -1955,6 +2677,27 @@
     return NULL;
 }
 
+    int
+expand_set_keyprotocol(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    expand_T *xp = args->oe_xp;
+    if (xp->xp_pattern > args->oe_set_arg && *(xp->xp_pattern-1) == ':')
+    {
+	// 'keyprotocol' only has well-defined terms for completion for the
+	// protocol part after the colon.
+	return expand_set_opt_string(
+		args,
+		p_kpc_protocol_values,
+		sizeof(p_kpc_protocol_values) / sizeof(p_kpc_protocol_values[0]) - 1,
+		numMatches,
+		matches);
+    }
+    // Use expand_set_opt_string instead of returning FAIL so that we can
+    // include the original value if args->oe_include_orig_val is set.
+    static char *(empty[]) = {NULL};
+    return expand_set_opt_string(args, empty, 0, numMatches, matches);
+}
+
 /*
  * The 'lispoptions' option is changed.
  */
@@ -1971,6 +2714,18 @@
     return NULL;
 }
 
+    int
+expand_set_lispoptions(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    static char *(p_lop_values[]) = {"expr:0", "expr:1", NULL};
+    return expand_set_opt_string(
+	    args,
+	    p_lop_values,
+	    sizeof(p_lop_values) / sizeof(p_lop_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 /*
  * The 'matchpairs' option is changed.
  */
@@ -2042,6 +2797,12 @@
 							args->os_errbuf);
 }
 
+    int
+expand_set_mouse(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_listflag(args, (char_u*)MOUSE_ALL, numMatches, matches);
+}
+
 /*
  * The 'mousemodel' option is changed.
  */
@@ -2060,6 +2821,17 @@
     return NULL;
 }
 
+    int
+expand_set_mousemodel(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_mousem_values,
+	    sizeof(p_mousem_values) / sizeof(p_mousem_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 #if defined(FEAT_MOUSESHAPE) || defined(PROTO)
     char *
 did_set_mouseshape(optset_T *args UNUSED)
@@ -2084,6 +2856,17 @@
     return did_set_opt_strings(*varp, p_nf_values, TRUE);
 }
 
+    int
+expand_set_nrformats(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_nf_values,
+	    sizeof(p_nf_values) / sizeof(p_nf_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 #if defined(FEAT_EVAL) || defined(PROTO)
 /*
  * One of the '*expr' options is changed: 'balloonexpr', 'diffexpr',
@@ -2144,6 +2927,58 @@
 
     return NULL;
 }
+
+    int
+expand_set_popupoption(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    expand_T *xp = args->oe_xp;
+
+    if (xp->xp_pattern > args->oe_set_arg && *(xp->xp_pattern-1) == ':')
+    {
+	// Within "highlight:"/"border:"/"align:", we have a subgroup of possible options.
+	int border_len = STRLEN("border:");
+	if (xp->xp_pattern - args->oe_set_arg >= border_len &&
+		STRNCMP(xp->xp_pattern - border_len, "border:", border_len) == 0)
+	{
+	    return expand_set_opt_string(
+		    args,
+		    p_popup_option_border_values,
+		    sizeof(p_popup_option_border_values) / sizeof(p_popup_option_border_values[0]) - 1,
+		    numMatches,
+		    matches);
+	}
+	int align_len = STRLEN("align:");
+	if (xp->xp_pattern - args->oe_set_arg >= align_len &&
+		STRNCMP(xp->xp_pattern - align_len, "align:", align_len) == 0)
+	{
+	    return expand_set_opt_string(
+		    args,
+		    p_popup_option_align_values,
+		    sizeof(p_popup_option_align_values) / sizeof(p_popup_option_align_values[0]) - 1,
+		    numMatches,
+		    matches);
+	}
+	int highlight_len = STRLEN("highlight:");
+	if (xp->xp_pattern - args->oe_set_arg >= highlight_len &&
+		STRNCMP(xp->xp_pattern - highlight_len, "highlight:", highlight_len) == 0)
+	{
+	    // Return the list of all highlight names
+	    return expand_set_opt_generic(
+		    args,
+		    get_highlight_name,
+		    numMatches,
+		    matches);
+	}
+	return FAIL;
+    }
+
+    return expand_set_opt_string(
+	    args,
+	    p_popup_option_values,
+	    sizeof(p_popup_option_values) / sizeof(p_popup_option_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
 #endif
 
 #if defined(FEAT_POSTSCRIPT) || defined(PROTO)
@@ -2178,6 +3013,30 @@
 }
 #endif
 
+#if defined(FEAT_PRINTER) || defined(PROTO)
+
+    static char_u *
+get_printoptions_names(expand_T *xp UNUSED, int idx)
+{
+    if (idx >= (int)(sizeof(printer_opts) / sizeof(printer_opts[0])))
+	return NULL;
+    return (char_u*)printer_opts[idx].name;
+}
+
+/*
+ * Expand 'printoptions' option
+ */
+    int
+expand_set_printoptions(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_generic(
+	    args,
+	    get_printoptions_names,
+	    numMatches,
+	    matches);
+}
+#endif
+
 #if defined(FEAT_STL_OPT) || defined(PROTO)
 /*
  * The 'statusline' or the 'tabline' or the 'rulerformat' option is changed.
@@ -2244,6 +3103,18 @@
 
     return NULL;
 }
+
+    int
+expand_set_rightleftcmd(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    static char *(p_rlc_values[]) = {"search", NULL};
+    return expand_set_opt_string(
+	    args,
+	    p_rlc_values,
+	    sizeof(p_rlc_values) / sizeof(p_rlc_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
 #endif
 
 #if defined(FEAT_STL_OPT) || defined(PROTO)
@@ -2266,6 +3137,17 @@
     return did_set_opt_strings(p_sbo, p_scbopt_values, TRUE);
 }
 
+    int
+expand_set_scrollopt(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_scbopt_values,
+	    sizeof(p_scbopt_values) / sizeof(p_scbopt_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 /*
  * The 'selection' option is changed.
  */
@@ -2278,6 +3160,17 @@
     return NULL;
 }
 
+    int
+expand_set_selection(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_sel_values,
+	    sizeof(p_sel_values) / sizeof(p_sel_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 /*
  * The 'selectmode' option is changed.
  */
@@ -2287,6 +3180,17 @@
     return did_set_opt_strings(p_slm, p_slm_values, TRUE);
 }
 
+    int
+expand_set_selectmode(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_slm_values,
+	    sizeof(p_slm_values) / sizeof(p_slm_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 #if defined(FEAT_SESSION) || defined(PROTO)
 /*
  * The 'sessionoptions' option is changed.
@@ -2306,6 +3210,17 @@
 
     return NULL;
 }
+
+    int
+expand_set_sessionoptions(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_ssop_values,
+	    sizeof(p_ssop_values) / sizeof(p_ssop_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
 #endif
 
 /*
@@ -2319,6 +3234,12 @@
     return did_set_option_listflag(*varp, (char_u *)SHM_ALL, args->os_errbuf);
 }
 
+    int
+expand_set_shortmess(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_listflag(args, (char_u*)SHM_ALL, numMatches, matches);
+}
+
 #if defined(FEAT_LINEBREAK) || defined(PROTO)
 /*
  * The 'showbreak' option is changed.
@@ -2349,6 +3270,17 @@
     return did_set_opt_strings(p_sloc, p_sloc_values, FALSE);
 }
 
+    int
+expand_set_showcmdloc(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_sloc_values,
+	    sizeof(p_sloc_values) / sizeof(p_sloc_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 #if defined(FEAT_SIGNS) || defined(PROTO)
 /*
  * The 'signcolumn' option is changed.
@@ -2369,6 +3301,17 @@
 
     return NULL;
 }
+
+    int
+expand_set_signcolumn(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_scl_values,
+	    sizeof(p_scl_values) / sizeof(p_scl_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
 #endif
 
 #if defined(FEAT_SPELL) || defined(PROTO)
@@ -2428,6 +3371,18 @@
     return NULL;
 }
 
+    int
+expand_set_spelloptions(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    static char *(p_spo_values[]) = {"camel", NULL};
+    return expand_set_opt_string(
+	    args,
+	    p_spo_values,
+	    sizeof(p_spo_values) / sizeof(p_spo_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 /*
  * The 'spellsuggest' option is changed.
  */
@@ -2439,6 +3394,17 @@
 
     return NULL;
 }
+
+    int
+expand_set_spellsuggest(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_sps_values,
+	    sizeof(p_sps_values) / sizeof(p_sps_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
 #endif
 
 /*
@@ -2450,6 +3416,17 @@
     return did_set_opt_strings(p_spk, p_spk_values, FALSE);
 }
 
+    int
+expand_set_splitkeep(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_spk_values,
+	    sizeof(p_spk_values) / sizeof(p_spk_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 #if defined(FEAT_STL_OPT) || defined(PROTO)
 /*
  * The 'statusline' option is changed.
@@ -2470,6 +3447,17 @@
     return did_set_opt_strings(p_sws, p_sws_values, FALSE);
 }
 
+    int
+expand_set_swapsync(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_sws_values,
+	    sizeof(p_sws_values) / sizeof(p_sws_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 /*
  * The 'switchbuf' option is changed.
  */
@@ -2479,6 +3467,17 @@
     return did_set_opt_flags(p_swb, p_swb_values, &swb_flags, TRUE);
 }
 
+    int
+expand_set_switchbuf(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_swb_values,
+	    sizeof(p_swb_values) / sizeof(p_swb_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 #if defined(FEAT_STL_OPT) || defined(PROTO)
 /*
  * The 'tabline' option is changed.
@@ -2520,6 +3519,17 @@
     return NULL;
 }
 
+    int
+expand_set_tagcase(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_tc_values,
+	    sizeof(p_tc_values) / sizeof(p_tc_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 /*
  * The 'term' option is changed.
  */
@@ -2653,6 +3663,17 @@
 {
     return did_set_opt_strings(p_twt, p_twt_values, FALSE);
 }
+
+    int
+expand_set_termwintype(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_twt_values,
+	    sizeof(p_twt_values) / sizeof(p_twt_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
 # endif
 #endif
 
@@ -2686,6 +3707,17 @@
 		(TOOLBAR_TEXT | TOOLBAR_ICONS)) != 0);
     return NULL;
 }
+
+    int
+expand_set_toolbar(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_toolbar_values,
+	    sizeof(p_toolbar_values) / sizeof(p_toolbar_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
 #endif
 
 #if (defined(FEAT_TOOLBAR) && defined(FEAT_GUI_GTK)) || defined(PROTO)
@@ -2703,6 +3735,17 @@
 		(TOOLBAR_TEXT | TOOLBAR_ICONS)) != 0);
     return NULL;
 }
+
+    int
+expand_set_toolbariconsize(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_tbis_values,
+	    sizeof(p_tbis_values) / sizeof(p_tbis_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
 #endif
 
 #if defined(UNIX) || defined(VMS) || defined(PROTO)
@@ -2726,6 +3769,17 @@
 
     return errmsg;
 }
+
+    int
+expand_set_ttymouse(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_ttym_values,
+	    sizeof(p_ttym_values) / sizeof(p_ttym_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
 #endif
 
 #if defined(FEAT_VARTABS) || defined(PROTO)
@@ -2932,6 +3986,17 @@
     return NULL;
 }
 
+    int
+expand_set_virtualedit(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_ve_values,
+	    sizeof(p_ve_values) / sizeof(p_ve_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 /*
  * The 'whichwrap' option is changed.
  */
@@ -2940,7 +4005,15 @@
 {
     char_u	**varp = (char_u **)args->os_varp;
 
-    return did_set_option_listflag(*varp, (char_u *)WW_ALL, args->os_errbuf);
+    // Add ',' to the list flags because 'whichwrap' is a flag
+    // list that is comma-separated.
+    return did_set_option_listflag(*varp, (char_u *)(WW_ALL ","), args->os_errbuf);
+}
+
+    int
+expand_set_whichwrap(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_listflag(args, (char_u*)WW_ALL, numMatches, matches);
 }
 
 /*
@@ -2954,6 +4027,17 @@
     return NULL;
 }
 
+    int
+expand_set_wildmode(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_wim_values,
+	    sizeof(p_wim_values) / sizeof(p_wim_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 /*
  * The 'wildoptions' option is changed.
  */
@@ -2963,6 +4047,17 @@
     return did_set_opt_strings(p_wop, p_wop_values, TRUE);
 }
 
+    int
+expand_set_wildoptions(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_wop_values,
+	    sizeof(p_wop_values) / sizeof(p_wop_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
+
 #if defined(FEAT_WAK) || defined(PROTO)
 /*
  * The 'winaltkeys' option is changed.
@@ -2985,6 +4080,17 @@
 # endif
     return errmsg;
 }
+
+    int
+expand_set_winaltkeys(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_string(
+	    args,
+	    p_wak_values,
+	    sizeof(p_wak_values) / sizeof(p_wak_values[0]) - 1,
+	    numMatches,
+	    matches);
+}
 #endif
 
 /*
@@ -2999,6 +4105,16 @@
     return NULL;
 }
 
+    int
+expand_set_wincolor(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+    return expand_set_opt_generic(
+	    args,
+	    get_highlight_name,
+	    numMatches,
+	    matches);
+}
+
 #ifdef FEAT_SYN_HL
 /*
  * When the 'syntax' option is set, load the syntax of that name.