13

Update 2020-07-17: Now I would recommend using the vim-matchup plugin. When matching pair highlighting is turned on, vim-matchup also highlights matching keywords like if/else/endif or while/continue/break.


Update 2017-12-04: I've updated the answer by @Tommy A below to account for poorly specified matchit.vim groups, and other situations where the % operator does not return the cursor to the original position, ever. Check out the differences in the "while" loop. Anyone reading this thread is advised to use this version, to avoid infinite loops:

" Loop until `%` returns the original line number. Abort if:
" (1) the % operator keeps us on the same line, or
" (2) the % operator doesn't return us to the same line after some nubmer of jumps
function! s:get_match_lines(line) abort
  let a:tolerance = 25
  let a:badbrea = 1
  let a:linebefore = -1
  let lines = []
  while a:tolerance && a:linebefore != line('.')
    let a:linebefore = line('.')
    let a:tolerance -= 1
    normal! %
    if line('.') == a:line
      let a:badbreak = 0
      break
    endif
    call add(lines, line('.'))
  endwhile
  exe 'normal! ' . a:line . 'gg'
  if a:badbreak == 1
    return []
  else
    return lines
  endif
endfunction

" Trigger matching line highlights.
" Here `b:hl_last_line` prevents running the script again while the
" cursor is moved to matching pair on the same line.
function! s:hl_matching_lines() abort
  if exists('b:hl_last_line') && b:hl_last_line == line('.')
    return
  endif
  let b:hl_last_line = line('.')
  let view = winsaveview()
  silent! call matchdelete(12345)  " arbitrary match ID
  let lines = s:get_match_lines(view.lnum)
  if empty(lines)  " try detecting at column 1
    call cursor(view.lnum, 1)
    let lines = s:get_match_lines(view.lnum)
  endif
  if len(lines)
    " call add(lines, view.lnum)  " highlight current line as well as other lines
    if exists('*matchaddpos')  " faster than using 'matchadd' patterns (if available)
      call matchaddpos('MatchLine', lines, 0, 12345)
    else  " highlight matching lines using the \%l atom and add highlight group
      call matchadd('MatchLine', join(map(lines, '''\%''.v:val.''l'''), '\|'), 0, 12345)
    endif
  endif
  call winrestview(view)
endfunction
function! s:hl_matching_lines_clear() abort
  silent! call matchdelete(12345)
  unlet! b:hl_last_line
endfunction

" Define highlight groups used for matching lines.
" Use the `MatchParen` group by default.
highlight default link MatchLine MatchParen
augroup matching_lines
  autocmd!
  autocmd CursorMoved * call s:hl_matching_lines()
  autocmd InsertEnter * call s:hl_matching_lines_clear()
  autocmd TextChanged,TextChangedI * call s:hl_matching_lines_clear()
augroup END

Original question:

Currently vim highlights matching parentheses, brackets, quotes, etc. with cyan background and white foreground -- the cursor can be moved between these with %. Thanks to my matchit.vim, I can also switch with % between if/end, for/end, etc., however these are not highlighted on selection.

How can I automatically highlight these matching pairs upon selection, like is automatically done with parentheses?

Furthermore, how can I modify the background color used for these pairs using :highlight?

Thanks in advance.

5
  • 2
    I know this is an old question, but I just saw it pop up onto the front page a moment ago. Just want to mention my new plugin match-up is designed to do exactly this, in a more robust way: github.com/andymass/vim-matchup (along with many other improvements over matchit).
    – Mass
    Dec 4, 2017 at 19:07
  • Looks really useful, thanks for making this! I'll try it out.
    – Luke Davis
    Dec 4, 2017 at 19:15
  • Hi, how I can use this code? If I put this in autoload, how should I call it?
    – BaRud
    Jul 17, 2020 at 11:24
  • @BaRud At this point I would recommend using vim-matchup instead (see update).
    – Luke Davis
    Jul 17, 2020 at 19:42
  • @LukeDavis: True, I have checked vim-matchup. But for my purpose, I dont want to use this.
    – BaRud
    Jul 18, 2020 at 5:04

1 Answer 1

13

I thought this idea was interesting, so I gave it a shot. It will be particularly useful in dense files, such as HTML.

match lines

The following script simply lets matchit.vim do what it does while recording the line numbers. Explanations are in the script's comments.

matchlines.vim

function! s:get_match_lines(line) abort
  let lines = []

  " Loop until `%` returns the original line number
  while 1
    normal %
    if line('.') == a:line
      " Note that the current line number is never added to the `lines`
      " list.
      break
    endif
    call add(lines, line('.'))
  endwhile

  return lines
endfunction

function! s:hl_matching_lines() abort
  " `b:hl_last_line` prevents running the script again while the cursor is
  " moved on the same line.  Otherwise, the cursor won't move if the current
  " line has matching pairs of something.
  if exists('b:hl_last_line') && b:hl_last_line == line('.')
    return
  endif

  let b:hl_last_line = line('.')

  " Save the window's state.
  let view = winsaveview()

  " Delete a previous match highlight.  `12345` is used for the match ID.
  " It can be anything as long as it's unique.
  silent! call matchdelete(12345)

  " Try to get matching lines from the current cursor position.
  let lines = s:get_match_lines(view.lnum)

  if empty(lines)
    " It's possible that the line has another matching line, but can't be
    " matched at the current column.  Move the cursor to column 1 to try
    " one more time.
    call cursor(view.lnum, 1)
    let lines = s:get_match_lines(view.lnum)
  endif

  if len(lines)
    " Since the current line is not in the `lines` list, only the other
    " lines are highlighted.  If you want to highlight the current line as
    " well:
    " call add(lines, view.lnum)
    if exists('*matchaddpos')
      " If matchaddpos() is availble, use it to highlight the lines since it's
      " faster than using a pattern in matchadd().
      call matchaddpos('MatchLine', lines, 0, 12345)
    else
      " Highlight the matching lines using the \%l atom.  The `MatchLine`
      " highlight group is used.
      call matchadd('MatchLine', join(map(lines, '''\%''.v:val.''l'''), '\|'), 0, 12345)
    endif
  endif

  " Restore the window's state.
  call winrestview(view)
endfunction

function! s:hl_matching_lines_clear() abort
  silent! call matchdelete(12345)
  unlet! b:hl_last_line
endfunction


" The highlight group that's used for highlighting matched lines.  By
" default, it will be the same as the `MatchParen` group.
highlight default link MatchLine MatchParen

augroup matching_lines
  autocmd!
  " Highlight lines as the cursor moves.
  autocmd CursorMoved * call s:hl_matching_lines()
  " Remove the highlight while in insert mode.
  autocmd InsertEnter * call s:hl_matching_lines_clear()
  " Remove the highlight after TextChanged.
  autocmd TextChanged,TextChangedI * call s:hl_matching_lines_clear()
augroup END

I don't really like this happening on CursorMoved, though. I think it's better as a key map that can be used when I need it:

nnoremap <silent> <leader>l :<c-u>call <sid>hl_matching_lines()<cr>
7
  • You could use the matchaddpos function instead. It is slightly faster and if you highlight the entire line anyway, it will simplify things a little bit. Jul 10, 2016 at 16:01
  • 1
    @KarlYngveLervåg Good point. I subconsciously avoid it because it's still a relatively new function (v7.4.330 I think) and it bit me in the ass once. I'll update the answer to use it.
    – Tommy A
    Jul 10, 2016 at 16:05
  • This is absolutely perfect, thanks so much! Good Vimscript practice too; will try to understand each line. I imagine this could be quite popular if you're the first to write this sort of utility.
    – Luke Davis
    Jul 11, 2016 at 22:14
  • @LukeDavis There is an undesired effect that comes from this that I noticed: it will screw up the jump list. I came up with a way to sort of fix it by using <c-o> for the number of times that a match was found and it works in a way. The problem is there's a bug in matchit.vim that adds the top line of the window to the jump list. It has been acknowledged, but there doesn't seem to be any rush to fix it.
    – Tommy A
    Jul 11, 2016 at 22:21
  • @TommyA Hey, thanks again for this utility. I actually find on my computer the delay with the CursorMove autocmd is pretty negligible. I updated your function s:get_match_lines(line) to help guard against infinite loops, which was becoming a big problem for me in certain weird contexts. Unfortunately matchit.vim is full of flaws. See my edit above and let me know if you have any suggestions; I'm a vimscript beginner.
    – Luke Davis
    Feb 3, 2017 at 22:05

Your Answer

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

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