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