Runtime files update
[MacVim.git] / runtime / indent / php.vim
blobd73460d07dba8a797be4201d7cee39b28e90581d
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:  2007 Jun 24
6 " Newsletter:   http://www.2072productions.com/?to=php-indent-for-vim-newsletter.php
7 " Version:      1.24
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
33 " '\s*$' correctly.
34 " So you have to remove those useless characters first with a command like:
36 " :%s /\r$//g
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
51 "                  won't be correct...
53 " Options: PHP_BracesAtCodeLevel = 1 to indent the '{' and '}' at the same
54 "                  level than the code they contain.
55 "                  Exemple:
56 "                       Instead of:
57 "                               if ($foo)
58 "                               {
59 "                                       foo();
60 "                               }
62 "                       You will write:
63 "                               if ($foo)
64 "                                       {
65 "                                       foo();
66 "                                       }
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")
72     finish
73 endif
74 let b:did_indent = 1
77 let php_sync_method = 0
80 if exists("PHP_default_indenting")
81     let b:PHP_default_indenting = PHP_default_indenting * &sw
82 else
83     let b:PHP_default_indenting = 0
84 endif
86 if exists("PHP_BracesAtCodeLevel")
87     let b:PHP_BracesAtCodeLevel = PHP_BracesAtCodeLevel
88 else
89     let b:PHP_BracesAtCodeLevel = 0
90 endif
92 if exists("PHP_autoformatcomment")
93     let b:PHP_autoformatcomment = PHP_autoformatcomment
94 else
95     let b:PHP_autoformatcomment = 1
96 endif
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
104 let b:InPHPcode = 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
110 let b:optionsset = 0
112 setlocal nosmartindent
113 setlocal noautoindent
114 setlocal nocindent
115 setlocal nolisp
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
125     silent! %s/\r$//g
126 endif
128 if exists("*GetPhpIndent")
129     finish " XXX
130 endif
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
143     endif
145     let old_lnum = lnum
147     while lnum > 1
148         let lnum = prevnonblank(lnum)
149         let lastline = getline(lnum)
151         if b:InPHPcode_and_script && lastline =~ '?>\s*$'
152             let lnum = lnum - 1
153         elseif lastline =~ '^\s*?>.*<?\%(php\)\=\s*$'
154             let lnum = lnum - 1
155         elseif lastline =~ '^\s*\%(//\|#\|/\*.*\*/\s*$\)'
156             let lnum = lnum - 1
157         elseif lastline =~ '\*/\s*$'
158             call cursor(lnum, 1)
159             if lastline !~ '^\*/'
160                 call search('\*/', 'W')
161             endif
162             let lnum = searchpair('/\*', '', '\*/', s:searchpairflags, 'Skippmatch2()')
164             let lastline = getline(lnum)
165             if lastline =~ '^\s*/\*'
166                 let lnum = lnum - 1
167             else
168                 break
169             endif
172         elseif lastline =~? '\%(//\s*\|?>.*\)\@<!<?\%(php\)\=\s*$\|^\s*<script\>'
174             while lastline !~ '\(<?.*\)\@<!?>' && lnum > 1
175                 let lnum = lnum - 1
176                 let lastline = getline(lnum)
177             endwhile
178             if lastline =~ '^\s*?>'
179                 let lnum = lnum - 1
180             else
181                 break
182             endif
185         elseif lastline =~? '^\a\w*;$' && lastline !~? s:notPhpHereDoc
186             let tofind=substitute( lastline, '\([^;]\+\);', '<<<\1$', '')
187             while getline(lnum) !~? tofind && lnum > 1
188                 let lnum = lnum - 1
189             endwhile
190         else
191             break
192         endif
193     endwhile
195     if lnum==1 && getline(lnum)!~ '<?'
196         let lnum=0
197     endif
199     if b:InPHPcode_and_script && !b:InPHPcode
200         let b:InPHPcode_and_script = 0
201     endif
205     return lnum
206 endfunction " }}}
208 function! Skippmatch2()
210     let line = getline(".")
212    if line =~ '\%(".*\)\@<=/\*\%(.*"\)\@=' || line =~ '\%(//.*\)\@<=/\*'
213        return 1
214    else
215        return 0
216    endif
217 endfun
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
222         return 0
223     else
224         return 1
225     endif
226 endfun " }}}
228 function! FindOpenBracket(lnum) " {{{
229     call cursor(a:lnum, 1)
230     return searchpair('{', '', '}', 'bW', 'Skippmatch()')
231 endfun " }}}
233 function! FindTheIfOfAnElse (lnum, StopAfterFirstPrevElse) " {{{
235     if getline(a:lnum) =~# '^\s*}\s*else\%(if\)\=\>'
236         let beforeelse = a:lnum
237     else
238         let beforeelse = GetLastRealCodeLNum(a:lnum - 1)
239     endif
241     if !s:level
242         let s:iftoskip = 0
243     endif
245     if getline(beforeelse) =~# '^\s*\%(}\s*\)\=else\%(\s*if\)\@!\>'
246         let s:iftoskip = s:iftoskip + 1
247     endif
249     if getline(beforeelse) =~ '^\s*}'
250         let beforeelse = FindOpenBracket(beforeelse)
252         if getline(beforeelse) =~ '^\s*{'
253             let beforeelse = GetLastRealCodeLNum(beforeelse - 1)
254         endif
255     endif
258     if !s:iftoskip && a:StopAfterFirstPrevElse && getline(beforeelse) =~# '^\s*\%([}]\s*\)\=else\%(if\)\=\>'
259         return beforeelse
260     endif
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
266         endif
268         let s:level =  s:level + 1
269         let beforeelse = FindTheIfOfAnElse(beforeelse, a:StopAfterFirstPrevElse)
270     endif
272     return beforeelse
274 endfunction " }}}
276 function! IslinePHP (lnum, tofind) " {{{
277     let cline = getline(a:lnum)
279     if a:tofind==""
280         let tofind = "^\\s*[\"']*\\s*\\zs\\S"
281     else
282         let tofind = a:tofind
283     endif
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'
292         return synname
293     else
294         return ""
295     endif
296 endfunction " }}}
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
305 endif
307 function! ResetOptions()
308     if ! b:optionsset
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
320         endif
321         let b:optionsset = 1
322     endif
323 endfunc
325 function! GetPhpIndent()
327     let b:GetLastRealCodeLNum_ADD = 0
329     let UserIsEditing=0
330     if  b:PHP_oldchangetick != b:changedtick
331         let b:PHP_oldchangetick = b:changedtick
332         let UserIsEditing=1
333     endif
335     if b:PHP_default_indenting
336         let b:PHP_default_indenting = g:PHP_default_indenting * &sw
337     endif
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'
345         endif
346         let b:PHP_indentbeforelast = b:PHP_lastindented
347     endif
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
354         endif
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
360         let b:InPHPcode = 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
368     endif
371     if !b:InPHPcode_checked " {{{ One time check
372         let b:InPHPcode_checked = 1
374         let synname = ""
375         if cline !~ '<?.*?>'
376             let synname = IslinePHP (prevnonblank(v:lnum), "")
377         endif
379         if synname!=""
380             if synname != "phpHereDoc" && synname != "phpHereDocDelimiter"
381                 let b:InPHPcode = 1
382                 let b:InPHPcode_tofind = ""
384                 if synname == "phpComment"
385                     let b:UserIsTypingComment = 1
386                 else
387                     let b:UserIsTypingComment = 0
388                 endif
390                 if synname =~? '^javaScript'
391                     let b:InPHPcode_and_script = 1
392                 endif
394             else
395                 let b:InPHPcode = 0
396                 let b:UserIsTypingComment = 0
398                 let lnum = v:lnum - 1
399                 while getline(lnum) !~? '<<<\a\w*$' && lnum > 1
400                     let lnum = lnum - 1
401                 endwhile
403                 let b:InPHPcode_tofind = substitute( getline(lnum), '^.*<<<\(\a\w*\)\c', '^\\s*\1;$', '')
404             endif
405         else
406             let b:InPHPcode = 0
407             let b:UserIsTypingComment = 0
408             let b:InPHPcode_tofind = '<?\%(.*?>\)\@!\|<script.*>'
409         endif
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
419             let b:InPHPcode = 1
420             let b:InPHPcode_tofind = ""
421             let b:UserIsTypingComment = 0
422             if cline =~ '\*/'
423                 call cursor(v:lnum, 1)
424                 if cline !~ '^\*/'
425                     call search('\*/', 'W')
426                 endif
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
435                 else
436                     return indent(lnum)
437                 endif
439             elseif cline =~? '<script\>'
440                 let b:InPHPcode_and_script = 1
441                 let b:GetLastRealCodeLNum_ADD = v:lnum
442             endif
443         endif
444     endif
446     if b:InPHPcode
448         if !b:InPHPcode_and_script && last_line =~ '\%(<?.*\)\@<!?>\%(.*<?\)\@!' && IslinePHP(lnum, '?>')=~"Delimiter"
449             if cline !~? s:PHP_startindenttag
450                 let b:InPHPcode = 0
451                 let b:InPHPcode_tofind = s:PHP_startindenttag
452             elseif cline =~? '<script\>'
453                 let b:InPHPcode_and_script = 1
454             endif
456         elseif last_line =~? '<<<\a\w*$'
457             let b:InPHPcode = 0
458             let b:InPHPcode_tofind = substitute( last_line, '^.*<<<\(\a\w*\)\c', '^\\s*\1;$', '')
460         elseif !UserIsEditing && cline =~ '^\s*/\*\%(.*\*/\)\@!' && getline(v:lnum + 1) !~ '^\s*\*'
461             let b:InPHPcode = 0
462             let b:InPHPcode_tofind = '\*/'
464         elseif cline =~? '^\s*</script>'
465             let b:InPHPcode = 0
466             let b:InPHPcode_tofind = s:PHP_startindenttag
467         endif
468     endif " }}}
471     if !b:InPHPcode && !b:InPHPcode_and_script
472         return -1
473     endif
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)
479         endif
480         let b:PHP_LastIndentedWasComment = 1
481     else
482         let b:PHP_LastIndentedWasComment = 0
483     endif " }}}
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
491             else
492                 return indent(lnum)
493             endif
494         else
495             let b:PHP_InsideMultilineComment = 0
496         endif
497     endif
499     if !b:PHP_InsideMultilineComment && cline =~ '^\s*/\*' && cline !~ '\*/\s*$'
500         if getline(v:lnum + 1) !~ '^\s*\*'
501             return -1
502         endif
503         let b:PHP_InsideMultilineComment = 1
504     endif " }}}
507     " Things always indented at col 1 (PHP delimiter: <?, ?>, Heredoc end) {{{
508     if cline =~# '^\s*<?' && cline !~ '?>'
509         return 0
510     endif
512     if  cline =~ '^\s*?>' && cline !~# '<?'
513         return 0
514     endif
516     if cline =~? '^\s*\a\w*;$' && cline !~? s:notPhpHereDoc
517         return 0
518     endif " }}}
520     let s:level = 0
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
530     endif
532     if lnum == 0
533         return b:PHP_default_indenting
534     endif
537     if cline =~ '^\s*}\%(}}\)\@!'
538         let ind = indent(FindOpenBracket(v:lnum))
539         let b:PHP_CurrentIndentLevel = b:PHP_default_indenting
540         return ind
541     endif
543     if cline =~ '^\s*\*/'
544         call cursor(v:lnum, 1)
545         if cline !~ '^\*/'
546             call search('\*/', 'W')
547         endif
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
554         else
555             return indent(lnum)
556         endif
557     endif
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
566         endif
567     endif
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
589                     let ind = ind + &sw
590                 endif
592                 return ind
593             endif
595             let last_line_num = last_line_num - 1
596             let previous_line = getline(last_line_num)
597         endwhile
599     elseif last_line =~# unstated && cline !~ '^\s*{\|^\s*);\='.endline
600         let ind = ind + &sw
601         return ind
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
608         while 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)
614                 endif
616                 let previous_line = getline(last_line_num)
618                 continue
619             else
621                 if getline(last_line_num) =~# '^\s*else\%(if\)\=\>'
622                     let last_line_num = FindTheIfOfAnElse(last_line_num, 0)
623                     continue
624                 endif
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
637                     break
638                 endif
640                 if after_previous_line=~# '^\s*'.s:blockstart.'.*)'.endline && previous_line =~# '[;}]'.endline
641                     break
642                 endif
644                 if one_ahead_indent == two_ahead_indent || last_line_num < 1
645                     if previous_line =~# '[;}]'.endline || last_line_num < 1
646                         break
647                     endif
648                 endif
649             endif
650         endwhile
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
657                 let ind = ind - &sw
658             endif
659             return ind
660         endif
661     endif
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
672         endif
673     endif
675     if !LastLineClosed
677         if last_line =~# '[{(]'.endline || last_line =~? '\h\w*\s*(.*,$' && pline !~ '[,(]'.endline
679             if !b:PHP_BracesAtCodeLevel || last_line !~# '^\s*{'
680                 let ind = ind + &sw
681             endif
683             if b:PHP_BracesAtCodeLevel || cline !~# defaultORcase
684                 let b:PHP_CurrentIndentLevel = ind
685                 return ind
686             endif
688         elseif last_line =~ '\S\+\s*),'.endline
689             call cursor(lnum, 1)
690             call search('),'.endline, 'W')
691             let openedparent = searchpair('(', '', ')', 'bW', 'Skippmatch()')
692             if openedparent != lnum
693                 let ind = indent(openedparent)
694             endif
697         elseif cline !~ '^\s*{' && pline =~ '\%(;\%(\s*?>\)\=\|<<<\a\w*\|{\|^\s*'.s:blockstart.'\s*(.*)\)'.endline.'\|^\s*}\|'.defaultORcase
699             let ind = ind + &sw
701         endif
703     elseif last_line =~# defaultORcase
704         let ind = ind + &sw
705     endif
707     if cline =~  '^\s*);\='
708         let ind = ind - &sw
709     elseif cline =~# defaultORcase
710         let ind = ind - &sw
712     endif
714     let b:PHP_CurrentIndentLevel = ind
715     return ind
716 endfunction