diff options
| -rw-r--r-- | runtime/doc/eval.txt | 74 | ||||
| -rw-r--r-- | src/nvim/eval.c | 58 | ||||
| -rw-r--r-- | src/nvim/testdir/Makefile | 1 | ||||
| -rw-r--r-- | src/nvim/testdir/test_alot.vim | 1 | ||||
| -rw-r--r-- | src/nvim/testdir/test_filter_map.vim | 77 | ||||
| -rw-r--r-- | src/nvim/testdir/test_partial.vim | 21 | ||||
| -rw-r--r-- | src/nvim/version.c | 2 | 
7 files changed, 192 insertions, 42 deletions
| diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index abb582b943..cf11b6d77f 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -3373,31 +3373,47 @@ filewritable({file})					*filewritable()*  		directory, and we can write to it, the result is 2. -filter({expr}, {string})					*filter()* -		{expr} must be a |List| or a |Dictionary|. -		For each item in {expr} evaluate {string} and when the result +filter({expr1}, {expr2})					*filter()* +		{expr1} must be a |List| or a |Dictionary|. +		For each item in {expr1} evaluate {expr2} and when the result  		is zero remove the item from the |List| or |Dictionary|. -		Inside {string} |v:val| has the value of the current item. +		{expr2} must be a |string| or |Funcref|. + +		if {expr2} is a |string|, inside {expr2} |v:val| has the value +		of the current item.  For a |Dictionary| |v:key| has the key +		of the current item.  		For a |Dictionary| |v:key| has the key of the current item.  		Examples: > -			:call filter(mylist, 'v:val !~ "OLD"') +			call filter(mylist, 'v:val !~ "OLD"')  <		Removes the items where "OLD" appears. > -			:call filter(mydict, 'v:key >= 8') +			call filter(mydict, 'v:key >= 8')  <		Removes the items with a key below 8. > -			:call filter(var, 0) +			call filter(var, 0)  <		Removes all the items, thus clears the |List| or |Dictionary|. -		Note that {string} is the result of expression and is then +		Note that {expr2} is the result of expression and is then  		used as an expression again.  Often it is good to use a  		|literal-string| to avoid having to double backslashes. +		If {expr2} is a |Funcref| it must take two arguments: +			1. the key or the index of the current item. +			2. the value of the current item. +		The function must return TRUE if the item should be kept. +		Example that keeps the odd items of a list: > +			func Odd(idx, val) +			  return a:idx % 2 == 1 +			endfunc +			call filter(mylist, function('Odd')) +<  		The operation is done in-place.  If you want a |List| or  		|Dictionary| to remain unmodified make a copy first: >  			:let l = filter(copy(mylist), 'v:val =~ "KEEP"') -<		Returns {expr}, the |List| or |Dictionary| that was filtered. -		When an error is encountered while evaluating {string} no -		further items in {expr} are processed. +<		Returns {expr1}, the |List| or |Dictionary| that was filtered. +		When an error is encountered while evaluating {expr2} no +		further items in {expr1} are processed. When {expr2} is a +		Funcref errors inside a function are ignored, unless it was +		defined with the "abort" flag.  finddir({name}[, {path}[, {count}]])				*finddir()* @@ -4948,29 +4964,43 @@ log10({expr})						*log10()*  <			-2.0 -map({expr}, {string})					*map()* -		{expr} must be a |List| or a |Dictionary|. -		Replace each item in {expr} with the result of evaluating -		{string}. -		Inside {string} |v:val| has the value of the current item. -		For a |Dictionary| |v:key| has the key of the current item -		and for a |List| |v:key| has the index of the current item. +map({expr1}, {expr2})					*map()* +		{expr1} must be a |List| or a |Dictionary|. +		Replace each item in {expr1} with the result of evaluating +		{expr2}. {expr2} must be a |string| or |Funcref|. +		 +		If {expr2} is a |string|, inside {expr2} |v:val| has the value +		of the current item. For a |Dictionary| |v:key| has the key +		of the current item and for a |List| |v:key| has the index of +		the current item.  		Example: >  			:call map(mylist, '"> " . v:val . " <"')  <		This puts "> " before and " <" after each item in "mylist". -		Note that {string} is the result of an expression and is then +		Note that {expr2} is the result of an expression and is then  		used as an expression again.  Often it is good to use a  		|literal-string| to avoid having to double backslashes.  You  		still have to double ' quotes +		If {expr2} is a |Funcref| it is called with two arguments: +			1. The key or the index of the current item. +			2. the value of the current item. +		The function must return the new value of the item. Example +		that changes each value by "key-value": > +			func KeyValue(key, val) +			  return a:key . '-' . a:val +			endfunc +			call map(myDict, function('KeyValue')) +<  		The operation is done in-place.  If you want a |List| or  		|Dictionary| to remain unmodified make a copy first: >  			:let tlist = map(copy(mylist), ' v:val . "\t"') -<		Returns {expr}, the |List| or |Dictionary| that was filtered. -		When an error is encountered while evaluating {string} no -		further items in {expr} are processed. +<		Returns {expr1}, the |List| or |Dictionary| that was filtered. +		When an error is encountered while evaluating {expr2} no +		further items in {expr1} are processed. When {expr2} is a +		Funcref errors inside a function are ignored, unless it was +		defined with the "abort" flag.  maparg({name}[, {mode} [, {abbr} [, {dict}]]])			*maparg()* diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 8692ddc334..e4afa18d10 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5285,7 +5285,8 @@ tv_equal (      return TRUE;    } -  // For VAR_FUNC and VAR_PARTIAL only compare the function name. +  // For VAR_FUNC and VAR_PARTIAL compare the function name, bound dict and +  // arguments.    if ((tv1->v_type == VAR_FUNC         || (tv1->v_type == VAR_PARTIAL && tv1->vval.v_partial != NULL))        && (tv2->v_type == VAR_FUNC @@ -9306,8 +9307,7 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what)   */  static void filter_map(typval_T *argvars, typval_T *rettv, int map)  { -  char_u buf[NUMBUFLEN]; -  char_u      *expr; +  typval_T    *expr;    listitem_T  *li, *nli;    list_T      *l = NULL;    dictitem_T  *di; @@ -9339,16 +9339,15 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)      return;    } -  expr = get_tv_string_buf_chk(&argvars[1], buf); -  /* On type errors, the preceding call has already displayed an error -   * message.  Avoid a misleading error message for an empty string that -   * was not passed as argument. */ -  if (expr != NULL) { +  expr = &argvars[1]; +  // On type errors, the preceding call has already displayed an error +  // message.  Avoid a misleading error message for an empty string that +  // was not passed as argument. +  if (expr->v_type != VAR_UNKNOWN) {      prepare_vimvar(VV_VAL, &save_val); -    expr = skipwhite(expr); -    /* We reset "did_emsg" to be able to detect whether an error -     * occurred during evaluation of the expression. */ +    // We reset "did_emsg" to be able to detect whether an error +    // occurred during evaluation of the expression.      save_did_emsg = did_emsg;      did_emsg = FALSE; @@ -9412,20 +9411,41 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)    copy_tv(&argvars[0], rettv);  } -static int filter_map_one(typval_T *tv, char_u *expr, int map, int *remp) +static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp)  {    typval_T rettv; +  typeval_T argv[3];    char_u      *s;    int retval = FAIL; +  int dummy;    copy_tv(tv, &vimvars[VV_VAL].vv_tv); -  s = expr; -  if (eval1(&s, &rettv, TRUE) == FAIL) -    goto theend; -  if (*s != NUL) {  /* check for trailing chars after expr */ -    EMSG2(_(e_invexpr2), s); -    clear_tv(&rettv); -    goto theend; +  argv[0] = vimvars[VV_KEY].vv_tv; +  argv[1] = vimvars[VV_VAL].vv_tv; +  s = expr->vval.v_string; +  if (expr->v_type == VAR_FUNC) { +    if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, 0L, 0L, &dummy, +                  true, NULL, NULL) == FAIL) { +      goto theend; +    } +  } else if (expr->v_type == VAR_PARTIAL) { +    partial_T *partial = expr->vval.v_partial; + +    s = partial->pt_name; +    if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, 0L, 0L, &dummy, +                  true, partial, NULL) == FAIL) { +      goto theend; +    } +  } else { +    s = skipwhite(s); +    if (eval1(&s, &rettv, true) == FAIL) { +      goto theend; +    } + +    if (*s != NUL) { // check for trailing chars after expr +      EMSG2(_(e_invexpr2), s); +      goto theend; +    }    }    if (map) {      /* map(): replace the list item value */ diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 721300c334..b8244fa0ec 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -34,6 +34,7 @@ NEW_TESTS ?= \  	    test_cscope.res \  	    test_digraph.res \  	    test_diffmode.res \ +		test_filter_map.res \  	    test_gn.res \  	    test_hardcopy.res \  	    test_help_tagjump.res \ diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index 60248bf430..6acc60163a 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -9,6 +9,7 @@ source test_ex_undo.vim  source test_expr.vim  source test_expr_utf8.vim  source test_feedkeys.vim +source test_filter_map.vim  source test_goto.vim  source test_jumps.vim  source test_match.vim diff --git a/src/nvim/testdir/test_filter_map.vim b/src/nvim/testdir/test_filter_map.vim new file mode 100644 index 0000000000..6bb063c76d --- /dev/null +++ b/src/nvim/testdir/test_filter_map.vim @@ -0,0 +1,77 @@ +" Test filter() and map() + +" list with expression string +func Test_filter_map_list_expr_string() +  " filter() +  call assert_equal([2, 3, 4], filter([1, 2, 3, 4], 'v:val > 1')) +  call assert_equal([3, 4], filter([1, 2, 3, 4], 'v:key > 1')) + +  " map() +  call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], 'v:val * 2')) +  call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], 'v:key * 2')) +endfunc + +" dict with expression string +func Test_filter_map_dict_expr_string() +  let dict = {"foo": 1, "bar": 2, "baz": 3} + +  " filter() +  call assert_equal({"bar": 2, "baz": 3}, filter(copy(dict), 'v:val > 1')) +  call assert_equal({"foo": 1, "baz": 3}, filter(copy(dict), 'v:key > "bar"')) + +  " map() +  call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), 'v:val * 2')) +  call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), 'v:key[0]')) +endfunc + +" list with funcref +func Test_filter_map_list_expr_funcref() +  " filter() +  func! s:filter1(index, val) abort +    return a:val > 1 +  endfunc +  call assert_equal([2, 3, 4], filter([1, 2, 3, 4], function('s:filter1'))) + +  func! s:filter2(index, val) abort +    return a:index > 1 +  endfunc +  call assert_equal([3, 4], filter([1, 2, 3, 4], function('s:filter2'))) + +  " map() +  func! s:filter3(index, val) abort +    return a:val * 2 +  endfunc +  call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], function('s:filter3'))) + +  func! s:filter4(index, val) abort +    return a:index * 2 +  endfunc +  call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], function('s:filter4'))) +endfunc + +" dict with funcref +func Test_filter_map_dict_expr_funcref() +  let dict = {"foo": 1, "bar": 2, "baz": 3} + +  " filter() +  func! s:filter1(key, val) abort +    return a:val > 1 +  endfunc +  call assert_equal({"bar": 2, "baz": 3}, filter(copy(dict), function('s:filter1'))) + +  func! s:filter2(key, val) abort +    return a:key > "bar" +  endfunc +  call assert_equal({"foo": 1, "baz": 3}, filter(copy(dict), function('s:filter2'))) + +  " map() +  func! s:filter3(key, val) abort +    return a:val * 2 +  endfunc +  call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), function('s:filter3'))) + +  func! s:filter4(key, val) abort +    return a:key[0] +  endfunc +  call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), function('s:filter4'))) +endfunc diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim index 3a6e162453..de5c26c2dd 100644 --- a/src/nvim/testdir/test_partial.vim +++ b/src/nvim/testdir/test_partial.vim @@ -14,6 +14,14 @@ func MySort(up, one, two)    return a:one < a:two ? 1 : -1  endfunc +func MyMap(sub, index, val) +  return a:val - a:sub +endfunc + +func MyFilter(threshold, index, val) +  return a:val > a:threshold +endfunc +  func Test_partial_args()    let Cb = function('MyFunc', ["foo", "bar"]) @@ -36,6 +44,16 @@ func Test_partial_args()    call assert_equal([1, 2, 3], sort([3, 1, 2], Sort))    let Sort = function('MySort', [0])    call assert_equal([3, 2, 1], sort([3, 1, 2], Sort)) + +  let Map = function('MyMap', [2]) +  call assert_equal([-1, 0, 1], map([1, 2, 3], Map)) +  let Map = function('MyMap', [3]) +  call assert_equal([-2, -1, 0], map([1, 2, 3], Map)) + +  let Filter = function('MyFilter', [1]) +  call assert_equal([2, 3], filter([1, 2, 3], Filter)) +  let Filter = function('MyFilter', [2]) +  call assert_equal([3], filter([1, 2, 3], Filter))  endfunc  func MyDictFunc(arg1, arg2) dict @@ -59,6 +77,9 @@ func Test_partial_dict()    call assert_equal("hello/xxx/yyy", Cb("xxx", "yyy"))    call assert_fails('Cb("fff")', 'E492:') +  let Cb = function('MyDictFunc', dict) +  call assert_equal({"foo": "hello/foo/1", "bar": "hello/bar/2"}, map({"foo": 1, "bar": 2}, Cb)) +    let dict = {"tr": function('tr', ['hello', 'h', 'H'])}    call assert_equal("Hello", dict.tr())  endfunc diff --git a/src/nvim/version.c b/src/nvim/version.c index 111b597eba..96dce2ca46 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -451,7 +451,7 @@ static int included_patches[] = {    // 1992,    // 1991,    1990, -  // 1989, +  1989,    // 1988 NA    // 1987 NA    // 1986, | 
