This is mainly for working on multiple files in vifm (log1.txt, log2.txt, etc.) so lines can be assumed to be sorted, though this should work in normal vim/nvim.

Let's say we have this:

test 1
test 2
test 3
test 4
test 5
testing 1
testing 2
testing 3

I'm on the line with test 3 (normal mode) and want to select everything from test 1 to test 5. I can choose to select every consecutive line (up & down) with the same starting substring test (with a space).

May be I'm on test 3 and want to select every consecutive line with the starting substring test (without a space), which is test 1 till testing 3 but not another.

In such cases, I want to visually select all consecutive lines with the same <count> characters. I can set this to 3,5,10,etc. I know the lines I want to select typically work with 5 starting characters so I can set that as a constant. However, I don't know beforehand what those 5 characters would be.

How can I make a macro/binding to do such selection?


If you want a command that does that based on the length of the prefix, here's one:

function! Prefix(count)
  if a:count == 0
  let prefix = strcharpart(getline('.'), 0, a:count)
  let pat = '\V\^'.escape(prefix, '\')
  echom "pat = [".pat."]"
  let start = line('.')
  let end = start
  let last = line('$')
  while start > 1 && match(getline(start-1), pat) == 0
    let start -= 1
  while end > 1 && match(getline(end+1), pat) == 0
    let end += 1
  execute printf('normal! %dGV%dG', start, end)
command! -bar -count=0 Prefix call Prefix(<count>)

You call it as :Prefix 4 to use the first four characters (match both test and testing), or :5Prefix to use the first five (match only test followed by a space.) You can pass the length of the prefix either before or after the command itself.

It works by assembling a pattern based on the first n characters of the line, then iterating through the lines that precede and follow the current one, as long as the pattern still matches. Then finally it visually select the range with the matching lines.


The only idea that comes to my mind is to use / to search for another:

  • V : select the first line
  • / : search the first non-matching line (another)
  • k : up a line
  • 2
    Or V/another/-<CR> – D. Ben Knoble Jul 15 '20 at 21:49
  • Oh, nice trick! – Biggybi Jul 16 '20 at 2:12
  • Now I’m wondering if you could automate slightly using a negative match/lookbehind and the - search offset – D. Ben Knoble Jul 16 '20 at 2:19
  • @D.BenKnoble I wonder too, but I was not able to do it, so I found another way! – Biggybi Jul 16 '20 at 3:12

Here's a seemingly working solution, but I'm not sure it's very robust. It selects down from the current line (not above).

function! SelectFirstWordBlock(all) abort
  let curpos = getpos('.')
  let curline = curpos[1]
  let nextline = curline + 1
  let firstword = split(trim(getline('.')), '\W\+')[0]
  while (a:all == 1 && split(trim(getline(nextline)), '\W\+')[0] =~ firstword)
    let nextline += 1
  while (a:all == 0 && split(trim(getline(nextline)), '\W\+')[0] ==# firstword)
    let nextline += 1
  normal V
  call cursor(nextline - 1, 0)
nnoremap <leader>V :silent! call SelectFirstWordBlock('1')<cr>
nnoremap <leader><c-v> :silent! call SelectFirstWordBlock('0')<cr>

The first mapping matches with a space (test X and not testx), whereas the second matches without it.

  • I'd like to match a set number of characters rather than a word, since I sometimes want to select things that match the first few characters but not an entire word. With matching by word, the selection wouldn't quite work if the cursor is on testing 2 and I want to also select up to test 1. – John Zhau Jul 16 '20 at 5:53
  • Indeed. I'm not quite sure how to do this though. – Biggybi Jul 16 '20 at 6: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.