15e3cb7e8SBram Moolenaar" Vim plugin for showing matching parens
25e3cb7e8SBram Moolenaar" Maintainer:  Bram Moolenaar <[email protected]>
3*db6ea063SBram Moolenaar" Last Change: 2014 Jul 09
45e3cb7e8SBram Moolenaar
55e3cb7e8SBram Moolenaar" Exit quickly when:
65e3cb7e8SBram Moolenaar" - this plugin was already loaded (or disabled)
75e3cb7e8SBram Moolenaar" - when 'compatible' is set
884a05accSBram Moolenaar" - the "CursorMoved" autocmd event is not available.
95e3cb7e8SBram Moolenaarif exists("g:loaded_matchparen") || &cp || !exists("##CursorMoved")
105e3cb7e8SBram Moolenaar  finish
115e3cb7e8SBram Moolenaarendif
125e3cb7e8SBram Moolenaarlet g:loaded_matchparen = 1
135e3cb7e8SBram Moolenaar
14ad3b366cSBram Moolenaarif !exists("g:matchparen_timeout")
15ad3b366cSBram Moolenaar  let g:matchparen_timeout = 300
16ad3b366cSBram Moolenaarendif
17ad3b366cSBram Moolenaarif !exists("g:matchparen_insert_timeout")
18ad3b366cSBram Moolenaar  let g:matchparen_insert_timeout = 60
19ad3b366cSBram Moolenaarendif
20ad3b366cSBram Moolenaar
215e3cb7e8SBram Moolenaaraugroup matchparen
225e3cb7e8SBram Moolenaar  " Replace all matchparen autocommands
23fa2e0444SBram Moolenaar  autocmd! CursorMoved,CursorMovedI,WinEnter * call s:Highlight_Matching_Pair()
24186628f6SBram Moolenaar  if exists('##TextChanged')
25186628f6SBram Moolenaar    autocmd! TextChanged,TextChangedI * call s:Highlight_Matching_Pair()
26186628f6SBram Moolenaar  endif
275e3cb7e8SBram Moolenaaraugroup END
285e3cb7e8SBram Moolenaar
295e3cb7e8SBram Moolenaar" Skip the rest if it was already done.
305e3cb7e8SBram Moolenaarif exists("*s:Highlight_Matching_Pair")
315e3cb7e8SBram Moolenaar  finish
325e3cb7e8SBram Moolenaarendif
335e3cb7e8SBram Moolenaar
345c73622aSBram Moolenaarlet s:cpo_save = &cpo
353b1ddfedSBram Moolenaarset cpo-=C
363b1ddfedSBram Moolenaar
375e3cb7e8SBram Moolenaar" The function that is invoked (very often) to define a ":match" highlighting
385e3cb7e8SBram Moolenaar" for any matching paren.
395e3cb7e8SBram Moolenaarfunction! s:Highlight_Matching_Pair()
405e3cb7e8SBram Moolenaar  " Remove any previous match.
41910f66f9SBram Moolenaar  if exists('w:paren_hl_on') && w:paren_hl_on
42b3414595SBram Moolenaar    silent! call matchdelete(3)
43910f66f9SBram Moolenaar    let w:paren_hl_on = 0
445e3cb7e8SBram Moolenaar  endif
455e3cb7e8SBram Moolenaar
4636fc535cSBram Moolenaar  " Avoid that we remove the popup menu.
47f2b2e70bSBram Moolenaar  " Return when there are no colors (looks like the cursor jumps).
48f2b2e70bSBram Moolenaar  if pumvisible() || (&t_Co < 8 && !has("gui_running"))
4936fc535cSBram Moolenaar    return
5036fc535cSBram Moolenaar  endif
5136fc535cSBram Moolenaar
525e3cb7e8SBram Moolenaar  " Get the character under the cursor and check if it's in 'matchpairs'.
535e3cb7e8SBram Moolenaar  let c_lnum = line('.')
545e3cb7e8SBram Moolenaar  let c_col = col('.')
555e3cb7e8SBram Moolenaar  let before = 0
565e3cb7e8SBram Moolenaar
57*db6ea063SBram Moolenaar  let text = getline(c_lnum)
58*db6ea063SBram Moolenaar  let c = text[c_col - 1]
5941e6cd5fSBram Moolenaar  let plist = split(&matchpairs, '.\zs[:,]')
605e3cb7e8SBram Moolenaar  let i = index(plist, c)
615e3cb7e8SBram Moolenaar  if i < 0
625e3cb7e8SBram Moolenaar    " not found, in Insert mode try character before the cursor
635e3cb7e8SBram Moolenaar    if c_col > 1 && (mode() == 'i' || mode() == 'R')
645e3cb7e8SBram Moolenaar      let before = 1
65*db6ea063SBram Moolenaar      let c = text[c_col - 2]
665e3cb7e8SBram Moolenaar      let i = index(plist, c)
675e3cb7e8SBram Moolenaar    endif
685e3cb7e8SBram Moolenaar    if i < 0
695e3cb7e8SBram Moolenaar      " not found, nothing to do
705e3cb7e8SBram Moolenaar      return
715e3cb7e8SBram Moolenaar    endif
725e3cb7e8SBram Moolenaar  endif
735e3cb7e8SBram Moolenaar
745e3cb7e8SBram Moolenaar  " Figure out the arguments for searchpairpos().
755e3cb7e8SBram Moolenaar  if i % 2 == 0
765e3cb7e8SBram Moolenaar    let s_flags = 'nW'
775e3cb7e8SBram Moolenaar    let c2 = plist[i + 1]
785e3cb7e8SBram Moolenaar  else
795e3cb7e8SBram Moolenaar    let s_flags = 'nbW'
805e3cb7e8SBram Moolenaar    let c2 = c
815e3cb7e8SBram Moolenaar    let c = plist[i - 1]
825e3cb7e8SBram Moolenaar  endif
835e3cb7e8SBram Moolenaar  if c == '['
845e3cb7e8SBram Moolenaar    let c = '\['
855e3cb7e8SBram Moolenaar    let c2 = '\]'
865e3cb7e8SBram Moolenaar  endif
875e3cb7e8SBram Moolenaar
885e3cb7e8SBram Moolenaar  " Find the match.  When it was just before the cursor move it there for a
89c06ac340SBram Moolenaar  " moment.
905e3cb7e8SBram Moolenaar  if before > 0
91*db6ea063SBram Moolenaar    let save_cursor = getcurpos()
925e3cb7e8SBram Moolenaar    call cursor(c_lnum, c_col - before)
935e3cb7e8SBram Moolenaar  endif
9425e2c9e3SBram Moolenaar
9525e2c9e3SBram Moolenaar  " When not in a string or comment ignore matches inside them.
965302d9ebSBram Moolenaar  " We match "escape" for special items, such as lispEscapeSpecial.
9725e2c9e3SBram Moolenaar  let s_skip ='synIDattr(synID(line("."), col("."), 0), "name") ' .
9881af9250SBram Moolenaar	\ '=~?  "string\\|character\\|singlequote\\|escape\\|comment"'
9925e2c9e3SBram Moolenaar  execute 'if' s_skip '| let s_skip = 0 | endif'
10025e2c9e3SBram Moolenaar
101f2b2e70bSBram Moolenaar  " Limit the search to lines visible in the window.
102f2b2e70bSBram Moolenaar  let stoplinebottom = line('w$')
103f2b2e70bSBram Moolenaar  let stoplinetop = line('w0')
104f2b2e70bSBram Moolenaar  if i % 2 == 0
105f2b2e70bSBram Moolenaar    let stopline = stoplinebottom
106f2b2e70bSBram Moolenaar  else
107f2b2e70bSBram Moolenaar    let stopline = stoplinetop
108f2b2e70bSBram Moolenaar  endif
109f2b2e70bSBram Moolenaar
110f2b2e70bSBram Moolenaar  " Limit the search time to 300 msec to avoid a hang on very long lines.
111f2b2e70bSBram Moolenaar  " This fails when a timeout is not supported.
112ad3b366cSBram Moolenaar  if mode() == 'i' || mode() == 'R'
113ad3b366cSBram Moolenaar    let timeout = exists("b:matchparen_insert_timeout") ? b:matchparen_insert_timeout : g:matchparen_insert_timeout
114ad3b366cSBram Moolenaar  else
115ad3b366cSBram Moolenaar    let timeout = exists("b:matchparen_timeout") ? b:matchparen_timeout : g:matchparen_timeout
116ad3b366cSBram Moolenaar  endif
117ad3b366cSBram Moolenaar  try
118ad3b366cSBram Moolenaar    let [m_lnum, m_col] = searchpairpos(c, '', c2, s_flags, s_skip, stopline, timeout)
11976929293SBram Moolenaar  catch /E118/
120f2b2e70bSBram Moolenaar    " Can't use the timeout, restrict the stopline a bit more to avoid taking
121f2b2e70bSBram Moolenaar    " a long time on closed folds and long lines.
122f2b2e70bSBram Moolenaar    " The "viewable" variables give a range in which we can scroll while
123f2b2e70bSBram Moolenaar    " keeping the cursor at the same position.
124f2b2e70bSBram Moolenaar    " adjustedScrolloff accounts for very large numbers of scrolloff.
125f2b2e70bSBram Moolenaar    let adjustedScrolloff = min([&scrolloff, (line('w$') - line('w0')) / 2])
126f2b2e70bSBram Moolenaar    let bottom_viewable = min([line('$'), c_lnum + &lines - adjustedScrolloff - 2])
127f2b2e70bSBram Moolenaar    let top_viewable = max([1, c_lnum-&lines+adjustedScrolloff + 2])
128f2b2e70bSBram Moolenaar    " one of these stoplines will be adjusted below, but the current values are
129f2b2e70bSBram Moolenaar    " minimal boundaries within the current window
130f2b2e70bSBram Moolenaar    if i % 2 == 0
131f2b2e70bSBram Moolenaar      if has("byte_offset") && has("syntax_items") && &smc > 0
132f2b2e70bSBram Moolenaar	let stopbyte = min([line2byte("$"), line2byte(".") + col(".") + &smc * 2])
133f2b2e70bSBram Moolenaar	let stopline = min([bottom_viewable, byte2line(stopbyte)])
134f2b2e70bSBram Moolenaar      else
135f2b2e70bSBram Moolenaar	let stopline = min([bottom_viewable, c_lnum + 100])
136f2b2e70bSBram Moolenaar      endif
137f2b2e70bSBram Moolenaar      let stoplinebottom = stopline
138f2b2e70bSBram Moolenaar    else
139f2b2e70bSBram Moolenaar      if has("byte_offset") && has("syntax_items") && &smc > 0
140f2b2e70bSBram Moolenaar	let stopbyte = max([1, line2byte(".") + col(".") - &smc * 2])
141f2b2e70bSBram Moolenaar	let stopline = max([top_viewable, byte2line(stopbyte)])
142f2b2e70bSBram Moolenaar      else
143f2b2e70bSBram Moolenaar	let stopline = max([top_viewable, c_lnum - 100])
144f2b2e70bSBram Moolenaar      endif
145f2b2e70bSBram Moolenaar      let stoplinetop = stopline
146f2b2e70bSBram Moolenaar    endif
1475e3cb7e8SBram Moolenaar    let [m_lnum, m_col] = searchpairpos(c, '', c2, s_flags, s_skip, stopline)
14876929293SBram Moolenaar  endtry
14925e2c9e3SBram Moolenaar
1505e3cb7e8SBram Moolenaar  if before > 0
151*db6ea063SBram Moolenaar    call setpos('.', save_cursor)
1525e3cb7e8SBram Moolenaar  endif
1535e3cb7e8SBram Moolenaar
1545e3cb7e8SBram Moolenaar  " If a match is found setup match highlighting.
155ef04586dSBram Moolenaar  if m_lnum > 0 && m_lnum >= stoplinetop && m_lnum <= stoplinebottom
156b3414595SBram Moolenaar    if exists('*matchaddpos')
157b3414595SBram Moolenaar      call matchaddpos('MatchParen', [[c_lnum, c_col - before], [m_lnum, m_col]], 10, 3)
158b3414595SBram Moolenaar    else
159e1438bb8SBram Moolenaar      exe '3match MatchParen /\(\%' . c_lnum . 'l\%' . (c_col - before) .
1605e3cb7e8SBram Moolenaar	    \ 'c\)\|\(\%' . m_lnum . 'l\%' . m_col . 'c\)/'
161b3414595SBram Moolenaar    endif
162910f66f9SBram Moolenaar    let w:paren_hl_on = 1
1635e3cb7e8SBram Moolenaar  endif
1645e3cb7e8SBram Moolenaarendfunction
1655e3cb7e8SBram Moolenaar
1665e3cb7e8SBram Moolenaar" Define commands that will disable and enable the plugin.
167b3414595SBram Moolenaarcommand! NoMatchParen windo silent! call matchdelete(3) | unlet! g:loaded_matchparen |
168fa2e0444SBram Moolenaar	  \ au! matchparen
169fa2e0444SBram Moolenaarcommand! DoMatchParen runtime plugin/matchparen.vim | windo doau CursorMoved
1703b1ddfedSBram Moolenaar
1715c73622aSBram Moolenaarlet &cpo = s:cpo_save
1725c73622aSBram Moolenaarunlet s:cpo_save
173