To: vim_dev@googlegroups.com Subject: Patch 9.0.1283 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 9.0.1283 Problem: The code for setting options is too complicated. Solution: Refactor the do_set() function. (Yegappan Lakshmanan, Lewis Russell, closes #11945) Files: src/option.c *** ../vim-9.0.1282/src/option.c 2023-02-02 16:34:07.741513245 +0000 --- src/option.c 2023-02-05 16:00:56.316836155 +0000 *************** *** 1211,1216 **** --- 1211,1219 ---- } } + /* + * :set operator types + */ typedef enum { OP_NONE = 0, OP_ADDING, // "opt+=arg" *************** *** 1218,1223 **** --- 1221,1401 ---- 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 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 ;> + 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,1730 **** } /* ! * 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. */ static char * ! do_set_option( int opt_flags, ! char_u **argp, ! char_u *arg_start, ! char_u **startarg, ! int *did_show, ! int *stopopteval, ! 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 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; } ! // find end of name ! key = 0; ! if (*arg == '<') { ! opt_idx = -1; ! // look out for ;> ! 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); } // remember character after option name afterchar = arg[len]; --- 1815,2114 ---- } /* ! * 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 ! * 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. + */ + static char * + do_set_option( + int opt_flags, + char_u **argp, + char_u *arg_start, + char_u **startarg, + int *did_show, + int *stopopteval, + char *errbuf, + size_t errbuflen) + { + int opt_idx; + 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 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 (parse_option_name(arg, &opt_idx, &len, &key) == FAIL) + return e_invalid_argument; + // remember character after option name afterchar = arg[len]; *************** *** 1748,1772 **** 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; ! } ! } nextchar = arg[len]; if (opt_idx == -1 && key == 0) // found a mismatch: skip --- 2132,2141 ---- while (VIM_ISWHITE(arg[len])) ++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,1862 **** } } ! // 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) ! 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) { --- 2179,2187 ---- } } ! // Make sure the option value can be changed. ! if (validate_opt_idx(opt_idx, opt_flags, flags, &errmsg) == FAIL) goto skip; if (vim_strchr((char_u *)"?=:!&<", nextchar) != NULL) { *************** *** 1888,1894 **** * allows only one '=' character per "set" command line. grrr. (jw) */ if (nextchar == '?' ! || (prefix == 1 && vim_strchr((char_u *)"=:&<", nextchar) == NULL && !(flags & P_BOOL))) { --- 2213,2219 ---- * allows only one '=' character per "set" command line. grrr. (jw) */ if (nextchar == '?' ! || (prefix == PREFIX_NONE && vim_strchr((char_u *)"=:&<", nextchar) == NULL && !(flags & P_BOOL))) { *************** *** 1939,2115 **** } 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 ! * 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); } skip: --- 2264,2273 ---- } else { ! errmsg = do_set_option_value(opt_idx, opt_flags, &arg, prefix, op, ! flags, varp, key_name, nextchar, ! afterchar, cp_val, stopopteval, errbuf, ! errbuflen); } skip: *** ../vim-9.0.1282/src/version.c 2023-02-05 14:47:41.271193236 +0000 --- src/version.c 2023-02-05 15:43:32.369677054 +0000 *************** *** 697,698 **** --- 697,700 ---- { /* Add new patch number below this line */ + /**/ + 1283, /**/ -- From "know your smileys": :'-D Laughing so much that they're crying /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///