blob: 549c26cf31d7044af06bd4d40878c4feb96f21fc [file] [log] [blame]
Bram Moolenaar071d4272004-06-13 20:20:40 +00001" matchit.vim: (global plugin) Extended "%" matching
Bram Moolenaar864207d2008-06-24 22:14:38 +00002" Last Change: Fri Jan 25 10:00 AM 2008 EST
Bram Moolenaar071d4272004-06-13 20:20:40 +00003" Maintainer: Benji Fisher PhD <benji@member.AMS.org>
Bram Moolenaar864207d2008-06-24 22:14:38 +00004" Version: 1.13.2, 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 = ""
Bram Moolenaar864207d2008-06-24 22:14:38 +000045let s:last_words = ":"
Bram Moolenaar071d4272004-06-13 20:20:40 +000046
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(".")
Bram Moolenaar864207d2008-06-24 22:14:38 +0000103 " Use default behavior if called with a count.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000104 if v:count
105 exe "normal! " . v:count . "%"
106 return s:CleanUp(restore_options, a:mode, startline, startcol)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000107 end
108
109 " First step: if not already done, set the script variables
110 " s:do_BR flag for whether there are backrefs
111 " s:pat parsed version of b:match_words
112 " s:all regexp based on s:pat and the default groups
113 "
Bram Moolenaar864207d2008-06-24 22:14:38 +0000114 if !exists("b:match_words") || b:match_words == ""
115 let match_words = ""
116 " Allow b:match_words = "GetVimMatchWords()" .
117 elseif b:match_words =~ ":"
Bram Moolenaar071d4272004-06-13 20:20:40 +0000118 let match_words = b:match_words
119 else
120 execute "let match_words =" b:match_words
121 endif
122" Thanks to Preben "Peppe" Guldberg and Bram Moolenaar for this suggestion!
123 if (match_words != s:last_words) || (&mps != s:last_mps) ||
124 \ exists("b:match_debug")
125 let s:last_words = match_words
126 let s:last_mps = &mps
Bram Moolenaar071d4272004-06-13 20:20:40 +0000127 " The next several lines were here before
128 " BF started messing with this script.
129 " quote the special chars in 'matchpairs', replace [,:] with \| and then
130 " append the builtin pairs (/*, */, #if, #ifdef, #else, #elif, #endif)
131 " let default = substitute(escape(&mps, '[$^.*~\\/?]'), '[,:]\+',
132 " \ '\\|', 'g').'\|\/\*\|\*\/\|#if\>\|#ifdef\>\|#else\>\|#elif\>\|#endif\>'
133 let default = escape(&mps, '[$^.*~\\/?]') . (strlen(&mps) ? "," : "") .
134 \ '\/\*:\*\/,#if\%(def\)\=:#else\>:#elif\>:#endif\>'
135 " s:all = pattern with all the keywords
Bram Moolenaar864207d2008-06-24 22:14:38 +0000136 let match_words = match_words . (strlen(match_words) ? "," : "") . default
137 if match_words !~ s:notslash . '\\\d'
138 let s:do_BR = 0
139 let s:pat = match_words
140 else
141 let s:do_BR = 1
142 let s:pat = s:ParseWords(match_words)
143 endif
144 let s:all = substitute(s:pat, s:notslash . '\zs[,:]\+', '\\|', 'g')
Bram Moolenaar071d4272004-06-13 20:20:40 +0000145 let s:all = '\%(' . s:all . '\)'
146 " let s:all = '\%(' . substitute(s:all, '\\\ze[,:]', '', 'g') . '\)'
147 if exists("b:match_debug")
148 let b:match_pat = s:pat
149 endif
150 endif
151
152 " Second step: set the following local variables:
153 " matchline = line on which the cursor started
154 " curcol = number of characters before match
155 " prefix = regexp for start of line to start of match
156 " suffix = regexp for end of match to end of line
157 " Require match to end on or after the cursor and prefer it to
158 " start on or before the cursor.
159 let matchline = getline(startline)
160 if a:word != ''
161 " word given
162 if a:word !~ s:all
163 echohl WarningMsg|echo 'Missing rule for word:"'.a:word.'"'|echohl NONE
164 return s:CleanUp(restore_options, a:mode, startline, startcol)
165 endif
166 let matchline = a:word
167 let curcol = 0
168 let prefix = '^\%('
169 let suffix = '\)$'
170 " Now the case when "word" is not given
171 else " Find the match that ends on or after the cursor and set curcol.
172 let regexp = s:Wholematch(matchline, s:all, startcol-1)
173 let curcol = match(matchline, regexp)
Bram Moolenaar864207d2008-06-24 22:14:38 +0000174 " If there is no match, give up.
175 if curcol == -1
176 return s:CleanUp(restore_options, a:mode, startline, startcol)
177 endif
Bram Moolenaarc81e5e72007-05-05 18:24:42 +0000178 let endcol = matchend(matchline, regexp)
179 let suf = strlen(matchline) - endcol
180 let prefix = (curcol ? '^.*\%' . (curcol + 1) . 'c\%(' : '^\%(')
181 let suffix = (suf ? '\)\%' . (endcol + 1) . 'c.*$' : '\)$')
Bram Moolenaar071d4272004-06-13 20:20:40 +0000182 endif
183 if exists("b:match_debug")
184 let b:match_match = matchstr(matchline, regexp)
185 let b:match_col = curcol+1
186 endif
187
188 " Third step: Find the group and single word that match, and the original
189 " (backref) versions of these. Then, resolve the backrefs.
190 " Set the following local variable:
191 " group = colon-separated list of patterns, one of which matches
192 " = ini:mid:fin or ini:fin
193 "
194 " Reconstruct the version with unresolved backrefs.
195 let patBR = substitute(match_words.',',
196 \ s:notslash.'\zs[,:]*,[,:]*', ',', 'g')
197 let patBR = substitute(patBR, s:notslash.'\zs:\{2,}', ':', 'g')
198 " Now, set group and groupBR to the matching group: 'if:endif' or
199 " 'while:endwhile' or whatever. A bit of a kluge: s:Choose() returns
200 " group . "," . groupBR, and we pick it apart.
201 let group = s:Choose(s:pat, matchline, ",", ":", prefix, suffix, patBR)
202 let i = matchend(group, s:notslash . ",")
203 let groupBR = strpart(group, i)
204 let group = strpart(group, 0, i-1)
205 " Now, matchline =~ prefix . substitute(group,':','\|','g') . suffix
206 if s:do_BR " Do the hard part: resolve those backrefs!
207 let group = s:InsertRefs(groupBR, prefix, group, suffix, matchline)
208 endif
209 if exists("b:match_debug")
210 let b:match_wholeBR = groupBR
211 let i = matchend(groupBR, s:notslash . ":")
212 let b:match_iniBR = strpart(groupBR, 0, i-1)
213 endif
214
215 " Fourth step: Set the arguments for searchpair().
216 let i = matchend(group, s:notslash . ":")
217 let j = matchend(group, '.*' . s:notslash . ":")
218 let ini = strpart(group, 0, i-1)
219 let mid = substitute(strpart(group, i,j-i-1), s:notslash.'\zs:', '\\|', 'g')
220 let fin = strpart(group, j)
Bram Moolenaar76b92b22006-03-24 22:46:53 +0000221 "Un-escape the remaining , and : characters.
222 let ini = substitute(ini, s:notslash . '\zs\\\(:\|,\)', '\1', 'g')
223 let mid = substitute(mid, s:notslash . '\zs\\\(:\|,\)', '\1', 'g')
224 let fin = substitute(fin, s:notslash . '\zs\\\(:\|,\)', '\1', 'g')
Bram Moolenaar071d4272004-06-13 20:20:40 +0000225 " searchpair() requires that these patterns avoid \(\) groups.
226 let ini = substitute(ini, s:notslash . '\zs\\(', '\\%(', 'g')
227 let mid = substitute(mid, s:notslash . '\zs\\(', '\\%(', 'g')
228 let fin = substitute(fin, s:notslash . '\zs\\(', '\\%(', 'g')
229 " Set mid. This is optimized for readability, not micro-efficiency!
230 if a:forward && matchline =~ prefix . fin . suffix
231 \ || !a:forward && matchline =~ prefix . ini . suffix
232 let mid = ""
233 endif
234 " Set flag. This is optimized for readability, not micro-efficiency!
235 if a:forward && matchline =~ prefix . fin . suffix
236 \ || !a:forward && matchline !~ prefix . ini . suffix
237 let flag = "bW"
238 else
239 let flag = "W"
240 endif
241 " Set skip.
242 if exists("b:match_skip")
243 let skip = b:match_skip
244 elseif exists("b:match_comment") " backwards compatibility and testing!
245 let skip = "r:" . b:match_comment
246 else
247 let skip = 's:comment\|string'
248 endif
249 let skip = s:ParseSkip(skip)
250 if exists("b:match_debug")
251 let b:match_ini = ini
252 let b:match_tail = (strlen(mid) ? mid.'\|' : '') . fin
253 endif
254
255 " Fifth step: actually start moving the cursor and call searchpair().
256 " Later, :execute restore_cursor to get to the original screen.
257 let restore_cursor = virtcol(".") . "|"
258 normal! g0
259 let restore_cursor = line(".") . "G" . virtcol(".") . "|zs" . restore_cursor
260 normal! H
261 let restore_cursor = "normal!" . line(".") . "Gzt" . restore_cursor
262 execute restore_cursor
Bram Moolenaarc81e5e72007-05-05 18:24:42 +0000263 call cursor(0, curcol + 1)
264 " normal! 0
265 " if curcol
266 " execute "normal!" . curcol . "l"
267 " endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000268 if skip =~ 'synID' && !(has("syntax") && exists("g:syntax_on"))
269 let skip = "0"
270 else
271 execute "if " . skip . "| let skip = '0' | endif"
272 endif
273 let sp_return = searchpair(ini, mid, fin, flag, skip)
274 let final_position = "call cursor(" . line(".") . "," . col(".") . ")"
275 " Restore cursor position and original screen.
276 execute restore_cursor
277 normal! m'
278 if sp_return > 0
279 execute final_position
280 endif
281 return s:CleanUp(restore_options, a:mode, startline, startcol, mid.'\|'.fin)
282endfun
283
284" Restore options and do some special handling for Operator-pending mode.
285" The optional argument is the tail of the matching group.
286fun! s:CleanUp(options, mode, startline, startcol, ...)
287 execute "set" a:options
288 " Open folds, if appropriate.
289 if a:mode != "o"
290 if &foldopen =~ "percent"
291 normal! zv
292 endif
293 " In Operator-pending mode, we want to include the whole match
294 " (for example, d%).
295 " This is only a problem if we end up moving in the forward direction.
296 elseif (a:startline < line(".")) ||
297 \ (a:startline == line(".") && a:startcol < col("."))
298 if a:0
299 " Check whether the match is a single character. If not, move to the
300 " end of the match.
301 let matchline = getline(".")
302 let currcol = col(".")
303 let regexp = s:Wholematch(matchline, a:1, currcol-1)
304 let endcol = matchend(matchline, regexp)
305 if endcol > currcol " This is NOT off by one!
306 execute "normal!" . (endcol - currcol) . "l"
307 endif
308 endif " a:0
309 endif " a:mode != "o" && etc.
310 return 0
311endfun
312
313" Example (simplified HTML patterns): if
314" a:groupBR = '<\(\k\+\)>:</\1>'
315" a:prefix = '^.\{3}\('
316" a:group = '<\(\k\+\)>:</\(\k\+\)>'
317" a:suffix = '\).\{2}$'
318" a:matchline = "123<tag>12" or "123</tag>12"
319" then extract "tag" from a:matchline and return "<tag>:</tag>" .
320fun! s:InsertRefs(groupBR, prefix, group, suffix, matchline)
321 if a:matchline !~ a:prefix .
322 \ substitute(a:group, s:notslash . '\zs:', '\\|', 'g') . a:suffix
323 return a:group
324 endif
325 let i = matchend(a:groupBR, s:notslash . ':')
326 let ini = strpart(a:groupBR, 0, i-1)
327 let tailBR = strpart(a:groupBR, i)
328 let word = s:Choose(a:group, a:matchline, ":", "", a:prefix, a:suffix,
329 \ a:groupBR)
330 let i = matchend(word, s:notslash . ":")
331 let wordBR = strpart(word, i)
332 let word = strpart(word, 0, i-1)
333 " Now, a:matchline =~ a:prefix . word . a:suffix
334 if wordBR != ini
335 let table = s:Resolve(ini, wordBR, "table")
336 else
337 " let table = "----------"
338 let table = ""
339 let d = 0
340 while d < 10
341 if tailBR =~ s:notslash . '\\' . d
342 " let table[d] = d
343 let table = table . d
344 else
345 let table = table . "-"
346 endif
347 let d = d + 1
348 endwhile
349 endif
350 let d = 9
351 while d
352 if table[d] != "-"
353 let backref = substitute(a:matchline, a:prefix.word.a:suffix,
354 \ '\'.table[d], "")
355 " Are there any other characters that should be escaped?
356 let backref = escape(backref, '*,:')
357 execute s:Ref(ini, d, "start", "len")
358 let ini = strpart(ini, 0, start) . backref . strpart(ini, start+len)
359 let tailBR = substitute(tailBR, s:notslash . '\zs\\' . d,
Bram Moolenaar5c736222010-01-06 20:54:52 +0100360 \ escape(backref, '\\&'), 'g')
Bram Moolenaar071d4272004-06-13 20:20:40 +0000361 endif
362 let d = d-1
363 endwhile
364 if exists("b:match_debug")
365 if s:do_BR
366 let b:match_table = table
367 let b:match_word = word
368 else
369 let b:match_table = ""
370 let b:match_word = ""
371 endif
372 endif
373 return ini . ":" . tailBR
374endfun
375
376" Input a comma-separated list of groups with backrefs, such as
377" a:groups = '\(foo\):end\1,\(bar\):end\1'
378" and return a comma-separated list of groups with backrefs replaced:
379" return '\(foo\):end\(foo\),\(bar\):end\(bar\)'
380fun! s:ParseWords(groups)
381 let groups = substitute(a:groups.",", s:notslash.'\zs[,:]*,[,:]*', ',', 'g')
382 let groups = substitute(groups, s:notslash . '\zs:\{2,}', ':', 'g')
383 let parsed = ""
384 while groups =~ '[^,:]'
385 let i = matchend(groups, s:notslash . ':')
386 let j = matchend(groups, s:notslash . ',')
387 let ini = strpart(groups, 0, i-1)
388 let tail = strpart(groups, i, j-i-1) . ":"
389 let groups = strpart(groups, j)
390 let parsed = parsed . ini
391 let i = matchend(tail, s:notslash . ':')
392 while i != -1
393 " In 'if:else:endif', ini='if' and word='else' and then word='endif'.
394 let word = strpart(tail, 0, i-1)
395 let tail = strpart(tail, i)
396 let i = matchend(tail, s:notslash . ':')
397 let parsed = parsed . ":" . s:Resolve(ini, word, "word")
398 endwhile " Now, tail has been used up.
399 let parsed = parsed . ","
400 endwhile " groups =~ '[^,:]'
Bram Moolenaar864207d2008-06-24 22:14:38 +0000401 let parsed = substitute(parsed, ',$', '', '')
Bram Moolenaar071d4272004-06-13 20:20:40 +0000402 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 . '\)'
Bram Moolenaarc81e5e72007-05-05 18:24:42 +0000418 let prefix = (a:start ? '\(^.*\%<' . (a:start + 2) . 'c\)\zs' : '^')
Bram Moolenaar071d4272004-06-13 20:20:40 +0000419 let len = strlen(a:string)
Bram Moolenaarc81e5e72007-05-05 18:24:42 +0000420 let suffix = (a:start+1 < len ? '\(\%>'.(a:start+1).'c.*$\)\@=' : '$')
Bram Moolenaar071d4272004-06-13 20:20:40 +0000421 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) ? "," : "") .
Bram Moolenaar864207d2008-06-24 22:14:38 +0000652 \ '\/\*:\*\/,#if\%(def\)\=:#else\>:#elif\>:#endif\>'
Bram Moolenaar071d4272004-06-13 20:20:40 +0000653 " 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
Bram Moolenaar864207d2008-06-24 22:14:38 +0000683 let open = substitute(s:pat . cdefault,
684 \ s:notslash . '\zs:.\{-}' . s:notslash . ',', '\\),\\(', 'g')
685 let open = '\(' . substitute(open, s:notslash . '\zs:.*$', '\\)', '')
686 let close = substitute(s:pat . cdefault,
687 \ s:notslash . '\zs,.\{-}' . s:notslash . ':', '\\),\\(', 'g')
688 let close = substitute(close, '^.\{-}' . s:notslash . ':', '\\(', '') . '\)'
Bram Moolenaar071d4272004-06-13 20:20:40 +0000689 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: