3 " Author: John Wellesz <John.wellesz (AT) teaser (DOT) fr>
4 " URL: http://www.2072productions.com/vim/indent/php.vim
5 " Last Change: 2007 Jun 24
6 " Newsletter: http://www.2072productions.com/?to=php-indent-for-vim-newsletter.php
9 " The change log and all the comments have been removed from this file.
11 " For a complete change log and fully commented code, download the script on
12 " 2072productions.com at the URI provided above.
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.
18 " Thanks a lot for using this script.
21 " NOTE: This script must be used with PHP syntax ON and with the php syntax
22 " script by Lutz Eymers ( http://www.isp.de/data/php.vim ) that's the script bundled with Vim.
25 " In the case you have syntax errors in your script such as end of HereDoc
26 " tags not at col 1 you'll have to indent your file 2 times (This script
27 " will automatically put HereDoc end tags at col 1).
30 " NOTE: If you are editing file in Unix file format and that (by accident)
31 " there are '\r' before new lines, this script won't be able to proceed
32 " correctly and will make many mistakes because it won't be able to match
34 " So you have to remove those useless characters first with a command like:
38 " or simply 'let' the option PHP_removeCRwhenUnix to 1 and the script will
39 " silently remove them when VIM load this script (at each bufread).
42 " Options: PHP_autoformatcomment = 0 to not enable autoformating of comment by
43 " default, if set to 0, this script will let the 'formatoptions' setting intact.
45 " Options: PHP_default_indenting = # of sw (default is 0), # of sw will be
46 " added to the indent of each line of PHP code.
48 " Options: PHP_removeCRwhenUnix = 1 to make the script automatically remove CR
49 " at end of lines (by default this option is unset), NOTE that you
50 " MUST remove CR when the fileformat is UNIX else the indentation
53 " Options: PHP_BracesAtCodeLevel = 1 to indent the '{' and '}' at the same
54 " level than the code they contain.
68 " NOTE: The script will be a bit slower if you use this option because
69 " some optimizations won't be available.
71 if exists("b:did_indent")
77 let php_sync_method = 0
80 if exists("PHP_default_indenting")
81 let b:PHP_default_indenting = PHP_default_indenting * &sw
83 let b:PHP_default_indenting = 0
86 if exists("PHP_BracesAtCodeLevel")
87 let b:PHP_BracesAtCodeLevel = PHP_BracesAtCodeLevel
89 let b:PHP_BracesAtCodeLevel = 0
92 if exists("PHP_autoformatcomment")
93 let b:PHP_autoformatcomment = PHP_autoformatcomment
95 let b:PHP_autoformatcomment = 1
98 let b:PHP_lastindented = 0
99 let b:PHP_indentbeforelast = 0
100 let b:PHP_indentinghuge = 0
101 let b:PHP_CurrentIndentLevel = b:PHP_default_indenting
102 let b:PHP_LastIndentedWasComment = 0
103 let b:PHP_InsideMultilineComment = 0
105 let b:InPHPcode_checked = 0
106 let b:InPHPcode_and_script = 0
107 let b:InPHPcode_tofind = ""
108 let b:PHP_oldchangetick = b:changedtick
109 let b:UserIsTypingComment = 0
112 setlocal nosmartindent
113 setlocal noautoindent
117 setlocal indentexpr=GetPhpIndent()
118 setlocal indentkeys=0{,0},0),:,!^F,o,O,e,*<Return>,=?>,=<?,=*/
122 let s:searchpairflags = 'bWr'
124 if &fileformat == "unix" && exists("PHP_removeCRwhenUnix") && PHP_removeCRwhenUnix
128 if exists("*GetPhpIndent")
132 let s:endline= '\s*\%(//.*\|#.*\|/\*.*\*/\s*\)\=$'
133 let s:PHP_startindenttag = '<?\%(.*?>\)\@!\|<script[^>]*>\%(.*<\/script>\)\@!'
134 "setlocal debug=msg " XXX
137 function! GetLastRealCodeLNum(startline) " {{{
139 let lnum = a:startline
141 if b:GetLastRealCodeLNum_ADD && b:GetLastRealCodeLNum_ADD == lnum + 1
142 let lnum = b:GetLastRealCodeLNum_ADD
148 let lnum = prevnonblank(lnum)
149 let lastline = getline(lnum)
151 if b:InPHPcode_and_script && lastline =~ '?>\s*$'
153 elseif lastline =~ '^\s*?>.*<?\%(php\)\=\s*$'
155 elseif lastline =~ '^\s*\%(//\|#\|/\*.*\*/\s*$\)'
157 elseif lastline =~ '\*/\s*$'
159 if lastline !~ '^\*/'
160 call search('\*/', 'W')
162 let lnum = searchpair('/\*', '', '\*/', s:searchpairflags, 'Skippmatch2()')
164 let lastline = getline(lnum)
165 if lastline =~ '^\s*/\*'
172 elseif lastline =~? '\%(//\s*\|?>.*\)\@<!<?\%(php\)\=\s*$\|^\s*<script\>'
174 while lastline !~ '\(<?.*\)\@<!?>' && lnum > 1
176 let lastline = getline(lnum)
178 if lastline =~ '^\s*?>'
185 elseif lastline =~? '^\a\w*;$' && lastline !~? s:notPhpHereDoc
186 let tofind=substitute( lastline, '\([^;]\+\);', '<<<\1$', '')
187 while getline(lnum) !~? tofind && lnum > 1
195 if lnum==1 && getline(lnum)!~ '<?'
199 if b:InPHPcode_and_script && !b:InPHPcode
200 let b:InPHPcode_and_script = 0
208 function! Skippmatch2()
210 let line = getline(".")
212 if line =~ '\%(".*\)\@<=/\*\%(.*"\)\@=' || line =~ '\%(//.*\)\@<=/\*'
219 function! Skippmatch() " {{{
220 let synname = synIDattr(synID(line("."), col("."), 0), "name")
221 if synname == "Delimiter" || synname == "phpRegionDelimiter" || synname =~# "^phpParent" || synname == "phpArrayParens" || synname =~# '^php\%(Block\|Brace\)' || synname == "javaScriptBraces" || synname == "phpComment" && b:UserIsTypingComment
228 function! FindOpenBracket(lnum) " {{{
229 call cursor(a:lnum, 1)
230 return searchpair('{', '', '}', 'bW', 'Skippmatch()')
233 function! FindTheIfOfAnElse (lnum, StopAfterFirstPrevElse) " {{{
235 if getline(a:lnum) =~# '^\s*}\s*else\%(if\)\=\>'
236 let beforeelse = a:lnum
238 let beforeelse = GetLastRealCodeLNum(a:lnum - 1)
245 if getline(beforeelse) =~# '^\s*\%(}\s*\)\=else\%(\s*if\)\@!\>'
246 let s:iftoskip = s:iftoskip + 1
249 if getline(beforeelse) =~ '^\s*}'
250 let beforeelse = FindOpenBracket(beforeelse)
252 if getline(beforeelse) =~ '^\s*{'
253 let beforeelse = GetLastRealCodeLNum(beforeelse - 1)
258 if !s:iftoskip && a:StopAfterFirstPrevElse && getline(beforeelse) =~# '^\s*\%([}]\s*\)\=else\%(if\)\=\>'
262 if getline(beforeelse) !~# '^\s*if\>' && beforeelse>1 || s:iftoskip && beforeelse>1
264 if s:iftoskip && getline(beforeelse) =~# '^\s*if\>'
265 let s:iftoskip = s:iftoskip - 1
268 let s:level = s:level + 1
269 let beforeelse = FindTheIfOfAnElse(beforeelse, a:StopAfterFirstPrevElse)
276 function! IslinePHP (lnum, tofind) " {{{
277 let cline = getline(a:lnum)
280 let tofind = "^\\s*[\"']*\\s*\\zs\\S"
282 let tofind = a:tofind
285 let tofind = tofind . '\c'
287 let coltotest = match (cline, tofind) + 1
289 let synname = synIDattr(synID(a:lnum, coltotest, 0), "name")
291 if synname =~ '^php' || synname=="Delimiter" || synname =~? '^javaScript'
298 let s:notPhpHereDoc = '\%(break\|return\|continue\|exit\);'
299 let s:blockstart = '\%(\%(\%(}\s*\)\=else\%(\s\+\)\=\)\=if\>\|else\>\|while\>\|switch\>\|for\%(each\)\=\>\|declare\>\|class\>\|interface\>\|abstract\>\|try\>\|catch\>\|[|&]\)'
301 let s:autorestoptions = 0
302 if ! s:autorestoptions
303 au BufWinEnter,Syntax *.php,*.php3,*.php4,*.php5 call ResetOptions()
304 let s:autorestoptions = 1
307 function! ResetOptions()
309 if b:PHP_autoformatcomment
311 setlocal comments=s1:/*,mb:*,ex:*/,://,:#
313 " setlocal formatoptions-=t
314 setlocal formatoptions+=q
315 setlocal formatoptions+=r
316 setlocal formatoptions+=o
317 " setlocal formatoptions+=w
318 setlocal formatoptions+=c
319 setlocal formatoptions+=b
325 function! GetPhpIndent()
327 let b:GetLastRealCodeLNum_ADD = 0
330 if b:PHP_oldchangetick != b:changedtick
331 let b:PHP_oldchangetick = b:changedtick
335 if b:PHP_default_indenting
336 let b:PHP_default_indenting = g:PHP_default_indenting * &sw
339 let cline = getline(v:lnum)
341 if !b:PHP_indentinghuge && b:PHP_lastindented > b:PHP_indentbeforelast
342 if b:PHP_indentbeforelast
343 let b:PHP_indentinghuge = 1
344 echom 'Large indenting detected, speed optimizations engaged'
346 let b:PHP_indentbeforelast = b:PHP_lastindented
349 if b:InPHPcode_checked && prevnonblank(v:lnum - 1) != b:PHP_lastindented
350 if b:PHP_indentinghuge
351 echom 'Large indenting deactivated'
352 let b:PHP_indentinghuge = 0
353 let b:PHP_CurrentIndentLevel = b:PHP_default_indenting
355 let b:PHP_lastindented = v:lnum
356 let b:PHP_LastIndentedWasComment=0
357 let b:PHP_InsideMultilineComment=0
358 let b:PHP_indentbeforelast = 0
361 let b:InPHPcode_checked = 0
362 let b:InPHPcode_and_script = 0
363 let b:InPHPcode_tofind = ""
365 elseif v:lnum > b:PHP_lastindented
366 let real_PHP_lastindented = b:PHP_lastindented
367 let b:PHP_lastindented = v:lnum
371 if !b:InPHPcode_checked " {{{ One time check
372 let b:InPHPcode_checked = 1
376 let synname = IslinePHP (prevnonblank(v:lnum), "")
380 if synname != "phpHereDoc" && synname != "phpHereDocDelimiter"
382 let b:InPHPcode_tofind = ""
384 if synname == "phpComment"
385 let b:UserIsTypingComment = 1
387 let b:UserIsTypingComment = 0
390 if synname =~? '^javaScript'
391 let b:InPHPcode_and_script = 1
396 let b:UserIsTypingComment = 0
398 let lnum = v:lnum - 1
399 while getline(lnum) !~? '<<<\a\w*$' && lnum > 1
403 let b:InPHPcode_tofind = substitute( getline(lnum), '^.*<<<\(\a\w*\)\c', '^\\s*\1;$', '')
407 let b:UserIsTypingComment = 0
408 let b:InPHPcode_tofind = '<?\%(.*?>\)\@!\|<script.*>'
410 endif "!b:InPHPcode_checked }}}
413 " Test if we are indenting PHP code {{{
414 let lnum = prevnonblank(v:lnum - 1)
415 let last_line = getline(lnum)
417 if b:InPHPcode_tofind!=""
418 if cline =~? b:InPHPcode_tofind
420 let b:InPHPcode_tofind = ""
421 let b:UserIsTypingComment = 0
423 call cursor(v:lnum, 1)
425 call search('\*/', 'W')
427 let lnum = searchpair('/\*', '', '\*/', s:searchpairflags, 'Skippmatch2()')
429 let b:PHP_CurrentIndentLevel = b:PHP_default_indenting
431 let b:PHP_LastIndentedWasComment = 0
433 if cline =~ '^\s*\*/'
434 return indent(lnum) + 1
439 elseif cline =~? '<script\>'
440 let b:InPHPcode_and_script = 1
441 let b:GetLastRealCodeLNum_ADD = v:lnum
448 if !b:InPHPcode_and_script && last_line =~ '\%(<?.*\)\@<!?>\%(.*<?\)\@!' && IslinePHP(lnum, '?>')=~"Delimiter"
449 if cline !~? s:PHP_startindenttag
451 let b:InPHPcode_tofind = s:PHP_startindenttag
452 elseif cline =~? '<script\>'
453 let b:InPHPcode_and_script = 1
456 elseif last_line =~? '<<<\a\w*$'
458 let b:InPHPcode_tofind = substitute( last_line, '^.*<<<\(\a\w*\)\c', '^\\s*\1;$', '')
460 elseif !UserIsEditing && cline =~ '^\s*/\*\%(.*\*/\)\@!' && getline(v:lnum + 1) !~ '^\s*\*'
462 let b:InPHPcode_tofind = '\*/'
464 elseif cline =~? '^\s*</script>'
466 let b:InPHPcode_tofind = s:PHP_startindenttag
471 if !b:InPHPcode && !b:InPHPcode_and_script
475 " Indent successive // or # comment the same way the first is {{{
476 if cline =~ '^\s*\%(//\|#\|/\*.*\*/\s*$\)'
477 if b:PHP_LastIndentedWasComment == 1
478 return indent(real_PHP_lastindented)
480 let b:PHP_LastIndentedWasComment = 1
482 let b:PHP_LastIndentedWasComment = 0
485 " Indent multiline /* comments correctly {{{
487 if b:PHP_InsideMultilineComment || b:UserIsTypingComment
488 if cline =~ '^\s*\*\%(\/\)\@!'
489 if last_line =~ '^\s*/\*'
490 return indent(lnum) + 1
495 let b:PHP_InsideMultilineComment = 0
499 if !b:PHP_InsideMultilineComment && cline =~ '^\s*/\*' && cline !~ '\*/\s*$'
500 if getline(v:lnum + 1) !~ '^\s*\*'
503 let b:PHP_InsideMultilineComment = 1
507 " Things always indented at col 1 (PHP delimiter: <?, ?>, Heredoc end) {{{
508 if cline =~# '^\s*<?' && cline !~ '?>'
512 if cline =~ '^\s*?>' && cline !~# '<?'
516 if cline =~? '^\s*\a\w*;$' && cline !~? s:notPhpHereDoc
522 let lnum = GetLastRealCodeLNum(v:lnum - 1)
524 let last_line = getline(lnum)
525 let ind = indent(lnum)
526 let endline= s:endline
528 if ind==0 && b:PHP_default_indenting
529 let ind = b:PHP_default_indenting
533 return b:PHP_default_indenting
537 if cline =~ '^\s*}\%(}}\)\@!'
538 let ind = indent(FindOpenBracket(v:lnum))
539 let b:PHP_CurrentIndentLevel = b:PHP_default_indenting
543 if cline =~ '^\s*\*/'
544 call cursor(v:lnum, 1)
546 call search('\*/', 'W')
548 let lnum = searchpair('/\*', '', '\*/', s:searchpairflags, 'Skippmatch2()')
550 let b:PHP_CurrentIndentLevel = b:PHP_default_indenting
552 if cline =~ '^\s*\*/'
553 return indent(lnum) + 1
559 let defaultORcase = '^\s*\%(default\|case\).*:'
561 if last_line =~ '[;}]'.endline && last_line !~# defaultORcase
562 if ind==b:PHP_default_indenting
563 return b:PHP_default_indenting
564 elseif b:PHP_indentinghuge && ind==b:PHP_CurrentIndentLevel && cline !~# '^\s*\%(else\|\%(case\|default\).*:\|[})];\=\)' && last_line !~# '^\s*\%(\%(}\s*\)\=else\)' && getline(GetLastRealCodeLNum(lnum - 1))=~';'.endline
565 return b:PHP_CurrentIndentLevel
569 let LastLineClosed = 0
571 let terminated = '\%(;\%(\s*?>\)\=\|<<<\a\w*\|}\)'.endline
573 let unstated = '\%(^\s*'.s:blockstart.'.*)\|\%(//.*\)\@<!\<e'.'lse\>\)'.endline
575 if ind != b:PHP_default_indenting && cline =~# '^\s*else\%(if\)\=\>'
576 let b:PHP_CurrentIndentLevel = b:PHP_default_indenting
577 return indent(FindTheIfOfAnElse(v:lnum, 1))
578 elseif cline =~ '^\s*{'
579 let previous_line = last_line
580 let last_line_num = lnum
582 while last_line_num > 1
584 if previous_line =~ '^\s*\%(' . s:blockstart . '\|\%([a-zA-Z]\s*\)*function\)' && previous_line !~ '^\s*[|&]'
586 let ind = indent(last_line_num)
588 if b:PHP_BracesAtCodeLevel
595 let last_line_num = last_line_num - 1
596 let previous_line = getline(last_line_num)
599 elseif last_line =~# unstated && cline !~ '^\s*{\|^\s*);\='.endline
603 elseif ind != b:PHP_default_indenting && last_line =~ terminated
604 let previous_line = last_line
605 let last_line_num = lnum
606 let LastLineClosed = 1
609 if previous_line =~ '^\s*}'
610 let last_line_num = FindOpenBracket(last_line_num)
612 if getline(last_line_num) =~ '^\s*{'
613 let last_line_num = GetLastRealCodeLNum(last_line_num - 1)
616 let previous_line = getline(last_line_num)
621 if getline(last_line_num) =~# '^\s*else\%(if\)\=\>'
622 let last_line_num = FindTheIfOfAnElse(last_line_num, 0)
627 let last_match = last_line_num
629 let one_ahead_indent = indent(last_line_num)
630 let last_line_num = GetLastRealCodeLNum(last_line_num - 1)
631 let two_ahead_indent = indent(last_line_num)
632 let after_previous_line = previous_line
633 let previous_line = getline(last_line_num)
636 if previous_line =~# defaultORcase.'\|{'.endline
640 if after_previous_line=~# '^\s*'.s:blockstart.'.*)'.endline && previous_line =~# '[;}]'.endline
644 if one_ahead_indent == two_ahead_indent || last_line_num < 1
645 if previous_line =~# '[;}]'.endline || last_line_num < 1
652 if indent(last_match) != ind
653 let ind = indent(last_match)
654 let b:PHP_CurrentIndentLevel = b:PHP_default_indenting
656 if cline =~# defaultORcase
663 let plinnum = GetLastRealCodeLNum(lnum - 1)
664 let pline = getline(plinnum)
666 let last_line = substitute(last_line,"\\(//\\|#\\)\\(\\(\\([^\"']*\\([\"']\\)[^\"']*\\5\\)\\+[^\"']*$\\)\\|\\([^\"']*$\\)\\)",'','')
669 if ind == b:PHP_default_indenting
670 if last_line =~ terminated
671 let LastLineClosed = 1
677 if last_line =~# '[{(]'.endline || last_line =~? '\h\w*\s*(.*,$' && pline !~ '[,(]'.endline
679 if !b:PHP_BracesAtCodeLevel || last_line !~# '^\s*{'
683 if b:PHP_BracesAtCodeLevel || cline !~# defaultORcase
684 let b:PHP_CurrentIndentLevel = ind
688 elseif last_line =~ '\S\+\s*),'.endline
690 call search('),'.endline, 'W')
691 let openedparent = searchpair('(', '', ')', 'bW', 'Skippmatch()')
692 if openedparent != lnum
693 let ind = indent(openedparent)
697 elseif cline !~ '^\s*{' && pline =~ '\%(;\%(\s*?>\)\=\|<<<\a\w*\|{\|^\s*'.s:blockstart.'\s*(.*)\)'.endline.'\|^\s*}\|'.defaultORcase
703 elseif last_line =~# defaultORcase
707 if cline =~ '^\s*);\='
709 elseif cline =~# defaultORcase
714 let b:PHP_CurrentIndentLevel = ind