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