aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Ennen <mike.ennen@gmail.com>2016-12-14 00:20:48 -0700
committerMichael Ennen <mike.ennen@gmail.com>2017-02-14 17:38:16 -0700
commitbb2afeb0266ffd410e2e226a376f7ddbac633491 (patch)
tree0488197944e3fc6a7c5ddf887eebc545c9f34c8b
parentb0fc6108c923a198325354ae36b71f90c4c68e19 (diff)
downloadrneovim-bb2afeb0266ffd410e2e226a376f7ddbac633491.tar.gz
rneovim-bb2afeb0266ffd410e2e226a376f7ddbac633491.tar.bz2
rneovim-bb2afeb0266ffd410e2e226a376f7ddbac633491.zip
vim-patch:7.4.1989
Problem: filter() and map() only accept a string argument. Solution: Implement using a Funcref argument (Yasuhiro Matsumoto, Ken Takata) https://github.com/vim/vim/commit/b33c7eb5b813cb631b2b0ca5c4029e1788a09bde
-rw-r--r--runtime/doc/eval.txt74
-rw-r--r--src/nvim/eval.c58
-rw-r--r--src/nvim/testdir/Makefile1
-rw-r--r--src/nvim/testdir/test_alot.vim1
-rw-r--r--src/nvim/testdir/test_filter_map.vim77
-rw-r--r--src/nvim/testdir/test_partial.vim21
-rw-r--r--src/nvim/version.c2
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,