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