1" Vim indent file 2" Language: PHP 3" Author: John Wellesz <John.wellesz (AT) teaser (DOT) fr> 4" URL: http://www.2072productions.com/vim/indent/php.vim 5" Last Change: 2005 Nobember 21st 6" Version: 1.20 7" 8" The change log and all the comments have been removed from this file. 9" 10" For a complete change log and fully commented code, download the script on 11" 2072productions.com at the URI provided above. 12" 13" If you find a bug, please e-mail me at John.wellesz (AT) teaser (DOT) fr 14" with an example of code that break the algorithm. 15" 16" 17" Thanks a lot for using this script. 18" 19" 20" NOTE: This script must be used with PHP syntax ON and with the php syntax 21" script by Lutz Eymers (http://www.isp.de/data/php.vim ) that's the script bundled with Gvim. 22" 23" 24" In the case you have syntax errors in your script such as end of HereDoc 25" tags not at col 1 you'll have to indent your file 2 times (This script 26" will automatically put HereDoc end tags at col 1). 27" 28" 29" NOTE: If you are editing file in Unix file format and that (by accident) 30" there are '\r' before new lines, this script won't be able to proceed 31" correctly and will make many mistakes because it won't be able to match 32" '\s*$' correctly. 33" So you have to remove those useless characters first with a command like: 34" 35" :%s /\r$//g 36" 37" or simply 'let' the option PHP_removeCRwhenUnix to 1 and the script will 38" silently remove them when VIM load this script (at each bufread). 39 40 41" Options: PHP_autoformatcomment = 0 to not enable autoformating of comment by 42" default, if set to 0, this script will let the 'formatoptions' setting intact. 43" 44" Options: PHP_default_indenting = # of sw (default is 0), # of sw will be 45" added to the indent of each line of PHP code. 46" 47" Options: PHP_removeCRwhenUnix = 1 to make the script automatically remove CR 48" at end of lines (by default this option is unset), NOTE that you 49" MUST remove CR when the fileformat is UNIX else the indentation 50" won't be correct... 51" 52" Options: PHP_BracesAtCodeLevel = 1 to indent the '{' and '}' at the same 53" level than the code they contain. 54" Exemple: 55" Instead of: 56" if ($foo) 57" { 58" foo(); 59" } 60" 61" You will write: 62" if ($foo) 63" { 64" foo(); 65" } 66" 67" NOTE: The script will be a bit slower if you use this option because 68" some optimizations won't be available. 69 70 71 72if exists("b:did_indent") 73 finish 74endif 75let b:did_indent = 1 76 77 78let php_sync_method = 0 79 80 81if exists("PHP_default_indenting") 82 let b:PHP_default_indenting = PHP_default_indenting * &sw 83else 84 let b:PHP_default_indenting = 0 85endif 86 87if exists("PHP_BracesAtCodeLevel") 88 let b:PHP_BracesAtCodeLevel = PHP_BracesAtCodeLevel 89else 90 let b:PHP_BracesAtCodeLevel = 0 91endif 92 93if exists("PHP_autoformatcomment") 94 let b:PHP_autoformatcomment = PHP_autoformatcomment 95else 96 let b:PHP_autoformatcomment = 1 97endif 98 99let b:PHP_lastindented = 0 100let b:PHP_indentbeforelast = 0 101let b:PHP_indentinghuge = 0 102let b:PHP_CurrentIndentLevel = b:PHP_default_indenting 103let b:PHP_LastIndentedWasComment = 0 104let b:PHP_InsideMultilineComment = 0 105let b:InPHPcode = 0 106let b:InPHPcode_checked = 0 107let b:InPHPcode_and_script = 0 108let b:InPHPcode_tofind = "" 109let b:PHP_oldchangetick = b:changedtick 110let b:UserIsTypingComment = 0 111let b:optionsset = 0 112 113setlocal nosmartindent 114setlocal noautoindent 115setlocal nocindent 116setlocal nolisp 117 118setlocal indentexpr=GetPhpIndent() 119setlocal indentkeys=0{,0},0),:,!^F,o,O,e,*<Return>,=?>,=<?,=*/ 120 121 122 123let s:searchpairflags = 'bWr' 124 125if &fileformat == "unix" && exists("PHP_removeCRwhenUnix") && PHP_removeCRwhenUnix 126 silent! %s/\r$//g 127endif 128 129if exists("*GetPhpIndent") 130 finish " XXX 131endif 132 133let s:endline= '\s*\%(//.*\|#.*\|/\*.*\*/\s*\)\=$' 134let s:PHP_startindenttag = '<?\%(.*?>\)\@!\|<script[^>]*>\%(.*<\/script>\)\@!' 135" setlocal debug=msg " XXX 136 137 138function! GetLastRealCodeLNum(startline) " {{{ 139 let lnum = a:startline 140 let old_lnum = lnum 141 142 while lnum > 1 143 let lnum = prevnonblank(lnum) 144 let lastline = getline(lnum) 145 146 if b:InPHPcode_and_script && lastline =~ '?>\s*$' 147 let lnum = lnum - 1 148 elseif lastline =~ '^\s*?>.*<?\%(php\)\=\s*$' 149 let lnum = lnum - 1 150 elseif lastline =~ '^\s*\%(//\|#\|/\*.*\*/\s*$\)' 151 let lnum = lnum - 1 152 elseif lastline =~ '\*/\s*$' 153 call cursor(lnum, 1) 154 if lastline !~ '^\*/' 155 call search('\*/', 'W') 156 endif 157 let lnum = searchpair('/\*', '', '\*/', s:searchpairflags, 'Skippmatch2()') 158 159 let lastline = getline(lnum) 160 if lastline =~ '^\s*/\*' 161 let lnum = lnum - 1 162 else 163 break 164 endif 165 166 167 elseif lastline =~? '\%(//\s*\|?>.*\)\@<!<?\%(php\)\=\s*$\|^\s*<script\>' 168 169 while lastline !~ '\(<?.*\)\@<!?>' && lnum > 1 170 let lnum = lnum - 1 171 let lastline = getline(lnum) 172 endwhile 173 if lastline =~ '^\s*?>' 174 let lnum = lnum - 1 175 else 176 break 177 endif 178 179 180 elseif lastline =~? '^\a\w*;$' && lastline !~? s:notPhpHereDoc 181 let tofind=substitute( lastline, '\([^;]\+\);', '<<<\1$', '') 182 while getline(lnum) !~? tofind && lnum > 1 183 let lnum = lnum - 1 184 endwhile 185 else 186 break 187 endif 188 endwhile 189 190 if lnum==1 && getline(lnum)!~ '<?' 191 let lnum=0 192 endif 193 194 if b:InPHPcode_and_script && !b:InPHPcode 195 let b:InPHPcode_and_script = 0 196 endif 197 return lnum 198endfunction " }}} 199 200function! Skippmatch2() 201 202 let line = getline(".") 203 204 if line =~ '\%(".*\)\@<=/\*\%(.*"\)\@=' || line =~ '\%(//.*\)\@<=/\*' 205 return 1 206 else 207 return 0 208 endif 209endfun 210 211function! Skippmatch() " {{{ 212 let synname = synIDattr(synID(line("."), col("."), 0), "name") 213 if synname == "Delimiter" || synname == "phpParent" || synname == "javaScriptBraces" || synname == "phpComment" && b:UserIsTypingComment 214 return 0 215 else 216 return 1 217 endif 218endfun " }}} 219 220function! FindOpenBracket(lnum) " {{{ 221 call cursor(a:lnum, 1) 222 return searchpair('{', '', '}', 'bW', 'Skippmatch()') 223endfun " }}} 224 225function! FindTheIfOfAnElse (lnum, StopAfterFirstPrevElse) " {{{ 226 227 if getline(a:lnum) =~# '^\s*}\s*else\%(if\)\=\>' 228 let beforeelse = a:lnum 229 else 230 let beforeelse = GetLastRealCodeLNum(a:lnum - 1) 231 endif 232 233 if !s:level 234 let s:iftoskip = 0 235 endif 236 237 if getline(beforeelse) =~# '^\s*\%(}\s*\)\=else\%(\s*if\)\@!\>' 238 let s:iftoskip = s:iftoskip + 1 239 endif 240 241 if getline(beforeelse) =~ '^\s*}' 242 let beforeelse = FindOpenBracket(beforeelse) 243 244 if getline(beforeelse) =~ '^\s*{' 245 let beforeelse = GetLastRealCodeLNum(beforeelse - 1) 246 endif 247 endif 248 249 250 if !s:iftoskip && a:StopAfterFirstPrevElse && getline(beforeelse) =~# '^\s*\%([}]\s*\)\=else\%(if\)\=\>' 251 return beforeelse 252 endif 253 254 if getline(beforeelse) !~# '^\s*if\>' && beforeelse>1 || s:iftoskip && beforeelse>1 255 256 if s:iftoskip && getline(beforeelse) =~# '^\s*if\>' 257 let s:iftoskip = s:iftoskip - 1 258 endif 259 260 let s:level = s:level + 1 261 let beforeelse = FindTheIfOfAnElse(beforeelse, a:StopAfterFirstPrevElse) 262 endif 263 264 return beforeelse 265 266endfunction " }}} 267 268function! IslinePHP (lnum, tofind) " {{{ 269 let cline = getline(a:lnum) 270 271 if a:tofind=="" 272 let tofind = "^\\s*[\"']*\\s*\\zs\\S" 273 else 274 let tofind = a:tofind 275 endif 276 277 let tofind = tofind . '\c' 278 279 let coltotest = match (cline, tofind) + 1 280 281 let synname = synIDattr(synID(a:lnum, coltotest, 0), "name") 282 283 if synname =~ '^php' || synname=="Delimiter" || synname =~? '^javaScript' 284 return synname 285 else 286 return "" 287 endif 288endfunction " }}} 289 290let s:notPhpHereDoc = '\%(break\|return\|continue\|exit\);' 291let s:blockstart = '\%(\%(\%(}\s*\)\=else\%(\s\+\)\=\)\=if\>\|else\>\|while\>\|switch\>\|for\%(each\)\=\>\|declare\>\|class\>\|interface\>\|abstract\>\|[|&]\)' 292 293let s:autorestoptions = 0 294if ! s:autorestoptions 295 au BufWinEnter,Syntax *.php,*.php3,*.php4,*.php5 call ResetOptions() 296 let s:autorestoptions = 1 297endif 298 299function! ResetOptions() 300 if ! b:optionsset 301 if b:PHP_autoformatcomment 302 303 setlocal comments=s1:/*,mb:*,ex:*/,://,:# 304 305 setlocal formatoptions-=t 306 setlocal formatoptions+=q 307 setlocal formatoptions+=r 308 setlocal formatoptions+=o 309 setlocal formatoptions+=w 310 setlocal formatoptions+=c 311 setlocal formatoptions+=b 312 endif 313 let b:optionsset = 1 314 endif 315endfunc 316 317function! GetPhpIndent() 318 319 let UserIsEditing=0 320 if b:PHP_oldchangetick != b:changedtick 321 let b:PHP_oldchangetick = b:changedtick 322 let UserIsEditing=1 323 endif 324 325 if b:PHP_default_indenting 326 let b:PHP_default_indenting = g:PHP_default_indenting * &sw 327 endif 328 329 let cline = getline(v:lnum) 330 331 if !b:PHP_indentinghuge && b:PHP_lastindented > b:PHP_indentbeforelast 332 if b:PHP_indentbeforelast 333 let b:PHP_indentinghuge = 1 334 echom 'Large indenting detected, speed optimizations engaged' 335 endif 336 let b:PHP_indentbeforelast = b:PHP_lastindented 337 endif 338 339 if b:InPHPcode_checked && prevnonblank(v:lnum - 1) != b:PHP_lastindented 340 if b:PHP_indentinghuge 341 echom 'Large indenting deactivated' 342 let b:PHP_indentinghuge = 0 343 let b:PHP_CurrentIndentLevel = b:PHP_default_indenting 344 endif 345 let b:PHP_lastindented = v:lnum 346 let b:PHP_LastIndentedWasComment=0 347 let b:PHP_InsideMultilineComment=0 348 let b:PHP_indentbeforelast = 0 349 350 let b:InPHPcode = 0 351 let b:InPHPcode_checked = 0 352 let b:InPHPcode_and_script = 0 353 let b:InPHPcode_tofind = "" 354 355 elseif v:lnum > b:PHP_lastindented 356 let real_PHP_lastindented = b:PHP_lastindented 357 let b:PHP_lastindented = v:lnum 358 endif 359 360 361 if !b:InPHPcode_checked " {{{ One time check 362 let b:InPHPcode_checked = 1 363 364 let synname = IslinePHP (prevnonblank(v:lnum), "") 365 366 if synname!="" 367 if synname != "phpHereDoc" 368 let b:InPHPcode = 1 369 let b:InPHPcode_tofind = "" 370 371 if synname == "phpComment" 372 let b:UserIsTypingComment = 1 373 else 374 let b:UserIsTypingComment = 0 375 endif 376 377 if synname =~? '^javaScript' 378 let b:InPHPcode_and_script = 1 379 endif 380 381 else 382 let b:InPHPcode = 0 383 let b:UserIsTypingComment = 0 384 385 let lnum = v:lnum - 1 386 while getline(lnum) !~? '<<<\a\w*$' && lnum > 1 387 let lnum = lnum - 1 388 endwhile 389 390 let b:InPHPcode_tofind = substitute( getline(lnum), '^.*<<<\(\a\w*\)\c', '^\\s*\1;$', '') 391 endif 392 else 393 let b:InPHPcode = 0 394 let b:UserIsTypingComment = 0 395 let b:InPHPcode_tofind = '<?\%(.*?>\)\@!\|<script.*>' 396 endif 397 endif "!b:InPHPcode_checked }}} 398 399 400 " Test if we are indenting PHP code {{{ 401 let lnum = prevnonblank(v:lnum - 1) 402 let last_line = getline(lnum) 403 404 if b:InPHPcode_tofind!="" 405 if cline =~? b:InPHPcode_tofind 406 let b:InPHPcode = 1 407 let b:InPHPcode_tofind = "" 408 let b:UserIsTypingComment = 0 409 if cline =~ '\*/' 410 call cursor(v:lnum, 1) 411 if cline !~ '^\*/' 412 call search('\*/', 'W') 413 endif 414 let lnum = searchpair('/\*', '', '\*/', s:searchpairflags, 'Skippmatch2()') 415 416 let b:PHP_CurrentIndentLevel = b:PHP_default_indenting 417 418 let b:PHP_LastIndentedWasComment = 0 419 420 if cline =~ '^\s*\*/' 421 return indent(lnum) + 1 422 else 423 return indent(lnum) 424 endif 425 426 elseif cline =~? '<script\>' 427 let b:InPHPcode_and_script = 1 428 endif 429 endif 430 endif 431 432 if b:InPHPcode 433 434 if !b:InPHPcode_and_script && last_line =~ '\%(<?.*\)\@<!?>\%(.*<?\)\@!' && IslinePHP(lnum, '?>')=="Delimiter" 435 if cline !~? s:PHP_startindenttag 436 let b:InPHPcode = 0 437 let b:InPHPcode_tofind = s:PHP_startindenttag 438 elseif cline =~? '<script\>' 439 let b:InPHPcode_and_script = 1 440 endif 441 442 elseif last_line =~? '<<<\a\w*$' 443 let b:InPHPcode = 0 444 let b:InPHPcode_tofind = substitute( last_line, '^.*<<<\(\a\w*\)\c', '^\\s*\1;$', '') 445 446 elseif !UserIsEditing && cline =~ '^\s*/\*\%(.*\*/\)\@!' && getline(v:lnum + 1) !~ '^\s*\*' 447 let b:InPHPcode = 0 448 let b:InPHPcode_tofind = '\*/' 449 450 elseif cline =~? '^\s*</script>' 451 let b:InPHPcode = 0 452 let b:InPHPcode_tofind = s:PHP_startindenttag 453 endif 454 endif " }}} 455 456 if !b:InPHPcode && !b:InPHPcode_and_script 457 return -1 458 endif 459 460 461 " Indent successive // or # comment the same way the first is {{{ 462 if cline =~ '^\s*\%(//\|#\|/\*.*\*/\s*$\)' 463 if b:PHP_LastIndentedWasComment == 1 464 return indent(real_PHP_lastindented) 465 endif 466 let b:PHP_LastIndentedWasComment = 1 467 else 468 let b:PHP_LastIndentedWasComment = 0 469 endif " }}} 470 471 " Indent multiline /* comments correctly {{{ 472 473 if b:PHP_InsideMultilineComment || b:UserIsTypingComment 474 if cline =~ '^\s*\*\%(\/\)\@!' 475 if last_line =~ '^\s*/\*' 476 return indent(lnum) + 1 477 else 478 return indent(lnum) 479 endif 480 else 481 let b:PHP_InsideMultilineComment = 0 482 endif 483 endif 484 485 if !b:PHP_InsideMultilineComment && cline =~ '^\s*/\*' 486 let b:PHP_InsideMultilineComment = 1 487 return -1 488 endif " }}} 489 490 491 " Things always indented at col 1 (PHP delimiter: <?, ?>, Heredoc end) {{{ 492 if cline =~# '^\s*<?' && cline !~ '?>' 493 return 0 494 endif 495 496 if cline =~ '^\s*?>' && cline !~# '<?' 497 return 0 498 endif 499 500 if cline =~? '^\s*\a\w*;$' && cline !~? s:notPhpHereDoc 501 return 0 502 endif " }}} 503 504 let s:level = 0 505 506 let lnum = GetLastRealCodeLNum(v:lnum - 1) 507 let last_line = getline(lnum) 508 let ind = indent(lnum) 509 let endline= s:endline 510 511 if ind==0 && b:PHP_default_indenting 512 let ind = b:PHP_default_indenting 513 endif 514 515 if lnum == 0 516 return b:PHP_default_indenting 517 endif 518 519 520 if cline =~ '^\s*}\%(}}\)\@!' 521 let ind = indent(FindOpenBracket(v:lnum)) 522 let b:PHP_CurrentIndentLevel = b:PHP_default_indenting 523 return ind 524 endif 525 526 if cline =~ '^\s*\*/' 527 call cursor(v:lnum, 1) 528 if cline !~ '^\*/' 529 call search('\*/', 'W') 530 endif 531 let lnum = searchpair('/\*', '', '\*/', s:searchpairflags, 'Skippmatch2()') 532 533 let b:PHP_CurrentIndentLevel = b:PHP_default_indenting 534 535 if cline =~ '^\s*\*/' 536 return indent(lnum) + 1 537 else 538 return indent(lnum) 539 endif 540 endif 541 542 let defaultORcase = '^\s*\%(default\|case\).*:' 543 544 if last_line =~ '[;}]'.endline && last_line !~# defaultORcase 545 if ind==b:PHP_default_indenting 546 return b:PHP_default_indenting 547 elseif b:PHP_indentinghuge && ind==b:PHP_CurrentIndentLevel && cline !~# '^\s*\%(else\|\%(case\|default\).*:\|[})];\=\)' && last_line !~# '^\s*\%(\%(}\s*\)\=else\)' && getline(GetLastRealCodeLNum(lnum - 1))=~';'.endline 548 return b:PHP_CurrentIndentLevel 549 endif 550 endif 551 552 let LastLineClosed = 0 553 554 let terminated = '\%(;\%(\s*?>\)\=\|<<<\a\w*\|}\)'.endline 555 556 let unstated = '\%(^\s*'.s:blockstart.'.*)\|\%(//.*\)\@<!\<e'.'lse\>\)'.endline 557 558 if ind != b:PHP_default_indenting && cline =~# '^\s*else\%(if\)\=\>' 559 let b:PHP_CurrentIndentLevel = b:PHP_default_indenting 560 return indent(FindTheIfOfAnElse(v:lnum, 1)) 561 elseif cline =~ '^\s*{' 562 let previous_line = last_line 563 let last_line_num = lnum 564 565 while last_line_num > 1 566 567 if previous_line =~ '^\s*\%(' . s:blockstart . '\|\%([a-zA-Z]\s*\)*function\)' && previous_line !~ '^\s*[|&]' 568 569 let ind = indent(last_line_num) 570 571 if b:PHP_BracesAtCodeLevel 572 let ind = ind + &sw 573 endif 574 575 return ind 576 endif 577 578 let last_line_num = last_line_num - 1 579 let previous_line = getline(last_line_num) 580 endwhile 581 582 elseif last_line =~# unstated && cline !~ '^\s*{\|^\s*);\='.endline 583 let ind = ind + &sw 584 return ind 585 586 elseif ind != b:PHP_default_indenting && last_line =~ terminated 587 let previous_line = last_line 588 let last_line_num = lnum 589 let LastLineClosed = 1 590 591 while 1 592 if previous_line =~ '^\s*}' 593 let last_line_num = FindOpenBracket(last_line_num) 594 595 if getline(last_line_num) =~ '^\s*{' 596 let last_line_num = GetLastRealCodeLNum(last_line_num - 1) 597 endif 598 599 let previous_line = getline(last_line_num) 600 601 continue 602 else 603 604 if getline(last_line_num) =~# '^\s*else\%(if\)\=\>' 605 let last_line_num = FindTheIfOfAnElse(last_line_num, 0) 606 continue 607 endif 608 609 610 let last_match = last_line_num 611 612 let one_ahead_indent = indent(last_line_num) 613 let last_line_num = GetLastRealCodeLNum(last_line_num - 1) 614 let two_ahead_indent = indent(last_line_num) 615 let after_previous_line = previous_line 616 let previous_line = getline(last_line_num) 617 618 619 if previous_line =~# defaultORcase.'\|{'.endline 620 break 621 endif 622 623 if after_previous_line=~# '^\s*'.s:blockstart.'.*)'.endline && previous_line =~# '[;}]'.endline 624 break 625 endif 626 627 if one_ahead_indent == two_ahead_indent || last_line_num < 1 628 if previous_line =~# '[;}]'.endline || last_line_num < 1 629 break 630 endif 631 endif 632 endif 633 endwhile 634 635 if indent(last_match) != ind 636 let ind = indent(last_match) 637 let b:PHP_CurrentIndentLevel = b:PHP_default_indenting 638 639 if cline =~# defaultORcase 640 let ind = ind - &sw 641 endif 642 return ind 643 endif 644 endif 645 646 let plinnum = GetLastRealCodeLNum(lnum - 1) 647 let pline = getline(plinnum) 648 649 let last_line = substitute(last_line,"\\(//\\|#\\)\\(\\(\\([^\"']*\\([\"']\\)[^\"']*\\5\\)\\+[^\"']*$\\)\\|\\([^\"']*$\\)\\)",'','') 650 651 652 if ind == b:PHP_default_indenting 653 if last_line =~ terminated 654 let LastLineClosed = 1 655 endif 656 endif 657 658 if !LastLineClosed 659 660 if last_line =~# '[{(]'.endline || last_line =~? '\h\w*\s*(.*,$' && pline !~ '[,(]'.endline 661 662 if !b:PHP_BracesAtCodeLevel || last_line !~# '^\s*{' 663 let ind = ind + &sw 664 endif 665 666 if b:PHP_BracesAtCodeLevel || cline !~# defaultORcase 667 let b:PHP_CurrentIndentLevel = ind 668 return ind 669 endif 670 671 elseif last_line =~ '\S\+\s*),'.endline 672 call cursor(lnum, 1) 673 call search('),'.endline, 'W') 674 let openedparent = searchpair('(', '', ')', 'bW', 'Skippmatch()') 675 if openedparent != lnum 676 let ind = indent(openedparent) 677 endif 678 679 680 elseif cline !~ '^\s*{' && pline =~ '\%(;\%(\s*?>\)\=\|<<<\a\w*\|{\|^\s*'.s:blockstart.'\s*(.*)\)'.endline.'\|^\s*}\|'.defaultORcase 681 682 let ind = ind + &sw 683 684 endif 685 686 elseif last_line =~# defaultORcase 687 let ind = ind + &sw 688 endif 689 690 if cline =~ '^\s*);\=' 691 let ind = ind - &sw 692 elseif cline =~# defaultORcase 693 let ind = ind - &sw 694 695 endif 696 697 let b:PHP_CurrentIndentLevel = ind 698 return ind 699endfunction 700 701" vim: set ts=8 sw=4 sts=4: 702