diff options
-rw-r--r-- | runtime/doc/builtin.txt | 4 | ||||
-rw-r--r-- | runtime/doc/map.txt | 3 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/vimfn.lua | 4 | ||||
-rw-r--r-- | src/nvim/eval.h | 7 | ||||
-rw-r--r-- | src/nvim/eval.lua | 5 | ||||
-rw-r--r-- | src/nvim/eval/typval.c | 112 | ||||
-rw-r--r-- | test/old/testdir/test_listdict.vim | 20 | ||||
-rw-r--r-- | test/old/testdir/test_method.vim | 6 |
8 files changed, 128 insertions, 33 deletions
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 08a9022aff..4ef1573494 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -3901,6 +3901,10 @@ items({dict}) *items()* for [key, value] in items(mydict) echo key .. ': ' .. value endfor +< + A List or a String argument is also supported. In these + cases, items() returns a List with the index and the value at + the index. jobpid({job}) *jobpid()* Return the PID (process id) of |job-id| {job}. diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt index ce2fbda045..e6eb01dae3 100644 --- a/runtime/doc/map.txt +++ b/runtime/doc/map.txt @@ -354,7 +354,8 @@ Note: *E1255* *E1136* <Cmd> commands must terminate, that is, they must be followed by <CR> in the -{rhs} of the mapping definition. |Command-line| mode is never entered. +{rhs} of the mapping definition. |Command-line| mode is never entered. To use +a literal <CR> in the {rhs}, use |<lt>|. 1.3 MAPPING AND MODES *:map-modes* diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index bd6550941d..17cd6e3318 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -4699,6 +4699,10 @@ function vim.fn.isnan(expr) end --- for [key, value] in items(mydict) --- echo key .. ': ' .. value --- endfor +--- < +--- A List or a String argument is also supported. In these +--- cases, items() returns a List with the index and the value at +--- the index. --- --- @param dict any --- @return any diff --git a/src/nvim/eval.h b/src/nvim/eval.h index d83af70ef7..004fc82222 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -237,13 +237,6 @@ typedef enum { EXPR_ISNOT, ///< isnot } exprtype_T; -/// Type for dict_list function -typedef enum { - kDictListKeys, ///< List dictionary keys. - kDictListValues, ///< List dictionary values. - kDictListItems, ///< List dictionary contents: [keys, values]. -} DictListType; - // Used for checking if local variables or arguments used in a lambda. extern bool *eval_lavars_used; diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 4bc0827bcc..687d052d19 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -5752,7 +5752,10 @@ M.funcs = { for [key, value] in items(mydict) echo key .. ': ' .. value endfor - + < + A List or a String argument is also supported. In these + cases, items() returns a List with the index and the value at + the index. ]=], name = 'items', params = { { 'dict', 'any' } }, diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 0d6de3c3e5..4458dba27d 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -16,6 +16,7 @@ #include "nvim/eval/executor.h" #include "nvim/eval/gc.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/typval_encode.h" #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" @@ -59,6 +60,13 @@ typedef struct { typedef int (*ListSorter)(const void *, const void *); +/// Type for tv_dict2list() function +typedef enum { + kDict2ListKeys, ///< List dictionary keys. + kDict2ListValues, ///< List dictionary values. + kDict2ListItems, ///< List dictionary contents: [keys, values]. +} DictListType; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/typval.c.generated.h" #endif @@ -85,6 +93,8 @@ static const char e_string_or_number_required_for_argument_nr[] = N_("E1220: String or Number required for argument %d"); static const char e_string_or_list_required_for_argument_nr[] = N_("E1222: String or List required for argument %d"); +static const char e_string_list_or_dict_required_for_argument_nr[] + = N_("E1225: String, List or Dictionary required for argument %d"); static const char e_list_or_blob_required_for_argument_nr[] = N_("E1226: List or Blob required for argument %d"); static const char e_blob_required_for_argument_nr[] @@ -782,6 +792,51 @@ void tv_list_flatten(list_T *list, listitem_T *first, int64_t maxitems, int64_t } } +/// "items(list)" function +/// Caller must have already checked that argvars[0] is a List. +static void tv_list2items(typval_T *argvars, typval_T *rettv) +{ + list_T *l = argvars[0].vval.v_list; + + tv_list_alloc_ret(rettv, tv_list_len(l)); + if (l == NULL) { + return; // null list behaves like an empty list + } + + varnumber_T idx = 0; + TV_LIST_ITER(l, li, { + list_T *l2 = tv_list_alloc(2); + tv_list_append_list(rettv->vval.v_list, l2); + tv_list_append_number(l2, idx); + tv_list_append_tv(l2, TV_LIST_ITEM_TV(li)); + idx++; + }); +} + +/// "items(string)" function +/// Caller must have already checked that argvars[0] is a String. +static void tv_string2items(typval_T *argvars, typval_T *rettv) +{ + const char *p = argvars[0].vval.v_string; + + tv_list_alloc_ret(rettv, kListLenMayKnow); + if (p == NULL) { + return; // null string behaves like an empty string + } + + for (varnumber_T idx = 0; *p != NUL; idx++) { + int len = utfc_ptr2len(p); + if (len == 0) { + break; + } + list_T *l2 = tv_list_alloc(2); + tv_list_append_list(rettv->vval.v_list, l2); + tv_list_append_number(l2, idx); + tv_list_append_string(l2, p, len); + p += len; + } +} + /// Extend first list with the second /// /// @param[out] l1 List to extend. @@ -3134,49 +3189,45 @@ void tv_dict_alloc_ret(typval_T *const ret_tv) /// Turn a dictionary into a list /// -/// @param[in] tv Dictionary to convert. Is checked for actually being +/// @param[in] argvars Arguments to items(). The first argument is check for being /// a dictionary, will give an error if not. /// @param[out] rettv Location where result will be saved. /// @param[in] what What to save in rettv. -static void tv_dict_list(typval_T *const tv, typval_T *const rettv, const DictListType what) +static void tv_dict2list(typval_T *const argvars, typval_T *const rettv, const DictListType what) { - if (tv->v_type != VAR_DICT) { - emsg(_(e_dictreq)); + if ((what == kDict2ListItems + ? tv_check_for_string_or_list_or_dict_arg(argvars, 0) + : tv_check_for_dict_arg(argvars, 0)) == FAIL) { + tv_list_alloc_ret(rettv, 0); return; } - tv_list_alloc_ret(rettv, tv_dict_len(tv->vval.v_dict)); - if (tv->vval.v_dict == NULL) { + dict_T *d = argvars[0].vval.v_dict; + tv_list_alloc_ret(rettv, tv_dict_len(d)); + if (d == NULL) { // NULL dict behaves like an empty dict return; } - TV_DICT_ITER(tv->vval.v_dict, di, { + TV_DICT_ITER(d, di, { typval_T tv_item = { .v_lock = VAR_UNLOCKED }; switch (what) { - case kDictListKeys: + case kDict2ListKeys: tv_item.v_type = VAR_STRING; tv_item.vval.v_string = xstrdup(di->di_key); break; - case kDictListValues: + case kDict2ListValues: tv_copy(&di->di_tv, &tv_item); break; - case kDictListItems: { + case kDict2ListItems: { // items() list_T *const sub_l = tv_list_alloc(2); tv_item.v_type = VAR_LIST; tv_item.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 = xstrdup(di->di_key), - }); - + tv_list_append_string(sub_l, di->di_key, -1); tv_list_append_tv(sub_l, &di->di_tv); - break; } } @@ -3188,19 +3239,25 @@ static void tv_dict_list(typval_T *const tv, typval_T *const rettv, const DictLi /// "items(dict)" function void f_items(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - tv_dict_list(argvars, rettv, 2); + if (argvars[0].v_type == VAR_STRING) { + tv_string2items(argvars, rettv); + } else if (argvars[0].v_type == VAR_LIST) { + tv_list2items(argvars, rettv); + } else { + tv_dict2list(argvars, rettv, kDict2ListItems); + } } /// "keys()" function void f_keys(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - tv_dict_list(argvars, rettv, 0); + tv_dict2list(argvars, rettv, kDict2ListKeys); } /// "values(dict)" function void f_values(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - tv_dict_list(argvars, rettv, 1); + tv_dict2list(argvars, rettv, kDict2ListValues); } /// "has_key()" function @@ -4398,6 +4455,19 @@ int tv_check_for_opt_string_or_list_arg(const typval_T *const args, const int id || tv_check_for_string_or_list_arg(args, idx) != FAIL) ? OK : FAIL; } +/// Give an error and return FAIL unless "args[idx]" is a string or a list or a dict +int tv_check_for_string_or_list_or_dict_arg(const typval_T *const args, const int idx) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + if (args[idx].v_type != VAR_STRING + && args[idx].v_type != VAR_LIST + && args[idx].v_type != VAR_DICT) { + semsg(_(e_string_list_or_dict_required_for_argument_nr), idx + 1); + return FAIL; + } + return OK; +} + /// Give an error and return FAIL unless "args[idx]" is a string /// or a function reference. int tv_check_for_string_or_func_arg(const typval_T *const args, const int idx) diff --git a/test/old/testdir/test_listdict.vim b/test/old/testdir/test_listdict.vim index 35d63c87d0..5e4a3fd1f8 100644 --- a/test/old/testdir/test_listdict.vim +++ b/test/old/testdir/test_listdict.vim @@ -195,6 +195,26 @@ func Test_list_range_assign() call CheckDefAndScriptFailure(lines, 'E1012:', 2) endfunc +func Test_list_items() + let r = [] + let l = ['a', 'b', 'c'] + for [idx, val] in items(l) + call extend(r, [[idx, val]]) + endfor + call assert_equal([[0, 'a'], [1, 'b'], [2, 'c']], r) + + call assert_fails('call items(3)', 'E1225:') +endfunc + +func Test_string_items() + let r = [] + let s = 'ábツ' + for [idx, val] in items(s) + call extend(r, [[idx, val]]) + endfor + call assert_equal([[0, 'á'], [1, 'b'], [2, 'ツ']], r) +endfunc + " Test removing items in list func Test_list_func_remove() let lines =<< trim END diff --git a/test/old/testdir/test_method.vim b/test/old/testdir/test_method.vim index 0c1d15f4ed..1b57bba282 100644 --- a/test/old/testdir/test_method.vim +++ b/test/old/testdir/test_method.vim @@ -20,9 +20,8 @@ func Test_list_method() call assert_equal(2, l->get(1)) call assert_equal(1, l->index(2)) call assert_equal([0, 1, 2, 3], [1, 2, 3]->insert(0)) - call assert_fails('eval l->items()', 'E715:') call assert_equal('1 2 3', l->join()) - call assert_fails('eval l->keys()', 'E715:') + call assert_fails('eval l->keys()', 'E1206:') call assert_equal(3, l->len()) call assert_equal([2, 3, 4], [1, 2, 3]->map('v:val + 1')) call assert_equal(3, l->max()) @@ -34,7 +33,7 @@ func Test_list_method() call assert_equal('[1, 2, 3]', l->string()) call assert_equal(v:t_list, l->type()) call assert_equal([1, 2, 3], [1, 1, 2, 3, 3]->uniq()) - call assert_fails('eval l->values()', 'E715:') + call assert_fails('eval l->values()', 'E1206:') call assert_fails('echo []->len', 'E107:') endfunc @@ -79,6 +78,7 @@ func Test_string_method() eval "a\rb\ec"->strtrans()->assert_equal('a^Mb^[c') eval "aあb"->strwidth()->assert_equal(4) eval 'abc'->substitute('b', 'x', '')->assert_equal('axc') + call assert_fails('eval 123->items()', 'E1225:') eval 'abc'->printf('the %s arg')->assert_equal('the abc arg') endfunc |