Update Help menu
[MacVim.git] / runtime / macros / matchit.vim
blobe41cda9e17a534177e2092e48aa6b5f23de86046
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
7 " Documentation:
8 "  The documentation is in a separate file, matchit.txt .
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.
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.
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.
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 = ":"
47 let s:save_cpo = &cpo
48 set cpo&vim
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>
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>
67 " text object:
68 vmap a% <Esc>[%v]%
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!"
85 let s:notslash = '\\\@<!\%(\\\\\)*'
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.
104   if v:count
105     exe "normal! " . v:count . "%"
106     return s:CleanUp(restore_options, a:mode, startline, startcol)
107   end
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
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
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
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
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)
282 endfun
284 " Restore options and do some special handling for Operator-pending mode.
285 " The optional argument is the tail of the matching group.
286 fun! 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
311 endfun
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>" .
320 fun! 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
374 endfun
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\)'
380 fun! 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
403 endfun
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)
416 fun! 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
425 endfun
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")
436 fun! 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
476 endfun
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).
482 fun! 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
499 endfun
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 "\".
508 fun! 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
554 endfun
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.
561 fun! 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
595 endfun
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.
600 if !exists(":MatchDebug")
601   command! -nargs=0 MatchDebug call s:Match_debug()
602 endif
604 fun! 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>
624 endfun
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.
635 fun! 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(".")
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
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
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
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
731 endfun
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")
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
772 "   " - TODO:  Construct the closing from group.
773 "   let fake = "end" . expand("<cword>")
774 "   execute startpos
775 "   return fake
776 " endfun
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
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
792 fun! 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
808 endfun
810 let &cpo = s:save_cpo
812 " vim:sts=2:sw=2: