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