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