diff options
Diffstat (limited to 'src/nvim/eval/funcs.c')
-rw-r--r-- | src/nvim/eval/funcs.c | 132 |
1 files changed, 132 insertions, 0 deletions
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index f1852f1b6d..13d8f52768 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -3488,6 +3488,138 @@ static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } +/// Evaluate "expr" with the v:key and v:val arguments and return the result. +/// The expression is expected to return a boolean value. The caller should set +/// the VV_KEY and VV_VAL vim variables before calling this function. +static varnumber_T indexof_eval_expr(typval_T *expr) +{ + typval_T argv[3]; + argv[0] = *get_vim_var_tv(VV_KEY); + argv[1] = *get_vim_var_tv(VV_VAL); + typval_T newtv; + newtv.v_type = VAR_UNKNOWN; + + if (eval_expr_typval(expr, argv, 2, &newtv) == FAIL) { + return false; + } + + bool error = false; + varnumber_T found = tv_get_bool_chk(&newtv, &error); + tv_clear(&newtv); + + return error ? false : found; +} + +/// Evaluate "expr" for each byte in the Blob "b" starting with the byte at +/// "startidx" and return the index of the byte where "expr" is TRUE. Returns +/// -1 if "expr" doesn't evaluate to TRUE for any of the bytes. +static varnumber_T indexof_blob(blob_T *b, varnumber_T startidx, typval_T *expr) +{ + if (b == NULL) { + return -1; + } + + if (startidx < 0) { + // negative index: index from the last byte + startidx = tv_blob_len(b) + startidx; + if (startidx < 0) { + startidx = 0; + } + } + + for (varnumber_T idx = startidx; idx < tv_blob_len(b); idx++) { + set_vim_var_nr(VV_KEY, idx); + set_vim_var_nr(VV_VAL, tv_blob_get(b, (int)idx)); + + if (indexof_eval_expr(expr)) { + return idx; + } + } + + return -1; +} + +/// Evaluate "expr" for each item in the List "l" starting with the item at +/// "startidx" and return the index of the item where "expr" is TRUE. Returns +/// -1 if "expr" doesn't evaluate to TRUE for any of the items. +static varnumber_T indexof_list(list_T *l, varnumber_T startidx, typval_T *expr) +{ + if (l == NULL) { + return -1; + } + + listitem_T *item; + varnumber_T idx = 0; + if (startidx == 0) { + item = tv_list_first(l); + } else { + // Start at specified item. + idx = tv_list_uidx(l, (int)startidx); + if (idx == -1) { + item = NULL; + } else { + item = tv_list_find(l, (int)idx); + assert(item != NULL); + } + } + + for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) { + set_vim_var_nr(VV_KEY, idx); + tv_copy(TV_LIST_ITEM_TV(item), get_vim_var_tv(VV_VAL)); + + bool found = indexof_eval_expr(expr); + tv_clear(get_vim_var_tv(VV_VAL)); + + if (found) { + return idx; + } + } + + return -1; +} + +/// "indexof()" function +static void f_indexof(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = -1; + + if (tv_check_for_list_or_blob_arg(argvars, 0) == FAIL + || tv_check_for_string_or_func_arg(argvars, 1) == FAIL + || tv_check_for_opt_dict_arg(argvars, 2) == FAIL) { + return; + } + + if ((argvars[1].v_type == VAR_STRING && argvars[1].vval.v_string == NULL) + || (argvars[1].v_type == VAR_FUNC && argvars[1].vval.v_partial == NULL)) { + return; + } + + varnumber_T startidx = 0; + if (argvars[2].v_type == VAR_DICT) { + startidx = tv_dict_get_number_def(argvars[2].vval.v_dict, "startidx", 0); + } + + typval_T save_val; + typval_T save_key; + prepare_vimvar(VV_VAL, &save_val); + prepare_vimvar(VV_KEY, &save_key); + + // We reset "did_emsg" to be able to detect whether an error occurred + // during evaluation of the expression. + const int save_did_emsg = did_emsg; + did_emsg = false; + + if (argvars[0].v_type == VAR_BLOB) { + rettv->vval.v_number = indexof_blob(argvars[0].vval.v_blob, startidx, &argvars[1]); + } else { + rettv->vval.v_number = indexof_list(argvars[0].vval.v_list, startidx, &argvars[1]); + } + + restore_vimvar(VV_KEY, &save_key); + restore_vimvar(VV_VAL, &save_val); + did_emsg |= save_did_emsg; +} + static bool inputsecret_flag = false; /// "input()" function |