Patch 9.0.0196
Problem:    Finding value in list may require a for loop.
Solution:   Add indexof(). (Yegappan Lakshmanan, closes #10903)
Files:      runtime/doc/builtin.txt, runtime/doc/usr_41.txt, src/evalfunc.c,
            src/testdir/test_blob.vim, src/testdir/test_listdict.vim,
            src/testdir/test_vim9_builtin.vim


*** ../vim-9.0.0195/runtime/doc/builtin.txt     2022-07-30 14:56:11.767483770 
+0100
--- runtime/doc/builtin.txt     2022-08-13 13:01:13.768039432 +0100
***************
*** 291,299 ****
  indent({lnum})                        Number  indent of line {lnum}
  index({object}, {expr} [, {start} [, {ic}]])
                                Number  index in {object} where {expr} appears
  input({prompt} [, {text} [, {completion}]])
                                String  get input from the user
! inputdialog({prompt} [, {text} [, {completion}]])
                                String  like input() but in a GUI dialog
  inputlist({textlist})         Number  let the user pick from a choice list
  inputrestore()                        Number  restore typeahead
--- 291,301 ----
  indent({lnum})                        Number  indent of line {lnum}
  index({object}, {expr} [, {start} [, {ic}]])
                                Number  index in {object} where {expr} appears
+ indexof({object}, {expr} [, {opts}]])
+                               Number  index in {object} where {expr} is true
  input({prompt} [, {text} [, {completion}]])
                                String  get input from the user
! inputdialog({prompt} [, {text} [, {cancelreturn}]])
                                String  like input() but in a GUI dialog
  inputlist({textlist})         Number  let the user pick from a choice list
  inputrestore()                        Number  restore typeahead
***************
*** 4724,4742 ****
                        GetLnum()->indent()
  
  index({object}, {expr} [, {start} [, {ic}]])                  *index()*
                If {object} is a |List| return the lowest index where the item
                has a value equal to {expr}.  There is no automatic
                conversion, so the String "4" is different from the Number 4.
                And the number 4 is different from the Float 4.0.  The value
!               of 'ignorecase' is not used here, case always matters.
  
                If {object} is |Blob| return the lowest index where the byte
                value is equal to {expr}.
  
                If {start} is given then start looking at the item with index
                {start} (may be negative for an item relative to the end).
                When {ic} is given and it is |TRUE|, ignore case.  Otherwise
                case must match.
                -1 is returned when {expr} is not found in {object}.
                Example: >
                        :let idx = index(words, "the")
--- 4732,4756 ----
                        GetLnum()->indent()
  
  index({object}, {expr} [, {start} [, {ic}]])                  *index()*
+               Find {expr} in {object} and return its index.  See
+               |filterof()| for using a lambda to select the item.
+ 
                If {object} is a |List| return the lowest index where the item
                has a value equal to {expr}.  There is no automatic
                conversion, so the String "4" is different from the Number 4.
                And the number 4 is different from the Float 4.0.  The value
!               of 'ignorecase' is not used here, case matters as indicated by
!               the {ic} argument.
  
                If {object} is |Blob| return the lowest index where the byte
                value is equal to {expr}.
  
                If {start} is given then start looking at the item with index
                {start} (may be negative for an item relative to the end).
+ 
                When {ic} is given and it is |TRUE|, ignore case.  Otherwise
                case must match.
+ 
                -1 is returned when {expr} is not found in {object}.
                Example: >
                        :let idx = index(words, "the")
***************
*** 4745,4750 ****
--- 4759,4802 ----
  <             Can also be used as a |method|: >
                        GetObject()->index(what)
  
