Mercurial > vim
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: |