diff options
Diffstat (limited to 'src/nvim/eval.c')
| -rw-r--r-- | src/nvim/eval.c | 4286 | 
1 files changed, 2182 insertions, 2104 deletions
| diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 124d6acfe9..cc29496968 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1,8 +1,12 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +  /*   * eval.c: Expression evaluation.   */  #include <assert.h> +#include <float.h>  #include <inttypes.h>  #include <stdarg.h>  #include <string.h> @@ -20,6 +24,7 @@  #endif  #include "nvim/eval.h"  #include "nvim/buffer.h" +#include "nvim/channel.h"  #include "nvim/charset.h"  #include "nvim/cursor.h"  #include "nvim/diff.h" @@ -43,6 +48,7 @@  #include "nvim/mbyte.h"  #include "nvim/memline.h"  #include "nvim/memory.h" +#include "nvim/menu.h"  #include "nvim/message.h"  #include "nvim/misc1.h"  #include "nvim/keymap.h" @@ -94,6 +100,7 @@  #include "nvim/lib/kvec.h"  #include "nvim/lib/khash.h"  #include "nvim/lib/queue.h" +#include "nvim/lua/executor.h"  #include "nvim/eval/typval.h"  #include "nvim/eval/executor.h"  #include "nvim/eval/gc.h" @@ -103,9 +110,6 @@  #define DICT_MAXNEST 100        /* maximum nesting of lists and dicts */ -#define DO_NOT_FREE_CNT 99999   /* refcount for dict or list that should not -                                   be freed. */ -  #define AUTOLOAD_CHAR '#'       /* Character used as separator in autoload                                      function/variable names. */ @@ -211,6 +215,15 @@ static garray_T ga_scripts = {0, 0, sizeof(scriptvar_T *), 4, NULL};  static int echo_attr = 0;   /* attributes used for ":echo" */ +/// Describe data to return from find_some_match() +typedef enum { +  kSomeMatch,  ///< Data for match(). +  kSomeMatchEnd,  ///< Data for matchend(). +  kSomeMatchList,  ///< Data for matchlist(). +  kSomeMatchStr,  ///< Data for matchstr(). +  kSomeMatchStrPos,  ///< Data for matchstrpos(). +} SomeMatchType; +  /// trans_function_name() flags  typedef enum {    TFN_INT = 1,  ///< May use internal function name @@ -328,7 +341,7 @@ static struct vimvar {    // VV_SEND_SERVER "servername"    // VV_REG "register"    // VV_OP "operator" -  VV(VV_COUNT,          "count",            VAR_NUMBER, VV_COMPAT+VV_RO), +  VV(VV_COUNT,          "count",            VAR_NUMBER, VV_RO),    VV(VV_COUNT1,         "count1",           VAR_NUMBER, VV_RO),    VV(VV_PREVCOUNT,      "prevcount",        VAR_NUMBER, VV_RO),    VV(VV_ERRMSG,         "errmsg",           VAR_STRING, VV_COMPAT), @@ -359,6 +372,7 @@ static struct vimvar {    VV(VV_DYING,          "dying",            VAR_NUMBER, VV_RO),    VV(VV_EXCEPTION,      "exception",        VAR_STRING, VV_RO),    VV(VV_THROWPOINT,     "throwpoint",       VAR_STRING, VV_RO), +  VV(VV_STDERR,         "stderr",           VAR_NUMBER, VV_RO),    VV(VV_REG,            "register",         VAR_STRING, VV_RO),    VV(VV_CMDBANG,        "cmdbang",          VAR_NUMBER, VV_RO),    VV(VV_INSERTMODE,     "insertmode",       VAR_STRING, VV_RO), @@ -388,7 +402,6 @@ static struct vimvar {    VV(VV_OLDFILES,       "oldfiles",         VAR_LIST, 0),    VV(VV_WINDOWID,       "windowid",         VAR_NUMBER, VV_RO_SBX),    VV(VV_PROGPATH,       "progpath",         VAR_STRING, VV_RO), -  VV(VV_COMMAND_OUTPUT, "command_output",   VAR_STRING, 0),    VV(VV_COMPLETED_ITEM, "completed_item",   VAR_DICT, VV_RO),    VV(VV_OPTION_NEW,     "option_new",       VAR_STRING, VV_RO),    VV(VV_OPTION_OLD,     "option_old",       VAR_STRING, VV_RO), @@ -431,37 +444,13 @@ static ScopeDictDictItem vimvars_var;  #define vimvarht  vimvardict.dv_hashtab  typedef struct { -  union { -    LibuvProcess uv; -    PtyProcess pty; -  } proc; -  Stream in, out, err;  // Initialized in common_job_start(). -  Terminal *term; -  bool stopped; -  bool exited; -  bool rpc; -  int refcount; -  Callback on_stdout, on_stderr, on_exit; -  int *status_ptr; -  uint64_t id; -  MultiQueue *events; -} TerminalJobData; - -typedef struct { -  TerminalJobData *data; -  Callback *callback; -  const char *type; -  list_T *received; -  int status; -} JobEvent; - -typedef struct {    TimeWatcher tw;    int timer_id;    int repeat_count;    int refcount;    long timeout;    bool stopped; +  bool paused;    Callback callback;  } timer_T; @@ -506,7 +495,6 @@ typedef enum {  #define FNE_INCL_BR     1       /* find_name_end(): include [] in name */  #define FNE_CHECK_START 2       /* find_name_end(): check name starts with                                     valid character */ -static PMap(uint64_t) *jobs = NULL;  static uint64_t last_timer_id = 0;  static PMap(uint64_t) *timers = NULL; @@ -549,7 +537,6 @@ void eval_init(void)  {    vimvars[VV_VERSION].vv_nr = VIM_VERSION_100; -  jobs = pmap_new(uint64_t)();    timers = pmap_new(uint64_t)();    struct vimvar   *p; @@ -581,9 +568,9 @@ void eval_init(void)    dict_T *const msgpack_types_dict = tv_dict_alloc();    for (size_t i = 0; i < ARRAY_SIZE(msgpack_type_names); i++) { -    list_T *const type_list = tv_list_alloc(); -    type_list->lv_lock = VAR_FIXED; -    type_list->lv_refcount = 1; +    list_T *const type_list = tv_list_alloc(0); +    tv_list_set_lock(type_list, VAR_FIXED); +    tv_list_ref(type_list);      dictitem_T *const di = tv_dict_item_alloc(msgpack_type_names[i]);      di->di_flags |= DI_FLAGS_RO|DI_FLAGS_FIX;      di->di_tv = (typval_T) { @@ -604,7 +591,8 @@ void eval_init(void)    dict_T *v_event = tv_dict_alloc();    v_event->dv_lock = VAR_FIXED;    set_vim_var_dict(VV_EVENT, v_event); -  set_vim_var_list(VV_ERRORS, tv_list_alloc()); +  set_vim_var_list(VV_ERRORS, tv_list_alloc(kListLenUnknown)); +  set_vim_var_nr(VV_STDERR,   CHAN_STDERR);    set_vim_var_nr(VV_SEARCHFORWARD, 1L);    set_vim_var_nr(VV_HLSEARCH, 1L);    set_vim_var_nr(VV_COUNT1, 1); @@ -1031,7 +1019,7 @@ char_u *eval_to_string(char_u *arg, char_u **nextcmd, int convert)        ga_init(&ga, (int)sizeof(char), 80);        if (tv.vval.v_list != NULL) {          tv_list_join(&ga, tv.vval.v_list, "\n"); -        if (tv.vval.v_list->lv_len > 0) { +        if (tv_list_len(tv.vval.v_list) > 0) {            ga_append(&ga, NL);          }        } @@ -1076,10 +1064,10 @@ char_u *eval_to_string_safe(char_u *arg, char_u **nextcmd, int use_sandbox)   * Evaluates "expr" silently.   * Returns -1 for an error.   */ -int eval_to_number(char_u *expr) +varnumber_T eval_to_number(char_u *expr)  {    typval_T rettv; -  int retval; +  varnumber_T retval;    char_u      *p = skipwhite(expr);    ++emsg_off; @@ -1119,10 +1107,11 @@ static void restore_vimvar(int idx, typval_T *save_tv)    vimvars[idx].vv_tv = *save_tv;    if (vimvars[idx].vv_type == VAR_UNKNOWN) {      hi = hash_find(&vimvarht, vimvars[idx].vv_di.di_key); -    if (HASHITEM_EMPTY(hi)) -      EMSG2(_(e_intern2), "restore_vimvar()"); -    else +    if (HASHITEM_EMPTY(hi)) { +      internal_error("restore_vimvar()"); +    } else {        hash_remove(&vimvarht, hi); +    }    }  } @@ -1160,31 +1149,35 @@ list_T *eval_spell_expr(char_u *badword, char_u *expr)    return list;  } -/* - * "list" is supposed to contain two items: a word and a number.  Return the - * word in "pp" and the number as the return value. - * Return -1 if anything isn't right. - * Used to get the good word and score from the eval_spell_expr() result. - */ -int get_spellword(list_T *list, const char **pp) +/// Get spell word from an entry from spellsuggest=expr: +/// +/// Entry in question is supposed to be a list (to be checked by the caller) +/// with two items: a word and a score represented as an unsigned number +/// (whether it actually is unsigned is not checked). +/// +/// Used to get the good word and score from the eval_spell_expr() result. +/// +/// @param[in]  list  List to get values from. +/// @param[out]  ret_word  Suggested word. Not initialized if return value is +///                        -1. +/// +/// @return -1 in case of error, score otherwise. +int get_spellword(list_T *const list, const char **ret_word)  { -  listitem_T  *li; - -  li = list->lv_first; -  if (li == NULL) { +  if (tv_list_len(list) != 2) { +    EMSG(_("E5700: Expression from 'spellsuggest' must yield lists with " +           "exactly two values"));      return -1;    } -  *pp = tv_get_string(&li->li_tv); - -  li = li->li_next; -  if (li == NULL) { +  *ret_word = tv_list_find_str(list, 0); +  if (*ret_word == NULL) {      return -1;    } -  return tv_get_number(&li->li_tv); +  return tv_list_find_nr(list, -1, NULL);  } -// Call some vimL function and return the result in "*rettv". +// 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.  // @@ -1198,7 +1191,7 @@ int call_vim_function(      typval_T *rettv  )  { -  long n; +  varnumber_T n;    int len;    int doesrange;    void        *save_funccalp = NULL; @@ -1251,21 +1244,19 @@ int call_vim_function(    return ret;  } -/* - * Call vimL function "func" and return the result as a number. - * Returns -1 when calling the function fails. - * Uses argv[argc] for the function arguments. - */ -long  -call_func_retnr ( -    char_u *func, -    int argc, -    const char_u *const *const argv, -    int safe                       // use the sandbox -) +/// 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]  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 rettv; -  long retval; +  varnumber_T retval;    /* All arguments are passed as strings, no conversion to number. */    if (call_vim_function(func, argc, argv, safe, TRUE, &rettv) == FAIL) @@ -1276,19 +1267,19 @@ call_func_retnr (    return retval;  } -/// Call VimL function and return the result as a string +/// 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]  safe  Use the sandbox.  /// -/// @return [allocated] NULL when calling function failes, allocated string +/// @return [allocated] NULL when calling function fails, allocated string  ///                     otherwise.  char *call_func_retstr(const char *const func, const int argc,                         const char_u *const *const argv,                         const bool safe) -  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC +  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. @@ -1302,18 +1293,17 @@ char *call_func_retstr(const char *const func, const int argc,    return retval;  } -/* - * Call vimL function "func" and return the result as a List. - * Uses argv[argc] for the function arguments. - * Returns NULL when there is something wrong. - */ -void * -call_func_retlist ( -    char_u *func, -    int argc, -    const char_u *const *const argv, -    int safe                       // use the sandbox -) +/// 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]  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 *const argv, +                        int safe)  {    typval_T rettv; @@ -1394,7 +1384,7 @@ void prof_child_exit(proftime_T *tm /* where waittime was stored */  int eval_foldexpr(char_u *arg, int *cp)  {    typval_T tv; -  int retval; +  varnumber_T retval;    char_u      *s;    int use_sandbox = was_set_insecurely((char_u *)"foldexpr",        OPT_LOCAL); @@ -1427,7 +1417,7 @@ int eval_foldexpr(char_u *arg, int *cp)      --sandbox;    --textlock; -  return retval; +  return (int)retval;  }  /* @@ -1524,9 +1514,6 @@ ex_let_vars (  )  {    char_u *arg = arg_start; -  list_T      *l; -  int i; -  listitem_T  *item;    typval_T ltv;    if (*arg != '[') { @@ -1538,46 +1525,52 @@ ex_let_vars (      return OK;    } -  /* -   * ":let [v1, v2] = list" or ":for [v1, v2] in listlist" -   */ -  if (tv->v_type != VAR_LIST || (l = tv->vval.v_list) == NULL) { +  // ":let [v1, v2] = list" or ":for [v1, v2] in listlist" +  if (tv->v_type != VAR_LIST) {      EMSG(_(e_listreq));      return FAIL;    } +  list_T *const l = tv->vval.v_list; -  i = tv_list_len(l); -  if (semicolon == 0 && var_count < i) { +  const int len = tv_list_len(l); +  if (semicolon == 0 && var_count < len) {      EMSG(_("E687: Less targets than List items"));      return FAIL;    } -  if (var_count - semicolon > i) { +  if (var_count - semicolon > len) {      EMSG(_("E688: More targets than List items"));      return FAIL;    } +  // List l may actually be NULL, but it should fail with E688 or even earlier +  // if you try to do ":let [] = v:_null_list". +  assert(l != NULL); -  item = l->lv_first; +  listitem_T *item = tv_list_first(l); +  size_t rest_len = tv_list_len(l);    while (*arg != ']') {      arg = skipwhite(arg + 1); -    arg = ex_let_one(arg, &item->li_tv, TRUE, (char_u *)",;]", nextchars); -    item = item->li_next; -    if (arg == NULL) +    arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, (const char_u *)",;]", +                     nextchars); +    if (arg == NULL) {        return FAIL; +    } +    rest_len--; +    item = TV_LIST_ITEM_NEXT(l, item);      arg = skipwhite(arg);      if (*arg == ';') {        /* Put the rest of the list (may be empty) in the var after ';'.         * Create a new list for this. */ -      l = tv_list_alloc(); +      list_T *const rest_list = tv_list_alloc(rest_len);        while (item != NULL) { -        tv_list_append_tv(l, &item->li_tv); -        item = item->li_next; +        tv_list_append_tv(rest_list, TV_LIST_ITEM_TV(item)); +        item = TV_LIST_ITEM_NEXT(l, item);        }        ltv.v_type = VAR_LIST; -      ltv.v_lock = 0; -      ltv.vval.v_list = l; -      l->lv_refcount = 1; +      ltv.v_lock = VAR_UNLOCKED; +      ltv.vval.v_list = rest_list; +      tv_list_ref(rest_list);        arg = ex_let_one(skipwhite(arg + 1), <v, false,                         (char_u *)"]", nextchars); @@ -1587,7 +1580,7 @@ ex_let_vars (        }        break;      } else if (*arg != ',' && *arg != ']') { -      EMSG2(_(e_intern2), "ex_let_vars()"); +      internal_error("ex_let_vars()");        return FAIL;      }    } @@ -2092,6 +2085,8 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,     * Loop until no more [idx] or .key is following.     */    lp->ll_tv = &v->di_tv; +  var1.v_type = VAR_UNKNOWN; +  var2.v_type = VAR_UNKNOWN;    while (*p == '[' || (*p == '.' && lp->ll_tv->v_type == VAR_DICT)) {      if (!(lp->ll_tv->v_type == VAR_LIST && lp->ll_tv->vval.v_list != NULL)          && !(lp->ll_tv->v_type == VAR_DICT @@ -2142,9 +2137,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,            if (!quiet) {              EMSG(_(e_dictrange));            } -          if (!empty1) { -            tv_clear(&var1); -          } +          tv_clear(&var1);            return NULL;          }          if (rettv != NULL && (rettv->v_type != VAR_LIST @@ -2152,9 +2145,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,            if (!quiet) {              emsgf(_("E709: [:] requires a List value"));            } -          if (!empty1) { -            tv_clear(&var1); -          } +          tv_clear(&var1);            return NULL;          }          p = skipwhite(p + 1); @@ -2163,16 +2154,12 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,          } else {            lp->ll_empty2 = false;            if (eval1(&p, &var2, true) == FAIL) {  // Recursive! -            if (!empty1) { -              tv_clear(&var1); -            } +            tv_clear(&var1);              return NULL;            }            if (!tv_check_str(&var2)) {              // Not a number or string. -            if (!empty1) { -              tv_clear(&var1); -            } +            tv_clear(&var1);              tv_clear(&var2);              return NULL;            } @@ -2185,12 +2172,8 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,          if (!quiet) {            emsgf(_(e_missbrac));          } -        if (!empty1) { -          tv_clear(&var1); -        } -        if (lp->ll_range && !lp->ll_empty2) { -          tv_clear(&var2); -        } +        tv_clear(&var1); +        tv_clear(&var2);          return NULL;        } @@ -2202,10 +2185,6 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,        if (len == -1) {          // "[key]": get key from "var1"          key = (char_u *)tv_get_string(&var1);  // is number or string -        if (key == NULL) { -          tv_clear(&var1); -          return NULL; -        }        }        lp->ll_list = NULL;        lp->ll_dict = lp->ll_tv->vval.v_dict; @@ -2248,9 +2227,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,            if (!quiet) {              emsgf(_(e_dictkey), key);            } -          if (len == -1) { -            tv_clear(&var1); -          } +          tv_clear(&var1);            return NULL;          }          if (len == -1) { @@ -2258,32 +2235,28 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,          } else {            lp->ll_newkey = vim_strnsave(key, len);          } -        if (len == -1) { -          tv_clear(&var1); -        } +        tv_clear(&var1);          break;        // existing variable, need to check if it can be changed        } else if (!(flags & GLV_READ_ONLY) && var_check_ro(lp->ll_di->di_flags,                                                            (const char *)name,                                                            (size_t)(p - name))) { -        if (len == -1) { -          tv_clear(&var1); -        } +        tv_clear(&var1);          return NULL;        } -      if (len == -1) { -        tv_clear(&var1); -      } +      tv_clear(&var1);        lp->ll_tv = &lp->ll_di->di_tv;      } else {        // Get the number and item for the only or first index of the List.        if (empty1) {          lp->ll_n1 = 0;        } else { -        lp->ll_n1 = tv_get_number(&var1);  // Is number or string. -        tv_clear(&var1); +        // Is number or string. +        lp->ll_n1 = (long)tv_get_number(&var1);        } +      tv_clear(&var1); +        lp->ll_dict = NULL;        lp->ll_list = lp->ll_tv->vval.v_list;        lp->ll_li = tv_list_find(lp->ll_list, lp->ll_n1); @@ -2294,9 +2267,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,          }        }        if (lp->ll_li == NULL) { -        if (lp->ll_range && !lp->ll_empty2) { -          tv_clear(&var2); -        } +        tv_clear(&var2);          if (!quiet) {            EMSGN(_(e_listidx), lp->ll_n1);          } @@ -2310,7 +2281,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,         * Otherwise "lp->ll_n2" is set to the second index.         */        if (lp->ll_range && !lp->ll_empty2) { -        lp->ll_n2 = tv_get_number(&var2);  // Is number or string. +        lp->ll_n2 = (long)tv_get_number(&var2);  // Is number or string.          tv_clear(&var2);          if (lp->ll_n2 < 0) {            ni = tv_list_find(lp->ll_list, lp->ll_n2); @@ -2334,10 +2305,11 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,          }        } -      lp->ll_tv = &lp->ll_li->li_tv; +      lp->ll_tv = TV_LIST_ITEM_TV(lp->ll_li);      }    } +  tv_clear(&var1);    return p;  } @@ -2378,9 +2350,9 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,                       &tv, &di, true, false) == OK) {          if ((di == NULL               || (!var_check_ro(di->di_flags, (const char *)lp->ll_name, -                               STRLEN(lp->ll_name)) +                               TV_CSTRING)                   && !tv_check_lock(di->di_tv.v_lock, (const char *)lp->ll_name, -                                   STRLEN(lp->ll_name)))) +                                   TV_CSTRING)))              && eexe_mod_op(&tv, rettv, (const char *)op) == OK) {            set_var(lp->ll_name, lp->ll_name_len, &tv, false);          } @@ -2393,53 +2365,61 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,    } else if (tv_check_lock(lp->ll_newkey == NULL                             ? lp->ll_tv->v_lock                             : lp->ll_tv->vval.v_dict->dv_lock, -                           (const char *)lp->ll_name, STRLEN(lp->ll_name))) { +                           (const char *)lp->ll_name, TV_CSTRING)) {    } else if (lp->ll_range) {      listitem_T *ll_li = lp->ll_li;      int ll_n1 = lp->ll_n1;      // Check whether any of the list items is locked -    for (listitem_T *ri = rettv->vval.v_list->lv_first; +    for (listitem_T *ri = tv_list_first(rettv->vval.v_list);           ri != NULL && ll_li != NULL; ) { -      if (tv_check_lock(ll_li->li_tv.v_lock, (const char *)lp->ll_name, -                        STRLEN(lp->ll_name))) { +      if (tv_check_lock(TV_LIST_ITEM_TV(ll_li)->v_lock, +                        (const char *)lp->ll_name, +                        TV_CSTRING)) {          return;        } -      ri = ri->li_next; +      ri = TV_LIST_ITEM_NEXT(rettv->vval.v_list, ri);        if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == ll_n1)) {          break;        } -      ll_li = ll_li->li_next; +      ll_li = TV_LIST_ITEM_NEXT(lp->ll_list, ll_li);        ll_n1++;      }      /*       * Assign the List values to the list items.       */ -    for (ri = rettv->vval.v_list->lv_first; ri != NULL; ) { +    for (ri = tv_list_first(rettv->vval.v_list); ri != NULL; ) {        if (op != NULL && *op != '=') { -        eexe_mod_op(&lp->ll_li->li_tv, &ri->li_tv, (const char *)op); +        eexe_mod_op(TV_LIST_ITEM_TV(lp->ll_li), TV_LIST_ITEM_TV(ri), +                    (const char *)op);        } else { -        tv_clear(&lp->ll_li->li_tv); -        tv_copy(&ri->li_tv, &lp->ll_li->li_tv); +        tv_clear(TV_LIST_ITEM_TV(lp->ll_li)); +        tv_copy(TV_LIST_ITEM_TV(ri), TV_LIST_ITEM_TV(lp->ll_li));        } -      ri = ri->li_next; -      if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == lp->ll_n1)) +      ri = TV_LIST_ITEM_NEXT(rettv->vval.v_list, ri); +      if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == lp->ll_n1)) {          break; -      if (lp->ll_li->li_next == NULL) { +      } +      assert(lp->ll_li != NULL); +      if (TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) == NULL) {          // Need to add an empty item.          tv_list_append_number(lp->ll_list, 0); -        assert(lp->ll_li->li_next); +        // ll_li may have become invalid after append, don’t use it. +        lp->ll_li = tv_list_last(lp->ll_list);  // Valid again. +      } else { +        lp->ll_li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li);        } -      lp->ll_li = lp->ll_li->li_next; -      ++lp->ll_n1; +      lp->ll_n1++;      } -    if (ri != NULL) +    if (ri != NULL) {        EMSG(_("E710: List value has more items than target")); -    else if (lp->ll_empty2 -             ? (lp->ll_li != NULL && lp->ll_li->li_next != NULL) -             : lp->ll_n1 != lp->ll_n2) +    } else if (lp->ll_empty2 +               ? (lp->ll_li != NULL +                  && TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) != NULL) +               : lp->ll_n1 != lp->ll_n2) {        EMSG(_("E711: List value has not enough items")); +    }    } else {      typval_T oldtv = TV_INITIAL_VALUE;      dict_T *dict = lp->ll_dict; @@ -2484,9 +2464,11 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,  notify:      if (watched) {        if (oldtv.v_type == VAR_UNKNOWN) { +        assert(lp->ll_newkey != NULL);          tv_dict_watcher_notify(dict, (char *)lp->ll_newkey, lp->ll_tv, NULL);        } else {          dictitem_T *di = lp->ll_di; +        assert(di->di_key != NULL);          tv_dict_watcher_notify(dict, (char *)di->di_key, lp->ll_tv, &oldtv);          tv_clear(&oldtv);        } @@ -2538,7 +2520,7 @@ void *eval_for_line(const char_u *arg, bool *errp, char_u **nextcmdp, int skip)           * list being used in "tv". */          fi->fi_list = l;          tv_list_watch_add(l, &fi->fi_lw); -        fi->fi_lw.lw_item = l->lv_first; +        fi->fi_lw.lw_item = tv_list_first(l);        }      }    } @@ -2556,21 +2538,18 @@ void *eval_for_line(const char_u *arg, bool *errp, char_u **nextcmdp, int skip)   * Return TRUE when a valid item was found, FALSE when at end of list or   * something wrong.   */ -int next_for_item(void *fi_void, char_u *arg) +bool next_for_item(void *fi_void, char_u *arg)  { -  forinfo_T   *fi = (forinfo_T *)fi_void; -  int result; -  listitem_T  *item; +  forinfo_T *fi = (forinfo_T *)fi_void; -  item = fi->fi_lw.lw_item; -  if (item == NULL) -    result = FALSE; -  else { -    fi->fi_lw.lw_item = item->li_next; -    result = (ex_let_vars(arg, &item->li_tv, TRUE, -                  fi->fi_semicolon, fi->fi_varcount, NULL) == OK); +  listitem_T *item = fi->fi_lw.lw_item; +  if (item == NULL) { +    return false; +  } 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);    } -  return result;  }  // TODO(ZyX-I): move to eval/ex_cmds @@ -2703,19 +2682,21 @@ void ex_call(exarg_T *eap)      return;    } -  tofree = trans_function_name(&arg, eap->skip, TFN_INT, &fudi, &partial); +  tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial);    if (fudi.fd_newkey != NULL) { -    /* Still need to give an error message for missing key. */ +    // Still need to give an error message for missing key.      EMSG2(_(e_dictkey), fudi.fd_newkey);      xfree(fudi.fd_newkey);    } -  if (tofree == NULL) +  if (tofree == NULL) {      return; +  } -  /* Increase refcount on dictionary, it could get deleted when evaluating -   * the arguments. */ -  if (fudi.fd_dict != NULL) -    ++fudi.fd_dict->dv_refcount; +  // Increase refcount on dictionary, it could get deleted when evaluating +  // the arguments. +  if (fudi.fd_dict != NULL) { +    fudi.fd_dict->dv_refcount++; +  }    // If it is the name of a variable of type VAR_FUNC or VAR_PARTIAL use its    // contents. For VAR_PARTIAL get its partial, unless we already have one @@ -2724,8 +2705,8 @@ void ex_call(exarg_T *eap)    name = deref_func_name((const char *)tofree, &len,                           partial != NULL ? NULL : &partial, false); -  /* Skip white space to allow ":call func ()".  Not good, but required for -   * backward compatibility. */ +  // Skip white space to allow ":call func ()".  Not good, but required for +  // backward compatibility.    startarg = skipwhite(arg);    rettv.v_type = VAR_UNKNOWN;  // tv_clear() uses this. @@ -2734,19 +2715,9 @@ void ex_call(exarg_T *eap)      goto end;    } -  /* -   * When skipping, evaluate the function once, to find the end of the -   * arguments. -   * When the function takes a range, this is discovered after the first -   * call, and the loop is broken. -   */ -  if (eap->skip) { -    ++emsg_skip; -    lnum = eap->line2;          /* do it once, also with an invalid range */ -  } else -    lnum = eap->line1; -  for (; lnum <= eap->line2; ++lnum) { -    if (!eap->skip && eap->addr_count > 0) { +  lnum = eap->line1; +  for (; lnum <= eap->line2; lnum++) { +    if (eap->addr_count > 0) {  // -V560        curwin->w_cursor.lnum = lnum;        curwin->w_cursor.col = 0;        curwin->w_cursor.coladd = 0; @@ -2754,40 +2725,40 @@ void ex_call(exarg_T *eap)      arg = startarg;      if (get_func_tv(name, (int)STRLEN(name), &rettv, &arg,                      eap->line1, eap->line2, &doesrange, -                    !eap->skip, partial, fudi.fd_dict) == FAIL) { +                    true, partial, fudi.fd_dict) == FAIL) {        failed = true;        break;      }      // Handle a function returning a Funcref, Dictionary or List. -    if (handle_subscript((const char **)&arg, &rettv, !eap->skip, true) +    if (handle_subscript((const char **)&arg, &rettv, true, true)          == FAIL) {        failed = true;        break;      }      tv_clear(&rettv); -    if (doesrange || eap->skip) { +    if (doesrange) {        break;      } -    /* Stop when immediately aborting on error, or when an interrupt -     * occurred or an exception was thrown but not caught. -     * get_func_tv() returned OK, so that the check for trailing -     * characters below is executed. */ -    if (aborting()) +    // Stop when immediately aborting on error, or when an interrupt +    // occurred or an exception was thrown but not caught. +    // get_func_tv() returned OK, so that the check for trailing +    // characters below is executed. +    if (aborting()) {        break; +    }    } -  if (eap->skip) -    --emsg_skip;    if (!failed) { -    /* Check for trailing illegal characters and a following command. */ +    // Check for trailing illegal characters and a following command.      if (!ends_excmd(*arg)) {        emsg_severe = TRUE;        EMSG(_(e_trailing)); -    } else +    } else {        eap->nextcmd = check_nextcmd(arg); +    }    }  end: @@ -2893,7 +2864,10 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit)      }      *name_end = cc;    } else if ((lp->ll_list != NULL -              && tv_check_lock(lp->ll_list->lv_lock, (const char *)lp->ll_name, +              // ll_list is not NULL when lvalue is not in a list, NULL lists +              // yield E689. +              && tv_check_lock(tv_list_locked(lp->ll_list), +                               (const char *)lp->ll_name,                                 lp->ll_name_len))               || (lp->ll_dict != NULL                   && tv_check_lock(lp->ll_dict->dv_lock, @@ -2901,27 +2875,26 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit)                                    lp->ll_name_len))) {      return FAIL;    } else if (lp->ll_range) { -    listitem_T    *li; -    listitem_T *ll_li = lp->ll_li; -    int ll_n1 = lp->ll_n1; - -    while (ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= ll_n1)) { -      li = ll_li->li_next; -      if (tv_check_lock(ll_li->li_tv.v_lock, (const char *)lp->ll_name, +    assert(lp->ll_list != NULL); +    // Delete a range of List items. +    listitem_T *const first_li = lp->ll_li; +    listitem_T *last_li = first_li; +    for (;;) { +      listitem_T *const li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li); +      if (tv_check_lock(TV_LIST_ITEM_TV(lp->ll_li)->v_lock, +                        (const char *)lp->ll_name,                          lp->ll_name_len)) {          return false;        } -      ll_li = li; -      ll_n1++; -    } - -    /* Delete a range of List items. */ -    while (lp->ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) { -      li = lp->ll_li->li_next; -      tv_list_item_remove(lp->ll_list, lp->ll_li);        lp->ll_li = li; -      ++lp->ll_n1; +      lp->ll_n1++; +      if (lp->ll_li == NULL || (!lp->ll_empty2 && lp->ll_n2 < lp->ll_n1)) { +        break; +      } else { +        last_li = lp->ll_li; +      }      } +    tv_list_remove_items(lp->ll_list, first_li, last_li);    } else {      if (lp->ll_list != NULL) {        // unlet a List item. @@ -2929,6 +2902,7 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit)      } else {        // unlet a Dictionary item.        dict_T *d = lp->ll_dict; +      assert(d != NULL);        dictitem_T *di = lp->ll_di;        bool watched = tv_dict_is_watched(d);        char *key = NULL; @@ -2983,7 +2957,7 @@ int do_unlet(const char *const name, const size_t name_len, const int forceit)        d = di->di_tv.vval.v_dict;      }      if (d == NULL) { -      EMSG2(_(e_intern2), "do_unlet()"); +      internal_error("do_unlet()");        return FAIL;      }      hashitem_T *hi = hash_find(ht, (const char_u *)varname); @@ -2992,14 +2966,13 @@ int do_unlet(const char *const name, const size_t name_len, const int forceit)      }      if (hi != NULL && !HASHITEM_EMPTY(hi)) {        dictitem_T *const di = TV_DICT_HI2DI(hi); -      if (var_check_fixed(di->di_flags, (const char *)name, STRLEN(name)) -          || var_check_ro(di->di_flags, (const char *)name, STRLEN(name)) -          || tv_check_lock(d->dv_lock, (const char *)name, STRLEN(name))) { +      if (var_check_fixed(di->di_flags, (const char *)name, TV_CSTRING) +          || var_check_ro(di->di_flags, (const char *)name, TV_CSTRING) +          || tv_check_lock(d->dv_lock, (const char *)name, TV_CSTRING)) {          return FAIL;        } -      if (d == NULL -          || tv_check_lock(d->dv_lock, (const char *)name, STRLEN(name))) { +      if (tv_check_lock(d->dv_lock, (const char *)name, TV_CSTRING)) {          return FAIL;        } @@ -3067,13 +3040,13 @@ static int do_lock_var(lval_T *lp, char_u *const name_end, const int deep,      /* (un)lock a range of List items. */      while (li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) { -      tv_item_lock(&li->li_tv, deep, lock); -      li = li->li_next; -      ++lp->ll_n1; +      tv_item_lock(TV_LIST_ITEM_TV(li), deep, lock); +      li = TV_LIST_ITEM_NEXT(lp->ll_list, li); +      lp->ll_n1++;      }    } else if (lp->ll_list != NULL) {      // (un)lock a List item. -    tv_item_lock(&lp->ll_li->li_tv, deep, lock); +    tv_item_lock(TV_LIST_ITEM_TV(lp->ll_li), deep, lock);    } else {      // (un)lock a Dictionary item.      tv_item_lock(&lp->ll_di->di_tv, deep, lock); @@ -3520,7 +3493,7 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate)    exptype_T type = TYPE_UNKNOWN;    int type_is = FALSE;              /* TRUE for "is" and "isnot" */    int len = 2; -  long n1, n2; +  varnumber_T n1, n2;    int ic;    /* @@ -3782,7 +3755,7 @@ static int eval5(char_u **arg, typval_T *rettv, int evaluate)    typval_T var2;    typval_T var3;    int op; -  long n1, n2; +  varnumber_T n1, n2;    float_T f1 = 0, f2 = 0;    char_u      *p; @@ -3912,29 +3885,26 @@ static int eval5(char_u **arg, typval_T *rettv, int evaluate)  // TODO(ZyX-I): move to eval/expressions -/* - * Handle fifth level expression: - *	*	number multiplication - *	/	number division - *	%	number modulo - * - * "arg" must point to the first non-white of the expression. - * "arg" is advanced to the next non-white after the recognized expression. - * - * Return OK or FAIL. - */ -static int  -eval6 ( -    char_u **arg, -    typval_T *rettv, -    int evaluate, -    int want_string              /* after "." operator */ -) +/// Handle fifth level expression: +///  - *  number multiplication +///  - /  number division +///  - %  number modulo +/// +/// @param[in,out]  arg  Points to the first non-whitespace character of the +///                      expression.  Is advanced to the next non-whitespace +///                      character after the recognized expression. +/// @param[out]  rettv  Location where result is saved. +/// @param[in]  evaluate  If not true, rettv is not populated. +/// @param[in]  want_string  True if "." is string_concatenation, otherwise +///                          float +/// @return  OK or FAIL. +static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string) +  FUNC_ATTR_NO_SANITIZE_UNDEFINED  {    typval_T var2;    int op; -  long n1, n2; -  int use_float = FALSE; +  varnumber_T n1, n2; +  bool use_float = false;    float_T f1 = 0, f2;    bool error = false; @@ -3955,7 +3925,7 @@ eval6 (      if (evaluate) {        if (rettv->v_type == VAR_FLOAT) {          f1 = rettv->vval.v_float; -        use_float = TRUE; +        use_float = true;          n1 = 0;        } else {          n1 = tv_get_number_chk(rettv, &error); @@ -3979,7 +3949,7 @@ eval6 (        if (var2.v_type == VAR_FLOAT) {          if (!use_float) {            f1 = n1; -          use_float = TRUE; +          use_float = true;          }          f2 = var2.vval.v_float;          n2 = 0; @@ -4022,18 +3992,20 @@ eval6 (          rettv->v_type = VAR_FLOAT;          rettv->vval.v_float = f1;        } else { -        if (op == '*') +        if (op == '*') {            n1 = n1 * n2; -        else if (op == '/') { -          if (n2 == 0) {                /* give an error message? */ -            if (n1 == 0) -              n1 = -0x7fffffffL - 1L;                   /* similar to NaN */ -            else if (n1 < 0) -              n1 = -0x7fffffffL; -            else -              n1 = 0x7fffffffL; -          } else +        } 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; +          }          } else {            if (n2 == 0)                  /* give an error message? */              n1 = 0; @@ -4082,7 +4054,7 @@ static int eval7(      int want_string                 // after "." operator  )  { -  long n; +  varnumber_T n;    int len;    char_u      *s;    char_u      *start_leader, *end_leader; @@ -4237,11 +4209,17 @@ static int eval7(          // use its contents.          s = deref_func_name((const char *)s, &len, &partial, !evaluate); +        // Need to make a copy, in case evaluating the arguments makes +        // the name invalid. +        s = xmemdupz(s, len); +          // Invoke the function.          ret = get_func_tv(s, len, rettv, arg,                            curwin->w_cursor.lnum, curwin->w_cursor.lnum,                            &len, evaluate, partial, NULL); +        xfree(s); +          // If evaluate is false rettv->v_type was not set in          // get_func_tv, but it's needed in handle_subscript() to parse          // what follows. So set it here. @@ -4280,7 +4258,7 @@ static int eval7(    // Apply logical NOT and unary '-', from right to left, ignore '+'.    if (ret == OK && evaluate && end_leader > start_leader) {      bool error = false; -    int val = 0; +    varnumber_T val = 0;      float_T f = 0.0;      if (rettv->v_type == VAR_FLOAT) { @@ -4527,18 +4505,18 @@ eval_index (            if (!empty2 && (n2 < 0 || n2 + 1 < n1)) {              n2 = -1;            } -          l = tv_list_alloc(); +          l = tv_list_alloc(n2 - n1 + 1);            item = tv_list_find(rettv->vval.v_list, n1);            while (n1++ <= n2) { -            tv_list_append_tv(l, &item->li_tv); -            item = item->li_next; +            tv_list_append_tv(l, TV_LIST_ITEM_TV(item)); +            item = TV_LIST_ITEM_NEXT(rettv->vval.v_list, item);            }            tv_clear(rettv);            rettv->v_type = VAR_LIST;            rettv->vval.v_list = l; -          l->lv_refcount++; +          tv_list_ref(l);          } else { -          tv_copy(&tv_list_find(rettv->vval.v_list, n1)->li_tv, &var1); +          tv_copy(TV_LIST_ITEM_TV(tv_list_find(rettv->vval.v_list, n1)), &var1);            tv_clear(rettv);            *rettv = var1;          } @@ -4763,7 +4741,7 @@ static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate)        // Special key, e.g.: "\<C-W>"        case '<': -        extra = trans_special((const char_u **) &p, STRLEN(p), name, true); +        extra = trans_special((const char_u **)&p, STRLEN(p), name, true, true);          if (extra != 0) {            name += extra;            break; @@ -4883,35 +4861,34 @@ void partial_unref(partial_T *pt)  static int get_list_tv(char_u **arg, typval_T *rettv, int evaluate)  {    list_T      *l = NULL; -  typval_T tv; -  listitem_T  *item;    if (evaluate) { -    l = tv_list_alloc(); +    l = tv_list_alloc(kListLenShouldKnow);    }    *arg = skipwhite(*arg + 1);    while (**arg != ']' && **arg != NUL) { -    if (eval1(arg, &tv, evaluate) == FAIL)      /* recursive! */ +    typval_T tv; +    if (eval1(arg, &tv, evaluate) == FAIL) {  // Recursive!        goto failret; +    }      if (evaluate) { -      item = tv_list_item_alloc(); -      item->li_tv = tv; -      item->li_tv.v_lock = 0; -      tv_list_append(l, item); +      tv.v_lock = VAR_UNLOCKED; +      tv_list_append_owned_tv(l, tv);      } -    if (**arg == ']') +    if (**arg == ']') {        break; +    }      if (**arg != ',') { -      EMSG2(_("E696: Missing comma in List: %s"), *arg); +      emsgf(_("E696: Missing comma in List: %s"), *arg);        goto failret;      }      *arg = skipwhite(*arg + 1);    }    if (**arg != ']') { -    EMSG2(_("E697: Missing end of List ']': %s"), *arg); +    emsgf(_("E697: Missing end of List ']': %s"), *arg);  failret:      if (evaluate) {        tv_list_free(l); @@ -4923,7 +4900,7 @@ failret:    if (evaluate) {      rettv->v_type = VAR_LIST;      rettv->vval.v_list = l; -    ++l->lv_refcount; +    tv_list_ref(l);    }    return OK; @@ -5095,7 +5072,8 @@ bool garbage_collect(bool testing)      do {        yankreg_T reg;        char name = NUL; -      reg_iter = op_register_iter(reg_iter, &name, ®); +      bool is_unnamed = false; +      reg_iter = op_register_iter(reg_iter, &name, ®, &is_unnamed);        if (name != NUL) {          ABORTING(set_ref_dict)(reg.additional_data, copyID);        } @@ -5133,12 +5111,12 @@ bool garbage_collect(bool testing)    // named functions (matters for closures)    ABORTING(set_ref_in_functions(copyID)); -  // Jobs +  // Channels    { -    TerminalJobData *data; -    map_foreach_value(jobs, data, { -      set_ref_in_callback(&data->on_stdout, copyID, NULL, NULL); -      set_ref_in_callback(&data->on_stderr, copyID, NULL, NULL); +    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_stderr, copyID, NULL, NULL);        set_ref_in_callback(&data->on_exit, copyID, NULL, NULL);      })    } @@ -5190,6 +5168,8 @@ bool garbage_collect(bool testing)      ABORTING(set_ref_list)(sub.additional_elements, copyID);    } +  ABORTING(set_ref_in_quickfix)(copyID); +    bool did_free = false;    if (!abort) {      // 2. Free lists and dictionaries that are not referenced. @@ -5257,8 +5237,8 @@ static int free_unref_items(int copyID)    // But don't free a list that has a watcher (used in a for loop), these    // are not referenced anywhere.    for (list_T *ll = gc_first_list; ll != NULL; ll = ll->lv_used_next) { -    if ((ll->lv_copyID & COPYID_MASK) != (copyID & COPYID_MASK) -        && ll->lv_watch == NULL) { +    if ((tv_list_copyid(ll) & COPYID_MASK) != (copyID & COPYID_MASK) +        && !tv_list_has_watchers(ll)) {        // Free the List and ordinary items it contains, but don't recurse        // into Lists and Dictionaries, they will be in the list of dicts        // or list of lists. @@ -5278,7 +5258,7 @@ static int free_unref_items(int copyID)    for (ll = gc_first_list; ll != NULL; ll = ll_next) {      ll_next = ll->lv_used_next;      if ((ll->lv_copyID & COPYID_MASK) != (copyID & COPYID_MASK) -        && ll->lv_watch == NULL) { +        && !tv_list_has_watchers(ll)) {        // Free the List and ordinary items it contains, but don't recurse        // into Lists and Dictionaries, they will be in the list of dicts        // or list of lists. @@ -5343,15 +5323,16 @@ bool set_ref_in_list(list_T *l, int copyID, ht_stack_T **ht_stack)    list_T *cur_l = l;    for (;;) { -    if (!abort) { -      // Mark each item in the list.  If the item contains a hashtab -      // it is added to ht_stack, if it contains a list it is added to -      // list_stack. -      for (listitem_T *li = cur_l->lv_first; !abort && li != NULL; -           li = li->li_next) { -        abort = set_ref_in_item(&li->li_tv, copyID, ht_stack, &list_stack); +    // Mark each item in the list.  If the item contains a hashtab +    // it is added to ht_stack, if it contains a list it is added to +    // list_stack. +    TV_LIST_ITER(cur_l, li, { +      if (abort) { +        break;        } -    } +      abort = set_ref_in_item(TV_LIST_ITEM_TV(li), copyID, ht_stack, +                              &list_stack); +    });      if (list_stack == NULL) {        break; @@ -5696,10 +5677,6 @@ static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs,          c = *p;          *p = NUL;          arg = vim_strsave(arg); -        if (arg == NULL) { -          *p = c; -          goto err_ret; -        }          // Check for duplicate argument name.          for (i = 0; i < newargs->ga_len; i++) { @@ -5821,12 +5798,8 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)      lambda_no++;      snprintf((char *)name, sizeof(name), "<lambda>%d", lambda_no); -    fp = (ufunc_T *)xcalloc(1, sizeof(ufunc_T) + STRLEN(name)); -    pt = (partial_T *)xcalloc(1, sizeof(partial_T)); -    if (pt == NULL) { -      xfree(fp); -      goto errret; -    } +    fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); +    pt = xcalloc(1, sizeof(partial_T));      ga_init(&newlines, (int)sizeof(char_u *), 1);      ga_grow(&newlines, 1); @@ -5892,6 +5865,19 @@ size_t string2float(const char *const text, float_T *const ret_value)  {    char *s = NULL; +  // MS-Windows does not deal with "inf" and "nan" properly +  if (STRNICMP(text, "inf", 3) == 0) { +    *ret_value = INFINITY; +    return 3; +  } +  if (STRNICMP(text, "-inf", 3) == 0) { +    *ret_value = -INFINITY; +    return 4; +  } +  if (STRNICMP(text, "nan", 3) == 0) { +    *ret_value = NAN; +    return 3; +  }    *ret_value = strtod(text, &s);    return (size_t) (s - text);  } @@ -5941,6 +5927,14 @@ static int get_env_tv(char_u **arg, typval_T *rettv, int evaluate)  }  #ifdef INCLUDE_GENERATED_DECLARATIONS + +#ifdef _MSC_VER +// This prevents MSVC from replacing the functions with intrinsics, +// and causing errors when trying to get their addresses in funcs.generated.h +#pragma function (ceil) +#pragma function (floor) +#endif +  # include "funcs.generated.h"  #endif @@ -6005,7 +5999,7 @@ char_u *get_expr_name(expand_T *xp, int idx)  /// @param[in]  name  Name of the function.  ///  /// Returns pointer to the function definition or NULL if not found. -static VimLFuncDef *find_internal_func(const char *const name) +static const VimLFuncDef *find_internal_func(const char *const name)    FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ALL  {    size_t len = strlen(name); @@ -6199,13 +6193,9 @@ static char_u *fname_trans_sid(const char_u *const name,        fname = fname_buf;      } else {        fname = xmalloc(i + STRLEN(name + llen) + 1); -      if (fname == NULL) { -        *error = ERROR_OTHER; -      } else { -        *tofree = fname; -        memmove(fname, fname_buf, (size_t)i); -        STRCPY(fname + i, name + llen); -      } +      *tofree = fname; +      memmove(fname, fname_buf, (size_t)i); +      STRCPY(fname + i, name + llen);      }    } else {      fname = (char_u *)name; @@ -6286,9 +6276,6 @@ call_func(    // Make a copy of the name, if it comes from a funcref variable it could    // be changed or deleted in the called function.    name = vim_strnsave(funcname, len); -  if (name == NULL) { -    return ret; -  }    fname = fname_trans_sid(name, fname_buf, &tofree, &error); @@ -6374,7 +6361,7 @@ call_func(        }      } else {        // Find the function name in the table, call its implementation. -      VimLFuncDef *const fdef = find_internal_func((const char *)fname); +      const VimLFuncDef *const fdef = find_internal_func((const char *)fname);        if (fdef != NULL) {          if (argcount < fdef->min_argc) {            error = ERROR_TOOFEW; @@ -6506,9 +6493,9 @@ static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr)    }    Error err = ERROR_INIT; -  Object result = fn(INTERNAL_CALL, args, &err); +  Object result = fn(VIML_INTERNAL_CALL, args, &err); -  if (err.set) { +  if (ERROR_SET(&err)) {      nvim_err_writeln(cstr_as_string(err.msg));      goto end;    } @@ -6520,6 +6507,7 @@ static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr)  end:    api_free_array(args);    api_free_object(result); +  api_clear_error(&err);  }  /* @@ -6549,14 +6537,10 @@ static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr)   */  static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { -  list_T      *l; - -  rettv->vval.v_number = 1;   /* Default: Failed */ +  rettv->vval.v_number = 1;  // Default: failed.    if (argvars[0].v_type == VAR_LIST) { -    const char *const arg_errmsg = _("add() argument"); -    const size_t arg_errmsg_len = strlen(arg_errmsg); -    if ((l = argvars[0].vval.v_list) != NULL -        && !tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len)) { +    list_T *const l = argvars[0].vval.v_list; +    if (!tv_check_lock(tv_list_locked(l), N_("add() argument"), TV_TRANSLATE)) {        tv_list_append_tv(l, &argvars[1]);        tv_copy(&argvars[0], rettv);      } @@ -6607,9 +6591,10 @@ static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr)        && u_save(lnum, lnum + 1) == OK) {      if (argvars[1].v_type == VAR_LIST) {        l = argvars[1].vval.v_list; -      if (l == NULL) +      if (l == NULL) {          return; -      li = l->lv_first; +      } +      li = tv_list_first(l);      }      for (;; ) {        if (l == NULL) { @@ -6617,7 +6602,7 @@ static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr)        } else if (li == NULL) {          break;  // End of list.        } else { -        tv = &li->li_tv;  // Append item from list. +        tv = TV_LIST_ITEM_TV(li);  // Append item from list.        }        const char *const line = tv_get_string_chk(tv);        if (line == NULL) {  // Type error. @@ -6629,7 +6614,7 @@ static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr)        if (l == NULL) {          break;        } -      li = li->li_next; +      li = TV_LIST_ITEM_NEXT(l, li);      }      appended_lines_mark(lnum, added); @@ -6673,7 +6658,7 @@ static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr)    int idx;    if (argvars[0].v_type != VAR_UNKNOWN) { -    idx = tv_get_number_chk(&argvars[0], NULL); +    idx = (int)tv_get_number_chk(&argvars[0], NULL);      if (idx >= 0 && idx < ARGCOUNT) {        rettv->vval.v_string = (char_u *)xstrdup(            (const char *)alist_name(&ARGLIST[idx])); @@ -6682,7 +6667,7 @@ static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr)      }      rettv->v_type = VAR_STRING;    } else { -    tv_list_alloc_ret(rettv); +    tv_list_alloc_ret(rettv, ARGCOUNT);      for (idx = 0; idx < ARGCOUNT; idx++) {        tv_list_append_string(rettv->vval.v_list,                              (const char *)alist_name(&ARGLIST[idx]), -1); @@ -6711,6 +6696,39 @@ static void prepare_assert_error(garray_T *gap)    }  } +// Append "str" to "gap", escaping unprintable characters. +// Changes NL to \n, CR to \r, etc. +static void ga_concat_esc(garray_T *gap, char_u *str) +{ +  char_u *p; +  char_u buf[NUMBUFLEN]; + +  if (str == NULL) { +    ga_concat(gap, (char_u *)"NULL"); +    return; +  } + +  for (p = str; *p != NUL; p++) { +    switch (*p) { +      case BS: ga_concat(gap, (char_u *)"\\b"); break; +      case ESC: ga_concat(gap, (char_u *)"\\e"); break; +      case FF: ga_concat(gap, (char_u *)"\\f"); break; +      case NL: ga_concat(gap, (char_u *)"\\n"); break; +      case TAB: ga_concat(gap, (char_u *)"\\t"); break; +      case CAR: ga_concat(gap, (char_u *)"\\r"); break; +      case '\\': ga_concat(gap, (char_u *)"\\\\"); break; +      default: +        if (*p < ' ') { +          vim_snprintf((char *)buf, NUMBUFLEN, "\\x%02x", *p); +          ga_concat(gap, buf); +        } else { +          ga_append(gap, *p); +        } +        break; +    } +  } +} +  // Fill "gap" with information about an assert error.  static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv,                                char_u *exp_str, typval_T *exp_tv, @@ -6725,28 +6743,30 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv,    } else {      if (atype == ASSERT_MATCH || atype == ASSERT_NOTMATCH) {        ga_concat(gap, (char_u *)"Pattern "); +    } else if (atype == ASSERT_NOTEQUAL) { +      ga_concat(gap, (char_u *)"Expected not equal to ");      } else {        ga_concat(gap, (char_u *)"Expected ");      }      if (exp_str == NULL) { -      tofree = (char_u *) encode_tv2string(exp_tv, NULL); -      ga_concat(gap, tofree); +      tofree = (char_u *)encode_tv2string(exp_tv, NULL); +      ga_concat_esc(gap, tofree);        xfree(tofree);      } else { -      ga_concat(gap, exp_str); +      ga_concat_esc(gap, exp_str);      } -    tofree = (char_u *)encode_tv2string(got_tv, NULL); -    if (atype == ASSERT_MATCH) { -      ga_concat(gap, (char_u *)" does not match "); -    } else if (atype == ASSERT_NOTMATCH) { -      ga_concat(gap, (char_u *)" does match "); -    } else if (atype == ASSERT_NOTEQUAL) { -      ga_concat(gap, (char_u *)" differs from "); -    } else { -      ga_concat(gap, (char_u *)" but got "); +    if (atype != ASSERT_NOTEQUAL) { +      if (atype == ASSERT_MATCH) { +        ga_concat(gap, (char_u *)" does not match "); +      } else if (atype == ASSERT_NOTMATCH) { +        ga_concat(gap, (char_u *)" does match "); +      } else { +        ga_concat(gap, (char_u *)" but got "); +      } +      tofree = (char_u *)encode_tv2string(got_tv, NULL); +      ga_concat_esc(gap, tofree); +      xfree(tofree);      } -    ga_concat(gap, tofree); -    xfree(tofree);    }  } @@ -6757,7 +6777,7 @@ static void assert_error(garray_T *gap)    if (vp->vv_type != VAR_LIST || vimvars[VV_ERRORS].vv_list == NULL) {      // Make sure v:errors is a list. -    set_vim_var_list(VV_ERRORS, tv_list_alloc()); +    set_vim_var_list(VV_ERRORS, tv_list_alloc(1));    }    tv_list_append_string(vimvars[VV_ERRORS].vv_list,                          (const char *)gap->ga_data, (ptrdiff_t)gap->ga_len); @@ -6789,6 +6809,17 @@ static void f_assert_notequal(typval_T *argvars, typval_T *rettv, FunPtr fptr)    assert_equal_common(argvars, ASSERT_NOTEQUAL);  } +/// "assert_report(msg) +static void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ +    garray_T ga; + +    prepare_assert_error(&ga); +    ga_concat(&ga, (const char_u *)tv_get_string(&argvars[0])); +    assert_error(&ga); +    ga_clear(&ga); +} +  /// "assert_exception(string[, msg])" function  static void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { @@ -7116,8 +7147,7 @@ static void f_bufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)        && argvars[1].v_type != VAR_UNKNOWN        && tv_get_number_chk(&argvars[1], &error) != 0        && !error -      && (name = tv_get_string_chk(&argvars[0])) != NULL -      && !error) { +      && (name = tv_get_string_chk(&argvars[0])) != NULL) {      buf = buflist_new((char_u *)name, NULL, 1, 0);    } @@ -7223,30 +7253,26 @@ static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, FunPtr fptr)  int func_call(char_u *name, typval_T *args, partial_T *partial,                dict_T *selfdict, typval_T *rettv)  { -  listitem_T  *item;    typval_T argv[MAX_FUNC_ARGS + 1];    int argc = 0;    int dummy;    int r = 0; -  for (item = args->vval.v_list->lv_first; item != NULL; -       item = item->li_next) { +  TV_LIST_ITER(args->vval.v_list, item, {      if (argc == MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) {        EMSG(_("E699: Too many arguments")); -      break; +      goto func_call_skip_call;      } -    /* Make a copy of each argument.  This is needed to be able to set -     * v_lock to VAR_FIXED in the copy without changing the original list. -     */ -    tv_copy(&item->li_tv, &argv[argc++]); -  } +    // Make a copy of each argument.  This is needed to be able to set +    // v_lock to VAR_FIXED in the copy without changing the original list. +    tv_copy(TV_LIST_ITEM_TV(item), &argv[argc++]); +  }); -  if (item == NULL) { -    r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL, -                  curwin->w_cursor.lnum, curwin->w_cursor.lnum, -                  &dummy, true, partial, selfdict); -  } +  r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL, +                curwin->w_cursor.lnum, curwin->w_cursor.lnum, +                &dummy, true, partial, selfdict); +func_call_skip_call:    // Free the arguments.    while (argc > 0) {      tv_clear(&argv[--argc]); @@ -7300,23 +7326,89 @@ static void f_changenr(typval_T *argvars, typval_T *rettv, FunPtr fptr)    rettv->vval.v_number = curbuf->b_u_seq_cur;  } +// "chanclose(id[, stream])" function +static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ +  rettv->v_type = VAR_NUMBER; +  rettv->vval.v_number = 0; + +  if (check_restricted() || check_secure()) { +    return; +  } + +  if (argvars[0].v_type != VAR_NUMBER || (argvars[1].v_type != VAR_STRING +        && argvars[1].v_type != VAR_UNKNOWN)) { +    EMSG(_(e_invarg)); +    return; +  } + +  ChannelPart part = kChannelPartAll; +  if (argvars[1].v_type == VAR_STRING) { +    char *stream = (char *)argvars[1].vval.v_string; +    if (!strcmp(stream, "stdin")) { +      part = kChannelPartStdin; +    } else if (!strcmp(stream, "stdout")) { +      part = kChannelPartStdout; +    } else if (!strcmp(stream, "stderr")) { +      part = kChannelPartStderr; +    } else if (!strcmp(stream, "rpc")) { +      part = kChannelPartRpc; +    } else { +      EMSG2(_("Invalid channel stream \"%s\""), stream); +      return; +    } +  } +  const char *error; +  rettv->vval.v_number = channel_close(argvars[0].vval.v_number, part, &error); +  if (!rettv->vval.v_number) { +    EMSG(error); +  } +} + +// "chansend(id, data)" function +static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ +  rettv->v_type = VAR_NUMBER; +  rettv->vval.v_number = 0; + +  if (check_restricted() || check_secure()) { +    return; +  } + +  if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type == VAR_UNKNOWN) { +    // First argument is the channel id and second is the data to write +    EMSG(_(e_invarg)); +    return; +  } + +  ptrdiff_t input_len = 0; +  char *input = save_tv_as_string(&argvars[1], &input_len, false); +  if (!input) { +    // Either the error has been handled by save_tv_as_string(), +    // or there is no input to send. +    return; +  } +  uint64_t id = argvars[0].vval.v_number; +  const char *error = NULL; +  rettv->vval.v_number = channel_send(id, input, input_len, &error); +  if (error) { +    EMSG(error); +  } +} +  /*   * "char2nr(string)" function   */  static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { -  if (has_mbyte) { -    int utf8 = 0; - -    if (argvars[1].v_type != VAR_UNKNOWN) { -      utf8 = tv_get_number_chk(&argvars[1], NULL); +  if (argvars[1].v_type != VAR_UNKNOWN) { +    if (!tv_check_num(&argvars[1])) { +      return;      } - -    rettv->vval.v_number = (utf8 ? *utf_ptr2char : *mb_ptr2char)( -        (const char_u *)tv_get_string(&argvars[0])); -  } else { -    rettv->vval.v_number = (uint8_t)(tv_get_string(&argvars[0])[0]);    } + +  rettv->vval.v_number = utf_ptr2char( +      (const char_u *)tv_get_string(&argvars[0]));  }  /* @@ -7397,12 +7489,12 @@ static void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr)    if (!undo_allowed())      return; -  if (argvars[1].v_type != VAR_LIST || argvars[1].vval.v_list == NULL) { +  if (argvars[1].v_type != VAR_LIST) {      EMSG(_(e_invarg));      return;    } -  const int startcol = tv_get_number_chk(&argvars[0], NULL); +  const colnr_T startcol = tv_get_number_chk(&argvars[0], NULL);    if (startcol <= 0) {      return;    } @@ -7505,7 +7597,7 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr)      long idx;      if ((l = argvars[0].vval.v_list) != NULL) { -      li = l->lv_first; +      li = tv_list_first(l);        if (argvars[2].v_type != VAR_UNKNOWN) {          bool error = false; @@ -7523,9 +7615,11 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr)            li = NULL;        } -      for (; li != NULL; li = li->li_next) -        if (tv_equal(&li->li_tv, &argvars[1], ic, FALSE)) -          ++n; +      for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { +        if (tv_equal(TV_LIST_ITEM_TV(li), &argvars[1], ic, false)) { +          n++; +        } +      }      }    } else if (argvars[0].v_type == VAR_DICT) {      int todo; @@ -7613,9 +7707,9 @@ static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr)      }    } else {      line = tv_get_lnum(argvars); -    col = tv_get_number_chk(&argvars[1], NULL); +    col = (long)tv_get_number_chk(&argvars[1], NULL);      if (argvars[2].v_type != VAR_UNKNOWN) { -      coladd = tv_get_number_chk(&argvars[2], NULL); +      coladd = (long)tv_get_number_chk(&argvars[2], NULL);      }    }    if (line < 0 || col < 0 @@ -7669,7 +7763,7 @@ static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr)    }    const char *const name = tv_get_string(&argvars[0]); -  if (name == NULL || *name == NUL) { +  if (*name == NUL) {      EMSG(_(e_invarg));      return;    } @@ -7843,34 +7937,51 @@ static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr)    bool n = true;    switch (argvars[0].v_type) { -  case VAR_STRING: -  case VAR_FUNC: -    n = argvars[0].vval.v_string == NULL -        || *argvars[0].vval.v_string == NUL; -    break; -  case VAR_PARTIAL: -    n = false; -    break; -  case VAR_NUMBER: -    n = argvars[0].vval.v_number == 0; -    break; -  case VAR_FLOAT: -    n = argvars[0].vval.v_float == 0.0; -    break; -  case VAR_LIST: -    n = argvars[0].vval.v_list == NULL -        || argvars[0].vval.v_list->lv_first == NULL; -    break; -  case VAR_DICT: -    n = argvars[0].vval.v_dict == NULL -        || argvars[0].vval.v_dict->dv_hashtab.ht_used == 0; -    break; -  case VAR_SPECIAL: -    n = argvars[0].vval.v_special != kSpecialVarTrue; -    break; -  case VAR_UNKNOWN: -    EMSG2(_(e_intern2), "f_empty(UNKNOWN)"); -    break; +    case VAR_STRING: +    case VAR_FUNC: { +      n = argvars[0].vval.v_string == NULL +          || *argvars[0].vval.v_string == NUL; +      break; +    } +    case VAR_PARTIAL: { +      n = false; +      break; +    } +    case VAR_NUMBER: { +      n = argvars[0].vval.v_number == 0; +      break; +    } +    case VAR_FLOAT: { +      n = argvars[0].vval.v_float == 0.0; +      break; +    } +    case VAR_LIST: { +      n = (tv_list_len(argvars[0].vval.v_list) == 0); +      break; +    } +    case VAR_DICT: { +      n = (tv_dict_len(argvars[0].vval.v_dict) == 0); +      break; +    } +    case VAR_SPECIAL: { +      // Using switch to get warning if SpecialVarValue receives more values. +      switch (argvars[0].vval.v_special) { +        case kSpecialVarTrue: { +          n = false; +          break; +        } +        case kSpecialVarFalse: +        case kSpecialVarNull: { +          n = true; +          break; +        } +      } +      break; +    } +    case VAR_UNKNOWN: { +      internal_error("f_empty(UNKNOWN)"); +      break; +    }    }    rettv->vval.v_number = n; @@ -7934,17 +8045,22 @@ static void f_executable(typval_T *argvars, typval_T *rettv, FunPtr fptr)            && os_can_exe((const char_u *)name, NULL, false)));  } +typedef struct { +  const list_T *const l; +  const listitem_T *li; +} GetListLineCookie; +  static char_u *get_list_line(int c, void *cookie, int indent)  { -  const listitem_T **const p = (const listitem_T **)cookie; -  const listitem_T *item = *p; +  GetListLineCookie *const p = (GetListLineCookie *)cookie; +  const listitem_T *const item = p->li;    if (item == NULL) {      return NULL;    }    char buf[NUMBUFLEN]; -  const char *const s = tv_get_string_buf_chk(&item->li_tv, buf); -  *p = item->li_next; +  const char *const s = tv_get_string_buf_chk(TV_LIST_ITEM_TV(item), buf); +  p->li = TV_LIST_ITEM_NEXT(p->l, item);    return (char_u *)(s == NULL ? NULL : xstrdup(s));  } @@ -7986,11 +8102,14 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr)      do_cmdline_cmd(tv_get_string(&argvars[0]));    } else if (argvars[0].vval.v_list != NULL) {      list_T *const list = argvars[0].vval.v_list; -    list->lv_refcount++; -    listitem_T *const item = list->lv_first; -    do_cmdline(NULL, get_list_line, (void *)&item, +    tv_list_ref(list); +    GetListLineCookie cookie = { +      .l = list, +      .li = tv_list_first(list), +    }; +    do_cmdline(NULL, get_list_line, (void *)&cookie,                 DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT|DOCMD_KEYTYPED); -    list->lv_refcount--; +    tv_list_unref(list);    }    msg_silent = save_msg_silent;    emsg_silent = save_emsg_silent; @@ -7998,8 +8117,7 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr)    ga_append(capture_ga, NUL);    rettv->v_type = VAR_STRING; -  rettv->vval.v_string = vim_strsave(capture_ga->ga_data); -  ga_clear(capture_ga); +  rettv->vval.v_string = capture_ga->ga_data;    capture_ga = save_capture_ga;  } @@ -8108,7 +8226,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr)      result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL);      emsg_off--;      if (rettv->v_type == VAR_LIST) { -      tv_list_alloc_ret(rettv); +      tv_list_alloc_ret(rettv, (result != NULL));        if (result != NULL) {          tv_list_append_string(rettv->vval.v_list, (const char *)result, -1);        } @@ -8131,8 +8249,8 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr)          rettv->vval.v_string = ExpandOne(&xpc, (char_u *)s, NULL, options,                                           WILD_ALL);        } else { -        tv_list_alloc_ret(rettv);          ExpandOne(&xpc, (char_u *)s, NULL, options, WILD_ALL_KEEP); +        tv_list_alloc_ret(rettv, xpc.xp_numfiles);          for (int i = 0; i < xpc.xp_numfiles; i++) {            tv_list_append_string(rettv->vval.v_list,                                  (const char *)xpc.xp_files[i], -1); @@ -8145,6 +8263,19 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr)    }  } + +/// "menu_get(path [, modes])" function +static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ +  tv_list_alloc_ret(rettv, kListLenMayKnow); +  int modes = MENU_ALL_MODES; +  if (argvars[1].v_type == VAR_STRING) { +    const char_u *const strmodes = (char_u *)tv_get_string(&argvars[1]); +    modes = get_menu_cmd_modes(strmodes, false, NULL, NULL); +  } +  menu_get((char_u *)tv_get_string(&argvars[0]), modes, rettv->vval.v_list); +} +  /*   * "extend(list, list [, idx])" function   * "extend(dict, dict [, action])" function @@ -8152,7 +8283,6 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr)  static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr)  {    const char *const arg_errmsg = N_("extend() argument"); -  const size_t arg_errmsg_len = strlen(arg_errmsg);    if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) {      long before; @@ -8160,22 +8290,15 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr)      list_T *const l1 = argvars[0].vval.v_list;      list_T *const l2 = argvars[1].vval.v_list; -    if (l1 == NULL) { -      const bool locked = tv_check_lock(VAR_FIXED, arg_errmsg, arg_errmsg_len); -      (void)locked; -      assert(locked == true); -    } else if (l2 == NULL) { -      // Do nothing -      tv_copy(&argvars[0], rettv); -    } else if (!tv_check_lock(l1->lv_lock, arg_errmsg, arg_errmsg_len)) { +    if (!tv_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) {        listitem_T *item;        if (argvars[2].v_type != VAR_UNKNOWN) { -        before = tv_get_number_chk(&argvars[2], &error); +        before = (long)tv_get_number_chk(&argvars[2], &error);          if (error) {            return;  // Type error; errmsg already given.          } -        if (before == l1->lv_len) { +        if (before == tv_list_len(l1)) {            item = NULL;          } else {            item = tv_list_find(l1, before); @@ -8184,8 +8307,9 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr)              return;            }          } -      } else +      } else {          item = NULL; +      }        tv_list_extend(l1, l2, item);        tv_copy(&argvars[0], rettv); @@ -8195,13 +8319,13 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr)      dict_T *const d1 = argvars[0].vval.v_dict;      dict_T *const d2 = argvars[1].vval.v_dict;      if (d1 == NULL) { -      const bool locked = tv_check_lock(VAR_FIXED, arg_errmsg, arg_errmsg_len); +      const bool locked = tv_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE);        (void)locked;        assert(locked == true);      } else if (d2 == NULL) {        // Do nothing        tv_copy(&argvars[0], rettv); -    } else if (!tv_check_lock(d1->dv_lock, arg_errmsg, arg_errmsg_len)) { +    } else if (!tv_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) {        const char *action = "force";        // Check the third argument.        if (argvars[2].v_type != VAR_UNKNOWN) { @@ -8304,7 +8428,7 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what)    }    if (count < 0) { -    tv_list_alloc_ret(rettv); +    tv_list_alloc_ret(rettv, kListLenUnknown);    }    if (*fname != NUL && !error) { @@ -8337,7 +8461,6 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what)  static void filter_map(typval_T *argvars, typval_T *rettv, int map)  {    typval_T    *expr; -  listitem_T  *li, *nli;    list_T      *l = NULL;    dictitem_T  *di;    hashtab_T   *ht; @@ -8349,20 +8472,22 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)    int todo;    char_u *ermsg = (char_u *)(map ? "map()" : "filter()");    const char *const arg_errmsg = (map -                                  ? _("map() argument") -                                  : _("filter() argument")); -  const size_t arg_errmsg_len = strlen(arg_errmsg); +                                  ? N_("map() argument") +                                  : N_("filter() argument"));    int save_did_emsg;    int idx = 0;    if (argvars[0].v_type == VAR_LIST) { +    tv_copy(&argvars[0], rettv);      if ((l = argvars[0].vval.v_list) == NULL -        || (!map && tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len))) { +        || (!map && tv_check_lock(tv_list_locked(l), arg_errmsg, +                                  TV_TRANSLATE))) {        return;      }    } else if (argvars[0].v_type == VAR_DICT) { +    tv_copy(&argvars[0], rettv);      if ((d = argvars[0].vval.v_dict) == NULL -        || (!map && tv_check_lock(d->dv_lock, arg_errmsg, arg_errmsg_len))) { +        || (!map && tv_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) {        return;      }    } else { @@ -8395,8 +8520,8 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)            di = TV_DICT_HI2DI(hi);            if (map -              && (tv_check_lock(di->di_tv.v_lock, arg_errmsg, arg_errmsg_len) -                  || var_check_ro(di->di_flags, arg_errmsg, arg_errmsg_len))) { +              && (tv_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE) +                  || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE))) {              break;            } @@ -8407,8 +8532,8 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)              break;            }            if (!map && rem) { -            if (var_check_fixed(di->di_flags, arg_errmsg, arg_errmsg_len) -                || var_check_ro(di->di_flags, arg_errmsg, arg_errmsg_len)) { +            if (var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE) +                || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) {                break;              }              tv_dict_item_remove(d, di); @@ -8419,18 +8544,21 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)      } else {        vimvars[VV_KEY].vv_type = VAR_NUMBER; -      for (li = l->lv_first; li != NULL; li = nli) { +      for (listitem_T *li = tv_list_first(l); li != NULL;) {          if (map -            && tv_check_lock(li->li_tv.v_lock, arg_errmsg, arg_errmsg_len)) { +            && tv_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, +                             TV_TRANSLATE)) {            break;          } -        nli = li->li_next;          vimvars[VV_KEY].vv_nr = idx; -        if (filter_map_one(&li->li_tv, expr, map, &rem) == FAIL -            || did_emsg) +        if (filter_map_one(TV_LIST_ITEM_TV(li), expr, map, &rem) == FAIL +            || did_emsg) {            break; +        }          if (!map && rem) { -          tv_list_item_remove(l, li); +          li = tv_list_item_remove(l, li); +        } else { +          li = TV_LIST_ITEM_NEXT(l, li);          }          idx++;        } @@ -8441,8 +8569,6 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)      did_emsg |= save_did_emsg;    } - -  tv_copy(&argvars[0], rettv);  }  static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) @@ -8540,9 +8666,9 @@ static void f_float2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)    float_T f;    if (tv_get_float_chk(argvars, &f)) { -    if (f < VARNUMBER_MIN) { -      rettv->vval.v_number = VARNUMBER_MIN; -    } else if (f > VARNUMBER_MAX) { +    if (f <= -VARNUMBER_MAX + DBL_EPSILON) { +      rettv->vval.v_number = -VARNUMBER_MAX; +    } else if (f >= VARNUMBER_MAX - DBL_EPSILON) {        rettv->vval.v_number = VARNUMBER_MAX;      } else {        rettv->vval.v_number = (varnumber_T)f; @@ -8666,7 +8792,6 @@ static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr)    char_u      *r;    int         len;    char        *txt; -  long        count;    rettv->v_type = VAR_STRING;    rettv->vval.v_string = NULL; @@ -8674,10 +8799,9 @@ static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr)    foldstart = (linenr_T)get_vim_var_nr(VV_FOLDSTART);    foldend = (linenr_T)get_vim_var_nr(VV_FOLDEND);    dashes = get_vim_var_str(VV_FOLDDASHES); -  if (foldstart > 0 && foldend <= curbuf->b_ml.ml_line_count -      && dashes != NULL) { -    /* Find first non-empty line in the fold. */ -    for (lnum = foldstart; lnum < foldend; ++lnum) { +  if (foldstart > 0 && foldend <= curbuf->b_ml.ml_line_count) { +    // Find first non-empty line in the fold. +    for (lnum = foldstart; lnum < foldend; lnum++) {        if (!linewhite(lnum)) {          break;        } @@ -8694,8 +8818,8 @@ static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr)            s = skipwhite(s + 1);        }      } -    count = (long)(foldend - foldstart + 1); -    txt = _("+-%s%3ld lines: "); +    unsigned long count = (unsigned long)(foldend - foldstart + 1); +    txt = ngettext("+-%s%3ld line: ", "+-%s%3ld lines: ", count);      r = xmalloc(STRLEN(txt)                  + STRLEN(dashes) // for %s                  + 20             // for %3ld @@ -8715,7 +8839,7 @@ static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr)  static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr)  {    char_u      *text; -  char_u buf[51]; +  char_u buf[FOLD_TEXT_LEN];    foldinfo_T foldinfo;    int fold_count; @@ -8800,10 +8924,8 @@ static void common_function(typval_T *argvars, typval_T *rettv,        snprintf(sid_buf, sizeof(sid_buf), "<SNR>%" PRId64 "_",                 (int64_t)current_SID);        name = xmalloc(STRLEN(sid_buf) + STRLEN(s + off) + 1); -      if (name != NULL) { -        STRCPY(name, sid_buf); -        STRCAT(name, s + off); -      } +      STRCPY(name, sid_buf); +      STRCAT(name, s + off);      } else {        name = vim_strsave(s);      } @@ -8838,7 +8960,7 @@ static void common_function(typval_T *argvars, typval_T *rettv,            goto theend;          }          list = argvars[arg_idx].vval.v_list; -        if (list == NULL || list->lv_len == 0) { +        if (tv_list_len(list) == 0) {            arg_idx = 0;          }        } @@ -8849,25 +8971,18 @@ static void common_function(typval_T *argvars, typval_T *rettv,        // result is a VAR_PARTIAL        if (arg_idx > 0 || (arg_pt != NULL && arg_pt->pt_argc > 0)) {          const int arg_len = (arg_pt == NULL ? 0 : arg_pt->pt_argc); -        const int lv_len = (list == NULL ? 0 : list->lv_len); +        const int lv_len = tv_list_len(list);          pt->pt_argc = arg_len + lv_len;          pt->pt_argv = xmalloc(sizeof(pt->pt_argv[0]) * pt->pt_argc); -        if (pt->pt_argv == NULL) { -          xfree(pt); -          xfree(name); -          goto theend; -        }          int i = 0;          for (; i < arg_len; i++) {            tv_copy(&arg_pt->pt_argv[i], &pt->pt_argv[i]);          }          if (lv_len > 0) { -          for (listitem_T *li = list->lv_first; -               li != NULL; -               li = li->li_next) { -            tv_copy(&li->li_tv, &pt->pt_argv[i++]); -          } +          TV_LIST_ITER(list, li, { +            tv_copy(TV_LIST_ITEM_TV(li), &pt->pt_argv[i++]); +          });          }        } @@ -8953,7 +9068,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)        li = tv_list_find(l, tv_get_number_chk(&argvars[1], &error));        if (!error && li != NULL) { -        tv = &li->li_tv; +        tv = TV_LIST_ITEM_TV(li);        }      }    } else if (argvars[0].v_type == VAR_DICT) { @@ -8981,13 +9096,10 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)        if (strcmp(what, "func") == 0 || strcmp(what, "name") == 0) {          rettv->v_type = (*what == 'f' ? VAR_FUNC : VAR_STRING);          const char *const n = (const char *)partial_name(pt); -        if (n == NULL) { -          rettv->vval.v_string = NULL; -        } else { -          rettv->vval.v_string = (char_u *)xstrdup(n); -          if (rettv->v_type == VAR_FUNC) { -            func_ref(rettv->vval.v_string); -          } +        assert(n != NULL); +        rettv->vval.v_string = (char_u *)xstrdup(n); +        if (rettv->v_type == VAR_FUNC) { +          func_ref(rettv->vval.v_string);          }        } else if (strcmp(what, "dict") == 0) {          rettv->v_type = VAR_DICT; @@ -8997,7 +9109,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)          }        } else if (strcmp(what, "args") == 0) {          rettv->v_type = VAR_LIST; -        if (tv_list_alloc_ret(rettv) != NULL) { +        if (tv_list_alloc_ret(rettv, pt->pt_argc) != NULL) {            for (int i = 0; i < pt->pt_argc; i++) {              tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]);            } @@ -9021,8 +9133,10 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)  }  /// Returns information about signs placed in a buffer as list of dicts. -static void get_buffer_signs(buf_T *buf, list_T *l) +static list_T *get_buffer_signs(buf_T *buf) +  FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT  { +  list_T *const l = tv_list_alloc(kListLenMayKnow);    for (signlist_T *sign = buf->b_signlist; sign; sign = sign->next) {      dict_T *const d = tv_dict_alloc(); @@ -9033,6 +9147,7 @@ static void get_buffer_signs(buf_T *buf, list_T *l)      tv_list_append_dict(l, d);    } +  return l;  }  /// Returns buffer options, variables and other attributes in a dictionary. @@ -9043,7 +9158,8 @@ static dict_T *get_buffer_info(buf_T *buf)    tv_dict_add_nr(dict, S_LEN("bufnr"), buf->b_fnum);    tv_dict_add_str(dict, S_LEN("name"),                    buf->b_ffname != NULL ? (const char *)buf->b_ffname : ""); -  tv_dict_add_nr(dict, S_LEN("lnum"), buflist_findlnum(buf)); +  tv_dict_add_nr(dict, S_LEN("lnum"), +                 buf == curbuf ? curwin->w_cursor.lnum : buflist_findlnum(buf));    tv_dict_add_nr(dict, S_LEN("loaded"), buf->b_ml.ml_mfp != NULL);    tv_dict_add_nr(dict, S_LEN("listed"), buf->b_p_bl);    tv_dict_add_nr(dict, S_LEN("changed"), bufIsChanged(buf)); @@ -9055,7 +9171,7 @@ static dict_T *get_buffer_info(buf_T *buf)    tv_dict_add_dict(dict, S_LEN("variables"), buf->b_vars);    // List of windows displaying this buffer -  list_T *const windows = tv_list_alloc(); +  list_T *const windows = tv_list_alloc(kListLenMayKnow);    FOR_ALL_TAB_WINDOWS(tp, wp) {      if (wp->w_buffer == buf) {        tv_list_append_number(windows, (varnumber_T)wp->handle); @@ -9065,9 +9181,7 @@ static dict_T *get_buffer_info(buf_T *buf)    if (buf->b_signlist != NULL) {      // List of signs placed in this buffer -    list_T *const signs = tv_list_alloc(); -    get_buffer_signs(buf, signs); -    tv_dict_add_list(dict, S_LEN("signs"), signs); +    tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf));    }    return dict; @@ -9081,7 +9195,7 @@ static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)    bool sel_buflisted = false;    bool sel_bufloaded = false; -  tv_list_alloc_ret(rettv); +  tv_list_alloc_ret(rettv, kListLenMayKnow);    // List of all the buffers or selected buffers    if (argvars[0].v_type == VAR_DICT) { @@ -9125,9 +9239,7 @@ static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)      }      dict_T *const d = get_buffer_info(buf); -    if (d != NULL) { -      tv_list_append_dict(rettv->vval.v_list, d); -    } +    tv_list_append_dict(rettv->vval.v_list, d);      if (argbuf != NULL) {        return;      } @@ -9142,35 +9254,33 @@ static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)   */  static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retlist, typval_T *rettv)  { -  char_u      *p; - -  rettv->v_type = VAR_STRING; +  rettv->v_type = (retlist ? VAR_LIST : VAR_STRING);    rettv->vval.v_string = NULL; -  if (retlist) { -    tv_list_alloc_ret(rettv); -  } -  if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0) +  if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0 || end < start) { +    if (retlist) { +      tv_list_alloc_ret(rettv, 0); +    }      return; +  } -  if (!retlist) { -    if (start >= 1 && start <= buf->b_ml.ml_line_count) -      p = ml_get_buf(buf, start, FALSE); -    else -      p = (char_u *)""; -    rettv->vval.v_string = vim_strsave(p); -  } else { -    if (end < start) -      return; - -    if (start < 1) +  if (retlist) { +    if (start < 1) {        start = 1; -    if (end > buf->b_ml.ml_line_count) +    } +    if (end > buf->b_ml.ml_line_count) {        end = buf->b_ml.ml_line_count; +    } +    tv_list_alloc_ret(rettv, end - start + 1);      while (start <= end) {        tv_list_append_string(rettv->vval.v_list,                              (const char *)ml_get_buf(buf, start++, false), -1);      } +  } else { +    rettv->v_type = VAR_STRING; +    rettv->vval.v_string = ((start >= 1 && start <= buf->b_ml.ml_line_count) +                            ? vim_strsave(ml_get_buf(buf, start, false)) +                            : NULL);    }  } @@ -9495,14 +9605,12 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr)  theend:    pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context); -  tv_list_alloc_ret(rettv); -  if (pat != NULL) { -    ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP); +  ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP); +  tv_list_alloc_ret(rettv, xpc.xp_numfiles); -    for (int i = 0; i < xpc.xp_numfiles; i++) { -      tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], -                            -1); -    } +  for (int i = 0; i < xpc.xp_numfiles; i++) { +    tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], +                          -1);    }    xfree(pat);    ExpandCleanup(&xpc); @@ -9602,13 +9710,15 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr)        if (from) {          break;        } -    case kCdScopeTab:         // FALLTHROUGH +      // fallthrough +    case kCdScopeTab:        assert(tp);        from = tp->tp_localdir;        if (from) {          break;        } -    case kCdScopeGlobal:      // FALLTHROUGH +      // fallthrough +    case kCdScopeGlobal:        if (globaldir) {        // `globaldir` is not always set.          from = globaldir;        } else if (os_dirname(cwd, MAXPATHL) == FAIL) {  // Get the OS CWD. @@ -9790,7 +9900,7 @@ static void f_getline(typval_T *argvars, typval_T *rettv, FunPtr fptr)    const linenr_T lnum = tv_get_lnum(argvars);    if (argvars[1].v_type == VAR_UNKNOWN) { -    end = 0; +    end = lnum;      retlist = false;    } else {      end = tv_get_lnum(&argvars[1]); @@ -9804,7 +9914,7 @@ static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg,                              typval_T *rettv)  {    if (what_arg->v_type == VAR_UNKNOWN) { -    tv_list_alloc_ret(rettv); +    tv_list_alloc_ret(rettv, kListLenMayKnow);      if (is_qf || wp != NULL) {        (void)get_errorlist(wp, -1, rettv->vval.v_list);      } @@ -9839,7 +9949,7 @@ static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)    matchitem_T *cur = curwin->w_match_head;    int i; -  tv_list_alloc_ret(rettv); +  tv_list_alloc_ret(rettv, kListLenMayKnow);    while (cur != NULL) {      dict_T *dict = tv_dict_alloc();      if (cur->match.regprog == NULL) { @@ -9852,7 +9962,7 @@ static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)          if (llpos->lnum == 0) {            break;          } -        list_T *l = tv_list_alloc(); +        list_T *const l = tv_list_alloc(1 + (llpos->col > 0 ? 2 : 0));          tv_list_append_number(l, (varnumber_T)llpos->lnum);          if (llpos->col > 0) {            tv_list_append_number(l, (varnumber_T)llpos->col); @@ -9901,7 +10011,7 @@ static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos)      fp = var2fpos(&argvars[0], true, &fnum);    } -  list_T *l = tv_list_alloc_ret(rettv); +  list_T *const l = tv_list_alloc_ret(rettv, 4 + (!!getcurpos));    tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0);    tv_list_append_number(l, ((fp != NULL)                              ? (varnumber_T)fp->lnum @@ -9974,12 +10084,12 @@ static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr)    if (return_list) {      rettv->v_type = VAR_LIST; -    rettv->vval.v_list =  +    rettv->vval.v_list =        get_reg_contents(regname, (arg2 ? kGRegExprSrc : 0) | kGRegList);      if (rettv->vval.v_list == NULL) { -      rettv->vval.v_list = tv_list_alloc(); +      rettv->vval.v_list = tv_list_alloc(0);      } -    rettv->vval.v_list->lv_refcount++; +    tv_list_ref(rettv->vval.v_list);    } else {      rettv->v_type = VAR_STRING;      rettv->vval.v_string = get_reg_contents(regname, arg2 ? kGRegExprSrc : 0); @@ -10027,7 +10137,7 @@ static dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx)    tv_dict_add_nr(dict, S_LEN("tabnr"), tp_idx); -  list_T *const l = tv_list_alloc(); +  list_T *const l = tv_list_alloc(kListLenMayKnow);    FOR_ALL_WINDOWS_IN_TAB(wp, tp) {      tv_list_append_number(l, (varnumber_T)wp->handle);    } @@ -10044,7 +10154,9 @@ static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)  {    tabpage_T *tparg = NULL; -  tv_list_alloc_ret(rettv); +  tv_list_alloc_ret(rettv, (argvars[0].v_type == VAR_UNKNOWN +                            ? 1 +                            : kListLenMayKnow));    if (argvars[0].v_type != VAR_UNKNOWN) {      // Information about one tab page @@ -10062,9 +10174,7 @@ static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)        continue;      }      dict_T *const d = get_tabpage_info(tp, tpnr); -    if (d != NULL) { -      tv_list_append_dict(rettv->vval.v_list, d); -    } +    tv_list_append_dict(rettv->vval.v_list, d);      if (tparg != NULL) {        return;      } @@ -10145,7 +10255,7 @@ static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)  {    win_T *wparg = NULL; -  tv_list_alloc_ret(rettv); +  tv_list_alloc_ret(rettv, kListLenMayKnow);    if (argvars[0].v_type != VAR_UNKNOWN) {      wparg = win_id2wp(argvars); @@ -10166,9 +10276,7 @@ static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)        }        winnr++;        dict_T *const d = get_win_info(wp, tabnr, winnr); -      if (d != NULL) { -        tv_list_append_dict(rettv->vval.v_list, d); -      } +      tv_list_append_dict(rettv->vval.v_list, d);        if (wparg != NULL) {          // found information about a specific window          return; @@ -10372,9 +10480,9 @@ static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr)        rettv->vval.v_string = ExpandOne(            &xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options, WILD_ALL);      } else { -      tv_list_alloc_ret(rettv);        ExpandOne(&xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options,                  WILD_ALL_KEEP); +      tv_list_alloc_ret(rettv, xpc.xp_numfiles);        for (int i = 0; i < xpc.xp_numfiles; i++) {          tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i],                                -1); @@ -10423,7 +10531,7 @@ static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr)      if (rettv->v_type == VAR_STRING) {        rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n");      } else { -      tv_list_alloc_ret(rettv); +      tv_list_alloc_ret(rettv, ga.ga_len);        for (int i = 0; i < ga.ga_len; i++) {          tv_list_append_string(rettv->vval.v_list,                                ((const char **)(ga.ga_data))[i], -1); @@ -10519,6 +10627,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)      "mouse",      "multi_byte",      "multi_lang", +    "num64",      "packages",      "path_extra",      "persistent_undo", @@ -10561,6 +10670,9 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)      "windows",      "winaltkeys",      "writebackup", +#if defined(HAVE_WSL) +    "wsl", +#endif      "nvim",    }; @@ -10597,6 +10709,10 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)        n = has_nvim_version(name + 5);      } else if (STRICMP(name, "vim_starting") == 0) {        n = (starting != 0); +    } else if (STRICMP(name, "ttyin") == 0) { +      n = stdin_isatty; +    } else if (STRICMP(name, "ttyout") == 0) { +      n = stdout_isatty;      } else if (STRICMP(name, "multi_byte_encoding") == 0) {        n = has_mbyte != 0;  #if defined(USE_ICONV) && defined(DYNAMIC_ICONV) @@ -10616,6 +10732,17 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)      n = true;    } +  if (STRICMP(name, "ruby") == 0 && n == true) { +    char *rubyhost = call_func_retstr("provider#ruby#Detect", 0, NULL, true); +    if (rubyhost) { +      if (*rubyhost == NUL) { +        // Invalid rubyhost executable. Gem is probably not installed. +        n = false; +      } +      xfree(rubyhost); +    } +  } +    rettv->vval.v_number = n;  } @@ -10935,39 +11062,42 @@ static void f_indent(typval_T *argvars, typval_T *rettv, FunPtr fptr)   */  static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { -  list_T      *l; -  listitem_T  *item;    long idx = 0; -  int ic = FALSE; +  bool ic = false;    rettv->vval.v_number = -1;    if (argvars[0].v_type != VAR_LIST) {      EMSG(_(e_listreq));      return;    } -  l = argvars[0].vval.v_list; +  list_T *const l = argvars[0].vval.v_list;    if (l != NULL) { -    item = l->lv_first; +    listitem_T *item = tv_list_first(l);      if (argvars[2].v_type != VAR_UNKNOWN) {        bool error = false; -      // Start at specified item.  Use the cached index that tv_list_find() -      // sets, so that a negative number also works. -      item = tv_list_find(l, tv_get_number_chk(&argvars[2], &error)); -      idx = l->lv_idx; -      if (argvars[3].v_type != VAR_UNKNOWN) { -        ic = tv_get_number_chk(&argvars[3], &error); -      } -      if (error) { +      // Start at specified item. +      idx = tv_list_uidx(l, tv_get_number_chk(&argvars[2], &error)); +      if (error || idx == -1) {          item = NULL; +      } else { +        item = tv_list_find(l, idx); +        assert(item != NULL); +      } +      if (argvars[3].v_type != VAR_UNKNOWN) { +        ic = !!tv_get_number_chk(&argvars[3], &error); +        if (error) { +          item = NULL; +        }        }      } -    for (; item != NULL; item = item->li_next, ++idx) -      if (tv_equal(&item->li_tv, &argvars[1], ic, FALSE)) { +    for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) { +      if (tv_equal(TV_LIST_ITEM_TV(item), &argvars[1], ic, false)) {          rettv->vval.v_number = idx;          break;        } +    }    }  } @@ -10980,83 +11110,132 @@ static int inputsecret_flag = 0;   * prompt. The third argument to f_inputdialog() specifies the value to return   * when the user cancels the prompt.   */ -static void get_user_input(typval_T *argvars, typval_T *rettv, int inputdialog) +void get_user_input(const typval_T *const argvars, +                    typval_T *const rettv, const bool inputdialog) +  FUNC_ATTR_NONNULL_ALL  { -  const char *prompt = tv_get_string_chk(&argvars[0]); -  int cmd_silent_save = cmd_silent; -  int xp_type = EXPAND_NOTHING; -  char_u      *xp_arg = NULL; -    rettv->v_type = VAR_STRING;    rettv->vval.v_string = NULL; -  cmd_silent = FALSE;           /* Want to see the prompt. */ -  if (prompt != NULL) { -    // Only the part of the message after the last NL is considered as -    // prompt for the command line. -    const char *p = strrchr(prompt, '\n'); -    if (p == NULL) { -      p = prompt; -    } else { -      p++; -      msg_start(); -      msg_clr_eos(); -      msg_puts_attr_len(prompt, p - prompt, echo_attr); -      msg_didout = false; -      msg_starthere(); +  const char *prompt = ""; +  const char *defstr = ""; +  const char *cancelreturn = NULL; +  const char *xp_name = NULL; +  Callback input_callback = { .type = kCallbackNone }; +  char prompt_buf[NUMBUFLEN]; +  char defstr_buf[NUMBUFLEN]; +  char cancelreturn_buf[NUMBUFLEN]; +  char xp_name_buf[NUMBUFLEN]; +  char def[1] = { 0 }; +  if (argvars[0].v_type == VAR_DICT) { +    if (argvars[1].v_type != VAR_UNKNOWN) { +      emsgf(_("E5050: {opts} must be the only argument")); +      return; +    } +    dict_T *const dict = argvars[0].vval.v_dict; +    prompt = tv_dict_get_string_buf_chk(dict, S_LEN("prompt"), prompt_buf, ""); +    if (prompt == NULL) { +      return; +    } +    defstr = tv_dict_get_string_buf_chk(dict, S_LEN("default"), defstr_buf, ""); +    if (defstr == NULL) { +      return; +    } +    cancelreturn = tv_dict_get_string_buf_chk(dict, S_LEN("cancelreturn"), +                                              cancelreturn_buf, def); +    if (cancelreturn == NULL) {  // error +      return; +    } +    if (*cancelreturn == NUL) { +      cancelreturn = NULL; +    } +    xp_name = tv_dict_get_string_buf_chk(dict, S_LEN("completion"), +                                         xp_name_buf, def); +    if (xp_name == NULL) {  // error +      return; +    } +    if (xp_name == def) {  // default to NULL +      xp_name = NULL; +    } +    if (!tv_dict_get_callback(dict, S_LEN("highlight"), &input_callback)) { +      return; +    } +  } else { +    prompt = tv_get_string_buf_chk(&argvars[0], prompt_buf); +    if (prompt == NULL) { +      return;      } -    cmdline_row = msg_row; - -    const char *defstr = ""; -    char buf[NUMBUFLEN];      if (argvars[1].v_type != VAR_UNKNOWN) { -      defstr = tv_get_string_buf_chk(&argvars[1], buf); -      if (defstr != NULL) { -        stuffReadbuffSpec(defstr); +      defstr = tv_get_string_buf_chk(&argvars[1], defstr_buf); +      if (defstr == NULL) { +        return;        } - -      if (!inputdialog && argvars[2].v_type != VAR_UNKNOWN) { -        char buf2[NUMBUFLEN]; -        // input() with a third argument: completion -        rettv->vval.v_string = NULL; - -        const char *const xp_name = tv_get_string_buf_chk(&argvars[2], buf2); -        if (xp_name == NULL) { +      if (argvars[2].v_type != VAR_UNKNOWN) { +        const char *const arg2 = tv_get_string_buf_chk(&argvars[2], +                                                       cancelreturn_buf); +        if (arg2 == NULL) {            return;          } - -        const int xp_namelen = (int)strlen(xp_name); - -        uint32_t argt; -        if (parse_compl_arg((char_u *)xp_name, xp_namelen, &xp_type, &argt, -                            &xp_arg) == FAIL) { -          return; +        if (inputdialog) { +          cancelreturn = arg2; +        } else { +          xp_name = arg2;          }        }      } +  } -    if (defstr != NULL) { -      int save_ex_normal_busy = ex_normal_busy; -      ex_normal_busy = 0; -      rettv->vval.v_string = -        getcmdline_prompt(inputsecret_flag ? NUL : '@', (char_u *)p, echo_attr, -                          xp_type, xp_arg); -      ex_normal_busy = save_ex_normal_busy; +  int xp_type = EXPAND_NOTHING; +  char *xp_arg = NULL; +  if (xp_name != NULL) { +    // input() with a third argument: completion +    const int xp_namelen = (int)strlen(xp_name); + +    uint32_t argt; +    if (parse_compl_arg((char_u *)xp_name, xp_namelen, &xp_type, +                        &argt, (char_u **)&xp_arg) == FAIL) { +      return;      } -    if (inputdialog && rettv->vval.v_string == NULL -        && argvars[1].v_type != VAR_UNKNOWN -        && argvars[2].v_type != VAR_UNKNOWN) { -      char buf[NUMBUFLEN]; -      rettv->vval.v_string = (char_u *)xstrdup(tv_get_string_buf( -          &argvars[2], buf)); +  } + +  int cmd_silent_save = cmd_silent; + +  cmd_silent = false;  // Want to see the prompt. +  // Only the part of the message after the last NL is considered as +  // prompt for the command line, unlsess cmdline is externalized +  const char *p = prompt; +  if (!ui_is_external(kUICmdline)) { +    const char *lastnl = strrchr(prompt, '\n'); +    if (lastnl != NULL) { +      p = lastnl+1; +      msg_start(); +      msg_clr_eos(); +      msg_puts_attr_len(prompt, p - prompt, echo_attr); +      msg_didout = false; +      msg_starthere();      } +  } +  cmdline_row = msg_row; -    xfree(xp_arg); +  stuffReadbuffSpec(defstr); -    /* since the user typed this, no need to wait for return */ -    need_wait_return = FALSE; -    msg_didout = FALSE; +  const int save_ex_normal_busy = ex_normal_busy; +  ex_normal_busy = 0; +  rettv->vval.v_string = +    (char_u *)getcmdline_prompt(inputsecret_flag ? NUL : '@', p, echo_attr, +                                xp_type, xp_arg, input_callback); +  ex_normal_busy = save_ex_normal_busy; +  callback_free(&input_callback); + +  if (rettv->vval.v_string == NULL && cancelreturn != NULL) { +    rettv->vval.v_string = (char_u *)xstrdup(cancelreturn);    } + +  xfree(xp_arg); + +  // Since the user typed this, no need to wait for return. +  need_wait_return = false; +  msg_didout = false;    cmd_silent = cmd_silent_save;  } @@ -11082,11 +11261,10 @@ static void f_inputdialog(typval_T *argvars, typval_T *rettv, FunPtr fptr)   */  static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { -  listitem_T  *li;    int selected;    int mouse_used; -  if (argvars[0].v_type != VAR_LIST || argvars[0].vval.v_list == NULL) { +  if (argvars[0].v_type != VAR_LIST) {      EMSG2(_(e_listarg), "inputlist()");      return;    } @@ -11097,15 +11275,16 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)    msg_scroll = TRUE;    msg_clr_eos(); -  for (li = argvars[0].vval.v_list->lv_first; li != NULL; li = li->li_next) { -    msg_puts(tv_get_string(&li->li_tv)); +  TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { +    msg_puts(tv_get_string(TV_LIST_ITEM_TV(li)));      msg_putchar('\n'); -  } +  }); -  /* Ask for choice. */ +  // Ask for choice.    selected = prompt_for_number(&mouse_used); -  if (mouse_used) +  if (mouse_used) {      selected -= lines_left; +  }    rettv->vval.v_number = selected;  } @@ -11158,13 +11337,11 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr)  {    list_T *l;    bool error = false; -  const char *const arg_errmsg = _("insert() argument"); -  const size_t arg_errmsg_len = strlen(arg_errmsg);    if (argvars[0].v_type != VAR_LIST) {      EMSG2(_(e_listarg), "insert()"); -  } else if ((l = argvars[0].vval.v_list) != NULL -             && !tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len)) { +  } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), +                            N_("insert() argument"), TV_TRANSLATE)) {      long before = 0;      if (argvars[2].v_type != VAR_UNKNOWN) {        before = tv_get_number_chk(&argvars[2], &error); @@ -11175,7 +11352,7 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr)      }      listitem_T *item = NULL; -    if (before != l->lv_len) { +    if (before != tv_list_len(l)) {        item = tv_list_find(l, before);        if (item == NULL) {          EMSGN(_(e_listidx), before); @@ -11239,7 +11416,7 @@ static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr)          EMSG2(_(e_dictkey), lv.ll_newkey);        } else if (lv.ll_list != NULL) {          // List item. -        rettv->vval.v_number = tv_islocked(&lv.ll_li->li_tv); +        rettv->vval.v_number = tv_islocked(TV_LIST_ITEM_TV(lv.ll_li));        } else {          // Dictionary item.          rettv->vval.v_number = tv_islocked(&lv.ll_di->di_tv); @@ -11268,43 +11445,41 @@ static void dict_list(typval_T *const tv, typval_T *const rettv,      return;    } -  tv_list_alloc_ret(rettv); +  tv_list_alloc_ret(rettv, tv_dict_len(tv->vval.v_dict));    TV_DICT_ITER(tv->vval.v_dict, di, { -    listitem_T *const li = tv_list_item_alloc(); -    tv_list_append(rettv->vval.v_list, li); +    typval_T tv = { .v_lock = VAR_UNLOCKED };      switch (what) {        case kDictListKeys: { -        li->li_tv.v_type = VAR_STRING; -        li->li_tv.v_lock = VAR_UNLOCKED; -        li->li_tv.vval.v_string = vim_strsave(di->di_key); +        tv.v_type = VAR_STRING; +        tv.vval.v_string = vim_strsave(di->di_key);          break;        }        case kDictListValues: { -        tv_copy(&di->di_tv, &li->li_tv); +        tv_copy(&di->di_tv, &tv);          break;        }        case kDictListItems: {          // items() -        list_T *const sub_l = tv_list_alloc(); -        li->li_tv.v_type = VAR_LIST; -        li->li_tv.v_lock = VAR_UNLOCKED; -        li->li_tv.vval.v_list = sub_l; -        sub_l->lv_refcount++; - -        listitem_T *sub_li = tv_list_item_alloc(); -        tv_list_append(sub_l, sub_li); -        sub_li->li_tv.v_type = VAR_STRING; -        sub_li->li_tv.v_lock = VAR_UNLOCKED; -        sub_li->li_tv.vval.v_string = vim_strsave(di->di_key); - -        sub_li = tv_list_item_alloc(); -        tv_list_append(sub_l, sub_li); -        tv_copy(&di->di_tv, &sub_li->li_tv); +        list_T *const sub_l = tv_list_alloc(2); +        tv.v_type = VAR_LIST; +        tv.vval.v_list = sub_l; +        tv_list_ref(sub_l); + +        tv_list_append_owned_tv(sub_l, (typval_T) { +          .v_type = VAR_STRING, +          .v_lock = VAR_UNLOCKED, +          .vval.v_string = (char_u *)xstrdup((const char *)di->di_key), +        }); + +        tv_list_append_tv(sub_l, &di->di_tv); +          break;        }      } + +    tv_list_append_owned_tv(rettv->vval.v_list, tv);    });  } @@ -11326,68 +11501,6 @@ static void f_items(typval_T *argvars, typval_T *rettv, FunPtr fptr)    dict_list(argvars, rettv, 2);  } -// "jobclose(id[, stream])" function -static void f_jobclose(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ -  rettv->v_type = VAR_NUMBER; -  rettv->vval.v_number = 0; - -  if (check_restricted() || check_secure()) { -    return; -  } - -  if (argvars[0].v_type != VAR_NUMBER || (argvars[1].v_type != VAR_STRING -        && argvars[1].v_type != VAR_UNKNOWN)) { -    EMSG(_(e_invarg)); -    return; -  } - -  TerminalJobData *data = find_job(argvars[0].vval.v_number); -  if (!data) { -    EMSG(_(e_invjob)); -    return; -  } - -  Process *proc = (Process *)&data->proc; - -  if (argvars[1].v_type == VAR_STRING) { -    char *stream = (char *)argvars[1].vval.v_string; -    if (!strcmp(stream, "stdin")) { -      if (data->rpc) { -        EMSG(_("Invalid stream on rpc job, use jobclose(id, 'rpc')")); -      } else { -        process_close_in(proc); -      } -    } else if (!strcmp(stream, "stdout")) { -      if (data->rpc) { -        EMSG(_("Invalid stream on rpc job, use jobclose(id, 'rpc')")); -      } else { -        process_close_out(proc); -      } -    } else if (!strcmp(stream, "stderr")) { -      process_close_err(proc); -    } else if (!strcmp(stream, "rpc")) { -      if (data->rpc) { -        channel_close(data->id); -      } else { -        EMSG(_("Invalid job stream: Not an rpc job")); -      } -    } else { -      EMSG2(_("Invalid job stream \"%s\""), stream); -    } -  } else { -    if (data->rpc) { -      channel_close(data->id); -      process_close_err(proc); -    } else { -      process_close_streams(proc); -      if (proc->type == kProcessTypePty) { -        pty_process_close_master(&data->proc.pty); -      } -    } -  } -} -  // "jobpid(id)" function  static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { @@ -11403,61 +11516,15 @@ static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr)      return;    } -  TerminalJobData *data = find_job(argvars[0].vval.v_number); +  Channel *data = find_job(argvars[0].vval.v_number, true);    if (!data) { -    EMSG(_(e_invjob));      return;    } -  Process *proc = (Process *)&data->proc; +  Process *proc = (Process *)&data->stream.proc;    rettv->vval.v_number = proc->pid;  } -// "jobsend()" function -static void f_jobsend(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ -  rettv->v_type = VAR_NUMBER; -  rettv->vval.v_number = 0; - -  if (check_restricted() || check_secure()) { -    return; -  } - -  if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type == VAR_UNKNOWN) { -    // First argument is the job id and second is the string or list to write -    // to the job's stdin -    EMSG(_(e_invarg)); -    return; -  } - -  TerminalJobData *data = find_job(argvars[0].vval.v_number); -  if (!data) { -    EMSG(_(e_invjob)); -    return; -  } - -  if (((Process *)&data->proc)->in->closed) { -    EMSG(_("Can't send data to the job: stdin is closed")); -    return; -  } - -  if (data->rpc) { -    EMSG(_("Can't send raw data to rpc channel")); -    return; -  } - -  ptrdiff_t input_len = 0; -  char *input = save_tv_as_string(&argvars[1], &input_len, false); -  if (!input) { -    // Either the error has been handled by save_tv_as_string(), or there is no -    // input to send. -    return; -  } - -  WBuffer *buf = wstream_new_buffer(input, input_len, 1, xfree); -  rettv->vval.v_number = wstream_write(data->proc.uv.process.in, buf); -} -  // "jobresize(job, width, height)" function  static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { @@ -11476,19 +11543,18 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr)    } -  TerminalJobData *data = find_job(argvars[0].vval.v_number); +  Channel *data = find_job(argvars[0].vval.v_number, true);    if (!data) { -    EMSG(_(e_invjob));      return;    } -  if (data->proc.uv.process.type != kProcessTypePty) { -    EMSG(_(e_jobnotpty)); +  if (data->stream.proc.type != kProcessTypePty) { +    EMSG(_(e_channotpty));      return;    } -  pty_process_resize(&data->proc.pty, argvars[1].vval.v_number, -      argvars[2].vval.v_number); +  pty_process_resize(&data->stream.pty, argvars[1].vval.v_number, +                     argvars[2].vval.v_number);    rettv->vval.v_number = 1;  } @@ -11508,15 +11574,13 @@ static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable)    }    list_T *argl = cmd_tv->vval.v_list; -  int argc = argl->lv_len; +  int argc = tv_list_len(argl);    if (!argc) {      EMSG(_(e_invarg));  // List must have at least one item.      return NULL;    } -  assert(argl->lv_first); - -  const char *exe = tv_get_string_chk(&argl->lv_first->li_tv); +  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) {        *executable = false; @@ -11531,15 +11595,15 @@ static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable)    // Build the argument vector    int i = 0;    char **argv = xcalloc(argc + 1, sizeof(char *)); -  for (listitem_T *arg = argl->lv_first; arg != NULL; arg = arg->li_next) { -    const char *a = tv_get_string_chk(&arg->li_tv); +  TV_LIST_ITER_CONST(argl, arg, { +    const char *a = tv_get_string_chk(TV_LIST_ITEM_TV(arg));      if (!a) {        // Did emsg in tv_get_string_chk; just deallocate argv.        shell_free_argv(argv);        return NULL;      }      argv[i++] = xstrdup(a); -  } +  });    return argv;  } @@ -11573,8 +11637,8 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)    bool detach = false;    bool rpc = false;    bool pty = false; -  Callback on_stdout = CALLBACK_NONE; -  Callback on_stderr = CALLBACK_NONE; +  CallbackReader on_stdout = CALLBACK_READER_INIT, +                 on_stderr = CALLBACK_READER_INIT;    Callback on_exit = CALLBACK_NONE;    char *cwd = NULL;    if (argvars[1].v_type == VAR_DICT) { @@ -11606,32 +11670,21 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)      }    } -  TerminalJobData *data = common_job_init(argv, on_stdout, on_stderr, on_exit, -                                          pty, rpc, detach, cwd); -  Process *proc = (Process *)&data->proc; +  uint16_t width = 0, height = 0; +  char *term_name = NULL;    if (pty) { -    uint16_t width = (uint16_t)tv_dict_get_number(job_opts, "width"); -    if (width > 0) { -      data->proc.pty.width = width; -    } -    uint16_t height = (uint16_t)tv_dict_get_number(job_opts, "height"); -    if (height > 0) { -      data->proc.pty.height = height; -    } -    char *term = tv_dict_get_string(job_opts, "TERM", true); -    if (term) { -      data->proc.pty.term_name = term; -    } +    width = (uint16_t)tv_dict_get_number(job_opts, "width"); +    height = (uint16_t)tv_dict_get_number(job_opts, "height"); +    term_name = tv_dict_get_string(job_opts, "TERM", true);    } -  if (!rpc && on_stdout.type == kCallbackNone) { -    proc->out = NULL; -  } -  if (on_stderr.type == kCallbackNone) { -    proc->err = NULL; +  Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty, +                                    rpc, detach, cwd, width, height, term_name, +                                    &rettv->vval.v_number); +  if (chan) { +    channel_create_event(chan, NULL);    } -  common_job_start(data, rettv);  }  // "jobstop()" function @@ -11651,14 +11704,12 @@ static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)    } -  TerminalJobData *data = find_job(argvars[0].vval.v_number); +  Channel *data = find_job(argvars[0].vval.v_number, true);    if (!data) { -    EMSG(_(e_invjob));      return;    } -  process_stop((Process *)&data->proc); -  data->stopped = true; +  process_stop((Process *)&data->stream.proc);    rettv->vval.v_number = 1;  } @@ -11678,30 +11729,34 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr)      return;    } +    list_T *args = argvars[0].vval.v_list; -  list_T *rv = tv_list_alloc(); +  Channel **jobs = xcalloc(tv_list_len(args), sizeof(*jobs));    ui_busy_start();    MultiQueue *waiting_jobs = multiqueue_new_parent(loop_on_put, &main_loop);    // For each item in the input list append an integer to the output list. -3    // is used to represent an invalid job id, -2 is for a interrupted job and    // -1 for jobs that were skipped or timed out. -  for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { -    TerminalJobData *data = NULL; -    if (arg->li_tv.v_type != VAR_NUMBER -        || !(data = find_job(arg->li_tv.vval.v_number))) { -      tv_list_append_number(rv, -3); + +  int i = 0; +  TV_LIST_ITER_CONST(args, arg, { +    Channel *chan = NULL; +    if (TV_LIST_ITEM_TV(arg)->v_type != VAR_NUMBER +        || !(chan = find_job(TV_LIST_ITEM_TV(arg)->vval.v_number, false))) { +      jobs[i] = NULL;      } else { -      // append the list item and set the status pointer so we'll collect the -      // status code when the job exits -      tv_list_append_number(rv, -1); -      data->status_ptr = &rv->lv_last->li_tv.vval.v_number; -      // Process any pending events for the job because we'll temporarily -      // replace the parent queue -      multiqueue_process_events(data->events); -      multiqueue_replace_parent(data->events, waiting_jobs); +      jobs[i] = chan; +      channel_incref(chan); +      if (chan->stream.proc.status < 0) { +        // Process any pending events for the job because we'll temporarily +        // replace the parent queue +        multiqueue_process_events(chan->events); +        multiqueue_replace_parent(chan->events, waiting_jobs); +      }      } -  } +    i++; +  });    int remaining = -1;    uint64_t before = 0; @@ -11710,24 +11765,21 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr)      before = os_hrtime();    } -  for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { -    TerminalJobData *data = NULL; +  for (i = 0; i < tv_list_len(args); i++) {      if (remaining == 0) {        // timed out        break;      } -    if (arg->li_tv.v_type != VAR_NUMBER -        || !(data = find_job(arg->li_tv.vval.v_number))) { + +    // if the job already exited, but wasn't freed yet +    if (jobs[i] == NULL || jobs[i]->stream.proc.status >= 0) {        continue;      } -    int status = process_wait((Process *)&data->proc, remaining, waiting_jobs); + +    int status = process_wait(&jobs[i]->stream.proc, remaining, +                              waiting_jobs);      if (status < 0) {        // interrupted or timed out, skip remaining jobs. -      if (status == -2) { -        // set the status so the user can distinguish between interrupted and -        // skipped/timeout jobs. -        *data->status_ptr = -2; -      }        break;      }      if (remaining > 0) { @@ -11740,32 +11792,26 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr)      }    } -  for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { -    TerminalJobData *data = NULL; -    if (arg->li_tv.v_type != VAR_NUMBER -        || !(data = find_job(arg->li_tv.vval.v_number))) { -      continue; -    } -    // remove the status pointer because the list may be freed before the -    // job exits -    data->status_ptr = NULL; -  } +  list_T *const rv = tv_list_alloc(tv_list_len(args));    // restore the parent queue for any jobs still alive -  for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { -    TerminalJobData *data = NULL; -    if (arg->li_tv.v_type != VAR_NUMBER -        || !(data = pmap_get(uint64_t)(jobs, arg->li_tv.vval.v_number))) { +  for (i = 0; i < tv_list_len(args); i++) { +    if (jobs[i] == NULL) { +      tv_list_append_number(rv, -3);        continue;      }      // restore the parent queue for the job -    multiqueue_process_events(data->events); -    multiqueue_replace_parent(data->events, main_loop.events); +    multiqueue_process_events(jobs[i]->events); +    multiqueue_replace_parent(jobs[i]->events, main_loop.events); + +    tv_list_append_number(rv, jobs[i]->stream.proc.status); +    channel_decref(jobs[i]);    }    multiqueue_free(waiting_jobs); +  xfree(jobs);    ui_busy_stop(); -  rv->lv_refcount++; +  tv_list_ref(rv);    rettv->v_type = VAR_LIST;    rettv->vval.v_list = rv;  } @@ -11779,9 +11825,6 @@ static void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr)      EMSG(_(e_listreq));      return;    } -  if (argvars[0].vval.v_list == NULL) { -    return; -  }    const char *const sep = (argvars[1].v_type == VAR_UNKNOWN                             ? " "                             : tv_get_string_chk(&argvars[1])); @@ -12050,38 +12093,81 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)    mode = get_map_mode((char_u **)&which, 0); -  keys = replace_termcodes(keys, STRLEN(keys), &keys_buf, true, true, false, +  keys = replace_termcodes(keys, STRLEN(keys), &keys_buf, true, true, true,                             CPO_TO_CPO_FLAGS);    rhs = check_map(keys, mode, exact, false, abbr, &mp, &buffer_local);    xfree(keys_buf);    if (!get_dict) { -    /* Return a string. */ -    if (rhs != NULL) -      rettv->vval.v_string = str2special_save(rhs, FALSE); +    // Return a string. +    if (rhs != NULL) { +      rettv->vval.v_string = (char_u *)str2special_save( +          (const char *)rhs, false, false); +    }    } else {      tv_dict_alloc_ret(rettv);      if (rhs != NULL) {        // Return a dictionary. -      char_u *lhs = str2special_save(mp->m_keys, true); -      char *const mapmode = map_mode_to_chars(mp->m_mode); -      dict_T *dict = rettv->vval.v_dict; +      mapblock_fill_dict(rettv->vval.v_dict, mp, buffer_local, true); +    } +  } +} -      tv_dict_add_str(dict, S_LEN("lhs"), (const char *)lhs); -      tv_dict_add_str(dict, S_LEN("rhs"), (const char *)mp->m_orig_str); -      tv_dict_add_nr(dict, S_LEN("noremap"), mp->m_noremap ? 1 : 0); -      tv_dict_add_nr(dict, S_LEN("expr"),  mp->m_expr ? 1 : 0); -      tv_dict_add_nr(dict, S_LEN("silent"), mp->m_silent ? 1 : 0); -      tv_dict_add_nr(dict, S_LEN("sid"), (varnumber_T)mp->m_script_ID); -      tv_dict_add_nr(dict, S_LEN("buffer"), (varnumber_T)buffer_local); -      tv_dict_add_nr(dict, S_LEN("nowait"), mp->m_nowait ? 1 : 0); -      tv_dict_add_str(dict, S_LEN("mode"), mapmode); +/// luaeval() function implementation +static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +  FUNC_ATTR_NONNULL_ALL +{ +  const char *const str = (const char *)tv_get_string_chk(&argvars[0]); +  if (str == NULL) { +    return; +  } -      xfree(lhs); -      xfree(mapmode); -    } +  executor_eval_lua(cstr_as_string((char *)str), &argvars[1], rettv); +} + +/// Fill a dictionary with all applicable maparg() like dictionaries +/// +/// @param  dict  The dictionary to be filled +/// @param  mp  The maphash that contains the mapping information +/// @param  buffer_value  The "buffer" value +/// @param  compatible  True for compatible with old maparg() dict +void mapblock_fill_dict(dict_T *const dict, +                        const mapblock_T *const mp, +                        long buffer_value, +                        bool compatible) +  FUNC_ATTR_NONNULL_ALL +{ +  char *const lhs = str2special_save((const char *)mp->m_keys, +                                     compatible, !compatible); +  char *const mapmode = map_mode_to_chars(mp->m_mode); +  varnumber_T noremap_value; + +  if (compatible) { +    // Keep old compatible behavior +    // This is unable to determine whether a mapping is a <script> mapping +    noremap_value = !!mp->m_noremap; +  } else { +    // Distinguish between <script> mapping +    // If it's not a <script> mapping, check if it's a noremap +    noremap_value = mp->m_noremap == REMAP_SCRIPT ? 2 : !!mp->m_noremap; +  } + +  if (compatible) { +    tv_dict_add_str(dict, S_LEN("rhs"), (const char *)mp->m_orig_str); +  } else { +    tv_dict_add_allocated_str(dict, S_LEN("rhs"), +                              str2special_save((const char *)mp->m_str, false, +                                               true));    } +  tv_dict_add_allocated_str(dict, S_LEN("lhs"), lhs); +  tv_dict_add_nr(dict, S_LEN("noremap"), noremap_value); +  tv_dict_add_nr(dict, S_LEN("expr"),  mp->m_expr ? 1 : 0); +  tv_dict_add_nr(dict, S_LEN("silent"), mp->m_silent ? 1 : 0); +  tv_dict_add_nr(dict, S_LEN("sid"), (varnumber_T)mp->m_script_ID); +  tv_dict_add_nr(dict, S_LEN("buffer"), (varnumber_T)buffer_value); +  tv_dict_add_nr(dict, S_LEN("nowait"), mp->m_nowait ? 1 : 0); +  tv_dict_add_allocated_str(dict, S_LEN("mode"), mapmode);  }  /* @@ -12109,7 +12195,8 @@ static void f_mapcheck(typval_T *argvars, typval_T *rettv, FunPtr fptr)  } -static void find_some_match(typval_T *argvars, typval_T *rettv, int type) +static void find_some_match(typval_T *const argvars, typval_T *const rettv, +                            const SomeMatchType type)  {    char_u      *str = NULL;    long        len = 0; @@ -12130,25 +12217,38 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type)    p_cpo = (char_u *)"";    rettv->vval.v_number = -1; -  if (type == 3 || type == 4) { -    // type 3: return empty list when there are no matches. -    // type 4: return ["", -1, -1, -1] -    tv_list_alloc_ret(rettv); -    if (type == 4) { +  switch (type) { +    // matchlist(): return empty list when there are no matches. +    case kSomeMatchList: { +      tv_list_alloc_ret(rettv, kListLenMayKnow); +      break; +    } +    // matchstrpos(): return ["", -1, -1, -1] +    case kSomeMatchStrPos: { +      tv_list_alloc_ret(rettv, 4);        tv_list_append_string(rettv->vval.v_list, "", 0);        tv_list_append_number(rettv->vval.v_list, -1);        tv_list_append_number(rettv->vval.v_list, -1);        tv_list_append_number(rettv->vval.v_list, -1); +      break; +    } +    case kSomeMatchStr: { +      rettv->v_type = VAR_STRING; +      rettv->vval.v_string = NULL; +      break; +    } +    case kSomeMatch: +    case kSomeMatchEnd: { +      // Do nothing: zero is default. +      break;      } -  } else if (type == 2) { -    rettv->v_type = VAR_STRING; -    rettv->vval.v_string = NULL;    }    if (argvars[0].v_type == VAR_LIST) { -    if ((l = argvars[0].vval.v_list) == NULL) +    if ((l = argvars[0].vval.v_list) == NULL) {        goto theend; -    li = l->lv_first; +    } +    li = tv_list_first(l);    } else {      expr = str = (char_u *)tv_get_string(&argvars[0]);      len = (long)STRLEN(str); @@ -12168,11 +12268,11 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type)        goto theend;      }      if (l != NULL) { -      li = tv_list_find(l, start); -      if (li == NULL) { +      idx = tv_list_uidx(l, start); +      if (idx == -1) {          goto theend;        } -      idx = l->lv_idx;  // Use the cached index. +      li = tv_list_find(l, idx);      } else {        if (start < 0)          start = 0; @@ -12208,7 +12308,8 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type)            break;          }          xfree(tofree); -        tofree = expr = str = (char_u *)encode_tv2echo(&li->li_tv, NULL); +        tofree = expr = str = (char_u *)encode_tv2echo(TV_LIST_ITEM_TV(li), +                                                       NULL);          if (str == NULL) {            break;          } @@ -12223,8 +12324,8 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type)        /* Advance to just after the match. */        if (l != NULL) { -        li = li->li_next; -        ++idx; +        li = TV_LIST_ITEM_NEXT(l, li); +        idx++;        } else {          startcol = (colnr_T)(regmatch.startp[0]                               + (*mb_ptr2len)(regmatch.startp[0]) - str); @@ -12236,62 +12337,76 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type)      }      if (match) { -      if (type == 4) { -        listitem_T *li1 = rettv->vval.v_list->lv_first; -        listitem_T *li2 = li1->li_next; -        listitem_T *li3 = li2->li_next; -        listitem_T *li4 = li3->li_next; -        xfree(li1->li_tv.vval.v_string); - -        int rd = (int)(regmatch.endp[0] - regmatch.startp[0]); -        li1->li_tv.vval.v_string = vim_strnsave(regmatch.startp[0], rd); -        li3->li_tv.vval.v_number = (varnumber_T)(regmatch.startp[0] - expr); -        li4->li_tv.vval.v_number = (varnumber_T)(regmatch.endp[0] - expr); -        if (l != NULL) { -          li2->li_tv.vval.v_number = (varnumber_T)idx; +      switch (type) { +        case kSomeMatchStrPos: { +          list_T *const ret_l = rettv->vval.v_list; +          listitem_T *li1 = tv_list_first(ret_l); +          listitem_T *li2 = TV_LIST_ITEM_NEXT(ret_l, li1); +          listitem_T *li3 = TV_LIST_ITEM_NEXT(ret_l, li2); +          listitem_T *li4 = TV_LIST_ITEM_NEXT(ret_l, li3); +          xfree(TV_LIST_ITEM_TV(li1)->vval.v_string); + +          const size_t rd = (size_t)(regmatch.endp[0] - regmatch.startp[0]); +          TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz( +              (const char *)regmatch.startp[0], rd); +          TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)( +              regmatch.startp[0] - expr); +          TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)( +              regmatch.endp[0] - expr); +          if (l != NULL) { +            TV_LIST_ITEM_TV(li2)->vval.v_number = (varnumber_T)idx; +          } +          break;          } -      } else if (type == 3) { -        int i; - -        /* return list with matched string and submatches */ -        for (i = 0; i < NSUBEXP; ++i) { -          if (regmatch.endp[i] == NULL) { -            tv_list_append_string(rettv->vval.v_list, NULL, 0); -          } else { -            tv_list_append_string(rettv->vval.v_list, -                                  (const char *)regmatch.startp[i], -                                  (regmatch.endp[i] - regmatch.startp[i])); +        case kSomeMatchList: { +          // Return list with matched string and submatches. +          for (int i = 0; i < NSUBEXP; i++) { +            if (regmatch.endp[i] == NULL) { +              tv_list_append_string(rettv->vval.v_list, NULL, 0); +            } else { +              tv_list_append_string(rettv->vval.v_list, +                                    (const char *)regmatch.startp[i], +                                    (regmatch.endp[i] - regmatch.startp[i])); +            }            } +          break;          } -      } else if (type == 2) { -        // Return matched string. -        if (l != NULL) { -          tv_copy(&li->li_tv, rettv); -        } else { -          rettv->vval.v_string = (char_u *)xmemdupz( -              (const char *)regmatch.startp[0], -              (size_t)(regmatch.endp[0] - regmatch.startp[0])); +        case kSomeMatchStr: { +          // Return matched string. +          if (l != NULL) { +            tv_copy(TV_LIST_ITEM_TV(li), rettv); +          } else { +            rettv->vval.v_string = (char_u *)xmemdupz( +                (const char *)regmatch.startp[0], +                (size_t)(regmatch.endp[0] - regmatch.startp[0])); +          } +          break;          } -      } else if (l != NULL) { -        rettv->vval.v_number = idx; -      } else { -        if (type != 0) { -          rettv->vval.v_number = -            (varnumber_T)(regmatch.startp[0] - str); -        } else { -          rettv->vval.v_number = -            (varnumber_T)(regmatch.endp[0] - str); +        case kSomeMatch: +        case kSomeMatchEnd: { +          if (l != NULL) { +            rettv->vval.v_number = idx; +          } else { +            if (type == kSomeMatch) { +              rettv->vval.v_number = +                (varnumber_T)(regmatch.startp[0] - str); +            } else { +              rettv->vval.v_number = +                (varnumber_T)(regmatch.endp[0] - str); +            } +            rettv->vval.v_number += (varnumber_T)(str - expr); +          } +          break;          } -        rettv->vval.v_number += (varnumber_T)(str - expr);        }      }      vim_regfree(regmatch.regprog);    } -  if (type == 4 && l == NULL) { +  if (type == kSomeMatchStrPos && l == NULL) {      // matchstrpos() without a list: drop the second item -    tv_list_item_remove(rettv->vval.v_list, -                        rettv->vval.v_list->lv_first->li_next); +    list_T *const ret_l = rettv->vval.v_list; +    tv_list_item_remove(ret_l, TV_LIST_ITEM_NEXT(ret_l, tv_list_first(ret_l)));    }  theend: @@ -12304,7 +12419,7 @@ theend:   */  static void f_match(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { -  find_some_match(argvars, rettv, 1); +  find_some_match(argvars, rettv, kSomeMatch);  }  /* @@ -12347,7 +12462,7 @@ static void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr)      return;    }    if (id >= 1 && id <= 3) { -    EMSGN("E798: ID is reserved for \":match\": %" PRId64, id); +    EMSGN(_("E798: ID is reserved for \":match\": %" PRId64), id);      return;    } @@ -12357,56 +12472,56 @@ static void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr)  static void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { -    rettv->vval.v_number = -1; +  rettv->vval.v_number = -1; -    char buf[NUMBUFLEN]; -    const char *const group = tv_get_string_buf_chk(&argvars[0], buf); -    if (group == NULL) { -        return; -    } +  char buf[NUMBUFLEN]; +  const char *const group = tv_get_string_buf_chk(&argvars[0], buf); +  if (group == NULL) { +    return; +  } -    if (argvars[1].v_type != VAR_LIST) { -        EMSG2(_(e_listarg), "matchaddpos()"); -        return; -    } +  if (argvars[1].v_type != VAR_LIST) { +    EMSG2(_(e_listarg), "matchaddpos()"); +    return; +  } -    list_T *l; -    l = argvars[1].vval.v_list; -    if (l == NULL) { -        return; -    } +  list_T *l; +  l = argvars[1].vval.v_list; +  if (l == NULL) { +    return; +  } -    bool error = false; -    int prio = 10; -    int id = -1; -    const char *conceal_char = NULL; +  bool error = false; +  int prio = 10; +  int id = -1; +  const char *conceal_char = NULL; -    if (argvars[2].v_type != VAR_UNKNOWN) { -      prio = tv_get_number_chk(&argvars[2], &error); -      if (argvars[3].v_type != VAR_UNKNOWN) { -        id = tv_get_number_chk(&argvars[3], &error); -        if (argvars[4].v_type != VAR_UNKNOWN) { -          if (argvars[4].v_type != VAR_DICT) { -            EMSG(_(e_dictreq)); -            return; -          } -          dictitem_T *di; -          if ((di = tv_dict_find(argvars[4].vval.v_dict, S_LEN("conceal"))) -              != NULL) { -            conceal_char = tv_get_string(&di->di_tv); -          } +  if (argvars[2].v_type != VAR_UNKNOWN) { +    prio = tv_get_number_chk(&argvars[2], &error); +    if (argvars[3].v_type != VAR_UNKNOWN) { +      id = tv_get_number_chk(&argvars[3], &error); +      if (argvars[4].v_type != VAR_UNKNOWN) { +        if (argvars[4].v_type != VAR_DICT) { +          EMSG(_(e_dictreq)); +          return; +        } +        dictitem_T *di; +        if ((di = tv_dict_find(argvars[4].vval.v_dict, S_LEN("conceal"))) +            != NULL) { +          conceal_char = tv_get_string(&di->di_tv);          }        }      } -    if (error == true) { -        return; -    } +  } +  if (error == true) { +    return; +  } -    // id == 3 is ok because matchaddpos() is supposed to substitute :3match  -    if (id == 1 || id == 2) { -        EMSGN("E798: ID is reserved for \"match\": %" PRId64, id); -        return; -    } +  // id == 3 is ok because matchaddpos() is supposed to substitute :3match +  if (id == 1 || id == 2) { +    EMSGN(_("E798: ID is reserved for \"match\": %" PRId64), id); +    return; +  }    rettv->vval.v_number = match_add(curwin, group, NULL, prio, id, l,                                     conceal_char); @@ -12417,14 +12532,16 @@ static void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)   */  static void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { -  tv_list_alloc_ret(rettv); +  const int id = tv_get_number(&argvars[0]); -  int id = tv_get_number(&argvars[0]); +  tv_list_alloc_ret(rettv, (id >= 1 && id <= 3 +                            ? 2 +                            : 0));    if (id >= 1 && id <= 3) { -    matchitem_T *m; +    matchitem_T *const m = (matchitem_T *)get_match(curwin, id); -    if ((m = (matchitem_T *)get_match(curwin, id)) != NULL) { +    if (m != NULL) {        tv_list_append_string(rettv->vval.v_list,                              (const char *)syn_id2name(m->hlg_id), -1);        tv_list_append_string(rettv->vval.v_list, (const char *)m->pattern, -1); @@ -12449,7 +12566,7 @@ static void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr)   */  static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { -  find_some_match(argvars, rettv, 0); +  find_some_match(argvars, rettv, kSomeMatchEnd);  }  /* @@ -12457,7 +12574,7 @@ static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr)   */  static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { -  find_some_match(argvars, rettv, 3); +  find_some_match(argvars, rettv, kSomeMatchList);  }  /* @@ -12465,13 +12582,13 @@ static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)   */  static void f_matchstr(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { -  find_some_match(argvars, rettv, 2); +  find_some_match(argvars, rettv, kSomeMatchStr);  }  /// "matchstrpos()" function  static void f_matchstrpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { -  find_some_match(argvars, rettv, 4); +  find_some_match(argvars, rettv, kSomeMatchStrPos);  }  /// Get maximal/minimal number value in a list or dictionary @@ -12487,41 +12604,41 @@ static void max_min(const typval_T *const tv, typval_T *const rettv,                      const bool domax)    FUNC_ATTR_NONNULL_ALL  { -  varnumber_T n = 0;    bool error = false; +  rettv->vval.v_number = 0; +  varnumber_T n = (domax ? VARNUMBER_MIN : VARNUMBER_MAX);    if (tv->v_type == VAR_LIST) { -    const list_T *const l = tv->vval.v_list; -    if (tv_list_len(l) != 0) { -      n = tv_get_number_chk(&l->lv_first->li_tv, &error); -      for (const listitem_T *li = l->lv_first->li_next; li != NULL && !error; -           li = li->li_next) { -        const varnumber_T i = tv_get_number_chk(&li->li_tv, &error); -        if (domax ? i > n : i < n) { -          n = i; -        } -      } +    if (tv_list_len(tv->vval.v_list) == 0) { +      return;      } +    TV_LIST_ITER_CONST(tv->vval.v_list, li, { +      const varnumber_T i = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error); +      if (error) { +        return; +      } +      if (domax ? i > n : i < n) { +        n = i; +      } +    });    } else if (tv->v_type == VAR_DICT) { -    if (tv->vval.v_dict != NULL) { -      bool first = true; -      TV_DICT_ITER(tv->vval.v_dict, di, { -        const varnumber_T i = tv_get_number_chk(&di->di_tv, &error); -        if (error) { -          break; -        } -        if (first) { -          n = i; -          first = true; -        } else if (domax ? i > n : i < n) { -          n = i; -        } -      }); +    if (tv_dict_len(tv->vval.v_dict) == 0) { +      return;      } +    TV_DICT_ITER(tv->vval.v_dict, di, { +      const varnumber_T i = tv_get_number_chk(&di->di_tv, &error); +      if (error) { +        return; +      } +      if (domax ? i > n : i < n) { +        n = i; +      } +    });    } else {      EMSG2(_(e_listdictarg), domax ? "max()" : "min()"); +    return;    } -  rettv->vval.v_number = error ? 0 : n; +  rettv->vval.v_number = n;  }  /* @@ -12545,7 +12662,7 @@ static void f_min(typval_T *argvars, typval_T *rettv, FunPtr fptr)   */  static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { -  int prot = 0755; +  int prot = 0755;  // -V536    rettv->vval.v_number = FAIL;    if (check_restricted() || check_secure()) @@ -12583,59 +12700,18 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)    }  } -/* - * "mode()" function - */ +/// "mode()" function  static void f_mode(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { -  char_u buf[3]; - -  buf[1] = NUL; -  buf[2] = NUL; +  char *mode = get_mode(); -  if (VIsual_active) { -    if (VIsual_select) -      buf[0] = VIsual_mode + 's' - 'v'; -    else -      buf[0] = VIsual_mode; -  } else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE -             || State == CONFIRM) { -    buf[0] = 'r'; -    if (State == ASKMORE) -      buf[1] = 'm'; -    else if (State == CONFIRM) -      buf[1] = '?'; -  } else if (State == EXTERNCMD) -    buf[0] = '!'; -  else if (State & INSERT) { -    if (State & VREPLACE_FLAG) { -      buf[0] = 'R'; -      buf[1] = 'v'; -    } else if (State & REPLACE_FLAG) -      buf[0] = 'R'; -    else -      buf[0] = 'i'; -  } else if (State & CMDLINE) { -    buf[0] = 'c'; -    if (exmode_active) -      buf[1] = 'v'; -  } else if (exmode_active) { -    buf[0] = 'c'; -    buf[1] = 'e'; -  } else if (State & TERM_FOCUS) { -    buf[0] = 't'; -  } else { -    buf[0] = 'n'; -    if (finish_op) -      buf[1] = 'o'; +  // Clear out the minor mode when the argument is not a non-zero number or +  // non-empty string. +  if (!non_zero_arg(&argvars[0])) { +    mode[1] = NUL;    } -  /* Clear out the minor mode when the argument is not a non-zero number or -   * non-empty string.  */ -  if (!non_zero_arg(&argvars[0])) -    buf[1] = NUL; - -  rettv->vval.v_string = vim_strsave(buf); +  rettv->vval.v_string = (char_u *)mode;    rettv->v_type = VAR_STRING;  } @@ -12647,23 +12723,20 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr)      EMSG2(_(e_listarg), "msgpackdump()");      return;    } -  list_T *ret_list = tv_list_alloc_ret(rettv); -  const list_T *list = argvars[0].vval.v_list; -  if (list == NULL) { -    return; -  } +  list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow); +  list_T *const list = argvars[0].vval.v_list;    msgpack_packer *lpacker = msgpack_packer_new(ret_list, &encode_list_write);    const char *const msg = _("msgpackdump() argument, index %i");    // Assume that translation will not take more then 4 times more space    char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN];    int idx = 0; -  for (listitem_T *li = list->lv_first; li != NULL; li = li->li_next) { -    vim_snprintf(msgbuf, sizeof(msgbuf), (char *) msg, idx); +  TV_LIST_ITER(list, li, { +    vim_snprintf(msgbuf, sizeof(msgbuf), (char *)msg, idx);      idx++; -    if (encode_vim_to_msgpack(lpacker, &li->li_tv, msgbuf) == FAIL) { +    if (encode_vim_to_msgpack(lpacker, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) {        break;      } -  } +  });    msgpack_packer_free(lpacker);  } @@ -12675,12 +12748,12 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr)      EMSG2(_(e_listarg), "msgpackparse()");      return;    } -  list_T *ret_list = tv_list_alloc_ret(rettv); -  const list_T *list = argvars[0].vval.v_list; -  if (list == NULL || list->lv_first == NULL) { +  list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow); +  const list_T *const list = argvars[0].vval.v_list; +  if (tv_list_len(list) == 0) {      return;    } -  if (list->lv_first->li_tv.v_type != VAR_STRING) { +  if (TV_LIST_ITEM_TV(tv_list_first(list))->v_type != VAR_STRING) {      EMSG2(_(e_invarg2), "List item is not a string");      return;    } @@ -12720,13 +12793,12 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr)          goto f_msgpackparse_exit;        }        if (result == MSGPACK_UNPACK_SUCCESS) { -        listitem_T *li = tv_list_item_alloc(); -        li->li_tv.v_type = VAR_UNKNOWN; -        tv_list_append(ret_list, li); -        if (msgpack_to_vim(unpacked.data, &li->li_tv) == FAIL) { +        typval_T tv = { .v_type = VAR_UNKNOWN }; +        if (msgpack_to_vim(unpacked.data, &tv) == FAIL) {            EMSG2(_(e_invarg2), "Failed to convert msgpack string");            goto f_msgpackparse_exit;          } +        tv_list_append_owned_tv(ret_list, tv);        }        if (result == MSGPACK_UNPACK_CONTINUE) {          if (rlret == OK) { @@ -12770,25 +12842,32 @@ static void f_nextnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr)   */  static void f_nr2char(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { -  char_u buf[NUMBUFLEN]; - -  if (has_mbyte) { -    int utf8 = 0; - -    if (argvars[1].v_type != VAR_UNKNOWN) { -      utf8 = tv_get_number_chk(&argvars[1], NULL); -    } -    if (utf8) { -      buf[(*utf_char2bytes)((int)tv_get_number(&argvars[0]), buf)] = NUL; -    } else { -      buf[(*mb_char2bytes)((int)tv_get_number(&argvars[0]), buf)] = NUL; +  if (argvars[1].v_type != VAR_UNKNOWN) { +    if (!tv_check_num(&argvars[1])) { +      return;      } -  } else { -    buf[0] = (char_u)tv_get_number(&argvars[0]); -    buf[1] = NUL;    } + +  bool error = false; +  const varnumber_T num = tv_get_number_chk(&argvars[0], &error); +  if (error) { +    return; +  } +  if (num < 0) { +    emsgf(_("E5070: Character number must not be less than zero")); +    return; +  } +  if (num > INT_MAX) { +    emsgf(_("E5071: Character number must not be greater than INT_MAX (%i)"), +          INT_MAX); +    return; +  } + +  char buf[MB_MAXBYTES]; +  const int len = utf_char2bytes((int)num, (char_u *)buf); +    rettv->v_type = VAR_STRING; -  rettv->vval.v_string = vim_strsave(buf); +  rettv->vval.v_string = xmemdupz(buf, (size_t)len);  }  /* @@ -12903,7 +12982,7 @@ static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr)    varnumber_T start;    varnumber_T end;    varnumber_T stride = 1; -  long i; +  varnumber_T i;    bool error = false;    start = tv_get_number_chk(&argvars[0], &error); @@ -12925,7 +13004,7 @@ static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr)    } else if (stride > 0 ? end + 1 < start : end - 1 > start) {      emsgf(_("E727: Start past end"));    } else { -    tv_list_alloc_ret(rettv); +    tv_list_alloc_ret(rettv, (end - start) / stride);      for (i = start; stride > 0 ? i <= end : i >= end; i += stride) {        tv_list_append_number(rettv->vval.v_list, (varnumber_T)i);      } @@ -12946,9 +13025,6 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)    long prevlen  = 0;                    /* length of data in prev */    long prevsize = 0;                    /* size of prev buffer */    long maxline  = MAXLNUM; -  long cnt      = 0; -  char_u      *p;                       /* position in buf */ -  char_u      *start;                   /* start of current line */    if (argvars[1].v_type != VAR_UNKNOWN) {      if (strcmp(tv_get_string(&argvars[1]), "b") == 0) { @@ -12959,7 +13035,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)      }    } -  tv_list_alloc_ret(rettv); +  list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown);    // Always open the file in binary mode, library functions have a mind of    // their own about CR-LF conversion. @@ -12969,19 +13045,20 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)      return;    } -  while (cnt < maxline || maxline < 0) { +  while (maxline < 0 || tv_list_len(l) < maxline) {      readlen = (int)fread(buf, 1, io_size, fd); -    /* This for loop processes what was read, but is also entered at end -     * of file so that either: -     * - an incomplete line gets written -     * - a "binary" file gets an empty line at the end if it ends in a -     *   newline.  */ +    // This for loop processes what was read, but is also entered at end +    // of file so that either: +    // - an incomplete line gets written +    // - a "binary" file gets an empty line at the end if it ends in a +    //   newline. +    char_u *p;  // Position in buf. +    char_u *start;  // Start of current line.      for (p = buf, start = buf;           p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary)); -         ++p) { +         p++) {        if (*p == '\n' || readlen <= 0) { -        listitem_T  *li;          char_u      *s  = NULL;          size_t len = p - start; @@ -13008,22 +13085,32 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)            prevlen = prevsize = 0;          } -        li = tv_list_item_alloc(); -        li->li_tv.v_type = VAR_STRING; -        li->li_tv.v_lock = 0; -        li->li_tv.vval.v_string = s; -        tv_list_append(rettv->vval.v_list, li); - -        start = p + 1;         /* step over newline */ -        if ((++cnt >= maxline && maxline >= 0) || readlen <= 0) +        tv_list_append_owned_tv(l, (typval_T) { +          .v_type = VAR_STRING, +          .v_lock = VAR_UNLOCKED, +          .vval.v_string = s, +        }); + +        start = p + 1;  // Step over newline. +        if (maxline < 0) { +          if (tv_list_len(l) > -maxline) { +            assert(tv_list_len(l) == 1 + (-maxline)); +            tv_list_item_remove(l, tv_list_first(l)); +          } +        } else if (tv_list_len(l) >= maxline) { +          assert(tv_list_len(l) == maxline); +          break; +        } +        if (readlen <= 0) {            break; -      } else if (*p == NUL) +        } +      } else if (*p == NUL) {          *p = '\n'; -      /* Check for utf8 "bom"; U+FEFF is encoded as EF BB BF.  Do this -       * when finding the BF and check the previous two bytes. */ -      else if (*p == 0xbf && enc_utf8 && !binary) { -        /* Find the two bytes before the 0xbf.	If p is at buf, or buf -         * + 1, these may be in the "prev" string. */ +      // Check for utf8 "bom"; U+FEFF is encoded as EF BB BF.  Do this +      // when finding the BF and check the previous two bytes. +      } else if (*p == 0xbf && !binary) { +        // Find the two bytes before the 0xbf.  If p is at buf, or buf + 1, +        // these may be in the "prev" string.          char_u back1 = p >= buf + 1 ? p[-1]                         : prevlen >= 1 ? prev[prevlen - 1] : NUL;          char_u back2 = p >= buf + 2 ? p[-2] @@ -13042,8 +13129,9 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)              /* have to shuffle buf to close gap */              int adjust_prevlen = 0; -            if (dest < buf) { -              adjust_prevlen = (int)(buf - dest);               /* must be 1 or 2 */ +            if (dest < buf) {  // -V782 +              adjust_prevlen = (int)(buf - dest);  // -V782 +              // adjust_prevlen must be 1 or 2.                dest = buf;              }              if (readlen > p - buf + 1) @@ -13056,8 +13144,9 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)        }      }     /* for */ -    if ((cnt >= maxline && maxline >= 0) || readlen <= 0) +    if ((maxline >= 0 && tv_list_len(l) >= maxline) || readlen <= 0) {        break; +    }      if (start < p) {        /* There's part of a line in buf, store it in "prev". */        if (p - start + prevlen >= prevsize) { @@ -13081,16 +13170,6 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)      }    }   /* while */ -  /* -   * For a negative line count use only the lines at the end of the file, -   * free the rest. -   */ -  if (maxline < 0) -    while (cnt > -maxline) { -      tv_list_item_remove(rettv->vval.v_list, rettv->vval.v_list->lv_first); -      cnt--; -    } -    xfree(prev);    fclose(fd);  } @@ -13104,9 +13183,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)  /// @return OK In case of success, FAIL in case of error  static int list2proftime(typval_T *arg, proftime_T *tm) FUNC_ATTR_NONNULL_ALL  { -  if (arg->v_type != VAR_LIST -      || arg->vval.v_list == NULL -      || arg->vval.v_list->lv_len != 2) { +  if (arg->v_type != VAR_LIST || tv_list_len(arg->vval.v_list) != 2) {      return FAIL;    } @@ -13120,7 +13197,7 @@ static int list2proftime(typval_T *arg, proftime_T *tm) FUNC_ATTR_NONNULL_ALL    // in f_reltime() we split up the 64-bit proftime_T into two 32-bit    // values, now we combine them again.    union { -    struct { varnumber_T low, high; } split; +    struct { int32_t low, high; } split;      proftime_T prof;    } u = { .split.high = n1, .split.low = n2 }; @@ -13161,7 +13238,7 @@ static void f_reltime(typval_T *argvars, typval_T *rettv, FunPtr fptr)    // (varnumber_T is defined as int). For all our supported platforms, int's    // are at least 32-bits wide. So we'll use two 32-bit values to store it.    union { -    struct { varnumber_T low, high; } split; +    struct { int32_t low, high; } split;      proftime_T prof;    } u = { .prof = res }; @@ -13171,7 +13248,7 @@ static void f_reltime(typval_T *argvars, typval_T *rettv, FunPtr fptr)    STATIC_ASSERT(sizeof(u.prof) == sizeof(u) && sizeof(u.split) == sizeof(u),        "type punning will produce incorrect results on this platform"); -  tv_list_alloc_ret(rettv); +  tv_list_alloc_ret(rettv, 2);    tv_list_append_number(rettv->vval.v_list, u.split.high);    tv_list_append_number(rettv->vval.v_list, u.split.low);  } @@ -13205,21 +13282,20 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr)    long end;    dict_T      *d;    dictitem_T  *di; -  const char *const arg_errmsg = _("remove() argument"); -  const size_t arg_errmsg_len = strlen(arg_errmsg); +  const char *const arg_errmsg = N_("remove() argument");    if (argvars[0].v_type == VAR_DICT) {      if (argvars[2].v_type != VAR_UNKNOWN) {        EMSG2(_(e_toomanyarg), "remove()");      } else if ((d = argvars[0].vval.v_dict) != NULL -               && !tv_check_lock(d->dv_lock, arg_errmsg, arg_errmsg_len)) { +               && !tv_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE)) {        const char *key = tv_get_string_chk(&argvars[1]);        if (key != NULL) {          di = tv_dict_find(d, key, -1);          if (di == NULL) {            EMSG2(_(e_dictkey), key); -        } else if (!var_check_fixed(di->di_flags, arg_errmsg, arg_errmsg_len) -                   && !var_check_ro(di->di_flags, arg_errmsg, arg_errmsg_len)) { +        } else if (!var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE) +                   && !var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) {            *rettv = di->di_tv;            di->di_tv = TV_INITIAL_VALUE;            tv_dict_item_remove(d, di); @@ -13231,8 +13307,8 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr)      }    } else if (argvars[0].v_type != VAR_LIST) {      EMSG2(_(e_listdictarg), "remove()"); -  } else if ((l = argvars[0].vval.v_list) != NULL -             && !tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len)) { +  } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), +                            arg_errmsg, TV_TRANSLATE)) {      bool error = false;      idx = tv_get_number_chk(&argvars[1], &error); @@ -13243,8 +13319,8 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr)      } else {        if (argvars[2].v_type == VAR_UNKNOWN) {          // Remove one item, return its value. -        tv_list_remove_items(l, item, item); -        *rettv = item->li_tv; +        tv_list_drop_items(l, item, item); +        *rettv = *TV_LIST_ITEM_TV(item);          xfree(item);        } else {          // Remove range of items, return list with values. @@ -13256,21 +13332,17 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr)          } else {            int cnt = 0; -          for (li = item; li != NULL; li = li->li_next) { -            ++cnt; -            if (li == item2) +          for (li = item; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { +            cnt++; +            if (li == item2) {                break; +            }            }            if (li == NULL) {  // Didn't find "item2" after "item".              emsgf(_(e_invrange));            } else { -            tv_list_remove_items(l, item, item2); -            l = tv_list_alloc_ret(rettv); -            l->lv_first = item; -            l->lv_last = item2; -            item->li_prev = NULL; -            item2->li_next = NULL; -            l->lv_len = cnt; +            tv_list_move_items(l, item, item2, tv_list_alloc_ret(rettv, cnt), +                               cnt);            }          }        } @@ -13300,7 +13372,7 @@ static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr)  {    varnumber_T n = tv_get_number(&argvars[1]);    if (argvars[0].v_type == VAR_LIST) { -    tv_list_alloc_ret(rettv); +    tv_list_alloc_ret(rettv, (n > 0) * n * tv_list_len(argvars[0].vval.v_list));      while (n-- > 0) {        tv_list_extend(rettv->vval.v_list, argvars[0].vval.v_list, NULL);      } @@ -13415,7 +13487,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr)            q[-1] = NUL;            q = (char *)path_tail((char_u *)p);          } -        if (q > p && !path_is_absolute_path((const char_u *)buf)) { +        if (q > p && !path_is_absolute((const char_u *)buf)) {            // Symlink is relative to directory of argument. Replace the            // symlink with the resolved name in the same directory.            const size_t p_len = strlen(p); @@ -13503,26 +13575,15 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr)   */  static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { -  const char *const arg_errmsg = _("reverse() argument"); -  const size_t arg_errmsg_len = strlen(arg_errmsg); -    list_T *l;    if (argvars[0].v_type != VAR_LIST) {      EMSG2(_(e_listarg), "reverse()"); -  } else if ((l = argvars[0].vval.v_list) != NULL -             && !tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len)) { -    listitem_T *li = l->lv_last; -    l->lv_first = l->lv_last = NULL; -    l->lv_len = 0; -    while (li != NULL) { -      listitem_T *const ni = li->li_prev; -      tv_list_append(l, li); -      li = ni; -    } +  } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), +                            N_("reverse() argument"), TV_TRANSLATE)) { +    tv_list_reverse(l);      rettv->vval.v_list = l;      rettv->v_type = VAR_LIST; -    l->lv_refcount++; -    l->lv_idx = l->lv_len - l->lv_idx - 1; +    tv_list_ref(l);    }  } @@ -13705,9 +13766,8 @@ static void f_rpcnotify(typval_T *argvars, typval_T *rettv, FunPtr fptr)      ADD(args, vim_to_object(tv));    } -  if (!channel_send_event((uint64_t)argvars[0].vval.v_number, -                          tv_get_string(&argvars[1]), -                          args)) { +  if (!rpc_send_event((uint64_t)argvars[0].vval.v_number, +                      tv_get_string(&argvars[1]), args)) {      EMSG2(_(e_invarg2), "Channel doesn't exist");      return;    } @@ -13745,7 +13805,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr)    scid_T save_current_SID;    uint8_t *save_sourcing_name, *save_autocmd_fname, *save_autocmd_match;    linenr_T save_sourcing_lnum; -  int save_autocmd_fname_full, save_autocmd_bufnr; +  int save_autocmd_bufnr;    void *save_funccalp;    if (l_provider_call_nesting) { @@ -13756,26 +13816,22 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr)      save_sourcing_lnum = sourcing_lnum;      save_autocmd_fname = autocmd_fname;      save_autocmd_match = autocmd_match; -    save_autocmd_fname_full = autocmd_fname_full;      save_autocmd_bufnr = autocmd_bufnr;      save_funccalp = save_funccal(); -    // +      current_SID = provider_caller_scope.SID;      sourcing_name = provider_caller_scope.sourcing_name;      sourcing_lnum = provider_caller_scope.sourcing_lnum;      autocmd_fname = provider_caller_scope.autocmd_fname;      autocmd_match = provider_caller_scope.autocmd_match; -    autocmd_fname_full = provider_caller_scope.autocmd_fname_full;      autocmd_bufnr = provider_caller_scope.autocmd_bufnr;      restore_funccal(provider_caller_scope.funccalp);    }    Error err = ERROR_INIT; -  Object result = channel_send_call((uint64_t)argvars[0].vval.v_number, -                                    tv_get_string(&argvars[1]), -                                    args, -                                    &err); +  Object result = rpc_send_call((uint64_t)argvars[0].vval.v_number, +                                tv_get_string(&argvars[1]), args, &err);    if (l_provider_call_nesting) {      current_SID = save_current_SID; @@ -13783,12 +13839,11 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr)      sourcing_lnum = save_sourcing_lnum;      autocmd_fname = save_autocmd_fname;      autocmd_match = save_autocmd_match; -    autocmd_fname_full = save_autocmd_fname_full;      autocmd_bufnr = save_autocmd_bufnr;      restore_funccal(save_funccalp);    } -  if (err.set) { +  if (ERROR_SET(&err)) {      nvim_err_writeln(cstr_as_string(err.msg));      goto end;    } @@ -13799,6 +13854,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr)  end:    api_free_object(result); +  api_clear_error(&err);  }  // "rpcstart()" function (DEPRECATED) @@ -13822,14 +13878,17 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)    int argsl = 0;    if (argvars[1].v_type == VAR_LIST) {      args = argvars[1].vval.v_list; -    argsl = args->lv_len; +    argsl = tv_list_len(args);      // Assert that all list items are strings -    for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { -      if (arg->li_tv.v_type != VAR_STRING) { -        EMSG(_(e_invarg)); +    int i = 0; +    TV_LIST_ITER_CONST(args, arg, { +      if (TV_LIST_ITEM_TV(arg)->v_type != VAR_STRING) { +        emsgf(_("E5010: List item %d of the second argument is not a string"), +              i);          return;        } -    } +      i++; +    });    }    if (argvars[0].vval.v_string == NULL || argvars[0].vval.v_string[0] == NUL) { @@ -13847,18 +13906,21 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)    int i = 1;    // Copy arguments to the vector    if (argsl > 0) { -    for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { -      argv[i++] = xstrdup(tv_get_string(&arg->li_tv)); -    } +    TV_LIST_ITER_CONST(args, arg, { +      argv[i++] = xstrdup(tv_get_string(TV_LIST_ITEM_TV(arg))); +    });    }    // The last item of argv must be NULL    argv[i] = NULL; -  TerminalJobData *data = common_job_init(argv, CALLBACK_NONE, CALLBACK_NONE, -                                          CALLBACK_NONE, false, true, false, -                                          NULL); -  common_job_start(data, rettv); +  Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT, +                                    CALLBACK_READER_INIT, CALLBACK_NONE, +                                    false, true, false, NULL, 0, 0, NULL, +                                    &rettv->vval.v_number); +  if (chan) { +    channel_create_event(chan, NULL); +  }  }  // "rpcstop()" function @@ -13878,10 +13940,16 @@ static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)    }    // if called with a job, stop it, else closes the channel -  if (pmap_get(uint64_t)(jobs, argvars[0].vval.v_number)) { +  uint64_t id = argvars[0].vval.v_number; +  if (find_job(id, false)) {      f_jobstop(argvars, rettv, NULL);    } else { -    rettv->vval.v_number = channel_close(argvars[0].vval.v_number); +    const char *error; +    rettv->vval.v_number = channel_close(argvars[0].vval.v_number, +                                         kChannelPartRpc, &error); +    if (!rettv->vval.v_number) { +      EMSG(error); +    }    }  } @@ -14071,7 +14139,7 @@ static void f_searchpairpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)    int lnum = 0;    int col = 0; -  tv_list_alloc_ret(rettv); +  tv_list_alloc_ret(rettv, 2);    if (searchpair_cmn(argvars, &match_pos) > 0) {      lnum = match_pos.lnum; @@ -14113,6 +14181,8 @@ do_searchpair (    int nest = 1;    int options = SEARCH_KEEP;    proftime_T tm; +  size_t pat2_len; +  size_t pat3_len;    /* Make 'cpoptions' empty, the 'l' flag should not be used here. */    save_cpo = p_cpo; @@ -14121,18 +14191,22 @@ do_searchpair (    /* Set the time limit, if there is one. */    tm = profile_setlimit(time_limit); -  /* Make two search patterns: start/end (pat2, for in nested pairs) and -   * start/middle/end (pat3, for the top pair). */ -  pat2 = xmalloc(STRLEN(spat) + STRLEN(epat) + 15); -  pat3 = xmalloc(STRLEN(spat) + STRLEN(mpat) + STRLEN(epat) + 23); -  sprintf((char *)pat2, "\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat); -  if (*mpat == NUL) +  // Make two search patterns: start/end (pat2, for in nested pairs) and +  // start/middle/end (pat3, for the top pair). +  pat2_len = STRLEN(spat) + STRLEN(epat) + 17; +  pat2 = xmalloc(pat2_len); +  pat3_len = STRLEN(spat) + STRLEN(mpat) + STRLEN(epat) + 25; +  pat3 = xmalloc(pat3_len); +  snprintf((char *)pat2, pat2_len, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat); +  if (*mpat == NUL) {      STRCPY(pat3, pat2); -  else -    sprintf((char *)pat3, "\\(%s\\m\\)\\|\\(%s\\m\\)\\|\\(%s\\m\\)", -        spat, epat, mpat); -  if (flags & SP_START) +  } else { +    snprintf((char *)pat3, pat3_len, +             "\\m\\(%s\\m\\)\\|\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat, mpat); +  } +  if (flags & SP_START) {      options |= SEARCH_START; +  }    save_cursor = curwin->w_cursor;    pos = curwin->w_cursor; @@ -14233,18 +14307,14 @@ do_searchpair (  static void f_searchpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)  {    pos_T match_pos; -  int lnum = 0; -  int col = 0; -  int n;    int flags = 0; -  tv_list_alloc_ret(rettv); +  const int n = search_cmn(argvars, &match_pos, &flags); -  n = search_cmn(argvars, &match_pos, &flags); -  if (n > 0) { -    lnum = match_pos.lnum; -    col = match_pos.col; -  } +  tv_list_alloc_ret(rettv, 2 + (!!(flags & SP_SUBPAT))); + +  const int lnum = (n > 0 ? match_pos.lnum : 0); +  const int col = (n > 0 ? match_pos.col : 0);    tv_list_append_number(rettv->vval.v_list, (varnumber_T)lnum);    tv_list_append_number(rettv->vval.v_list, (varnumber_T)col); @@ -14260,13 +14330,9 @@ static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)    char **addrs = server_address_list(&n);    // Copy addrs into a linked list. -  list_T *l = tv_list_alloc_ret(rettv); +  list_T *const l = tv_list_alloc_ret(rettv, n);    for (size_t i = 0; i < n; i++) { -    listitem_T *li = tv_list_item_alloc(); -    li->li_tv.v_type = VAR_STRING; -    li->li_tv.v_lock = 0; -    li->li_tv.vval.v_string = (char_u *)addrs[i]; -    tv_list_append(l, li); +    tv_list_append_allocated_string(l, addrs[i]);    }    xfree(addrs);  } @@ -14281,22 +14347,39 @@ static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)      return;    } +  char *address;    // If the user supplied an address, use it, otherwise use a temp.    if (argvars[0].v_type != VAR_UNKNOWN) {      if (argvars[0].v_type != VAR_STRING) {        EMSG(_(e_invarg));        return;      } else { -      rettv->vval.v_string = (char_u *)xstrdup(tv_get_string(argvars)); +      address = xstrdup(tv_get_string(argvars));      }    } else { -    rettv->vval.v_string = (char_u *)server_address_new(); +    address = server_address_new();    } -  int result = server_start((char *) rettv->vval.v_string); +  int result = server_start(address); +  xfree(address); +    if (result != 0) { -    EMSG2("Failed to start server: %s", uv_strerror(result)); +    EMSG2("Failed to start server: %s", +          result > 0 ? "Unknown system error" : uv_strerror(result)); +    return;    } + +  // Since it's possible server_start adjusted the given {address} (e.g., +  // "localhost:" will now have a port), return the final value to the user. +  size_t n; +  char **addrs = server_address_list(&n); +  rettv->vval.v_string = (char_u *)addrs[n - 1]; + +  n--; +  for (size_t i = 0; i < n; i++) { +    xfree(addrs[i]); +  } +  xfree(addrs);  }  /// "serverstop()" function @@ -14306,13 +14389,16 @@ static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)      return;    } -  if (argvars[0].v_type == VAR_UNKNOWN || argvars[0].v_type != VAR_STRING) { +  if (argvars[0].v_type != VAR_STRING) {      EMSG(_(e_invarg));      return;    } +  rettv->v_type = VAR_NUMBER; +  rettv->vval.v_number = 0;    if (argvars[0].vval.v_string) { -    server_stop((char *) argvars[0].vval.v_string); +    bool rv = server_stop((char *)argvars[0].vval.v_string); +    rettv->vval.v_number = (rv ? 1 : 0);    }  } @@ -14330,7 +14416,7 @@ static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)    buf_T *const buf = get_buf_tv(&argvars[0], false);    typval_T *varp = &argvars[2]; -  if (buf != NULL && varname != NULL && varp != NULL) { +  if (buf != NULL && varname != NULL) {      if (*varname == '&') {        long numval;        bool error = false; @@ -14457,25 +14543,26 @@ static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr)    const char *line = NULL;    if (argvars[1].v_type == VAR_LIST) {      l = argvars[1].vval.v_list; -    li = l->lv_first; +    li = tv_list_first(l);    } else {      line = tv_get_string_chk(&argvars[1]);    } -  /* default result is zero == OK */ +  // Default result is zero == OK.    for (;; ) { -    if (l != NULL) { +    if (argvars[1].v_type == VAR_LIST) {        // List argument, get next string.        if (li == NULL) {          break;        } -      line = tv_get_string_chk(&li->li_tv); -      li = li->li_next; +      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) +    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. */ @@ -14547,7 +14634,8 @@ static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv)      return;    }    const char *const act = tv_get_string_chk(action_arg); -  if ((*act == 'a' || *act == 'r' || *act == ' ') && act[1] == NUL) { +  if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f') +      && act[1] == NUL) {      action = *act;    } else {      EMSG2(_(e_invact), act); @@ -14576,8 +14664,8 @@ skip_args:      title = (wp ? "setloclist()" : "setqflist()");    } -  list_T *l = list_arg->vval.v_list; -  if (l && set_errorlist(wp, l, action, (char_u *)title, d) == OK) { +  list_T *const l = list_arg->vval.v_list; +  if (set_errorlist(wp, l, action, (char_u *)title, d) == OK) {      rettv->vval.v_number = 0;    }  } @@ -14602,8 +14690,6 @@ static void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr)   */  static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { -  list_T      *l; -  listitem_T  *li;    dict_T      *d;    list_T      *s = NULL; @@ -14612,95 +14698,89 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)      EMSG(_(e_listreq));      return;    } -  if ((l = argvars[0].vval.v_list) != NULL) { - -    /* To some extent make sure that we are dealing with a list from -     * "getmatches()". */ -    li = l->lv_first; -    while (li != NULL) { -      if (li->li_tv.v_type != VAR_DICT -          || (d = li->li_tv.vval.v_dict) == NULL) { -        EMSG(_(e_invarg)); -        return; -      } -      if (!(tv_dict_find(d, S_LEN("group")) != NULL -            && (tv_dict_find(d, S_LEN("pattern")) != NULL -                || tv_dict_find(d, S_LEN("pos1")) != NULL) -            && tv_dict_find(d, S_LEN("priority")) != NULL -            && tv_dict_find(d, S_LEN("id")) != NULL)) { -        EMSG(_(e_invarg)); -        return; -      } -      li = li->li_next; +  list_T *const l = argvars[0].vval.v_list; +  // To some extent make sure that we are dealing with a list from +  // "getmatches()". +  int i = 0; +  TV_LIST_ITER_CONST(l, li, { +    if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT +        || (d = TV_LIST_ITEM_TV(li)->vval.v_dict) == NULL) { +      emsgf(_("E474: List item %d is either not a dictionary " +              "or an empty one"), i); +      return;      } +    if (!(tv_dict_find(d, S_LEN("group")) != NULL +          && (tv_dict_find(d, S_LEN("pattern")) != NULL +              || tv_dict_find(d, S_LEN("pos1")) != NULL) +          && tv_dict_find(d, S_LEN("priority")) != NULL +          && tv_dict_find(d, S_LEN("id")) != NULL)) { +      emsgf(_("E474: List item %d is missing one of the required keys"), i); +      return; +    } +    i++; +  }); + +  clear_matches(curwin); +  bool match_add_failed = false; +  TV_LIST_ITER_CONST(l, li, { +    int i = 0; -    clear_matches(curwin); -    li = l->lv_first; -    bool match_add_failed = false; -    while (li != NULL) { -      int i = 0; +    d = TV_LIST_ITEM_TV(li)->vval.v_dict; +    dictitem_T *const di = tv_dict_find(d, S_LEN("pattern")); +    if (di == NULL) { +      if (s == NULL) { +        s = tv_list_alloc(9); +      } -      d = li->li_tv.vval.v_dict; -      dictitem_T *const di = tv_dict_find(d, S_LEN("pattern")); -      if (di == NULL) { -        if (s == NULL) { -          s = tv_list_alloc(); -          if (s == NULL) { +      // match from matchaddpos() +      for (i = 1; i < 9; i++) { +        char buf[5]; +        snprintf(buf, sizeof(buf), "pos%d", i); +        dictitem_T *const pos_di = tv_dict_find(d, buf, -1); +        if (pos_di != NULL) { +          if (pos_di->di_tv.v_type != VAR_LIST) {              return;            } -        } - -        // match from matchaddpos() -        for (i = 1; i < 9; i++) { -          char buf[5]; -          snprintf(buf, sizeof(buf), "pos%d", i); -          dictitem_T *const pos_di = tv_dict_find(d, buf, -1); -          if (pos_di != NULL) { -            if (pos_di->di_tv.v_type != VAR_LIST) { -              return; -            } -            tv_list_append_tv(s, &pos_di->di_tv); -            s->lv_refcount++; -          } else { -            break; -          } +          tv_list_append_tv(s, &pos_di->di_tv); +          tv_list_ref(s); +        } else { +          break;          }        } +    } -      // Note: there are three number buffers involved: -      // - group_buf below. -      // - numbuf in tv_dict_get_string(). -      // - mybuf in tv_get_string(). -      // -      // If you change this code make sure that buffers will not get -      // accidentally reused. -      char group_buf[NUMBUFLEN]; -      const char *const group = tv_dict_get_string_buf(d, "group", group_buf); -      const int priority = (int)tv_dict_get_number(d, "priority"); -      const int id = (int)tv_dict_get_number(d, "id"); -      dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal")); -      const char *const conceal = (conceal_di != NULL -                                   ? tv_get_string(&conceal_di->di_tv) -                                   : NULL); -      if (i == 0) { -        if (match_add(curwin, group, -                      tv_dict_get_string(d, "pattern", false), -                      priority, id, NULL, conceal) != id) { -          match_add_failed = true; -        } -      } else { -        if (match_add(curwin, group, NULL, priority, id, s, conceal) != id) { -          match_add_failed = true; -        } -        tv_list_unref(s); -        s = NULL; +    // Note: there are three number buffers involved: +    // - group_buf below. +    // - numbuf in tv_dict_get_string(). +    // - mybuf in tv_get_string(). +    // +    // If you change this code make sure that buffers will not get +    // accidentally reused. +    char group_buf[NUMBUFLEN]; +    const char *const group = tv_dict_get_string_buf(d, "group", group_buf); +    const int priority = (int)tv_dict_get_number(d, "priority"); +    const int id = (int)tv_dict_get_number(d, "id"); +    dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal")); +    const char *const conceal = (conceal_di != NULL +                                 ? tv_get_string(&conceal_di->di_tv) +                                 : NULL); +    if (i == 0) { +      if (match_add(curwin, group, +                    tv_dict_get_string(d, "pattern", false), +                    priority, id, NULL, conceal) != id) { +        match_add_failed = true;        } -      li = li->li_next; -    } -    if (!match_add_failed) { -      rettv->vval.v_number = 0; +    } else { +      if (match_add(curwin, group, NULL, priority, id, s, conceal) != id) { +        match_add_failed = true; +      } +      tv_list_unref(s); +      s = NULL;      } +  }); +  if (!match_add_failed) { +    rettv->vval.v_number = 0;    }  } @@ -14777,6 +14857,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr)      regname = '"';    } +  bool set_unnamed = false;    if (argvars[2].v_type != VAR_UNKNOWN) {      const char *stropt = tv_get_string_chk(&argvars[2]);      if (stropt == NULL) { @@ -14805,6 +14886,10 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr)            }            break;          } +        case 'u': case '"': {  // unnamed register +          set_unnamed = true; +          break; +        }        }      }    } @@ -14812,7 +14897,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr)    if (argvars[1].v_type == VAR_LIST) {      list_T *ll = argvars[1].vval.v_list;      // If the list is NULL handle like an empty list. -    int len = ll == NULL ? 0 : ll->lv_len; +    const int len = tv_list_len(ll);      // First half: use for pointers to result lines; second half: use for      // pointers to allocated copies. @@ -14821,11 +14906,9 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr)      char **allocval = lstval + len + 2;      char **curallocval = allocval; -    for (listitem_T *li = ll == NULL ? NULL : ll->lv_first; -         li != NULL; -         li = li->li_next) { +    TV_LIST_ITER_CONST(ll, li, {        char buf[NUMBUFLEN]; -      *curval = tv_get_string_buf_chk(&li->li_tv, buf); +      *curval = tv_get_string_buf_chk(TV_LIST_ITEM_TV(li), buf);        if (*curval == NULL) {          goto free_lstval;        } @@ -14837,7 +14920,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr)          curallocval++;        }        curval++; -    } +    });      *curval++ = NULL;      write_reg_contents_lst(regname, (char_u **)lstval, append, yank_type, @@ -14857,6 +14940,11 @@ free_lstval:                            append, yank_type, block_len);    }    rettv->vval.v_number = 0; + +  if (set_unnamed) { +    // Discard the result. We already handle the error case. +    if (op_register_set_previous(regname)) { } +  }  }  /* @@ -14874,7 +14962,7 @@ static void f_settabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)    const char *const varname = tv_get_string_chk(&argvars[1]);    typval_T *const varp = &argvars[2]; -  if (varname != NULL && varp != NULL && tp != NULL) { +  if (varname != NULL && tp != NULL) {      tabpage_T *const save_curtab = curtab;      goto_tabpage_tp(tp, false, false); @@ -15001,11 +15089,57 @@ static void f_simplify(typval_T *argvars, typval_T *rettv, FunPtr fptr)    rettv->v_type = VAR_STRING;  } -/// struct used in the array that's given to qsort() -typedef struct { -  listitem_T *item; -  int idx; -} sortItem_T; +/// "sockconnect()" function +static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ +  if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) { +    EMSG(_(e_invarg)); +    return; +  } +  if (argvars[2].v_type != VAR_DICT && argvars[2].v_type != VAR_UNKNOWN) { +    // Wrong argument types +    EMSG2(_(e_invarg2), "expected dictionary"); +    return; +  } + +  const char *mode = tv_get_string(&argvars[0]); +  const char *address = tv_get_string(&argvars[1]); + +  bool tcp; +  if (strcmp(mode, "tcp") == 0) { +    tcp = true; +  } else if (strcmp(mode, "pipe") == 0) { +    tcp = false; +  } else { +    EMSG2(_(e_invarg2), "invalid mode"); +    return; +  } + +  bool rpc = false; +  CallbackReader on_data = CALLBACK_READER_INIT; +  if (argvars[2].v_type == VAR_DICT) { +    dict_T *opts = argvars[2].vval.v_dict; +    rpc = tv_dict_get_number(opts, "rpc") != 0; + +    if (!tv_dict_get_callback(opts, S_LEN("on_data"), &on_data.cb)) { +      return; +    } +    on_data.buffered = tv_dict_get_number(opts, "data_buffered"); +    if (on_data.buffered && on_data.cb.type == kCallbackNone) { +      on_data.self = opts; +    } +  } + +  const char *error = NULL; +  uint64_t id = channel_connect(tcp, address, rpc, on_data, 50, &error); + +  if (error) { +    EMSG2(_("connection failed: %s"), error); +  } + +  rettv->vval.v_number = (varnumber_T)id; +  rettv->v_type = VAR_NUMBER; +}  /// struct storing information about current sort  typedef struct { @@ -15027,17 +15161,17 @@ static sortinfo_T *sortinfo = NULL;   */  static int item_compare(const void *s1, const void *s2, bool keep_zero)  { -  sortItem_T *const si1 = (sortItem_T *)s1; -  sortItem_T *const si2 = (sortItem_T *)s2; +  ListSortItem *const si1 = (ListSortItem *)s1; +  ListSortItem *const si2 = (ListSortItem *)s2; -  typval_T *const tv1 = &si1->item->li_tv; -  typval_T *const tv2 = &si2->item->li_tv; +  typval_T *const tv1 = TV_LIST_ITEM_TV(si1->item); +  typval_T *const tv2 = TV_LIST_ITEM_TV(si2->item);    int res;    if (sortinfo->item_compare_numbers) { -    const long v1 = tv_get_number(tv1); -    const long v2 = tv_get_number(tv2); +    const varnumber_T v1 = tv_get_number(tv1); +    const varnumber_T v2 = tv_get_number(tv2);      res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1;      goto item_compare_end; @@ -15103,6 +15237,8 @@ item_compare_end:    // When the result would be zero, compare the item indexes.  Makes the    // sort stable.    if (res == 0 && !keep_zero) { +    // WARNING: When using uniq si1 and si2 are actually listitem_T **, no +    // indexes are there.      res = si1->idx > si2->idx ? 1 : -1;    }    return res; @@ -15120,7 +15256,7 @@ static int item_compare_not_keeping_zero(const void *s1, const void *s2)  static int item_compare2(const void *s1, const void *s2, bool keep_zero)  { -  sortItem_T  *si1, *si2; +  ListSortItem *si1, *si2;    int res;    typval_T rettv;    typval_T argv[3]; @@ -15133,8 +15269,8 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero)      return 0;    } -  si1 = (sortItem_T *)s1; -  si2 = (sortItem_T *)s2; +  si1 = (ListSortItem *)s1; +  si2 = (ListSortItem *)s2;    if (partial == NULL) {      func_name = sortinfo->item_compare_func; @@ -15144,8 +15280,8 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero)    // Copy the values.  This is needed to be able to set v_lock to VAR_FIXED    // in the copy without changing the original list items. -  tv_copy(&si1->item->li_tv, &argv[0]); -  tv_copy(&si2->item->li_tv, &argv[1]); +  tv_copy(TV_LIST_ITEM_TV(si1->item), &argv[0]); +  tv_copy(TV_LIST_ITEM_TV(si2->item), &argv[1]);    rettv.v_type = VAR_UNKNOWN;  // tv_clear() uses this    res = call_func((const char_u *)func_name, @@ -15168,6 +15304,8 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero)    // When the result would be zero, compare the pointers themselves.  Makes    // the sort stable.    if (res == 0 && !keep_zero) { +    // WARNING: When using uniq si1 and si2 are actually listitem_T **, no +    // indexes are there.      res = si1->idx > si2->idx ? 1 : -1;    } @@ -15189,9 +15327,7 @@ static int item_compare2_not_keeping_zero(const void *s1, const void *s2)   */  static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort)  { -  list_T      *l; -  listitem_T  *li; -  sortItem_T  *ptrs; +  ListSortItem  *ptrs;    long len;    long i; @@ -15202,21 +15338,19 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort)    sortinfo = &info;    const char *const arg_errmsg = (sort -                                  ? _("sort() argument") -                                  : _("uniq() argument")); -  const size_t arg_errmsg_len = strlen(arg_errmsg); +                                  ? N_("sort() argument") +                                  : N_("uniq() argument"));    if (argvars[0].v_type != VAR_LIST) {      EMSG2(_(e_listarg), sort ? "sort()" : "uniq()");    } else { -    l = argvars[0].vval.v_list; -    if (l == NULL -        || tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len)) { -        goto theend; +    list_T *const l = argvars[0].vval.v_list; +    if (tv_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) { +      goto theend;      }      rettv->vval.v_list = l;      rettv->v_type = VAR_LIST; -    ++l->lv_refcount; +    tv_list_ref(l);      len = tv_list_len(l);      if (len <= 1) { @@ -15282,80 +15416,45 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort)        }      } -    /* Make an array with each entry pointing to an item in the List. */ -    ptrs = xmalloc((size_t)(len * sizeof (sortItem_T))); +    // Make an array with each entry pointing to an item in the List. +    ptrs = xmalloc((size_t)(len * sizeof(ListSortItem))); -    i = 0;      if (sort) { -      // sort(): ptrs will be the list to sort. -      for (li = l->lv_first; li != NULL; li = li->li_next) { -        ptrs[i].item = li; -        ptrs[i].idx = i; -        i++; -      } -        info.item_compare_func_err = false; -      // Test the compare function. -      if ((info.item_compare_func != NULL -           || info.item_compare_partial != NULL) -          && item_compare2_not_keeping_zero(&ptrs[0], &ptrs[1]) -             == ITEM_COMPARE_FAIL) { +      tv_list_item_sort(l, ptrs, +                        ((info.item_compare_func == NULL +                          && info.item_compare_partial == NULL) +                         ? item_compare_not_keeping_zero +                         : item_compare2_not_keeping_zero), +                        &info.item_compare_func_err); +      if (info.item_compare_func_err) {          EMSG(_("E702: Sort compare function failed")); -      } else { -        // Sort the array with item pointers. -        qsort(ptrs, (size_t)len, sizeof (sortItem_T), -              (info.item_compare_func == NULL -               && info.item_compare_partial == NULL ? -               item_compare_not_keeping_zero : -               item_compare2_not_keeping_zero)); - -        if (!info.item_compare_func_err) { -          // Clear the list and append the items in the sorted order. -          l->lv_first    = NULL; -          l->lv_last     = NULL; -          l->lv_idx_item = NULL; -          l->lv_len      = 0; - -          for (i = 0; i < len; i++) { -            tv_list_append(l, ptrs[i].item); -          } -        }        }      } else { -      int (*item_compare_func_ptr)(const void *, const void *); +      ListSorter item_compare_func_ptr;        // f_uniq(): ptrs will be a stack of items to remove.        info.item_compare_func_err = false;        if (info.item_compare_func != NULL            || info.item_compare_partial != NULL) { -          item_compare_func_ptr = item_compare2_keeping_zero; +        item_compare_func_ptr = item_compare2_keeping_zero;        } else { -          item_compare_func_ptr = item_compare_keeping_zero; -      } - -      for (li = l->lv_first; li != NULL && li->li_next != NULL; li = li->li_next) { -        if (item_compare_func_ptr(&li, &li->li_next) == 0) { -          ptrs[i++].item = li; -        } -        if (info.item_compare_func_err) { -          EMSG(_("E882: Uniq compare function failed")); -          break; -        } +        item_compare_func_ptr = item_compare_keeping_zero;        } -      if (!info.item_compare_func_err) { -        while (--i >= 0) { -          assert(ptrs[i].item->li_next); -          li = ptrs[i].item->li_next; -          ptrs[i].item->li_next = li->li_next; -          if (li->li_next != NULL) { -            li->li_next->li_prev = ptrs[i].item; -          } else { -            l->lv_last = ptrs[i].item; +      int idx = 0; +      for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l)) +           ; li != NULL;) { +        listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li); +        if (item_compare_func_ptr(&prev_li, &li) == 0) { +          if (info.item_compare_func_err) {  // -V547 +            EMSG(_("E882: Uniq compare function failed")); +            break;            } -          tv_list_watch_fix(l, li); -          tv_list_item_free(li); -          l->lv_len--; +          li = tv_list_item_remove(l, li); +        } else { +          idx++; +          li = TV_LIST_ITEM_NEXT(l, li);          }        }      } @@ -15373,6 +15472,39 @@ static void f_sort(typval_T *argvars, typval_T *rettv, FunPtr fptr)    do_sort_uniq(argvars, rettv, true);  } +/// "stdioopen()" function +static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ +  if (argvars[0].v_type != VAR_DICT) { +    EMSG(_(e_invarg)); +    return; +  } + + +  bool rpc = false; +  CallbackReader on_stdin = CALLBACK_READER_INIT; +  dict_T *opts = argvars[0].vval.v_dict; +  rpc = tv_dict_get_number(opts, "rpc") != 0; + +  if (!tv_dict_get_callback(opts, S_LEN("on_stdin"), &on_stdin.cb)) { +    return; +  } +  on_stdin.buffered = tv_dict_get_number(opts, "stdin_buffered"); +  if (on_stdin.buffered && on_stdin.cb.type == kCallbackNone) { +    on_stdin.self = opts; +  } + +  const char *error; +  uint64_t id = channel_from_stdio(rpc, on_stdin, &error); +  if (!id) { +    EMSG2(e_stdiochan2, error); +  } + + +  rettv->vval.v_number = (varnumber_T)id; +  rettv->v_type = VAR_NUMBER; +} +  /// "uniq({list})" function  static void f_uniq(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { @@ -15413,13 +15545,12 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr)    hlf_T attr = HLF_COUNT;    size_t len = 0; -  tv_list_alloc_ret(rettv); -    if (argvars[0].v_type == VAR_UNKNOWN) {      // Find the start and length of the badly spelled word.      len = spell_move_to(curwin, FORWARD, true, true, &attr);      if (len != 0) {        word = (char *)get_cursor_pos_ptr(); +      curwin->w_set_curswant = true;      }    } else if (curwin->w_p_spell && *curbuf->b_s.b_p_spl != NUL) {      const char *str = tv_get_string_chk(&argvars[0]); @@ -15439,6 +15570,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr)    }    assert(len <= INT_MAX); +  tv_list_alloc_ret(rettv, 2);    tv_list_append_string(rettv->vval.v_list, word, len);    tv_list_append_string(rettv->vval.v_list,                          (attr == HLF_SPB ? "bad" @@ -15455,46 +15587,40 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr)  {    bool typeerr = false;    int maxcount; -  garray_T ga; -  listitem_T  *li; +  garray_T ga = GA_EMPTY_INIT_VALUE;    bool need_capital = false; -  tv_list_alloc_ret(rettv); -    if (curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) {      const char *const str = tv_get_string(&argvars[0]);      if (argvars[1].v_type != VAR_UNKNOWN) {        maxcount = tv_get_number_chk(&argvars[1], &typeerr);        if (maxcount <= 0) { -        return; +        goto f_spellsuggest_return;        }        if (argvars[2].v_type != VAR_UNKNOWN) {          need_capital = tv_get_number_chk(&argvars[2], &typeerr);          if (typeerr) { -          return; +          goto f_spellsuggest_return;          }        } -    } else +    } else {        maxcount = 25; +    }      spell_suggest_list(&ga, (char_u *)str, maxcount, need_capital, false); +  } -    for (int i = 0; i < ga.ga_len; i++) { -      char *p = ((char **)ga.ga_data)[i]; - -      li = tv_list_item_alloc(); -      li->li_tv.v_type = VAR_STRING; -      li->li_tv.v_lock = 0; -      li->li_tv.vval.v_string = (char_u *)p; -      tv_list_append(rettv->vval.v_list, li); -    } -    ga_clear(&ga); +f_spellsuggest_return: +  tv_list_alloc_ret(rettv, (ptrdiff_t)ga.ga_len); +  for (int i = 0; i < ga.ga_len; i++) { +    char *const p = ((char **)ga.ga_data)[i]; +    tv_list_append_allocated_string(rettv->vval.v_list, p);    } +  ga_clear(&ga);  }  static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { -  regmatch_T regmatch;    char_u      *save_cpo;    int match;    colnr_T col = 0; @@ -15521,14 +15647,19 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr)      pat = "[\\x01- ]\\+";    } -  tv_list_alloc_ret(rettv); +  tv_list_alloc_ret(rettv, kListLenMayKnow); -  if (typeerr) +  if (typeerr) {      return; +  } -  regmatch.regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING); +  regmatch_T regmatch = { +    .regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING), +    .startp = { NULL }, +    .endp = { NULL }, +    .rm_ic = false, +  };    if (regmatch.regprog != NULL) { -    regmatch.rm_ic = FALSE;      while (*str != NUL || keepempty) {        if (*str == NUL) {          match = false;  // Empty item at the end. @@ -15541,14 +15672,15 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr)        } else {          end = str + strlen(str);        } -      if (keepempty || end > str || (rettv->vval.v_list->lv_len > 0 +      if (keepempty || end > str || (tv_list_len(rettv->vval.v_list) > 0                                       && *str != NUL                                       && match                                       && end < (const char *)regmatch.endp[0])) {          tv_list_append_string(rettv->vval.v_list, str, end - str);        } -      if (!match) +      if (!match) {          break; +      }        // Advance to just after the match.        if (regmatch.endp[0] > (char_u *)str) {          col = 0; @@ -15565,17 +15697,71 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr)    p_cpo = save_cpo;  } +/// "stdpath()" helper for list results +static void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv) +  FUNC_ATTR_NONNULL_ALL +{ +  const void *iter = NULL; +  list_T *const list = tv_list_alloc(kListLenShouldKnow); +  rettv->v_type = VAR_LIST; +  rettv->vval.v_list = list; +  tv_list_ref(list); +  char *const dirs = stdpaths_get_xdg_var(xdg); +  do { +    size_t dir_len; +    const char *dir; +    iter = vim_env_iter(':', dirs, iter, &dir, &dir_len); +    if (dir != NULL && dir_len > 0) { +      char *dir_with_nvim = xmemdupz(dir, dir_len); +      dir_with_nvim = concat_fnames_realloc(dir_with_nvim, "nvim", true); +      tv_list_append_string(list, dir_with_nvim, strlen(dir_with_nvim)); +      xfree(dir_with_nvim); +    } +  } while (iter != NULL); +  xfree(dirs); +} + +/// "stdpath(type)" function +static void f_stdpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ +  rettv->v_type = VAR_STRING; +  rettv->vval.v_string = NULL; + +  const char *const p = tv_get_string_chk(&argvars[0]); +  if (p == NULL) { +    return;  // Type error; errmsg already given. +  } + +  if (strcmp(p, "config") == 0) { +    rettv->vval.v_string = (char_u *)get_xdg_home(kXDGConfigHome); +  } else if (strcmp(p, "data") == 0) { +    rettv->vval.v_string = (char_u *)get_xdg_home(kXDGDataHome); +  } else if (strcmp(p, "cache") == 0) { +    rettv->vval.v_string = (char_u *)get_xdg_home(kXDGCacheHome); +  } else if (strcmp(p, "config_dirs") == 0) { +    get_xdg_var_list(kXDGConfigDirs, rettv); +  } else if (strcmp(p, "data_dirs") == 0) { +    get_xdg_var_list(kXDGDataDirs, rettv); +  } else { +    EMSG2(_("E6100: \"%s\" is not a valid stdpath"), p); +  } +} +  /*   * "str2float()" function   */  static void f_str2float(typval_T *argvars, typval_T *rettv, FunPtr fptr)  {    char_u *p = skipwhite((const char_u *)tv_get_string(&argvars[0])); +  bool isneg = (*p == '-'); -  if (*p == '+') { +  if (*p == '+' || *p == '-') {      p = skipwhite(p + 1);    }    (void)string2float((char *)p, &rettv->vval.v_float); +  if (isneg) { +    rettv->vval.v_float *= -1; +  }    rettv->v_type = VAR_FLOAT;  } @@ -15583,7 +15769,7 @@ static void f_str2float(typval_T *argvars, typval_T *rettv, FunPtr fptr)  static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)  {    int base = 10; -  long n; +  varnumber_T n;    int what;    if (argvars[1].v_type != VAR_UNKNOWN) { @@ -15595,7 +15781,8 @@ static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)    }    char_u *p = skipwhite((const char_u *)tv_get_string(&argvars[0])); -  if (*p == '+') { +  bool isneg = (*p == '-'); +  if (*p == '+' || *p == '-') {      p = skipwhite(p + 1);    }    switch (base) { @@ -15616,7 +15803,11 @@ static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)      }    }    vim_str2nr(p, NULL, NULL, what, &n, NULL, 0); -  rettv->vval.v_number = n; +  if (isneg) { +    rettv->vval.v_number = -n; +  } else { +    rettv->vval.v_number = n; +  }  }  /* @@ -15947,7 +16138,7 @@ static void f_strridx(typval_T *argvars, typval_T *rettv, FunPtr fptr)  static void f_strtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr)  {    rettv->v_type = VAR_STRING; -  rettv->vval.v_string = transstr((char_u *)tv_get_string(&argvars[0])); +  rettv->vval.v_string = (char_u *)transstr(tv_get_string(&argvars[0]));  }  /* @@ -16078,7 +16269,7 @@ static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr)        break;      }      case 'n': {  // name -      p = get_highlight_name(NULL, id - 1); +      p = get_highlight_name_ext(NULL, id - 1, false);        break;      }      case 'r': {  // reverse @@ -16142,7 +16333,6 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr)    memset(str, NUL, sizeof(str)); -  tv_list_alloc_ret(rettv);    if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count && col >= 0        && (size_t)col <= STRLEN(ml_get(lnum)) && curwin->w_p_cole > 0) {      (void)syn_get_id(curwin, lnum, col, false, NULL, false); @@ -16151,18 +16341,16 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr)      // get the conceal character      if ((syntax_flags & HL_CONCEAL) && curwin->w_p_cole < 3) {        cchar = syn_get_sub_char(); -      if (cchar == NUL && curwin->w_p_cole == 1 && lcs_conceal != NUL) { -        cchar = lcs_conceal; +      if (cchar == NUL && curwin->w_p_cole == 1) { +        cchar = (lcs_conceal == NUL) ? ' ' : lcs_conceal;        }        if (cchar != NUL) { -        if (has_mbyte) -          (*mb_char2bytes)(cchar, str); -        else -          str[0] = cchar; +        utf_char2bytes(cchar, str);        }      }    } +  tv_list_alloc_ret(rettv, 3);    tv_list_append_number(rettv->vval.v_list, (syntax_flags & HL_CONCEAL) != 0);    // -1 to auto-determine strlen    tv_list_append_string(rettv->vval.v_list, (const char *)str, -1); @@ -16185,7 +16373,7 @@ static void f_synstack(typval_T *argvars, typval_T *rettv, FunPtr fptr)        && lnum <= curbuf->b_ml.ml_line_count        && col >= 0        && (size_t)col <= STRLEN(ml_get(lnum))) { -    tv_list_alloc_ret(rettv); +    tv_list_alloc_ret(rettv, kListLenMayKnow);      (void)syn_get_id(curwin, lnum, col, false, NULL, true);      int id; @@ -16201,7 +16389,7 @@ static list_T *string_to_list(const char *str, size_t len, const bool keepempty)    if (!keepempty && str[len - 1] == NL) {      len--;    } -  list_T *const list = tv_list_alloc(); +  list_T *const list = tv_list_alloc(kListLenMayKnow);    encode_list_write(list, str, len);    return list;  } @@ -16247,7 +16435,7 @@ static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv,    if (res == NULL) {      if (retlist) {        // return an empty list when there's no output -      tv_list_alloc_ret(rettv); +      tv_list_alloc_ret(rettv, 0);      } else {        rettv->vval.v_string = (char_u *) xstrdup("");      } @@ -16260,7 +16448,7 @@ static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv,        keepempty = tv_get_number(&argvars[2]);      }      rettv->vval.v_list = string_to_list(res, nread, (bool)keepempty); -    rettv->vval.v_list->lv_refcount++; +    tv_list_ref(rettv->vval.v_list);      rettv->v_type = VAR_LIST;      xfree(res); @@ -16313,7 +16501,7 @@ static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr)      }    }    if (wp != NULL) { -    tv_list_alloc_ret(rettv); +    tv_list_alloc_ret(rettv, kListLenMayKnow);      while (wp != NULL) {        tv_list_append_number(rettv->vval.v_list, wp->w_buffer->b_fnum);        wp = wp->w_next; @@ -16411,7 +16599,7 @@ static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr)    char *fname;    tagname_T tn; -  tv_list_alloc_ret(rettv); +  tv_list_alloc_ret(rettv, kListLenUnknown);    fname = xmalloc(MAXPATHL);    bool first = true; @@ -16440,8 +16628,8 @@ static void f_taglist(typval_T *argvars, typval_T *rettv, FunPtr fptr)    if (argvars[1].v_type != VAR_UNKNOWN) {      fname = tv_get_string(&argvars[1]);    } -  (void)get_tags(tv_list_alloc_ret(rettv), (char_u *)tag_pattern, -                 (char_u *)fname); +  (void)get_tags(tv_list_alloc_ret(rettv, kListLenUnknown), +                 (char_u *)tag_pattern, (char_u *)fname);  }  /* @@ -16480,8 +16668,9 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)      return;    } -  Callback on_stdout = CALLBACK_NONE, on_stderr = CALLBACK_NONE, -           on_exit = CALLBACK_NONE; +  CallbackReader on_stdout = CALLBACK_READER_INIT, +                 on_stderr = CALLBACK_READER_INIT; +  Callback on_exit = CALLBACK_NONE;    dict_T *job_opts = NULL;    const char *cwd = ".";    if (argvars[1].v_type == VAR_DICT) { @@ -16504,23 +16693,17 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)      }    } -  TerminalJobData *data = common_job_init(argv, on_stdout, on_stderr, on_exit, -                                          true, false, false, cwd); -  data->proc.pty.width = curwin->w_width; -  data->proc.pty.height = curwin->w_height; -  data->proc.pty.term_name = xstrdup("xterm-256color"); -  if (!common_job_start(data, rettv)) { +  uint16_t term_width = MAX(0, curwin->w_width - win_col_off(curwin)); +  Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, +                                    true, false, false, cwd, +                                    term_width, curwin->w_height, +                                    xstrdup("xterm-256color"), +                                    &rettv->vval.v_number); +  if (rettv->vval.v_number <= 0) {      return;    } -  TerminalOptions topts; -  topts.data = data; -  topts.width = curwin->w_width; -  topts.height = curwin->w_height; -  topts.write_cb = term_write; -  topts.resize_cb = term_resize; -  topts.close_cb = term_close; -  int pid = data->proc.pty.process.pid; +  int pid = chan->stream.pty.process.pid;    char buf[1024];    // format the title with the pid to conform with the term:// URI @@ -16530,17 +16713,17 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)    curbuf->b_p_swf = false;    (void)setfname(curbuf, (char_u *)buf, NULL, true);    // Save the job id and pid in b:terminal_job_{id,pid} -  Error err; +  Error err = ERROR_INIT; +  // deprecated: use 'channel' buffer option    dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_id"), -               INTEGER_OBJ(rettv->vval.v_number), false, false, &err); +               INTEGER_OBJ(chan->id), false, false, &err); +  api_clear_error(&err);    dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_pid"),                 INTEGER_OBJ(pid), false, false, &err); +  api_clear_error(&err); -  Terminal *term = terminal_open(topts); -  data->term = term; -  data->refcount++; - -  return; +  channel_terminal_open(chan); +  channel_create_event(chan, NULL);  }  // "test_garbagecollect_now()" function @@ -16552,6 +16735,18 @@ static void f_test_garbagecollect_now(typval_T *argvars,    garbage_collect(true);  } +// "test_write_list_log()" function +static void f_test_write_list_log(typval_T *const argvars, +                                  typval_T *const rettv, +                                  FunPtr fptr) +{ +  const char *const fname = tv_get_string_chk(&argvars[0]); +  if (fname == NULL) { +    return; +  } +  list_write_log(fname); +} +  bool callback_from_typval(Callback *const callback, typval_T *const arg)    FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT  { @@ -16573,30 +16768,6 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg)    return true;  } -/// Unref/free callback -void callback_free(Callback *const callback) -  FUNC_ATTR_NONNULL_ALL -{ -  switch (callback->type) { -    case kCallbackFuncref: { -      func_unref(callback->data.funcref); -      xfree(callback->data.funcref); -      break; -    } -    case kCallbackPartial: { -      partial_unref(callback->data.partial); -      break; -    } -    case kCallbackNone: { -      break; -    } -    default: { -      abort(); -    } -  } -  callback->type = kCallbackNone; -} -  bool callback_call(Callback *const callback, const int argcount_in,                     typval_T *const argvars_in, typval_T *const rettv)    FUNC_ATTR_NONNULL_ALL @@ -16651,6 +16822,102 @@ static bool set_ref_in_callback(Callback *callback, int copyID,    return false;  } +static bool set_ref_in_callback_reader(CallbackReader *reader, int copyID, +                                       ht_stack_T **ht_stack, +                                       list_stack_T **list_stack) +{ +  if (set_ref_in_callback(&reader->cb, copyID, ht_stack, list_stack)) { +    return true; +  } + +  if (reader->self) { +    typval_T tv; +    tv.v_type = VAR_DICT; +    tv.vval.v_dict = reader->self; +    return set_ref_in_item(&tv, copyID, ht_stack, list_stack); +  } +  return false; +} + +static void add_timer_info(typval_T *rettv, timer_T *timer) +{ +  list_T *list = rettv->vval.v_list; +  dict_T *dict = tv_dict_alloc(); + +  tv_list_append_dict(list, dict); +  tv_dict_add_nr(dict, S_LEN("id"), timer->timer_id); +  tv_dict_add_nr(dict, S_LEN("time"), timer->timeout); +  tv_dict_add_nr(dict, S_LEN("paused"), timer->paused); + +  tv_dict_add_nr(dict, S_LEN("repeat"), +                 (timer->repeat_count < 0 ? -1 : timer->repeat_count)); + +  dictitem_T *di = tv_dict_item_alloc("callback"); +  if (tv_dict_add(dict, di) == FAIL) { +    xfree(di); +    return; +  } + +  if (timer->callback.type == kCallbackPartial) { +    di->di_tv.v_type = VAR_PARTIAL; +    di->di_tv.vval.v_partial = timer->callback.data.partial; +    timer->callback.data.partial->pt_refcount++; +  } else if (timer->callback.type == kCallbackFuncref) { +    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) +{ +  timer_T *timer; +  map_foreach_value(timers, timer, { +    if (!timer->stopped) { +      add_timer_info(rettv, timer); +    } +  }) +} + +/// "timer_info([timer])" function +static void f_timer_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ +  tv_list_alloc_ret(rettv, (argvars[0].v_type != VAR_UNKNOWN +                            ? 1 +                            : timers->table->n_occupied)); +  if (argvars[0].v_type != VAR_UNKNOWN) { +    if (argvars[0].v_type != VAR_NUMBER) { +      EMSG(_(e_number_exp)); +      return; +    } +    timer_T *timer = pmap_get(uint64_t)(timers, tv_get_number(&argvars[0])); +    if (timer != NULL && !timer->stopped) { +      add_timer_info(rettv, timer); +    } +  } else { +    add_timer_info_all(rettv); +  } +} + +/// "timer_pause(timer, paused)" function +static void f_timer_pause(typval_T *argvars, typval_T *unused, FunPtr fptr) +{ +  if (argvars[0].v_type != VAR_NUMBER) { +    EMSG(_(e_number_exp)); +    return; +  } +  int paused = (bool)tv_get_number(&argvars[1]); +  timer_T *timer = pmap_get(uint64_t)(timers, tv_get_number(&argvars[0])); +  if (timer != NULL) { +    if (!timer->paused && paused) { +      time_watcher_stop(&timer->tw); +    } else if (timer->paused && !paused) { +      time_watcher_start(&timer->tw, timer_due_cb, timer->timeout, +                         timer->timeout); +    } +    timer->paused = paused; +  } +}  /// "timer_start(timeout, callback, opts)" function  static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) @@ -16685,6 +16952,7 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr)    timer = xmalloc(sizeof *timer);    timer->refcount = 1;    timer->stopped = false; +  timer->paused = false;    timer->repeat_count = repeat;    timer->timeout = timeout;    timer->timer_id = last_timer_id++; @@ -16694,8 +16962,7 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr)    timer->tw.events = multiqueue_new_child(main_loop.events);    // if main loop is blocked, don't queue up multiple events    timer->tw.blockable = true; -  time_watcher_start(&timer->tw, timer_due_cb, timeout, -                     timeout * (repeat != 1)); +  time_watcher_start(&timer->tw, timer_due_cb, timeout, timeout);    pmap_put(uint64_t)(timers, timer->timer_id, timer);    rettv->vval.v_number = timer->timer_id; @@ -16719,13 +16986,19 @@ static void f_timer_stop(typval_T *argvars, typval_T *rettv, FunPtr fptr)      timer_stop(timer);  } +static void f_timer_stopall(typval_T *argvars, typval_T *unused, FunPtr fptr) +{ +  timer_stop_all(); +} +  // invoked on the main loop  static void timer_due_cb(TimeWatcher *tw, void *data)  {    timer_T *timer = (timer_T *)data; -  if (timer->stopped) { +  if (timer->stopped || timer->paused) {      return;    } +    timer->refcount++;    // if repeat was negative repeat forever    if (timer->repeat_count >= 0 && --timer->repeat_count == 0) { @@ -16778,7 +17051,7 @@ static void timer_decref(timer_T *timer)    }  } -void timer_teardown(void) +static void timer_stop_all(void)  {    timer_T *timer;    map_foreach_value(timers, timer, { @@ -16786,35 +17059,19 @@ void timer_teardown(void)    })  } +void timer_teardown(void) +{ +  timer_stop_all(); +} +  /*   * "tolower(string)" function   */  static void f_tolower(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { -  char_u *p = (char_u *)xstrdup(tv_get_string(&argvars[0]));    rettv->v_type = VAR_STRING; -  rettv->vval.v_string = p; - -  while (*p != NUL) { -    int l; - -    if (enc_utf8) { -      int c, lc; - -      c = utf_ptr2char(p); -      lc = utf_tolower(c); -      l = utf_ptr2len(p); -      /* TODO: reallocate string when byte count changes. */ -      if (utf_char2len(lc) == l) -        utf_char2bytes(lc, p); -      p += l; -    } else if (has_mbyte && (l = (*mb_ptr2len)(p)) > 1) -      p += l;                 /* skip multi-byte character */ -    else { -      *p = TOLOWER_LOC(*p);         /* note that tolower() can be a macro */ -      ++p; -    } -  } +  rettv->vval.v_string = (char_u *)strcase_save(tv_get_string(&argvars[0]), +                                                false);  }  /* @@ -16823,7 +17080,8 @@ static void f_tolower(typval_T *argvars, typval_T *rettv, FunPtr fptr)  static void f_toupper(typval_T *argvars, typval_T *rettv, FunPtr fptr)  {    rettv->v_type = VAR_STRING; -  rettv->vval.v_string = (char_u *)strup_save(tv_get_string(&argvars[0])); +  rettv->vval.v_string = (char_u *)strcase_save(tv_get_string(&argvars[0]), +                                                true);  }  /* @@ -16956,7 +17214,7 @@ static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr)        break;      }      case VAR_UNKNOWN: { -      EMSG2(_(e_intern2), "f_type(UNKNOWN)"); +      internal_error("f_type(UNKNOWN)");        break;      }    } @@ -16992,7 +17250,6 @@ static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr)    tv_dict_alloc_ret(rettv);    dict_T *dict = rettv->vval.v_dict; -  list_T *list;    tv_dict_add_nr(dict, S_LEN("synced"), (varnumber_T)curbuf->b_u_synced);    tv_dict_add_nr(dict, S_LEN("seq_last"), (varnumber_T)curbuf->b_u_seq_last); @@ -17002,9 +17259,7 @@ static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr)    tv_dict_add_nr(dict, S_LEN("time_cur"), (varnumber_T)curbuf->b_u_time_cur);    tv_dict_add_nr(dict, S_LEN("save_cur"), (varnumber_T)curbuf->b_u_save_nr_cur); -  list = tv_list_alloc(); -  u_eval_tree(curbuf->b_u_oldhead, list); -  tv_dict_add_list(dict, S_LEN("entries"), list); +  tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead));  }  /* @@ -17063,7 +17318,7 @@ static void f_wildmenumode(typval_T *argvars, typval_T *rettv, FunPtr fptr)  /// "win_findbuf()" function  static void f_win_findbuf(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { -  tv_list_alloc_ret(rettv); +  tv_list_alloc_ret(rettv, kListLenMayKnow);    win_findbuf(argvars, rettv->vval.v_list);  } @@ -17082,8 +17337,7 @@ static void f_win_gotoid(typval_T *argvars, typval_T *rettv, FunPtr fptr)  /// "win_id2tabwin()" function  static void f_win_id2tabwin(typval_T *argvars, typval_T *rettv, FunPtr fptr)  { -  tv_list_alloc_ret(rettv); -  win_id2tabwin(argvars, rettv->vval.v_list); +  win_id2tabwin(argvars, rettv);  }  /// "win_id2win()" function @@ -17245,7 +17499,7 @@ static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr)    tv_dict_add_nr(dict, S_LEN("skipcol"), (varnumber_T)curwin->w_skipcol);  } -/// Writes list of strings to file +/// Write "list" of strings to file "fd".  ///  /// @param  fp  File to write to.  /// @param[in]  list  List to write. @@ -17254,10 +17508,11 @@ static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr)  /// @return true in case of success, false otherwise.  static bool write_list(FileDescriptor *const fp, const list_T *const list,                         const bool binary) +  FUNC_ATTR_NONNULL_ARG(1)  {    int error = 0; -  for (const listitem_T *li = list->lv_first; li != NULL; li = li->li_next) { -    const char *const s = tv_get_string_chk(&li->li_tv); +  TV_LIST_ITER_CONST(list, li, { +    const char *const s = tv_get_string_chk(TV_LIST_ITEM_TV(li));      if (s == NULL) {        return false;      } @@ -17284,14 +17539,14 @@ static bool write_list(FileDescriptor *const fp, const list_T *const list,          }        }      } -    if (!binary || li->li_next != NULL) { +    if (!binary || TV_LIST_ITEM_NEXT(list, li) != NULL) {        const ptrdiff_t written = file_write(fp, "\n", 1);        if (written < 0) {          error = (int)written;          goto write_list_error;        }      } -  } +  });    if ((error = file_flush(fp)) != 0) {      goto write_list_error;    } @@ -17301,56 +17556,29 @@ write_list_error:    return false;  } -/// Initializes a static list with 10 items. -void init_static_list(staticList10_T *sl) -{ -  list_T *l = &sl->sl_list; - -  memset(sl, 0, sizeof(staticList10_T)); -  l->lv_first = &sl->sl_items[0]; -  l->lv_last = &sl->sl_items[9]; -  l->lv_refcount = DO_NOT_FREE_CNT; -  l->lv_lock = VAR_FIXED; -  sl->sl_list.lv_len = 10; - -  for (int i = 0; i < 10; i++) { -    listitem_T *li = &sl->sl_items[i]; - -    if (i == 0) { -      li->li_prev = NULL; -    } else { -      li->li_prev = li - 1; -    } -    if (i == 9) { -      li->li_next = NULL; -    } else { -      li->li_next = li + 1; -    } -  } -} -  /// Saves a typval_T as a string.  /// -/// For lists, replaces NLs with NUL and separates items with NLs. +/// For lists or buffers, replaces NLs with NUL and separates items with NLs.  /// -/// @param[in]  tv   A value to store as a string. -/// @param[out] len  The length of the resulting string or -1 on error. +/// @param[in]  tv   Value to store as a string. +/// @param[out] len  Length of the resulting string or -1 on error.  /// @param[in]  endnl If true, the output will end in a newline (if a list).  /// @returns an allocated string if `tv` represents a VimL string, list, or  ///          number; NULL otherwise.  static char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl)    FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL  { +  *len = 0;    if (tv->v_type == VAR_UNKNOWN) { -    *len = 0;      return NULL;    } -  // For types other than list, let tv_get_string_buf_chk() get the value or +  // For other types, let tv_get_string_buf_chk() get the value or    // print an error. -  if (tv->v_type != VAR_LIST) { +  if (tv->v_type != VAR_LIST && tv->v_type != VAR_NUMBER) {      const char *ret = tv_get_string_chk(tv); -    if (ret && (*len = strlen(ret))) { +    if (ret) { +      *len = strlen(ret);        return xmemdupz(ret, (size_t)(*len));      } else {        *len = -1; @@ -17358,12 +17586,44 @@ static char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl)      }    } +  if (tv->v_type == VAR_NUMBER) {  // Treat number as a buffer-id. +    buf_T *buf = buflist_findnr(tv->vval.v_number); +    if (buf) { +      for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { +        for (char_u *p = ml_get_buf(buf, lnum, false); *p != NUL; p++) { +          *len += 1; +        } +        *len += 1; +      } +    } else { +      EMSGN(_(e_nobufnr), tv->vval.v_number); +      *len = -1; +      return NULL; +    } + +    if (*len == 0) { +      return NULL; +    } + +    char *ret = xmalloc(*len + 1); +    char *end = ret; +    for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { +      for (char_u *p = ml_get_buf(buf, lnum, false); *p != NUL; p++) { +        *end++ = (*p == '\n') ? NUL : *p; +      } +      *end++ = '\n'; +    } +    *end = NUL; +    *len = end - ret; +    return ret; +  } + +  assert(tv->v_type == VAR_LIST);    // Pre-calculate the resulting length. -  *len = 0;    list_T *list = tv->vval.v_list; -  for (listitem_T *li = list->lv_first; li != NULL; li = li->li_next) { -    *len += strlen(tv_get_string(&li->li_tv)) + 1; -  } +  TV_LIST_ITER_CONST(list, li, { +    *len += strlen(tv_get_string(TV_LIST_ITEM_TV(li))) + 1; +  });    if (*len == 0) {      return NULL; @@ -17371,14 +17631,14 @@ static char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl)    char *ret = xmalloc(*len + endnl);    char *end = ret; -  for (listitem_T *li = list->lv_first; li != NULL; li = li->li_next) { -    for (const char *s = tv_get_string(&li->li_tv); *s != NUL; s++) { +  TV_LIST_ITER_CONST(list, li, { +    for (const char *s = tv_get_string(TV_LIST_ITEM_TV(li)); *s != NUL; s++) {        *end++ = (*s == '\n') ? NUL : *s;      } -    if (endnl || li->li_next != NULL) { +    if (endnl || TV_LIST_ITEM_NEXT(list, li) != NULL) {        *end++ = '\n';      } -  } +  });    *end = NUL;    *len = end - ret;    return ret; @@ -17418,9 +17678,6 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr)      EMSG2(_(e_listarg), "writefile()");      return;    } -  if (argvars[0].vval.v_list == NULL) { -    return; -  }    bool binary = false;    bool append = false; @@ -17525,9 +17782,9 @@ pos_T *var2fpos(const typval_T *const tv, const int dollar_lnum,      // We accept "$" for the column number: last column.      li = tv_list_find(l, 1L); -    if (li != NULL && li->li_tv.v_type == VAR_STRING -        && li->li_tv.vval.v_string != NULL -        && STRCMP(li->li_tv.vval.v_string, "$") == 0) { +    if (li != NULL && TV_LIST_ITEM_TV(li)->v_type == VAR_STRING +        && TV_LIST_ITEM_TV(li)->vval.v_string != NULL +        && STRCMP(TV_LIST_ITEM_TV(li)->vval.v_string, "$") == 0) {        pos.col = len + 1;      } @@ -17604,17 +17861,18 @@ pos_T *var2fpos(const typval_T *const tv, const int dollar_lnum,   */  static int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp)  { -  list_T      *l = arg->vval.v_list; +  list_T *l;    long i = 0;    long n; -  /* List must be: [fnum, lnum, col, coladd, curswant], where "fnum" is only -   * there when "fnump" isn't NULL; "coladd" and "curswant" are optional. */ +  // List must be: [fnum, lnum, col, coladd, curswant], where "fnum" is only +  // there when "fnump" isn't NULL; "coladd" and "curswant" are optional.    if (arg->v_type != VAR_LIST -      || l == NULL -      || l->lv_len < (fnump == NULL ? 2 : 3) -      || l->lv_len > (fnump == NULL ? 4 : 5)) +      || (l = arg->vval.v_list) == NULL +      || tv_list_len(l) < (fnump == NULL ? 2 : 3) +      || tv_list_len(l) > (fnump == NULL ? 4 : 5)) {      return FAIL; +  }    if (fnump != NULL) {      n = tv_list_find_nr(l, i++, NULL);  // fnum @@ -17930,7 +18188,7 @@ static int eval_isnamec1(int c)  /*   * Get number v: variable value.   */ -long get_vim_var_nr(int idx) FUNC_ATTR_PURE +varnumber_T get_vim_var_nr(int idx) FUNC_ATTR_PURE  {    return vimvars[idx].vv_nr;  } @@ -18039,7 +18297,7 @@ void set_vim_var_list(const VimVarIndex idx, list_T *const val)    vimvars[idx].vv_type = VAR_LIST;    vimvars[idx].vv_list = val;    if (val != NULL) { -    val->lv_refcount++; +    tv_list_ref(val);    }  } @@ -18339,47 +18597,39 @@ void set_selfdict(typval_T *rettv, dict_T *selfdict)    // Turn "dict.Func" into a partial for "Func" with "dict".    if (fp != NULL && (fp->uf_flags & FC_DICT)) {      partial_T *pt = (partial_T *)xcalloc(1, sizeof(partial_T)); - -    if (pt != NULL) { -      pt->pt_refcount = 1; -      pt->pt_dict = selfdict; -      (selfdict->dv_refcount)++; -      pt->pt_auto = true; -      if (rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING) { -        // Just a function: Take over the function name and use selfdict. -        pt->pt_name = rettv->vval.v_string; +    pt->pt_refcount = 1; +    pt->pt_dict = selfdict; +    (selfdict->dv_refcount)++; +    pt->pt_auto = true; +    if (rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING) { +      // Just a function: Take over the function name and use selfdict. +      pt->pt_name = rettv->vval.v_string; +    } else { +      partial_T *ret_pt = rettv->vval.v_partial; +      int i; + +      // Partial: copy the function name, use selfdict and copy +      // args. Can't take over name or args, the partial might +      // be referenced elsewhere. +      if (ret_pt->pt_name != NULL) { +        pt->pt_name = vim_strsave(ret_pt->pt_name); +        func_ref(pt->pt_name);        } else { -        partial_T *ret_pt = rettv->vval.v_partial; -        int i; - -        // Partial: copy the function name, use selfdict and copy -        // args. Can't take over name or args, the partial might -        // be referenced elsewhere. -        if (ret_pt->pt_name != NULL) { -          pt->pt_name = vim_strsave(ret_pt->pt_name); -          func_ref(pt->pt_name); -        } else { -          pt->pt_func = ret_pt->pt_func; -          func_ptr_ref(pt->pt_func); -        } -        if (ret_pt->pt_argc > 0) { -          size_t arg_size = sizeof(typval_T) * ret_pt->pt_argc; -          pt->pt_argv = (typval_T *)xmalloc(arg_size); -          if (pt->pt_argv == NULL) { -            // out of memory: drop the arguments -            pt->pt_argc = 0; -          } else { -            pt->pt_argc = ret_pt->pt_argc; -            for (i = 0; i < pt->pt_argc; i++) { -              tv_copy(&ret_pt->pt_argv[i], &pt->pt_argv[i]); -            } -          } +        pt->pt_func = ret_pt->pt_func; +        func_ptr_ref(pt->pt_func); +      } +      if (ret_pt->pt_argc > 0) { +        size_t arg_size = sizeof(typval_T) * ret_pt->pt_argc; +        pt->pt_argv = (typval_T *)xmalloc(arg_size); +        pt->pt_argc = ret_pt->pt_argc; +        for (i = 0; i < pt->pt_argc; i++) { +          tv_copy(&ret_pt->pt_argv[i], &pt->pt_argv[i]);          } -        partial_unref(ret_pt);        } -      rettv->v_type = VAR_PARTIAL; -      rettv->vval.v_partial = pt; +      partial_unref(ret_pt);      } +    rettv->v_type = VAR_PARTIAL; +    rettv->vval.v_partial = pt;    }  } @@ -18443,7 +18693,7 @@ static dictitem_T *find_var_in_ht(hashtab_T *const ht,        case 'l': return (current_funccal == NULL                          ? NULL : (dictitem_T *)¤t_funccal->l_vars_var);        case 'a': return (current_funccal == NULL -                        ? NULL : (dictitem_T *)¤t_funccal->l_avars_var); +                        ? NULL : (dictitem_T *)&get_funccal()->l_avars_var);      }      return NULL;    } @@ -18526,7 +18776,7 @@ static hashtab_T *find_var_ht_dict(const char *name, const size_t name_len,    if (name_len == 0) {      return NULL;    } -  if (name_len == 1 || (name_len >= 2 && name[1] != ':')) { +  if (name_len == 1 || name[1] != ':') {      // name has implicit scope      if (name[0] == ':' || name[0] == AUTOLOAD_CHAR) {        // The name must not start with a colon or #. @@ -18843,7 +19093,7 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv,          }          return;        } else if (v->di_tv.v_type != tv->v_type) { -        EMSG2(_(e_intern2), "set_var()"); +        internal_error("set_var()");        }      } @@ -18863,6 +19113,9 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv,        return;      } +    // Make sure dict is valid +    assert(dict != NULL); +      v = xmalloc(sizeof(dictitem_T) + strlen(varname));      STRCPY(v->di_key, varname);      if (tv_dict_add(dict, v) == FAIL) { @@ -18896,24 +19149,43 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv,  ///  /// @param[in]  flags  di_flags attribute value.  /// @param[in]  name  Variable name, for use in error message. -/// @param[in]  name_len  Variable name length. +/// @param[in]  name_len  Variable name length. Use #TV_TRANSLATE to translate +///                       variable name and compute the length. Use #TV_CSTRING +///                       to compute the length with strlen() without +///                       translating. +/// +///                       Both #TV_… values are used for optimization purposes: +///                       variable name with its length is needed only in case +///                       of error, when no error occurs computing them is +///                       a waste of CPU resources. This especially applies to +///                       gettext.  ///  /// @return True if variable is read-only: either always or in sandbox when  ///         sandbox is enabled, false otherwise. -bool var_check_ro(const int flags, const char *const name, -                  const size_t name_len) +bool var_check_ro(const int flags, const char *name, +                  size_t name_len)    FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL  { +  const char *error_message = NULL;    if (flags & DI_FLAGS_RO) { -    emsgf(_(e_readonlyvar), (int)name_len, name); -    return true; +    error_message = N_(e_readonlyvar); +  } else if ((flags & DI_FLAGS_RO_SBX) && sandbox) { +    error_message = N_("E794: Cannot set variable in the sandbox: \"%.*s\"");    } -  if ((flags & DI_FLAGS_RO_SBX) && sandbox) { -    emsgf(_("E794: Cannot set variable in the sandbox: \"%.*s\""), -          (int)name_len, name); -    return true; + +  if (error_message == NULL) { +    return false;    } -  return false; +  if (name_len == TV_TRANSLATE) { +    name = _(name); +    name_len = strlen(name); +  } else if (name_len == TV_CSTRING) { +    name_len = strlen(name); +  } + +  emsgf(_(error_message), (int)name_len, name); + +  return true;  }  /// Check whether variable is fixed (DI_FLAGS_FIX) @@ -18922,14 +19194,29 @@ bool var_check_ro(const int flags, const char *const name,  ///  /// @param[in]  flags  di_flags attribute value.  /// @param[in]  name  Variable name, for use in error message. -/// @param[in]  name_len  Variable name length. +/// @param[in]  name_len  Variable name length. Use #TV_TRANSLATE to translate +///                       variable name and compute the length. Use #TV_CSTRING +///                       to compute the length with strlen() without +///                       translating. +/// +///                       Both #TV_… values are used for optimization purposes: +///                       variable name with its length is needed only in case +///                       of error, when no error occurs computing them is +///                       a waste of CPU resources. This especially applies to +///                       gettext.  ///  /// @return True if variable is fixed, false otherwise. -static bool var_check_fixed(const int flags, const char *const name, -                            const size_t name_len) +static bool var_check_fixed(const int flags, const char *name, +                            size_t name_len)    FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL  {    if (flags & DI_FLAGS_FIX) { +    if (name_len == TV_TRANSLATE) { +      name = _(name); +      name_len = strlen(name); +    } else if (name_len == TV_CSTRING) { +      name_len = strlen(name); +    }      emsgf(_("E795: Cannot delete variable %.*s"), (int)name_len, name);      return true;    } @@ -19046,12 +19333,12 @@ int var_item_copy(const vimconv_T *const conv,    case VAR_LIST:      to->v_type = VAR_LIST;      to->v_lock = 0; -    if (from->vval.v_list == NULL) +    if (from->vval.v_list == NULL) {        to->vval.v_list = NULL; -    else if (copyID != 0 && from->vval.v_list->lv_copyID == copyID) { -      /* use the copy made earlier */ -      to->vval.v_list = from->vval.v_list->lv_copylist; -      ++to->vval.v_list->lv_refcount; +    } else if (copyID != 0 && tv_list_copyid(from->vval.v_list) == copyID) { +      // Use the copy made earlier. +      to->vval.v_list = tv_list_latest_copy(from->vval.v_list); +      tv_list_ref(to->vval.v_list);      } else {        to->vval.v_list = tv_list_copy(conv, from->vval.v_list, deep, copyID);      } @@ -19076,7 +19363,7 @@ int var_item_copy(const vimconv_T *const conv,      }      break;    case VAR_UNKNOWN: -    EMSG2(_(e_intern2), "var_item_copy(UNKNOWN)"); +    internal_error("var_item_copy(UNKNOWN)");      ret = FAIL;    }    --recurse; @@ -19144,14 +19431,10 @@ void ex_echo(exarg_T *eap)              }              msg_putchar_attr((uint8_t)(*p), echo_attr);            } else { -            if (has_mbyte) { -              int i = (*mb_ptr2len)((const char_u *)p); +            int i = (*mb_ptr2len)((const char_u *)p); -              (void)msg_outtrans_len_attr((char_u *)p, i, echo_attr); -              p += i - 1; -            } else { -              (void)msg_outtrans_len_attr((char_u *)p, 1, echo_attr); -            } +            (void)msg_outtrans_len_attr((char_u *)p, i, echo_attr); +            p += i - 1;            }          }        } @@ -19276,18 +19559,22 @@ static const char *find_option_end(const char **const arg, int *const opt_flags)    } else if (*p == 'l' && p[1] == ':') {      *opt_flags = OPT_LOCAL;      p += 2; -  } else +  } else {      *opt_flags = 0; +  } -  if (!ASCII_ISALPHA(*p)) +  if (!ASCII_ISALPHA(*p)) {      return NULL; +  }    *arg = p; -  if (p[0] == 't' && p[1] == '_' && p[2] != NUL && p[3] != NUL) -    p += 4;         /* termcap option */ -  else -    while (ASCII_ISALPHA(*p)) -      ++p; +  if (p[0] == 't' && p[1] == '_' && p[2] != NUL && p[3] != NUL) { +    p += 4;  // t_xx/termcap option +  } else { +    while (ASCII_ISALPHA(*p)) { +      p++; +    } +  }    return p;  } @@ -19321,6 +19608,7 @@ void ex_function(exarg_T *eap)    int todo;    hashitem_T  *hi;    int sourcing_lnum_off; +  bool show_block = false;    /*     * ":function" without argument: list functions. @@ -19400,8 +19688,9 @@ void ex_function(exarg_T *eap)       * interrupt, or an exception.       */      if (!aborting()) { -      if (!eap->skip && fudi.fd_newkey != NULL) +      if (fudi.fd_newkey != NULL) {          EMSG2(_(e_dictkey), fudi.fd_newkey); +      }        xfree(fudi.fd_newkey);        return;      } else @@ -19493,6 +19782,11 @@ void ex_function(exarg_T *eap)      goto errret_2;    } +  if (KeyTyped && ui_is_external(kUICmdline)) { +    show_block = true; +    ui_ext_cmdline_block_append(0, (const char *)eap->cmd); +  } +    // find extra arguments "range", "dict", "abort" and "closure"    for (;; ) {      p = skipwhite(p); @@ -19521,10 +19815,12 @@ void ex_function(exarg_T *eap)    /* When there is a line break use what follows for the function body.     * Makes 'exe "func Test()\n...\nendfunc"' work. */ -  if (*p == '\n') +  const char *const end = (const char *)p + STRLEN(p); +  if (*p == '\n') {      line_arg = p + 1; -  else if (*p != NUL && *p != '"' && !eap->skip && !did_emsg) -    EMSG(_(e_trailing)); +  } else if (*p != NUL && *p != '"' && !eap->skip && !did_emsg) { +    emsgf(_(e_trailing)); +  }    /*     * Read the body of the function, until ":endfunction" is found. @@ -19543,7 +19839,9 @@ void ex_function(exarg_T *eap)      if (!eap->skip && did_emsg)        goto erret; -    msg_putchar('\n');              /* don't overwrite the function name */ +    if (!ui_is_external(kUICmdline)) { +      msg_putchar('\n');              // don't overwrite the function name +    }      cmdline_row = msg_row;    } @@ -19577,6 +19875,9 @@ void ex_function(exarg_T *eap)        EMSG(_("E126: Missing :endfunction"));        goto erret;      } +    if (show_block) { +      ui_ext_cmdline_block_append(indent, (const char *)theline); +    }      /* Detect line continuation: sourcing_lnum increased more than one. */      if (sourcing_lnum > sourcing_lnum_off + 1) @@ -19598,8 +19899,30 @@ void ex_function(exarg_T *eap)        /* Check for "endfunction". */        if (checkforcmd(&p, "endfunction", 4) && nesting-- == 0) { -        if (line_arg == NULL) +        if (*p == '!') { +          p++; +        } +        const char *const comment_start = strchr((const char *)p, '"'); +        const char *const endfunc_end = (comment_start +                                         ? strchr(comment_start, '\n') +                                         : strpbrk((const char *)p, "\n|")); +        p = (endfunc_end +             ? (char_u *)endfunc_end +             : p + STRLEN(p)); +        if (*p == '|') { +          emsgf(_(e_trailing2), p); +          if (line_arg == NULL) { +            xfree(theline); +          } +          goto erret; +        } +        if (line_arg == NULL) {            xfree(theline); +        } else { +          if ((const char *)p < end) { +            eap->nextcmd = p + 1; +          } +        }          break;        } @@ -19626,9 +19949,15 @@ void ex_function(exarg_T *eap)          }        } -      /* Check for ":append" or ":insert". */ +      // Check for ":append", ":change", ":insert".        p = skip_range(p, NULL);        if ((p[0] == 'a' && (!ASCII_ISALPHA(p[1]) || p[1] == 'p')) +          || (p[0] == 'c' +              && (!ASCII_ISALPHA(p[1]) +                  || (p[1] == 'h' && (!ASCII_ISALPHA(p[2]) +                                      || (p[2] == 'a' +                                          && (STRNCMP(&p[3], "nge", 3) != 0 +                                              || !ASCII_ISALPHA(p[6])))))))            || (p[0] == 'i'                && (!ASCII_ISALPHA(p[1]) || (p[1] == 'n'                                             && (!ASCII_ISALPHA(p[2]) @@ -19736,12 +20065,12 @@ void ex_function(exarg_T *eap)      }      if (fudi.fd_di == NULL) {        if (tv_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg, -                        STRLEN(eap->arg))) { +                        TV_CSTRING)) {          // Can't add a function to a locked dictionary          goto erret;        }      } else if (tv_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg, -                             STRLEN(eap->arg))) { +                             TV_CSTRING)) {        // Can't change an existing function if it is locked        goto erret;      } @@ -19778,7 +20107,7 @@ void ex_function(exarg_T *eap)        }      } -    fp = xcalloc(1, sizeof(ufunc_T) + STRLEN(name)); +    fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);      if (fudi.fd_dict != NULL) {        if (fudi.fd_di == NULL) { @@ -19841,6 +20170,9 @@ ret_free:    xfree(name);    did_emsg |= saved_did_emsg;    need_wait_return |= saved_wait_return; +  if (show_block) { +    ui_ext_cmdline_block_leave(); +  }  }  /// Get a function name, translating "<SID>" and "<SNR>". @@ -20530,8 +20862,9 @@ char_u *get_user_func_name(expand_T *xp, int idx)        return (char_u *)"";       // don't show dict and lambda functions      } -    if (STRLEN(fp->uf_name) + 4 >= IOSIZE) -      return fp->uf_name;       /* prevents overflow */ +    if (STRLEN(fp->uf_name) + 4 >= IOSIZE) { +      return fp->uf_name;  // Prevent overflow. +    }      cat_func_name(IObuff, fp);      if (xp->xp_context != EXPAND_USER_FUNC) { @@ -20601,7 +20934,9 @@ void ex_delfunction(exarg_T *eap)    if (!eap->skip) {      if (fp == NULL) { -      EMSG2(_(e_nofunc), eap->arg); +      if (!eap->forceit) { +        EMSG2(_(e_nofunc), eap->arg); +      }        return;      }      if (fp->uf_calls > 0) { @@ -20715,11 +21050,11 @@ void func_unref(char_u *name)    if (fp == NULL && isdigit(*name)) {  #ifdef EXITFREE      if (!entered_free_all_mem) { -      EMSG2(_(e_intern2), "func_unref()"); +      internal_error("func_unref()");        abort();      }  #else -      EMSG2(_(e_intern2), "func_unref()"); +      internal_error("func_unref()");        abort();  #endif    } @@ -20758,7 +21093,7 @@ void func_ref(char_u *name)    } else if (isdigit(*name)) {      // Only give an error for a numbered function.      // Fail silently, when named or lambda function isn't found. -    EMSG2(_(e_intern2), "func_ref()"); +    internal_error("func_ref()");    }  } @@ -20770,6 +21105,22 @@ void func_ptr_ref(ufunc_T *fp)    }  } +/// Check whether funccall is still referenced outside +/// +/// It is supposed to be referenced if either it is referenced itself or if l:, +/// a: or a:000 are referenced as all these are statically allocated within +/// funccall structure. +static inline bool fc_referenced(const funccall_T *const fc) +  FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +  FUNC_ATTR_NONNULL_ALL +{ +  return ((fc->l_varlist.lv_refcount  // NOLINT(runtime/deprecated) +           != DO_NOT_FREE_CNT) +          || fc->l_vars.dv_refcount != DO_NOT_FREE_CNT +          || fc->l_avars.dv_refcount != DO_NOT_FREE_CNT +          || fc->fc_refcount > 0); +} +  /// Call a user function  ///  /// @param  fp  Function to call. @@ -20883,9 +21234,8 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,    v->di_tv.v_type = VAR_LIST;    v->di_tv.v_lock = VAR_FIXED;    v->di_tv.vval.v_list = &fc->l_varlist; -  memset(&fc->l_varlist, 0, sizeof(list_T)); -  fc->l_varlist.lv_refcount = DO_NOT_FREE_CNT; -  fc->l_varlist.lv_lock = VAR_FIXED; +  tv_list_init_static(&fc->l_varlist); +  tv_list_set_lock(&fc->l_varlist, VAR_FIXED);    // Set a:firstline to "firstline" and a:lastline to "lastline".    // Set a:name to named arguments. @@ -20934,8 +21284,8 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,      if (ai >= 0 && ai < MAX_FUNC_ARGS) {        tv_list_append(&fc->l_varlist, &fc->l_listitems[ai]); -      fc->l_listitems[ai].li_tv = argvars[i]; -      fc->l_listitems[ai].li_tv.v_lock = VAR_FIXED; +      *TV_LIST_ITEM_TV(&fc->l_listitems[ai]) = argvars[i]; +      TV_LIST_ITEM_TV(&fc->l_listitems[ai])->v_lock = VAR_FIXED;      }    } @@ -20982,11 +21332,11 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,              // Do not want errors such as E724 here.              emsg_off++;              char *tofree = encode_tv2string(&argvars[i], NULL); -            char *s = tofree;              emsg_off--; -            if (s != NULL) { +            if (tofree != NULL) { +              char *s = tofree; +              char buf[MSG_BUF_LEN];                if (vim_strsize((char_u *)s) > MSG_BUF_CLEN) { -                char buf[MSG_BUF_LEN];                  trunc_string((char_u *)s, (char_u *)buf, MSG_BUF_CLEN,                               sizeof(buf));                  s = buf; @@ -21005,15 +21355,17 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,      }    } +  const bool do_profiling_yes = do_profiling == PROF_YES; +    bool func_not_yet_profiling_but_should = -    do_profiling == PROF_YES -    && !fp->uf_profiling && has_profiling(FALSE, fp->uf_name, NULL); +    do_profiling_yes +    && !fp->uf_profiling && has_profiling(false, fp->uf_name, NULL);    if (func_not_yet_profiling_but_should)      func_do_profile(fp);    bool func_or_func_caller_profiling = -    do_profiling == PROF_YES +    do_profiling_yes      && (fp->uf_profiling          || (fc->caller != NULL && fc->caller->func->uf_profiling)); @@ -21023,7 +21375,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,      fp->uf_tm_children = profile_zero();    } -  if (do_profiling == PROF_YES) { +  if (do_profiling_yes) {      script_prof_save(&wait_start);    } @@ -21048,7 +21400,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,    if (func_or_func_caller_profiling) {      call_start = profile_end(call_start); -    call_start = profile_sub_wait(wait_start, call_start); +    call_start = profile_sub_wait(wait_start, call_start);  // -V614      fp->uf_tm_total = profile_add(fp->uf_tm_total, call_start);      fp->uf_tm_self = profile_self(fp->uf_tm_self, call_start,          fp->uf_tm_children); @@ -21099,8 +21451,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,    sourcing_name = save_sourcing_name;    sourcing_lnum = save_sourcing_lnum;    current_SID = save_current_SID; -  if (do_profiling == PROF_YES) +  if (do_profiling_yes) {      script_prof_restore(&wait_start); +  }    if (p_verbose >= 12 && sourcing_name != NULL) {      ++no_wait_return; @@ -21119,10 +21472,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,    // If the a:000 list and the l: and a: dicts are not referenced and there    // is no closure using it, we can free the funccall_T and what's in it. -  if (fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT -      && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT -      && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT -      && fc->fc_refcount <= 0) { +  if (!fc_referenced(fc)) {      free_funccal(fc, false);    } else {      // "fc" is still in use.  This can happen when returning "a:000", @@ -21137,10 +21487,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,      });      // Make a copy of the a:000 items, since we didn't do that above. -    for (listitem_T *li = fc->l_varlist.lv_first; li != NULL; -         li = li->li_next) { -      tv_copy(&li->li_tv, &li->li_tv); -    } +    TV_LIST_ITER(&fc->l_varlist, li, { +      tv_copy(TV_LIST_ITEM_TV(li), TV_LIST_ITEM_TV(li)); +    });    }    if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) { @@ -21167,10 +21516,8 @@ static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force)      return;    } -  if (--fc->fc_refcount <= 0 && (force || ( -      fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT -      && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT -      && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT))) { +  fc->fc_refcount--; +  if (force ? fc->fc_refcount <= 0 : !fc_referenced(fc)) {      for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) {        if (fc == *pfc) {          *pfc = fc->caller; @@ -21205,8 +21552,6 @@ free_funccal (      int free_val              /* a: vars were allocated */  )  { -  listitem_T  *li; -    for (int i = 0; i < fc->fc_funcs.ga_len; i++) {      ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; @@ -21224,14 +21569,14 @@ free_funccal (    // allocated variables.    vars_clear_ext(&fc->l_avars.dv_hashtab, free_val); -  /* free all l: variables */ +  // Free all l: variables.    vars_clear(&fc->l_vars.dv_hashtab);    // Free the a:000 variables if they were allocated.    if (free_val) { -    for (li = fc->l_varlist.lv_first; li != NULL; li = li->li_next) { -      tv_clear(&li->li_tv); -    } +    TV_LIST_ITER(&fc->l_varlist, li, { +      tv_clear(TV_LIST_ITEM_TV(li)); +    });    }    func_ptr_unref(fc->func); @@ -21726,52 +22071,6 @@ void last_set_msg(scid_T scriptID)    }  } -/* - * List v:oldfiles in a nice way. - */ -void ex_oldfiles(exarg_T *eap) -{ -  list_T      *l = get_vim_var_list(VV_OLDFILES); -  listitem_T  *li; -  long nr = 0; - -  if (l == NULL) -    msg((char_u *)_("No old files")); -  else { -    msg_start(); -    msg_scroll = TRUE; -    for (li = l->lv_first; li != NULL && !got_int; li = li->li_next) { -      msg_outnum(++nr); -      MSG_PUTS(": "); -      msg_outtrans((char_u *)tv_get_string(&li->li_tv)); -      msg_clr_eos(); -      msg_putchar('\n'); -      ui_flush();                  /* output one line at a time */ -      os_breakcheck(); -    } -    /* Assume "got_int" was set to truncate the listing. */ -    got_int = FALSE; - -    // File selection prompt on ":browse oldfiles" -    if (cmdmod.browse) { -      quit_more = false; -      nr = prompt_for_number(false); -      msg_starthere(); -      if (nr > 0 && nr <= l->lv_len) { -        const char *const p = tv_list_find_str(l, nr - 1); -        if (p == NULL) { -          return; -        } -        char *const s = (char *)expand_env_save((char_u *)p); -        eap->arg = (char_u *)s; -        eap->cmdidx = CMD_edit; -        do_exedit(eap, NULL); -        xfree(s); -      } -    } -  } -} -  // reset v:option_new, v:option_old and v:option_type  void reset_v_option_vars(void)  { @@ -22124,317 +22423,54 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub,    return ret;  } -static inline TerminalJobData *common_job_init(char **argv, -                                               Callback on_stdout, -                                               Callback on_stderr, -                                               Callback on_exit, -                                               bool pty, -                                               bool rpc, -                                               bool detach, -                                               const char *cwd) -{ -  TerminalJobData *data = xcalloc(1, sizeof(TerminalJobData)); -  data->stopped = false; -  data->on_stdout = on_stdout; -  data->on_stderr = on_stderr; -  data->on_exit = on_exit; -  data->events = multiqueue_new_child(main_loop.events); -  data->rpc = rpc; -  if (pty) { -    data->proc.pty = pty_process_init(&main_loop, data); -  } else { -    data->proc.uv = libuv_process_init(&main_loop, data); -  } -  Process *proc = (Process *)&data->proc; -  proc->argv = argv; -  proc->in = &data->in; -  proc->out = &data->out; -  if (!pty) { -    proc->err = &data->err; -  } -  proc->cb = eval_job_process_exit_cb; -  proc->events = data->events; -  proc->detach = detach; -  proc->cwd = cwd; -  return data; -} -  /// common code for getting job callbacks for jobstart, termopen and rpcstart  ///  /// @return true/false on success/failure. -static inline bool common_job_callbacks(dict_T *vopts, Callback *on_stdout, -                                        Callback *on_stderr, Callback *on_exit) +static inline bool common_job_callbacks(dict_T *vopts, +                                        CallbackReader *on_stdout, +                                        CallbackReader *on_stderr, +                                        Callback *on_exit)  { -  if (tv_dict_get_callback(vopts, S_LEN("on_stdout"), on_stdout) -      &&tv_dict_get_callback(vopts, S_LEN("on_stderr"), on_stderr) +  if (tv_dict_get_callback(vopts, S_LEN("on_stdout"), &on_stdout->cb) +      &&tv_dict_get_callback(vopts, S_LEN("on_stderr"), &on_stderr->cb)        && tv_dict_get_callback(vopts, S_LEN("on_exit"), on_exit)) { +    on_stdout->buffered = tv_dict_get_number(vopts, "stdout_buffered"); +    on_stderr->buffered = tv_dict_get_number(vopts, "stderr_buffered"); +    if (on_stdout->buffered && on_stdout->cb.type == kCallbackNone) { +      on_stdout->self = vopts; +    } +    if (on_stderr->buffered && on_stderr->cb.type == kCallbackNone) { +      on_stderr->self = vopts; +    }      vopts->dv_refcount++;      return true;    } -  callback_free(on_stdout); -  callback_free(on_stderr); +  callback_reader_free(on_stdout); +  callback_reader_free(on_stderr);    callback_free(on_exit);    return false;  } -static inline bool common_job_start(TerminalJobData *data, typval_T *rettv) -{ -  Process *proc = (Process *)&data->proc; -  if (proc->type == kProcessTypePty && proc->detach) { -    EMSG2(_(e_invarg2), "terminal/pty job cannot be detached"); -    xfree(data->proc.pty.term_name); -    shell_free_argv(proc->argv); -    free_term_job_data_event((void **)&data); -    return false; -  } - -  data->id = next_chan_id++; -  pmap_put(uint64_t)(jobs, data->id, data); - -  data->refcount++; -  char *cmd = xstrdup(proc->argv[0]); -  int status = process_spawn(proc); -  if (status) { -    EMSG3(_(e_jobspawn), os_strerror(status), cmd); -    xfree(cmd); -    if (proc->type == kProcessTypePty) { -      xfree(data->proc.pty.term_name); -    } -    rettv->vval.v_number = proc->status; -    term_job_data_decref(data); -    return false; -  } -  xfree(cmd); - - -  if (data->rpc) { -    // the rpc channel takes over the in and out streams -    channel_from_process(proc, data->id); -  } else { -    wstream_init(proc->in, 0); -    if (proc->out) { -      rstream_init(proc->out, 0); -      rstream_start(proc->out, on_job_stdout, data); -    } -  } - -  if (proc->err) { -    rstream_init(proc->err, 0); -    rstream_start(proc->err, on_job_stderr, data); -  } -  rettv->vval.v_number = data->id; -  return true; -} - -static inline void free_term_job_data_event(void **argv) -{ -  TerminalJobData *data = argv[0]; -  callback_free(&data->on_stdout); -  callback_free(&data->on_stderr); -  callback_free(&data->on_exit); - -  multiqueue_free(data->events); -  pmap_del(uint64_t)(jobs, data->id); -  xfree(data); -} - -static inline void free_term_job_data(TerminalJobData *data) -{ -  // data->queue may still be used after this function returns(process_wait), so -  // only free in the next event loop iteration -  multiqueue_put(main_loop.fast_events, free_term_job_data_event, 1, data); -} -// vimscript job callbacks must be executed on Nvim main loop -static inline void process_job_event(TerminalJobData *data, Callback *callback, -                                     const char *type, char *buf, size_t count, -                                     int status) +static Channel *find_job(uint64_t id, bool show_error)  { -  JobEvent event_data; -  event_data.received = NULL; -  if (buf) { -    event_data.received = tv_list_alloc(); -    char *ptr = buf; -    size_t remaining = count; -    size_t off = 0; - -    while (off < remaining) { -      // append the line -      if (ptr[off] == NL) { -        tv_list_append_string(event_data.received, ptr, off); -        size_t skip = off + 1; -        ptr += skip; -        remaining -= skip; -        off = 0; -        continue; -      } -      if (ptr[off] == NUL) { -        // Translate NUL to NL -        ptr[off] = NL; +  Channel *data = find_channel(id); +  if (!data || data->streamtype != kChannelStreamProc +      || process_is_stopped(&data->stream.proc)) { +    if (show_error) { +      if (data && data->streamtype != kChannelStreamProc) { +        EMSG(_(e_invchanjob)); +      } else { +        EMSG(_(e_invchan));        } -      off++;      } -    tv_list_append_string(event_data.received, ptr, off); -  } else { -    event_data.status = status; -  } -  event_data.data = data; -  event_data.callback = callback; -  event_data.type = type; -  on_job_event(&event_data); -} - -static void on_job_stdout(Stream *stream, RBuffer *buf, size_t count, -    void *job, bool eof) -{ -  TerminalJobData *data = job; -  on_job_output(stream, job, buf, count, eof, &data->on_stdout, "stdout"); -} - -static void on_job_stderr(Stream *stream, RBuffer *buf, size_t count, -    void *job, bool eof) -{ -  TerminalJobData *data = job; -  on_job_output(stream, job, buf, count, eof, &data->on_stderr, "stderr"); -} - -static void on_job_output(Stream *stream, TerminalJobData *data, RBuffer *buf, -                          size_t count, bool eof, Callback *callback, -                          const char *type) -{ -  if (eof) { -    return; -  } - -  // stub variable, to keep reading consistent with the order of events, only -  // consider the count parameter. -  size_t r; -  char *ptr = rbuffer_read_ptr(buf, &r); - -  // The order here matters, the terminal must receive the data first because -  // process_job_event will modify the read buffer(convert NULs into NLs) -  if (data->term) { -    terminal_receive(data->term, ptr, count); -  } - -  rbuffer_consumed(buf, count); -  if (callback->type != kCallbackNone) { -    process_job_event(data, callback, type, ptr, count, 0); -  } -} - -static void eval_job_process_exit_cb(Process *proc, int status, void *d) -{ -  TerminalJobData *data = d; -  if (data->term && !data->exited) { -    data->exited = true; -    char msg[sizeof("\r\n[Process exited ]") + NUMBUFLEN]; -    snprintf(msg, sizeof msg, "\r\n[Process exited %d]", proc->status); -    terminal_close(data->term, msg); -  } -  if (data->rpc) { -    channel_process_exit(data->id, status); -  } - -  if (data->status_ptr) { -    *data->status_ptr = status; -  } - -  process_job_event(data, &data->on_exit, "exit", NULL, 0, status); - -  term_job_data_decref(data); -} - -static void term_write(char *buf, size_t size, void *d) -{ -  TerminalJobData *job = d; -  if (job->in.closed) { -    // If the backing stream was closed abruptly, there may be write events -    // ahead of the terminal close event. Just ignore the writes. -    ILOG("write failed: stream is closed"); -    return; -  } -  WBuffer *wbuf = wstream_new_buffer(xmemdup(buf, size), size, 1, xfree); -  wstream_write(&job->in, wbuf); -} - -static void term_resize(uint16_t width, uint16_t height, void *d) -{ -  TerminalJobData *data = d; -  pty_process_resize(&data->proc.pty, width, height); -} - -static inline void term_delayed_free(void **argv) -{ -  TerminalJobData *j = argv[0]; -  if (j->in.pending_reqs || j->out.pending_reqs || j->err.pending_reqs) { -    multiqueue_put(j->events, term_delayed_free, 1, j); -    return; -  } - -  terminal_destroy(j->term); -  term_job_data_decref(j); -} - -static void term_close(void *d) -{ -  TerminalJobData *data = d; -  if (!data->exited) { -    data->exited = true; -    process_stop((Process *)&data->proc); -  } -  multiqueue_put(data->events, term_delayed_free, 1, data); -} - -static void term_job_data_decref(TerminalJobData *data) -{ -  if (!(--data->refcount)) { -    free_term_job_data(data); -  } -} - -static void on_job_event(JobEvent *ev) -{ -  if (!ev->callback) { -    return; -  } - -  typval_T argv[4]; - -  argv[0].v_type = VAR_NUMBER; -  argv[0].v_lock = 0; -  argv[0].vval.v_number = ev->data->id; - -  if (ev->received) { -    argv[1].v_type = VAR_LIST; -    argv[1].v_lock = 0; -    argv[1].vval.v_list = ev->received; -    argv[1].vval.v_list->lv_refcount++; -  } else { -    argv[1].v_type = VAR_NUMBER; -    argv[1].v_lock = 0; -    argv[1].vval.v_number = ev->status; -  } - -  argv[2].v_type = VAR_STRING; -  argv[2].v_lock = 0; -  argv[2].vval.v_string = (uint8_t *)ev->type; - -  typval_T rettv = TV_INITIAL_VALUE; -  callback_call(ev->callback, 3, argv, &rettv); -  tv_clear(&rettv); -} - -static TerminalJobData *find_job(uint64_t id) -{ -  TerminalJobData *data = pmap_get(uint64_t)(jobs, id); -  if (!data || data->stopped) {      return NULL;    }    return data;  } +  static void script_host_eval(char *name, typval_T *argvars, typval_T *rettv)  {    if (check_restricted() || check_secure()) { @@ -22446,7 +22482,7 @@ static void script_host_eval(char *name, typval_T *argvars, typval_T *rettv)      return;    } -  list_T *args = tv_list_alloc(); +  list_T *args = tv_list_alloc(1);    tv_list_append_string(args, (const char *)argvars[0].vval.v_string, -1);    *rettv = eval_call_provider(name, "eval", args);  } @@ -22464,7 +22500,6 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)      .sourcing_lnum = sourcing_lnum,      .autocmd_fname = autocmd_fname,      .autocmd_match = autocmd_match, -    .autocmd_fname_full = autocmd_fname_full,      .autocmd_bufnr = autocmd_bufnr,      .funccalp = save_funccal()    }; @@ -22475,8 +22510,8 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)      {.v_type = VAR_LIST, .vval.v_list = arguments, .v_lock = 0},      {.v_type = VAR_UNKNOWN}    }; -  typval_T rettv = {.v_type = VAR_UNKNOWN, .v_lock = 0}; -  arguments->lv_refcount++; +  typval_T rettv = { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED }; +  tv_list_ref(arguments);    int dummy;    (void)call_func((const char_u *)func, @@ -22497,13 +22532,14 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)    restore_funccal(provider_caller_scope.funccalp);    provider_caller_scope = saved_provider_caller_scope;    provider_call_nesting--; -   +  assert(provider_call_nesting >= 0); +    return rettv;  }  bool eval_has_provider(const char *name)  { -#define check_provider(name) \ +#define CHECK_PROVIDER(name) \    if (has_##name == -1) { \      has_##name = !!find_func((char_u *)"provider#" #name "#Call"); \      if (!has_##name) { \ @@ -22519,19 +22555,61 @@ bool eval_has_provider(const char *name)    static int has_python3 = -1;    static int has_ruby = -1; -  if (!strcmp(name, "clipboard")) { -    check_provider(clipboard); +  if (strequal(name, "clipboard")) { +    CHECK_PROVIDER(clipboard);      return has_clipboard; -  } else if (!strcmp(name, "python3")) { -    check_provider(python3); +  } else if (strequal(name, "python3")) { +    CHECK_PROVIDER(python3);      return has_python3; -  } else if (!strcmp(name, "python")) { -    check_provider(python); +  } else if (strequal(name, "python")) { +    CHECK_PROVIDER(python);      return has_python; -  } else if (!strcmp(name, "ruby")) { -    check_provider(ruby); +  } else if (strequal(name, "ruby")) { +    CHECK_PROVIDER(ruby);      return has_ruby;    }    return false;  } + +/// Writes "<sourcing_name>:<sourcing_lnum>" to `buf[bufsize]`. +void eval_fmt_source_name_line(char *buf, size_t bufsize) +{ +  if (sourcing_name) { +    snprintf(buf, bufsize, "%s:%" PRIdLINENR, sourcing_name, sourcing_lnum); +  } else { +    snprintf(buf, bufsize, "?"); +  } +} + +/// ":checkhealth [plugins]" +void ex_checkhealth(exarg_T *eap) +{ +  bool found = !!find_func((char_u *)"health#check"); +  if (!found +      && script_autoload("health#check", sizeof("health#check") - 1, false)) { +    found = !!find_func((char_u *)"health#check"); +  } +  if (!found) { +    const char *vimruntime_env = os_getenv("VIMRUNTIME"); +    if (vimruntime_env == NULL) { +      EMSG(_("E5009: $VIMRUNTIME is empty or unset")); +    } else { +      bool rtp_ok = NULL != strstr((char *)p_rtp, vimruntime_env); +      if (rtp_ok) { +        EMSG2(_("E5009: Invalid $VIMRUNTIME: %s"), vimruntime_env); +      } else { +        EMSG(_("E5009: Invalid 'runtimepath'")); +      } +    } +    return; +  } + +  size_t bufsize = STRLEN(eap->arg) + sizeof("call health#check('')"); +  char *buf = xmalloc(bufsize); +  snprintf(buf, bufsize, "call health#check('%s')", eap->arg); + +  do_cmdline_cmd(buf); + +  xfree(buf); +} | 
