" Vim plugin for showing matching pairs " Maintainer: Egor V. Rogov " Last Change: 2009 Mar 16 " Version: 1.0 " Based On: matchparen.vim (author: Bram Moolenaar) " Exit quickly when: " - this plugin was already loaded (or disabled) " - when 'compatible' is set " - the "CursorMoved" autocmd event is not availble. if exists("g:loaded_matchpair") || &cp || !exists("##CursorMoved") finish endif let g:loaded_matchpair = 1 let g:pairslist = [ \ ['(', ')'], \ ['\[', '\]'], \ ['{', '}'], \ ] augroup matchpair " Replace all matchpair autocommands autocmd! CursorMoved,CursorMovedI,WinEnter * call s:Highlight_Matching_Pairs() autocmd FileType plsql let g:pairslist = [ \ ['(', ')'], \ ['\[', '\]'], \ ['{', '}'], \ ['\c\%(\', '\c\\|\', '\c\'], \ ['\c\%(\', '\c\'], \ ['\c\\|\', '\c\', '\c\\%(\s\+IF\>\|\s\+LOOP\>\)\@!'] \ ] augroup END " Skip the rest if it was already done. if exists("*s:Highlight_Matching_Pairs") finish endif let cpo_save = &cpo set cpo-=C " The function that is invoked (very often) to define a ":match" highlighting " for any matching pair. function! s:Highlight_Matching_Pairs() " Remove any previous match. if exists('w:paren_hl_on') && w:paren_hl_on 3match none let w:paren_hl_on = 0 endif " Avoid that we remove the popup menu. " Return when there are no colors (looks like the cursor jumps). if pumvisible() || (&t_Co < 8 && !has("gui_running")) return endif " Search for a word under the cursor let l = line('.') let p = col('.') - 1 let c = getline(l) let pos_sav = -1 " Check each word in pairs list, use the first match found for pair in g:pairslist for word in pair let start = 0 let pos = match(c,word) " Iterate until match is found exactly under the cursor while pos <= p && pos != -1 let str = matchstr(c,word,start) if pos + strlen(str) - 1 >= p " Match is found under the cursor, call highlight function return s:Highlight_Match(l, pos, word, pair) else " Match before the cursor, need to try again more to the right if pos + strlen(str) >= p " Match right before the cursor; for now just save it let pair_sav = pair let word_sav = word let pos_sav = pos endif let start = pos + strlen(str) let pos = match(c,word,start) endif endwhile endfor endfor if pos_sav != -1 " If there was a saved match before the cursor, use it return s:Highlight_Match(l, pos_sav, word_sav, pair_sav) endif endfunction function s:Highlight_Match(l, pos, word, pair) let idx = index(a:pair, a:word) let poslist = [[a:l, a:pos+1, a:word]] if idx < len(a:pair) - 1 let poslist += s:Get_Positions(a:l, a:pos+1, a:pair, 1) endif if idx > 0 let poslist += s:Get_Positions(a:l, a:pos+1, a:pair, 0) endif "echo poslist "if len(poslist) > 1 return s:Highlight_Positions(poslist, a:pair) "endif endfunction function s:Get_Positions(l, pos, pair, dir) " Parts of the pair let p1 = a:pair[0] let p2 = len(a:pair) > 2 ? a:pair[1] : '' let p3 = a:pair[len(a:pair)-1] " Flags for searchpairpos() if a:dir let s_flags = 'W' else let s_flags = 'bW' endif " Find the match and move cursor there for a moment. " Cursor movement is needed for correct handling of pair ends by " searchpairpos() let save_cursor = winsaveview() call cursor(a:l, a:pos) " When not in a string or comment ignore matches inside them. let s_skip ='synIDattr(synID(line("."), col("."), 0), "name") ' . \ '=~? "string\\|character\\|singlequote\\|comment"' execute 'if' s_skip '| let s_skip = 0 | endif' " Limit the search to lines visible in the window. let stoplinebottom = line('w$') let stoplinetop = line('w0') if a:dir let stopline = stoplinebottom else let stopline = stoplinetop endif let poslist = [] let found = 1 while found try " Limit the search time to 300 msec to avoid a hang on very long lines. " This fails when a timeout is not supported. let [m_lnum, m_col] = searchpairpos(p1, p2, p3, s_flags, s_skip, stopline, 300) catch /E118/ " Can't use the timeout, restrict the stopline a bit more to avoid taking " a long time on closed folds and long lines. " The "viewable" variables give a range in which we can scroll while " keeping the cursor at the same position. " adjustedScrolloff accounts for very large numbers of scrolloff. let adjustedScrolloff = min([&scrolloff, (line('w$') - line('w0')) / 2]) let bottom_viewable = min([line('$'), a:l + &lines - adjustedScrolloff - 2]) let top_viewable = max([1, a:l-&lines+adjustedScrolloff + 2]) " one of these stoplines will be adjusted below, but the current values are " minimal boundaries within the current window if a:dir if has("byte_offset") && has("syntax_items") && &smc > 0 let stopbyte = min([line2byte("$"), line2byte(".") + col(".") + &smc * 2]) let stopline = min([bottom_viewable, byte2line(stopbyte)]) else let stopline = min([bottom_viewable, a:c_lnum + 100]) endif let stoplinebottom = stopline else if has("byte_offset") && has("syntax_items") && &smc > 0 let stopbyte = max([1, line2byte(".") + col(".") - &smc * 2]) let stopline = max([top_viewable, byte2line(stopbyte)]) else let stopline = max([top_viewable, a:c_lnum - 100]) endif let stoplinetop = stopline endif let ic_save = &ic set ic let [m_lnum, m_col] = searchpairpos(p1, p2, p3, s_flags, s_skip, stopline) let &ic = ic_save endtry let found = ( m_lnum > 0 && m_lnum >= stoplinetop && m_lnum <= stoplinebottom ) if found let i = 0 while i < len(a:pair) if searchpos(a:pair[i], 'cnW', line('.')) == [m_lnum, m_col] let poslist = add(poslist, [m_lnum, m_col, a:pair[i]]) if i == 0 || i == len(a:pair)-1 let found = 0 endif break endif let i += 1 endwhile endif endwhile call winrestview(save_cursor) return poslist endfunction " Compare two lists: l[0] comtains line number, l[1] contains position within " the line function s:Pos_Compare(l1, l2) if a:l1[0] < a:l2[0] return -1 elseif a:l1[0] > a:l2[0] return 1 else if a:l1[1] < a:l2[1] return -1 elseif a:l1[1] > a:l2[1] return 1 else return 0 endif endif endfunction function s:Highlight_Positions(poslist, pair) let regexp = [] for [l,pos,word] in a:poslist let regexp += ['\%'.l.'l\%'.pos.'c\('.word.'\)'] endfor try exe '3match MatchParen /'.join(regexp,'\|').'/' let w:paren_hl_on = 1 catch /E51/ " Too manu \( - ok, just highlight the first and the last items let pl = sort(copy(a:poslist), "s:Pos_Compare") let n = len(pl)-1 let regexp = ['\%'.pl[0][0].'l\%'.pl[0][1].'c\('.pl[0][2].'\)'] let regexp+= ['\%'.pl[n][0].'l\%'.pl[n][1].'c\('.pl[n][2].'\)'] exe '3match MatchParen /'.join(regexp,'\|').'/' let w:paren_hl_on = 1 endtry endfunction " Define commands that will disable and enable the plugin. command! NoMatchPair windo 3match none | unlet! g:loaded_matchpair | \ au! matchpair command! DoMatchPair runtime plugin/matchpair.vim | windo doau CursorMoved let &cpo = cpo_save