+ indexof({object}, {expr} [, {opt}])                   *indexof()*
+               {object} must be a |List| or a |Blob|.
+               If {object} is a |List|, evaluate {expr} for each item in the
+               List until the expression returns v:true and return the index
+               of this item.
+ 
+               If {object} is a |Blob| evaluate {expr} for each byte in the
+               Blob until the expression returns v:true and return the index
+               of this byte.
+ 
+               {expr} must be a |string| or |Funcref|.
+ 
+               If {expr} is a |string|: If {object} is a |List|, inside
+               {expr} |v:key| has the index of the current List item and
+               |v:val| has the value of the item.  If {object} is a |Blob|,
+               inside {expr} |v:key| has the index of the current byte and
+               |v:val| has the byte value.
+ 
+               If {expr} 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 is found and the
+               search should stop.
+ 
+               The optional argument {opt} is a Dict and supports the
+               following items:
+                   start       start evaluating {expr} at the item with index
+                               {start} (may be negative for an item relative
+                               to the end).
+               Returns -1 when {expr} evaluates to v:false for all the items.
+               Example: >
+                       :let l = [#{n: 10}, #{n: 20}, #{n: 30]]
+                       :let idx = indexof(l, "v:val.n == 20")
+                       :let idx = indexof(l, {i, v -> v.n == 30})
+ 
+ <             Can also be used as a |method|: >
+                       mylist->indexof(expr)
+ 
  input({prompt} [, {text} [, {completion}]])           *input()*
                The result is a String, which is whatever the user typed on
                the command-line.  The {prompt} argument is either a prompt
*** ../vim-9.0.0195/runtime/doc/usr_41.txt      2022-06-28 11:21:07.000000000 
+0100
--- runtime/doc/usr_41.txt      2022-08-13 13:03:52.867486037 +0100
***************
*** 791,804 ****
        reduce()                reduce a List to a value
        slice()                 take a slice of a List
        sort()                  sort a List
!       reverse()               reverse the order of a List
        uniq()                  remove copies of repeated adjacent items
        split()                 split a String into a List
        join()                  join List items into a String
        range()                 return a List with a sequence of numbers
        string()                String representation of a List
        call()                  call a function with List as arguments
!       index()                 index of a value in a List
        max()                   maximum value in a List
        min()                   minimum value in a List
        count()                 count number of times a value appears in a List
--- 792,807 ----
        reduce()                reduce a List to a value
        slice()                 take a slice of a List
        sort()                  sort a List
!       reverse()               reverse the order of a List or Blob
        uniq()                  remove copies of repeated adjacent items
        split()                 split a String into a List
        join()                  join List items into a String
        range()                 return a List with a sequence of numbers
        string()                String representation of a List
        call()                  call a function with List as arguments
!       index()                 index of a value in a List or Blob
!       indexof()               index in a List or Blob where an expression
!                               evaluates to true 
        max()                   maximum value in a List
        min()                   minimum value in a List
        count()                 count number of times a value appears in a List
*** ../vim-9.0.0195/src/evalfunc.c      2022-08-04 18:50:10.882699744 +0100
--- src/evalfunc.c      2022-08-13 12:47:00.263611526 +0100
***************
*** 79,84 ****
--- 79,85 ----
  static void f_hlexists(typval_T *argvars, typval_T *rettv);
  static void f_hostname(typval_T *argvars, typval_T *rettv);
  static void f_index(typval_T *argvars, typval_T *rettv);
+ static void f_indexof(typval_T *argvars, typval_T *rettv);
  static void f_input(typval_T *argvars, typval_T *rettv);
  static void f_inputdialog(typval_T *argvars, typval_T *rettv);
  static void f_inputlist(typval_T *argvars, typval_T *rettv);
***************
*** 1037,1042 ****
--- 1038,1044 ----
  static argcheck_T arg14_glob[] = {arg_string, arg_bool, arg_bool, arg_bool};
  static argcheck_T arg25_globpath[] = {arg_string, arg_string, arg_bool, 
arg_bool, arg_bool};
  static argcheck_T arg24_index[] = {arg_list_or_blob, arg_item_of_prev, 
arg_number, arg_bool};
+ static argcheck_T arg23_index[] = {arg_list_or_blob, arg_filter_func, 
arg_dict_any};
  static argcheck_T arg23_insert[] = {arg_list_or_blob, arg_item_of_prev, 
arg_number};
  static argcheck_T arg1_len[] = {arg_len1};
  static argcheck_T arg3_libcall[] = {arg_string, arg_string, arg_string_or_nr};
***************
*** 1995,2000 ****
--- 1997,2004 ----
                        ret_number,         f_indent},
      {"index",         2, 4, FEARG_1,      arg24_index,
                        ret_number,         f_index},
