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