1

How can I get the line numbers of all matching strings in a buffer?
Like in this buffer, I want to get the list [1,6,50] of all line numbers with matching strings.

1:  test ....


6:  test ....

50: test ....
7
  • 3
    Welcome to our site :) I suspect that you are facing a XY problem: what do you actually want to do with this list once you get it? Because there is possibly an easier way to do it than parsing the line number and then reusing these numbers. For example using this command :execute 'grep test ' . expand('%') | copen would get the lines matching your pattern in the quicklist window which is pretty useful. (The limitation being that you need your buffer to be saved in a file) – statox Jul 21 '20 at 13:32
  • @statox, over the years I had quite a few cases where I needed to obtain the indices of the elements that match a predicate. Unfortunately Vim doesn't offer any simple solution. – Luc Hermitte Jul 21 '20 at 14:05
  • 2
    Something like let mylist = [] | g/^test/call add(mylist, line('.')) should work okay. But @statox is right: 9 out of 10 it's XY problem. – Matt Jul 21 '20 at 14:12
  • 1
    @Matt this is also worth an answer :+1: – statox Jul 21 '20 at 14:14
  • 1
    Still don't see what's problem with just :g. Internally it does two passes and works just okay. Do not reinvent the wheel. – Matt Jul 21 '20 at 14:25
4

Doing two manual passes over the text is rarely needed. Most often one just does

g/^test/DoSomething

And that's enough. The reason is that :g is implemented in two passes, so it's not a problem if you, say, add a few lines after each match.

However, if you really want those numbers in a List, you can achieve this with the same pattern:

let temp = []
g/^test/call add(temp, line('.'))
1
2

A convoluted (and untested) way I sometimes use. The idea is to use map() to associate line numbers to the actual (matching) lines, then filter() to keep only the matching lines, then map() again to return only the line numbers

" Note: This code uses lambdas and vim 8.{recent} methods.
:echo getline(1, '$')->map({l, v -> [l+1, v =~ 'test']})->filter({k,v -> v[1]})->map({k,v -> v[0]})

Another convoluted approach consists in using a function that permits to apply changes as matches are found (still untested)

:let g:lines = []
:call getline(1, '$')->map({k,v -> v =~ 'test' ? add(g:lines, k+1) : g:lines})
:echo g:lines

" or 
:call map(getline(1, '$'), {k,v -> v =~ 'test' ? add(g:lines, k+1) : g:lines})

Note: we could also use :global which is much simpler (see Matt's answer) but as it messes search register and cursor position, I usually avoid it when writing plugins.

3
  • I tried the second on neovim. i got trailing characters – xiaodong huan Jul 21 '20 at 14:26
  • 1
    @xiaodonghuan Vim and Neovim are not 100% compatible. You should add the appropriate tag. – Matt Jul 21 '20 at 14:31
  • @xiaodonghuan, I've fixed the second approach, and added the old method-less syntax. – Luc Hermitte Jul 21 '20 at 14:49

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.