neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

commit 9c3ba551288a9f05d489dfee1077bab7261644be
parent 992543c361630c73dd30f16916bd30fe63eb8033
Author: zeertzjq <zeertzjq@outlook.com>
Date:   Mon, 16 Feb 2026 21:39:23 +0800

Merge pull request #37902 from zeertzjq/vim-9.1.1668

vim-patch:partial:9.1.1668,9.1.1954
Diffstat:
Mruntime/doc/usr_41.txt | 1+
Mruntime/doc/vimfn.txt | 3++-
Mruntime/lua/vim/_meta/vimfn.lua | 3++-
Msrc/nvim/errors.h | 1+
Msrc/nvim/eval.c | 8++++++--
Msrc/nvim/eval.lua | 3++-
Msrc/nvim/eval/typval.c | 55+++++++++++++++++++++++++++++++++----------------------
Mtest/old/testdir/test_blob.vim | 20++++++++++++++++++++
Mtest/old/testdir/vim9.vim | 126++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
9 files changed, 166 insertions(+), 54 deletions(-)

diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt @@ -745,6 +745,7 @@ Blob manipulation: *blob-functions* reverse() reverse the order of numbers in a blob index() index of a value in a Blob indexof() index in a Blob where an expression is true + items() get List of Blob index-value pairs Other computation: *bitwise-function* and() bitwise AND diff --git a/runtime/doc/vimfn.txt b/runtime/doc/vimfn.txt @@ -5415,7 +5415,7 @@ items({expr}) *items()* Return a |List| with all key/index and value pairs of {expr}. Each |List| item is a list with two items: - for a |Dict|: the key and the value - - for a |List| or |String|: the index and the value + - for a |List|, |Blob| or |String|: the index and the value The returned |List| is in arbitrary order for a |Dict|, otherwise it's in ascending order of the index. @@ -5428,6 +5428,7 @@ items({expr}) *items()* endfor echo items([1, 2, 3]) echo items("foobar") + echo items(0z0102) < Parameters: ~ diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua @@ -4901,7 +4901,7 @@ function vim.fn.isnan(expr) end --- Return a |List| with all key/index and value pairs of {expr}. --- Each |List| item is a list with two items: --- - for a |Dict|: the key and the value ---- - for a |List| or |String|: the index and the value +--- - for a |List|, |Blob| or |String|: the index and the value --- The returned |List| is in arbitrary order for a |Dict|, --- otherwise it's in ascending order of the index. --- @@ -4914,6 +4914,7 @@ function vim.fn.isnan(expr) end --- endfor --- echo items([1, 2, 3]) --- echo items("foobar") +--- echo items(0z0102) --- < --- --- @param expr table|string diff --git a/src/nvim/errors.h b/src/nvim/errors.h @@ -197,6 +197,7 @@ EXTERN const char e_invalid_line_number_nr[] INIT(= N_("E966: Invalid line numbe EXTERN const char e_reduce_of_an_empty_str_with_no_initial_value[] INIT(= N_("E998: Reduce of an empty %s with no initial value")); +EXTERN const char e_invalid_value_for_blob_nr[] INIT(= N_("E1239: Invalid value for blob: 0x" PRIX64)); EXTERN const char e_stray_closing_curly_str[] INIT(= N_("E1278: Stray '}' without a matching '{': %s")); EXTERN const char e_missing_close_curly_str[] diff --git a/src/nvim/eval.c b/src/nvim/eval.c @@ -1315,9 +1315,13 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, bool copy, const bool } } else { bool error = false; - const char val = (char)tv_get_number_chk(rettv, &error); + const varnumber_T val = tv_get_number_chk(rettv, &error); if (!error) { - tv_blob_set_append(lp->ll_blob, lp->ll_n1, (uint8_t)val); + if (val < 0 || val > 255) { + semsg(_(e_invalid_value_for_blob_nr), val); + } else { + tv_blob_set_append(lp->ll_blob, lp->ll_n1, (uint8_t)val); + } } } } else if (op != NULL && *op != '=') { diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua @@ -6044,7 +6044,7 @@ M.funcs = { Return a |List| with all key/index and value pairs of {expr}. Each |List| item is a list with two items: - for a |Dict|: the key and the value - - for a |List| or |String|: the index and the value + - for a |List|, |Blob| or |String|: the index and the value The returned |List| is in arbitrary order for a |Dict|, otherwise it's in ascending order of the index. @@ -6057,6 +6057,7 @@ M.funcs = { endfor echo items([1, 2, 3]) echo items("foobar") + echo items(0z0102) < ]=], name = 'items', diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c @@ -93,14 +93,12 @@ 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_dict_blob_or_string_required_for_argument_nr[] + = N_("E1225: List, Dictionary, Blob or String 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[] = N_("E1238: Blob required for argument %d"); -static const char e_invalid_value_for_blob_nr[] - = N_("E1239: Invalid value for blob: %d"); static const char e_string_list_or_blob_required_for_argument_nr[] = N_("E1252: String, List or Blob required for argument %d"); static const char e_string_or_function_required_for_argument_nr[] @@ -793,6 +791,30 @@ void tv_list_flatten(list_T *list, listitem_T *first, int64_t maxitems, int64_t } } +/// "items(blob)" function +/// Converts a Blob into a List of [index, byte] pairs. +/// Caller must have already checked that argvars[0] is a Blob. +/// A null blob behaves like an empty blob. +static void tv_blob2items(typval_T *argvars, typval_T *rettv) +{ + blob_T *blob = argvars[0].vval.v_blob; + + tv_list_alloc_ret(rettv, tv_blob_len(blob)); + + for (int i = 0; i < tv_blob_len(blob); i++) { + list_T *l2 = tv_list_alloc(2); + tv_list_append_list(rettv->vval.v_list, l2); + tv_list_append_number(l2, i); + tv_list_append_number(l2, tv_blob_get(blob, i)); + } +} + +/// "items(dict)" function +static void tv_dict2items(typval_T *argvars, typval_T *rettv) +{ + tv_dict2list(argvars, rettv, kDict2ListItems); +} + /// "items(list)" function /// Caller must have already checked that argvars[0] is a List. static void tv_list2items(typval_T *argvars, typval_T *rettv) @@ -3226,9 +3248,7 @@ void tv_dict_alloc_ret(typval_T *const ret_tv) /// @param[in] what What to save in rettv. static void tv_dict2list(typval_T *const argvars, typval_T *const rettv, const DictListType what) { - if ((what == kDict2ListItems - ? tv_check_for_string_or_list_or_dict_arg(argvars, 0) - : tv_check_for_dict_arg(argvars, 0)) == FAIL) { + if (tv_check_for_dict_arg(argvars, 0) == FAIL) { tv_list_alloc_ret(rettv, 0); return; } @@ -3267,15 +3287,19 @@ static void tv_dict2list(typval_T *const argvars, typval_T *const rettv, const D }); } -/// "items(dict)" function +/// "items()" function void f_items(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type == VAR_STRING) { tv_string2items(argvars, rettv); } else if (argvars[0].v_type == VAR_LIST) { tv_list2items(argvars, rettv); + } else if (argvars[0].v_type == VAR_BLOB) { + tv_blob2items(argvars, rettv); + } else if (argvars[0].v_type == VAR_DICT) { + tv_dict2items(argvars, rettv); } else { - tv_dict2list(argvars, rettv, kDict2ListItems); + semsg(_(e_list_dict_blob_or_string_required_for_argument_nr), 1); } } @@ -4511,19 +4535,6 @@ 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_blob.vim b/test/old/testdir/test_blob.vim @@ -884,4 +884,24 @@ func Test_indexof() call assert_fails('let i = indexof(b, " ")', 'E15:') endfunc +" Test for using the items() function with a blob +func Test_blob_items() + let lines =<< trim END + call assert_equal([[0, 0xAA], [1, 0xBB], [2, 0xCC]], 0zAABBCC->items()) + call assert_equal([[0, 0]], 0z00->items()) + call assert_equal([], 0z->items()) + call assert_equal([], v:_null_blob->items()) + END + call CheckSourceLegacyAndVim9Success(lines) +endfunc + +" Test for setting a byte in a blob with invalid value +func Test_blob_byte_set_invalid_value() + let lines =<< trim END + VAR b = 0zD0C3E4E18E1B + LET b[0] = 229539777187355 + END + call CheckSourceLegacyAndVim9Failure(lines, 'E1239: Invalid value for blob:') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/vim9.vim b/test/old/testdir/vim9.vim @@ -89,20 +89,24 @@ func CheckLegacyFailure(lines, error) endtry endfunc +" Translate "lines" to legacy Vim script +func s:LegacyTrans(lines) + return a:lines->mapnew({_, v -> + \ v->substitute('\<VAR\>', 'let', 'g') + \ ->substitute('\<LET\>', 'let', 'g') + \ ->substitute('\<LSTART\>', '{', 'g') + \ ->substitute('\<LMIDDLE\>', '->', 'g') + \ ->substitute('\<LEND\>', '}', 'g') + \ ->substitute('\<TRUE\>', '1', 'g') + \ ->substitute('\<FALSE\>', '0', 'g') + \ ->substitute('#"', ' "', 'g') + \ }) +endfunc + " Execute "lines" in a legacy function, translated as in " CheckLegacyAndVim9Success() func CheckTransLegacySuccess(lines) - let legacylines = a:lines->mapnew({_, v -> - \ v->substitute('\<VAR\>', 'let', 'g') - \ ->substitute('\<LET\>', 'let', 'g') - \ ->substitute('\<LSTART\>', '{', 'g') - \ ->substitute('\<LMIDDLE\>', '->', 'g') - \ ->substitute('\<LEND\>', '}', 'g') - \ ->substitute('\<TRUE\>', '1', 'g') - \ ->substitute('\<FALSE\>', '0', 'g') - \ ->substitute('#"', ' "', 'g') - \ }) - call CheckLegacySuccess(legacylines) + call CheckLegacySuccess(s:LegacyTrans(a:lines)) endfunc func CheckTransDefSuccess(lines) @@ -143,6 +147,65 @@ func CheckLegacyAndVim9Failure(lines, error) call CheckLegacyFailure(legacylines, legacyError) endfunc +" Check that "lines" inside a legacy function has no error. +func CheckSourceLegacySuccess(lines) + let cwd = getcwd() + new + call setline(1, ['func Func()'] + a:lines + ['endfunc', 'call Func()']) + let bnr = bufnr() + try + :source + finally + delfunc! Func + call chdir(cwd) + exe $':bw! {bnr}' + endtry +endfunc + +" Check that "lines" inside a legacy function results in the expected error +func CheckSourceLegacyFailure(lines, error) + let cwd = getcwd() + new + call setline(1, ['func Func()'] + a:lines + ['endfunc', 'call Func()']) + let bnr = bufnr() + try + call assert_fails('source', a:error) + finally + delfunc! Func + call chdir(cwd) + exe $':bw! {bnr}' + endtry +endfunc + +" Execute "lines" in a legacy function, translated as in +" CheckSourceLegacyAndVim9Success() +func CheckSourceTransLegacySuccess(lines) + call CheckSourceLegacySuccess(s:LegacyTrans(a:lines)) +endfunc + +" Execute "lines" in a :def function, translated as in +" CheckLegacyAndVim9Success() +func CheckSourceTransDefSuccess(lines) + return +endfunc + +" Execute "lines" in a Vim9 script, translated as in +" CheckLegacyAndVim9Success() +func CheckSourceTransVim9Success(lines) + return +endfunc + +" Execute "lines" in a legacy function, :def function and Vim9 script. +" Use 'VAR' for a declaration. +" Use 'LET' for an assignment +" Use ' #"' for a comment +" Use LSTART arg LMIDDLE expr LEND for lambda +" Use 'TRUE' for 1 in legacy, true in Vim9 +" Use 'FALSE' for 0 in legacy, false in Vim9 +func CheckSourceLegacyAndVim9Success(lines) + call CheckSourceTransLegacySuccess(a:lines) +endfunc + " :source a list of "lines" and check whether it fails with "error" func CheckSourceScriptFailure(lines, error, lnum = -3) if get(a:lines, 0, '') ==# 'vim9script' @@ -195,26 +258,10 @@ func CheckSourceScriptSuccess(lines) endtry endfunc -func CheckSourceSuccess(lines) - call CheckSourceScriptSuccess(a:lines) -endfunc - -func CheckSourceFailure(lines, error, lnum = -3) - call CheckSourceScriptFailure(a:lines, a:error, a:lnum) -endfunc - -func CheckSourceFailureList(lines, errors, lnum = -3) - call CheckSourceScriptFailureList(a:lines, a:errors, a:lnum) -endfunc - func CheckSourceDefSuccess(lines) return endfunc -func CheckSourceDefAndScriptSuccess(lines) - return -endfunc - func CheckSourceDefCompileSuccess(lines) return endfunc @@ -235,3 +282,28 @@ func CheckSourceDefExecAndScriptFailure(lines, error, lnum = -3) return endfunc +func CheckSourceSuccess(lines) + call CheckSourceScriptSuccess(a:lines) +endfunc + +func CheckSourceFailure(lines, error, lnum = -3) + call CheckSourceScriptFailure(a:lines, a:error, a:lnum) +endfunc + +func CheckSourceFailureList(lines, errors, lnum = -3) + call CheckSourceScriptFailureList(a:lines, a:errors, a:lnum) +endfunc + +func CheckSourceDefAndScriptSuccess(lines) + return +endfunc + +" Execute "lines" in a legacy function, :def function and Vim9 script. +" Use 'VAR' for a declaration. +" Use 'LET' for an assignment +" Use ' #"' for a comment +func CheckSourceLegacyAndVim9Failure(lines, error) + let legacyError = type(a:error) == type('string') ? a:error : a:error[0] + call CheckSourceLegacyFailure(s:LegacyTrans(a:lines), legacyError) +endfunc +