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 Aug 15 6" Version: 1.17 7" 8" For a complete change log and lots of comments in the code, download the script on 9" 2072productions.com at the URI provided above. 10" 11" 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" Options: PHP_default_indenting = # of sw (default is 0), # of sw will be 41" added to the indent of each line of PHP code. 42" 43" Options: PHP_removeCRwhenUnix = 1 to make the script automatically remove CR 44" at end of lines (by default this option is unset), NOTE that you 45" MUST remove CR when the fileformat is UNIX else the indentation 46" won't be correct... 47" 48" Options: PHP_BracesAtCodeLevel = 1 to indent the '{' and '}' at the same 49" level than the code they contain. 50" Exemple: 51" Instead of: 52" if ($foo) 53" { 54" foo(); 55" } 56" 57" You will write: 58" if ($foo) 59" { 60" foo(); 61" } 62" 63" NOTE: The script will be a bit slower if you use this option because 64" some optimizations won't be available. 65 66 67if exists("b:did_indent") 68 finish 69endif 70let b:did_indent = 1 71 72" This script set the option php_sync_method of PHP syntax script to 0 73" (fromstart indenting method) in order to have an accurate syntax. 74" If you are using very big PHP files (which is a bad idea) you will 75" experience slowings down while editing, if your code contains only PHP 76" code you can comment the line below. 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 93 94let b:PHP_lastindented = 0 95let b:PHP_indentbeforelast = 0 96let b:PHP_indentinghuge = 0 97let b:PHP_CurrentIndentLevel = b:PHP_default_indenting 98let b:PHP_LastIndentedWasComment = 0 99let b:PHP_InsideMultilineComment = 0 100let b:InPHPcode = 0 101let b:InPHPcode_checked = 0 102let b:InPHPcode_and_script = 0 103let b:InPHPcode_tofind = "" 104let b:PHP_oldchangetick = b:changedtick 105let b:UserIsTypingComment = 0 106let b:optionsset = 0 107 108setlocal nosmartindent 109setlocal noautoindent 110setlocal nocindent 111setlocal nolisp " autoindent must be on, so this line is also useless... 112 113setlocal indentexpr=GetPhpIndent() 114setlocal indentkeys=0{,0},0),:,!^F,o,O,e,*<Return>,=?>,=<?,=*/ 115 116 117if version <= 603 && &encoding == 'utf-8' 118 let s:searchpairflags = 'bW' 119else 120 let s:searchpairflags = 'bWr' 121endif 122 123if &fileformat == "unix" && exists("PHP_removeCRwhenUnix") && PHP_removeCRwhenUnix 124 silent! %s/\r$//g 125endif 126 127if exists("*GetPhpIndent") 128 finish " XXX 129endif 130 131let s:endline= '\s*\%(//.*\|#.*\|/\*.*\*/\s*\)\=$' 132let s:PHP_startindenttag = '<?\%(.*?>\)\@!\|<script[^>]*>\%(.*<\/script>\)\@!' 133"setlocal debug=msg " XXX 134 135 136function! GetLastRealCodeLNum(startline) " {{{ 137 "Inspired from the function SkipJavaBlanksAndComments by Toby Allsopp for indent/java.vim 138 let lnum = a:startline 139 let old_lnum = lnum 140 141 while lnum > 1 142 let lnum = prevnonblank(lnum) 143 let lastline = getline(lnum) 144 145 if b:InPHPcode_and_script && lastline =~ '?>\s*$' 146 let lnum = lnum - 1 147 elseif lastline =~ '^\s*?>.*<?\%(php\)\=\s*$' 148 let lnum = lnum - 1 149 elseif lastline =~ '^\s*\%(//\|#\|/\*.*\*/\s*$\)' " if line is under comment 150 let lnum = lnum - 1 151 elseif lastline =~ '\*/\s*$' " skip multiline comments 152 call cursor(lnum, 1) 153 call search('\*/\zs', 'W') " positition the cursor after the first */ 154 let lnum = searchpair('/\*', '', '\*/\zs', s:searchpairflags) " find the most outside /* 155 156 let lastline = getline(lnum) 157 if lastline =~ '^\s*/\*' " if line contains nothing but comment 158 let lnum = lnum - 1 " do the job again on the line before (a comment can hide another...) 159 else 160 break 161 endif 162 163 164 elseif lastline =~? '\%(//\s*\|?>.*\)\@<!<?\%(php\)\=\s*$\|^\s*<script\>' " skip non php code 165 166 while lastline !~ '\(<?.*\)\@<!?>' && lnum > 1 167 let lnum = lnum - 1 168 let lastline = getline(lnum) 169 endwhile 170 if lastline =~ '^\s*?>' " if line contains nothing but end tag 171 let lnum = lnum - 1 172 else 173 break " else there is something important before the ?> 174 endif 175 176 177 elseif lastline =~? '^\a\w*;$' && lastline !~? s:notPhpHereDoc " match the end of a heredoc 178 let tofind=substitute( lastline, '\([^;]\+\);', '<<<\1$', '') 179 while getline(lnum) !~? tofind && lnum > 1 180 let lnum = lnum - 1 181 endwhile 182 else 183 break " if none of these were true then we are done 184 endif 185 endwhile 186 187 if lnum==1 && getline(lnum)!~ '<?' 188 let lnum=0 189 endif 190 191 if b:InPHPcode_and_script && !b:InPHPcode 192 let b:InPHPcode_and_script = 0 193 endif 194 return lnum 195endfunction 196" }}} 197 198function! Skippmatch() " {{{ 199 let synname = synIDattr(synID(line("."), col("."), 0), "name") 200 if synname == "Delimiter" || synname == "phpParent" || synname == "javaScriptBraces" || synname == "phpComment" && b:UserIsTypingComment 201 return 0 202 else 203 return 1 204 endif 205endfun 206" }}} 207 208function! FindOpenBracket(lnum) " {{{ 209 call cursor(a:lnum, 1) " set the cursor to the start of the lnum line 210 return searchpair('{', '', '}', 'bW', 'Skippmatch()') 211endfun 212" }}} 213 214function! FindTheIfOfAnElse (lnum, StopAfterFirstPrevElse) " {{{ 215" A very clever recoursive function created by me (John Wellesz) that find the "if" corresponding to an 216" "else". This function can easily be adapted for other languages :) 217 218 if getline(a:lnum) =~# '^\s*}\s*else\%(if\)\=\>' 219 let beforeelse = a:lnum " we do this so we can find the opened bracket to speed up the process 220 else 221 let beforeelse = GetLastRealCodeLNum(a:lnum - 1) 222 endif 223 224 if !s:level 225 let s:iftoskip = 0 226 endif 227 228 if getline(beforeelse) =~# '^\s*\%(}\s*\)\=else\%(\s*if\)\@!\>' 229 let s:iftoskip = s:iftoskip + 1 230 endif 231 232 if getline(beforeelse) =~ '^\s*}' 233 let beforeelse = FindOpenBracket(beforeelse) 234 235 if getline(beforeelse) =~ '^\s*{' 236 let beforeelse = GetLastRealCodeLNum(beforeelse - 1) 237 endif 238 endif 239 240 241 if !s:iftoskip && a:StopAfterFirstPrevElse && getline(beforeelse) =~# '^\s*\%([}]\s*\)\=else\%(if\)\=\>' 242 return beforeelse 243 endif 244 245 if getline(beforeelse) !~# '^\s*if\>' && beforeelse>1 || s:iftoskip && beforeelse>1 246 247 if s:iftoskip && getline(beforeelse) =~# '^\s*if\>' 248 let s:iftoskip = s:iftoskip - 1 249 endif 250 251 let s:level = s:level + 1 252 let beforeelse = FindTheIfOfAnElse(beforeelse, a:StopAfterFirstPrevElse) 253 endif 254 255 return beforeelse 256 257endfunction 258" }}} 259 260function! IslinePHP (lnum, tofind) " {{{ 261 let cline = getline(a:lnum) 262 263 if a:tofind=="" 264 let tofind = "^\\s*[\"']*\s*\\zs\\S" " This correct the issue where lines beginning by a 265 " single or double quote were not indented in some cases. 266 else 267 let tofind = a:tofind 268 endif 269 270 let tofind = tofind . '\c' " ignorecase 271 272 let coltotest = match (cline, tofind) + 1 "find the first non blank char in the current line 273 274 let synname = synIDattr(synID(a:lnum, coltotest, 0), "name") " ask to syntax what is its name 275 276 if synname =~ '^php' || synname=="Delimiter" || synname =~? '^javaScript' 277 return synname 278 else 279 return "" 280 endif 281endfunction 282" }}} 283 284let s:notPhpHereDoc = '\%(break\|return\|continue\|exit\);' 285let s:blockstart = '\%(\%(\%(}\s*\)\=else\%(\s\+\)\=\)\=if\>\|while\>\|switch\>\|for\%(each\)\=\>\|declare\>\|[|&]\)' 286 287let s:autorestoptions = 0 288if ! s:autorestoptions 289 au BufWinEnter,Syntax *.php,*.php3,*.php4,*.php5 call ResetOptions() 290 let s:autorestoptions = 1 291endif 292 293function! ResetOptions() 294 if ! b:optionsset 295 setlocal formatoptions=qroc 296 let b:optionsset = 1 297 endif 298endfunc 299 300function! GetPhpIndent() 301 "############################################## 302 "########### MAIN INDENT FUNCTION ############# 303 "############################################## 304 305 let UserIsEditing=0 306 if b:PHP_oldchangetick != b:changedtick 307 let b:PHP_oldchangetick = b:changedtick 308 let UserIsEditing=1 309 endif 310 311 if b:PHP_default_indenting 312 let b:PHP_default_indenting = g:PHP_default_indenting * &sw 313 endif 314 315 let cline = getline(v:lnum) " current line 316 317 if !b:PHP_indentinghuge && b:PHP_lastindented > b:PHP_indentbeforelast 318 if b:PHP_indentbeforelast 319 let b:PHP_indentinghuge = 1 320 echom 'Large indenting detected, speed optimizations engaged' 321 endif 322 let b:PHP_indentbeforelast = b:PHP_lastindented 323 endif 324 325 if b:InPHPcode_checked && prevnonblank(v:lnum - 1) != b:PHP_lastindented 326 if b:PHP_indentinghuge 327 echom 'Large indenting deactivated' 328 let b:PHP_indentinghuge = 0 329 let b:PHP_CurrentIndentLevel = b:PHP_default_indenting 330 endif 331 let b:PHP_lastindented = v:lnum 332 let b:PHP_LastIndentedWasComment=0 333 let b:PHP_InsideMultilineComment=0 334 let b:PHP_indentbeforelast = 0 335 336 let b:InPHPcode = 0 337 let b:InPHPcode_checked = 0 338 let b:InPHPcode_and_script = 0 339 let b:InPHPcode_tofind = "" 340 341 elseif v:lnum > b:PHP_lastindented " we are indenting line in > order (we can rely on the line before) 342 let real_PHP_lastindented = b:PHP_lastindented 343 let b:PHP_lastindented = v:lnum 344 endif 345 346 347 if !b:InPHPcode_checked " {{{ One time check 348 let b:InPHPcode_checked = 1 349 350 let synname = IslinePHP (prevnonblank(v:lnum), "") " the line could be blank (if the user presses 'return') 351 352 if synname!="" 353 if synname != "phpHereDoc" 354 let b:InPHPcode = 1 355 let b:InPHPcode_tofind = "" 356 357 if synname == "phpComment" 358 let b:UserIsTypingComment = 1 359 else 360 let b:UserIsTypingComment = 0 361 endif 362 363 if synname =~? '^javaScript' 364 let b:InPHPcode_and_script = 1 365 endif 366 367 else 368 let b:InPHPcode = 0 369 let b:UserIsTypingComment = 0 370 371 let lnum = v:lnum - 1 372 while getline(lnum) !~? '<<<\a\w*$' && lnum > 1 373 let lnum = lnum - 1 374 endwhile 375 376 let b:InPHPcode_tofind = substitute( getline(lnum), '^.*<<<\(\a\w*\)\c', '^\\s*\1;$', '') 377 endif 378 else " IslinePHP returned "" => we are not in PHP or Javascript 379 let b:InPHPcode = 0 380 let b:UserIsTypingComment = 0 381 " Then we have to find a php start tag... 382 let b:InPHPcode_tofind = '<?\%(.*?>\)\@!\|<script.*>' 383 endif 384 endif "!b:InPHPcode_checked }}} 385 386 387 let lnum = prevnonblank(v:lnum - 1) 388 let last_line = getline(lnum) 389 390 if b:InPHPcode_tofind!="" 391 if cline =~? b:InPHPcode_tofind 392 let b:InPHPcode = 1 393 let b:InPHPcode_tofind = "" 394 let b:UserIsTypingComment = 0 395 if cline =~ '\*/' " End comment tags must be indented like start comment tags 396 call cursor(v:lnum, 1) 397 call search('\*/\zs', 'W') 398 let lnum = searchpair('/\*', '', '\*/\zs', s:searchpairflags) " find the most outside /* 399 400 let b:PHP_CurrentIndentLevel = b:PHP_default_indenting 401 let b:PHP_LastIndentedWasComment = 0 " prevent a problem if multiline /**/ comment are surounded by 402 " other types of comments 403 404 if cline =~ '^\s*\*/' 405 return indent(lnum) + 1 406 else 407 return indent(lnum) 408 endif 409 410 elseif cline =~? '<script\>' " a more accurate test is useless since there isn't any other possibility 411 let b:InPHPcode_and_script = 1 412 endif 413 endif 414 endif 415 416 417 if b:InPHPcode 418 419 if !b:InPHPcode_and_script && last_line =~ '\%(<?.*\)\@<!?>\%(.*<?\)\@!' && IslinePHP(lnum, '?>')=="Delimiter" 420 if cline !~? s:PHP_startindenttag 421 let b:InPHPcode = 0 422 let b:InPHPcode_tofind = s:PHP_startindenttag 423 elseif cline =~? '<script\>' 424 let b:InPHPcode_and_script = 1 425 endif 426 427 elseif last_line =~? '<<<\a\w*$' 428 let b:InPHPcode = 0 429 let b:InPHPcode_tofind = substitute( last_line, '^.*<<<\(\a\w*\)\c', '^\\s*\1;$', '') 430 431 elseif !UserIsEditing && cline =~ '^\s*/\*\%(.*\*/\)\@!' && getline(v:lnum + 1) !~ '^\s*\*' " XXX indent comments 432 let b:InPHPcode = 0 433 let b:InPHPcode_tofind = '\*/' 434 435 elseif cline =~? '^\s*</script>' 436 let b:InPHPcode = 0 437 let b:InPHPcode_tofind = s:PHP_startindenttag 438 endif 439 endif " }}} 440 441 if !b:InPHPcode && !b:InPHPcode_and_script 442 return -1 443 endif 444 445 446 " Indent successive // or # comment the same way the first is {{{ 447 if cline =~ '^\s*\%(//\|#\|/\*.*\*/\s*$\)' 448 if b:PHP_LastIndentedWasComment == 1 449 return indent(real_PHP_lastindented) " line replaced in 1.02 450 endif 451 let b:PHP_LastIndentedWasComment = 1 452 else 453 let b:PHP_LastIndentedWasComment = 0 454 endif 455 " }}} 456 457 " Indent multiline /* comments correctly {{{ 458 459 460 if b:PHP_InsideMultilineComment || b:UserIsTypingComment 461 if cline =~ '^\s*\*\%(\/\)\@!' " if cline == '*' 462 if last_line =~ '^\s*/\*' " if last_line == '/*' 463 return indent(lnum) + 1 464 else 465 return indent(lnum) 466 endif 467 else 468 let b:PHP_InsideMultilineComment = 0 469 endif 470 endif 471 472 if !b:PHP_InsideMultilineComment && cline =~ '^\s*/\*' " if cline == '/*' 473 let b:PHP_InsideMultilineComment = 1 474 return -1 475 endif 476 " }}} 477 478 if cline =~# '^\s*<?' && cline !~ '?>' " Added the ^\s* part in version 1.03 479 return 0 480 endif 481 482 if cline =~ '^\s*?>' && cline !~# '<?' 483 return 0 484 endif 485 486 if cline =~? '^\s*\a\w*;$' && cline !~? s:notPhpHereDoc 487 return 0 488 endif 489 " }}} 490 491 let s:level = 0 492 493 let lnum = GetLastRealCodeLNum(v:lnum - 1) 494 let last_line = getline(lnum) " last line 495 let ind = indent(lnum) " by default 496 let endline= s:endline 497 498 if ind==0 && b:PHP_default_indenting 499 let ind = b:PHP_default_indenting 500 endif 501 502 if lnum == 0 503 return b:PHP_default_indenting 504 endif 505 506 507 if cline =~ '^\s*}\%(}}\)\@!' 508 let ind = indent(FindOpenBracket(v:lnum)) 509 let b:PHP_CurrentIndentLevel = b:PHP_default_indenting 510 return ind 511 endif 512 513 if cline =~ '^\s*\*/' " End comment tags must be indented like start comment tags 514 call cursor(v:lnum, 1) 515 call search('\*/\zs', 'W') 516 let lnum = searchpair('/\*', '', '\*/\zs', s:searchpairflags) " find the most outside /* 517 518 let b:PHP_CurrentIndentLevel = b:PHP_default_indenting 519 520 if cline =~ '^\s*\*/' 521 return indent(lnum) + 1 522 else 523 return indent(lnum) 524 endif 525 endif 526 527 let defaultORcase = '^\s*\%(default\|case\).*:' 528 529 if last_line =~ '[;}]'.endline && last_line !~# defaultORcase 530 if ind==b:PHP_default_indenting " if no indentation for the previous line 531 return b:PHP_default_indenting 532 elseif b:PHP_indentinghuge && ind==b:PHP_CurrentIndentLevel && cline !~# '^\s*\%(else\|\%(case\|default\).*:\|[})];\=\)' && last_line !~# '^\s*\%(\%(}\s*\)\=else\)' && getline(GetLastRealCodeLNum(lnum - 1))=~';'.endline 533 return b:PHP_CurrentIndentLevel 534 endif 535 endif 536 537 let LastLineClosed = 0 " used to prevent redundant tests in the last part of the script 538 539 let terminated = '\%(;\%(\s*?>\)\=\|<<<\a\w*\|}\)'.endline 540 541 let unstated = '\%(^\s*'.s:blockstart.'.*)\|\%(//.*\)\@<!\<e'.'lse\>\)'.endline 542 543 if ind != b:PHP_default_indenting && cline =~# '^\s*else\%(if\)\=\>' 544 let b:PHP_CurrentIndentLevel = b:PHP_default_indenting " prevent optimized to work at next call 545 return indent(FindTheIfOfAnElse(v:lnum, 1)) 546 elseif last_line =~# unstated && cline !~ '^\s*{\|^\s*);\='.endline 547 let ind = ind + &sw 548 return ind 549 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 557 while 1 558 if previous_line =~ '^\s*}' 559 let last_line_num = FindOpenBracket(last_line_num) 560 561 if getline(last_line_num) =~ '^\s*{' 562 let last_line_num = GetLastRealCodeLNum(last_line_num - 1) 563 endif 564 565 let previous_line = getline(last_line_num) 566 567 continue 568 else 569 if getline(last_line_num) =~# '^\s*else\%(if\)\=\>' 570 let last_line_num = FindTheIfOfAnElse(last_line_num, 0) 571 continue " re-run the loop (we could find a '}' again) 572 endif 573 574 575 let last_match = last_line_num " remember the 'topest' line we found so far 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 " if nothing was done lets the old script continue 601 let ind = indent(last_match) " let's use the indent of the last line matched by the alhorithm above 602 let b:PHP_CurrentIndentLevel = b:PHP_default_indenting " line added in version 1.02 to prevent optimized mode 603 " from acting in some special cases 604 605 if cline =~# defaultORcase 606 let ind = ind - &sw 607 endif 608 return ind 609 endif 610 endif 611 612 let plinnum = GetLastRealCodeLNum(lnum - 1) 613 let pline = getline(plinnum) " previous to last line 614 615 let last_line = substitute(last_line,"\\(//\\|#\\)\\(\\(\\([^\"']*\\([\"']\\)[^\"']*\\5\\)\\+[^\"']*$\\)\\|\\([^\"']*$\\)\\)",'','') 616 617 618 if ind == b:PHP_default_indenting 619 if last_line =~ terminated 620 let LastLineClosed = 1 621 endif 622 endif 623 624 if !LastLineClosed " the last line isn't a .*; or a }$ line 625 if last_line =~# '[{(]'.endline || last_line =~? '\h\w*\s*(.*,$' && pline !~ '[,(]'.endline 626 627 if !b:PHP_BracesAtCodeLevel || last_line !~# '^\s*{' " XXX mod { 628 let ind = ind + &sw 629 endif 630 631 if b:PHP_BracesAtCodeLevel || cline !~# defaultORcase " XXX mod (2) { 632 " case and default are not indented inside blocks 633 let b:PHP_CurrentIndentLevel = ind 634 return ind 635 endif 636 637 elseif last_line =~ '\S\+\s*),'.endline 638 call cursor(lnum, 1) 639 call search('),'.endline, 'W') 640 let openedparent = searchpair('(', '', ')', 'bW', 'Skippmatch()') 641 if openedparent != lnum 642 let ind = indent(openedparent) 643 endif 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 if b:PHP_BracesAtCodeLevel && cline =~# '^\s*{' " XXX mod { 651 let ind = ind + &sw 652 endif 653 654 elseif last_line =~# defaultORcase 655 let ind = ind + &sw 656 endif 657 658 if cline =~ '^\s*);\=' 659 let ind = ind - &sw 660 elseif cline =~# defaultORcase 661 let ind = ind - &sw 662 663 endif 664 665 let b:PHP_CurrentIndentLevel = ind 666 return ind 667endfunction 668 669" vim: set ts=4 sw=4: 670" vim: set ff=unix: 671