From 96b358e9f138e60a24c3f3c9b45e9b82ffb92c1c Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 30 Jul 2024 11:08:16 +0800 Subject: vim-patch:partial:9.0.0327: items() does not work on a list Problem: items() does not work on a list. (Sergey Vlasov) Solution: Make items() work on a list. (closes vim/vim#11013) https://github.com/vim/vim/commit/976f859763b215050a03248dbc2bb62fa5d0d059 Skip CHECK_LIST_MATERIALIZE. Co-authored-by: Bram Moolenaar --- src/nvim/eval.h | 7 ---- src/nvim/eval/typval.c | 75 +++++++++++++++++++++++++++++++------- test/old/testdir/test_listdict.vim | 11 ++++++ 3 files changed, 73 insertions(+), 20 deletions(-) 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/typval.c b/src/nvim/eval/typval.c index 0d6de3c3e5..8fc8dcc479 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 @@ -87,6 +95,8 @@ static const char e_string_or_list_required_for_argument_nr[] = N_("E1222: String or List 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_list_or_dict_required_for_argument_nr[] + = N_("E1227: List or Dictionary required for argument %d"); static const char e_blob_required_for_argument_nr[] = N_("E1238: Blob required for argument %d"); static const char e_invalid_value_for_blob_nr[] @@ -782,6 +792,27 @@ 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++; + }); +} + /// Extend first list with the second /// /// @param[out] l1 List to extend. @@ -3134,35 +3165,38 @@ 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_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; @@ -3188,19 +3222,23 @@ 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_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 +4436,17 @@ 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 list or dict +int tv_check_for_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_LIST && args[idx].v_type != VAR_DICT) { + semsg(_(e_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..e976dc4c29 100644 --- a/test/old/testdir/test_listdict.vim +++ b/test/old/testdir/test_listdict.vim @@ -195,6 +195,17 @@ 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)', 'E1227:') +endfunc + " Test removing items in list func Test_list_func_remove() let lines =<< trim END -- cgit From 2dd0a90f211268517af30607c8eade0fef533edb Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 30 Jul 2024 11:53:00 +0800 Subject: vim-patch:9.0.0330: method tests fail Problem: Method tests fail. Solution: Adjust for change of items(). https://github.com/vim/vim/commit/f92cfb1acc3fef74eef0c83c1a35a2b6a9f93a9b Co-authored-by: Bram Moolenaar --- test/old/testdir/test_method.vim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/old/testdir/test_method.vim b/test/old/testdir/test_method.vim index 0c1d15f4ed..fd6e1b1523 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 "x"->items()', 'E1227:') eval 'abc'->printf('the %s arg')->assert_equal('the abc arg') endfunc -- cgit From 8ca3c1515c3fd5f70e870e535649a4c2afc5bbaf Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 30 Jul 2024 11:34:38 +0800 Subject: vim-patch:9.0.0331: cannot use items() on a string Problem: Cannot use items() on a string. Solution: Make items() work on a string. (closes vim/vim#11016) https://github.com/vim/vim/commit/3e518a8ec74065aedd67d352c93d6ae6be550316 Co-authored-by: Bram Moolenaar --- src/nvim/eval/typval.c | 53 ++++++++++++++++++++++++++------------ test/old/testdir/test_listdict.vim | 11 +++++++- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 8fc8dcc479..4458dba27d 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -93,10 +93,10 @@ 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_list_or_dict_required_for_argument_nr[] - = N_("E1227: List or Dictionary required for argument %d"); static const char e_blob_required_for_argument_nr[] = N_("E1238: Blob required for argument %d"); static const char e_invalid_value_for_blob_nr[] @@ -813,6 +813,30 @@ static void tv_list2items(typval_T *argvars, typval_T *rettv) }); } +/// "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. @@ -3172,7 +3196,7 @@ void tv_dict_alloc_ret(typval_T *const ret_tv) static void tv_dict2list(typval_T *const argvars, typval_T *const rettv, const DictListType what) { if ((what == kDict2ListItems - ? tv_check_for_list_or_dict_arg(argvars, 0) + ? 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; @@ -3202,15 +3226,8 @@ static void tv_dict2list(typval_T *const argvars, typval_T *const rettv, const D 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; } } @@ -3222,7 +3239,9 @@ static void tv_dict2list(typval_T *const argvars, typval_T *const rettv, const D /// "items(dict)" function void f_items(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - if (argvars[0].v_type == VAR_LIST) { + 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); @@ -4436,12 +4455,14 @@ 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 list or dict -int tv_check_for_list_or_dict_arg(const typval_T *const args, const int idx) +/// 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_LIST && args[idx].v_type != VAR_DICT) { - semsg(_(e_list_or_dict_required_for_argument_nr), idx + 1); + 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; diff --git a/test/old/testdir/test_listdict.vim b/test/old/testdir/test_listdict.vim index e976dc4c29..5e4a3fd1f8 100644 --- a/test/old/testdir/test_listdict.vim +++ b/test/old/testdir/test_listdict.vim @@ -203,7 +203,16 @@ func Test_list_items() endfor call assert_equal([[0, 'a'], [1, 'b'], [2, 'c']], r) - call assert_fails('call items(3)', 'E1227:') + 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 -- cgit From 520d94cc23416e14a4535c9ca6b7963681a7f461 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 30 Jul 2024 11:54:23 +0800 Subject: vim-patch:9.0.0333: method test fails Problem: Method test fails. Solution: Adjust test for items() now working on string. https://github.com/vim/vim/commit/171a1607f4b0b3cdcbbe5e886da37a5d11f15684 Co-authored-by: Bram Moolenaar --- test/old/testdir/test_method.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/old/testdir/test_method.vim b/test/old/testdir/test_method.vim index fd6e1b1523..1b57bba282 100644 --- a/test/old/testdir/test_method.vim +++ b/test/old/testdir/test_method.vim @@ -78,7 +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 "x"->items()', 'E1227:') + call assert_fails('eval 123->items()', 'E1225:') eval 'abc'->printf('the %s arg')->assert_equal('the abc arg') endfunc -- cgit From 0af056ebce189deedb58f7048108977fa5d81713 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 30 Jul 2024 11:46:24 +0800 Subject: vim-patch:49cdd62: runtime(doc): list of new/changed features in version9.txt closes: vim/vim#13753 https://github.com/vim/vim/commit/49cdd629a39d7e40e7349e65cb177e2442871a04 Co-authored-by: Yegappan Lakshmanan --- runtime/doc/builtin.txt | 4 ++++ runtime/doc/map.txt | 3 ++- runtime/lua/vim/_meta/vimfn.lua | 4 ++++ src/nvim/eval.lua | 5 ++++- 4 files changed, 14 insertions(+), 2 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* commands must terminate, that is, they must be followed by 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 in the {rhs}, use ||. 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.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' } }, -- cgit