15e3cb7e8SBram Moolenaar" Vim plugin for showing matching parens 25e3cb7e8SBram Moolenaar" Maintainer: Bram Moolenaar <[email protected]> 3*56994d21SBram Moolenaar" Last Change: 2021 Apr 08 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() 2473fef330SBram Moolenaar autocmd! WinLeave * call s:Remove_Matches() 25186628f6SBram Moolenaar if exists('##TextChanged') 26186628f6SBram Moolenaar autocmd! TextChanged,TextChangedI * call s:Highlight_Matching_Pair() 27186628f6SBram Moolenaar endif 285e3cb7e8SBram Moolenaaraugroup END 295e3cb7e8SBram Moolenaar 305e3cb7e8SBram Moolenaar" Skip the rest if it was already done. 315e3cb7e8SBram Moolenaarif exists("*s:Highlight_Matching_Pair") 325e3cb7e8SBram Moolenaar finish 335e3cb7e8SBram Moolenaarendif 345e3cb7e8SBram Moolenaar 355c73622aSBram Moolenaarlet s:cpo_save = &cpo 363b1ddfedSBram Moolenaarset cpo-=C 373b1ddfedSBram Moolenaar 385e3cb7e8SBram Moolenaar" The function that is invoked (very often) to define a ":match" highlighting 395e3cb7e8SBram Moolenaar" for any matching paren. 401ff14ba2SBram Moolenaarfunc s:Highlight_Matching_Pair() 415e3cb7e8SBram Moolenaar " Remove any previous match. 4273fef330SBram Moolenaar call s:Remove_Matches() 435e3cb7e8SBram Moolenaar 4436fc535cSBram Moolenaar " Avoid that we remove the popup menu. 45f2b2e70bSBram Moolenaar " Return when there are no colors (looks like the cursor jumps). 46f2b2e70bSBram Moolenaar if pumvisible() || (&t_Co < 8 && !has("gui_running")) 4736fc535cSBram Moolenaar return 4836fc535cSBram Moolenaar endif 4936fc535cSBram Moolenaar 505e3cb7e8SBram Moolenaar " Get the character under the cursor and check if it's in 'matchpairs'. 515e3cb7e8SBram Moolenaar let c_lnum = line('.') 525e3cb7e8SBram Moolenaar let c_col = col('.') 535e3cb7e8SBram Moolenaar let before = 0 545e3cb7e8SBram Moolenaar 55db6ea063SBram Moolenaar let text = getline(c_lnum) 56c21d67e3SBram Moolenaar let matches = matchlist(text, '\(.\)\=\%'.c_col.'c\(.\=\)') 57256972a9SBram Moolenaar if empty(matches) 58256972a9SBram Moolenaar let [c_before, c] = ['', ''] 59256972a9SBram Moolenaar else 60256972a9SBram Moolenaar let [c_before, c] = matches[1:2] 61256972a9SBram Moolenaar endif 6241e6cd5fSBram Moolenaar let plist = split(&matchpairs, '.\zs[:,]') 635e3cb7e8SBram Moolenaar let i = index(plist, c) 645e3cb7e8SBram Moolenaar if i < 0 655e3cb7e8SBram Moolenaar " not found, in Insert mode try character before the cursor 665e3cb7e8SBram Moolenaar if c_col > 1 && (mode() == 'i' || mode() == 'R') 67256972a9SBram Moolenaar let before = strlen(c_before) 68256972a9SBram Moolenaar let c = c_before 695e3cb7e8SBram Moolenaar let i = index(plist, c) 705e3cb7e8SBram Moolenaar endif 715e3cb7e8SBram Moolenaar if i < 0 725e3cb7e8SBram Moolenaar " not found, nothing to do 735e3cb7e8SBram Moolenaar return 745e3cb7e8SBram Moolenaar endif 755e3cb7e8SBram Moolenaar endif 765e3cb7e8SBram Moolenaar 775e3cb7e8SBram Moolenaar " Figure out the arguments for searchpairpos(). 785e3cb7e8SBram Moolenaar if i % 2 == 0 795e3cb7e8SBram Moolenaar let s_flags = 'nW' 805e3cb7e8SBram Moolenaar let c2 = plist[i + 1] 815e3cb7e8SBram Moolenaar else 825e3cb7e8SBram Moolenaar let s_flags = 'nbW' 835e3cb7e8SBram Moolenaar let c2 = c 845e3cb7e8SBram Moolenaar let c = plist[i - 1] 855e3cb7e8SBram Moolenaar endif 865e3cb7e8SBram Moolenaar if c == '[' 875e3cb7e8SBram Moolenaar let c = '\[' 885e3cb7e8SBram Moolenaar let c2 = '\]' 895e3cb7e8SBram Moolenaar endif 905e3cb7e8SBram Moolenaar 915e3cb7e8SBram Moolenaar " Find the match. When it was just before the cursor move it there for a 92c06ac340SBram Moolenaar " moment. 935e3cb7e8SBram Moolenaar if before > 0 9407d87790SBram Moolenaar let has_getcurpos = exists("*getcurpos") 9507d87790SBram Moolenaar if has_getcurpos 9607d87790SBram Moolenaar " getcurpos() is more efficient but doesn't exist before 7.4.313. 97db6ea063SBram Moolenaar let save_cursor = getcurpos() 9807d87790SBram Moolenaar else 9907d87790SBram Moolenaar let save_cursor = winsaveview() 10007d87790SBram Moolenaar endif 1015e3cb7e8SBram Moolenaar call cursor(c_lnum, c_col - before) 1025e3cb7e8SBram Moolenaar endif 10325e2c9e3SBram Moolenaar 1043d1d6475SBram Moolenaar if !has("syntax") || !exists("g:syntax_on") 1053d1d6475SBram Moolenaar let s_skip = "0" 1063d1d6475SBram Moolenaar else 1073d1d6475SBram Moolenaar " Build an expression that detects whether the current cursor position is 1083d1d6475SBram Moolenaar " in certain syntax types (string, comment, etc.), for use as 1093d1d6475SBram Moolenaar " searchpairpos()'s skip argument. 110130cbfc3SBram Moolenaar " We match "escape" for special items, such as lispEscapeSpecial, and 111130cbfc3SBram Moolenaar " match "symbol" for lispBarSymbol. 112e98cfe1cSBram Moolenaar let s_skip = '!empty(filter(map(synstack(line("."), col(".")), ''synIDattr(v:val, "name")''), ' . 113*56994d21SBram Moolenaar \ '''v:val =~? "string\\|character\\|singlequote\\|escape\\|symbol\\|comment"''))' 114e98cfe1cSBram Moolenaar " If executing the expression determines that the cursor is currently in 115e98cfe1cSBram Moolenaar " one of the syntax types, then we want searchpairpos() to find the pair 116e98cfe1cSBram Moolenaar " within those syntax types (i.e., not skip). Otherwise, the cursor is 1173d1d6475SBram Moolenaar " outside of the syntax types and s_skip should keep its value so we skip 1183d1d6475SBram Moolenaar " any matching pair inside the syntax types. 1193d1d6475SBram Moolenaar " Catch if this throws E363: pattern uses more memory than 'maxmempattern'. 1203d1d6475SBram Moolenaar try 1213d1d6475SBram Moolenaar execute 'if ' . s_skip . ' | let s_skip = "0" | endif' 1223d1d6475SBram Moolenaar catch /^Vim\%((\a\+)\)\=:E363/ 1233d1d6475SBram Moolenaar " We won't find anything, so skip searching, should keep Vim responsive. 1243d1d6475SBram Moolenaar return 1253d1d6475SBram Moolenaar endtry 1263d1d6475SBram Moolenaar endif 12725e2c9e3SBram Moolenaar 128f2b2e70bSBram Moolenaar " Limit the search to lines visible in the window. 129f2b2e70bSBram Moolenaar let stoplinebottom = line('w$') 130f2b2e70bSBram Moolenaar let stoplinetop = line('w0') 131f2b2e70bSBram Moolenaar if i % 2 == 0 132f2b2e70bSBram Moolenaar let stopline = stoplinebottom 133f2b2e70bSBram Moolenaar else 134f2b2e70bSBram Moolenaar let stopline = stoplinetop 135f2b2e70bSBram Moolenaar endif 136f2b2e70bSBram Moolenaar 137f2b2e70bSBram Moolenaar " Limit the search time to 300 msec to avoid a hang on very long lines. 138f2b2e70bSBram Moolenaar " This fails when a timeout is not supported. 139ad3b366cSBram Moolenaar if mode() == 'i' || mode() == 'R' 140ad3b366cSBram Moolenaar let timeout = exists("b:matchparen_insert_timeout") ? b:matchparen_insert_timeout : g:matchparen_insert_timeout 141ad3b366cSBram Moolenaar else 142ad3b366cSBram Moolenaar let timeout = exists("b:matchparen_timeout") ? b:matchparen_timeout : g:matchparen_timeout 143ad3b366cSBram Moolenaar endif 144ad3b366cSBram Moolenaar try 145ad3b366cSBram Moolenaar let [m_lnum, m_col] = searchpairpos(c, '', c2, s_flags, s_skip, stopline, timeout) 14676929293SBram Moolenaar catch /E118/ 147f2b2e70bSBram Moolenaar " Can't use the timeout, restrict the stopline a bit more to avoid taking 148f2b2e70bSBram Moolenaar " a long time on closed folds and long lines. 149f2b2e70bSBram Moolenaar " The "viewable" variables give a range in which we can scroll while 150f2b2e70bSBram Moolenaar " keeping the cursor at the same position. 151f2b2e70bSBram Moolenaar " adjustedScrolloff accounts for very large numbers of scrolloff. 152f2b2e70bSBram Moolenaar let adjustedScrolloff = min([&scrolloff, (line('w$') - line('w0')) / 2]) 153f2b2e70bSBram Moolenaar let bottom_viewable = min([line('$'), c_lnum + &lines - adjustedScrolloff - 2]) 154f2b2e70bSBram Moolenaar let top_viewable = max([1, c_lnum-&lines+adjustedScrolloff + 2]) 155f2b2e70bSBram Moolenaar " one of these stoplines will be adjusted below, but the current values are 156f2b2e70bSBram Moolenaar " minimal boundaries within the current window 157f2b2e70bSBram Moolenaar if i % 2 == 0 158f2b2e70bSBram Moolenaar if has("byte_offset") && has("syntax_items") && &smc > 0 159f2b2e70bSBram Moolenaar let stopbyte = min([line2byte("$"), line2byte(".") + col(".") + &smc * 2]) 160f2b2e70bSBram Moolenaar let stopline = min([bottom_viewable, byte2line(stopbyte)]) 161f2b2e70bSBram Moolenaar else 162f2b2e70bSBram Moolenaar let stopline = min([bottom_viewable, c_lnum + 100]) 163f2b2e70bSBram Moolenaar endif 164f2b2e70bSBram Moolenaar let stoplinebottom = stopline 165f2b2e70bSBram Moolenaar else 166f2b2e70bSBram Moolenaar if has("byte_offset") && has("syntax_items") && &smc > 0 167f2b2e70bSBram Moolenaar let stopbyte = max([1, line2byte(".") + col(".") - &smc * 2]) 168f2b2e70bSBram Moolenaar let stopline = max([top_viewable, byte2line(stopbyte)]) 169f2b2e70bSBram Moolenaar else 170f2b2e70bSBram Moolenaar let stopline = max([top_viewable, c_lnum - 100]) 171f2b2e70bSBram Moolenaar endif 172f2b2e70bSBram Moolenaar let stoplinetop = stopline 173f2b2e70bSBram Moolenaar endif 1745e3cb7e8SBram Moolenaar let [m_lnum, m_col] = searchpairpos(c, '', c2, s_flags, s_skip, stopline) 17576929293SBram Moolenaar endtry 17625e2c9e3SBram Moolenaar 1775e3cb7e8SBram Moolenaar if before > 0 17807d87790SBram Moolenaar if has_getcurpos 179db6ea063SBram Moolenaar call setpos('.', save_cursor) 18007d87790SBram Moolenaar else 18107d87790SBram Moolenaar call winrestview(save_cursor) 18207d87790SBram Moolenaar endif 1835e3cb7e8SBram Moolenaar endif 1845e3cb7e8SBram Moolenaar 1855e3cb7e8SBram Moolenaar " If a match is found setup match highlighting. 186ef04586dSBram Moolenaar if m_lnum > 0 && m_lnum >= stoplinetop && m_lnum <= stoplinebottom 187b3414595SBram Moolenaar if exists('*matchaddpos') 188b3414595SBram Moolenaar call matchaddpos('MatchParen', [[c_lnum, c_col - before], [m_lnum, m_col]], 10, 3) 189b3414595SBram Moolenaar else 190e1438bb8SBram Moolenaar exe '3match MatchParen /\(\%' . c_lnum . 'l\%' . (c_col - before) . 1915e3cb7e8SBram Moolenaar \ 'c\)\|\(\%' . m_lnum . 'l\%' . m_col . 'c\)/' 192b3414595SBram Moolenaar endif 193910f66f9SBram Moolenaar let w:paren_hl_on = 1 1945e3cb7e8SBram Moolenaar endif 1955e3cb7e8SBram Moolenaarendfunction 1965e3cb7e8SBram Moolenaar 19773fef330SBram Moolenaarfunc s:Remove_Matches() 19873fef330SBram Moolenaar if exists('w:paren_hl_on') && w:paren_hl_on 19973fef330SBram Moolenaar silent! call matchdelete(3) 20073fef330SBram Moolenaar let w:paren_hl_on = 0 20173fef330SBram Moolenaar endif 20273fef330SBram Moolenaarendfunc 20373fef330SBram Moolenaar 20473fef330SBram Moolenaar 2055e3cb7e8SBram Moolenaar" Define commands that will disable and enable the plugin. 2061ff14ba2SBram Moolenaarcommand DoMatchParen call s:DoMatchParen() 2071ff14ba2SBram Moolenaarcommand NoMatchParen call s:NoMatchParen() 20801164a65SBram Moolenaar 2091ff14ba2SBram Moolenaarfunc s:NoMatchParen() 21001164a65SBram Moolenaar let w = winnr() 21101164a65SBram Moolenaar noau windo silent! call matchdelete(3) 21201164a65SBram Moolenaar unlet! g:loaded_matchparen 21301164a65SBram Moolenaar exe "noau ". w . "wincmd w" 21401164a65SBram Moolenaar au! matchparen 21501164a65SBram Moolenaarendfunc 21601164a65SBram Moolenaar 2171ff14ba2SBram Moolenaarfunc s:DoMatchParen() 21801164a65SBram Moolenaar runtime plugin/matchparen.vim 21901164a65SBram Moolenaar let w = winnr() 22001164a65SBram Moolenaar silent windo doau CursorMoved 22101164a65SBram Moolenaar exe "noau ". w . "wincmd w" 22201164a65SBram Moolenaarendfunc 2233b1ddfedSBram Moolenaar 2245c73622aSBram Moolenaarlet &cpo = s:cpo_save 2255c73622aSBram Moolenaarunlet s:cpo_save 226