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