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