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