0
0
mirror of https://github.com/vim/vim.git synced 2025-09-29 04:34:16 -04:00

patch 9.0.1283: the code for setting options is too complicated

Problem:    The code for setting options is too complicated.
Solution:   Refactor the do_set() function. (Yegappan Lakshmanan, Lewis
            Russell, closes #11945)
This commit is contained in:
Yegappan Lakshmanan
2023-02-05 16:02:35 +00:00
committed by Bram Moolenaar
parent c8ef30bc2e
commit c72078b631
2 changed files with 463 additions and 303 deletions

View File

@@ -1211,6 +1211,9 @@ ex_set(exarg_T *eap)
}
}
/*
* :set operator types
*/
typedef enum {
OP_NONE = 0,
OP_ADDING, // "opt+=arg"
@@ -1218,6 +1221,181 @@ typedef enum {
OP_REMOVING, // "opt-=arg"
} set_op_T;
typedef enum {
PREFIX_NO = 0, // "no" prefix
PREFIX_NONE, // no prefix
PREFIX_INV, // "inv" prefix
} set_prefix_T;
/*
* Return the prefix type for the option name in *argp.
*/
static set_prefix_T
get_option_prefix(char_u **argp)
{
int prefix = PREFIX_NONE;
char_u *arg = *argp;
if (STRNCMP(arg, "no", 2) == 0 && STRNCMP(arg, "novice", 6) != 0)
{
prefix = PREFIX_NO;
arg += 2;
}
else if (STRNCMP(arg, "inv", 3) == 0)
{
prefix = PREFIX_INV;
arg += 3;
}
*argp = arg;
return prefix;
}
/*
* Parse the option name in "arg" and return the option index in "*opt_idxp",
* and the option name length in "*lenp". For a <t_xx> option, return the key
* number in "*keyp".
*
* Returns FAIL if an option starting with "<" doesn't end with a ">",
* otherwise returns OK.
*/
static int
parse_option_name(char_u *arg, int *opt_idxp, int *lenp, int *keyp)
{
int key = 0;
int len;
int opt_idx;
if (*arg == '<')
{
opt_idx = -1;
// look out for <t_>;>
if (arg[1] == 't' && arg[2] == '_' && arg[3] && arg[4])
len = 5;
else
{
len = 1;
while (arg[len] != NUL && arg[len] != '>')
++len;
}
if (arg[len] != '>')
return FAIL;
arg[len] = NUL; // put NUL after name
if (arg[1] == 't' && arg[2] == '_') // could be term code
opt_idx = findoption(arg + 1);
arg[len++] = '>'; // restore '>'
if (opt_idx == -1)
key = find_key_option(arg + 1, TRUE);
}
else
{
int nextchar; // next non-white char after option name
len = 0;
/*
* The two characters after "t_" may not be alphanumeric.
*/
if (arg[0] == 't' && arg[1] == '_' && arg[2] && arg[3])
len = 4;
else
while (ASCII_ISALNUM(arg[len]) || arg[len] == '_')
++len;
nextchar = arg[len];
arg[len] = NUL; // put NUL after name
opt_idx = findoption(arg);
arg[len] = nextchar; // restore nextchar
if (opt_idx == -1)
key = find_key_option(arg, FALSE);
}
*keyp = key;
*lenp = len;
*opt_idxp = opt_idx;
return OK;
}
/*
* Get the option operator (+=, ^=, -=).
*/
static set_op_T
get_opt_op(char_u *arg)
{
set_op_T op = OP_NONE;
if (*arg != NUL && *(arg + 1) == '=')
{
if (*arg == '+')
op = OP_ADDING; // "+="
else if (*arg == '^')
op = OP_PREPENDING; // "^="
else if (*arg == '-')
op = OP_REMOVING; // "-="
}
return op;
}
/*
* Validate whether the value of the option in "opt_idx" can be changed.
* Returns FAIL if the option can be skipped or cannot be changed. Returns OK
* if it can be changed.
*/
static int
validate_opt_idx(int opt_idx, int opt_flags, long_u flags, char **errmsg)
{
// Skip all options that are not window-local (used when showing
// an already loaded buffer in a window).
if ((opt_flags & OPT_WINONLY)
&& (opt_idx < 0 || options[opt_idx].var != VAR_WIN))
return FAIL;
// Skip all options that are window-local (used for :vimgrep).
if ((opt_flags & OPT_NOWIN) && opt_idx >= 0
&& options[opt_idx].var == VAR_WIN)
return FAIL;
// Disallow changing some options from modelines.
if (opt_flags & OPT_MODELINE)
{
if (flags & (P_SECURE | P_NO_ML))
{
*errmsg = e_not_allowed_in_modeline;
return FAIL;
}
if ((flags & P_MLE) && !p_mle)
{
*errmsg = e_not_allowed_in_modeline_when_modelineexpr_is_off;
return FAIL;
}
#ifdef FEAT_DIFF
// In diff mode some options are overruled. This avoids that
// 'foldmethod' becomes "marker" instead of "diff" and that
// "wrap" gets set.
if (curwin->w_p_diff
&& opt_idx >= 0 // shut up coverity warning
&& (
# ifdef FEAT_FOLDING
options[opt_idx].indir == PV_FDM ||
# endif
options[opt_idx].indir == PV_WRAP))
return FAIL;
#endif
}
#ifdef HAVE_SANDBOX
// Disallow changing some options in the sandbox
if (sandbox != 0 && (flags & P_SECURE))
{
*errmsg = e_not_allowed_in_sandbox;
return FAIL;
}
#endif
return OK;
}
/*
* Part of do_set() for string options.
* Returns FAIL on failure, do not process further options.
@@ -1636,6 +1814,263 @@ do_set_string(
return *errmsg == NULL ? OK : FAIL;
}
/*
* Set a boolean option
*/
static char *
do_set_option_bool(
int opt_idx,
int opt_flags,
set_prefix_T prefix,
long_u flags,
char_u *varp,
int nextchar,
int afterchar,
int cp_val)
{
varnumber_T value;
if (nextchar == '=' || nextchar == ':')
return e_invalid_argument;
/*
* ":set opt!": invert
* ":set opt&": reset to default value
* ":set opt<": reset to global value
*/
if (nextchar == '!')
value = *(int *)(varp) ^ 1;
else if (nextchar == '&')
value = (int)(long)(long_i)options[opt_idx].def_val[
((flags & P_VI_DEF) || cp_val) ? VI_DEFAULT : VIM_DEFAULT];
else if (nextchar == '<')
{
// For 'autoread' -1 means to use global value.
if ((int *)varp == &curbuf->b_p_ar && opt_flags == OPT_LOCAL)
value = -1;
else
value = *(int *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL);
}
else
{
/*
* ":set invopt": invert
* ":set opt" or ":set noopt": set or reset
*/
if (nextchar != NUL && !VIM_ISWHITE(afterchar))
return e_trailing_characters;
if (prefix == PREFIX_INV)
value = *(int *)(varp) ^ 1;
else
value = prefix == PREFIX_NO ? 0 : 1;
}
return set_bool_option(opt_idx, varp, (int)value, opt_flags);
}
/*
* Set a numeric option
*/
static char *
do_set_option_numeric(
int opt_idx,
int opt_flags,
char_u **argp,
int nextchar,
set_op_T op,
long_u flags,
int cp_val,
char_u *varp,
char *errbuf,
size_t errbuflen)
{
char_u *arg = *argp;
varnumber_T value;
int i;
char *errmsg = NULL;
/*
* Different ways to set a number option:
* & set to default value
* < set to global value
* <xx> accept special key codes for 'wildchar'
* c accept any non-digit for 'wildchar'
* [-]0-9 set number
* other error
*/
++arg;
if (nextchar == '&')
value = (long)(long_i)options[opt_idx].def_val[
((flags & P_VI_DEF) || cp_val) ? VI_DEFAULT : VIM_DEFAULT];
else if (nextchar == '<')
{
// For 'undolevels' NO_LOCAL_UNDOLEVEL means to
// use the global value.
if ((long *)varp == &curbuf->b_p_ul && opt_flags == OPT_LOCAL)
value = NO_LOCAL_UNDOLEVEL;
else
value = *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL);
}
else if (((long *)varp == &p_wc || (long *)varp == &p_wcm)
&& (*arg == '<'
|| *arg == '^'
|| (*arg != NUL
&& (!arg[1] || VIM_ISWHITE(arg[1]))
&& !VIM_ISDIGIT(*arg))))
{
value = string_to_key(arg, FALSE);
if (value == 0 && (long *)varp != &p_wcm)
{
errmsg = e_invalid_argument;
goto skip;
}
}
else if (*arg == '-' || VIM_ISDIGIT(*arg))
{
// Allow negative (for 'undolevels'), octal and hex numbers.
vim_str2nr(arg, NULL, &i, STR2NR_ALL, &value, NULL, 0, TRUE);
if (i == 0 || (arg[i] != NUL && !VIM_ISWHITE(arg[i])))
{
errmsg = e_number_required_after_equal;
goto skip;
}
}
else
{
errmsg = e_number_required_after_equal;
goto skip;
}
if (op == OP_ADDING)
value = *(long *)varp + value;
else if (op == OP_PREPENDING)
value = *(long *)varp * value;
else if (op == OP_REMOVING)
value = *(long *)varp - value;
errmsg = set_num_option(opt_idx, varp, value, errbuf, errbuflen,
opt_flags);
skip:
*argp = arg;
return errmsg;
}
/*
* Set a key code (t_xx) option
*/
static char *
do_set_option_keycode(char_u **argp, char_u *key_name, int nextchar)
{
char_u *arg = *argp;
char_u *p;
if (nextchar == '&')
{
if (add_termcap_entry(key_name, TRUE) == FAIL)
return e_not_found_in_termcap;
}
else
{
++arg; // jump to after the '=' or ':'
for (p = arg; *p && !VIM_ISWHITE(*p); ++p)
if (*p == '\\' && p[1] != NUL)
++p;
nextchar = *p;
*p = NUL;
add_termcode(key_name, arg, FALSE);
*p = nextchar;
}
if (full_screen)
ttest(FALSE);
redraw_all_later(UPD_CLEAR);
*argp = arg;
return NULL;
}
/*
* Set an option to a new value.
*/
static char *
do_set_option_value(
int opt_idx,
int opt_flags,
char_u **argp,
set_prefix_T prefix,
set_op_T op,
long_u flags,
char_u *varp,
char_u *key_name,
int nextchar,
int afterchar,
int cp_val,
int *stopopteval,
char *errbuf,
size_t errbuflen)
{
int value_checked = FALSE;
char *errmsg = NULL;
char_u *arg = *argp;
if (flags & P_BOOL)
{
// boolean option
errmsg = do_set_option_bool(opt_idx, opt_flags, prefix, flags, varp,
nextchar, afterchar, cp_val);
if (errmsg != NULL)
goto skip;
}
else
{
// numeric or string option
if (vim_strchr((char_u *)"=:&<", nextchar) == NULL
|| prefix != PREFIX_NONE)
{
errmsg = e_invalid_argument;
goto skip;
}
if (flags & P_NUM)
{
// numeric option
errmsg = do_set_option_numeric(opt_idx, opt_flags, &arg, nextchar,
op, flags, cp_val, varp,
errbuf, errbuflen);
if (errmsg != NULL)
goto skip;
}
else if (opt_idx >= 0)
{
// string option
if (do_set_string(opt_idx, opt_flags, &arg, nextchar, op, flags,
cp_val, varp, errbuf, &value_checked,
&errmsg) == FAIL)
{
if (errmsg != NULL)
goto skip;
*stopopteval = TRUE;
goto skip;
}
}
else
{
// key code option
errmsg = do_set_option_keycode(&arg, key_name, nextchar);
if (errmsg != NULL)
goto skip;
}
}
if (opt_idx >= 0)
did_set_option(opt_idx, opt_flags, op == OP_NONE, value_checked);
skip:
*argp = arg;
return errmsg;
}
/*
* Set an option to a new value.
* Return NULL if OK, return an untranslated error message when something is
@@ -1652,78 +2087,27 @@ do_set_option(
char *errbuf,
size_t errbuflen)
{
char *errmsg = NULL;
int prefix; // 1: nothing, 0: "no", 2: "inv" in front of name
int nextchar; // next non-white char after option name
int afterchar; // character just after option name
char_u *arg = *argp;
int key;
int opt_idx;
int len;
set_op_T op = 0;
long_u flags; // flags for current option
char_u *varp = NULL; // pointer to variable for current option
char_u *arg;
set_prefix_T prefix; // no prefix, "no" prefix or "inv" prefix
set_op_T op;
long_u flags; // flags for current option
char_u *varp; // pointer to variable for current option
char_u key_name[2];
int cp_val = 0;
varnumber_T value;
int i;
int nextchar; // next non-white char after option name
int afterchar; // character just after option name
int cp_val;
char *errmsg = NULL;
int key;
int len;
prefix = 1;
if (STRNCMP(arg, "no", 2) == 0 && STRNCMP(arg, "novice", 6) != 0)
{
prefix = 0;
arg += 2;
}
else if (STRNCMP(arg, "inv", 3) == 0)
{
prefix = 2;
arg += 3;
}
prefix = get_option_prefix(argp);
arg = *argp;
// find end of name
key = 0;
if (*arg == '<')
{
opt_idx = -1;
// look out for <t_>;>
if (arg[1] == 't' && arg[2] == '_' && arg[3] && arg[4])
len = 5;
else
{
len = 1;
while (arg[len] != NUL && arg[len] != '>')
++len;
}
if (arg[len] != '>')
{
errmsg = e_invalid_argument;
goto skip;
}
arg[len] = NUL; // put NUL after name
if (arg[1] == 't' && arg[2] == '_') // could be term code
opt_idx = findoption(arg + 1);
arg[len++] = '>'; // restore '>'
if (opt_idx == -1)
key = find_key_option(arg + 1, TRUE);
}
else
{
len = 0;
/*
* The two characters after "t_" may not be alphanumeric.
*/
if (arg[0] == 't' && arg[1] == '_' && arg[2] && arg[3])
len = 4;
else
while (ASCII_ISALNUM(arg[len]) || arg[len] == '_')
++len;
nextchar = arg[len];
arg[len] = NUL; // put NUL after name
opt_idx = findoption(arg);
arg[len] = nextchar; // restore nextchar
if (opt_idx == -1)
key = find_key_option(arg, FALSE);
}
if (parse_option_name(arg, &opt_idx, &len, &key) == FAIL)
return e_invalid_argument;
// remember character after option name
afterchar = arg[len];
@@ -1748,25 +2132,10 @@ do_set_option(
while (VIM_ISWHITE(arg[len]))
++len;
op = OP_NONE;
if (arg[len] != NUL && arg[len + 1] == '=')
{
if (arg[len] == '+')
{
op = OP_ADDING; // "+="
++len;
}
else if (arg[len] == '^')
{
op = OP_PREPENDING; // "^="
++len;
}
else if (arg[len] == '-')
{
op = OP_REMOVING; // "-="
++len;
}
}
op = get_opt_op(arg + len);
if (op != OP_NONE)
len++;
nextchar = arg[len];
if (opt_idx == -1 && key == 0) // found a mismatch: skip
@@ -1810,54 +2179,10 @@ do_set_option(
}
}
// Skip all options that are not window-local (used when showing
// an already loaded buffer in a window).
if ((opt_flags & OPT_WINONLY)
&& (opt_idx < 0 || options[opt_idx].var != VAR_WIN))
// Make sure the option value can be changed.
if (validate_opt_idx(opt_idx, opt_flags, flags, &errmsg) == FAIL)
goto skip;
// Skip all options that are window-local (used for :vimgrep).
if ((opt_flags & OPT_NOWIN) && opt_idx >= 0
&& options[opt_idx].var == VAR_WIN)
goto skip;
// Disallow changing some options from modelines.
if (opt_flags & OPT_MODELINE)
{
if (flags & (P_SECURE | P_NO_ML))
{
errmsg = e_not_allowed_in_modeline;
goto skip;
}
if ((flags & P_MLE) && !p_mle)
{
errmsg = e_not_allowed_in_modeline_when_modelineexpr_is_off;
goto skip;
}
#ifdef FEAT_DIFF
// In diff mode some options are overruled. This avoids that
// 'foldmethod' becomes "marker" instead of "diff" and that
// "wrap" gets set.
if (curwin->w_p_diff
&& opt_idx >= 0 // shut up coverity warning
&& (
#ifdef FEAT_FOLDING
options[opt_idx].indir == PV_FDM ||
#endif
options[opt_idx].indir == PV_WRAP))
goto skip;
#endif
}
#ifdef HAVE_SANDBOX
// Disallow changing some options in the sandbox
if (sandbox != 0 && (flags & P_SECURE))
{
errmsg = e_not_allowed_in_sandbox;
goto skip;
}
#endif
if (vim_strchr((char_u *)"?=:!&<", nextchar) != NULL)
{
arg += len;
@@ -1888,7 +2213,7 @@ do_set_option(
* allows only one '=' character per "set" command line. grrr. (jw)
*/
if (nextchar == '?'
|| (prefix == 1
|| (prefix == PREFIX_NONE
&& vim_strchr((char_u *)"=:&<", nextchar) == NULL
&& !(flags & P_BOOL)))
{
@@ -1939,177 +2264,10 @@ do_set_option(
}
else
{
int value_checked = FALSE;
if (flags & P_BOOL) // boolean
{
if (nextchar == '=' || nextchar == ':')
{
errmsg = e_invalid_argument;
goto skip;
}
/*
* ":set opt!": invert
* ":set opt&": reset to default value
* ":set opt<": reset to global value
*/
if (nextchar == '!')
value = *(int *)(varp) ^ 1;
else if (nextchar == '&')
value = (int)(long)(long_i)options[opt_idx].def_val[
((flags & P_VI_DEF) || cp_val)
? VI_DEFAULT : VIM_DEFAULT];
else if (nextchar == '<')
{
// For 'autoread' -1 means to use global value.
if ((int *)varp == &curbuf->b_p_ar
&& opt_flags == OPT_LOCAL)
value = -1;
else
value = *(int *)get_varp_scope(&(options[opt_idx]),
OPT_GLOBAL);
}
else
{
/*
* ":set invopt": invert
* ":set opt" or ":set noopt": set or reset
*/
if (nextchar != NUL && !VIM_ISWHITE(afterchar))
{
errmsg = e_trailing_characters;
goto skip;
}
if (prefix == 2) // inv
value = *(int *)(varp) ^ 1;
else
value = prefix;
}
errmsg = set_bool_option(opt_idx, varp, (int)value,
opt_flags);
}
else // numeric or string
{
if (vim_strchr((char_u *)"=:&<", nextchar) == NULL
|| prefix != 1)
{
errmsg = e_invalid_argument;
goto skip;
}
if (flags & P_NUM) // numeric
{
/*
* Different ways to set a number option:
* & set to default value
* < set to global value
* <xx> accept special key codes for 'wildchar'
* c accept any non-digit for 'wildchar'
* [-]0-9 set number
* other error
*/
++arg;
if (nextchar == '&')
value = (long)(long_i)options[opt_idx].def_val[
((flags & P_VI_DEF) || cp_val)
? VI_DEFAULT : VIM_DEFAULT];
else if (nextchar == '<')
{
// For 'undolevels' NO_LOCAL_UNDOLEVEL means to
// use the global value.
if ((long *)varp == &curbuf->b_p_ul
&& opt_flags == OPT_LOCAL)
value = NO_LOCAL_UNDOLEVEL;
else
value = *(long *)get_varp_scope(
&(options[opt_idx]), OPT_GLOBAL);
}
else if (((long *)varp == &p_wc
|| (long *)varp == &p_wcm)
&& (*arg == '<'
|| *arg == '^'
|| (*arg != NUL
&& (!arg[1] || VIM_ISWHITE(arg[1]))
&& !VIM_ISDIGIT(*arg))))
{
value = string_to_key(arg, FALSE);
if (value == 0 && (long *)varp != &p_wcm)
{
errmsg = e_invalid_argument;
goto skip;
}
}
else if (*arg == '-' || VIM_ISDIGIT(*arg))
{
// Allow negative (for 'undolevels'), octal and
// hex numbers.
vim_str2nr(arg, NULL, &i, STR2NR_ALL,
&value, NULL, 0, TRUE);
if (i == 0 || (arg[i] != NUL
&& !VIM_ISWHITE(arg[i])))
{
errmsg = e_number_required_after_equal;
goto skip;
}
}
else
{
errmsg = e_number_required_after_equal;
goto skip;
}
if (op == OP_ADDING)
value = *(long *)varp + value;
else if (op == OP_PREPENDING)
value = *(long *)varp * value;
else if (op == OP_REMOVING)
value = *(long *)varp - value;
errmsg = set_num_option(opt_idx, varp, value,
errbuf, errbuflen, opt_flags);
}
else if (opt_idx >= 0) // string
{
if (do_set_string(opt_idx, opt_flags, &arg, nextchar,
op, flags, cp_val, varp, errbuf,
&value_checked, &errmsg) == FAIL)
{
if (errmsg != NULL)
goto skip;
*stopopteval = TRUE;
goto skip;
}
}
else // key code option
{
char_u *p;
if (nextchar == '&')
{
if (add_termcap_entry(key_name, TRUE) == FAIL)
errmsg = e_not_found_in_termcap;
}
else
{
++arg; // jump to after the '=' or ':'
for (p = arg; *p && !VIM_ISWHITE(*p); ++p)
if (*p == '\\' && p[1] != NUL)
++p;
nextchar = *p;
*p = NUL;
add_termcode(key_name, arg, FALSE);
*p = nextchar;
}
if (full_screen)
ttest(FALSE);
redraw_all_later(UPD_CLEAR);
}
}
if (opt_idx >= 0)
did_set_option(
opt_idx, opt_flags, op == OP_NONE, value_checked);
errmsg = do_set_option_value(opt_idx, opt_flags, &arg, prefix, op,
flags, varp, key_name, nextchar,
afterchar, cp_val, stopopteval, errbuf,
errbuflen);
}
skip: