1"  matchit.vim: (global plugin) Extended "%" matching
2"  Last Change: 2017 Sep 15
3"  Maintainer:  Benji Fisher PhD   <[email protected]>
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
42if exists("loaded_matchit") || &cp
43  finish
44endif
45let loaded_matchit = 1
46let s:last_mps = ""
47let s:last_words = ":"
48let s:patBR = ""
49
50let s:save_cpo = &cpo
51set cpo&vim
52
53nnoremap <silent> %  :<C-U>call <SID>Match_wrapper('',1,'n') <CR>
54nnoremap <silent> g% :<C-U>call <SID>Match_wrapper('',0,'n') <CR>
55vnoremap <silent> %  :<C-U>call <SID>Match_wrapper('',1,'v') <CR>m'gv``
56vnoremap <silent> g% :<C-U>call <SID>Match_wrapper('',0,'v') <CR>m'gv``
57onoremap <silent> %  v:<C-U>call <SID>Match_wrapper('',1,'o') <CR>
58onoremap <silent> g% v:<C-U>call <SID>Match_wrapper('',0,'o') <CR>
59
60" Analogues of [{ and ]} using matching patterns:
61nnoremap <silent> [% :<C-U>call <SID>MultiMatch("bW", "n") <CR>
62nnoremap <silent> ]% :<C-U>call <SID>MultiMatch("W",  "n") <CR>
63vmap [% <Esc>[%m'gv``
64vmap ]% <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``
67onoremap <silent> [% v:<C-U>call <SID>MultiMatch("bW", "o") <CR>
68onoremap <silent> ]% v:<C-U>call <SID>MultiMatch("W",  "o") <CR>
69
70" text object:
71vmap 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
88let s:notslash = '\\\@<!\%(\\\\\)*'
89
90function! 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)
288endfun
289
290" Restore options and do some special handling for Operator-pending mode.
291" The optional argument is the tail of the matching group.
292fun! 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
319endfun
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>" .
328fun! 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
382endfun
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\)'
388fun! 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
411endfun
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)
424fun! 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
433endfun
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")
444fun! 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
484endfun
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).
490fun! 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
507endfun
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 "\".
516fun! 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
562endfun
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.
569fun! 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
603endfun
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.
608if !exists(":MatchDebug")
609  command! -nargs=0 MatchDebug call s:Match_debug()
610endif
611
612fun! 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>
632endfun
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.
643fun! 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
739endfun
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
800fun! 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
816endfun
817
818let &cpo = s:save_cpo
819unlet s:save_cpo
820
821" vim:sts=2:sw=2:
822