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