:help index explains that only exact matches are returned. How would you add a predicate to the search, so that we could find the first element that matches that?

I want to write this:

  let i = 0
  let commit = ''
  while i < len(l:state.commits)
    let commit = l:state.commits[i]
    if predicate(commit) then
    i += 1

but much simpler, as:

let commit = index(l:state.commits, predicate)

Can this be done in vimscript?


This sort of "functional programming" in VimScript could be implemented by :h filter() function. For example,

function! FindItem(object, Fn) abort
    return get(filter(copy(a:object), "a:Fn(v:val)"), 0, v:null)

Alternatively, your Fn can accept two arguments (index, value) instead of one (and, maybe, even somehow cache "the first successful result" to avoid extra checks). In this case, you can use it directly: return get(filter(copy(a:object), a:Fn), 0, v:null)

Also, if your Vim (not in Neovim yet) is modern enough, you may have access to :h reduce() function. Then you can write it like this:

function! FindItem(object, Fn) abort
    return reduce(a:object, {acc, val -> acc isnot v:null ? acc : a:Fn(val) ? val : v:null}, v:null)
  • To call the function: let commit = FindItem(state.commits, {item -> StartsWith(parent_commit, item.short_commit_hash)}) Feb 6 at 10:53

The fastest way I've found (in execution time) was to filter everything and check the first item if not empty. This is because loops are really slow

:echo filter(copy(state.commits), predicate)[0]

When I need the index, I use map + index

:echo index(map(copy(state.commits), 'predicate ? 1 : 0'), 1)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.