I'm trying to find words before the cursor using searchpos, then use matchlist to get the captures. I can't find a reliable way to anchor the pattern for matchlist. For example, if the cursor is positioned on [cursor]

" 3456789012345
" !!foobar !!foobaz [cursor]  !!foobat

let pat = '\%(!!\)\@<=fooba\(.\)'
let [l, c] = searchpos(pat, 'bWn')    " returns [2, 14]

echo 'pat1'
echo l c matchlist(getline(l), pat)                     | " bad
echo l c matchlist(getline(l), pat, c-3)                | " ok
echo l c matchlist(getline(l), '\%'.c.'c\%('.pat.'\)')  | " ok

searchpos finds the column 14, since the look-behind is not included in the match. The first matchlist finds the wrong fooba.. Thus, I need to anchor the pattern using the column number. The second alternative is not an option because it would require me to know how many characters were zero-width'd. The third one works great, until...

let pat = '!!\zsfooba\(.\)'
let [l, c] = searchpos(pat, 'bWn')    " also returns [2, 14]

echo 'pat2'
echo l c matchlist(getline(l), pat)                     | " bad
echo l c matchlist(getline(l), pat, c-3)                | " ok
echo l c matchlist(getline(l), '\%'.c.'c\%('.pat.'\)')  | " bad

Here, prepending the column number doesn't work because although \zs is zero-width, the stuff before it is not.


  • I need a general-purpose solution since this is for a plugin and pat is supplied by the user.
  • I can't simply replace \zs with \%14c\zs, because there may be alternations \| in the pattern. For example foo\%14c\zsbar\|foobaz will allow foobaz in any position.
  • This is not actually peculiar to searchpos- the same would be seen with e.g., the character index from match.
  • Negative look-behinds in pat are possible.
  • A perfect answer would include the possibility of \ze.
  • Sneakily changing the test cases after I answer! Cursor position is easy to workaround, though, I think? Going to have to think about negative lookbehind some more, though.
    – Rich
    Nov 17, 2017 at 19:34
  • that is not so easily possible. Thinking about a multiline pattern, that will likely break Nov 21, 2017 at 12:33
  • I'm not following your new note about it not being peculiar to searchpos. Could you add a new test case to demonstrate how it's failing with match? (Or just describe it more?) Similarly, a test case for \ze would be helpful.
    – Rich
    Nov 21, 2017 at 22:44

1 Answer 1


The problem seems to be that is that searchpos is searching backwards (from the position of the cursor) and matchlist is searching forwards (from the start of the line).

How about altering the way we call matchlist to search backwards too, via the time honoured technique of brute force?

let line1 = getline(l)
let line2 = getline(l)[c:]
let line1 = line1[:c-1]

" We want to ignore matches found *after* the position returned by 
" searchpos()
let boo = matchlist(line2, pat)

while len(line1) > 0
  let line2 = line1[-1:] . line2
  let line1 = line1[:-2]
  let yay = matchlist(line2, pat)
  if len(yay) > 0 && yay != boo
    echo l c yay

It's ugly and inefficient, and I have a feeling it might be possible to construct a complicated regular expression that breaks it, but it works for the test cases given.

  • Interested to see if you come up with anything using clever lookbehinds. A while loop solution is what I will resort to if there's no more efficient way. Note that I updated the example to make it clear there could be fooba. after the cursor position also.
    – Mass
    Nov 17, 2017 at 19:34
  • No, I meant negative lookbehind will break this solution. I think it might be possible to workaround, though, although I'm not sure if alternations will flummox me, too. The cursor position thing should be easier to fix, I think.
    – Rich
    Nov 17, 2017 at 19:37
  • @Mass I edited my answer to handle your question edit. I think I was mistaken about negative lookbehind, so I'm not aware of any cases that this answer fails on. Please let me know if you find any!
    – Rich
    Nov 21, 2017 at 12:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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