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