home *** CD-ROM | disk | FTP | other *** search
/ PC World 2005 June / PCWorld_2005-06_cd.bin / software / vyzkuste / firewally / firewally.exe / framework-2.3.exe / matchit.vim < prev    next >
Text File  |  2003-08-12  |  34KB  |  899 lines

  1. "  matchit.vim: (global plugin) Extended "%" matching
  2. "  Last Change: June 12, 2002
  3. "  Maintainer:  Benji Fisher PhD   <benji@member.AMS.org>
  4. "  Version:     1.7, for Vim 6.1
  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, 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
  40. if exists("loaded_matchit") || &cp
  41.   finish
  42. endif
  43. let loaded_matchit = 1
  44. let s:last_mps = ""
  45. let s:last_words = ""
  46.  
  47. let s:save_cpo = &cpo
  48. set cpo&vim
  49.  
  50. nnoremap <silent> %  :<C-U>call <SID>Match_wrapper('',1,'n') <CR>
  51. nnoremap <silent> g% :<C-U>call <SID>Match_wrapper('',0,'n') <CR>
  52. vnoremap <silent> %  :<C-U>call <SID>Match_wrapper('',1,'v') <CR>m'gv``
  53. vnoremap <silent> g% :<C-U>call <SID>Match_wrapper('',0,'v') <CR>m'gv``
  54. onoremap <silent> %  v:<C-U>call <SID>Match_wrapper('',1,'o') <CR>
  55. onoremap <silent> g% v:<C-U>call <SID>Match_wrapper('',0,'o') <CR>
  56.  
  57. " Analogues of [{ and ]} using matching patterns:
  58. nnoremap <silent> [% :<C-U>call <SID>MultiMatch("bW", "n") <CR>
  59. nnoremap <silent> ]% :<C-U>call <SID>MultiMatch("W",  "n") <CR>
  60. vmap [% <Esc>[%m'gv``
  61. vmap ]% <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``
  64. onoremap <silent> [% v:<C-U>call <SID>MultiMatch("bW", "o") <CR>
  65. onoremap <silent> ]% v:<C-U>call <SID>MultiMatch("W",  "o") <CR>
  66.  
  67. " text object:
  68. vmap 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.  
  85. let s:notslash = '\\\@<!\%(\\\\\)*'
  86.  
  87. function! 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 or if no patterns are defined.
  104.   if v:count
  105.     exe "normal! " . v:count . "%"
  106.     return s:CleanUp(restore_options, a:mode, startline, startcol)
  107.   elseif !exists("b:match_words") || b:match_words == ""
  108.     silent! normal! %
  109.     return s:CleanUp(restore_options, a:mode, startline, startcol)
  110.   end
  111.  
  112.   " First step:  if not already done, set the script variables
  113.   "   s:do_BR    flag for whether there are backrefs
  114.   "   s:pat    parsed version of b:match_words
  115.   "   s:all    regexp based on s:pat and the default groups
  116.   "
  117.   " Allow b:match_words = "GetVimMatchWords()" .
  118.   if b:match_words =~ ":"
  119.     let match_words = b:match_words
  120.   else
  121.     execute "let match_words =" b:match_words
  122.   endif
  123. " Thanks to Preben "Peppe" Guldberg and Bram Moolenaar for this suggestion!
  124.   if (match_words != s:last_words) || (&mps != s:last_mps) ||
  125.     \ exists("b:match_debug")
  126.     let s:last_words = match_words
  127.     let s:last_mps = &mps
  128.     if match_words !~ s:notslash . '\\\d'
  129.       let s:do_BR = 0
  130.       let s:pat = match_words
  131.     else
  132.       let s:do_BR = 1
  133.       let s:pat = s:ParseWords(match_words)
  134.     endif
  135.     " The next several lines were here before
  136.     " BF started messing with this script.
  137.     " quote the special chars in 'matchpairs', replace [,:] with \| and then
  138.     " append the builtin pairs (/*, */, #if, #ifdef, #else, #elif, #endif)
  139.     " let default = substitute(escape(&mps, '[$^.*~\\/?]'), '[,:]\+',
  140.     "  \ '\\|', 'g').'\|\/\*\|\*\/\|#if\>\|#ifdef\>\|#else\>\|#elif\>\|#endif\>'
  141.     let default = escape(&mps, '[$^.*~\\/?]') . (strlen(&mps) ? "," : "") .
  142.       \ '\/\*:\*\/,#if\%(def\)\=:#else\>:#elif\>:#endif\>'
  143.     " s:all = pattern with all the keywords
  144.     let s:all = s:pat . (strlen(s:pat) ? "," : "") . default
  145.     let s:all = substitute(s:all, s:notslash . '\zs[,:]\+', '\\|', 'g')
  146.     let s:all = '\%(' . s:all . '\)'
  147.     " let s:all = '\%(' . substitute(s:all, '\\\ze[,:]', '', 'g') . '\)'
  148.     if exists("b:match_debug")
  149.       let b:match_pat = s:pat
  150.     endif
  151.   endif
  152.  
  153.   " Second step:  set the following local variables:
  154.   "     matchline = line on which the cursor started
  155.   "     curcol    = number of characters before match
  156.   "     prefix    = regexp for start of line to start of match
  157.   "     suffix    = regexp for end of match to end of line
  158.   " Require match to end on or after the cursor and prefer it to
  159.   " start on or before the cursor.
  160.   let matchline = getline(startline)
  161.   if a:word != ''
  162.     " word given
  163.     if a:word !~ s:all
  164.       echohl WarningMsg|echo 'Missing rule for word:"'.a:word.'"'|echohl NONE
  165.       return s:CleanUp(restore_options, a:mode, startline, startcol)
  166.     endif
  167.     let matchline = a:word
  168.     let curcol = 0
  169.     let prefix = '^\%('
  170.     let suffix = '\)$'
  171.   " Now the case when "word" is not given
  172.   else    " Find the match that ends on or after the cursor and set curcol.
  173.     let regexp = s:Wholematch(matchline, s:all, startcol-1)
  174.     let curcol = match(matchline, regexp)
  175.     let suf = strlen(matchline) - matchend(matchline, regexp)
  176.     let prefix = (curcol ? '^.\{'  . curcol . '}\%(' : '^\%(')
  177.     let suffix = (suf ? '\).\{' . suf . '}$'  : '\)$')
  178.     " If the match comes from the defaults, bail out.
  179.     if matchline !~ prefix .
  180.       \ substitute(s:pat, s:notslash.'\zs[,:]\+', '\\|', 'g') . suffix
  181.       silent! norm! %
  182.       return s:CleanUp(restore_options, a:mode, startline, startcol)
  183.     endif
  184.   endif
  185.   if exists("b:match_debug")
  186.     let b:match_match = matchstr(matchline, regexp)
  187.     let b:match_col = curcol+1
  188.   endif
  189.  
  190.   " Third step:  Find the group and single word that match, and the original
  191.   " (backref) versions of these.  Then, resolve the backrefs.
  192.   " Set the following local variable:
  193.   " group = colon-separated list of patterns, one of which matches
  194.   "       = ini:mid:fin or ini:fin
  195.   "
  196.   " Reconstruct the version with unresolved backrefs.
  197.   let patBR = substitute(match_words.',',
  198.     \ s:notslash.'\zs[,:]*,[,:]*', ',', 'g')
  199.   let patBR = substitute(patBR, s:notslash.'\zs:\{2,}', ':', 'g')
  200.   " Now, set group and groupBR to the matching group: 'if:endif' or
  201.   " 'while:endwhile' or whatever.  A bit of a kluge:  s:Choose() returns
  202.   " group . "," . groupBR, and we pick it apart.
  203.   let group = s:Choose(s:pat, matchline, ",", ":", prefix, suffix, patBR)
  204.   let i = matchend(group, s:notslash . ",")
  205.   let groupBR = strpart(group, i)
  206.   let group = strpart(group, 0, i-1)
  207.   " Now, matchline =~ prefix . substitute(group,':','\|','g') . suffix
  208.   if s:do_BR " Do the hard part:  resolve those backrefs!
  209.     let group = s:InsertRefs(groupBR, prefix, group, suffix, matchline)
  210.   endif
  211.   if exists("b:match_debug")
  212.     let b:match_wholeBR = groupBR
  213.     let i = matchend(groupBR, s:notslash . ":")
  214.     let b:match_iniBR = strpart(groupBR, 0, i-1)
  215.   endif
  216.  
  217.   " Fourth step:  Set the arguments for searchpair().
  218.   let i = matchend(group, s:notslash . ":")
  219.   let j = matchend(group, '.*' . s:notslash . ":")
  220.   let ini = strpart(group, 0, i-1)
  221.   let mid = substitute(strpart(group, i,j-i-1), s:notslash.'\zs:', '\\|', 'g')
  222.   let fin = strpart(group, j)
  223.   " searchpair() requires that these patterns avoid \(\) groups.
  224.   let ini = substitute(ini, s:notslash . '\zs\\(', '\\%(', 'g')
  225.   let mid = substitute(mid, s:notslash . '\zs\\(', '\\%(', 'g')
  226.   let fin = substitute(fin, s:notslash . '\zs\\(', '\\%(', 'g')
  227.   " Set mid.  This is optimized for readability, not micro-efficiency!
  228.   if a:forward && matchline =~ prefix . fin . suffix
  229.     \ || !a:forward && matchline =~ prefix . ini . suffix
  230.     let mid = ""
  231.   endif
  232.   " Set flag.  This is optimized for readability, not micro-efficiency!
  233.   if a:forward && matchline =~ prefix . fin . suffix
  234.     \ || !a:forward && matchline !~ prefix . ini . suffix
  235.     let flag = "bW"
  236.   else
  237.     let flag = "W"
  238.   endif
  239.   " Set skip.
  240.   if exists("b:match_skip")
  241.     let skip = b:match_skip
  242.   elseif exists("b:match_comment") " backwards compatibility and testing!
  243.     let skip = "r:" . b:match_comment
  244.   else
  245.     let skip = 's:comment\|string'
  246.   endif
  247.   let skip = s:ParseSkip(skip)
  248.   if exists("b:match_debug")
  249.     let b:match_ini = ini
  250.     let b:match_tail = (strlen(mid) ? mid.'\|' : '') . fin
  251.   endif
  252.  
  253.   " Fifth step:  actually start moving the cursor and call searchpair().
  254.   " Later, :execute restore_cursor to get to the original screen.
  255.   let restore_cursor = line(".") . "G" . virtcol(".") . "|"
  256.   normal! H
  257.   let restore_cursor = "normal!" . line(".") . "Gzt" . restore_cursor
  258.   execute restore_cursor
  259.   normal! 0
  260.   if curcol
  261.     execute "normal!" . curcol . "l"
  262.   endif
  263.   if skip =~ 'synID' && !(has("syntax") && exists("g:syntax_on"))
  264.     let skip = "0"
  265.   else
  266.     execute "if " . skip . "| let skip = '0' | endif"
  267.   endif
  268.   let sp_return = searchpair(ini, mid, fin, flag, skip)
  269.   let final_position = line(".") . "normal!" . virtcol(".") . "|"
  270.   " Restore cursor position and original screen.
  271.   execute restore_cursor
  272.   normal! m'
  273.   if sp_return > 0
  274.     execute final_position
  275.   endif
  276.   return s:CleanUp(restore_options, a:mode, startline, startcol, mid.'\|'.fin)
  277. endfun
  278.  
  279. " Restore options and do some special handling for Operator-pending mode.
  280. " The optional argument is the tail of the matching group.
  281. fun! s:CleanUp(options, mode, startline, startcol, ...)
  282.   execute "set" a:options
  283.   " Open folds, if appropriate.
  284.   if a:mode != "o"
  285.     if &foldopen =~ "percent"
  286.       normal! zv
  287.     endif
  288.     " In Operator-pending mode, we want to include the whole match
  289.     " (for example, d%).
  290.     " This is only a problem if we end up moving in the forward direction.
  291.   elseif (a:startline < line(".")) ||
  292.     \ (a:startline == line(".") && a:startcol < col("."))
  293.     if a:0
  294.       " Check whether the match is a single character.  If not, move to the
  295.       " end of the match.
  296.       let matchline = getline(".")
  297.       let currcol = col(".")
  298.       let regexp = s:Wholematch(matchline, a:1, currcol-1)
  299.       let endcol = matchend(matchline, regexp)
  300.       if endcol > currcol  " This is NOT off by one!
  301.     execute "normal!" . (endcol - currcol) . "l"
  302.       endif
  303.     endif " a:0
  304.   endif " a:mode != "o" && etc.
  305.   return 0
  306. endfun
  307.  
  308. " Example (simplified HTML patterns):  if
  309. "   a:groupBR    = '<\(\k\+\)>:</\1>'
  310. "   a:prefix    = '^.\{3}\('
  311. "   a:group    = '<\(\k\+\)>:</\(\k\+\)>'
  312. "   a:suffix    = '\).\{2}$'
  313. "   a:matchline    =  "123<tag>12" or "123</tag>12"
  314. " then extract "tag" from a:matchline and return "<tag>:</tag>" .
  315. fun! s:InsertRefs(groupBR, prefix, group, suffix, matchline)
  316.   if a:matchline !~ a:prefix .
  317.     \ substitute(a:group, s:notslash . '\zs:', '\\|', 'g') . a:suffix
  318.     return a:group
  319.   endif
  320.   let i = matchend(a:groupBR, s:notslash . ':')
  321.   let ini = strpart(a:groupBR, 0, i-1)
  322.   let tailBR = strpart(a:groupBR, i)
  323.   let word = s:Choose(a:group, a:matchline, ":", "", a:prefix, a:suffix,
  324.     \ a:groupBR)
  325.   let i = matchend(word, s:notslash . ":")
  326.   let wordBR = strpart(word, i)
  327.   let word = strpart(word, 0, i-1)
  328.   " Now, a:matchline =~ a:prefix . word . a:suffix
  329.   if wordBR != ini
  330.     let table = s:Resolve(ini, wordBR, "table")
  331.   else
  332.     " let table = "----------"
  333.     let table = ""
  334.     let d = 0
  335.     while d < 10
  336.       if tailBR =~ s:notslash . '\\' . d
  337.     " let table[d] = d
  338.     let table = table . d
  339.       else
  340.     let table = table . "-"
  341.       endif
  342.       let d = d + 1
  343.     endwhile
  344.   endif
  345.   let d = 9
  346.   while d
  347.     if table[d] != "-"
  348.       let backref = substitute(a:matchline, a:prefix.word.a:suffix,
  349.     \ '\'.table[d], "")
  350.     " Are there any other characters that should be escaped?
  351.       let backref = escape(backref, '*,:')
  352.       execute s:Ref(ini, d, "start", "len")
  353.       let ini = strpart(ini, 0, start) . backref . strpart(ini, start+len)
  354.       let tailBR = substitute(tailBR, s:notslash . '\zs\\' . d,
  355.     \ escape(backref, '\\'), 'g')
  356.     endif
  357.     let d = d-1
  358.   endwhile
  359.   if exists("b:match_debug")
  360.     if s:do_BR
  361.       let b:match_table = table
  362.       let b:match_word = word
  363.     else
  364.       let b:match_table = ""
  365.       let b:match_word = ""
  366.     endif
  367.   endif
  368.   return ini . ":" . tailBR
  369. endfun
  370.  
  371. " Input a comma-separated list of groups with backrefs, such as
  372. "   a:groups = '\(foo\):end\1,\(bar\):end\1'
  373. " and return a comma-separated list of groups with backrefs replaced:
  374. "   return '\(foo\):end\(foo\),\(bar\):end\(bar\)'
  375. fun! s:ParseWords(groups)
  376.   let groups = substitute(a:groups.",", s:notslash.'\zs[,:]*,[,:]*', ',', 'g')
  377.   let groups = substitute(groups, s:notslash . '\zs:\{2,}', ':', 'g')
  378.   let parsed = ""
  379.   while groups =~ '[^,:]'
  380.     let i = matchend(groups, s:notslash . ':')
  381.     let j = matchend(groups, s:notslash . ',')
  382.     let ini = strpart(groups, 0, i-1)
  383.     let tail = strpart(groups, i, j-i-1) . ":"
  384.     let groups = strpart(groups, j)
  385.     let parsed = parsed . ini
  386.     let i = matchend(tail, s:notslash . ':')
  387.     while i != -1
  388.       " In 'if:else:endif', ini='if' and word='else' and then word='endif'.
  389.       let word = strpart(tail, 0, i-1)
  390.       let tail = strpart(tail, i)
  391.       let i = matchend(tail, s:notslash . ':')
  392.       let parsed = parsed . ":" . s:Resolve(ini, word, "word")
  393.     endwhile " Now, tail has been used up.
  394.     let parsed = parsed . ","
  395.   endwhile " groups =~ '[^,:]'
  396.   return parsed
  397. endfun
  398.  
  399. " TODO I think this can be simplified and/or made more efficient.
  400. " TODO What should I do if a:start is out of range?
  401. " Return a regexp that matches all of a:string, such that
  402. " matchstr(a:string, regexp) represents the match for a:pat that starts
  403. " as close to a:start as possible, before being preferred to after, and
  404. " ends after a:start .
  405. " Usage:
  406. " let regexp = s:Wholematch(getline("."), 'foo\|bar', col(".")-1)
  407. " let i      = match(getline("."), regexp)
  408. " let j      = matchend(getline("."), regexp)
  409. " let match  = matchstr(getline("."), regexp)
  410. fun! s:Wholematch(string, pat, start)
  411.   let group = '\%(' . a:pat . '\)'
  412.   let prefix = (a:start ? '\(^.\{,' . a:start . '}\)\zs' : '^')
  413.   let len = strlen(a:string)
  414.   let suffix = (a:start+1 < len ? '\(.\{,'.(len-a:start-1).'}$\)\@=' : '$')
  415.   if a:string !~ prefix . group . suffix
  416.     let prefix = ''
  417.   endif
  418.   return prefix . group . suffix
  419. endfun
  420.  
  421. " No extra arguments:  s:Ref(string, d) will
  422. " find the d'th occurrence of '\(' and return it, along with everything up
  423. " to and including the matching '\)'.
  424. " One argument:  s:Ref(string, d, "start") returns the index of the start
  425. " of the d'th '\(' and any other argument returns the length of the group.
  426. " Two arguments:  s:Ref(string, d, "foo", "bar") returns a string to be
  427. " executed, having the effect of
  428. "   :let foo = s:Ref(string, d, "start")
  429. "   :let bar = s:Ref(string, d, "len")
  430. fun! s:Ref(string, d, ...)
  431.   let len = strlen(a:string)
  432.   if a:d == 0
  433.     let start = 0
  434.   else
  435.     let cnt = a:d
  436.     let match = a:string
  437.     while cnt
  438.       let cnt = cnt - 1
  439.       let index = matchend(match, s:notslash . '\\(')
  440.       if index == -1
  441.     return ""
  442.       endif
  443.       let match = strpart(match, index)
  444.     endwhile
  445.     let start = len - strlen(match)
  446.     if a:0 == 1 && a:1 == "start"
  447.       return start - 2
  448.     endif
  449.     let cnt = 1
  450.     while cnt
  451.       let index = matchend(match, s:notslash . '\\(\|\\)') - 1
  452.       if index == -2
  453.     return ""
  454.       endif
  455.       " Increment if an open, decrement if a ')':
  456.       let cnt = cnt + (match[index]=="(" ? 1 : -1)  " ')'
  457.       " let cnt = stridx('0(', match[index]) + cnt
  458.       let match = strpart(match, index+1)
  459.     endwhile
  460.     let start = start - 2
  461.     let len = len - start - strlen(match)
  462.   endif
  463.   if a:0 == 1
  464.     return len
  465.   elseif a:0 == 2
  466.     return "let " . a:1 . "=" . start . "| let " . a:2 . "=" . len
  467.   else
  468.     return strpart(a:string, start, len)
  469.   endif
  470. endfun
  471.  
  472. " Count the number of disjoint copies of pattern in string.
  473. " If the pattern is a literal string and contains no '0' or '1' characters
  474. " then s:Count(string, pattern, '0', '1') should be faster than
  475. " s:Count(string, pattern).
  476. fun! s:Count(string, pattern, ...)
  477.   let pat = escape(a:pattern, '\\')
  478.   if a:0 > 1
  479.     let foo = substitute(a:string, '[^'.a:pattern.']', "a:1", "g")
  480.     let foo = substitute(a:string, pat, a:2, "g")
  481.     let foo = substitute(foo, '[^' . a:2 . ']', "", "g")
  482.     return strlen(foo)
  483.   endif
  484.   let result = 0
  485.   let foo = a:string
  486.   let index = matchend(foo, pat)
  487.   while index != -1
  488.     let result = result + 1
  489.     let foo = strpart(foo, index)
  490.     let index = matchend(foo, pat)
  491.   endwhile
  492.   return result
  493. endfun
  494.  
  495. " s:Resolve('\(a\)\(b\)', '\(c\)\2\1\1\2') should return table.word, where
  496. " word = '\(c\)\(b\)\(a\)\3\2' and table = '-32-------'.  That is, the first
  497. " '\1' in target is replaced by '\(a\)' in word, table[1] = 3, and this
  498. " indicates that all other instances of '\1' in target are to be replaced
  499. " by '\3'.  The hard part is dealing with nesting...
  500. " Note that ":" is an illegal character for source and target,
  501. " unless it is preceded by "\".
  502. fun! s:Resolve(source, target, output)
  503.   let word = a:target
  504.   let i = matchend(word, s:notslash . '\\\d') - 1
  505.   let table = "----------"
  506.   while i != -2 " There are back references to be replaced.
  507.     let d = word[i]
  508.     let backref = s:Ref(a:source, d)
  509.     " The idea is to replace '\d' with backref.  Before we do this,
  510.     " replace any \(\) groups in backref with :1, :2, ... if they
  511.     " correspond to the first, second, ... group already inserted
  512.     " into backref.  Later, replace :1 with \1 and so on.  The group
  513.     " number w+b within backref corresponds to the group number
  514.     " s within a:source.
  515.     " w = number of '\(' in word before the current one
  516.     let w = s:Count(
  517.     \ substitute(strpart(word, 0, i-1), '\\\\', '', 'g'), '\(', '1')
  518.     let b = 1 " number of the current '\(' in backref
  519.     let s = d " number of the current '\(' in a:source
  520.     while b <= s:Count(substitute(backref, '\\\\', '', 'g'), '\(', '1')
  521.     \ && s < 10
  522.       if table[s] == "-"
  523.     if w + b < 10
  524.       " let table[s] = w + b
  525.       let table = strpart(table, 0, s) . (w+b) . strpart(table, s+1)
  526.     endif
  527.     let b = b + 1
  528.     let s = s + 1
  529.       else
  530.     execute s:Ref(backref, b, "start", "len")
  531.     let ref = strpart(backref, start, len)
  532.     let backref = strpart(backref, 0, start) . ":". table[s]
  533.     \ . strpart(backref, start+len)
  534.     let s = s + s:Count(substitute(ref, '\\\\', '', 'g'), '\(', '1')
  535.       endif
  536.     endwhile
  537.     let word = strpart(word, 0, i-1) . backref . strpart(word, i+1)
  538.     let i = matchend(word, s:notslash . '\\\d') - 1
  539.   endwhile
  540.   let word = substitute(word, s:notslash . '\zs:', '\\', 'g')
  541.   if a:output == "table"
  542.     return table
  543.   elseif a:output == "word"
  544.     return word
  545.   else
  546.     return table . word
  547.   endif
  548. endfun
  549.  
  550. " Assume a:comma = ",".  Then the format for a:patterns and a:1 is
  551. "   a:patterns = "<pat1>,<pat2>,..."
  552. "   a:1 = "<alt1>,<alt2>,..."
  553. " If <patn> is the first pattern that matches a:string then return <patn>
  554. " if no optional arguments are given; return <patn>,<altn> if a:1 is given.
  555. fun! s:Choose(patterns, string, comma, branch, prefix, suffix, ...)
  556.   let tail = (a:patterns =~ a:comma."$" ? a:patterns : a:patterns . a:comma)
  557.   let i = matchend(tail, s:notslash . a:comma)
  558.   if a:0
  559.     let alttail = (a:1 =~ a:comma."$" ? a:1 : a:1 . a:comma)
  560.     let j = matchend(alttail, s:notslash . a:comma)
  561.   endif
  562.   let current = strpart(tail, 0, i-1)
  563.   if a:branch == ""
  564.     let currpat = current
  565.   else
  566.     let currpat = substitute(current, a:branch, '\\|', 'g')
  567.   endif
  568.   while a:string !~ a:prefix . currpat . a:suffix
  569.     let tail = strpart(tail, i)
  570.     let i = matchend(tail, s:notslash . a:comma)
  571.     if i == -1
  572.       return -1
  573.     endif
  574.     let current = strpart(tail, 0, i-1)
  575.     if a:branch == ""
  576.       let currpat = current
  577.     else
  578.       let currpat = substitute(current, a:branch, '\\|', 'g')
  579.     endif
  580.     if a:0
  581.       let alttail = strpart(alttail, j)
  582.       let j = matchend(alttail, s:notslash . a:comma)
  583.     endif
  584.   endwhile
  585.   if a:0
  586.     let current = current . a:comma . strpart(alttail, 0, j-1)
  587.   endif
  588.   return current
  589. endfun
  590.  
  591. " Call this function to turn on debugging information.  Every time the main
  592. " script is run, buffer variables will be saved.  These can be used directly
  593. " or viewed using the menu items below.
  594. if !exists(":MatchDebug")
  595.   command! -nargs=0 MatchDebug call s:Match_debug()
  596. endif
  597.  
  598. fun! s:Match_debug()
  599.   let b:match_debug = 1    " Save debugging information.
  600.   " pat = all of b:match_words with backrefs parsed
  601.   amenu &Matchit.&pat    :echo b:match_pat<CR>
  602.   " match = bit of text that is recognized as a match
  603.   amenu &Matchit.&match    :echo b:match_match<CR>
  604.   " curcol = cursor column of the start of the matching text
  605.   amenu &Matchit.&curcol    :echo b:match_col<CR>
  606.   " wholeBR = matching group, original version
  607.   amenu &Matchit.wh&oleBR    :echo b:match_wholeBR<CR>
  608.   " iniBR = 'if' piece, original version
  609.   amenu &Matchit.ini&BR    :echo b:match_iniBR<CR>
  610.   " ini = 'if' piece, with all backrefs resolved from match
  611.   amenu &Matchit.&ini    :echo b:match_ini<CR>
  612.   " tail = 'else\|endif' piece, with all backrefs resolved from match
  613.   amenu &Matchit.&tail    :echo b:match_tail<CR>
  614.   " fin = 'endif' piece, with all backrefs resolved from match
  615.   amenu &Matchit.&word    :echo b:match_word<CR>
  616.   " '\'.d in ini refers to the same thing as '\'.table[d] in word.
  617.   amenu &Matchit.t&able    :echo '0:' . b:match_table . ':9'<CR>
  618. endfun
  619.  
  620. " Jump to the nearest unmatched "(" or "if" or "<tag>" if a:spflag == "bW"
  621. " or the nearest unmatched "</tag>" or "endif" or ")" if a:spflag == "W".
  622. " Return a "mark" for the original position, so that
  623. "   let m = MultiMatch("bW", "n") ... execute m
  624. " will return to the original position.  If there is a problem, do not
  625. " move the cursor and return "", unless a count is given, in which case
  626. " go up or down as many levels as possible and again return "".
  627. " TODO This relies on the same patterns as % matching.  It might be a good
  628. " idea to give it its own matching patterns.
  629. fun! s:MultiMatch(spflag, mode)
  630.   if !exists("b:match_words") || b:match_words == ""
  631.     return ""
  632.   end
  633.   let restore_options = (&ic ? "" : "no") . "ignorecase"
  634.   if exists("b:match_ignorecase")
  635.     let &ignorecase = b:match_ignorecase
  636.   endif
  637.   let startline = line(".")
  638.   let startcol = col(".")
  639.  
  640.   " First step:  if not already done, set the script variables
  641.   "   s:do_BR    flag for whether there are backrefs
  642.   "   s:pat    parsed version of b:match_words
  643.   "   s:all    regexp based on s:pat and the default groups
  644.   " This part is copied and slightly modified from s:Match_wrapper().
  645.   let default = escape(&mps, '[$^.*~\\/?]') . (strlen(&mps) ? "," : "") .
  646.     \ '\/\*:\*\/,#if\%(def\)\=:$else\>:#elif\>:#endif\>'
  647.   " Allow b:match_words = "GetVimMatchWords()" .
  648.   if b:match_words =~ ":"
  649.     let match_words = b:match_words
  650.   else
  651.     execute "let match_words =" b:match_words
  652.   endif
  653.   if (match_words != s:last_words) || (&mps != s:last_mps) ||
  654.     \ exists("b:match_debug")
  655.     let s:last_words = match_words
  656.     let s:last_mps = &mps
  657.     if match_words !~ s:notslash . '\\\d'
  658.       let s:do_BR = 0
  659.       let s:pat = match_words
  660.     else
  661.       let s:do_BR = 1
  662.       let s:pat = s:ParseWords(match_words)
  663.     endif
  664.     let s:all = '\%(' . substitute(s:pat . (strlen(s:pat)?",":"") . default,
  665.       \    '[,:]\+','\\|','g') . '\)'
  666.     if exists("b:match_debug")
  667.       let b:match_pat = s:pat
  668.     endif
  669.   endif
  670.  
  671.   " Second step:  figure out the patterns for searchpair()
  672.   " and save the screen, cursor position, and 'ignorecase'.
  673.   " - TODO:  A lot of this is copied from s:Match_wrapper().
  674.   " - maybe even more functionality should be split off
  675.   " - into separate functions!
  676.   let open =  substitute(s:pat . default, ':[^,]*,', '\\),\\(', 'g')
  677.   let open =  '\(' . substitute(open, ':[^,]*$', '\\)', '')
  678.   let close = substitute(s:pat . default, ',[^,]*:', '\\),\\(', 'g')
  679.   let close = substitute(close, '[^,]*:', '\\(', '') . '\)'
  680.   if exists("b:match_skip")
  681.     let skip = b:match_skip
  682.   elseif exists("b:match_comment") " backwards compatibility and testing!
  683.     let skip = "r:" . b:match_comment
  684.   else
  685.     let skip = 's:comment\|string'
  686.   endif
  687.   let skip = s:ParseSkip(skip)
  688.   let restore_cursor = line(".") . "G" . virtcol(".") . "|"
  689.   normal! H
  690.   let restore_cursor = "normal!" . line(".") . "Gzt" . restore_cursor
  691.   execute restore_cursor
  692.  
  693.   " Third step: call searchpair().
  694.   " Replace '\('--but not '\\('--with '\%(' and ',' with '\|'.
  695.   let openpat =  substitute(open, '\(\\\@<!\(\\\\\)*\)\@<=\\(', '\\%(', 'g')
  696.   let openpat = substitute(openpat, ',', '\\|', 'g')
  697.   let closepat = substitute(close, '\(\\\@<!\(\\\\\)*\)\@<=\\(', '\\%(', 'g')
  698.   let closepat = substitute(closepat, ',', '\\|', 'g')
  699.   if skip =~ 'synID' && !(has("syntax") && exists("g:syntax_on"))
  700.     let skip = '0'
  701.   else
  702.     execute "if " . skip . "| let skip = '0' | endif"
  703.   endif
  704.   mark '
  705.   let level = v:count1
  706.   while level
  707.     if searchpair(openpat, '', closepat, a:spflag, skip) < 1
  708.       call s:CleanUp(restore_options, a:mode, startline, startcol)
  709.       return ""
  710.     endif
  711.     let level = level - 1
  712.   endwhile
  713.  
  714.   " Restore options and return a string to restore the original position.
  715.   call s:CleanUp(restore_options, a:mode, startline, startcol)
  716.   return restore_cursor
  717. endfun
  718.  
  719. " Search backwards for "if" or "while" or "<tag>" or ...
  720. " and return "endif" or "endwhile" or "</tag>" or ... .
  721. " For now, this uses b:match_words and the same script variables
  722. " as s:Match_wrapper() .  Later, it may get its own patterns,
  723. " either from a buffer variable or passed as arguments.
  724. " fun! s:Autocomplete()
  725. "   echo "autocomplete not yet implemented :-("
  726. "   if !exists("b:match_words") || b:match_words == ""
  727. "     return ""
  728. "   end
  729. "   let startpos = s:MultiMatch("bW")
  730. "
  731. "   if startpos == ""
  732. "     return ""
  733. "   endif
  734. "   " - TODO:  figure out whether 'if' or '<tag>' matched, and construct
  735. "   " - the appropriate closing.
  736. "   let matchline = getline(".")
  737. "   let curcol = col(".") - 1
  738. "   " - TODO:  Change the s:all argument if there is a new set of match pats.
  739. "   let regexp = s:Wholematch(matchline, s:all, curcol)
  740. "   let suf = strlen(matchline) - matchend(matchline, regexp)
  741. "   let prefix = (curcol ? '^.\{'  . curcol . '}\%(' : '^\%(')
  742. "   let suffix = (suf ? '\).\{' . suf . '}$'  : '\)$')
  743. "   " Reconstruct the version with unresolved backrefs.
  744. "   let patBR = substitute(b:match_words.',', '[,:]*,[,:]*', ',', 'g')
  745. "   let patBR = substitute(patBR, ':\{2,}', ':', "g")
  746. "   " Now, set group and groupBR to the matching group: 'if:endif' or
  747. "   " 'while:endwhile' or whatever.
  748. "   let group = s:Choose(s:pat, matchline, ",", ":", prefix, suffix, patBR)
  749. "   let i = matchend(group, s:notslash . ",")
  750. "   let groupBR = strpart(group, i)
  751. "   let group = strpart(group, 0, i-1)
  752. "   " Now, matchline =~ prefix . substitute(group,':','\|','g') . suffix
  753. "   if s:do_BR
  754. "     let group = s:InsertRefs(groupBR, prefix, group, suffix, matchline)
  755. "   endif
  756. " " let g:group = group
  757. "
  758. "   " - TODO:  Construct the closing from group.
  759. "   let fake = "end" . expand("<cword>")
  760. "   execute startpos
  761. "   return fake
  762. " endfun
  763.  
  764. " Close all open structures.  "Get the heck out of here!"
  765. " fun! s:Gthhoh()
  766. "   let close = s:Autocomplete()
  767. "   while strlen(close)
  768. "     put=close
  769. "     let close = s:Autocomplete()
  770. "   endwhile
  771. " endfun
  772.  
  773. " Parse special strings as typical skip arguments for searchpair():
  774. "   s:foo becomes (current syntax item) =~ foo
  775. "   S:foo becomes (current syntax item) !~ foo
  776. "   r:foo becomes (line before cursor) =~ foo
  777. "   R:foo becomes (line before cursor) !~ foo
  778. fun! s:ParseSkip(str)
  779.   let skip = a:str
  780.   if skip[1] == ":"
  781.     if skip[0] == "s"
  782.       let skip = "synIDattr(synID(line('.'),col('.'),1),'name') =~? '" .
  783.     \ strpart(skip,2) . "'"
  784.     elseif skip[0] == "S"
  785.       let skip = "synIDattr(synID(line('.'),col('.'),1),'name') !~? '" .
  786.     \ strpart(skip,2) . "'"
  787.     elseif skip[0] == "r"
  788.       let skip = "strpart(getline('.'),0,col('.'))=~'" . strpart(skip,2). "'"
  789.     elseif skip[0] == "R"
  790.       let skip = "strpart(getline('.'),0,col('.'))!~'" . strpart(skip,2). "'"
  791.     endif
  792.   endif
  793.   return skip
  794. endfun
  795.  
  796. aug Matchit
  797.   let s:notend = '\%(\<end\s\+\)\@<!'
  798.   au!
  799.   " ASP:  Active Server Pages (with Visual Basic Script)
  800.   " thanks to Gontran BAERTS
  801.   au FileType aspvbs if !exists("b:match_words") |
  802.     \ do Matchit FileType html |
  803.   \ let b:match_words =
  804.   \ s:notend . '\<If\>:^\s\+\<Else\>:\<ElseIf\>:\<end\s\+\<if\>,' .
  805.   \ s:notend . '\<Select\s\+\<Case\>:\<Case\>:\<Case\s\+\<Else\>:' .
  806.   \    '\<End\s\+\<Select\>,' .
  807.   \ '^\s*\<Sub\>:\<End\s\+\<Sub\>,' .
  808.   \ '^\s*\<Function\>:\<End\s\+\<Function\>,' .
  809.   \ '\<Class\>:\<End\s\+\<Class\>,' .
  810.   \ '^\s*\<Do\>:\<Loop\>,' .
  811.   \ '^\s*\<For\>:\<Next\>,' .
  812.   \ '\<While\>:\<Wend\>,' .
  813.   \ b:match_words
  814.   \ | endif
  815.   " Csh:  thanks to Johannes Zellner
  816.   " - Both foreach and end must appear alone on separate lines.
  817.   " - The words else and endif must appear at the beginning of input lines;
  818.   "   the if must appear alone on its input line or after an else.
  819.   " - Each case label and the default label must appear at the start of a
  820.   "   line.
  821.   " - while and end must appear alone on their input lines.
  822.   au FileType csh,tcsh  if !exists("b:match_words") |
  823.     \ let b:match_words =
  824.       \ '^\s*\<if\>.*(.*).*\<then\>:'.
  825.       \   '^\s*\<else\>\s\+\<if\>.*(.*).*\<then\>:^\s*\<else\>:'.
  826.       \   '^\s*\<endif\>,'.
  827.       \ '\%(^\s*\<foreach\>\s\+\S\+\|^s*\<while\>\).*(.*):'.
  828.       \   '\<break\>:\<continue\>:^\s*\<end\>,'.
  829.       \ '^\s*\<switch\>.*(.*):^\s*\<case\>\s\+:^\s*\<default\>:^\s*\<endsw\>'
  830.       \ | endif
  831.   " DTD:  thanks to Johannes Zellner
  832.   " - match <!--, --> style comments.
  833.   " - match <! with >
  834.   " - TODO:  why does '--:--,'. not work ?
  835.   au! FileType dtd if !exists("b:match_words") |
  836.     \ let b:match_words =
  837.     \ '<!--:-->,'.
  838.     \ '<!:>'
  839.     \ | endif
  840.   " Entity:  see XML.
  841.   " Essbase:
  842.   au BufNewFile,BufRead *.csc if !exists("b:match_words") |
  843.     \ let b:match_words=
  844.   \ '\<fix\>:\<endfix\>,' .
  845.   \ '\<if\>:\<else\%(if\)\=\>:\<endif\>,' .
  846.   \ '\<!loopondimensions\>\|\<!looponselected\>:\<!endloop\>'
  847.   \ | endif
  848.   " HTML:  thanks to Johannes Zellner.
  849.     au FileType html,jsp,php if !exists("b:match_words") |
  850.       \    let b:match_ignorecase = 1 |
  851.       \    let b:match_skip = 's:Comment' |
  852.       \ let b:match_words = '<:>,' .
  853.       \ '<\@<=[ou]l[^>]*\%(>\|$\):<\@<=li>:<\@<=/[ou]l>,' .
  854.       \ '<\@<=\([^/][^ \t>]*\)[^>]*\%(>\|$\):<\@<=/\1>'
  855.       \ | endif
  856.   " Pascal:
  857.   au FileType pascal if !exists("b:match_words") |
  858.     \ let b:match_words='\<begin\>:\<end\>'
  859.     \ | endif
  860.   " SGML:  see XML
  861.   " Shell:  thanks to Johannes Zellner
  862.   let s:sol = '\%(;\s*\|^\s*\)\@<='  " start of line
  863.   au FileType sh,config if !exists("b:match_words") |
  864.     \ let b:match_words =
  865.       \ s:sol.'if\>:' . s:sol.'elif\>:' . s:sol.'else\>:' . s:sol. 'fi\>,' .
  866.       \ s:sol.'\%(for\|while\)\>:' . s:sol. 'done\>,' .
  867.       \ s:sol.'case\>:' . s:sol. 'esac\>'
  868.       \ | endif
  869.   " RPM Spec:  thanks to Max Ischenko
  870.   au FileType spec if !exists("b:match_words") |
  871.     \ let b:match_ignorecase = 0 | let b:match_words =
  872.     \ '^Name:^%description:^%clean:^%setup:^%build:^%install:^%files:' .
  873.     \ '^%package:^%preun:^%postun:^%changelog'
  874.     \ | endif
  875.   " Tcsh:  see Csh
  876.   " XML:  thanks to Johannes Zellner and Akbar Ibrahim
  877.   " - case sensitive
  878.   " - don't match empty tags <fred/>
  879.   " - match <!--, --> style comments (but not --, --)
  880.   " - match <!, > inlined dtd's. This is not perfect, as it
  881.   "   gets confused for example by
  882.   "       <!ENTITY gt ">">
  883.   au! FileType xml,sgml,entity,xslt,svg,xhtml,xsl,xsd
  884.     \ if !exists("b:match_words") |
  885.     \ let b:match_ignorecase=0 | let b:match_words =
  886.     \ '<:>,' .
  887.     \ '<\@<=!\[CDATA\[:]]>,'.
  888.     \ '<\@<=!--:-->,'.
  889.     \ '<\@<=?\k\+:?>,'.
  890.     \ '<\@<=\([^ \t>/]\+\)\%(\s\+[^>]*\%([^/]>\|$\)\|>\|$\):<\@<=/\1>,'.
  891.     \ '<\@<=\%([^ \t>/]\+\)\%(\s\+[^/>]*\|$\):/>'
  892.     \ | endif
  893.  
  894. aug END
  895.  
  896. let &cpo = s:save_cpo
  897.  
  898. " vim:sts=2:sw=2:ff=unix:
  899.