+     {"indexof",               2, 3, FEARG_1,      arg23_index,
+                       ret_number,         f_indexof},
      {"input",         1, 3, FEARG_1,      arg3_string,
                        ret_string,         f_input},
      {"inputdialog",   1, 3, FEARG_1,      arg3_string,
***************
*** 6789,6794 ****
--- 6793,6928 ----
      }
  }
  
+ /*
+  * Evaluate 'expr' with the v:key and v:val arguments and return the result.
+  * The expression is expected to return a boolean value.  The caller should 
set
+  * the VV_KEY and VV_VAL vim variables before calling this function.
+  */
+     static int
+ indexof_eval_expr(typval_T *expr)
+ {
+     typval_T  argv[3];
+     typval_T  newtv;
+     varnumber_T       found;
+     int               error = FALSE;
+ 
+     argv[0] = *get_vim_var_tv(VV_KEY);
+     argv[1] = *get_vim_var_tv(VV_VAL);
+     newtv.v_type = VAR_UNKNOWN;
+ 
+     if (eval_expr_typval(expr, argv, 2, &newtv) == FAIL)
+       return FALSE;
+ 
+     found = tv_get_bool_chk(&newtv, &error);
+ 
+     return error ? FALSE : found;
+ }
+ 
+ /*
+  * "indexof()" function
+  */
+     static void
+ f_indexof(typval_T *argvars, typval_T *rettv)
+ {
+     list_T    *l;
+     listitem_T        *item;
+     blob_T    *b;
+     long      startidx = 0;
+     long      idx = 0;
+     typval_T  save_val;
+     typval_T  save_key;
+     int               save_did_emsg;
+ 
+     rettv->vval.v_number = -1;
+ 
+     if (check_for_list_or_blob_arg(argvars, 0) == FAIL
+           || check_for_string_or_func_arg(argvars, 1) == FAIL
+           || check_for_opt_dict_arg(argvars, 2) == FAIL)
+       return;
+ 
+     if ((argvars[1].v_type == VAR_STRING && argvars[1].vval.v_string == NULL)
+           || (argvars[1].v_type == VAR_FUNC
+               && argvars[1].vval.v_partial == NULL))
+       return;
+ 
+     if (argvars[2].v_type == VAR_DICT)
+       startidx = dict_get_number_def(argvars[2].vval.v_dict, "startidx", 0);
+ 
+     prepare_vimvar(VV_VAL, &save_val);
+     prepare_vimvar(VV_KEY, &save_key);
+ 
+     // 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;
+ 
+     if (argvars[0].v_type == VAR_BLOB)
+     {
+       b = argvars[0].vval.v_blob;
+       if (b == NULL)
+           goto theend;
+       if (startidx < 0)
+       {
+           startidx = blob_len(b) + startidx;
+           if (startidx < 0)
+               startidx = 0;
+       }
+ 
+       set_vim_var_type(VV_KEY, VAR_NUMBER);
+       set_vim_var_type(VV_VAL, VAR_NUMBER);
+ 
+       for (idx = startidx; idx < blob_len(b); ++idx)
+       {
+           set_vim_var_nr(VV_KEY, idx);
+           set_vim_var_nr(VV_VAL, blob_get(b, idx));
+ 
+           if (indexof_eval_expr(&argvars[1]))
+           {
+               rettv->vval.v_number = idx;
+               break;
+           }
+       }
+     }
+     else
+     {
+       l = argvars[0].vval.v_list;
+       if (l == NULL)
+           goto theend;
+ 
+       CHECK_LIST_MATERIALIZE(l);
+ 
+       if (startidx == 0)
+           item = l->lv_first;
+       else
+       {
+           // Start at specified item.  Use the cached index that list_find()
+           // sets, so that a negative number also works.
+           item = list_find(l, startidx);
+           if (item != NULL)
+               idx = l->lv_u.mat.lv_idx;
+       }
+ 
+       set_vim_var_type(VV_KEY, VAR_NUMBER);
+ 
+       for ( ; item != NULL; item = item->li_next, ++idx)
+       {
+           set_vim_var_nr(VV_KEY, idx);
+           copy_tv(&item->li_tv, get_vim_var_tv(VV_VAL));
+ 
+           if (indexof_eval_expr(&argvars[1]))
+           {
+               rettv->vval.v_number = idx;
+               break;
+           }
+       }
+     }
+ 
+ theend:
+     restore_vimvar(VV_KEY, &save_key);
+     restore_vimvar(VV_VAL, &save_val);
+     did_emsg |= save_did_emsg;
+ }
+ 
  static int inputsecret_flag = 0;
  
  /*
*** ../vim-9.0.0195/src/testdir/test_blob.vim   2022-04-23 10:46:04.000000000 
+0100
--- src/testdir/test_blob.vim   2022-08-13 12:47:00.263611526 +0100
***************
*** 764,767 ****
--- 764,793 ----
    call assert_equal(0, x)
  endfunc
  
+ " Test for the indexof() function
+ func Test_indexof()
+   let b = 0zdeadbeef
+   call assert_equal(0, indexof(b, {i, v -> v == 0xde}))
+   call assert_equal(3, indexof(b, {i, v -> v == 0xef}))
+   call assert_equal(-1, indexof(b, {i, v -> v == 0x1}))
+   call assert_equal(1, indexof(b, "v:val == 0xad"))
+   call assert_equal(-1, indexof(b, "v:val == 0xff"))
+ 
+   call assert_equal(-1, indexof(0z, "v:val == 0x0"))
+   call assert_equal(-1, indexof(test_null_blob(), "v:val == 0xde"))
+   call assert_equal(-1, indexof(b, test_null_string()))
+   call assert_equal(-1, indexof(b, test_null_function()))
+ 
+   let b = 0z01020102
+   call assert_equal(1, indexof(b, "v:val == 0x02", #{startidx: 0}))
+   call assert_equal(2, indexof(b, "v:val == 0x01", #{startidx: -2}))
+   call assert_equal(-1, indexof(b, "v:val == 0x01", #{startidx: 5}))
+   call assert_equal(0, indexof(b, "v:val == 0x01", #{startidx: -5}))
+   call assert_equal(0, indexof(b, "v:val == 0x01", test_null_dict()))
+ 
+   " failure cases
+   call assert_fails('let i = indexof(b, "val == 0xde")', 'E121:')
+   call assert_fails('let i = indexof(b, {})', 'E1256:')
+ endfunc
+ 
  " vim: shiftwidth=2 sts=2 expandtab
*** ../vim-9.0.0195/src/testdir/test_listdict.vim       2022-04-23 
10:46:04.000000000 +0100
--- src/testdir/test_listdict.vim       2022-08-13 12:47:00.263611526 +0100
***************
*** 1446,1449 ****
--- 1446,1494 ----
    unlockvar d
  endfunc
  
+ " Test for the indexof() function
+ func Test_indexof()
+   let l = [#{color: 'red'}, #{color: 'blue'}, #{color: 'green'}]
+   call assert_equal(0, indexof(l, {i, v -> v.color == 'red'}))
+   call assert_equal(2, indexof(l, {i, v -> v.color == 'green'}))
+   call assert_equal(-1, indexof(l, {i, v -> v.color == 'grey'}))
+   call assert_equal(1, indexof(l, "v:val.color == 'blue'"))
+   call assert_equal(-1, indexof(l, "v:val.color == 'cyan'"))
+ 
+   let l = [#{n: 10}, #{n: 10}, #{n: 20}]
+   call assert_equal(0, indexof(l, "v:val.n == 10", #{startidx: 0}))
+   call assert_equal(1, indexof(l, "v:val.n == 10", #{startidx: -2}))
+   call assert_equal(-1, indexof(l, "v:val.n == 10", #{startidx: 4}))
+   call assert_equal(-1, indexof(l, "v:val.n == 10", #{startidx: -4}))
+   call assert_equal(0, indexof(l, "v:val.n == 10", test_null_dict()))
+ 
+   call assert_equal(-1, indexof([], {i, v -> v == 'a'}))
+   call assert_equal(-1, indexof(test_null_list(), {i, v -> v == 'a'}))
+   call assert_equal(-1, indexof(l, test_null_string()))
+   call assert_equal(-1, indexof(l, test_null_function()))
+ 
+   " failure cases
+   call assert_fails('let i = indexof(l, "v:val == ''cyan''")', 'E735:')
+   call assert_fails('let i = indexof(l, "color == ''cyan''")', 'E121:')
+   call assert_fails('let i = indexof(l, {})', 'E1256:')
+   call assert_fails('let i = indexof({}, "v:val == 2")', 'E1226:')
+   call assert_fails('let i = indexof([], "v:val == 2", [])', 'E1206:')
+ 
+   func TestIdx(k, v)
+     return a:v.n == 20
+   endfunc
+   call assert_equal(2, indexof(l, function("TestIdx")))
+   delfunc TestIdx
+   func TestIdx(k, v)
+     return {}
+   endfunc
+   call assert_fails('let i = indexof(l, function("TestIdx"))', 'E728:')
+   delfunc TestIdx
+   func TestIdx(k, v)
+     throw "IdxError"
+   endfunc
+   call assert_fails('let i = indexof(l, function("TestIdx"))', 'E605:')
+   delfunc TestIdx
+ endfunc
+ 
  " vim: shiftwidth=2 sts=2 expandtab
*** ../vim-9.0.0195/src/testdir/test_vim9_builtin.vim   2022-07-27 
12:30:08.405165929 +0100
--- src/testdir/test_vim9_builtin.vim   2022-08-13 12:47:00.263611526 +0100
***************
*** 2066,2071 ****
--- 2066,2078 ----
    v9.CheckDefAndScriptFailure(['index(0z1020, 10, 1, 2)'], ['E1013: Argument 
4: type mismatch, expected bool but got number', 'E1212: Bool required for 
argument 4'])
  enddef
  
+ def Test_indexof()
+   var l = [{color: 'red'}, {color: 'blue'}, {color: 'green'}]
+   indexof(l, (i, v) => v.color == 'green')->assert_equal(2)
+   var b = 0zdeadbeef
+   indexof(b, "v:val == 0xef")->assert_equal(3)
+ enddef
+ 
  def Test_input()
    v9.CheckDefAndScriptFailure(['input(5)'], ['E1013: Argument 1: type 
mismatch, expected string but got number', 'E1174: String required for argument 
1'])
    v9.CheckDefAndScriptFailure(['input(["a"])'], ['E1013: Argument 1: type 
mismatch, expected string but got list<string>', 'E1174: String required for 
argument 1'])
*** ../vim-9.0.0195/src/version.c       2022-08-12 21:57:09.574588305 +0100
--- src/version.c       2022-08-13 12:48:30.571148840 +0100
***************
*** 737,738 ****
--- 737,740 ----
  {   /* Add new patch number below this line */
+ /**/
+     196,
  /**/

-- 
       "To whoever finds this note -
       I have been imprisoned by my father who wishes me to marry
       against my will.  Please please please please come and rescue me.
       I am in the tall tower of Swamp Castle."
   SIR LAUNCELOT's eyes light up with holy inspiration.
                 "Monty Python and the Holy Grail" PYTHON (MONTY) PICTURES LTD

 /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
///                                                                      \\\
\\\        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/20220813121003.2BE1D1C08E8%40moolenaar.net.

Raspunde prin e-mail lui