" Vim indent file " Language: PHP " Author: John Wellesz " URL: http://www.2072productions.com/vim/indent/php.vim " Last Change: 2006 January 15th " Newsletter: http://www.2072productions.com/?to=php-indent-for-vim-newsletter.php " Version: 1.23 " " Changes: 1.23 - html tag was preceded by a "?>" it wasn't indented. " - Some other minor corrections and improvements. " " " Changes: 1.16 - Now starting and ending '*' of multiline '/* */' comments are aligned " on the '*' of the '/*' comment starter. " - Some code improvements that make indentation faster. " " Changes: 1.15 - Corrected some problems with the indentation of " multiline "array()" declarations. " " Changes: 1.14 - Added auto-formatting for comments (using the Vim option formatoptions=qroc). " - Added the script option PHP_BracesAtCodeLevel to " indent the '{' and '}' at the same level than the " code they contain. " " Changes: 1.13 - Some code cleaning and typo corrections (Thanks to " Emanuele Giaquinta for his patches) " " Changes: 1.12 - The bug involving searchpair() and utf-8 encoding in Vim 6.3 will " not make this script to hang but you'll have to be " careful to not write '/* */' comments with other '/*' " inside the comments else the indentation won't be correct. " NOTE: This is true only if you are using utf-8 and vim 6.3. " " Changes: 1.11 - If the "case" of a "switch" wasn't alone on its line " and if the "switch" was at col 0 (or at default indenting) " the lines following the "case" were not indented. " " Changes: 1.10 - Lines beginning by a single or double quote were " not indented in some cases. " " Changes: 1.09 - JavaScript code was not always directly indented. " " Changes: 1.08 - End comment tags '*/' are indented like start tags '/*'. " - When typing a multiline comment, '}' are indented " according to other commented '{'. " - Added a new option 'PHP_removeCRwhenUnix' to " automatically remove CR at end of lines when the file " format is Unix. " - Changed the file format of this very file to Unix. " - This version seems to correct several issues some people " had with 1.07. " " Changes: 1.07 - Added support for "Here document" tags: " - HereDoc end tags are indented properly. " - HereDoc content remains unchanged. " - All the code that is outside PHP delimiters remains " unchanged. " - New feature: The content of html tags is considered as PHP " and indented according to the surrounding PHP code. " - "else if" are detected as "elseif". " - Multiline /**/ are indented when the user types it but " remain unchanged when indenting from their beginning. " - Fixed indenting of // and # comments. " - php_sync_method option is set to 0 (fromstart). " This is required for complex PHP scripts else the indent " may fail. " - Files with non PHP code at the beginning could alter the indent " of the following PHP code. " - Other minor improvements and corrections. " " Changes: 1.06: - Switch block were no longer indented correctly... " - Added an option to use a default indenting instead of 0. " (whereas I still can't find any good reason to use it!) " - A problem with ^\s*);\= lines where ending a non '{}' " structure. " - Changed script local variable to be buffer local " variable instead. " " Changes: 1.05: - Lines containing "" and "?> ,=?>,=\)\@!\|]*>\%(.*<\/script>\)\@!' " setlocal debug=msg " XXX function! GetLastRealCodeLNum(startline) " {{{ "Inspired from the function SkipJavaBlanksAndComments by Toby Allsopp for indent/java.vim let lnum = a:startline " Used to indent html tag correctly if b:GetLastRealCodeLNum_ADD && b:GetLastRealCodeLNum_ADD == lnum + 1 let lnum = b:GetLastRealCodeLNum_ADD endif let old_lnum = lnum while lnum > 1 let lnum = prevnonblank(lnum) let lastline = getline(lnum) " if we are inside an html ' let b:InPHPcode = 0 let b:InPHPcode_tofind = s:PHP_startindenttag " Note that b:InPHPcode_and_script is still true so that the " can be indented correctly endif endif " }}} " Non PHP code is let as it is if !b:InPHPcode && !b:InPHPcode_and_script return -1 endif " Align correctly multi // or # lines " Indent successive // or # comment the same way the first is {{{ if cline =~ '^\s*\%(//\|#\|/\*.*\*/\s*$\)' if b:PHP_LastIndentedWasComment == 1 return indent(real_PHP_lastindented) endif let b:PHP_LastIndentedWasComment = 1 else let b:PHP_LastIndentedWasComment = 0 endif " }}} " Indent multiline /* comments correctly {{{ "if we are on the start of a MULTI * beginning comment or if the user is "currently typing a /* beginning comment. if b:PHP_InsideMultilineComment || b:UserIsTypingComment if cline =~ '^\s*\*\%(\/\)\@!' " if cline == '*' if last_line =~ '^\s*/\*' " if last_line == '/*' return indent(lnum) + 1 else return indent(lnum) endif else let b:PHP_InsideMultilineComment = 0 endif endif if !b:PHP_InsideMultilineComment && cline =~ '^\s*/\*' && cline !~ '\*/\s*$' " if cline == '/*' if getline(v:lnum + 1) !~ '^\s*\*' return -1 endif let b:PHP_InsideMultilineComment = 1 endif " }}} " Some tags are always indented to col 1 " Things always indented at col 1 (PHP delimiter: , Heredoc end) {{{ " PHP start tags are always at col 1, useless to indent unless the end tag " is on the same line if cline =~# '^\s*' return 0 endif " PHP end tags are always at col 1, useless to indent unless if it's " followed by a start tag on the same line if cline =~ '^\s*?>' && cline !~# '\)\=\|<<<\a\w*\|}\)'.endline " What is a terminated line? " - a line terminated by a ";" optionaly followed by a "?>" " - a HEREDOC starter line (the content of such block is never seen by this script) " - a "}" not followed by a "{" let unstated = '\%(^\s*'.s:blockstart.'.*)\|\%(//.*\)\@\)'.endline " What is an unstated line? " - an "else" at the end of line " - a s:blockstart (if while etc...) followed by anything and a ")" at " the end of line " if the current line is an 'else' starting line " (to match an 'else' preceded by a '}' is irrelevant and futile - see " code above) if ind != b:PHP_default_indenting && cline =~# '^\s*else\%(if\)\=\>' " prevent optimized to work at next call let b:PHP_CurrentIndentLevel = b:PHP_default_indenting return indent(FindTheIfOfAnElse(v:lnum, 1)) elseif cline =~ '^\s*{' let previous_line = last_line let last_line_num = lnum while last_line_num > 1 if previous_line =~ '^\s*\%(' . s:blockstart . '\|\%([a-zA-Z]\s*\)*function\)' && previous_line !~ '^\s*[|&]' let ind = indent(last_line_num) " If the PHP_BracesAtCodeLevel is set then indent the '{' if b:PHP_BracesAtCodeLevel let ind = ind + &sw endif return ind endif let last_line_num = last_line_num - 1 let previous_line = getline(last_line_num) endwhile elseif last_line =~# unstated && cline !~ '^\s*{\|^\s*);\='.endline let ind = ind + &sw return ind " If the last line is terminated by ';' or if it's a closing '}' " We need to check if this isn't the end of a multilevel non '{}' " structure such as: " Exemple: " if ($truc) " echo 'truc'; " " OR " " if ($truc) " while ($truc) { " lkhlkh(); " echo 'infinite loop\n'; " } " " OR even (ADDED for version 1.17 - no modification required ) " " $thing = " "something"; elseif ind != b:PHP_default_indenting && last_line =~ terminated " If we are here it means that the previous line is: " - a *;$ line " - a [beginning-blanck] } followed by anything but a { $ let previous_line = last_line let last_line_num = lnum let LastLineClosed = 1 " The idea here is to check if the current line is after a non '{}' " structure so we can indent it like the top of that structure. " The top of that structure is caracterized by a if (ff)$ style line " preceded by a stated line. If there is no such structure then we " just have to find two 'normal' lines following each other with the " same indentation and with the first of these two lines terminated by " a ; or by a }... while 1 " let's skip '{}' blocks if previous_line =~ '^\s*}' " find the openning '{' let last_line_num = FindOpenBracket(last_line_num) " if the '{' is alone on the line get the line before if getline(last_line_num) =~ '^\s*{' let last_line_num = GetLastRealCodeLNum(last_line_num - 1) endif let previous_line = getline(last_line_num) continue else " At this point we know that the previous_line isn't a closing " '}' so we can check if we really are in such a structure. " it's not a '}' but it could be an else alone... if getline(last_line_num) =~# '^\s*else\%(if\)\=\>' let last_line_num = FindTheIfOfAnElse(last_line_num, 0) " re-run the loop (we could find a '}' again) continue endif " So now it's ok we can check :-) " A good quality is to have confidence in oneself so to know " if yes or no we are in that struct lets test the indent of " last_line_num and of last_line_num - 1! " If those are == then we are almost done. " " That isn't sufficient, we need to test how the first of the " 2 lines is ended... " Remember the 'topest' line we found so far let last_match = last_line_num let one_ahead_indent = indent(last_line_num) let last_line_num = GetLastRealCodeLNum(last_line_num - 1) let two_ahead_indent = indent(last_line_num) let after_previous_line = previous_line let previous_line = getline(last_line_num) " If we find a '{' or a case/default then we are inside that block so lets " indent properly... Like the line following that block starter if previous_line =~# defaultORcase.'\|{'.endline break endif " The 3 lines below are not necessary for the script to work " but it makes it work a little more faster in some (rare) cases. " We verify if we are at the top of a non '{}' struct. if after_previous_line=~# '^\s*'.s:blockstart.'.*)'.endline && previous_line =~# '[;}]'.endline break endif if one_ahead_indent == two_ahead_indent || last_line_num < 1 " So the previous line and the line before are at the same " col. Now we just have to check if the line before is a ;$ or [}]$ ended line " we always check the most ahead line of the 2 lines so " it's useless to match ')$' since the lines couldn't have " the same indent... if previous_line =~# '[;}]'.endline || last_line_num < 1 break endif endif endif endwhile if indent(last_match) != ind " let's use the indent of the last line matched by the alhorithm above let ind = indent(last_match) " line added in version 1.02 to prevent optimized mode " from acting in some special cases let b:PHP_CurrentIndentLevel = b:PHP_default_indenting " case and default are indented 1 level below if cline =~# defaultORcase let ind = ind - &sw endif return ind endif " if nothing was done lets the old script continue endif let plinnum = GetLastRealCodeLNum(lnum - 1) " previous to last line let pline = getline(plinnum) " REMOVE comments at end of line before treatment " the first part of the regex removes // from the end of line when they are " followed by a number of '"' which is a multiple of 2. The second part " removes // that are not followed by any '"' " Sorry for this unreadable thing... let last_line = substitute(last_line,"\\(//\\|#\\)\\(\\(\\([^\"']*\\([\"']\\)[^\"']*\\5\\)\\+[^\"']*$\\)\\|\\([^\"']*$\\)\\)",'','') if ind == b:PHP_default_indenting if last_line =~ terminated let LastLineClosed = 1 endif endif " Indent blocks enclosed by {} or () (default indenting) if !LastLineClosed " the last line isn't a .*; or a }$ line " Indent correctly multilevel and multiline '(.*)' things " if the last line is a [{(]$ or a multiline function call (or array " declaration) with already one parameter on the opening ( line if last_line =~# '[{(]'.endline || last_line =~? '\h\w*\s*(.*,$' && pline !~ '[,(]'.endline if !b:PHP_BracesAtCodeLevel || last_line !~# '^\s*{' let ind = ind + &sw endif if b:PHP_BracesAtCodeLevel || cline !~# defaultORcase " case and default are not indented inside blocks let b:PHP_CurrentIndentLevel = ind return ind endif " If the last line isn't empty and ends with a '),' then check if the " ')' was oppened on the same line, if not it means it closes a " multiline '(.*)' thing and that the current line need to be " de-indented one time. elseif last_line =~ '\S\+\s*),'.endline call cursor(lnum, 1) call search('),'.endline, 'W') let openedparent = searchpair('(', '', ')', 'bW', 'Skippmatch()') if openedparent != lnum let ind = indent(openedparent) endif " In all other cases if the last line isn't terminated indent 1 " level higher but only if the line before the last line wasn't " indented for the same reason. elseif cline !~ '^\s*{' && pline =~ '\%(;\%(\s*?>\)\=\|<<<\a\w*\|{\|^\s*'.s:blockstart.'\s*(.*)\)'.endline.'\|^\s*}\|'.defaultORcase let ind = ind + &sw endif elseif last_line =~# defaultORcase let ind = ind + &sw endif " If the current line closes a multiline function call or array def XXX if cline =~ '^\s*);\=' let ind = ind - &sw " CASE and DEFAULT are indented at the same level than the SWITCH elseif cline =~# defaultORcase let ind = ind - &sw endif let b:PHP_CurrentIndentLevel = ind return ind endfunction " vim: set ts=8 sw=4 sts=4: