patch 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: #18080
Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
			
			
This commit is contained in:
		
				
					committed by
					
						 Christian Brabandt
						Christian Brabandt
					
				
			
			
				
	
			
			
			
						parent
						
							6b56711804
						
					
				
				
					commit
					da34f84847
				
			| @ -1,4 +1,4 @@ | |||||||
| *builtin.txt*	For Vim version 9.1.  Last change: 2025 Aug 20 | *builtin.txt*	For Vim version 9.1.  Last change: 2025 Aug 23 | ||||||
|  |  | ||||||
|  |  | ||||||
| 		  VIM REFERENCE MANUAL	  by Bram Moolenaar | 		  VIM REFERENCE MANUAL	  by Bram Moolenaar | ||||||
| @ -344,7 +344,7 @@ isinf({expr})			Number	determine if {expr} is infinity value | |||||||
| 					(positive or negative) | 					(positive or negative) | ||||||
| islocked({expr})		Number	|TRUE| if {expr} is locked | islocked({expr})		Number	|TRUE| if {expr} is locked | ||||||
| isnan({expr})			Number	|TRUE| if {expr} is NaN | isnan({expr})			Number	|TRUE| if {expr} is NaN | ||||||
| items({expr})			List	key-value pairs in {expr} | items({expr})			List	key/index-value pairs in {expr} | ||||||
| job_getchannel({job})		Channel	get the channel handle for {job} | job_getchannel({job})		Channel	get the channel handle for {job} | ||||||
| job_info([{job}])		Dict	get information about {job} | job_info([{job}])		Dict	get information about {job} | ||||||
| job_setoptions({job}, {options}) none	set options for {job} | job_setoptions({job}, {options}) none	set options for {job} | ||||||
| @ -6314,7 +6314,8 @@ items({expr})						*items()* | |||||||
| 		Return a |List| with all key/index and value pairs of {expr}. | 		Return a |List| with all key/index and value pairs of {expr}. | ||||||
| 		Each |List| item is a list with two items: | 		Each |List| item is a list with two items: | ||||||
| 		- for a |Dict|: the key and the value | 		- for a |Dict|: the key and the value | ||||||
| 		- for a |List|, |Tuple| or |String|: the index and the value | 		- for a |List|, |Tuple|, |Blob| or |String|: the index and the | ||||||
|  | 		  value | ||||||
| 		The returned |List| is in arbitrary order for a |Dict|, | 		The returned |List| is in arbitrary order for a |Dict|, | ||||||
| 		otherwise it's in ascending order of the index. | 		otherwise it's in ascending order of the index. | ||||||
|  |  | ||||||
| @ -6328,6 +6329,7 @@ items({expr})						*items()* | |||||||
| 			echo items([1, 2, 3]) | 			echo items([1, 2, 3]) | ||||||
| 			echo items(('a', 'b', 'c')) | 			echo items(('a', 'b', 'c')) | ||||||
| 			echo items("foobar") | 			echo items("foobar") | ||||||
|  | 			echo items(0z0102) | ||||||
| < | < | ||||||
| 		Can also be used as a |method|: > | 		Can also be used as a |method|: > | ||||||
| 			mydict->items() | 			mydict->items() | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| *usr_41.txt*	For Vim version 9.1.  Last change: 2025 Aug 18 | *usr_41.txt*	For Vim version 9.1.  Last change: 2025 Aug 23 | ||||||
|  |  | ||||||
| 		     VIM USER MANUAL - by Bram Moolenaar | 		     VIM USER MANUAL - by Bram Moolenaar | ||||||
|  |  | ||||||
| @ -920,6 +920,7 @@ Blob manipulation:					*blob-functions* | |||||||
| 	reverse()		reverse the order of numbers in a blob | 	reverse()		reverse the order of numbers in a blob | ||||||
| 	index()			index of a value in a Blob | 	index()			index of a value in a Blob | ||||||
| 	indexof()		index in a Blob where an expression is true | 	indexof()		index in a Blob where an expression is true | ||||||
|  | 	items()			get List of Blob index-value pairs | ||||||
|  |  | ||||||
| Other computation:					*bitwise-function* | Other computation:					*bitwise-function* | ||||||
| 	and()			bitwise AND | 	and()			bitwise AND | ||||||
|  | |||||||
| @ -41648,6 +41648,8 @@ Other new features ~ | |||||||
| - Add the new default highlighting groups "Bold", "Italic" and "BoldItalic" | - Add the new default highlighting groups "Bold", "Italic" and "BoldItalic" | ||||||
|   for use in syntax scripts. |   for use in syntax scripts. | ||||||
| 
 | 
 | ||||||
|  | - |items()| function now supports Blob. | ||||||
|  | 
 | ||||||
| 							*changed-9.2* | 							*changed-9.2* | ||||||
| Changed~ | Changed~ | ||||||
| ------- | ------- | ||||||
|  | |||||||
							
								
								
									
										32
									
								
								src/blob.c
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								src/blob.c
									
									
									
									
									
								
							| @ -289,6 +289,38 @@ blob2string(blob_T *blob, char_u **tofree, char_u *numbuf) | |||||||
|     return *tofree; |     return *tofree; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * "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. | ||||||
|  |  */ | ||||||
|  |     void | ||||||
|  | blob2items(typval_T *argvars, typval_T *rettv) | ||||||
|  | { | ||||||
|  |     blob_T	*blob = argvars[0].vval.v_blob; | ||||||
|  |  | ||||||
|  |     if (rettv_list_alloc(rettv) == FAIL) | ||||||
|  | 	return; | ||||||
|  |  | ||||||
|  |     for (int i = 0; i < blob_len(blob); i++) | ||||||
|  |     { | ||||||
|  | 	list_T	*l2 = list_alloc(); | ||||||
|  | 	if (l2 == NULL) | ||||||
|  | 	    return; | ||||||
|  |  | ||||||
|  | 	if (list_append_list(rettv->vval.v_list, l2) == FAIL) | ||||||
|  | 	{ | ||||||
|  | 	    vim_free(l2); | ||||||
|  | 	    return; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (list_append_number(l2, i) == FAIL | ||||||
|  | 		|| list_append_number(l2, blob_get(blob, i)) == FAIL) | ||||||
|  | 	    return; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Convert a string variable, in the format of blob2string(), to a blob. |  * Convert a string variable, in the format of blob2string(), to a blob. | ||||||
|  * Return NULL when conversion failed. |  * Return NULL when conversion failed. | ||||||
|  | |||||||
							
								
								
									
										17
									
								
								src/dict.c
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								src/dict.c
									
									
									
									
									
								
							| @ -1557,9 +1557,7 @@ dict2list(typval_T *argvars, typval_T *rettv, dict2list_T what) | |||||||
|     if (rettv_list_alloc(rettv) == FAIL) |     if (rettv_list_alloc(rettv) == FAIL) | ||||||
| 	return; | 	return; | ||||||
|  |  | ||||||
|     if ((what == DICT2LIST_ITEMS |     if (check_for_dict_arg(argvars, 0) == FAIL) | ||||||
| 		? check_for_string_list_tuple_or_dict_arg(argvars, 0) |  | ||||||
| 		: check_for_dict_arg(argvars, 0)) == FAIL) |  | ||||||
| 	return; | 	return; | ||||||
|  |  | ||||||
|     d = argvars[0].vval.v_dict; |     d = argvars[0].vval.v_dict; | ||||||
| @ -1612,19 +1610,12 @@ dict2list(typval_T *argvars, typval_T *rettv, dict2list_T what) | |||||||
| } | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * "items(dict)" function |  * "items()" function | ||||||
|  */ |  */ | ||||||
|     void |     void | ||||||
| f_items(typval_T *argvars, typval_T *rettv) | dict2items(typval_T *argvars, typval_T *rettv) | ||||||
| { | { | ||||||
|     if (argvars[0].v_type == VAR_STRING) |     dict2list(argvars, rettv, DICT2LIST_ITEMS); | ||||||
| 	string2items(argvars, rettv); |  | ||||||
|     else if (argvars[0].v_type == VAR_LIST) |  | ||||||
| 	list2items(argvars, rettv); |  | ||||||
|     else if (argvars[0].v_type == VAR_TUPLE) |  | ||||||
| 	tuple2items(argvars, rettv); |  | ||||||
|     else |  | ||||||
| 	dict2list(argvars, rettv, DICT2LIST_ITEMS); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  | |||||||
| @ -97,6 +97,7 @@ static void f_inputsecret(typval_T *argvars, typval_T *rettv); | |||||||
| static void f_interrupt(typval_T *argvars, typval_T *rettv); | static void f_interrupt(typval_T *argvars, typval_T *rettv); | ||||||
| static void f_invert(typval_T *argvars, typval_T *rettv); | static void f_invert(typval_T *argvars, typval_T *rettv); | ||||||
| static void f_islocked(typval_T *argvars, typval_T *rettv); | static void f_islocked(typval_T *argvars, typval_T *rettv); | ||||||
|  | static void f_items(typval_T *argvars, typval_T *rettv); | ||||||
| static void f_keytrans(typval_T *argvars, typval_T *rettv); | static void f_keytrans(typval_T *argvars, typval_T *rettv); | ||||||
| static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv); | static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv); | ||||||
| static void f_libcall(typval_T *argvars, typval_T *rettv); | static void f_libcall(typval_T *argvars, typval_T *rettv); | ||||||
| @ -726,11 +727,12 @@ arg_list_tuple_dict_blob_or_string( | |||||||
| 	    || type->tt_type == VAR_STRING | 	    || type->tt_type == VAR_STRING | ||||||
| 	    || type_any_or_unknown(type)) | 	    || type_any_or_unknown(type)) | ||||||
| 	return OK; | 	return OK; | ||||||
|     arg_type_mismatch(&t_list_any, type, context->arg_idx + 1); |  | ||||||
|  |     semsg(_(e_list_tuple_dict_blob_or_string_required_for_argument_nr), | ||||||
|  | 						 context->arg_idx + 1); | ||||||
|     return FAIL; |     return FAIL; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Check second argument of map(), filter(), foreach(). |  * Check second argument of map(), filter(), foreach(). | ||||||
|  */ |  */ | ||||||
| @ -1248,7 +1250,7 @@ static argcheck_T arg1_list_number[] = {arg_list_number}; | |||||||
| static argcheck_T arg1_reverse[] = {arg_reverse}; | static argcheck_T arg1_reverse[] = {arg_reverse}; | ||||||
| static argcheck_T arg1_list_or_tuple_or_dict[] = {arg_list_or_tuple_or_dict}; | static argcheck_T arg1_list_or_tuple_or_dict[] = {arg_list_or_tuple_or_dict}; | ||||||
| static argcheck_T arg1_list_string[] = {arg_list_string}; | static argcheck_T arg1_list_string[] = {arg_list_string}; | ||||||
| static argcheck_T arg1_string_list_tuple_or_dict[] = {arg_string_list_tuple_or_dict}; | static argcheck_T arg1_list_tuple_dict_blob_or_string[] = {arg_list_tuple_dict_blob_or_string}; | ||||||
| static argcheck_T arg1_lnum[] = {arg_lnum}; | static argcheck_T arg1_lnum[] = {arg_lnum}; | ||||||
| static argcheck_T arg1_number[] = {arg_number}; | static argcheck_T arg1_number[] = {arg_number}; | ||||||
| static argcheck_T arg1_string[] = {arg_string}; | static argcheck_T arg1_string[] = {arg_string}; | ||||||
| @ -2432,7 +2434,7 @@ static funcentry_T global_functions[] = | |||||||
| 			ret_number_bool,    f_islocked}, | 			ret_number_bool,    f_islocked}, | ||||||
|     {"isnan",		1, 1, FEARG_1,	    arg1_float_or_nr, |     {"isnan",		1, 1, FEARG_1,	    arg1_float_or_nr, | ||||||
| 			ret_number_bool,    MATH_FUNC(f_isnan)}, | 			ret_number_bool,    MATH_FUNC(f_isnan)}, | ||||||
|     {"items",		1, 1, FEARG_1,	    arg1_string_list_tuple_or_dict, |     {"items",		1, 1, FEARG_1,	    arg1_list_tuple_dict_blob_or_string, | ||||||
| 			ret_list_items,	    f_items}, | 			ret_list_items,	    f_items}, | ||||||
|     {"job_getchannel",	1, 1, FEARG_1,	    arg1_job, |     {"job_getchannel",	1, 1, FEARG_1,	    arg1_job, | ||||||
| 			ret_channel,	    JOB_FUNC(f_job_getchannel)}, | 			ret_channel,	    JOB_FUNC(f_job_getchannel)}, | ||||||
| @ -8716,6 +8718,26 @@ f_islocked(typval_T *argvars, typval_T *rettv) | |||||||
|     clear_lval(&lv); |     clear_lval(&lv); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * "items(dict)" function | ||||||
|  |  */ | ||||||
|  |     static void | ||||||
|  | f_items(typval_T *argvars, typval_T *rettv) | ||||||
|  | { | ||||||
|  |     if (argvars[0].v_type == VAR_STRING) | ||||||
|  | 	string2items(argvars, rettv); | ||||||
|  |     else if (argvars[0].v_type == VAR_LIST) | ||||||
|  | 	list2items(argvars, rettv); | ||||||
|  |     else if (argvars[0].v_type == VAR_TUPLE) | ||||||
|  | 	tuple2items(argvars, rettv); | ||||||
|  |     else if (argvars[0].v_type == VAR_BLOB) | ||||||
|  | 	blob2items(argvars, rettv); | ||||||
|  |     else if (argvars[0].v_type == VAR_DICT) | ||||||
|  | 	dict2items(argvars, rettv); | ||||||
|  |     else | ||||||
|  | 	semsg(_(e_list_tuple_dict_blob_or_string_required_for_argument_nr), 1); | ||||||
|  | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * "keytrans()" function |  * "keytrans()" function | ||||||
|  */ |  */ | ||||||
|  | |||||||
| @ -13,6 +13,7 @@ int blob_equal(blob_T *b1, blob_T *b2); | |||||||
| int read_blob(FILE *fd, typval_T *rettv, off_T offset, off_T size_arg); | int read_blob(FILE *fd, typval_T *rettv, off_T offset, off_T size_arg); | ||||||
| int write_blob(FILE *fd, blob_T *blob); | int write_blob(FILE *fd, blob_T *blob); | ||||||
| char_u *blob2string(blob_T *blob, char_u **tofree, char_u *numbuf); | char_u *blob2string(blob_T *blob, char_u **tofree, char_u *numbuf); | ||||||
|  | void blob2items(typval_T *argvars, typval_T *rettv); | ||||||
| blob_T *string2blob(char_u *str); | blob_T *string2blob(char_u *str); | ||||||
| int blob_slice_or_index(blob_T *blob, int is_range, varnumber_T n1, varnumber_T n2, int exclusive, typval_T *rettv); | int blob_slice_or_index(blob_T *blob, int is_range, varnumber_T n1, varnumber_T n2, int exclusive, typval_T *rettv); | ||||||
| int check_blob_index(long bloblen, varnumber_T n1, int quiet); | int check_blob_index(long bloblen, varnumber_T n1, int quiet); | ||||||
|  | |||||||
| @ -46,7 +46,7 @@ long dict_count(dict_T *d, typval_T *needle, int ic); | |||||||
| void dict_extend_func(typval_T *argvars, type_T *type, char *func_name, char_u *arg_errmsg, int is_new, typval_T *rettv); | void dict_extend_func(typval_T *argvars, type_T *type, char *func_name, char_u *arg_errmsg, int is_new, typval_T *rettv); | ||||||
| void dict_filter_map(dict_T *d, filtermap_T filtermap, type_T *argtype, char *func_name, char_u *arg_errmsg, typval_T *expr, typval_T *rettv); | void dict_filter_map(dict_T *d, filtermap_T filtermap, type_T *argtype, char *func_name, char_u *arg_errmsg, typval_T *expr, typval_T *rettv); | ||||||
| void dict_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg); | void dict_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg); | ||||||
| void f_items(typval_T *argvars, typval_T *rettv); | void dict2items(typval_T *argvars, typval_T *rettv); | ||||||
| void f_keys(typval_T *argvars, typval_T *rettv); | void f_keys(typval_T *argvars, typval_T *rettv); | ||||||
| void f_values(typval_T *argvars, typval_T *rettv); | void f_values(typval_T *argvars, typval_T *rettv); | ||||||
| void dict_set_items_ro(dict_T *di); | void dict_set_items_ro(dict_T *di); | ||||||
|  | |||||||
| @ -865,4 +865,15 @@ func Test_indexof() | |||||||
|   call assert_fails('let i = indexof(b, " ")', 'E15:') |   call assert_fails('let i = indexof(b, " ")', 'E15:') | ||||||
| endfunc | 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([], test_null_blob()->items()) | ||||||
|  |   END | ||||||
|  |   call v9.CheckSourceLegacyAndVim9Success(lines) | ||||||
|  | endfunc | ||||||
|  |  | ||||||
| " vim: shiftwidth=2 sts=2 expandtab | " vim: shiftwidth=2 sts=2 expandtab | ||||||
|  | |||||||
| @ -230,7 +230,7 @@ func Test_list_items() | |||||||
|   endfor |   endfor | ||||||
|   call assert_equal([[0, 'a'], [1, 'b'], [2, 'c']], r) |   call assert_equal([[0, 'a'], [1, 'b'], [2, 'c']], r) | ||||||
|  |  | ||||||
|   call assert_fails('call items(3)', 'E1225:') |   call assert_fails('call items(3)', 'E1251:') | ||||||
| endfunc | endfunc | ||||||
|  |  | ||||||
| func Test_string_items() | func Test_string_items() | ||||||
|  | |||||||
| @ -76,7 +76,7 @@ func Test_string_method() | |||||||
|   eval "a\rb\ec"->strtrans()->assert_equal('a^Mb^[c') |   eval "a\rb\ec"->strtrans()->assert_equal('a^Mb^[c') | ||||||
|   eval "aあb"->strwidth()->assert_equal(4) |   eval "aあb"->strwidth()->assert_equal(4) | ||||||
|   eval 'abc'->substitute('b', 'x', '')->assert_equal('axc') |   eval 'abc'->substitute('b', 'x', '')->assert_equal('axc') | ||||||
|   call assert_fails('eval 123->items()', 'E1225:') |   call assert_fails('eval 123->items()', 'E1251: List, Tuple, Dictionary, Blob or String required for argument 1') | ||||||
|  |  | ||||||
|   eval 'abc'->printf('the %s arg')->assert_equal('the abc arg') |   eval 'abc'->printf('the %s arg')->assert_equal('the abc arg') | ||||||
| endfunc | endfunc | ||||||
|  | |||||||
| @ -1655,7 +1655,7 @@ enddef | |||||||
|  |  | ||||||
| def Test_foreach() | def Test_foreach() | ||||||
|   CheckFeature job |   CheckFeature job | ||||||
|   v9.CheckSourceDefAndScriptFailure(['foreach(test_null_job(), "")'], ['E1013: Argument 1: type mismatch, expected list<any> but got job', 'E1251: List, Tuple, Dictionary, Blob or String required for argument 1']) |   v9.CheckSourceDefAndScriptFailure(['foreach(test_null_job(), "")'], 'E1251: List, Tuple, Dictionary, Blob or String required for argument 1') | ||||||
| enddef | enddef | ||||||
|  |  | ||||||
| def Test_fullcommand() | def Test_fullcommand() | ||||||
| @ -2492,15 +2492,19 @@ def Test_islocked() | |||||||
| enddef | enddef | ||||||
|  |  | ||||||
| def Test_items() | def Test_items() | ||||||
|   v9.CheckSourceDefFailure(['123->items()'], 'E1225:') |   v9.CheckSourceDefFailure(['123->items()'], 'E1251: List, Tuple, Dictionary, Blob or String required for argument 1') | ||||||
|  |  | ||||||
|  |   # Dict | ||||||
|   assert_equal([['a', 10], ['b', 20]], {'a': 10, 'b': 20}->items()) |   assert_equal([['a', 10], ['b', 20]], {'a': 10, 'b': 20}->items()) | ||||||
|   assert_equal([], {}->items()) |   assert_equal([], {}->items()) | ||||||
|   assert_equal(['x', 'x'], {'a': 10, 'b': 20}->items()->map((_, _) => 'x')) |   assert_equal(['x', 'x'], {'a': 10, 'b': 20}->items()->map((_, _) => 'x')) | ||||||
|  |  | ||||||
|  |   # List | ||||||
|   assert_equal([[0, 'a'], [1, 'b']], ['a', 'b']->items()) |   assert_equal([[0, 'a'], [1, 'b']], ['a', 'b']->items()) | ||||||
|   assert_equal([], []->items()) |   assert_equal([], []->items()) | ||||||
|   assert_equal([], test_null_list()->items()) |   assert_equal([], test_null_list()->items()) | ||||||
|  |  | ||||||
|  |   # String | ||||||
|   assert_equal([[0, 'a'], [1, '웃'], [2, 'ć']], 'a웃ć'->items()) |   assert_equal([[0, 'a'], [1, '웃'], [2, 'ć']], 'a웃ć'->items()) | ||||||
|   assert_equal([], ''->items()) |   assert_equal([], ''->items()) | ||||||
|   assert_equal([], test_null_string()->items()) |   assert_equal([], test_null_string()->items()) | ||||||
|  | |||||||
| @ -724,6 +724,8 @@ static char *(features[]) = | |||||||
|  |  | ||||||
| static int included_patches[] = | static int included_patches[] = | ||||||
| {   /* Add new patch number below this line */ | {   /* Add new patch number below this line */ | ||||||
|  | /**/ | ||||||
|  |     1668, | ||||||
| /**/ | /**/ | ||||||
|     1667, |     1667, | ||||||
| /**/ | /**/ | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user