blob: 7fcc53e803254570fa9e776930817c9b0232ccdc [file] [log] [blame]
Bram Moolenaar071d4272004-06-13 20:20:40 +00001" matchit.vim: (global plugin) Extended "%" matching
Bram Moolenaarc81e5e72007-05-05 18:24:42 +00002" Last Change: Mon May 15 10:00 PM 2006 EDT
Bram Moolenaar071d4272004-06-13 20:20:40 +00003" Maintainer: Benji Fisher PhD <benji@member.AMS.org>
Bram Moolenaarc81e5e72007-05-05 18:24:42 +00004" Version: 1.11, for Vim 6.3+
Bram Moolenaar071d4272004-06-13 20:20:40 +00005" 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
Bram Moolenaarc81e5e72007-05-05 18:24:42 +000018" Collett, Stephen Wall, Dany St-Amant, Yuheng Xie, and Johannes Zellner.
Bram Moolenaar071d4272004-06-13 20:20:40 +000019
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)
Bram Moolenaarc81e5e72007-05-05 18:24:42 +0000175 let endcol = matchend(matchline, regexp)
176 let suf = strlen(matchline) - endcol
177 let prefix = (curcol ? '^.*\%' . (curcol + 1) . 'c\%(' : '^\%(')
178 let suffix = (suf ? '\)\%' . (endcol + 1) . 'c.*$' : '\)$')
Bram Moolenaar071d4272004-06-13 20:20:40 +0000179 " If the match comes from the defaults, bail out.
180 if matchline !~ prefix .
181 \ substitute(s:pat, s:notslash.'\zs[,:]\+', '\\|', 'g') . suffix
182 silent! norm! %
183 return s:CleanUp(restore_options, a:mode, startline, startcol)
184 endif
185 endif
186 if exists("b:match_debug")
187 let b:match_match = matchstr(matchline, regexp)
188 let b:match_col = curcol+1
189 endif
190
191 " Third step: Find the group and single word that match, and the original
192 " (backref) versions of these. Then, resolve the backrefs.
193 " Set the following local variable:
194 " group = colon-separated list of patterns, one of which matches
195 " = ini:mid:fin or ini:fin
196 "
197 " Reconstruct the version with unresolved backrefs.
198 let patBR = substitute(match_words.',',
199 \ s:notslash.'\zs[,:]*,[,:]*', ',', 'g')
200 let patBR = substitute(patBR, s:notslash.'\zs:\{2,}', ':', 'g')
201 " Now, set group and groupBR to the matching group: 'if:endif' or
202 " 'while:endwhile' or whatever. A bit of a kluge: s:Choose() returns
203 " group . "," . groupBR, and we pick it apart.
204 let group = s:Choose(s:pat, matchline, ",", ":", prefix, suffix, patBR)
205 let i = matchend(group, s:notslash . ",")
206 let groupBR = strpart(group, i)
207 let group = strpart(group, 0, i-1)
208 " Now, matchline =~ prefix . substitute(group,':','\|','g') . suffix
209 if s:do_BR " Do the hard part: resolve those backrefs!
210 let group = s:InsertRefs(groupBR, prefix, group, suffix, matchline)
211 endif
212 if exists("b:match_debug")
213 let b:match_wholeBR = groupBR
214 let i = matchend(groupBR, s:notslash . ":")
215 let b:match_iniBR = strpart(groupBR, 0, i-1)
216 endif
217
218 " Fourth step: Set the arguments for searchpair().
219 let i = matchend(group, s:notslash . ":")
220 let j = matchend(group, '.*' . s:notslash . ":")
221 let ini = strpart(group, 0, i-1)
222 let mid = substitute(strpart(group, i,j-i-1), s:notslash.'\zs:', '\\|', 'g')
223 let fin = strpart(group, j)
Bram Moolenaar76b92b22006-03-24 22:46:53 +0000224 "Un-escape the remaining , and : characters.
225 let ini = substitute(ini, s:notslash . '\zs\\\(:\|,\)', '\1', 'g')
226 let mid = substitute(mid, s:notslash . '\zs\\\(:\|,\)', '\1', 'g')
227 let fin = substitute(fin, s:notslash . '\zs\\\(:\|,\)', '\1', 'g')
Bram Moolenaar071d4272004-06-13 20:20:40 +0000228 " searchpair() requires that these patterns avoid \(\) groups.
229 let ini = substitute(ini, s:notslash . '\zs\\(', '\\%(', 'g')
230 let mid = substitute(mid, s:notslash . '\zs\\(', '\\%(', 'g')
231 let fin = substitute(fin, s:notslash . '\zs\\(', '\\%(', 'g')
232 " Set mid. 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 mid = ""
236 endif
237 " Set flag. This is optimized for readability, not micro-efficiency!
238 if a:forward && matchline =~ prefix . fin . suffix
239 \ || !a:forward && matchline !~ prefix . ini . suffix
240 let flag = "bW"
241 else
242 let flag = "W"
243 endif
244 " Set skip.
245 if exists("b:match_skip")
246 let skip = b:match_skip
247 elseif exists("b:match_comment") " backwards compatibility and testing!
248 let skip = "r:" . b:match_comment
249 else
250 let skip = 's:comment\|string'
251 endif
252 let skip = s:ParseSkip(skip)
253 if exists("b:match_debug")
254 let b:match_ini = ini
255 let b:match_tail = (strlen(mid) ? mid.'\|' : '') . fin
256 endif
257
258 " Fifth step: actually start moving the cursor and call searchpair().
259 " Later, :execute restore_cursor to get to the original screen.
260 let restore_cursor = virtcol(".") . "|"
261 normal! g0
262 let restore_cursor = line(".") . "G" . virtcol(".") . "|zs" . restore_cursor
263 normal! H
264 let restore_cursor = "normal!" . line(".") . "Gzt" . restore_cursor
265 execute restore_cursor
Bram Moolenaarc81e5e72007-05-05 18:24:42 +0000266 call cursor(0, curcol + 1)
267 " normal! 0
268 " if curcol
269 " execute "normal!" . curcol . "l"
270 " endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000271 if skip =~ 'synID' && !(has("syntax") && exists("g:syntax_on"))
272 let skip = "0"
273 else
274 execute "if " . skip . "| let skip = '0' | endif"
275 endif
276 let sp_return = searchpair(ini, mid, fin, flag, skip)
277 let final_position = "call cursor(" . line(".") . "," . col(".") . ")"
278 " Restore cursor position and original screen.
279 execute restore_cursor
280 normal! m'
281 if sp_return > 0
282 execute final_position
283 endif
284 return s:CleanUp(restore_options, a:mode, startline, startcol, mid.'\|'.fin)
285endfun
286
287" Restore options and do some special handling for Operator-pending mode.
288" The optional argument is the tail of the matching group.
289fun! s:CleanUp(options, mode, startline, startcol, ...)
290 execute "set" a:options
291 " Open folds, if appropriate.
292 if a:mode != "o"
293 if &foldopen =~ "percent"
294 normal! zv
295 endif
296 " In Operator-pending mode, we want to include the whole match
297 " (for example, d%).
298 " This is only a problem if we end up moving in the forward direction.
299 elseif (a:startline < line(".")) ||
300 \ (a:startline == line(".") && a:startcol < col("."))
301 if a:0
302 " Check whether the match is a single character. If not, move to the
303 " end of the match.
304 let matchline = getline(".")
305 let currcol = col(".")
306 let regexp = s:Wholematch(matchline, a:1, currcol-1)
307 let endcol = matchend(matchline, regexp)
308 if endcol > currcol " This is NOT off by one!
309 execute "normal!" . (endcol - currcol) . "l"
310 endif
311 endif " a:0
312 endif " a:mode != "o" && etc.
313 return 0
314endfun
315
316" Example (simplified HTML patterns): if
317" a:groupBR = '<\(\k\+\)>:</\1>'
318" a:prefix = '^.\{3}\('
319" a:group = '<\(\k\+\)>:</\(\k\+\)>'
320" a:suffix = '\).\{2}$'
321" a:matchline = "123<tag>12" or "123</tag>12"
322" then extract "tag" from a:matchline and return "<tag>:</tag>" .
323fun! s:InsertRefs(groupBR, prefix, group, suffix, matchline)
324 if a:matchline !~ a:prefix .
325 \ substitute(a:group, s:notslash . '\zs:', '\\|', 'g') . a:suffix
326 return a:group
327 endif
328 let i = matchend(a:groupBR, s:notslash . ':')
329 let ini = strpart(a:groupBR, 0, i-1)
330 let tailBR = strpart(a:groupBR, i)
331 let word = s:Choose(a:group, a:matchline, ":", "", a:prefix, a:suffix,
332 \ a:groupBR)
333 let i = matchend(word, s:notslash . ":")
334 let wordBR = strpart(word, i)
335 let word = strpart(word, 0, i-1)
336 " Now, a:matchline =~ a:prefix . word . a:suffix
337 if wordBR != ini
338 let table = s:Resolve(ini, wordBR, "table")
339 else
340 " let table = "----------"
341 let table = ""
342 let d = 0
343 while d < 10
344 if tailBR =~ s:notslash . '\\' . d
345 " let table[d] = d
346 let table = table . d
347 else
348 let table = table . "-"
349 endif
350 let d = d + 1
351 endwhile
352 endif
353 let d = 9
354 while d
355 if table[d] != "-"
356 let backref = substitute(a:matchline, a:prefix.word.a:suffix,
357 \ '\'.table[d], "")
358 " Are there any other characters that should be escaped?
359 let backref = escape(backref, '*,:')
360 execute s:Ref(ini, d, "start", "len")
361 let ini = strpart(ini, 0, start) . backref . strpart(ini, start+len)
362 let tailBR = substitute(tailBR, s:notslash . '\zs\\' . d,
363 \ escape(backref, '\\'), 'g')
364 endif
365 let d = d-1
366 endwhile
367 if exists("b:match_debug")
368 if s:do_BR
369 let b:match_table = table
370 let b:match_word = word
371 else
372 let b:match_table = ""
373 let b:match_word = ""
374 endif
375 endif
376 return ini . ":" . tailBR
377endfun
378
379" Input a comma-separated list of groups with backrefs, such as
380" a:groups = '\(foo\):end\1,\(bar\):end\1'
381" and return a comma-separated list of groups with backrefs replaced:
382" return '\(foo\):end\(foo\),\(bar\):end\(bar\)'
383fun! s:ParseWords(groups)
384 let groups = substitute(a:groups.",", s:notslash.'\zs[,:]*,[,:]*', ',', 'g')
385 let groups = substitute(groups, s:notslash . '\zs:\{2,}', ':', 'g')
386 let parsed = ""
387 while groups =~ '[^,:]'
388 let i = matchend(groups, s:notslash . ':')
389 let j = matchend(groups, s:notslash . ',')
390 let ini = strpart(groups, 0, i-1)
391 let tail = strpart(groups, i, j-i-1) . ":"
392 let groups = strpart(groups, j)
393 let parsed = parsed . ini
394 let i = matchend(tail, s:notslash . ':')
395 while i != -1
396 " In 'if:else:endif', ini='if' and word='else' and then word='endif'.
397 let word = strpart(tail, 0, i-1)
398 let tail = strpart(tail, i)
399 let i = matchend(tail, s:notslash . ':')
400 let parsed = parsed . ":" . s:Resolve(ini, word, "word")
401 endwhile " Now, tail has been used up.
402 let parsed = parsed . ","
403 endwhile " groups =~ '[^,:]'
404 return parsed
405endfun
406
407" TODO I think this can be simplified and/or made more efficient.
408" TODO What should I do if a:start is out of range?
409" Return a regexp that matches all of a:string, such that
410" matchstr(a:string, regexp) represents the match for a:pat that starts
411" as close to a:start as possible, before being preferred to after, and
412" ends after a:start .
413" Usage:
414" let regexp = s:Wholematch(getline("."), 'foo\|bar', col(".")-1)
415" let i = match(getline("."), regexp)
416" let j = matchend(getline("."), regexp)
417" let match = matchstr(getline("."), regexp)
418fun! s:Wholematch(string, pat, start)
419 let group = '\%(' . a:pat . '\)'
Bram Moolenaarc81e5e72007-05-05 18:24:42 +0000420 let prefix = (a:start ? '\(^.*\%<' . (a:start + 2) . 'c\)\zs' : '^')
Bram Moolenaar071d4272004-06-13 20:20:40 +0000421 let len = strlen(a:string)
Bram Moolenaarc81e5e72007-05-05 18:24:42 +0000422 let suffix = (a:start+1 < len ? '\(\%>'.(a:start+1).'c.*$\)\@=' : '$')
Bram Moolenaar071d4272004-06-13 20:20:40 +0000423 if a:string !~ prefix . group . suffix
424 let prefix = ''
425 endif
426 return prefix . group . suffix
427endfun
428
429" No extra arguments: s:Ref(string, d) will
430" find the d'th occurrence of '\(' and return it, along with everything up
431" to and including the matching '\)'.
432" One argument: s:Ref(string, d, "start") returns the index of the start
433" of the d'th '\(' and any other argument returns the length of the group.
434" Two arguments: s:Ref(string, d, "foo", "bar") returns a string to be
435" executed, having the effect of
436" :let foo = s:Ref(string, d, "start")
437" :let bar = s:Ref(string, d, "len")
438fun! s:Ref(string, d, ...)
439 let len = strlen(a:string)
440 if a:d == 0
441 let start = 0
442 else
443 let cnt = a:d
444 let match = a:string
445 while cnt
446 let cnt = cnt - 1
447 let index = matchend(match, s:notslash . '\\(')
448 if index == -1
449 return ""
450 endif
451 let match = strpart(match, index)
452 endwhile
453 let start = len - strlen(match)
454 if a:0 == 1 && a:1 == "start"
455 return start - 2
456 endif
457 let cnt = 1
458 while cnt
459 let index = matchend(match, s:notslash . '\\(\|\\)') - 1
460 if index == -2
461 return ""
462 endif
463 " Increment if an open, decrement if a ')':
464 let cnt = cnt + (match[index]=="(" ? 1 : -1) " ')'
465 " let cnt = stridx('0(', match[index]) + cnt
466 let match = strpart(match, index+1)
467 endwhile
468 let start = start - 2
469 let len = len - start - strlen(match)
470 endif
471 if a:0 == 1
472 return len
473 elseif a:0 == 2
474 return "let " . a:1 . "=" . start . "| let " . a:2 . "=" . len
475 else
476 return strpart(a:string, start, len)
477 endif
478endfun
479
480" Count the number of disjoint copies of pattern in string.
481" If the pattern is a literal string and contains no '0' or '1' characters
482" then s:Count(string, pattern, '0', '1') should be faster than
483" s:Count(string, pattern).
484fun! s:Count(string, pattern, ...)
485 let pat = escape(a:pattern, '\\')
486 if a:0 > 1
487 let foo = substitute(a:string, '[^'.a:pattern.']', "a:1", "g")
488 let foo = substitute(a:string, pat, a:2, "g")
489 let foo = substitute(foo, '[^' . a:2 . ']', "", "g")
490 return strlen(foo)
491 endif
492 let result = 0
493 let foo = a:string
494 let index = matchend(foo, pat)
495 while index != -1
496 let result = result + 1
497 let foo = strpart(foo, index)
498 let index = matchend(foo, pat)
499 endwhile
500 return result
501endfun
502
503" s:Resolve('\(a\)\(b\)', '\(c\)\2\1\1\2') should return table.word, where
504" word = '\(c\)\(b\)\(a\)\3\2' and table = '-32-------'. That is, the first
505" '\1' in target is replaced by '\(a\)' in word, table[1] = 3, and this
506" indicates that all other instances of '\1' in target are to be replaced
507" by '\3'. The hard part is dealing with nesting...
508" Note that ":" is an illegal character for source and target,
509" unless it is preceded by "\".
510fun! s:Resolve(source, target, output)
511 let word = a:target
512 let i = matchend(word, s:notslash . '\\\d') - 1
513 let table = "----------"
514 while i != -2 " There are back references to be replaced.
515 let d = word[i]
516 let backref = s:Ref(a:source, d)
517 " The idea is to replace '\d' with backref. Before we do this,
518 " replace any \(\) groups in backref with :1, :2, ... if they
519 " correspond to the first, second, ... group already inserted
520 " into backref. Later, replace :1 with \1 and so on. The group
521 " number w+b within backref corresponds to the group number
522 " s within a:source.
523 " w = number of '\(' in word before the current one
524 let w = s:Count(
525 \ substitute(strpart(word, 0, i-1), '\\\\', '', 'g'), '\(', '1')
526 let b = 1 " number of the current '\(' in backref
527 let s = d " number of the current '\(' in a:source
528 while b <= s:Count(substitute(backref, '\\\\', '', 'g'), '\(', '1')
529 \ && s < 10
530 if table[s] == "-"
531 if w + b < 10
532 " let table[s] = w + b
533 let table = strpart(table, 0, s) . (w+b) . strpart(table, s+1)
534 endif
535 let b = b + 1
536 let s = s + 1
537 else
538 execute s:Ref(backref, b, "start", "len")
539 let ref = strpart(backref, start, len)
540 let backref = strpart(backref, 0, start) . ":". table[s]
541 \ . strpart(backref, start+len)
542 let s = s + s:Count(substitute(ref, '\\\\', '', 'g'), '\(', '1')
543 endif
544 endwhile
545 let word = strpart(word, 0, i-1) . backref . strpart(word, i+1)
546 let i = matchend(word, s:notslash . '\\\d') - 1
547 endwhile
548 let word = substitute(word, s:notslash . '\zs:', '\\', 'g')
549 if a:output == "table"
550 return table
551 elseif a:output == "word"
552 return word
553 else
554 return table . word
555 endif
556endfun
557
558" Assume a:comma = ",". Then the format for a:patterns and a:1 is
559" a:patterns = "<pat1>,<pat2>,..."
560" a:1 = "<alt1>,<alt2>,..."
561" If <patn> is the first pattern that matches a:string then return <patn>
562" if no optional arguments are given; return <patn>,<altn> if a:1 is given.
563fun! s:Choose(patterns, string, comma, branch, prefix, suffix, ...)
564 let tail = (a:patterns =~ a:comma."$" ? a:patterns : a:patterns . a:comma)
565 let i = matchend(tail, s:notslash . a:comma)
566 if a:0
567 let alttail = (a:1 =~ a:comma."$" ? a:1 : a:1 . a:comma)
568 let j = matchend(alttail, s:notslash . a:comma)
569 endif
570 let current = strpart(tail, 0, i-1)
571 if a:branch == ""
572 let currpat = current
573 else
Bram Moolenaar76b92b22006-03-24 22:46:53 +0000574 let currpat = substitute(current, s:notslash . a:branch, '\\|', 'g')
Bram Moolenaar071d4272004-06-13 20:20:40 +0000575 endif
576 while a:string !~ a:prefix . currpat . a:suffix
577 let tail = strpart(tail, i)
578 let i = matchend(tail, s:notslash . a:comma)
579 if i == -1
580 return -1
581 endif
582 let current = strpart(tail, 0, i-1)
583 if a:branch == ""
584 let currpat = current
585 else
Bram Moolenaar76b92b22006-03-24 22:46:53 +0000586 let currpat = substitute(current, s:notslash . a:branch, '\\|', 'g')
Bram Moolenaar071d4272004-06-13 20:20:40 +0000587 endif
588 if a:0
589 let alttail = strpart(alttail, j)
590 let j = matchend(alttail, s:notslash . a:comma)
591 endif
592 endwhile
593 if a:0
594 let current = current . a:comma . strpart(alttail, 0, j-1)
595 endif
596 return current
597endfun
598
599" Call this function to turn on debugging information. Every time the main
600" script is run, buffer variables will be saved. These can be used directly
601" or viewed using the menu items below.
602if !exists(":MatchDebug")
603 command! -nargs=0 MatchDebug call s:Match_debug()
604endif
605
606fun! s:Match_debug()
607 let b:match_debug = 1 " Save debugging information.
608 " pat = all of b:match_words with backrefs parsed
609 amenu &Matchit.&pat :echo b:match_pat<CR>
610 " match = bit of text that is recognized as a match
611 amenu &Matchit.&match :echo b:match_match<CR>
612 " curcol = cursor column of the start of the matching text
613 amenu &Matchit.&curcol :echo b:match_col<CR>
614 " wholeBR = matching group, original version
615 amenu &Matchit.wh&oleBR :echo b:match_wholeBR<CR>
616 " iniBR = 'if' piece, original version
617 amenu &Matchit.ini&BR :echo b:match_iniBR<CR>
618 " ini = 'if' piece, with all backrefs resolved from match
619 amenu &Matchit.&ini :echo b:match_ini<CR>
620 " tail = 'else\|endif' piece, with all backrefs resolved from match
621 amenu &Matchit.&tail :echo b:match_tail<CR>
622 " fin = 'endif' piece, with all backrefs resolved from match
623 amenu &Matchit.&word :echo b:match_word<CR>
624 " '\'.d in ini refers to the same thing as '\'.table[d] in word.
625 amenu &Matchit.t&able :echo '0:' . b:match_table . ':9'<CR>
626endfun
627
628" Jump to the nearest unmatched "(" or "if" or "<tag>" if a:spflag == "bW"
629" or the nearest unmatched "</tag>" or "endif" or ")" if a:spflag == "W".
630" Return a "mark" for the original position, so that
631" let m = MultiMatch("bW", "n") ... execute m
632" will return to the original position. If there is a problem, do not
633" move the cursor and return "", unless a count is given, in which case
634" go up or down as many levels as possible and again return "".
635" TODO This relies on the same patterns as % matching. It might be a good
636" idea to give it its own matching patterns.
637fun! s:MultiMatch(spflag, mode)
638 if !exists("b:match_words") || b:match_words == ""
639 return ""
640 end
641 let restore_options = (&ic ? "" : "no") . "ignorecase"
642 if exists("b:match_ignorecase")
643 let &ignorecase = b:match_ignorecase
644 endif
645 let startline = line(".")
646 let startcol = col(".")
647
648 " First step: if not already done, set the script variables
649 " s:do_BR flag for whether there are backrefs
650 " s:pat parsed version of b:match_words
651 " s:all regexp based on s:pat and the default groups
652 " This part is copied and slightly modified from s:Match_wrapper().
653 let default = escape(&mps, '[$^.*~\\/?]') . (strlen(&mps) ? "," : "") .
654 \ '\/\*:\*\/,#if\%(def\)\=:$else\>:#elif\>:#endif\>'
655 " Allow b:match_words = "GetVimMatchWords()" .
656 if b:match_words =~ ":"
657 let match_words = b:match_words
658 else
659 execute "let match_words =" b:match_words
660 endif
661 if (match_words != s:last_words) || (&mps != s:last_mps) ||
662 \ exists("b:match_debug")
663 let s:last_words = match_words
664 let s:last_mps = &mps
665 if match_words !~ s:notslash . '\\\d'
666 let s:do_BR = 0
667 let s:pat = match_words
668 else
669 let s:do_BR = 1
670 let s:pat = s:ParseWords(match_words)
671 endif
672 let s:all = '\%(' . substitute(s:pat . (strlen(s:pat)?",":"") . default,
673 \ '[,:]\+','\\|','g') . '\)'
674 if exists("b:match_debug")
675 let b:match_pat = s:pat
676 endif
677 endif
678
679 " Second step: figure out the patterns for searchpair()
680 " and save the screen, cursor position, and 'ignorecase'.
681 " - TODO: A lot of this is copied from s:Match_wrapper().
682 " - maybe even more functionality should be split off
683 " - into separate functions!
684 let cdefault = (s:pat =~ '[^,]$' ? "," : "") . default
685 let open = substitute(s:pat . cdefault, ':[^,]*,', '\\),\\(', 'g')
686 let open = '\(' . substitute(open, ':[^,]*$', '\\)', '')
687 let close = substitute(s:pat . cdefault, ',[^,]*:', '\\),\\(', 'g')
688 let close = substitute(close, '[^,]*:', '\\(', '') . '\)'
689 if exists("b:match_skip")
690 let skip = b:match_skip
691 elseif exists("b:match_comment") " backwards compatibility and testing!
692 let skip = "r:" . b:match_comment
693 else
694 let skip = 's:comment\|string'
695 endif
696 let skip = s:ParseSkip(skip)
697 " let restore_cursor = line(".") . "G" . virtcol(".") . "|"
698 " normal! H
699 " let restore_cursor = "normal!" . line(".") . "Gzt" . restore_cursor
700 let restore_cursor = virtcol(".") . "|"
701 normal! g0
702 let restore_cursor = line(".") . "G" . virtcol(".") . "|zs" . restore_cursor
703 normal! H
704 let restore_cursor = "normal!" . line(".") . "Gzt" . restore_cursor
705 execute restore_cursor
706
707 " Third step: call searchpair().
708 " Replace '\('--but not '\\('--with '\%(' and ',' with '\|'.
709 let openpat = substitute(open, '\(\\\@<!\(\\\\\)*\)\@<=\\(', '\\%(', 'g')
710 let openpat = substitute(openpat, ',', '\\|', 'g')
711 let closepat = substitute(close, '\(\\\@<!\(\\\\\)*\)\@<=\\(', '\\%(', 'g')
712 let closepat = substitute(closepat, ',', '\\|', 'g')
713 if skip =~ 'synID' && !(has("syntax") && exists("g:syntax_on"))
714 let skip = '0'
715 else
716 execute "if " . skip . "| let skip = '0' | endif"
717 endif
718 mark '
719 let level = v:count1
720 while level
721 if searchpair(openpat, '', closepat, a:spflag, skip) < 1
722 call s:CleanUp(restore_options, a:mode, startline, startcol)
723 return ""
724 endif
725 let level = level - 1
726 endwhile
727
728 " Restore options and return a string to restore the original position.
729 call s:CleanUp(restore_options, a:mode, startline, startcol)
730 return restore_cursor
731endfun
732
733" Search backwards for "if" or "while" or "<tag>" or ...
734" and return "endif" or "endwhile" or "</tag>" or ... .
735" For now, this uses b:match_words and the same script variables
736" as s:Match_wrapper() . Later, it may get its own patterns,
737" either from a buffer variable or passed as arguments.
738" fun! s:Autocomplete()
739" echo "autocomplete not yet implemented :-("
740" if !exists("b:match_words") || b:match_words == ""
741" return ""
742" end
743" let startpos = s:MultiMatch("bW")
744"
745" if startpos == ""
746" return ""
747" endif
748" " - TODO: figure out whether 'if' or '<tag>' matched, and construct
749" " - the appropriate closing.
750" let matchline = getline(".")
751" let curcol = col(".") - 1
752" " - TODO: Change the s:all argument if there is a new set of match pats.
753" let regexp = s:Wholematch(matchline, s:all, curcol)
754" let suf = strlen(matchline) - matchend(matchline, regexp)
755" let prefix = (curcol ? '^.\{' . curcol . '}\%(' : '^\%(')
756" let suffix = (suf ? '\).\{' . suf . '}$' : '\)$')
757" " Reconstruct the version with unresolved backrefs.
758" let patBR = substitute(b:match_words.',', '[,:]*,[,:]*', ',', 'g')
759" let patBR = substitute(patBR, ':\{2,}', ':', "g")
760" " Now, set group and groupBR to the matching group: 'if:endif' or
761" " 'while:endwhile' or whatever.
762" let group = s:Choose(s:pat, matchline, ",", ":", prefix, suffix, patBR)
763" let i = matchend(group, s:notslash . ",")
764" let groupBR = strpart(group, i)
765" let group = strpart(group, 0, i-1)
766" " Now, matchline =~ prefix . substitute(group,':','\|','g') . suffix
767" if s:do_BR
768" let group = s:InsertRefs(groupBR, prefix, group, suffix, matchline)
769" endif
770" " let g:group = group
771"
772" " - TODO: Construct the closing from group.
773" let fake = "end" . expand("<cword>")
774" execute startpos
775" return fake
776" endfun
777
778" Close all open structures. "Get the heck out of here!"
779" fun! s:Gthhoh()
780" let close = s:Autocomplete()
781" while strlen(close)
782" put=close
783" let close = s:Autocomplete()
784" endwhile
785" endfun
786
787" Parse special strings as typical skip arguments for searchpair():
788" s:foo becomes (current syntax item) =~ foo
789" S:foo becomes (current syntax item) !~ foo
790" r:foo becomes (line before cursor) =~ foo
791" R:foo becomes (line before cursor) !~ foo
792fun! s:ParseSkip(str)
793 let skip = a:str
794 if skip[1] == ":"
795 if skip[0] == "s"
796 let skip = "synIDattr(synID(line('.'),col('.'),1),'name') =~? '" .
797 \ strpart(skip,2) . "'"
798 elseif skip[0] == "S"
799 let skip = "synIDattr(synID(line('.'),col('.'),1),'name') !~? '" .
800 \ strpart(skip,2) . "'"
801 elseif skip[0] == "r"
802 let skip = "strpart(getline('.'),0,col('.'))=~'" . strpart(skip,2). "'"
803 elseif skip[0] == "R"
804 let skip = "strpart(getline('.'),0,col('.'))!~'" . strpart(skip,2). "'"
805 endif
806 endif
807 return skip
808endfun
809
810let &cpo = s:save_cpo
811
812" vim:sts=2:sw=2: