vim-patch:partial:9.1.1668: items() does not work for Blobs

Problem:  items() does not work for Blobs
Solution: Extend items() to support Blob
          (Yegappan Lakshmanan).

closes: vim/vim#18080

da34f84847

Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>
This commit is contained in:
zeertzjq
2026-02-16 20:37:43 +08:00
parent 992543c361
commit cbec4603a0
7 changed files with 150 additions and 50 deletions

View File

@@ -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

View File

@@ -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: ~

View File

@@ -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

View File

@@ -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',

View File

@@ -93,8 +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_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[]
@@ -793,6 +793,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 +3250,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 +3289,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 +4537,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)

View File

@@ -884,4 +884,15 @@ 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
" vim: shiftwidth=2 sts=2 expandtab

View File

@@ -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