xref: /vim-8.2.3635/runtime/indent/php.vim (revision f4cd3e80)
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 Nobember 21st
6" Version: 1.20
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"  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
41" Options: PHP_autoformatcomment = 0 to not enable autoformating of comment by
42"		    default, if set to 0, this script will let the 'formatoptions' setting intact.
43"
44" Options: PHP_default_indenting = # of sw (default is 0), # of sw will be
45"		   added to the indent of each line of PHP code.
46"
47" Options: PHP_removeCRwhenUnix = 1 to make the script automatically remove CR
48"		   at end of lines (by default this option is unset), NOTE that you
49"		   MUST remove CR when the fileformat is UNIX else the indentation
50"		   won't be correct...
51"
52" Options: PHP_BracesAtCodeLevel = 1 to indent the '{' and '}' at the same
53"		   level than the code they contain.
54"		   Exemple:
55"			Instead of:
56"				if ($foo)
57"				{
58"					foo();
59"				}
60"
61"			You will write:
62"				if ($foo)
63"					{
64"					foo();
65"					}
66"
67"			NOTE: The script will be a bit slower if you use this option because
68"			some optimizations won't be available.
69
70
71
72if exists("b:did_indent")
73    finish
74endif
75let b:did_indent = 1
76
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
93if exists("PHP_autoformatcomment")
94    let b:PHP_autoformatcomment = PHP_autoformatcomment
95else
96    let b:PHP_autoformatcomment = 1
97endif
98
99let b:PHP_lastindented = 0
100let b:PHP_indentbeforelast = 0
101let b:PHP_indentinghuge = 0
102let b:PHP_CurrentIndentLevel = b:PHP_default_indenting
103let b:PHP_LastIndentedWasComment = 0
104let b:PHP_InsideMultilineComment = 0
105let b:InPHPcode = 0
106let b:InPHPcode_checked = 0
107let b:InPHPcode_and_script = 0
108let b:InPHPcode_tofind = ""
109let b:PHP_oldchangetick = b:changedtick
110let b:UserIsTypingComment = 0
111let b:optionsset = 0
112
113setlocal nosmartindent
114setlocal noautoindent
115setlocal nocindent
116setlocal nolisp
117
118setlocal indentexpr=GetPhpIndent()
119setlocal indentkeys=0{,0},0),:,!^F,o,O,e,*<Return>,=?>,=<?,=*/
120
121
122
123let s:searchpairflags = 'bWr'
124
125if &fileformat == "unix" && exists("PHP_removeCRwhenUnix") && PHP_removeCRwhenUnix
126    silent! %s/\r$//g
127endif
128
129if exists("*GetPhpIndent")
130   finish " XXX
131endif
132
133let s:endline= '\s*\%(//.*\|#.*\|/\*.*\*/\s*\)\=$'
134let s:PHP_startindenttag = '<?\%(.*?>\)\@!\|<script[^>]*>\%(.*<\/script>\)\@!'
135" setlocal debug=msg " XXX
136
137
138function! GetLastRealCodeLNum(startline) " {{{
139    let lnum = a:startline
140    let old_lnum = lnum
141
142    while lnum > 1
143	let lnum = prevnonblank(lnum)
144	let lastline = getline(lnum)
145
146	if b:InPHPcode_and_script && lastline =~ '?>\s*$'
147	    let lnum = lnum - 1
148	elseif lastline =~ '^\s*?>.*<?\%(php\)\=\s*$'
149	    let lnum = lnum - 1
150	elseif lastline =~ '^\s*\%(//\|#\|/\*.*\*/\s*$\)'
151	    let lnum = lnum - 1
152	elseif lastline =~ '\*/\s*$'
153	    call cursor(lnum, 1)
154	    if lastline !~ '^\*/'
155		call search('\*/', 'W')
156	    endif
157	    let lnum = searchpair('/\*', '', '\*/', s:searchpairflags, 'Skippmatch2()')
158
159	    let lastline = getline(lnum)
160	    if lastline =~ '^\s*/\*'
161		let lnum = lnum - 1
162	    else
163		break
164	    endif
165
166
167	elseif lastline =~? '\%(//\s*\|?>.*\)\@<!<?\%(php\)\=\s*$\|^\s*<script\>'
168
169	    while lastline !~ '\(<?.*\)\@<!?>' && lnum > 1
170		let lnum = lnum - 1
171		let lastline = getline(lnum)
172	    endwhile
173	    if lastline =~ '^\s*?>'
174		let lnum = lnum - 1
175	    else
176		break
177	    endif
178
179
180	elseif lastline =~? '^\a\w*;$' && lastline !~? s:notPhpHereDoc
181	    let tofind=substitute( lastline, '\([^;]\+\);', '<<<\1$', '')
182	    while getline(lnum) !~? tofind && lnum > 1
183		let lnum = lnum - 1
184	    endwhile
185	else
186	    break
187	endif
188    endwhile
189
190    if lnum==1 && getline(lnum)!~ '<?'
191	let lnum=0
192    endif
193
194    if b:InPHPcode_and_script && !b:InPHPcode
195	let b:InPHPcode_and_script = 0
196    endif
197    return lnum
198endfunction " }}}
199
200function! Skippmatch2()
201
202    let line = getline(".")
203
204   if line =~ '\%(".*\)\@<=/\*\%(.*"\)\@=' || line =~ '\%(//.*\)\@<=/\*'
205       return 1
206   else
207       return 0
208   endif
209endfun
210
211function! Skippmatch()  " {{{
212    let synname = synIDattr(synID(line("."), col("."), 0), "name")
213    if synname == "Delimiter" || synname == "phpParent" || synname == "javaScriptBraces" || synname == "phpComment" && b:UserIsTypingComment
214	return 0
215    else
216	return 1
217    endif
218endfun " }}}
219
220function! FindOpenBracket(lnum) " {{{
221    call cursor(a:lnum, 1)
222    return searchpair('{', '', '}', 'bW', 'Skippmatch()')
223endfun " }}}
224
225function! FindTheIfOfAnElse (lnum, StopAfterFirstPrevElse) " {{{
226
227    if getline(a:lnum) =~# '^\s*}\s*else\%(if\)\=\>'
228	let beforeelse = a:lnum
229    else
230	let beforeelse = GetLastRealCodeLNum(a:lnum - 1)
231    endif
232
233    if !s:level
234	let s:iftoskip = 0
235    endif
236
237    if getline(beforeelse) =~# '^\s*\%(}\s*\)\=else\%(\s*if\)\@!\>'
238	let s:iftoskip = s:iftoskip + 1
239    endif
240
241    if getline(beforeelse) =~ '^\s*}'
242	let beforeelse = FindOpenBracket(beforeelse)
243
244	if getline(beforeelse) =~ '^\s*{'
245	    let beforeelse = GetLastRealCodeLNum(beforeelse - 1)
246	endif
247    endif
248
249
250    if !s:iftoskip && a:StopAfterFirstPrevElse && getline(beforeelse) =~# '^\s*\%([}]\s*\)\=else\%(if\)\=\>'
251	return beforeelse
252    endif
253
254    if getline(beforeelse) !~# '^\s*if\>' && beforeelse>1 || s:iftoskip && beforeelse>1
255
256	if  s:iftoskip && getline(beforeelse) =~# '^\s*if\>'
257	    let s:iftoskip = s:iftoskip - 1
258	endif
259
260	let s:level =  s:level + 1
261	let beforeelse = FindTheIfOfAnElse(beforeelse, a:StopAfterFirstPrevElse)
262    endif
263
264    return beforeelse
265
266endfunction " }}}
267
268function! IslinePHP (lnum, tofind) " {{{
269    let cline = getline(a:lnum)
270
271    if a:tofind==""
272	let tofind = "^\\s*[\"']*\\s*\\zs\\S"
273    else
274	let tofind = a:tofind
275    endif
276
277    let tofind = tofind . '\c'
278
279    let coltotest = match (cline, tofind) + 1
280
281    let synname = synIDattr(synID(a:lnum, coltotest, 0), "name")
282
283    if synname =~ '^php' || synname=="Delimiter" || synname =~? '^javaScript'
284	return synname
285    else
286	return ""
287    endif
288endfunction " }}}
289
290let s:notPhpHereDoc = '\%(break\|return\|continue\|exit\);'
291let s:blockstart = '\%(\%(\%(}\s*\)\=else\%(\s\+\)\=\)\=if\>\|else\>\|while\>\|switch\>\|for\%(each\)\=\>\|declare\>\|class\>\|interface\>\|abstract\>\|[|&]\)'
292
293let s:autorestoptions = 0
294if ! s:autorestoptions
295    au BufWinEnter,Syntax	*.php,*.php3,*.php4,*.php5	call ResetOptions()
296    let s:autorestoptions = 1
297endif
298
299function! ResetOptions()
300    if ! b:optionsset
301	if b:PHP_autoformatcomment
302
303	    setlocal comments=s1:/*,mb:*,ex:*/,://,:#
304
305	    setlocal formatoptions-=t
306	    setlocal formatoptions+=q
307	    setlocal formatoptions+=r
308	    setlocal formatoptions+=o
309	    setlocal formatoptions+=w
310	    setlocal formatoptions+=c
311	    setlocal formatoptions+=b
312	endif
313	let b:optionsset = 1
314    endif
315endfunc
316
317function! GetPhpIndent()
318
319    let UserIsEditing=0
320    if	b:PHP_oldchangetick != b:changedtick
321	let b:PHP_oldchangetick = b:changedtick
322	let UserIsEditing=1
323    endif
324
325    if b:PHP_default_indenting
326	let b:PHP_default_indenting = g:PHP_default_indenting * &sw
327    endif
328
329    let cline = getline(v:lnum)
330
331    if !b:PHP_indentinghuge && b:PHP_lastindented > b:PHP_indentbeforelast
332	if b:PHP_indentbeforelast
333	    let b:PHP_indentinghuge = 1
334	    echom 'Large indenting detected, speed optimizations engaged'
335	endif
336	let b:PHP_indentbeforelast = b:PHP_lastindented
337    endif
338
339    if b:InPHPcode_checked && prevnonblank(v:lnum - 1) != b:PHP_lastindented
340	if b:PHP_indentinghuge
341	    echom 'Large indenting deactivated'
342	    let b:PHP_indentinghuge = 0
343	    let b:PHP_CurrentIndentLevel = b:PHP_default_indenting
344	endif
345	let b:PHP_lastindented = v:lnum
346	let b:PHP_LastIndentedWasComment=0
347	let b:PHP_InsideMultilineComment=0
348	let b:PHP_indentbeforelast = 0
349
350	let b:InPHPcode = 0
351	let b:InPHPcode_checked = 0
352	let b:InPHPcode_and_script = 0
353	let b:InPHPcode_tofind = ""
354
355    elseif v:lnum > b:PHP_lastindented
356	let real_PHP_lastindented = b:PHP_lastindented
357	let b:PHP_lastindented = v:lnum
358    endif
359
360
361    if !b:InPHPcode_checked " {{{ One time check
362	let b:InPHPcode_checked = 1
363
364	let synname = IslinePHP (prevnonblank(v:lnum), "")
365
366	if synname!=""
367	    if synname != "phpHereDoc"
368		let b:InPHPcode = 1
369		let b:InPHPcode_tofind = ""
370
371		if synname == "phpComment"
372		    let b:UserIsTypingComment = 1
373		else
374		    let b:UserIsTypingComment = 0
375		endif
376
377		if synname =~? '^javaScript'
378		    let b:InPHPcode_and_script = 1
379		endif
380
381	    else
382		let b:InPHPcode = 0
383		let b:UserIsTypingComment = 0
384
385		let lnum = v:lnum - 1
386		while getline(lnum) !~? '<<<\a\w*$' && lnum > 1
387		    let lnum = lnum - 1
388		endwhile
389
390		let b:InPHPcode_tofind = substitute( getline(lnum), '^.*<<<\(\a\w*\)\c', '^\\s*\1;$', '')
391	    endif
392	else
393	    let b:InPHPcode = 0
394	    let b:UserIsTypingComment = 0
395	    let b:InPHPcode_tofind = '<?\%(.*?>\)\@!\|<script.*>'
396	endif
397    endif "!b:InPHPcode_checked }}}
398
399
400    " Test if we are indenting PHP code {{{
401    let lnum = prevnonblank(v:lnum - 1)
402    let last_line = getline(lnum)
403
404    if b:InPHPcode_tofind!=""
405	if cline =~? b:InPHPcode_tofind
406	    let	b:InPHPcode = 1
407	    let b:InPHPcode_tofind = ""
408	    let b:UserIsTypingComment = 0
409	    if cline =~ '\*/'
410		call cursor(v:lnum, 1)
411		if cline !~ '^\*/'
412		    call search('\*/', 'W')
413		endif
414		let lnum = searchpair('/\*', '', '\*/', s:searchpairflags, 'Skippmatch2()')
415
416		let b:PHP_CurrentIndentLevel = b:PHP_default_indenting
417
418		let b:PHP_LastIndentedWasComment = 0
419
420		if cline =~ '^\s*\*/'
421		    return indent(lnum) + 1
422		else
423		    return indent(lnum)
424		endif
425
426	    elseif cline =~? '<script\>'
427		let b:InPHPcode_and_script = 1
428	    endif
429	endif
430    endif
431
432    if b:InPHPcode
433
434	if !b:InPHPcode_and_script && last_line =~ '\%(<?.*\)\@<!?>\%(.*<?\)\@!' && IslinePHP(lnum, '?>')=="Delimiter"
435	    if cline !~? s:PHP_startindenttag
436		let b:InPHPcode = 0
437		let b:InPHPcode_tofind = s:PHP_startindenttag
438	    elseif cline =~? '<script\>'
439		let b:InPHPcode_and_script = 1
440	    endif
441
442	elseif last_line =~? '<<<\a\w*$'
443	    let b:InPHPcode = 0
444	    let b:InPHPcode_tofind = substitute( last_line, '^.*<<<\(\a\w*\)\c', '^\\s*\1;$', '')
445
446	elseif !UserIsEditing && cline =~ '^\s*/\*\%(.*\*/\)\@!' && getline(v:lnum + 1) !~ '^\s*\*'
447	    let b:InPHPcode = 0
448	    let b:InPHPcode_tofind = '\*/'
449
450	elseif cline =~? '^\s*</script>'
451	    let b:InPHPcode = 0
452	    let b:InPHPcode_tofind = s:PHP_startindenttag
453	endif
454    endif " }}}
455
456    if !b:InPHPcode && !b:InPHPcode_and_script
457	return -1
458    endif
459
460
461    " Indent successive // or # comment the same way the first is {{{
462    if cline =~ '^\s*\%(//\|#\|/\*.*\*/\s*$\)'
463	if b:PHP_LastIndentedWasComment == 1
464	    return indent(real_PHP_lastindented)
465	endif
466	let b:PHP_LastIndentedWasComment = 1
467    else
468	let b:PHP_LastIndentedWasComment = 0
469    endif " }}}
470
471    " Indent multiline /* comments correctly {{{
472
473    if b:PHP_InsideMultilineComment || b:UserIsTypingComment
474	if cline =~ '^\s*\*\%(\/\)\@!'
475	    if last_line =~ '^\s*/\*'
476		return indent(lnum) + 1
477	    else
478		return indent(lnum)
479	    endif
480	else
481	    let b:PHP_InsideMultilineComment = 0
482	endif
483    endif
484
485    if !b:PHP_InsideMultilineComment && cline =~ '^\s*/\*'
486	let b:PHP_InsideMultilineComment = 1
487	return -1
488    endif " }}}
489
490
491    " Things always indented at col 1 (PHP delimiter: <?, ?>, Heredoc end) {{{
492    if cline =~# '^\s*<?' && cline !~ '?>'
493	return 0
494    endif
495
496    if  cline =~ '^\s*?>' && cline !~# '<?'
497	return 0
498    endif
499
500    if cline =~? '^\s*\a\w*;$' && cline !~? s:notPhpHereDoc
501	return 0
502    endif " }}}
503
504    let s:level = 0
505
506    let lnum = GetLastRealCodeLNum(v:lnum - 1)
507    let last_line = getline(lnum)
508    let ind = indent(lnum)
509    let endline= s:endline
510
511    if ind==0 && b:PHP_default_indenting
512	let ind = b:PHP_default_indenting
513    endif
514
515    if lnum == 0
516	return b:PHP_default_indenting
517    endif
518
519
520    if cline =~ '^\s*}\%(}}\)\@!'
521	let ind = indent(FindOpenBracket(v:lnum))
522	let b:PHP_CurrentIndentLevel = b:PHP_default_indenting
523	return ind
524    endif
525
526    if cline =~ '^\s*\*/'
527	call cursor(v:lnum, 1)
528	if cline !~ '^\*/'
529	    call search('\*/', 'W')
530	endif
531	let lnum = searchpair('/\*', '', '\*/', s:searchpairflags, 'Skippmatch2()')
532
533	let b:PHP_CurrentIndentLevel = b:PHP_default_indenting
534
535	if cline =~ '^\s*\*/'
536	    return indent(lnum) + 1
537	else
538	    return indent(lnum)
539	endif
540    endif
541
542    let defaultORcase = '^\s*\%(default\|case\).*:'
543
544    if last_line =~ '[;}]'.endline && last_line !~# defaultORcase
545	if ind==b:PHP_default_indenting
546	    return b:PHP_default_indenting
547	elseif b:PHP_indentinghuge && ind==b:PHP_CurrentIndentLevel && cline !~# '^\s*\%(else\|\%(case\|default\).*:\|[})];\=\)' && last_line !~# '^\s*\%(\%(}\s*\)\=else\)' && getline(GetLastRealCodeLNum(lnum - 1))=~';'.endline
548	    return b:PHP_CurrentIndentLevel
549	endif
550    endif
551
552    let LastLineClosed = 0
553
554    let terminated = '\%(;\%(\s*?>\)\=\|<<<\a\w*\|}\)'.endline
555
556    let unstated   = '\%(^\s*'.s:blockstart.'.*)\|\%(//.*\)\@<!\<e'.'lse\>\)'.endline
557
558    if ind != b:PHP_default_indenting && cline =~# '^\s*else\%(if\)\=\>'
559	let b:PHP_CurrentIndentLevel = b:PHP_default_indenting
560	return indent(FindTheIfOfAnElse(v:lnum, 1))
561    elseif cline =~ '^\s*{'
562	let previous_line = last_line
563	let last_line_num = lnum
564
565	while last_line_num > 1
566
567	    if previous_line =~ '^\s*\%(' . s:blockstart . '\|\%([a-zA-Z]\s*\)*function\)' && previous_line !~ '^\s*[|&]'
568
569		let ind = indent(last_line_num)
570
571		if  b:PHP_BracesAtCodeLevel
572		    let ind = ind + &sw
573		endif
574
575		return ind
576	    endif
577
578	    let last_line_num = last_line_num - 1
579	    let previous_line = getline(last_line_num)
580	endwhile
581
582    elseif last_line =~# unstated && cline !~ '^\s*{\|^\s*);\='.endline
583	let ind = ind + &sw
584	return ind
585
586    elseif ind != b:PHP_default_indenting && last_line =~ terminated
587	let previous_line = last_line
588	let last_line_num = lnum
589	let LastLineClosed = 1
590
591	while 1
592	    if previous_line =~ '^\s*}'
593		let last_line_num = FindOpenBracket(last_line_num)
594
595		if getline(last_line_num) =~ '^\s*{'
596		    let last_line_num = GetLastRealCodeLNum(last_line_num - 1)
597		endif
598
599		let previous_line = getline(last_line_num)
600
601		continue
602	    else
603
604		if getline(last_line_num) =~# '^\s*else\%(if\)\=\>'
605		    let last_line_num = FindTheIfOfAnElse(last_line_num, 0)
606		    continue
607		endif
608
609
610		let last_match = last_line_num
611
612		let one_ahead_indent = indent(last_line_num)
613		let last_line_num = GetLastRealCodeLNum(last_line_num - 1)
614		let two_ahead_indent = indent(last_line_num)
615		let after_previous_line = previous_line
616		let previous_line = getline(last_line_num)
617
618
619		if previous_line =~# defaultORcase.'\|{'.endline
620		    break
621		endif
622
623		if after_previous_line=~# '^\s*'.s:blockstart.'.*)'.endline && previous_line =~# '[;}]'.endline
624		    break
625		endif
626
627		if one_ahead_indent == two_ahead_indent || last_line_num < 1
628		    if previous_line =~# '[;}]'.endline || last_line_num < 1
629			break
630		    endif
631		endif
632	    endif
633	endwhile
634
635	if indent(last_match) != ind
636	    let ind = indent(last_match)
637	    let b:PHP_CurrentIndentLevel = b:PHP_default_indenting
638
639	    if cline =~# defaultORcase
640		let ind = ind - &sw
641	    endif
642	    return ind
643	endif
644    endif
645
646    let plinnum = GetLastRealCodeLNum(lnum - 1)
647    let pline = getline(plinnum)
648
649    let last_line = substitute(last_line,"\\(//\\|#\\)\\(\\(\\([^\"']*\\([\"']\\)[^\"']*\\5\\)\\+[^\"']*$\\)\\|\\([^\"']*$\\)\\)",'','')
650
651
652    if ind == b:PHP_default_indenting
653	if last_line =~ terminated
654	    let LastLineClosed = 1
655	endif
656    endif
657
658    if !LastLineClosed
659
660	if last_line =~# '[{(]'.endline || last_line =~? '\h\w*\s*(.*,$' && pline !~ '[,(]'.endline
661
662	    if !b:PHP_BracesAtCodeLevel || last_line !~# '^\s*{'
663		let ind = ind + &sw
664	    endif
665
666	    if b:PHP_BracesAtCodeLevel || cline !~# defaultORcase
667		let b:PHP_CurrentIndentLevel = ind
668		return ind
669	    endif
670
671	elseif last_line =~ '\S\+\s*),'.endline
672	    call cursor(lnum, 1)
673	    call search('),'.endline, 'W')
674	    let openedparent = searchpair('(', '', ')', 'bW', 'Skippmatch()')
675	    if openedparent != lnum
676		let ind = indent(openedparent)
677	    endif
678
679
680	elseif cline !~ '^\s*{' && pline =~ '\%(;\%(\s*?>\)\=\|<<<\a\w*\|{\|^\s*'.s:blockstart.'\s*(.*)\)'.endline.'\|^\s*}\|'.defaultORcase
681
682	    let ind = ind + &sw
683
684	endif
685
686    elseif last_line =~# defaultORcase
687	let ind = ind + &sw
688    endif
689
690    if cline =~  '^\s*);\='
691	let ind = ind - &sw
692    elseif cline =~# defaultORcase
693	let ind = ind - &sw
694
695    endif
696
697    let b:PHP_CurrentIndentLevel = ind
698    return ind
699endfunction
700
701" vim: set ts=8 sw=4 sts=4:
702