Mercurial > vim
changeset 31902:1c5ef864fe4c v9.0.1283
patch 9.0.1283: the code for setting options is too complicated
Commit: https://github.com/vim/vim/commit/c72078b631832a1f3d7d8ee90560d54f7d7d3ffe
Author: Yegappan Lakshmanan <yegappan@yahoo.com>
Date: Sun Feb 5 16:02:35 2023 +0000
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)
author | Bram Moolenaar <Bram@vim.org> |
---|---|
date | Sun, 05 Feb 2023 17:15:04 +0100 |
parents | e18577c21e54 |
children | 1cedb94012da |
files | src/option.c src/version.c |
diffstat | 2 files changed, 464 insertions(+), 304 deletions(-) [+] |
line wrap: on
line diff
--- a/src/option.c +++ b/src/option.c @@ -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. @@ -1637,6 +1815,263 @@ do_set_string( } /* + * 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 * wrong. "errbuf[errbuflen]" can be used to create the error message. @@ -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; - - 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; - } + 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 = 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)) - 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) + // Make sure the option value can be changed. + if (validate_opt_idx(opt_idx, opt_flags, flags, &errmsg) == FAIL) 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: