comparison runtime/pack/dist/opt/matchit/autoload/matchit.vim @ 15735:5688ec97294b

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