source: issm/oecreview/Archive/14312-15392/ISSM-14736-14737.diff

Last change on this file was 15393, checked in by Mathieu Morlighem, 12 years ago

NEW: adding Archive/14312-15392 for oecreview

File size: 31.3 KB
  • ../trunk-jpl/externalpackages/vim/addons/vim/plugin/matchit.vim

     
     1"  matchit.vim: (global plugin) Extended "%" matching
     2"  Last Change: Fri Jan 25 10:00 AM 2008 EST
     3"  Maintainer:  Benji Fisher PhD   <benji@member.AMS.org>
     4"  Version:     1.13.2, for Vim 6.3+
     5"  URL:         http://www.vim.org/script.php?script_id=39
     6
     7" Documentation:
     8"  The documentation is in a separate file, matchit.txt .
     9
     10" Credits:
     11"  Vim editor by Bram Moolenaar (Thanks, Bram!)
     12"  Original script and design by Raul Segura Acevedo
     13"  Support for comments by Douglas Potts
     14"  Support for back references and other improvements by Benji Fisher
     15"  Support for many languages by Johannes Zellner
     16"  Suggestions for improvement, bug reports, and support for additional
     17"  languages by Jordi-Albert Batalla, Neil Bird, Servatius Brandt, Mark
     18"  Collett, Stephen Wall, Dany St-Amant, Yuheng Xie, and Johannes Zellner.
     19
     20" Debugging:
     21"  If you'd like to try the built-in debugging commands...
     22"   :MatchDebug      to activate debugging for the current buffer
     23"  This saves the values of several key script variables as buffer-local
     24"  variables.  See the MatchDebug() function, below, for details.
     25
     26" TODO:  I should think about multi-line patterns for b:match_words.
     27"   This would require an option:  how many lines to scan (default 1).
     28"   This would be useful for Python, maybe also for *ML.
     29" TODO:  Maybe I should add a menu so that people will actually use some of
     30"   the features that I have implemented.
     31" TODO:  Eliminate the MultiMatch function.  Add yet another argument to
     32"   Match_wrapper() instead.
     33" TODO:  Allow :let b:match_words = '\(\(foo\)\(bar\)\):\3\2:end\1'
     34" TODO:  Make backrefs safer by using '\V' (very no-magic).
     35" TODO:  Add a level of indirection, so that custom % scripts can use my
     36"   work but extend it.
     37
     38" allow user to prevent loading
     39" and prevent duplicate loading
     40if exists("loaded_matchit") || &cp
     41  finish
     42endif
     43let loaded_matchit = 1
     44let s:last_mps = ""
     45let s:last_words = ":"
     46
     47let s:save_cpo = &cpo
     48set cpo&vim
     49
     50nnoremap <silent> %  :<C-U>call <SID>Match_wrapper('',1,'n') <CR>
     51nnoremap <silent> g% :<C-U>call <SID>Match_wrapper('',0,'n') <CR>
     52vnoremap <silent> %  :<C-U>call <SID>Match_wrapper('',1,'v') <CR>m'gv``
     53vnoremap <silent> g% :<C-U>call <SID>Match_wrapper('',0,'v') <CR>m'gv``
     54onoremap <silent> %  v:<C-U>call <SID>Match_wrapper('',1,'o') <CR>
     55onoremap <silent> g% v:<C-U>call <SID>Match_wrapper('',0,'o') <CR>
     56
     57" Analogues of [{ and ]} using matching patterns:
     58nnoremap <silent> [% :<C-U>call <SID>MultiMatch("bW", "n") <CR>
     59nnoremap <silent> ]% :<C-U>call <SID>MultiMatch("W",  "n") <CR>
     60vmap [% <Esc>[%m'gv``
     61vmap ]% <Esc>]%m'gv``
     62" vnoremap <silent> [% :<C-U>call <SID>MultiMatch("bW", "v") <CR>m'gv``
     63" vnoremap <silent> ]% :<C-U>call <SID>MultiMatch("W",  "v") <CR>m'gv``
     64onoremap <silent> [% v:<C-U>call <SID>MultiMatch("bW", "o") <CR>
     65onoremap <silent> ]% v:<C-U>call <SID>MultiMatch("W",  "o") <CR>
     66
     67" text object:
     68vmap a% <Esc>[%v]%
     69
     70" Auto-complete mappings:  (not yet "ready for prime time")
     71" TODO Read :help write-plugin for the "right" way to let the user
     72" specify a key binding.
     73"   let g:match_auto = '<C-]>'
     74"   let g:match_autoCR = '<C-CR>'
     75" if exists("g:match_auto")
     76"   execute "inoremap " . g:match_auto . ' x<Esc>"=<SID>Autocomplete()<CR>Pls'
     77" endif
     78" if exists("g:match_autoCR")
     79"   execute "inoremap " . g:match_autoCR . ' <CR><C-R>=<SID>Autocomplete()<CR>'
     80" endif
     81" if exists("g:match_gthhoh")
     82"   execute "inoremap " . g:match_gthhoh . ' <C-O>:call <SID>Gthhoh()<CR>'
     83" endif " gthhoh = "Get the heck out of here!"
     84
     85let s:notslash = '\\\@<!\%(\\\\\)*'
     86
     87function! s:Match_wrapper(word, forward, mode) range
     88  " In s:CleanUp(), :execute "set" restore_options .
     89  let restore_options = (&ic ? " " : " no") . "ignorecase"
     90  if exists("b:match_ignorecase")
     91    let &ignorecase = b:match_ignorecase
     92  endif
     93  let restore_options = " ve=" . &ve . restore_options
     94  set ve=
     95  " If this function was called from Visual mode, make sure that the cursor
     96  " is at the correct end of the Visual range:
     97  if a:mode == "v"
     98    execute "normal! gv\<Esc>"
     99  endif
     100  " In s:CleanUp(), we may need to check whether the cursor moved forward.
     101  let startline = line(".")
     102  let startcol = col(".")
     103  " Use default behavior if called with a count.
     104  if v:count
     105    exe "normal! " . v:count . "%"
     106    return s:CleanUp(restore_options, a:mode, startline, startcol)
     107  end
     108
     109  " First step:  if not already done, set the script variables
     110  "   s:do_BR   flag for whether there are backrefs
     111  "   s:pat     parsed version of b:match_words
     112  "   s:all     regexp based on s:pat and the default groups
     113  "
     114  if !exists("b:match_words") || b:match_words == ""
     115    let match_words = ""
     116    " Allow b:match_words = "GetVimMatchWords()" .
     117  elseif b:match_words =~ ":"
     118    let match_words = b:match_words
     119  else
     120    execute "let match_words =" b:match_words
     121  endif
     122" Thanks to Preben "Peppe" Guldberg and Bram Moolenaar for this suggestion!
     123  if (match_words != s:last_words) || (&mps != s:last_mps) ||
     124    \ exists("b:match_debug")
     125    let s:last_words = match_words
     126    let s:last_mps = &mps
     127    " The next several lines were here before
     128    " BF started messing with this script.
     129    " quote the special chars in 'matchpairs', replace [,:] with \| and then
     130    " append the builtin pairs (/*, */, #if, #ifdef, #else, #elif, #endif)
     131    " let default = substitute(escape(&mps, '[$^.*~\\/?]'), '[,:]\+',
     132    "  \ '\\|', 'g').'\|\/\*\|\*\/\|#if\>\|#ifdef\>\|#else\>\|#elif\>\|#endif\>'
     133    let default = escape(&mps, '[$^.*~\\/?]') . (strlen(&mps) ? "," : "") .
     134      \ '\/\*:\*\/,#if\%(def\)\=:#else\>:#elif\>:#endif\>'
     135    " s:all = pattern with all the keywords
     136    let match_words = match_words . (strlen(match_words) ? "," : "") . default
     137    if match_words !~ s:notslash . '\\\d'
     138      let s:do_BR = 0
     139      let s:pat = match_words
     140    else
     141      let s:do_BR = 1
     142      let s:pat = s:ParseWords(match_words)
     143    endif
     144    let s:all = substitute(s:pat, s:notslash . '\zs[,:]\+', '\\|', 'g')
     145    let s:all = '\%(' . s:all . '\)'
     146    " let s:all = '\%(' . substitute(s:all, '\\\ze[,:]', '', 'g') . '\)'
     147    if exists("b:match_debug")
     148      let b:match_pat = s:pat
     149    endif
     150  endif
     151
     152  " Second step:  set the following local variables:
     153  "     matchline = line on which the cursor started
     154  "     curcol    = number of characters before match
     155  "     prefix    = regexp for start of line to start of match
     156  "     suffix    = regexp for end of match to end of line
     157  " Require match to end on or after the cursor and prefer it to
     158  " start on or before the cursor.
     159  let matchline = getline(startline)
     160  if a:word != ''
     161    " word given
     162    if a:word !~ s:all
     163      echohl WarningMsg|echo 'Missing rule for word:"'.a:word.'"'|echohl NONE
     164      return s:CleanUp(restore_options, a:mode, startline, startcol)
     165    endif
     166    let matchline = a:word
     167    let curcol = 0
     168    let prefix = '^\%('
     169    let suffix = '\)$'
     170  " Now the case when "word" is not given
     171  else  " Find the match that ends on or after the cursor and set curcol.
     172    let regexp = s:Wholematch(matchline, s:all, startcol-1)
     173    let curcol = match(matchline, regexp)
     174    " If there is no match, give up.
     175    if curcol == -1
     176      return s:CleanUp(restore_options, a:mode, startline, startcol)
     177    endif
     178    let endcol = matchend(matchline, regexp)
     179    let suf = strlen(matchline) - endcol
     180    let prefix = (curcol ? '^.*\%'  . (curcol + 1) . 'c\%(' : '^\%(')
     181    let suffix = (suf ? '\)\%' . (endcol + 1) . 'c.*$'  : '\)$')
     182  endif
     183  if exists("b:match_debug")
     184    let b:match_match = matchstr(matchline, regexp)
     185    let b:match_col = curcol+1
     186  endif
     187
     188  " Third step:  Find the group and single word that match, and the original
     189  " (backref) versions of these.  Then, resolve the backrefs.
     190  " Set the following local variable:
     191  " group = colon-separated list of patterns, one of which matches
     192  "       = ini:mid:fin or ini:fin
     193  "
     194  " Reconstruct the version with unresolved backrefs.
     195  let patBR = substitute(match_words.',',
     196    \ s:notslash.'\zs[,:]*,[,:]*', ',', 'g')
     197  let patBR = substitute(patBR, s:notslash.'\zs:\{2,}', ':', 'g')
     198  " Now, set group and groupBR to the matching group: 'if:endif' or
     199  " 'while:endwhile' or whatever.  A bit of a kluge:  s:Choose() returns
     200  " group . "," . groupBR, and we pick it apart.
     201  let group = s:Choose(s:pat, matchline, ",", ":", prefix, suffix, patBR)
     202  let i = matchend(group, s:notslash . ",")
     203  let groupBR = strpart(group, i)
     204  let group = strpart(group, 0, i-1)
     205  " Now, matchline =~ prefix . substitute(group,':','\|','g') . suffix
     206  if s:do_BR " Do the hard part:  resolve those backrefs!
     207    let group = s:InsertRefs(groupBR, prefix, group, suffix, matchline)
     208  endif
     209  if exists("b:match_debug")
     210    let b:match_wholeBR = groupBR
     211    let i = matchend(groupBR, s:notslash . ":")
     212    let b:match_iniBR = strpart(groupBR, 0, i-1)
     213  endif
     214
     215  " Fourth step:  Set the arguments for searchpair().
     216  let i = matchend(group, s:notslash . ":")
     217  let j = matchend(group, '.*' . s:notslash . ":")
     218  let ini = strpart(group, 0, i-1)
     219  let mid = substitute(strpart(group, i,j-i-1), s:notslash.'\zs:', '\\|', 'g')
     220  let fin = strpart(group, j)
     221  "Un-escape the remaining , and : characters.
     222  let ini = substitute(ini, s:notslash . '\zs\\\(:\|,\)', '\1', 'g')
     223  let mid = substitute(mid, s:notslash . '\zs\\\(:\|,\)', '\1', 'g')
     224  let fin = substitute(fin, s:notslash . '\zs\\\(:\|,\)', '\1', 'g')
     225  " searchpair() requires that these patterns avoid \(\) groups.
     226  let ini = substitute(ini, s:notslash . '\zs\\(', '\\%(', 'g')
     227  let mid = substitute(mid, s:notslash . '\zs\\(', '\\%(', 'g')
     228  let fin = substitute(fin, s:notslash . '\zs\\(', '\\%(', 'g')
     229  " Set mid.  This is optimized for readability, not micro-efficiency!
     230  if a:forward && matchline =~ prefix . fin . suffix
     231    \ || !a:forward && matchline =~ prefix . ini . suffix
     232    let mid = ""
     233  endif
     234  " Set flag.  This is optimized for readability, not micro-efficiency!
     235  if a:forward && matchline =~ prefix . fin . suffix
     236    \ || !a:forward && matchline !~ prefix . ini . suffix
     237    let flag = "bW"
     238  else
     239    let flag = "W"
     240  endif
     241  " Set skip.
     242  if exists("b:match_skip")
     243    let skip = b:match_skip
     244  elseif exists("b:match_comment") " backwards compatibility and testing!
     245    let skip = "r:" . b:match_comment
     246  else
     247    let skip = 's:comment\|string'
     248  endif
     249  let skip = s:ParseSkip(skip)
     250  if exists("b:match_debug")
     251    let b:match_ini = ini
     252    let b:match_tail = (strlen(mid) ? mid.'\|' : '') . fin
     253  endif
     254
     255  " Fifth step:  actually start moving the cursor and call searchpair().
     256  " Later, :execute restore_cursor to get to the original screen.
     257  let restore_cursor = virtcol(".") . "|"
     258  normal! g0
     259  let restore_cursor = line(".") . "G" .  virtcol(".") . "|zs" . restore_cursor
     260  normal! H
     261  let restore_cursor = "normal!" . line(".") . "Gzt" . restore_cursor
     262  execute restore_cursor
     263  call cursor(0, curcol + 1)
     264  " normal! 0
     265  " if curcol
     266  "   execute "normal!" . curcol . "l"
     267  " endif
     268  if skip =~ 'synID' && !(has("syntax") && exists("g:syntax_on"))
     269    let skip = "0"
     270  else
     271    execute "if " . skip . "| let skip = '0' | endif"
     272  endif
     273  let sp_return = searchpair(ini, mid, fin, flag, skip)
     274  let final_position = "call cursor(" . line(".") . "," . col(".") . ")"
     275  " Restore cursor position and original screen.
     276  execute restore_cursor
     277  normal! m'
     278  if sp_return > 0
     279    execute final_position
     280  endif
     281  return s:CleanUp(restore_options, a:mode, startline, startcol, mid.'\|'.fin)
     282endfun
     283
     284" Restore options and do some special handling for Operator-pending mode.
     285" The optional argument is the tail of the matching group.
     286fun! s:CleanUp(options, mode, startline, startcol, ...)
     287  execute "set" a:options
     288  " Open folds, if appropriate.
     289  if a:mode != "o"
     290    if &foldopen =~ "percent"
     291      normal! zv
     292    endif
     293    " In Operator-pending mode, we want to include the whole match
     294    " (for example, d%).
     295    " This is only a problem if we end up moving in the forward direction.
     296  elseif (a:startline < line(".")) ||
     297        \ (a:startline == line(".") && a:startcol < col("."))
     298    if a:0
     299      " Check whether the match is a single character.  If not, move to the
     300      " end of the match.
     301      let matchline = getline(".")
     302      let currcol = col(".")
     303      let regexp = s:Wholematch(matchline, a:1, currcol-1)
     304      let endcol = matchend(matchline, regexp)
     305      if endcol > currcol  " This is NOT off by one!
     306        execute "normal!" . (endcol - currcol) . "l"
     307      endif
     308    endif " a:0
     309  endif " a:mode != "o" && etc.
     310  return 0
     311endfun
     312
     313" Example (simplified HTML patterns):  if
     314"   a:groupBR   = '<\(\k\+\)>:</\1>'
     315"   a:prefix    = '^.\{3}\('
     316"   a:group     = '<\(\k\+\)>:</\(\k\+\)>'
     317"   a:suffix    = '\).\{2}$'
     318"   a:matchline =  "123<tag>12" or "123</tag>12"
     319" then extract "tag" from a:matchline and return "<tag>:</tag>" .
     320fun! s:InsertRefs(groupBR, prefix, group, suffix, matchline)
     321  if a:matchline !~ a:prefix .
     322    \ substitute(a:group, s:notslash . '\zs:', '\\|', 'g') . a:suffix
     323    return a:group
     324  endif
     325  let i = matchend(a:groupBR, s:notslash . ':')
     326  let ini = strpart(a:groupBR, 0, i-1)
     327  let tailBR = strpart(a:groupBR, i)
     328  let word = s:Choose(a:group, a:matchline, ":", "", a:prefix, a:suffix,
     329    \ a:groupBR)
     330  let i = matchend(word, s:notslash . ":")
     331  let wordBR = strpart(word, i)
     332  let word = strpart(word, 0, i-1)
     333  " Now, a:matchline =~ a:prefix . word . a:suffix
     334  if wordBR != ini
     335    let table = s:Resolve(ini, wordBR, "table")
     336  else
     337    " let table = "----------"
     338    let table = ""
     339    let d = 0
     340    while d < 10
     341      if tailBR =~ s:notslash . '\\' . d
     342        " let table[d] = d
     343        let table = table . d
     344      else
     345        let table = table . "-"
     346      endif
     347      let d = d + 1
     348    endwhile
     349  endif
     350  let d = 9
     351  while d
     352    if table[d] != "-"
     353      let backref = substitute(a:matchline, a:prefix.word.a:suffix,
     354        \ '\'.table[d], "")
     355        " Are there any other characters that should be escaped?
     356      let backref = escape(backref, '*,:')
     357      execute s:Ref(ini, d, "start", "len")
     358      let ini = strpart(ini, 0, start) . backref . strpart(ini, start+len)
     359      let tailBR = substitute(tailBR, s:notslash . '\zs\\' . d,
     360        \ escape(backref, '\\'), 'g')
     361    endif
     362    let d = d-1
     363  endwhile
     364  if exists("b:match_debug")
     365    if s:do_BR
     366      let b:match_table = table
     367      let b:match_word = word
     368    else
     369      let b:match_table = ""
     370      let b:match_word = ""
     371    endif
     372  endif
     373  return ini . ":" . tailBR
     374endfun
     375
     376" Input a comma-separated list of groups with backrefs, such as
     377"   a:groups = '\(foo\):end\1,\(bar\):end\1'
     378" and return a comma-separated list of groups with backrefs replaced:
     379"   return '\(foo\):end\(foo\),\(bar\):end\(bar\)'
     380fun! s:ParseWords(groups)
     381  let groups = substitute(a:groups.",", s:notslash.'\zs[,:]*,[,:]*', ',', 'g')
     382  let groups = substitute(groups, s:notslash . '\zs:\{2,}', ':', 'g')
     383  let parsed = ""
     384  while groups =~ '[^,:]'
     385    let i = matchend(groups, s:notslash . ':')
     386    let j = matchend(groups, s:notslash . ',')
     387    let ini = strpart(groups, 0, i-1)
     388    let tail = strpart(groups, i, j-i-1) . ":"
     389    let groups = strpart(groups, j)
     390    let parsed = parsed . ini
     391    let i = matchend(tail, s:notslash . ':')
     392    while i != -1
     393      " In 'if:else:endif', ini='if' and word='else' and then word='endif'.
     394      let word = strpart(tail, 0, i-1)
     395      let tail = strpart(tail, i)
     396      let i = matchend(tail, s:notslash . ':')
     397      let parsed = parsed . ":" . s:Resolve(ini, word, "word")
     398    endwhile " Now, tail has been used up.
     399    let parsed = parsed . ","
     400  endwhile " groups =~ '[^,:]'
     401  let parsed = substitute(parsed, ',$', '', '')
     402  return parsed
     403endfun
     404
     405" TODO I think this can be simplified and/or made more efficient.
     406" TODO What should I do if a:start is out of range?
     407" Return a regexp that matches all of a:string, such that
     408" matchstr(a:string, regexp) represents the match for a:pat that starts
     409" as close to a:start as possible, before being preferred to after, and
     410" ends after a:start .
     411" Usage:
     412" let regexp = s:Wholematch(getline("."), 'foo\|bar', col(".")-1)
     413" let i      = match(getline("."), regexp)
     414" let j      = matchend(getline("."), regexp)
     415" let match  = matchstr(getline("."), regexp)
     416fun! s:Wholematch(string, pat, start)
     417  let group = '\%(' . a:pat . '\)'
     418  let prefix = (a:start ? '\(^.*\%<' . (a:start + 2) . 'c\)\zs' : '^')
     419  let len = strlen(a:string)
     420  let suffix = (a:start+1 < len ? '\(\%>'.(a:start+1).'c.*$\)\@=' : '$')
     421  if a:string !~ prefix . group . suffix
     422    let prefix = ''
     423  endif
     424  return prefix . group . suffix
     425endfun
     426
     427" No extra arguments:  s:Ref(string, d) will
     428" find the d'th occurrence of '\(' and return it, along with everything up
     429" to and including the matching '\)'.
     430" One argument:  s:Ref(string, d, "start") returns the index of the start
     431" of the d'th '\(' and any other argument returns the length of the group.
     432" Two arguments:  s:Ref(string, d, "foo", "bar") returns a string to be
     433" executed, having the effect of
     434"   :let foo = s:Ref(string, d, "start")
     435"   :let bar = s:Ref(string, d, "len")
     436fun! s:Ref(string, d, ...)
     437  let len = strlen(a:string)
     438  if a:d == 0
     439    let start = 0
     440  else
     441    let cnt = a:d
     442    let match = a:string
     443    while cnt
     444      let cnt = cnt - 1
     445      let index = matchend(match, s:notslash . '\\(')
     446      if index == -1
     447        return ""
     448      endif
     449      let match = strpart(match, index)
     450    endwhile
     451    let start = len - strlen(match)
     452    if a:0 == 1 && a:1 == "start"
     453      return start - 2
     454    endif
     455    let cnt = 1
     456    while cnt
     457      let index = matchend(match, s:notslash . '\\(\|\\)') - 1
     458      if index == -2
     459        return ""
     460      endif
     461      " Increment if an open, decrement if a ')':
     462      let cnt = cnt + (match[index]=="(" ? 1 : -1)  " ')'
     463      " let cnt = stridx('0(', match[index]) + cnt
     464      let match = strpart(match, index+1)
     465    endwhile
     466    let start = start - 2
     467    let len = len - start - strlen(match)
     468  endif
     469  if a:0 == 1
     470    return len
     471  elseif a:0 == 2
     472    return "let " . a:1 . "=" . start . "| let " . a:2 . "=" . len
     473  else
     474    return strpart(a:string, start, len)
     475  endif
     476endfun
     477
     478" Count the number of disjoint copies of pattern in string.
     479" If the pattern is a literal string and contains no '0' or '1' characters
     480" then s:Count(string, pattern, '0', '1') should be faster than
     481" s:Count(string, pattern).
     482fun! s:Count(string, pattern, ...)
     483  let pat = escape(a:pattern, '\\')
     484  if a:0 > 1
     485    let foo = substitute(a:string, '[^'.a:pattern.']', "a:1", "g")
     486    let foo = substitute(a:string, pat, a:2, "g")
     487    let foo = substitute(foo, '[^' . a:2 . ']', "", "g")
     488    return strlen(foo)
     489  endif
     490  let result = 0
     491  let foo = a:string
     492  let index = matchend(foo, pat)
     493  while index != -1
     494    let result = result + 1
     495    let foo = strpart(foo, index)
     496    let index = matchend(foo, pat)
     497  endwhile
     498  return result
     499endfun
     500
     501" s:Resolve('\(a\)\(b\)', '\(c\)\2\1\1\2') should return table.word, where
     502" word = '\(c\)\(b\)\(a\)\3\2' and table = '-32-------'.  That is, the first
     503" '\1' in target is replaced by '\(a\)' in word, table[1] = 3, and this
     504" indicates that all other instances of '\1' in target are to be replaced
     505" by '\3'.  The hard part is dealing with nesting...
     506" Note that ":" is an illegal character for source and target,
     507" unless it is preceded by "\".
     508fun! s:Resolve(source, target, output)
     509  let word = a:target
     510  let i = matchend(word, s:notslash . '\\\d') - 1
     511  let table = "----------"
     512  while i != -2 " There are back references to be replaced.
     513    let d = word[i]
     514    let backref = s:Ref(a:source, d)
     515    " The idea is to replace '\d' with backref.  Before we do this,
     516    " replace any \(\) groups in backref with :1, :2, ... if they
     517    " correspond to the first, second, ... group already inserted
     518    " into backref.  Later, replace :1 with \1 and so on.  The group
     519    " number w+b within backref corresponds to the group number
     520    " s within a:source.
     521    " w = number of '\(' in word before the current one
     522    let w = s:Count(
     523    \ substitute(strpart(word, 0, i-1), '\\\\', '', 'g'), '\(', '1')
     524    let b = 1 " number of the current '\(' in backref
     525    let s = d " number of the current '\(' in a:source
     526    while b <= s:Count(substitute(backref, '\\\\', '', 'g'), '\(', '1')
     527    \ && s < 10
     528      if table[s] == "-"
     529        if w + b < 10
     530          " let table[s] = w + b
     531          let table = strpart(table, 0, s) . (w+b) . strpart(table, s+1)
     532        endif
     533        let b = b + 1
     534        let s = s + 1
     535      else
     536        execute s:Ref(backref, b, "start", "len")
     537        let ref = strpart(backref, start, len)
     538        let backref = strpart(backref, 0, start) . ":". table[s]
     539        \ . strpart(backref, start+len)
     540        let s = s + s:Count(substitute(ref, '\\\\', '', 'g'), '\(', '1')
     541      endif
     542    endwhile
     543    let word = strpart(word, 0, i-1) . backref . strpart(word, i+1)
     544    let i = matchend(word, s:notslash . '\\\d') - 1
     545  endwhile
     546  let word = substitute(word, s:notslash . '\zs:', '\\', 'g')
     547  if a:output == "table"
     548    return table
     549  elseif a:output == "word"
     550    return word
     551  else
     552    return table . word
     553  endif
     554endfun
     555
     556" Assume a:comma = ",".  Then the format for a:patterns and a:1 is
     557"   a:patterns = "<pat1>,<pat2>,..."
     558"   a:1 = "<alt1>,<alt2>,..."
     559" If <patn> is the first pattern that matches a:string then return <patn>
     560" if no optional arguments are given; return <patn>,<altn> if a:1 is given.
     561fun! s:Choose(patterns, string, comma, branch, prefix, suffix, ...)
     562  let tail = (a:patterns =~ a:comma."$" ? a:patterns : a:patterns . a:comma)
     563  let i = matchend(tail, s:notslash . a:comma)
     564  if a:0
     565    let alttail = (a:1 =~ a:comma."$" ? a:1 : a:1 . a:comma)
     566    let j = matchend(alttail, s:notslash . a:comma)
     567  endif
     568  let current = strpart(tail, 0, i-1)
     569  if a:branch == ""
     570    let currpat = current
     571  else
     572    let currpat = substitute(current, s:notslash . a:branch, '\\|', 'g')
     573  endif
     574  while a:string !~ a:prefix . currpat . a:suffix
     575    let tail = strpart(tail, i)
     576    let i = matchend(tail, s:notslash . a:comma)
     577    if i == -1
     578      return -1
     579    endif
     580    let current = strpart(tail, 0, i-1)
     581    if a:branch == ""
     582      let currpat = current
     583    else
     584      let currpat = substitute(current, s:notslash . a:branch, '\\|', 'g')
     585    endif
     586    if a:0
     587      let alttail = strpart(alttail, j)
     588      let j = matchend(alttail, s:notslash . a:comma)
     589    endif
     590  endwhile
     591  if a:0
     592    let current = current . a:comma . strpart(alttail, 0, j-1)
     593  endif
     594  return current
     595endfun
     596
     597" Call this function to turn on debugging information.  Every time the main
     598" script is run, buffer variables will be saved.  These can be used directly
     599" or viewed using the menu items below.
     600if !exists(":MatchDebug")
     601  command! -nargs=0 MatchDebug call s:Match_debug()
     602endif
     603
     604fun! s:Match_debug()
     605  let b:match_debug = 1 " Save debugging information.
     606  " pat = all of b:match_words with backrefs parsed
     607  amenu &Matchit.&pat   :echo b:match_pat<CR>
     608  " match = bit of text that is recognized as a match
     609  amenu &Matchit.&match :echo b:match_match<CR>
     610  " curcol = cursor column of the start of the matching text
     611  amenu &Matchit.&curcol        :echo b:match_col<CR>
     612  " wholeBR = matching group, original version
     613  amenu &Matchit.wh&oleBR       :echo b:match_wholeBR<CR>
     614  " iniBR = 'if' piece, original version
     615  amenu &Matchit.ini&BR :echo b:match_iniBR<CR>
     616  " ini = 'if' piece, with all backrefs resolved from match
     617  amenu &Matchit.&ini   :echo b:match_ini<CR>
     618  " tail = 'else\|endif' piece, with all backrefs resolved from match
     619  amenu &Matchit.&tail  :echo b:match_tail<CR>
     620  " fin = 'endif' piece, with all backrefs resolved from match
     621  amenu &Matchit.&word  :echo b:match_word<CR>
     622  " '\'.d in ini refers to the same thing as '\'.table[d] in word.
     623  amenu &Matchit.t&able :echo '0:' . b:match_table . ':9'<CR>
     624endfun
     625
     626" Jump to the nearest unmatched "(" or "if" or "<tag>" if a:spflag == "bW"
     627" or the nearest unmatched "</tag>" or "endif" or ")" if a:spflag == "W".
     628" Return a "mark" for the original position, so that
     629"   let m = MultiMatch("bW", "n") ... execute m
     630" will return to the original position.  If there is a problem, do not
     631" move the cursor and return "", unless a count is given, in which case
     632" go up or down as many levels as possible and again return "".
     633" TODO This relies on the same patterns as % matching.  It might be a good
     634" idea to give it its own matching patterns.
     635fun! s:MultiMatch(spflag, mode)
     636  if !exists("b:match_words") || b:match_words == ""
     637    return ""
     638  end
     639  let restore_options = (&ic ? "" : "no") . "ignorecase"
     640  if exists("b:match_ignorecase")
     641    let &ignorecase = b:match_ignorecase
     642  endif
     643  let startline = line(".")
     644  let startcol = col(".")
     645
     646  " First step:  if not already done, set the script variables
     647  "   s:do_BR   flag for whether there are backrefs
     648  "   s:pat     parsed version of b:match_words
     649  "   s:all     regexp based on s:pat and the default groups
     650  " This part is copied and slightly modified from s:Match_wrapper().
     651  let default = escape(&mps, '[$^.*~\\/?]') . (strlen(&mps) ? "," : "") .
     652    \ '\/\*:\*\/,#if\%(def\)\=:#else\>:#elif\>:#endif\>'
     653  " Allow b:match_words = "GetVimMatchWords()" .
     654  if b:match_words =~ ":"
     655    let match_words = b:match_words
     656  else
     657    execute "let match_words =" b:match_words
     658  endif
     659  if (match_words != s:last_words) || (&mps != s:last_mps) ||
     660    \ exists("b:match_debug")
     661    let s:last_words = match_words
     662    let s:last_mps = &mps
     663    if match_words !~ s:notslash . '\\\d'
     664      let s:do_BR = 0
     665      let s:pat = match_words
     666    else
     667      let s:do_BR = 1
     668      let s:pat = s:ParseWords(match_words)
     669    endif
     670    let s:all = '\%(' . substitute(s:pat . (strlen(s:pat)?",":"") . default,
     671      \ '[,:]\+','\\|','g') . '\)'
     672    if exists("b:match_debug")
     673      let b:match_pat = s:pat
     674    endif
     675  endif
     676
     677  " Second step:  figure out the patterns for searchpair()
     678  " and save the screen, cursor position, and 'ignorecase'.
     679  " - TODO:  A lot of this is copied from s:Match_wrapper().
     680  " - maybe even more functionality should be split off
     681  " - into separate functions!
     682  let cdefault = (s:pat =~ '[^,]$' ? "," : "") . default
     683  let open =  substitute(s:pat . cdefault,
     684        \ s:notslash . '\zs:.\{-}' . s:notslash . ',', '\\),\\(', 'g')
     685  let open =  '\(' . substitute(open, s:notslash . '\zs:.*$', '\\)', '')
     686  let close = substitute(s:pat . cdefault,
     687        \ s:notslash . '\zs,.\{-}' . s:notslash . ':', '\\),\\(', 'g')
     688  let close = substitute(close, '^.\{-}' . s:notslash . ':', '\\(', '') . '\)'
     689  if exists("b:match_skip")
     690    let skip = b:match_skip
     691  elseif exists("b:match_comment") " backwards compatibility and testing!
     692    let skip = "r:" . b:match_comment
     693  else
     694    let skip = 's:comment\|string'
     695  endif
     696  let skip = s:ParseSkip(skip)
     697  " let restore_cursor = line(".") . "G" . virtcol(".") . "|"
     698  " normal! H
     699  " let restore_cursor = "normal!" . line(".") . "Gzt" . restore_cursor
     700  let restore_cursor = virtcol(".") . "|"
     701  normal! g0
     702  let restore_cursor = line(".") . "G" .  virtcol(".") . "|zs" . restore_cursor
     703  normal! H
     704  let restore_cursor = "normal!" . line(".") . "Gzt" . restore_cursor
     705  execute restore_cursor
     706
     707  " Third step: call searchpair().
     708  " Replace '\('--but not '\\('--with '\%(' and ',' with '\|'.
     709  let openpat =  substitute(open, '\(\\\@<!\(\\\\\)*\)\@<=\\(', '\\%(', 'g')
     710  let openpat = substitute(openpat, ',', '\\|', 'g')
     711  let closepat = substitute(close, '\(\\\@<!\(\\\\\)*\)\@<=\\(', '\\%(', 'g')
     712  let closepat = substitute(closepat, ',', '\\|', 'g')
     713  if skip =~ 'synID' && !(has("syntax") && exists("g:syntax_on"))
     714    let skip = '0'
     715  else
     716    execute "if " . skip . "| let skip = '0' | endif"
     717  endif
     718  mark '
     719  let level = v:count1
     720  while level
     721    if searchpair(openpat, '', closepat, a:spflag, skip) < 1
     722      call s:CleanUp(restore_options, a:mode, startline, startcol)
     723      return ""
     724    endif
     725    let level = level - 1
     726  endwhile
     727
     728  " Restore options and return a string to restore the original position.
     729  call s:CleanUp(restore_options, a:mode, startline, startcol)
     730  return restore_cursor
     731endfun
     732
     733" Search backwards for "if" or "while" or "<tag>" or ...
     734" and return "endif" or "endwhile" or "</tag>" or ... .
     735" For now, this uses b:match_words and the same script variables
     736" as s:Match_wrapper() .  Later, it may get its own patterns,
     737" either from a buffer variable or passed as arguments.
     738" fun! s:Autocomplete()
     739"   echo "autocomplete not yet implemented :-("
     740"   if !exists("b:match_words") || b:match_words == ""
     741"     return ""
     742"   end
     743"   let startpos = s:MultiMatch("bW")
     744"
     745"   if startpos == ""
     746"     return ""
     747"   endif
     748"   " - TODO:  figure out whether 'if' or '<tag>' matched, and construct
     749"   " - the appropriate closing.
     750"   let matchline = getline(".")
     751"   let curcol = col(".") - 1
     752"   " - TODO:  Change the s:all argument if there is a new set of match pats.
     753"   let regexp = s:Wholematch(matchline, s:all, curcol)
     754"   let suf = strlen(matchline) - matchend(matchline, regexp)
     755"   let prefix = (curcol ? '^.\{'  . curcol . '}\%(' : '^\%(')
     756"   let suffix = (suf ? '\).\{' . suf . '}$'  : '\)$')
     757"   " Reconstruct the version with unresolved backrefs.
     758"   let patBR = substitute(b:match_words.',', '[,:]*,[,:]*', ',', 'g')
     759"   let patBR = substitute(patBR, ':\{2,}', ':', "g")
     760"   " Now, set group and groupBR to the matching group: 'if:endif' or
     761"   " 'while:endwhile' or whatever.
     762"   let group = s:Choose(s:pat, matchline, ",", ":", prefix, suffix, patBR)
     763"   let i = matchend(group, s:notslash . ",")
     764"   let groupBR = strpart(group, i)
     765"   let group = strpart(group, 0, i-1)
     766"   " Now, matchline =~ prefix . substitute(group,':','\|','g') . suffix
     767"   if s:do_BR
     768"     let group = s:InsertRefs(groupBR, prefix, group, suffix, matchline)
     769"   endif
     770" " let g:group = group
     771"
     772"   " - TODO:  Construct the closing from group.
     773"   let fake = "end" . expand("<cword>")
     774"   execute startpos
     775"   return fake
     776" endfun
     777
     778" Close all open structures.  "Get the heck out of here!"
     779" fun! s:Gthhoh()
     780"   let close = s:Autocomplete()
     781"   while strlen(close)
     782"     put=close
     783"     let close = s:Autocomplete()
     784"   endwhile
     785" endfun
     786
     787" Parse special strings as typical skip arguments for searchpair():
     788"   s:foo becomes (current syntax item) =~ foo
     789"   S:foo becomes (current syntax item) !~ foo
     790"   r:foo becomes (line before cursor) =~ foo
     791"   R:foo becomes (line before cursor) !~ foo
     792fun! s:ParseSkip(str)
     793  let skip = a:str
     794  if skip[1] == ":"
     795    if skip[0] == "s"
     796      let skip = "synIDattr(synID(line('.'),col('.'),1),'name') =~? '" .
     797        \ strpart(skip,2) . "'"
     798    elseif skip[0] == "S"
     799      let skip = "synIDattr(synID(line('.'),col('.'),1),'name') !~? '" .
     800        \ strpart(skip,2) . "'"
     801    elseif skip[0] == "r"
     802      let skip = "strpart(getline('.'),0,col('.'))=~'" . strpart(skip,2). "'"
     803    elseif skip[0] == "R"
     804      let skip = "strpart(getline('.'),0,col('.'))!~'" . strpart(skip,2). "'"
     805    endif
     806  endif
     807  return skip
     808endfun
     809
     810let &cpo = s:save_cpo
     811
     812" vim:sts=2:sw=2:
Note: See TracBrowser for help on using the repository browser.