diff options
Diffstat (limited to 'src/nvim/eval.c')
| -rw-r--r-- | src/nvim/eval.c | 1167 | 
1 files changed, 789 insertions, 378 deletions
diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 942f8d5e09..a3a66a5764 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -24,8 +24,10 @@  #endif  #include "nvim/eval.h"  #include "nvim/buffer.h" +#include "nvim/change.h"  #include "nvim/channel.h"  #include "nvim/charset.h" +#include "nvim/context.h"  #include "nvim/cursor.h"  #include "nvim/diff.h"  #include "nvim/edit.h" @@ -176,6 +178,7 @@ static char *e_funcref = N_("E718: Funcref required");  static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary");  static char *e_nofunc = N_("E130: Unknown function: %s");  static char *e_illvar = N_("E461: Illegal variable name: %s"); +static char *e_cannot_mod = N_("E995: Cannot modify existing variable");  static const char *e_readonlyvar = N_(      "E46: Cannot change read-only variable \"%.*s\""); @@ -302,15 +305,6 @@ typedef struct {    list_T      *fi_list;         /* list being used */  } forinfo_T; -/* - * enum used by var_flavour() - */ -typedef enum { -  VAR_FLAVOUR_DEFAULT,          /* doesn't start with uppercase */ -  VAR_FLAVOUR_SESSION,          /* starts with uppercase, some lower */ -  VAR_FLAVOUR_SHADA             /* all uppercase */ -} var_flavour_T; -  /* values for vv_flags: */  #define VV_COMPAT       1       /* compatible, also used without "v:" */  #define VV_RO           2       /* read-only */ @@ -533,6 +527,35 @@ const list_T *eval_msgpack_type_lists[] = {    [kMPExt] = NULL,  }; +// Return "n1" divided by "n2", taking care of dividing by zero. +varnumber_T num_divide(varnumber_T n1, varnumber_T n2) +  FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ +  varnumber_T result; + +  if (n2 == 0) {  // give an error message? +    if (n1 == 0) { +      result = VARNUMBER_MIN;  // similar to NaN +    } else if (n1 < 0) { +      result = -VARNUMBER_MAX; +    } else { +      result = VARNUMBER_MAX; +    } +  } else { +    result = n1 / n2; +  } + +  return result; +} + +// Return "n1" modulus "n2", taking care of dividing by zero. +varnumber_T num_modulus(varnumber_T n1, varnumber_T n2) +  FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ +  // Give an error when n2 is 0? +  return (n2 == 0) ? 0 : (n1 % n2); +} +  /*   * Initialize the global and v: variables.   */ @@ -776,10 +799,11 @@ var_redir_start(    did_emsg = FALSE;    tv.v_type = VAR_STRING;    tv.vval.v_string = (char_u *)""; -  if (append) -    set_var_lval(redir_lval, redir_endp, &tv, TRUE, (char_u *)"."); -  else -    set_var_lval(redir_lval, redir_endp, &tv, TRUE, (char_u *)"="); +  if (append) { +    set_var_lval(redir_lval, redir_endp, &tv, true, false, (char_u *)"."); +  } else { +    set_var_lval(redir_lval, redir_endp, &tv, true, false, (char_u *)"="); +  }    clear_lval(redir_lval);    err = did_emsg;    did_emsg |= save_emsg; @@ -837,7 +861,7 @@ void var_redir_stop(void)        redir_endp = (char_u *)get_lval(redir_varname, NULL, redir_lval,                                        false, false, 0, FNE_CHECK_START);        if (redir_endp != NULL && redir_lval->ll_name != NULL) { -        set_var_lval(redir_lval, redir_endp, &tv, false, (char_u *)"."); +        set_var_lval(redir_lval, redir_endp, &tv, false, false, (char_u *)".");        }        clear_lval(redir_lval);      } @@ -955,6 +979,89 @@ eval_to_bool(    return retval;  } +// Call eval1() and give an error message if not done at a lower level. +static int eval1_emsg(char_u **arg, typval_T *rettv, bool evaluate) +  FUNC_ATTR_NONNULL_ARG(1, 2) +{ +  const char_u *const start = *arg; +  const int did_emsg_before = did_emsg; +  const int called_emsg_before = called_emsg; + +  const int ret = eval1(arg, rettv, evaluate); +  if (ret == FAIL) { +    // Report the invalid expression unless the expression evaluation has +    // been cancelled due to an aborting error, an interrupt, or an +    // exception, or we already gave a more specific error. +    // Also check called_emsg for when using assert_fails(). +    if (!aborting() +        && did_emsg == did_emsg_before +        && called_emsg == called_emsg_before) { +      emsgf(_(e_invexpr2), start); +    } +  } +  return ret; +} + +static int eval_expr_typval(const typval_T *expr, typval_T *argv, +                            int argc, typval_T *rettv) +  FUNC_ATTR_NONNULL_ARG(1, 2, 4) +{ +  int dummy; + +  if (expr->v_type == VAR_FUNC) { +    const char_u *const s = expr->vval.v_string; +    if (s == NULL || *s == NUL) { +      return FAIL; +    } +    if (call_func(s, (int)STRLEN(s), rettv, argc, argv, NULL, +                  0L, 0L, &dummy, true, NULL, NULL) == FAIL) { +      return FAIL; +    } +  } else if (expr->v_type == VAR_PARTIAL) { +    partial_T *const partial = expr->vval.v_partial; +    const char_u *const s = partial_name(partial); +    if (s == NULL || *s == NUL) { +      return FAIL; +    } +    if (call_func(s, (int)STRLEN(s), rettv, argc, argv, NULL, +                  0L, 0L, &dummy, true, partial, NULL) == FAIL) { +      return FAIL; +    } +  } else { +    char buf[NUMBUFLEN]; +    char_u *s = (char_u *)tv_get_string_buf_chk(expr, buf); +    if (s == NULL) { +      return FAIL; +    } +    s = skipwhite(s); +    if (eval1_emsg(&s, rettv, true) == FAIL) { +      return FAIL; +    } +    if (*s != NUL) {  // check for trailing chars after expr +      tv_clear(rettv); +      emsgf(_(e_invexpr2), s); +      return FAIL; +    } +  } +  return OK; +} + +/// Like eval_to_bool() but using a typval_T instead of a string. +/// Works for string, funcref and partial. +static bool eval_expr_to_bool(const typval_T *expr, bool *error) +  FUNC_ATTR_NONNULL_ARG(1, 2) +{ +  typval_T argv, rettv; + +  if (eval_expr_typval(expr, &argv, 0, &rettv) == FAIL) { +    *error = true; +    return false; +  } +  const bool res = (tv_get_number_chk(&rettv, error) != 0); +  tv_clear(&rettv); +  return res; +} +  /// Top level evaluation function, returning a string  ///  /// @param[in]  arg  String to evaluate. @@ -1190,68 +1297,35 @@ int get_spellword(list_T *const list, const char **ret_word)  // Call some vim script function and return the result in "*rettv". -// Uses argv[argc] for the function arguments.  Only Number and String -// arguments are currently supported. +// Uses argv[0] to argv[argc-1] for the function arguments. argv[argc] +// should have type VAR_UNKNOWN.  //  // Return OK or FAIL.  int call_vim_function(      const char_u *func,      int argc, -    const char_u *const *const argv, -    bool safe,                       // use the sandbox -    int str_arg_only,               // all arguments are strings -    typval_T *rettv +    typval_T *argv, +    typval_T *rettv, +    bool safe                       // use the sandbox  )  { -  varnumber_T n; -  int len;    int doesrange;    void        *save_funccalp = NULL;    int ret; -  typval_T *argvars = xmalloc((argc + 1) * sizeof(typval_T)); - -  for (int i = 0; i < argc; i++) { -    // Pass a NULL or empty argument as an empty string -    if (argv[i] == NULL || *argv[i] == NUL) { -      argvars[i].v_type = VAR_STRING; -      argvars[i].vval.v_string = (char_u *)""; -      continue; -    } - -    if (str_arg_only) { -      len = 0; -    } else { -      // Recognize a number argument, the others must be strings. A dash -      // is a string too. -      vim_str2nr(argv[i], NULL, &len, STR2NR_ALL, &n, NULL, 0); -      if (len == 1 && *argv[i] == '-') { -        len = 0; -      } -    } -    if (len != 0 && len == (int)STRLEN(argv[i])) { -      argvars[i].v_type = VAR_NUMBER; -      argvars[i].vval.v_number = n; -    } else { -      argvars[i].v_type = VAR_STRING; -      argvars[i].vval.v_string = (char_u *)argv[i]; -    } -  } -    if (safe) {      save_funccalp = save_funccal();      ++sandbox;    }    rettv->v_type = VAR_UNKNOWN;  // tv_clear() uses this. -  ret = call_func(func, (int)STRLEN(func), rettv, argc, argvars, NULL, +  ret = call_func(func, (int)STRLEN(func), rettv, argc, argv, NULL,                    curwin->w_cursor.lnum, curwin->w_cursor.lnum,                    &doesrange, true, NULL, NULL);    if (safe) {      --sandbox;      restore_funccal(save_funccalp);    } -  xfree(argvars);    if (ret == FAIL) {      tv_clear(rettv); @@ -1259,47 +1333,44 @@ int call_vim_function(    return ret;  } -  /// Call Vim script function and return the result as a number  ///  /// @param[in]  func  Function name.  /// @param[in]  argc  Number of arguments. -/// @param[in]  argv  Array with string arguments. +/// @param[in]  argv  Array with typval_T arguments.  /// @param[in]  safe  Use with sandbox.  ///  /// @return -1 when calling function fails, result of function otherwise.  varnumber_T call_func_retnr(char_u *func, int argc, -                            const char_u *const *const argv, int safe) +                            typval_T *argv, int safe)  {    typval_T rettv;    varnumber_T retval; -  /* All arguments are passed as strings, no conversion to number. */ -  if (call_vim_function(func, argc, argv, safe, TRUE, &rettv) == FAIL) +  if (call_vim_function(func, argc, argv, &rettv, safe) == FAIL) {      return -1; - +  }    retval = tv_get_number_chk(&rettv, NULL);    tv_clear(&rettv);    return retval;  } -  /// Call Vim script function and return the result as a string  ///  /// @param[in]  func  Function name.  /// @param[in]  argc  Number of arguments. -/// @param[in]  argv  Array with string arguments. +/// @param[in]  argv  Array with typval_T arguments.  /// @param[in]  safe  Use the sandbox.  ///  /// @return [allocated] NULL when calling function fails, allocated string  ///                     otherwise.  char *call_func_retstr(const char *const func, int argc, -                       const char_u *const *argv, +                       typval_T *argv,                         bool safe)    FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC  {    typval_T rettv;    // All arguments are passed as strings, no conversion to number. -  if (call_vim_function((const char_u *)func, argc, argv, safe, true, &rettv) +  if (call_vim_function((const char_u *)func, argc, argv, &rettv, safe)        == FAIL) {      return NULL;    } @@ -1308,24 +1379,24 @@ char *call_func_retstr(const char *const func, int argc,    tv_clear(&rettv);    return retval;  } -  /// Call Vim script function and return the result as a List  ///  /// @param[in]  func  Function name.  /// @param[in]  argc  Number of arguments. -/// @param[in]  argv  Array with string arguments. +/// @param[in]  argv  Array with typval_T arguments.  /// @param[in]  safe  Use the sandbox.  ///  /// @return [allocated] NULL when calling function fails or return tv is not a  ///                     List, allocated List otherwise. -void *call_func_retlist(char_u *func, int argc, const char_u *const *argv, +void *call_func_retlist(char_u *func, int argc, typval_T *argv,                          bool safe)  {    typval_T rettv; -  /* All arguments are passed as strings, no conversion to number. */ -  if (call_vim_function(func, argc, argv, safe, TRUE, &rettv) == FAIL) +  // All arguments are passed as strings, no conversion to number. +  if (call_vim_function(func, argc, argv, &rettv, safe) == FAIL) {      return NULL; +  }    if (rettv.v_type != VAR_LIST) {      tv_clear(&rettv); @@ -1436,21 +1507,33 @@ int eval_foldexpr(char_u *arg, int *cp)    return (int)retval;  } -/* - * ":let"			list all variable values - * ":let var1 var2"		list variable values - * ":let var = expr"		assignment command. - * ":let var += expr"		assignment command. - * ":let var -= expr"		assignment command. - * ":let var *= expr"		assignment command. - * ":let var /= expr"		assignment command. - * ":let var %= expr"		assignment command. - * ":let var .= expr"		assignment command. - * ":let var ..= expr"		assignment command. - * ":let [var1, var2] = expr"	unpack list. - */ +// ":cons[t] var = expr1" define constant +// ":cons[t] [name1, name2, ...] = expr1" define constants unpacking list +// ":cons[t] [name, ..., ; lastname] = expr" define constants unpacking list +void ex_const(exarg_T *eap) +{ +  ex_let_const(eap, true); +} + +// ":let" list all variable values +// ":let var1 var2" list variable values +// ":let var = expr" assignment command. +// ":let var += expr" assignment command. +// ":let var -= expr" assignment command. +// ":let var *= expr" assignment command. +// ":let var /= expr" assignment command. +// ":let var %= expr" assignment command. +// ":let var .= expr" assignment command. +// ":let var ..= expr" assignment command. +// ":let [var1, var2] = expr" unpack list. +// ":let [name, ..., ; lastname] = expr" unpack list.  void ex_let(exarg_T *eap)  { +  ex_let_const(eap, false); +} + +static void ex_let_const(exarg_T *eap, const bool is_const) +{    char_u      *arg = eap->arg;    char_u      *expr = NULL;    typval_T rettv; @@ -1512,7 +1595,8 @@ void ex_let(exarg_T *eap)        }        emsg_skip--;      } else if (i != FAIL) { -      (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, op); +      (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, +                        is_const, op);        tv_clear(&rettv);      }    } @@ -1530,9 +1614,10 @@ static int  ex_let_vars(      char_u *arg_start,      typval_T *tv, -    int copy,                       /* copy values from "tv", don't move */ -    int semicolon,                  /* from skip_var_list() */ -    int var_count,                  /* from skip_var_list() */ +    int copy,                       // copy values from "tv", don't move +    int semicolon,                  // from skip_var_list() +    int var_count,                  // from skip_var_list() +    int is_const,                   // lock variables for :const      char_u *nextchars  )  { @@ -1543,8 +1628,9 @@ ex_let_vars(      /*       * ":let var = expr" or ":for var in list"       */ -    if (ex_let_one(arg, tv, copy, nextchars, nextchars) == NULL) +    if (ex_let_one(arg, tv, copy, is_const, nextchars, nextchars) == NULL) {        return FAIL; +    }      return OK;    } @@ -1572,8 +1658,8 @@ ex_let_vars(    size_t rest_len = tv_list_len(l);    while (*arg != ']') {      arg = skipwhite(arg + 1); -    arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, (const char_u *)",;]", -                     nextchars); +    arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, is_const, +                     (const char_u *)",;]", nextchars);      if (arg == NULL) {        return FAIL;      } @@ -1595,7 +1681,7 @@ ex_let_vars(        ltv.vval.v_list = rest_list;        tv_list_ref(rest_list); -      arg = ex_let_one(skipwhite(arg + 1), <v, false, +      arg = ex_let_one(skipwhite(arg + 1), <v, false, is_const,                         (char_u *)"]", nextchars);        tv_clear(<v);        if (arg == NULL) { @@ -1683,6 +1769,15 @@ static void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty,      if (!HASHITEM_EMPTY(hi)) {        todo--;        di = TV_DICT_HI2DI(hi); +      char buf[IOSIZE]; + +      // apply :filter /pat/ to variable name +      xstrlcpy(buf, prefix, IOSIZE - 1); +      xstrlcat(buf, (char *)di->di_key, IOSIZE); +      if (message_filtered((char_u *)buf)) { +        continue; +      } +        if (empty || di->di_tv.v_type != VAR_STRING            || di->di_tv.vval.v_string != NULL) {          list_one_var(di, prefix, first); @@ -1851,8 +1946,8 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first)  /// @return a pointer to the char just after the var name or NULL in case of  ///         error.  static char_u *ex_let_one(char_u *arg, typval_T *const tv, -                          const bool copy, const char_u *const endchars, -                          const char_u *const op) +                          const bool copy, const bool is_const, +                          const char_u *const endchars, const char_u *const op)    FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT  {    char_u *arg_end = NULL; @@ -1864,6 +1959,10 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,     * ":let $VAR = expr": Set environment variable.     */    if (*arg == '$') { +    if (is_const) { +      EMSG(_("E996: Cannot lock an environment variable")); +      return NULL; +    }      // Find the end of the name.      arg++;      char *name = (char *)arg; @@ -1909,6 +2008,10 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,    // ":let &l:option = expr": Set local option value.    // ":let &g:option = expr": Set global option value.    } else if (*arg == '&') { +    if (is_const) { +      EMSG(_("E996: Cannot lock an option")); +      return NULL; +    }      // Find the end of the name.      char *const p = (char *)find_option_end((const char **)&arg, &opt_flags);      if (p == NULL @@ -1938,8 +2041,8 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,                case '+': n = numval + n; break;                case '-': n = numval - n; break;                case '*': n = numval * n; break; -              case '/': n = numval / n; break; -              case '%': n = numval % n; break; +              case '/': n = num_divide(numval, n); break; +              case '%': n = num_modulus(numval, n); break;              }            } else if (opt_type == 0 && stringval != NULL) {  // string              char *const oldstringval = stringval; @@ -1959,6 +2062,10 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,      }    // ":let @r = expr": Set register contents.    } else if (*arg == '@') { +    if (is_const) { +      EMSG(_("E996: Cannot lock a register")); +      return NULL; +    }      arg++;      if (op != NULL && vim_strchr((char_u *)"+-*/%", *op) != NULL) {        emsgf(_(e_letwrong), op); @@ -1998,7 +2105,7 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,        if (endchars != NULL && vim_strchr(endchars, *skipwhite(p)) == NULL) {          EMSG(_(e_letunexp));        } else { -        set_var_lval(&lv, p, tv, copy, op); +        set_var_lval(&lv, p, tv, copy, is_const, op);          arg_end = p;        }      } @@ -2249,6 +2356,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,          /* Can't add "v:" variable. */          if (lp->ll_dict == &vimvardict) {            EMSG2(_(e_illvar), name); +          tv_clear(&var1);            return NULL;          } @@ -2363,7 +2471,7 @@ static void clear_lval(lval_T *lp)   * "%" for "%=", "." for ".=" or "=" for "=".   */  static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, -                         int copy, const char_u *op) +                         int copy, const bool is_const, const char_u *op)  {    int cc;    listitem_T  *ri; @@ -2375,6 +2483,12 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,      if (op != NULL && *op != '=') {        typval_T tv; +      if (is_const) { +        EMSG(_(e_cannot_mod)); +        *endp = cc; +        return; +      } +        // handle +=, -=, *=, /=, %= and .=        di = NULL;        if (get_var_tv((const char *)lp->ll_name, (int)STRLEN(lp->ll_name), @@ -2390,7 +2504,7 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,          tv_clear(&tv);        }      } else { -      set_var(lp->ll_name, lp->ll_name_len, rettv, copy); +      set_var_const(lp->ll_name, lp->ll_name_len, rettv, copy, is_const);      }      *endp = cc;    } else if (tv_check_lock(lp->ll_newkey == NULL @@ -2401,6 +2515,11 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,      listitem_T *ll_li = lp->ll_li;      int ll_n1 = lp->ll_n1; +    if (is_const) { +      EMSG(_("E996: Cannot lock a range")); +      return; +    } +      // Check whether any of the list items is locked      for (ri = tv_list_first(rettv->vval.v_list);           ri != NULL && ll_li != NULL; ) { @@ -2456,6 +2575,11 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,      dict_T *dict = lp->ll_dict;      bool watched = tv_dict_is_watched(dict); +    if (is_const) { +      EMSG(_("E996: Cannot lock a list or dict")); +      return; +    } +      // Assign to a List or Dictionary item.      if (lp->ll_newkey != NULL) {        if (op != NULL && *op != '=') { @@ -2579,7 +2703,7 @@ bool next_for_item(void *fi_void, char_u *arg)    } else {      fi->fi_lw.lw_item = TV_LIST_ITEM_NEXT(fi->fi_list, item);      return (ex_let_vars(arg, TV_LIST_ITEM_TV(item), true, -                        fi->fi_semicolon, fi->fi_varcount, NULL) == OK); +                        fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK);    }  } @@ -4048,22 +4172,9 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string)          if (op == '*') {            n1 = n1 * n2;          } else if (op == '/') { -          if (n2 == 0) {                // give an error message? -            if (n1 == 0) { -              n1 = VARNUMBER_MIN;  // similar to NaN -            } else if (n1 < 0) { -              n1 = -VARNUMBER_MAX; -            } else { -              n1 = VARNUMBER_MAX; -            } -          } else { -            n1 = n1 / n2; -          } +          n1 = num_divide(n1, n2);          } else { -          if (n2 == 0)                  /* give an error message? */ -            n1 = 0; -          else -            n1 = n1 % n2; +          n1 = num_modulus(n1, n2);          }          rettv->v_type = VAR_NUMBER;          rettv->vval.v_number = n1; @@ -4199,7 +4310,7 @@ static int eval7(    // Dictionary: {key: val, key: val}    case '{':   ret = get_lambda_tv(arg, rettv, evaluate);                if (ret == NOTDONE) { -                ret = get_dict_tv(arg, rettv, evaluate); +                ret = dict_get_tv(arg, rettv, evaluate);                }      break; @@ -5123,7 +5234,7 @@ bool garbage_collect(bool testing)        yankreg_T reg;        char name = NUL;        bool is_unnamed = false; -      reg_iter = op_register_iter(reg_iter, &name, ®, &is_unnamed); +      reg_iter = op_global_reg_iter(reg_iter, &name, ®, &is_unnamed);        if (name != NUL) {          ABORTING(set_ref_dict)(reg.additional_data, copyID);        } @@ -5165,7 +5276,7 @@ bool garbage_collect(bool testing)    {      Channel *data;      map_foreach_value(channels, data, { -      set_ref_in_callback_reader(&data->on_stdout, copyID, NULL, NULL); +      set_ref_in_callback_reader(&data->on_data, copyID, NULL, NULL);        set_ref_in_callback_reader(&data->on_stderr, copyID, NULL, NULL);        set_ref_in_callback(&data->on_exit, copyID, NULL, NULL);      }) @@ -5583,7 +5694,7 @@ static bool set_ref_in_funccal(funccall_T *fc, int copyID)   * Allocate a variable for a Dictionary and fill it from "*arg".   * Return OK or FAIL.  Returns NOTDONE for {expr}.   */ -static int get_dict_tv(char_u **arg, typval_T *rettv, int evaluate) +static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate)  {    dict_T      *d = NULL;    typval_T tvkey; @@ -5871,10 +5982,6 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)        fp->uf_scoped = NULL;      } -    fp->uf_tml_count = NULL; -    fp->uf_tml_total = NULL; -    fp->uf_tml_self = NULL; -    fp->uf_profiling = false;      if (prof_def_func()) {        func_do_profile(fp);      } @@ -6312,6 +6419,7 @@ call_func(      partial_T *partial,             // optional, can be NULL      dict_T *selfdict_in             // Dictionary for "self"  ) +  FUNC_ATTR_NONNULL_ARG(1, 3, 5, 9)  {    int ret = FAIL;    int error = ERROR_NONE; @@ -6545,6 +6653,10 @@ static void float_op_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr)  static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { +  if (check_restricted() || check_secure()) { +    return; +  } +    ApiDispatchWrapper fn = (ApiDispatchWrapper)fptr;    Array args = ARRAY_DICT_INIT; @@ -6969,10 +7081,14 @@ static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr)  {    const char *const cmd = tv_get_string_chk(&argvars[0]);    garray_T    ga; +  int         save_trylevel = trylevel; +  // trylevel must be zero for a ":throw" command to be considered failed +  trylevel = 0;    called_emsg = false;    suppress_errthrow = true;    emsg_silent = true; +    do_cmdline_cmd(cmd);    if (!called_emsg) {      prepare_assert_error(&ga); @@ -6994,6 +7110,7 @@ static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr)      }    } +  trylevel = save_trylevel;    called_emsg = false;    suppress_errthrow = false;    emsg_silent = false; @@ -7161,6 +7278,14 @@ static buf_T *find_buffer(typval_T *avar)    return buf;  } +// "bufadd(expr)" function +static void f_bufadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ +  char_u *name = (char_u *)tv_get_string(&argvars[0]); + +  rettv->vval.v_number = buflist_add(*name == NUL ? NULL : name, 0); +} +  /*   * "bufexists(expr)" function   */ @@ -7180,6 +7305,21 @@ static void f_buflisted(typval_T *argvars, typval_T *rettv, FunPtr fptr)    rettv->vval.v_number = (buf != NULL && buf->b_p_bl);  } +// "bufload(expr)" function +static void f_bufload(typval_T *argvars, typval_T *unused, FunPtr fptr) +{ +  buf_T *buf = get_buf_arg(&argvars[0]); + +  if (buf != NULL && buf->b_ml.ml_mfp == NULL) { +    aco_save_T aco; + +    aucmd_prepbuf(&aco, buf); +    swap_exists_action = SEA_NONE; +    open_buffer(false, NULL, 0); +    aucmd_restbuf(&aco); +  } +} +  /*   * "bufloaded(expr)" function   */ @@ -7856,6 +7996,116 @@ static void f_cscope_connection(typval_T *argvars, typval_T *rettv, FunPtr fptr)                                         (char_u *)prepend);  } +/// "ctxget([{index}])" function +static void f_ctxget(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ +  size_t index = 0; +  if (argvars[0].v_type == VAR_NUMBER) { +    index = argvars[0].vval.v_number; +  } else if (argvars[0].v_type != VAR_UNKNOWN) { +    EMSG2(_(e_invarg2), "expected nothing or a Number as an argument"); +    return; +  } + +  Context *ctx = ctx_get(index); +  if (ctx == NULL) { +    EMSG3(_(e_invargNval), "index", "out of bounds"); +    return; +  } + +  Dictionary ctx_dict = ctx_to_dict(ctx); +  Error err = ERROR_INIT; +  object_to_vim(DICTIONARY_OBJ(ctx_dict), rettv, &err); +  api_free_dictionary(ctx_dict); +  api_clear_error(&err); +} + +/// "ctxpop()" function +static void f_ctxpop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ +  if (!ctx_restore(NULL, kCtxAll)) { +    EMSG(_("Context stack is empty")); +  } +} + +/// "ctxpush([{types}])" function +static void f_ctxpush(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ +  int types = kCtxAll; +  if (argvars[0].v_type == VAR_LIST) { +    types = 0; +    TV_LIST_ITER(argvars[0].vval.v_list, li, { +      typval_T *tv_li = TV_LIST_ITEM_TV(li); +      if (tv_li->v_type == VAR_STRING) { +        if (strequal((char *)tv_li->vval.v_string, "regs")) { +          types |= kCtxRegs; +        } else if (strequal((char *)tv_li->vval.v_string, "jumps")) { +          types |= kCtxJumps; +        } else if (strequal((char *)tv_li->vval.v_string, "buflist")) { +          types |= kCtxBuflist; +        } else if (strequal((char *)tv_li->vval.v_string, "gvars")) { +          types |= kCtxGVars; +        } else if (strequal((char *)tv_li->vval.v_string, "sfuncs")) { +          types |= kCtxSFuncs; +        } else if (strequal((char *)tv_li->vval.v_string, "funcs")) { +          types |= kCtxFuncs; +        } +      } +    }); +  } else if (argvars[0].v_type != VAR_UNKNOWN) { +    EMSG2(_(e_invarg2), "expected nothing or a List as an argument"); +    return; +  } +  ctx_save(NULL, types); +} + +/// "ctxset({context}[, {index}])" function +static void f_ctxset(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ +  if (argvars[0].v_type != VAR_DICT) { +    EMSG2(_(e_invarg2), "expected dictionary as first argument"); +    return; +  } + +  size_t index = 0; +  if (argvars[1].v_type == VAR_NUMBER) { +    index = argvars[1].vval.v_number; +  } else if (argvars[1].v_type != VAR_UNKNOWN) { +    EMSG2(_(e_invarg2), "expected nothing or a Number as second argument"); +    return; +  } + +  Context *ctx = ctx_get(index); +  if (ctx == NULL) { +    EMSG3(_(e_invargNval), "index", "out of bounds"); +    return; +  } + +  int save_did_emsg = did_emsg; +  did_emsg = false; + +  Dictionary dict = vim_to_object(&argvars[0]).data.dictionary; +  Context tmp = CONTEXT_INIT; +  ctx_from_dict(dict, &tmp); + +  if (did_emsg) { +    ctx_free(&tmp); +  } else { +    ctx_free(ctx); +    *ctx = tmp; +  } + +  api_free_dictionary(dict); +  did_emsg = save_did_emsg; +} + +/// "ctxsize()" function +static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ +  rettv->v_type = VAR_NUMBER; +  rettv->vval.v_number = ctx_size(); +} +  /// "cursor(lnum, col)" function, or  /// "cursor(list)"  /// @@ -8246,6 +8496,25 @@ static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr)    rettv->vval.v_number = n;  } +/// "environ()" function +static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ +  tv_dict_alloc_ret(rettv); + +  for (int i = 0; ; i++) { +    // TODO(justinmk): use os_copyfullenv from #7202 ? +    char *envname = os_getenvname_at_index((size_t)i); +    if (envname == NULL) { +      break; +    } +    const char *value = os_getenv(envname); +    tv_dict_add_str(rettv->vval.v_dict, +                    (char *)envname, STRLEN((char *)envname), +                    value == NULL ? "" : value); +    xfree(envname); +  } +} +  /*   * "escape({string}, {chars})" function   */ @@ -8259,6 +8528,20 @@ static void f_escape(typval_T *argvars, typval_T *rettv, FunPtr fptr)    rettv->v_type = VAR_STRING;  } +/// "getenv()" function +static void f_getenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ +  char_u *p = (char_u *)vim_getenv(tv_get_string(&argvars[0])); + +  if (p == NULL) { +    rettv->v_type = VAR_SPECIAL; +    rettv->vval.v_number = kSpecialVarNull; +    return; +  } +  rettv->vval.v_string = p; +  rettv->v_type = VAR_STRING; +} +  /*   * "eval()" function   */ @@ -8298,10 +8581,7 @@ static void f_executable(typval_T *argvars, typval_T *rettv, FunPtr fptr)    const char *name = tv_get_string(&argvars[0]);    // Check in $PATH and also check directly if there is a directory name -  rettv->vval.v_number = ( -      os_can_exe((const char_u *)name, NULL, true) -      || (gettail_dir(name) != name -          && os_can_exe((const char_u *)name, NULL, false))); +  rettv->vval.v_number = os_can_exe(name, NULL, true);  }  typedef struct { @@ -8406,12 +8686,12 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr)  static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr)  {    const char *arg = tv_get_string(&argvars[0]); -  char_u *path = NULL; +  char *path = NULL; -  (void)os_can_exe((const char_u *)arg, &path, true); +  (void)os_can_exe(arg, &path, true);    rettv->v_type = VAR_STRING; -  rettv->vval.v_string = path; +  rettv->vval.v_string = (char_u *)path;  }  /// Find a window: When using a Window ID in any tab page, when using a number @@ -8438,7 +8718,7 @@ static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr)    const char *p = tv_get_string(&argvars[0]);    if (*p == '$') {  // Environment variable.      // First try "normal" environment variables (fast). -    if (os_getenv(p + 1) != NULL) { +    if (os_env_exists(p + 1)) {        n = true;      } else {        // Try expanding things like $VIM and ${HOME}. @@ -8834,6 +9114,7 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)        }        hash_unlock(ht);      } else { +      assert(argvars[0].v_type == VAR_LIST);        vimvars[VV_KEY].vv_type = VAR_NUMBER;        for (listitem_T *li = tv_list_first(l); li != NULL;) { @@ -8864,44 +9145,17 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)  }  static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) +  FUNC_ATTR_NONNULL_ARG(1, 2)  {    typval_T rettv;    typval_T argv[3];    int retval = FAIL; -  int dummy;    tv_copy(tv, &vimvars[VV_VAL].vv_tv);    argv[0] = vimvars[VV_KEY].vv_tv;    argv[1] = vimvars[VV_VAL].vv_tv; -  if (expr->v_type == VAR_FUNC) { -    const char_u *const s = expr->vval.v_string; -    if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, NULL, -                  0L, 0L, &dummy, true, NULL, NULL) == FAIL) { -      goto theend; -    } -  } else if (expr->v_type == VAR_PARTIAL) { -    partial_T *partial = expr->vval.v_partial; - -    const char_u *const s = partial_name(partial); -    if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, NULL, -                  0L, 0L, &dummy, true, partial, NULL) == FAIL) { -      goto theend; -    } -  } else { -    char buf[NUMBUFLEN]; -    const char *s = tv_get_string_buf_chk(expr, buf); -    if (s == NULL) { -      goto theend; -    } -    s = (const char *)skipwhite((const char_u *)s); -    if (eval1((char_u **)&s, &rettv, true) == FAIL) { -      goto theend; -    } - -    if (*s != NUL) {  // check for trailing chars after expr -      emsgf(_(e_invexpr2), s); -      goto theend; -    } +  if (eval_expr_typval(expr, argv, 2, &rettv) == FAIL) { +    goto theend;    }    if (map) {      // map(): replace the list item value. @@ -9360,6 +9614,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)    dictitem_T  *di;    dict_T      *d;    typval_T    *tv = NULL; +  bool what_is_dict = false;    if (argvars[0].v_type == VAR_LIST) {      if ((l = argvars[0].vval.v_list) != NULL) { @@ -9401,7 +9656,10 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)            func_ref(rettv->vval.v_string);          }        } else if (strcmp(what, "dict") == 0) { -        tv_dict_set_ret(rettv, pt->pt_dict); +        what_is_dict = true; +        if (pt->pt_dict != NULL) { +          tv_dict_set_ret(rettv, pt->pt_dict); +        }        } else if (strcmp(what, "args") == 0) {          rettv->v_type = VAR_LIST;          if (tv_list_alloc_ret(rettv, pt->pt_argc) != NULL) { @@ -9412,7 +9670,12 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)        } else {          EMSG2(_(e_invarg2), what);        } -      return; + +      // When {what} == "dict" and pt->pt_dict == NULL, evaluate the +      // third argument +      if (!what_is_dict) { +        return; +      }      }    } else {      EMSG2(_(e_listdictarg), "get()"); @@ -9886,13 +10149,13 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr)    if (strcmp(tv_get_string(&argvars[1]), "cmdline") == 0) {      set_one_cmd_context(&xpc, tv_get_string(&argvars[0])); -    xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern); +    xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);      goto theend;    }    ExpandInit(&xpc);    xpc.xp_pattern = (char_u *)tv_get_string(&argvars[0]); -  xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern); +  xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);    xpc.xp_context = cmdcomplete_str_to_type(        (char_u *)tv_get_string(&argvars[1]));    if (xpc.xp_context == EXPAND_NOTHING) { @@ -9902,17 +10165,17 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr)    if (xpc.xp_context == EXPAND_MENUS) {      set_context_in_menu_cmd(&xpc, (char_u *)"menu", xpc.xp_pattern, false); -    xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern); +    xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);    }    if (xpc.xp_context == EXPAND_CSCOPE) {      set_context_in_cscope_cmd(&xpc, (const char *)xpc.xp_pattern, CMD_cscope); -    xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern); +    xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);    }    if (xpc.xp_context == EXPAND_SIGN) {      set_context_in_sign_cmd(&xpc, xpc.xp_pattern); -    xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern); +    xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);    }  theend: @@ -10619,6 +10882,7 @@ static dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr)    tv_dict_add_nr(dict, S_LEN("bufnr"), wp->w_buffer->b_fnum);    tv_dict_add_nr(dict, S_LEN("wincol"), wp->w_wincol); +  tv_dict_add_nr(dict, S_LEN("terminal"), bt_terminal(wp->w_buffer));    tv_dict_add_nr(dict, S_LEN("quickfix"), bt_quickfix(wp->w_buffer));    tv_dict_add_nr(dict, S_LEN("loclist"),                   (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL)); @@ -10996,7 +11260,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)      "fork",  #endif      "gettext", -#if defined(HAVE_ICONV_H) && defined(USE_ICONV) +#if defined(HAVE_ICONV)      "iconv",  #endif      "insert_expand", @@ -11109,10 +11373,6 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)        n = stdout_isatty;      } else if (STRICMP(name, "multi_byte_encoding") == 0) {        n = has_mbyte != 0; -#if defined(USE_ICONV) && defined(DYNAMIC_ICONV) -    } else if (STRICMP(name, "iconv") == 0) { -      n = iconv_enabled(false); -#endif      } else if (STRICMP(name, "syntax_items") == 0) {        n = syntax_present(curwin);  #ifdef UNIX @@ -11935,9 +12195,18 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr)    rettv->vval.v_number = 1;  } +/// Builds a process argument vector from a VimL object (typval_T). +/// +/// @param[in]  cmd_tv      VimL object +/// @param[out] cmd         Returns the command or executable name. +/// @param[out] executable  Returns `false` if argv[0] is not executable. +/// +/// @returns Result of `shell_build_argv()` if `cmd_tv` is a String. +///          Else, string values of `cmd_tv` copied to a (char **) list with +///          argv[0] resolved to full path ($PATHEXT-resolved on Windows).  static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable)  { -  if (cmd_tv->v_type == VAR_STRING) { +  if (cmd_tv->v_type == VAR_STRING) {  // String => "shell semantics".      const char *cmd_str = tv_get_string(cmd_tv);      if (cmd) {        *cmd = cmd_str; @@ -11957,16 +12226,17 @@ static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable)      return NULL;    } -  const char *exe = tv_get_string_chk(TV_LIST_ITEM_TV(tv_list_first(argl))); -  if (!exe || !os_can_exe((const char_u *)exe, NULL, true)) { -    if (exe && executable) { +  const char *arg0 = tv_get_string_chk(TV_LIST_ITEM_TV(tv_list_first(argl))); +  char *exe_resolved = NULL; +  if (!arg0 || !os_can_exe(arg0, &exe_resolved, true)) { +    if (arg0 && executable) {        *executable = false;      }      return NULL;    }    if (cmd) { -    *cmd = exe; +    *cmd = exe_resolved;    }    // Build the argument vector @@ -11977,10 +12247,15 @@ static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable)      if (!a) {        // Did emsg in tv_get_string_chk; just deallocate argv.        shell_free_argv(argv); +      xfree(exe_resolved);        return NULL;      }      argv[i++] = xstrdup(a);    }); +  // Replace argv[0] with absolute path. The only reason for this is to make +  // $PATHEXT work on Windows with jobstart([…]). #9569 +  xfree(argv[0]); +  argv[0] = exe_resolved;    return argv;  } @@ -12080,14 +12355,21 @@ static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)      return;    } -    Channel *data = find_job(argvars[0].vval.v_number, true);    if (!data) {      return;    } +  const char *error = NULL; +  if (data->is_rpc) { +    // Ignore return code, but show error later. +    (void)channel_close(data->id, kChannelPartRpc, &error); +  }    process_stop((Process *)&data->stream.proc);    rettv->vval.v_number = 1; +  if (error) { +    EMSG(error); +  }  }  // "jobwait(ids[, timeout])" function @@ -13454,7 +13736,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)    // Always open the file in binary mode, library functions have a mind of    // their own about CR-LF conversion.    const char *const fname = tv_get_string(&argvars[0]); -  if (*fname == NUL || (fd = mch_fopen(fname, READBIN)) == NULL) { +  if (*fname == NUL || (fd = os_fopen(fname, READBIN)) == NULL) {      EMSG2(_(e_notopen), *fname == NUL ? _("<empty>") : fname);      return;    } @@ -13686,11 +13968,7 @@ static void f_reltime(typval_T *argvars, typval_T *rettv, FunPtr fptr)    tv_list_append_number(rettv->vval.v_list, u.split.low);  } -/// f_reltimestr - return a string that represents the value of {time} -/// -/// @return The string representation of the argument, the format is the -///         number of seconds followed by a dot, followed by the number -///         of microseconds. +/// "reltimestr()" function  static void f_reltimestr(typval_T *argvars, typval_T *rettv, FunPtr fptr)    FUNC_ATTR_NONNULL_ALL  { @@ -13699,7 +13977,7 @@ static void f_reltimestr(typval_T *argvars, typval_T *rettv, FunPtr fptr)    rettv->v_type = VAR_STRING;    rettv->vval.v_string = NULL;    if (list2proftime(&argvars[0], &tm) == OK) { -    rettv->vval.v_string = (char_u *) xstrdup(profile_msg(tm)); +    rettv->vval.v_string = (char_u *)xstrdup(profile_msg(tm));    }  } @@ -14501,10 +14779,10 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos)    long lnum_stop = 0;    long time_limit = 0; -  // Get the three pattern arguments: start, middle, end. +  // Get the three pattern arguments: start, middle, end. Will result in an +  // error if not a valid argument.    char nbuf1[NUMBUFLEN];    char nbuf2[NUMBUFLEN]; -  char nbuf3[NUMBUFLEN];    const char *spat = tv_get_string_chk(&argvars[0]);    const char *mpat = tv_get_string_buf_chk(&argvars[1], nbuf1);    const char *epat = tv_get_string_buf_chk(&argvars[2], nbuf2); @@ -14532,23 +14810,28 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos)    }    // Optional fifth argument: skip expression. -  const char *skip; +  const typval_T *skip;    if (argvars[3].v_type == VAR_UNKNOWN        || argvars[4].v_type == VAR_UNKNOWN) { -    skip = ""; +    skip = NULL;    } else { -    skip = tv_get_string_buf_chk(&argvars[4], nbuf3); -    if (skip == NULL) { +    skip = &argvars[4]; +    if (skip->v_type != VAR_FUNC +        && skip->v_type != VAR_PARTIAL +        && skip->v_type != VAR_STRING) { +      emsgf(_(e_invarg2), tv_get_string(&argvars[4]));        goto theend;  // Type error.      }      if (argvars[5].v_type != VAR_UNKNOWN) {        lnum_stop = tv_get_number_chk(&argvars[5], NULL);        if (lnum_stop < 0) { +        emsgf(_(e_invarg2), tv_get_string(&argvars[5]));          goto theend;        }        if (argvars[6].v_type != VAR_UNKNOWN) {          time_limit = tv_get_number_chk(&argvars[6], NULL);          if (time_limit < 0) { +          emsgf(_(e_invarg2), tv_get_string(&argvars[6]));            goto theend;          }        } @@ -14556,7 +14839,7 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos)    }    retval = do_searchpair( -      (char_u *)spat, (char_u *)mpat, (char_u *)epat, dir, (char_u *)skip, +      (char_u *)spat, (char_u *)mpat, (char_u *)epat, dir, skip,        flags, match_pos, lnum_stop, time_limit);  theend: @@ -14604,7 +14887,7 @@ do_searchpair(      char_u *mpat,          // middle pattern      char_u *epat,          // end pattern      int dir,               // BACKWARD or FORWARD -    char_u *skip,          // skip expression +    const typval_T *skip,  // skip expression      int flags,             // SP_SETPCMARK and other SP_ values      pos_T *match_pos,      linenr_T lnum_stop,    // stop at this line if not zero @@ -14620,8 +14903,8 @@ do_searchpair(    pos_T save_cursor;    pos_T save_pos;    int n; -  int r;    int nest = 1; +  bool use_skip = false;    int options = SEARCH_KEEP;    proftime_T tm;    size_t pat2_len; @@ -14651,6 +14934,13 @@ do_searchpair(      options |= SEARCH_START;    } +  if (skip != NULL) { +    // Empty string means to not use the skip expression. +    if (skip->v_type == VAR_STRING || skip->v_type == VAR_FUNC) { +      use_skip = skip->vval.v_string != NULL && *skip->vval.v_string != NUL; +    } +  } +    save_cursor = curwin->w_cursor;    pos = curwin->w_cursor;    clearpos(&firstpos); @@ -14680,12 +14970,12 @@ do_searchpair(      /* clear the start flag to avoid getting stuck here */      options &= ~SEARCH_START; -    /* If the skip pattern matches, ignore this match. */ -    if (*skip != NUL) { +    // If the skip pattern matches, ignore this match. +    if (use_skip) {        save_pos = curwin->w_cursor;        curwin->w_cursor = pos; -      bool err; -      r = eval_to_bool(skip, &err, NULL, false); +      bool err = false; +      const bool r = eval_expr_to_bool(skip, &err);        curwin->w_cursor = save_pos;        if (err) {          /* Evaluating {skip} caused an error, break here. */ @@ -14846,6 +15136,123 @@ static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)    }  } +/// Set line or list of lines in buffer "buf". +static void set_buffer_lines(buf_T *buf, linenr_T lnum, typval_T *lines, +                             typval_T *rettv) +{ +  list_T      *l = NULL; +  listitem_T  *li = NULL; +  long        added = 0; +  linenr_T    lcount; +  buf_T       *curbuf_save = NULL; +  win_T       *curwin_save = NULL; +  int         is_curbuf = buf == curbuf; + +  // When using the current buffer ml_mfp will be set if needed.  Useful when +  // setline() is used on startup.  For other buffers the buffer must be +  // loaded. +  if (buf == NULL || (!is_curbuf && buf->b_ml.ml_mfp == NULL) || lnum < 1) { +    rettv->vval.v_number = 1;  // FAIL +    return; +  } + +  if (!is_curbuf) { +    wininfo_T *wip; + +    curbuf_save = curbuf; +    curwin_save = curwin; +    curbuf = buf; +    for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) { +      if (wip->wi_win != NULL) { +        curwin = wip->wi_win; +        break; +      } +    } +  } + +  lcount = curbuf->b_ml.ml_line_count; + +  const char *line = NULL; + +  if (lines->v_type == VAR_LIST) { +    l = lines->vval.v_list; +    li = tv_list_first(l); +  } else { +    line = tv_get_string_chk(lines); +  } + +  // Default result is zero == OK. +  for (;; ) { +    if (lines->v_type == VAR_LIST) { +      // List argument, get next string. +      if (li == NULL) { +        break; +      } +      line = tv_get_string_chk(TV_LIST_ITEM_TV(li)); +      li = TV_LIST_ITEM_NEXT(l, li); +    } + +    rettv->vval.v_number = 1;  // FAIL +    if (line == NULL || lnum > curbuf->b_ml.ml_line_count + 1) { +      break; +    } + +    // When coming here from Insert mode, sync undo, so that this can be +    // undone separately from what was previously inserted. +    if (u_sync_once == 2) { +      u_sync_once = 1;  // notify that u_sync() was called +      u_sync(true); +    } + +    if (lnum <= curbuf->b_ml.ml_line_count) { +      // Existing line, replace it. +      if (u_savesub(lnum) == OK +          && ml_replace(lnum, (char_u *)line, true) == OK) { +        changed_bytes(lnum, 0); +        if (is_curbuf && lnum == curwin->w_cursor.lnum) { +          check_cursor_col(); +        } +        rettv->vval.v_number = 0;  // OK +      } +    } else if (added > 0 || u_save(lnum - 1, lnum) == OK) { +      // lnum is one past the last line, append the line. +      added++; +      if (ml_append(lnum - 1, (char_u *)line, 0, false) == OK) { +        rettv->vval.v_number = 0;  // OK +      } +    } + +    if (l == NULL)                      /* only one string argument */ +      break; +    ++lnum; +  } + +  if (added > 0) { +    appended_lines_mark(lcount, added); +  } + +  if (!is_curbuf) { +     curbuf = curbuf_save; +     curwin = curwin_save; +  } +} + +/// "setbufline()" function +static void f_setbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ +    linenr_T lnum; +    buf_T    *buf; + +    buf = tv_get_buf(&argvars[0], false); +    if (buf == NULL) { +      rettv->vval.v_number = 1;  // FAIL +    } else { +      lnum = tv_get_lnum_buf(&argvars[1], buf); + +      set_buffer_lines(buf, lnum, &argvars[2], rettv); +    } +} +  /*   * "setbufvar()" function   */ @@ -14941,6 +15348,20 @@ static void f_setcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)    }  } +/// "setenv()" function +static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ +  char namebuf[NUMBUFLEN]; +  char valbuf[NUMBUFLEN]; +  const char *name = tv_get_string_buf(&argvars[0], namebuf); + +  if (argvars[1].v_type == VAR_SPECIAL +      && argvars[1].vval.v_number == kSpecialVarNull) { +    os_unsetenv(name); +  } else { +    os_setenv(name, tv_get_string_buf(&argvars[1], valbuf), 1); +  } +}  /// "setfperm({fname}, {mode})" function  static void f_setfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) @@ -14978,67 +15399,8 @@ static void f_setfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr)   */  static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { -  list_T      *l = NULL; -  listitem_T  *li = NULL; -  long added = 0; -  linenr_T lcount = curbuf->b_ml.ml_line_count; -    linenr_T lnum = tv_get_lnum(&argvars[0]); -  const char *line = NULL; -  if (argvars[1].v_type == VAR_LIST) { -    l = argvars[1].vval.v_list; -    li = tv_list_first(l); -  } else { -    line = tv_get_string_chk(&argvars[1]); -  } - -  // Default result is zero == OK. -  for (;; ) { -    if (argvars[1].v_type == VAR_LIST) { -      // List argument, get next string. -      if (li == NULL) { -        break; -      } -      line = tv_get_string_chk(TV_LIST_ITEM_TV(li)); -      li = TV_LIST_ITEM_NEXT(l, li); -    } - -    rettv->vval.v_number = 1;  // FAIL -    if (line == NULL || lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) { -      break; -    } - -    /* When coming here from Insert mode, sync undo, so that this can be -     * undone separately from what was previously inserted. */ -    if (u_sync_once == 2) { -      u_sync_once = 1;       /* notify that u_sync() was called */ -      u_sync(TRUE); -    } - -    if (lnum <= curbuf->b_ml.ml_line_count) { -      // Existing line, replace it. -      if (u_savesub(lnum) == OK -          && ml_replace(lnum, (char_u *)line, true) == OK) { -        changed_bytes(lnum, 0); -        if (lnum == curwin->w_cursor.lnum) -          check_cursor_col(); -        rettv->vval.v_number = 0;               /* OK */ -      } -    } else if (added > 0 || u_save(lnum - 1, lnum) == OK) { -      // lnum is one past the last line, append the line. -      added++; -      if (ml_append(lnum - 1, (char_u *)line, 0, false) == OK) { -        rettv->vval.v_number = 0;  // OK -      } -    } - -    if (l == NULL)                      /* only one string argument */ -      break; -    ++lnum; -  } - -  if (added > 0) -    appended_lines_mark(lcount, added); +  set_buffer_lines(curbuf, lnum, &argvars[1], rettv);  }  /// Create quickfix/location list from VimL values @@ -15390,7 +15752,7 @@ free_lstval:    if (set_unnamed) {      // Discard the result. We already handle the error case. -    if (op_register_set_previous(regname)) { } +    if (op_reg_set_previous(regname)) { }    }  } @@ -15503,7 +15865,7 @@ static void f_setwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)  static void setwinvar(typval_T *argvars, typval_T *rettv, int off)  { -  if (check_restricted() || check_secure()) { +  if (check_secure()) {      return;    } @@ -15681,6 +16043,7 @@ static void f_sign_getplaced(typval_T *argvars, typval_T *rettv, FunPtr fptr)          if (notanum) {            return;          } +        (void)lnum;          lnum = tv_get_lnum(&di->di_tv);        }        if ((di = tv_dict_find(dict, "id", -1)) != NULL) { @@ -15735,9 +16098,6 @@ static void f_sign_jump(typval_T *argvars, typval_T *rettv, FunPtr fptr)      sign_group = NULL;  // global sign group    } else {      sign_group = xstrdup(sign_group_chk); -    if (sign_group == NULL) { -      return; -    }    }    // Buffer to place the sign @@ -15786,9 +16146,6 @@ static void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr)      group = NULL;  // global sign group    } else {      group = vim_strsave((const char_u *)group_chk); -    if (group == NULL) { -      return; -    }    }    // Sign name @@ -15816,6 +16173,7 @@ static void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr)        if (notanum) {          goto cleanup;        } +      (void)lnum;        lnum = tv_get_lnum(&di->di_tv);      }      if ((di = tv_dict_find(dict, "priority", -1)) != NULL) { @@ -15881,9 +16239,6 @@ static void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr)      group = NULL;  // global sign group    } else {      group = vim_strsave((const char_u *)group_chk); -    if (group == NULL) { -      return; -    }    }    if (argvars[1].v_type != VAR_UNKNOWN) { @@ -16352,9 +16707,7 @@ static void f_uniq(typval_T *argvars, typval_T *rettv, FunPtr fptr)    do_sort_uniq(argvars, rettv, false);  } -//  // "reltimefloat()" function -//  static void f_reltimefloat(typval_T *argvars , typval_T *rettv, FunPtr fptr)    FUNC_ATTR_NONNULL_ALL  { @@ -16363,7 +16716,7 @@ static void f_reltimefloat(typval_T *argvars , typval_T *rettv, FunPtr fptr)    rettv->v_type = VAR_FLOAT;    rettv->vval.v_float = 0;    if (list2proftime(&argvars[0], &tm) == OK) { -    rettv->vval.v_float = ((float_T)tm) / 1000000000; +    rettv->vval.v_float = (float_T)profile_signed(tm) / 1000000000.0;    }  } @@ -16406,6 +16759,8 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr)            break;          }          str += len; +        capcol -= len; +        len = 0;        }      }    } @@ -17776,7 +18131,6 @@ static void add_timer_info(typval_T *rettv, timer_T *timer)      di->di_tv.v_type = VAR_FUNC;      di->di_tv.vval.v_string = vim_strsave(timer->callback.data.funcref);    } -  di->di_tv.v_lock = 0;  }  static void add_timer_info_all(typval_T *rettv) @@ -19033,8 +19387,11 @@ static int get_name_len(const char **const arg,    }    len += get_id_len(arg); -  if (len == 0 && verbose) +  // Only give an error when there is something, otherwise it will be +  // reported at a higher level. +  if (len == 0 && verbose && **arg != NUL) {      EMSG2(_(e_invexpr2), *arg); +  }    return len;  } @@ -20047,6 +20404,24 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv,                      const bool copy)    FUNC_ATTR_NONNULL_ALL  { +  set_var_const(name, name_len, tv, copy, false); +} + +/// Set variable to the given value +/// +/// If the variable already exists, the value is updated. Otherwise the variable +/// is created. +/// +/// @param[in]  name  Variable name to set. +/// @param[in]  name_len  Length of the variable name. +/// @param  tv  Variable value. +/// @param[in]  copy  True if value in tv is to be copied. +/// @param[in]  is_const  True if value in tv is to be locked. +static void set_var_const(const char *name, const size_t name_len, +                          typval_T *const tv, const bool copy, +                          const bool is_const) +  FUNC_ATTR_NONNULL_ALL +{    dictitem_T  *v;    hashtab_T   *ht;    dict_T *dict; @@ -20072,6 +20447,11 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv,    typval_T oldtv = TV_INITIAL_VALUE;    if (v != NULL) { +    if (is_const) { +      EMSG(_(e_cannot_mod)); +      return; +    } +      // existing variable, need to clear the value      if (var_check_ro(v->di_flags, name, name_len)          || tv_check_lock(v->di_tv.v_lock, name, name_len)) { @@ -20138,6 +20518,9 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv,        return;      }      v->di_flags = DI_FLAGS_ALLOC; +    if (is_const) { +      v->di_flags |= DI_FLAGS_LOCK; +    }    }    if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) { @@ -20156,6 +20539,10 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv,        tv_clear(&oldtv);      }    } + +  if (is_const) { +    v->di_tv.v_lock |= VAR_LOCKED; +  }  }  /// Check whether variable is read-only (DI_FLAGS_RO, DI_FLAGS_RO_SBX) @@ -20480,29 +20867,19 @@ void ex_echohl(exarg_T *eap)   */  void ex_execute(exarg_T *eap)  { -  char_u      *arg = eap->arg; +  char_u *arg = eap->arg;    typval_T rettv;    int ret = OK; -  char_u      *p;    garray_T ga; -  int save_did_emsg = did_emsg; +  int save_did_emsg;    ga_init(&ga, 1, 80);    if (eap->skip)      ++emsg_skip;    while (*arg != NUL && *arg != '|' && *arg != '\n') { -    p = arg; -    if (eval1(&arg, &rettv, !eap->skip) == FAIL) { -      /* -       * Report the invalid expression unless the expression evaluation -       * has been cancelled due to an aborting error, an interrupt, or an -       * exception. -       */ -      if (!aborting() && did_emsg == save_did_emsg) { -        EMSG2(_(e_invexpr2), p); -      } -      ret = FAIL; +    ret = eval1_emsg(&arg, &rettv, !eap->skip); +    if (ret == FAIL) {        break;      } @@ -20632,8 +21009,11 @@ void ex_function(exarg_T *eap)          if (!HASHITEM_EMPTY(hi)) {            --todo;            fp = HI2UF(hi); +          if (message_filtered(fp->uf_name)) { +            continue; +          }            if (!func_name_refcount(fp->uf_name)) { -            list_func_head(fp, false); +            list_func_head(fp, false, false);            }          }        } @@ -20664,7 +21044,7 @@ void ex_function(exarg_T *eap)              fp = HI2UF(hi);              if (!isdigit(*fp->uf_name)                  && vim_regexec(®match, fp->uf_name, 0)) -              list_func_head(fp, FALSE); +              list_func_head(fp, false, false);            }          }          vim_regfree(regmatch.regprog); @@ -20714,9 +21094,12 @@ void ex_function(exarg_T *eap)    saved_did_emsg = did_emsg;    did_emsg = FALSE; -  /* -   * ":function func" with only function name: list function. -   */ +  // +  // ":function func" with only function name: list function. +  // If bang is given: +  //  - include "!" in function head +  //  - exclude line numbers from function body +  //    if (!paren) {      if (!ends_excmd(*skipwhite(p))) {        EMSG(_(e_trailing)); @@ -20728,17 +21111,20 @@ void ex_function(exarg_T *eap)      if (!eap->skip && !got_int) {        fp = find_func(name);        if (fp != NULL) { -        list_func_head(fp, TRUE); -        for (int j = 0; j < fp->uf_lines.ga_len && !got_int; ++j) { -          if (FUNCLINE(fp, j) == NULL) +        list_func_head(fp, !eap->forceit, eap->forceit); +        for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) { +          if (FUNCLINE(fp, j) == NULL) {              continue; -          msg_putchar('\n'); -          msg_outnum((long)j + 1); -          if (j < 9) { -            msg_putchar(' ');            } -          if (j < 99) { -            msg_putchar(' '); +          msg_putchar('\n'); +          if (!eap->forceit) { +            msg_outnum((long)j + 1); +            if (j < 9) { +              msg_putchar(' '); +            } +            if (j < 99) { +              msg_putchar(' '); +            }            }            msg_prt_line(FUNCLINE(fp, j), false);            ui_flush();                  // show a line at a time @@ -20746,7 +21132,7 @@ void ex_function(exarg_T *eap)          }          if (!got_int) {            msg_putchar('\n'); -          msg_puts("   endfunction"); +          msg_puts(eap->forceit ? "endfunction" : "   endfunction");          }        } else          emsg_funcname(N_("E123: Undefined function: %s"), name); @@ -20895,7 +21281,8 @@ void ex_function(exarg_T *eap)        goto erret;      }      if (show_block) { -      ui_ext_cmdline_block_append(indent, (const char *)theline); +      assert(indent >= 0); +      ui_ext_cmdline_block_append((size_t)indent, (const char *)theline);      }      /* Detect line continuation: sourcing_lnum increased more than one. */ @@ -21064,9 +21451,10 @@ void ex_function(exarg_T *eap)          overwrite = true;        } else {          // redefine existing function -        ga_clear_strings(&(fp->uf_args)); -        ga_clear_strings(&(fp->uf_lines));          XFREE_CLEAR(name); +        func_clear_items(fp); +        fp->uf_profiling = false; +        fp->uf_prof_initialized = false;        }      }    } else { @@ -21137,7 +21525,6 @@ void ex_function(exarg_T *eap)          tv_clear(&fudi.fd_di->di_tv);        }        fudi.fd_di->di_tv.v_type = VAR_FUNC; -      fudi.fd_di->di_tv.v_lock = 0;        fudi.fd_di->di_tv.vval.v_string = vim_strsave(name);        /* behave like "dict" was used */ @@ -21162,12 +21549,9 @@ void ex_function(exarg_T *eap)    } else {      fp->uf_scoped = NULL;    } -  fp->uf_tml_count = NULL; -  fp->uf_tml_total = NULL; -  fp->uf_tml_self = NULL; -  fp->uf_profiling = FALSE; -  if (prof_def_func()) +  if (prof_def_func()) {      func_do_profile(fp); +  }    fp->uf_varargs = varargs;    if (sandbox) {      flags |= FC_SANDBOX; @@ -21438,15 +21822,17 @@ static inline bool eval_fname_sid(const char *const name)    return *name == 's' || TOUPPER_ASC(name[2]) == 'I';  } -/* - * List the head of the function: "name(arg1, arg2)". - */ -static void list_func_head(ufunc_T *fp, int indent) +/// List the head of the function: "name(arg1, arg2)". +/// +/// @param[in]  fp      Function pointer. +/// @param[in]  indent  Indent line. +/// @param[in]  force   Include bang "!" (i.e.: "function!"). +static void list_func_head(ufunc_T *fp, int indent, bool force)  {    msg_start();    if (indent)      MSG_PUTS("   "); -  MSG_PUTS("function "); +  MSG_PUTS(force ? "function! " : "function ");    if (fp->uf_name[0] == K_SPECIAL) {      MSG_PUTS_ATTR("<SNR>", HL_ATTR(HLF_8));      msg_puts((const char *)fp->uf_name + 3); @@ -21628,25 +22014,29 @@ static void func_do_profile(ufunc_T *fp)  {    int len = fp->uf_lines.ga_len; -  if (len == 0) -    len = 1;      /* avoid getting error for allocating zero bytes */ -  fp->uf_tm_count = 0; -  fp->uf_tm_self = profile_zero(); -  fp->uf_tm_total = profile_zero(); +  if (!fp->uf_prof_initialized) { +    if (len == 0) { +      len = 1;  // avoid getting error for allocating zero bytes +    } +    fp->uf_tm_count = 0; +    fp->uf_tm_self = profile_zero(); +    fp->uf_tm_total = profile_zero(); -  if (fp->uf_tml_count == NULL) { -    fp->uf_tml_count = xcalloc(len, sizeof(int)); -  } +    if (fp->uf_tml_count == NULL) { +      fp->uf_tml_count = xcalloc(len, sizeof(int)); +    } -  if (fp->uf_tml_total == NULL) { -    fp->uf_tml_total = xcalloc(len, sizeof(proftime_T)); -  } +    if (fp->uf_tml_total == NULL) { +      fp->uf_tml_total = xcalloc(len, sizeof(proftime_T)); +    } -  if (fp->uf_tml_self == NULL) { -    fp->uf_tml_self = xcalloc(len, sizeof(proftime_T)); -  } +    if (fp->uf_tml_self == NULL) { +      fp->uf_tml_self = xcalloc(len, sizeof(proftime_T)); +    } -  fp->uf_tml_idx = -1; +    fp->uf_tml_idx = -1; +    fp->uf_prof_initialized = true; +  }    fp->uf_profiling = TRUE;  } @@ -21672,7 +22062,7 @@ void func_dump_profile(FILE *fd)      if (!HASHITEM_EMPTY(hi)) {        --todo;        fp = HI2UF(hi); -      if (fp->uf_profiling) { +      if (fp->uf_prof_initialized) {          sorttab[st_len++] = fp;          if (fp->uf_name[0] == K_SPECIAL) @@ -21832,6 +22222,7 @@ static bool script_autoload(const char *const name, const size_t name_len,  }  /// Return the autoload script name for a function or variable name +/// Caller must make sure that "name" contains AUTOLOAD_CHAR.  ///  /// @param[in]  name  Variable/function name.  /// @param[in]  name_len  Name length. @@ -22015,6 +22406,16 @@ static bool func_remove(ufunc_T *fp)    return false;  } +static void func_clear_items(ufunc_T *fp) +{ +  ga_clear_strings(&(fp->uf_args)); +  ga_clear_strings(&(fp->uf_lines)); + +  XFREE_CLEAR(fp->uf_tml_count); +  XFREE_CLEAR(fp->uf_tml_total); +  XFREE_CLEAR(fp->uf_tml_self); +} +  /// Free all things that a function contains. Does not free the function  /// itself, use func_free() for that.  /// @@ -22027,11 +22428,7 @@ static void func_clear(ufunc_T *fp, bool force)    fp->uf_cleared = true;    // clear this function -  ga_clear_strings(&(fp->uf_args)); -  ga_clear_strings(&(fp->uf_lines)); -  xfree(fp->uf_tml_count); -  xfree(fp->uf_tml_total); -  xfree(fp->uf_tml_self); +  func_clear_items(fp);    funccal_unref(fp->uf_scoped, fp, force);  } @@ -22174,6 +22571,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,    char_u      *name;    proftime_T wait_start;    proftime_T call_start; +  int started_profiling = false;    bool did_save_redo = false;    save_redo_T save_redo; @@ -22393,8 +22791,10 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,      do_profiling_yes      && !fp->uf_profiling && has_profiling(false, fp->uf_name, NULL); -  if (func_not_yet_profiling_but_should) +  if (func_not_yet_profiling_but_should) { +    started_profiling = true;      func_do_profile(fp); +  }    bool func_or_func_caller_profiling =      do_profiling_yes @@ -22442,6 +22842,10 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,        fc->caller->func->uf_tml_children =          profile_add(fc->caller->func->uf_tml_children, call_start);      } +    if (started_profiling) { +      // make a ":profdel func" stop profiling the function +      fp->uf_profiling = false; +    }    }    /* when being verbose, mention the return value */ @@ -23012,7 +23416,7 @@ dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen,  /// @return Pointer that needs to be passed to next `var_shada_iter` invocation  ///         or NULL to indicate that iteration is over.  const void *var_shada_iter(const void *const iter, const char **const name, -                           typval_T *rettv) +                           typval_T *rettv, var_flavour_T flavour)    FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(2, 3)  {    const hashitem_T *hi; @@ -23023,7 +23427,7 @@ const void *var_shada_iter(const void *const iter, const char **const name,      hi = globvarht.ht_array;      while ((size_t) (hi - hifirst) < hinum             && (HASHITEM_EMPTY(hi) -               || var_flavour(hi->hi_key) != VAR_FLAVOUR_SHADA)) { +               || !(var_flavour(hi->hi_key) & flavour))) {        hi++;      }      if ((size_t) (hi - hifirst) == hinum) { @@ -23035,7 +23439,7 @@ const void *var_shada_iter(const void *const iter, const char **const name,    *name = (char *)TV_DICT_HI2DI(hi)->di_key;    tv_copy(&TV_DICT_HI2DI(hi)->di_tv, rettv);    while ((size_t)(++hi - hifirst) < hinum) { -    if (!HASHITEM_EMPTY(hi) && var_flavour(hi->hi_key) == VAR_FLAVOUR_SHADA) { +    if (!HASHITEM_EMPTY(hi) && (var_flavour(hi->hi_key) & flavour)) {        return hi;      }    } @@ -23609,50 +24013,57 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)    return rettv;  } +/// Checks if a named provider is enabled.  bool eval_has_provider(const char *name)  { -#define CHECK_PROVIDER(name) \ -  if (has_##name == -1) { \ -    has_##name = !!find_func((char_u *)"provider#" #name "#Call"); \ -    if (!has_##name) { \ -      script_autoload("provider#" #name "#Call", \ -                      sizeof("provider#" #name "#Call") - 1, \ -                      false); \ -      has_##name = !!find_func((char_u *)"provider#" #name "#Call"); \ -    } \ -  } - -  static int has_clipboard = -1; -  static int has_python = -1; -  static int has_python3 = -1; -  static int has_ruby = -1; - -  if (strequal(name, "clipboard")) { -    CHECK_PROVIDER(clipboard); -    return has_clipboard; -  } else if (strequal(name, "python3")) { -    CHECK_PROVIDER(python3); -    return has_python3; -  } else if (strequal(name, "python")) { -    CHECK_PROVIDER(python); -    return has_python; -  } else if (strequal(name, "ruby")) { -    bool need_check_ruby = (has_ruby == -1); -    CHECK_PROVIDER(ruby); -    if (need_check_ruby && has_ruby == 1) { -      char *rubyhost = call_func_retstr("provider#ruby#Detect", 0, NULL, true); -      if (rubyhost) { -        if (*rubyhost == NUL) { -          // Invalid rubyhost executable. Gem is probably not installed. -          has_ruby = 0; -        } -        xfree(rubyhost); +  if (!strequal(name, "clipboard") +      && !strequal(name, "python") +      && !strequal(name, "python3") +      && !strequal(name, "ruby") +      && !strequal(name, "node")) { +    // Avoid autoload for non-provider has() features. +    return false; +  } + +  char buf[256]; +  int len; +  typval_T tv; + +  // Get the g:loaded_xx_provider variable. +  len = snprintf(buf, sizeof(buf), "g:loaded_%s_provider", name); +  if (get_var_tv(buf, len, &tv, NULL, false, true) == FAIL) { +    // Trigger autoload once. +    len = snprintf(buf, sizeof(buf), "provider#%s#bogus", name); +    script_autoload(buf, len, false); + +    // Retry the (non-autoload-style) variable. +    len = snprintf(buf, sizeof(buf), "g:loaded_%s_provider", name); +    if (get_var_tv(buf, len, &tv, NULL, false, true) == FAIL) { +      // Show a hint if Call() is defined but g:loaded_xx_provider is missing. +      snprintf(buf, sizeof(buf), "provider#%s#Call", name); +      if (!!find_func((char_u *)buf) && p_lpl) { +        emsgf("provider: %s: missing required variable g:loaded_%s_provider", +              name, name);        } +      return false;      } -    return has_ruby;    } -  return false; +  bool ok = (tv.v_type == VAR_NUMBER) +    ? 2 == tv.vval.v_number  // Value of 2 means "loaded and working". +    : false; + +  if (ok) { +    // Call() must be defined if provider claims to be working. +    snprintf(buf, sizeof(buf), "provider#%s#Call", name); +    if (!find_func((char_u *)buf)) { +      emsgf("provider: %s: g:loaded_%s_provider=2 but %s is not defined", +            name, name, buf); +      ok = false; +    } +  } + +  return ok;  }  /// Writes "<sourcing_name>:<sourcing_lnum>" to `buf[bufsize]`.  | 
