812 lines · 30.0 KB · Text
RawDownload
"  matchit.vim: (global plugin) Extended "%" matching"  Last Change: Mon May 15 10:00 PM 2006 EDT"  Maintainer:  Benji Fisher PhD   <benji@member.AMS.org>"  Version:     1.11, for Vim 6.3+"  URL:		http://www.vim.org/script.php?script_id=39 " Documentation:"  The documentation is in a separate file, matchit.txt . " Credits:"  Vim editor by Bram Moolenaar (Thanks, Bram!)"  Original script and design by Raul Segura Acevedo"  Support for comments by Douglas Potts"  Support for back references and other improvements by Benji Fisher"  Support for many languages by Johannes Zellner"  Suggestions for improvement, bug reports, and support for additional"  languages by Jordi-Albert Batalla, Neil Bird, Servatius Brandt, Mark"  Collett, Stephen Wall, Dany St-Amant, Yuheng Xie, and Johannes Zellner. " Debugging:"  If you'd like to try the built-in debugging commands..."   :MatchDebug      to activate debugging for the current buffer"  This saves the values of several key script variables as buffer-local"  variables.  See the MatchDebug() function, below, for details. " TODO:  I should think about multi-line patterns for b:match_words."   This would require an option:  how many lines to scan (default 1)."   This would be useful for Python, maybe also for *ML." TODO:  Maybe I should add a menu so that people will actually use some of"   the features that I have implemented." TODO:  Eliminate the MultiMatch function.  Add yet another argument to"   Match_wrapper() instead." TODO:  Allow :let b:match_words = '\(\(foo\)\(bar\)\):\3\2:end\1'" TODO:  Make backrefs safer by using '\V' (very no-magic)." TODO:  Add a level of indirection, so that custom % scripts can use my"   work but extend it. " allow user to prevent loading" and prevent duplicate loadingif exists("loaded_matchit") || &cp  finishendiflet loaded_matchit = 1let s:last_mps = ""let s:last_words = "" let s:save_cpo = &cposet cpo&vim nnoremap <silent> %  :<C-U>call <SID>Match_wrapper('',1,'n') <CR>nnoremap <silent> g% :<C-U>call <SID>Match_wrapper('',0,'n') <CR>vnoremap <silent> %  :<C-U>call <SID>Match_wrapper('',1,'v') <CR>m'gv``vnoremap <silent> g% :<C-U>call <SID>Match_wrapper('',0,'v') <CR>m'gv``onoremap <silent> %  v:<C-U>call <SID>Match_wrapper('',1,'o') <CR>onoremap <silent> g% v:<C-U>call <SID>Match_wrapper('',0,'o') <CR> " Analogues of [{ and ]} using matching patterns:nnoremap <silent> [% :<C-U>call <SID>MultiMatch("bW", "n") <CR>nnoremap <silent> ]% :<C-U>call <SID>MultiMatch("W",  "n") <CR>vmap [% <Esc>[%m'gv``vmap ]% <Esc>]%m'gv``" vnoremap <silent> [% :<C-U>call <SID>MultiMatch("bW", "v") <CR>m'gv``" vnoremap <silent> ]% :<C-U>call <SID>MultiMatch("W",  "v") <CR>m'gv``onoremap <silent> [% v:<C-U>call <SID>MultiMatch("bW", "o") <CR>onoremap <silent> ]% v:<C-U>call <SID>MultiMatch("W",  "o") <CR> " text object:vmap a% <Esc>[%v]% " Auto-complete mappings:  (not yet "ready for prime time")" TODO Read :help write-plugin for the "right" way to let the user" specify a key binding."   let g:match_auto = '<C-]>'"   let g:match_autoCR = '<C-CR>'" if exists("g:match_auto")"   execute "inoremap " . g:match_auto . ' x<Esc>"=<SID>Autocomplete()<CR>Pls'" endif" if exists("g:match_autoCR")"   execute "inoremap " . g:match_autoCR . ' <CR><C-R>=<SID>Autocomplete()<CR>'" endif" if exists("g:match_gthhoh")"   execute "inoremap " . g:match_gthhoh . ' <C-O>:call <SID>Gthhoh()<CR>'" endif " gthhoh = "Get the heck out of here!" let s:notslash = '\\\@<!\%(\\\\\)*' function! s:Match_wrapper(word, forward, mode) range  " In s:CleanUp(), :execute "set" restore_options .  let restore_options = (&ic ? " " : " no") . "ignorecase"  if exists("b:match_ignorecase")    let &ignorecase = b:match_ignorecase  endif  let restore_options = " ve=" . &ve . restore_options  set ve=  " If this function was called from Visual mode, make sure that the cursor  " is at the correct end of the Visual range:  if a:mode == "v"    execute "normal! gv\<Esc>"  endif  " In s:CleanUp(), we may need to check whether the cursor moved forward.  let startline = line(".")  let startcol = col(".")  " Use default behavior if called with a count or if no patterns are defined.  if v:count    exe "normal! " . v:count . "%"    return s:CleanUp(restore_options, a:mode, startline, startcol)  elseif !exists("b:match_words") || b:match_words == ""    silent! normal! %    return s:CleanUp(restore_options, a:mode, startline, startcol)  end   " First step:  if not already done, set the script variables  "   s:do_BR	flag for whether there are backrefs  "   s:pat	parsed version of b:match_words  "   s:all	regexp based on s:pat and the default groups  "  " Allow b:match_words = "GetVimMatchWords()" .  if b:match_words =~ ":"    let match_words = b:match_words  else    execute "let match_words =" b:match_words  endif" Thanks to Preben "Peppe" Guldberg and Bram Moolenaar for this suggestion!  if (match_words != s:last_words) || (&mps != s:last_mps) ||    \ exists("b:match_debug")    let s:last_words = match_words    let s:last_mps = &mps    if match_words !~ s:notslash . '\\\d'      let s:do_BR = 0      let s:pat = match_words    else      let s:do_BR = 1      let s:pat = s:ParseWords(match_words)    endif    " The next several lines were here before    " BF started messing with this script.    " quote the special chars in 'matchpairs', replace [,:] with \| and then    " append the builtin pairs (/*, */, #if, #ifdef, #else, #elif, #endif)    " let default = substitute(escape(&mps, '[$^.*~\\/?]'), '[,:]\+',    "  \ '\\|', 'g').'\|\/\*\|\*\/\|#if\>\|#ifdef\>\|#else\>\|#elif\>\|#endif\>'    let default = escape(&mps, '[$^.*~\\/?]') . (strlen(&mps) ? "," : "") .      \ '\/\*:\*\/,#if\%(def\)\=:#else\>:#elif\>:#endif\>'    " s:all = pattern with all the keywords    let s:all = s:pat . (strlen(s:pat) ? "," : "") . default    let s:all = substitute(s:all, s:notslash . '\zs[,:]\+', '\\|', 'g')    let s:all = '\%(' . s:all . '\)'    " let s:all = '\%(' . substitute(s:all, '\\\ze[,:]', '', 'g') . '\)'    if exists("b:match_debug")      let b:match_pat = s:pat    endif  endif   " Second step:  set the following local variables:  "     matchline = line on which the cursor started  "     curcol    = number of characters before match  "     prefix    = regexp for start of line to start of match  "     suffix    = regexp for end of match to end of line  " Require match to end on or after the cursor and prefer it to  " start on or before the cursor.  let matchline = getline(startline)  if a:word != ''    " word given    if a:word !~ s:all      echohl WarningMsg|echo 'Missing rule for word:"'.a:word.'"'|echohl NONE      return s:CleanUp(restore_options, a:mode, startline, startcol)    endif    let matchline = a:word    let curcol = 0    let prefix = '^\%('    let suffix = '\)$'  " Now the case when "word" is not given  else	" Find the match that ends on or after the cursor and set curcol.    let regexp = s:Wholematch(matchline, s:all, startcol-1)    let curcol = match(matchline, regexp)    let endcol = matchend(matchline, regexp)    let suf = strlen(matchline) - endcol    let prefix = (curcol ? '^.*\%'  . (curcol + 1) . 'c\%(' : '^\%(')    let suffix = (suf ? '\)\%' . (endcol + 1) . 'c.*$'  : '\)$')    " If the match comes from the defaults, bail out.    if matchline !~ prefix .      \ substitute(s:pat, s:notslash.'\zs[,:]\+', '\\|', 'g') . suffix      silent! norm! %      return s:CleanUp(restore_options, a:mode, startline, startcol)    endif  endif  if exists("b:match_debug")    let b:match_match = matchstr(matchline, regexp)    let b:match_col = curcol+1  endif   " Third step:  Find the group and single word that match, and the original  " (backref) versions of these.  Then, resolve the backrefs.  " Set the following local variable:  " group = colon-separated list of patterns, one of which matches  "       = ini:mid:fin or ini:fin  "  " Reconstruct the version with unresolved backrefs.  let patBR = substitute(match_words.',',    \ s:notslash.'\zs[,:]*,[,:]*', ',', 'g')  let patBR = substitute(patBR, s:notslash.'\zs:\{2,}', ':', 'g')  " Now, set group and groupBR to the matching group: 'if:endif' or  " 'while:endwhile' or whatever.  A bit of a kluge:  s:Choose() returns  " group . "," . groupBR, and we pick it apart.  let group = s:Choose(s:pat, matchline, ",", ":", prefix, suffix, patBR)  let i = matchend(group, s:notslash . ",")  let groupBR = strpart(group, i)  let group = strpart(group, 0, i-1)  " Now, matchline =~ prefix . substitute(group,':','\|','g') . suffix  if s:do_BR " Do the hard part:  resolve those backrefs!    let group = s:InsertRefs(groupBR, prefix, group, suffix, matchline)  endif  if exists("b:match_debug")    let b:match_wholeBR = groupBR    let i = matchend(groupBR, s:notslash . ":")    let b:match_iniBR = strpart(groupBR, 0, i-1)  endif   " Fourth step:  Set the arguments for searchpair().  let i = matchend(group, s:notslash . ":")  let j = matchend(group, '.*' . s:notslash . ":")  let ini = strpart(group, 0, i-1)  let mid = substitute(strpart(group, i,j-i-1), s:notslash.'\zs:', '\\|', 'g')  let fin = strpart(group, j)  "Un-escape the remaining , and : characters.  let ini = substitute(ini, s:notslash . '\zs\\\(:\|,\)', '\1', 'g')  let mid = substitute(mid, s:notslash . '\zs\\\(:\|,\)', '\1', 'g')  let fin = substitute(fin, s:notslash . '\zs\\\(:\|,\)', '\1', 'g')  " searchpair() requires that these patterns avoid \(\) groups.  let ini = substitute(ini, s:notslash . '\zs\\(', '\\%(', 'g')  let mid = substitute(mid, s:notslash . '\zs\\(', '\\%(', 'g')  let fin = substitute(fin, s:notslash . '\zs\\(', '\\%(', 'g')  " Set mid.  This is optimized for readability, not micro-efficiency!  if a:forward && matchline =~ prefix . fin . suffix    \ || !a:forward && matchline =~ prefix . ini . suffix    let mid = ""  endif  " Set flag.  This is optimized for readability, not micro-efficiency!  if a:forward && matchline =~ prefix . fin . suffix    \ || !a:forward && matchline !~ prefix . ini . suffix    let flag = "bW"  else    let flag = "W"  endif  " Set skip.  if exists("b:match_skip")    let skip = b:match_skip  elseif exists("b:match_comment") " backwards compatibility and testing!    let skip = "r:" . b:match_comment  else    let skip = 's:comment\|string'  endif  let skip = s:ParseSkip(skip)  if exists("b:match_debug")    let b:match_ini = ini    let b:match_tail = (strlen(mid) ? mid.'\|' : '') . fin  endif   " Fifth step:  actually start moving the cursor and call searchpair().  " Later, :execute restore_cursor to get to the original screen.  let restore_cursor = virtcol(".") . "|"  normal! g0  let restore_cursor = line(".") . "G" .  virtcol(".") . "|zs" . restore_cursor  normal! H  let restore_cursor = "normal!" . line(".") . "Gzt" . restore_cursor  execute restore_cursor  call cursor(0, curcol + 1)  " normal! 0  " if curcol  "   execute "normal!" . curcol . "l"  " endif  if skip =~ 'synID' && !(has("syntax") && exists("g:syntax_on"))    let skip = "0"  else    execute "if " . skip . "| let skip = '0' | endif"  endif  let sp_return = searchpair(ini, mid, fin, flag, skip)  let final_position = "call cursor(" . line(".") . "," . col(".") . ")"  " Restore cursor position and original screen.  execute restore_cursor  normal! m'  if sp_return > 0    execute final_position  endif  return s:CleanUp(restore_options, a:mode, startline, startcol, mid.'\|'.fin)endfun " Restore options and do some special handling for Operator-pending mode." The optional argument is the tail of the matching group.fun! s:CleanUp(options, mode, startline, startcol, ...)  execute "set" a:options  " Open folds, if appropriate.  if a:mode != "o"    if &foldopen =~ "percent"      normal! zv    endif    " In Operator-pending mode, we want to include the whole match    " (for example, d%).    " This is only a problem if we end up moving in the forward direction.  elseif (a:startline < line(".")) ||	\ (a:startline == line(".") && a:startcol < col("."))    if a:0      " Check whether the match is a single character.  If not, move to the      " end of the match.      let matchline = getline(".")      let currcol = col(".")      let regexp = s:Wholematch(matchline, a:1, currcol-1)      let endcol = matchend(matchline, regexp)      if endcol > currcol  " This is NOT off by one!	execute "normal!" . (endcol - currcol) . "l"      endif    endif " a:0  endif " a:mode != "o" && etc.  return 0endfun " Example (simplified HTML patterns):  if"   a:groupBR	= '<\(\k\+\)>:</\1>'"   a:prefix	= '^.\{3}\('"   a:group	= '<\(\k\+\)>:</\(\k\+\)>'"   a:suffix	= '\).\{2}$'"   a:matchline	=  "123<tag>12" or "123</tag>12"" then extract "tag" from a:matchline and return "<tag>:</tag>" .fun! s:InsertRefs(groupBR, prefix, group, suffix, matchline)  if a:matchline !~ a:prefix .    \ substitute(a:group, s:notslash . '\zs:', '\\|', 'g') . a:suffix    return a:group  endif  let i = matchend(a:groupBR, s:notslash . ':')  let ini = strpart(a:groupBR, 0, i-1)  let tailBR = strpart(a:groupBR, i)  let word = s:Choose(a:group, a:matchline, ":", "", a:prefix, a:suffix,    \ a:groupBR)  let i = matchend(word, s:notslash . ":")  let wordBR = strpart(word, i)  let word = strpart(word, 0, i-1)  " Now, a:matchline =~ a:prefix . word . a:suffix  if wordBR != ini    let table = s:Resolve(ini, wordBR, "table")  else    " let table = "----------"    let table = ""    let d = 0    while d < 10      if tailBR =~ s:notslash . '\\' . d	" let table[d] = d	let table = table . d      else	let table = table . "-"      endif      let d = d + 1    endwhile  endif  let d = 9  while d    if table[d] != "-"      let backref = substitute(a:matchline, a:prefix.word.a:suffix,	\ '\'.table[d], "")	" Are there any other characters that should be escaped?      let backref = escape(backref, '*,:')      execute s:Ref(ini, d, "start", "len")      let ini = strpart(ini, 0, start) . backref . strpart(ini, start+len)      let tailBR = substitute(tailBR, s:notslash . '\zs\\' . d,	\ escape(backref, '\\'), 'g')    endif    let d = d-1  endwhile  if exists("b:match_debug")    if s:do_BR      let b:match_table = table      let b:match_word = word    else      let b:match_table = ""      let b:match_word = ""    endif  endif  return ini . ":" . tailBRendfun " Input a comma-separated list of groups with backrefs, such as"   a:groups = '\(foo\):end\1,\(bar\):end\1'" and return a comma-separated list of groups with backrefs replaced:"   return '\(foo\):end\(foo\),\(bar\):end\(bar\)'fun! s:ParseWords(groups)  let groups = substitute(a:groups.",", s:notslash.'\zs[,:]*,[,:]*', ',', 'g')  let groups = substitute(groups, s:notslash . '\zs:\{2,}', ':', 'g')  let parsed = ""  while groups =~ '[^,:]'    let i = matchend(groups, s:notslash . ':')    let j = matchend(groups, s:notslash . ',')    let ini = strpart(groups, 0, i-1)    let tail = strpart(groups, i, j-i-1) . ":"    let groups = strpart(groups, j)    let parsed = parsed . ini    let i = matchend(tail, s:notslash . ':')    while i != -1      " In 'if:else:endif', ini='if' and word='else' and then word='endif'.      let word = strpart(tail, 0, i-1)      let tail = strpart(tail, i)      let i = matchend(tail, s:notslash . ':')      let parsed = parsed . ":" . s:Resolve(ini, word, "word")    endwhile " Now, tail has been used up.    let parsed = parsed . ","  endwhile " groups =~ '[^,:]'  return parsedendfun " TODO I think this can be simplified and/or made more efficient." TODO What should I do if a:start is out of range?" Return a regexp that matches all of a:string, such that" matchstr(a:string, regexp) represents the match for a:pat that starts" as close to a:start as possible, before being preferred to after, and" ends after a:start ." Usage:" let regexp = s:Wholematch(getline("."), 'foo\|bar', col(".")-1)" let i      = match(getline("."), regexp)" let j      = matchend(getline("."), regexp)" let match  = matchstr(getline("."), regexp)fun! s:Wholematch(string, pat, start)  let group = '\%(' . a:pat . '\)'  let prefix = (a:start ? '\(^.*\%<' . (a:start + 2) . 'c\)\zs' : '^')  let len = strlen(a:string)  let suffix = (a:start+1 < len ? '\(\%>'.(a:start+1).'c.*$\)\@=' : '$')  if a:string !~ prefix . group . suffix    let prefix = ''  endif  return prefix . group . suffixendfun " No extra arguments:  s:Ref(string, d) will" find the d'th occurrence of '\(' and return it, along with everything up" to and including the matching '\)'." One argument:  s:Ref(string, d, "start") returns the index of the start" of the d'th '\(' and any other argument returns the length of the group." Two arguments:  s:Ref(string, d, "foo", "bar") returns a string to be" executed, having the effect of"   :let foo = s:Ref(string, d, "start")"   :let bar = s:Ref(string, d, "len")fun! s:Ref(string, d, ...)  let len = strlen(a:string)  if a:d == 0    let start = 0  else    let cnt = a:d    let match = a:string    while cnt      let cnt = cnt - 1      let index = matchend(match, s:notslash . '\\(')      if index == -1	return ""      endif      let match = strpart(match, index)    endwhile    let start = len - strlen(match)    if a:0 == 1 && a:1 == "start"      return start - 2    endif    let cnt = 1    while cnt      let index = matchend(match, s:notslash . '\\(\|\\)') - 1      if index == -2	return ""      endif      " Increment if an open, decrement if a ')':      let cnt = cnt + (match[index]=="(" ? 1 : -1)  " ')'      " let cnt = stridx('0(', match[index]) + cnt      let match = strpart(match, index+1)    endwhile    let start = start - 2    let len = len - start - strlen(match)  endif  if a:0 == 1    return len  elseif a:0 == 2    return "let " . a:1 . "=" . start . "| let " . a:2 . "=" . len  else    return strpart(a:string, start, len)  endifendfun " Count the number of disjoint copies of pattern in string." If the pattern is a literal string and contains no '0' or '1' characters" then s:Count(string, pattern, '0', '1') should be faster than" s:Count(string, pattern).fun! s:Count(string, pattern, ...)  let pat = escape(a:pattern, '\\')  if a:0 > 1    let foo = substitute(a:string, '[^'.a:pattern.']', "a:1", "g")    let foo = substitute(a:string, pat, a:2, "g")    let foo = substitute(foo, '[^' . a:2 . ']', "", "g")    return strlen(foo)  endif  let result = 0  let foo = a:string  let index = matchend(foo, pat)  while index != -1    let result = result + 1    let foo = strpart(foo, index)    let index = matchend(foo, pat)  endwhile  return result

Lines 1–500 of 812.