xref: /vim-8.2.3635/runtime/indent/php.vim (revision 482aaeb0)
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