From 8fabfe238542dcfd114b72f00b074a65e68890b3 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Thu, 2 Jul 2015 15:16:39 +0200 Subject: [PATCH] PHP folding plugin --- .vim/ftplugin/php/phpfolding.vim | 633 +++++++++++++++++++++++++++++++ .vimrc | 6 +- 2 files changed, 636 insertions(+), 3 deletions(-) create mode 100644 .vim/ftplugin/php/phpfolding.vim diff --git a/.vim/ftplugin/php/phpfolding.vim b/.vim/ftplugin/php/phpfolding.vim new file mode 100644 index 0000000..1ac6ada --- /dev/null +++ b/.vim/ftplugin/php/phpfolding.vim @@ -0,0 +1,633 @@ +" Plugin for automatic folding of PHP functions (also folds related PHPdoc) +" +" Maintainer: Ray Burgemeestre +" Last Change: 2013 Aug 26 +" +" USAGE +" If you enabled the script in your after/ftplugin directory (see install) +" then it will be executed after you open a .php file. +" +" After e.g. adding/moving functions, you can re-execute the script by using +" the following key mappings: +" +" F5 - To fold functions, classes, and other stuff with PHPdoc (depending +" on your configuration). +" F6 - To do the same with more extensive bracket checking (might work +" better if your folds are messed up due to misinterpreted brackets). +" F7 - To remove all folds. +" +" INSTALL +" 1. Put phpfolding.vim in your plugin directory (~/.vim/ftplugin/php/) +" +" make sure you have "filetype plugin on" in your .vimrc! +" +" 2. You might want to add the following keyboard mappings to your .vimrc: +" +" map :EnableFastPHPFolds +" map :EnablePHPFolds +" map :DisablePHPFolds +" +" 3. You can disable auto folding in your .vimrc with: +" +" let g:DisableAutoPHPFolding = 1 +" +" By default EnableFastPHPFolds is called. Do these mess up your folds, +" you can try to replace EnableFastPHPFolds by EnablePHPFolds. You can +" change this in function s:CheckAutocmdEnablePHPFold. +" +" NOTE +" It may be that you need to load the plugin from your .vimrc manually, in +" case it doesn't work: +" +" let php_folding=0 +" (if you can't use the after directory in step 3) +" source ~/path/to/phpfolding.vim +" (if you're not using the default plugin directory) +" +" MORE INFORMATION +" - In PHPCustomFolds() you can i.e. comment the PHPFoldPureBlock('class', ...) +" call to have the script not fold classes. You can also change the second +" parameter passed to that function call, to have it or not have it fold +" PhpDoc comments. All other folding you can turn on/off in this function. +" - You can tweak the foldtext to your liking in the function PHPFoldText(). +" - You can set some preferences and default settings a few lines below +" at the "Script configuration" part. +" +" This script is tested with Vim version >= 6.3 on windows and linux. +let s:save_cpo = &cpo +set cpo&vim + +" Avoid reloading {{{1 +if exists('loaded_phpfolding') + " ftplugin section + if !get(g:, 'DisableAutoPHPFolding', 0) + call s:EnableFastPHPFolds() + endif + + let &cpo = s:save_cpo + unlet s:save_cpo + finish +endif + +let loaded_phpfolding = 1 +" }}} + +" .vimrc variable to disable autofolding for php files {{{1 +if !exists("g:DisableAutoPHPFolding") + let g:DisableAutoPHPFolding = 0 +endif +let g:DisablePHPFoldingClass = get(g:, 'DisablePHPFoldingClass', 1) +" }}} + +command! EnableFastPHPFolds call EnableFastPHPFolds() +command! -nargs=* EnablePHPFolds call EnablePHPFolds() +command! DisablePHPFolds call DisablePHPFolds() + +" {{{ Script configuration +" Display the following after the foldtext if a fold contains phpdoc +let g:phpDocIncludedPostfix = '**' +let g:phpDocBlockIncludedPostfix = '**#@+' + +" Default values +" .. search this # of empty lines for PhpDoc comments +let g:searchPhpDocLineCount = 1 +" .. search this # of empty lines that 'trail' the foldmatch +let g:searchEmptyLinesPostfixing = 1 +" }}} +" {{{ Script constants +let s:synIDattr_exists = exists('*synIDattr') +let s:TRUE = 1 +let s:FALSE = 0 +let s:MODE_CREATE_FOLDS = 1 +let s:MODE_REMEMBER_FOLD_SETTINGS = 2 +let s:FOLD_WITH_PHPDOC = 1 +let s:FOLD_WITHOUT_PHPDOC = 2 +let s:SEARCH_PAIR_START_FIRST = 1 +let s:SEARCH_PAIR_IMMEDIATELY = 2 +" }}} + +function! s:EnableFastPHPFolds() " {{{ + call s:EnablePHPFolds(s:FALSE) +endfunction +" }}} +function! s:EnablePHPFolds(...) " {{{ + let s:extensiveBracketChecking = s:TRUE + + " Check function arguments + if a:0 == 1 + let s:extensiveBracketChecking = a:1 + endif + + " Remember cursor information if possible + let s:savedCursor = line(".") + + " Initialize variables + setlocal foldmethod=manual + setlocal foldtext=PHPFoldText() + let s:openFoldListItems = 0 + let s:fileLineCount = line('$') + + let s:searchPhpDocLineCount = g:searchPhpDocLineCount + let s:searchEmptyLinesPostfixing = g:searchEmptyLinesPostfixing + + + " Move to end of file + exec s:fileLineCount + + " First pass: Look for Folds, remember opened folds + let s:foldingMode = s:MODE_REMEMBER_FOLD_SETTINGS + call s:PHPCustomFolds() + + " Second pass: Recreate Folds, restore previously opened + let s:foldingMode = s:MODE_CREATE_FOLDS + " .. Remove all folds first + normal! zE + let s:foldsCreated = 0 + call s:PHPCustomFolds() + " .. Fold all + normal! zM + + " Restore previously opened folds + let currentItem = 0 + while currentItem < s:openFoldListItems + exec s:foldsOpenedList{currentItem} + normal! zo + let currentItem = currentItem + 1 + endwhile + + :redraw + echo s:foldsCreated . " fold(s) created" + + " Restore cursor + exec s:savedCursor + +endfunction +" }}} +function! s:DisablePHPFolds() " {{{ + "setlocal foldmethod=manual + setlocal foldtext= + normal! zE + echo "php fold(s) deleted" +endfunction +" }}} +function! s:PHPCustomFolds() " {{{ + " NOTE: The two last parameters for functions PHPFoldProperties() and + " PHPFoldPureBlock() overwrite: 'g:searchPhpDocLineCount' and + " 'g:searchEmptyLinesPostfixing'.. + + " Fold function with PhpDoc (function foo() {}) + call s:PHPFoldPureBlock('function', s:FOLD_WITH_PHPDOC) + + " Fold class properties with PhpDoc (var $foo = NULL;) + call s:PHPFoldProperties('^\s*\(\(private\)\|\(public\)\|\(protected\)\|\(var\)\)\s\(static\s\)*\$', ";", s:FOLD_WITH_PHPDOC, 1, 1) + + if !g:DisablePHPFoldingClass + " Fold class without PhpDoc (class foo {}) + call s:PHPFoldPureBlock('^\s*\(abstract\s*\)\?class', s:FOLD_WITH_PHPDOC) + endif + + " Fold define()'s with their PhpDoc + call s:PHPFoldProperties('^\s*define\s*(', ";", s:FOLD_WITH_PHPDOC) + + " Fold includes with their PhpDoc + call s:PHPFoldProperties('^\s*require\s*', ";", s:FOLD_WITH_PHPDOC) + call s:PHPFoldProperties('^\s*include\s*', ";", s:FOLD_WITH_PHPDOC) + + " Fold GLOBAL Arrays with their PhpDoc (some PEAR packages use these) + call s:PHPFoldProperties('^\s*\$GLOBALS.*array\s*(', ";", s:FOLD_WITH_PHPDOC) + + " Fold marker style comments ({{{ foo }}}) + call s:PHPFoldMarkers('{{{', '}}}') + + " Fold PhpDoc "DocBlock" templates (#@+ foo #@-) + call s:PHPFoldMarkers('#@+', '#@-') +endfunction +" }}} +function! s:PHPFoldPureBlock(startPattern, ...) " {{{ + let s:searchPhpDocLineCount = g:searchPhpDocLineCount + let s:searchEmptyLinesPostfixing = g:searchEmptyLinesPostfixing + let s:currentPhpDocMode = s:FOLD_WITH_PHPDOC + + if a:0 >= 1 + " Do we also put the PHP doc part in the fold? + let s:currentPhpDocMode = a:1 + endif + if a:0 >= 2 + " How far do we want to look for PhpDoc comments? + let s:searchPhpDocLineCount = a:2 + endif + if a:0 == 3 + " How greedy are we on postfixing empty lines? + let s:searchEmptyLinesPostfixing = a:3 + endif + + " Move to file end + exec s:fileLineCount + + " Loop through file, searching for folds + while 1 + let s:lineStart = s:FindPureBlockStart(a:startPattern) + + if s:lineStart != 0 + + let s:lineStart = s:FindOptionalPHPDocComment() + let s:lineStop = s:FindPureBlockEnd('{', '}', s:SEARCH_PAIR_START_FIRST) + + " Stop on Error + if s:lineStop == 0 + break + endif + + " Do something with the potential fold based on the Mode we're in + call s:HandleFold() + + else + break + endif + + " Goto fold start (remember we're searching upwards) + exec s:lineStart + endwhile + + + if s:foldingMode != s:MODE_REMEMBER_FOLD_SETTINGS + " Remove created folds + normal! zR + endif +endfunction +" }}} +function! s:PHPFoldMarkers(startPattern, endPattern, ...) " {{{ + let s:currentPhpDocMode = s:FOLD_WITHOUT_PHPDOC + + " Move to file end + exec s:fileLineCount + + " Loop through file, searching for folds + while 1 + let s:lineStart = s:FindPatternStart(a:startPattern) + + if s:lineStart != 0 + let s:lineStart = s:FindOptionalPHPDocComment() + " The fourth parameter is for disabling the search for trailing + " empty lines.. + let s:lineStop = s:FindPureBlockEnd(a:startPattern, a:endPattern, + \ s:SEARCH_PAIR_IMMEDIATELY, s:FALSE) + + " Stop on Error + if s:lineStop == 0 + break + endif + + " Do something with the potential fold based on the Mode we're in + call s:HandleFold() + else + break + endif + + " Goto fold start (remember we're searching upwards) + exec s:lineStart + endwhile + + if s:foldingMode != s:MODE_REMEMBER_FOLD_SETTINGS + " Remove created folds + normal! zR + endif +endfunction +" }}} +function! s:PHPFoldProperties(startPattern, endPattern, ...) " {{{ + let s:searchPhpDocLineCount = g:searchPhpDocLineCount + let s:searchEmptyLinesPostfixing = g:searchEmptyLinesPostfixing + let s:currentPhpDocMode = s:FOLD_WITH_PHPDOC + if a:0 >= 1 + " Do we also put the PHP doc part in the fold? + let s:currentPhpDocMode = a:1 + endif + if a:0 >= 2 + " How far do we want to look for PhpDoc comments? + let s:searchPhpDocLineCount = a:2 + endif + if a:0 == 3 + " How greedy are we on postfixing empty lines? + let s:searchEmptyLinesPostfixing = a:3 + endif + + " Move to end of file + exec s:fileLineCount + + " Loop through file, searching for folds + while 1 + let s:lineStart = s:FindPatternStart(a:startPattern) + + if s:lineStart != 0 + let s:lineStart = s:FindOptionalPHPDocComment() + let s:lineStop = s:FindPatternEnd(a:endPattern) + + " Stop on Error + if s:lineStop == 0 + break + endif + + " Do something with the potential fold based on the Mode we're in + call s:HandleFold() + else + break + endif + + " Goto fold start (remember we're searching upwards) + exec s:lineStart + + endwhile + + if s:foldingMode != s:MODE_REMEMBER_FOLD_SETTINGS + " Remove created folds + normal! zR + endif +endfunction +" }}} +function! s:HandleFold() " {{{ + if s:foldingMode == s:MODE_REMEMBER_FOLD_SETTINGS + " If we are in an actual fold.., + if foldlevel(s:lineStart) != 0 + " .. and it is not closed.., + if foldclosed(s:lineStart) == -1 + " .. and it is more then one lines + " (it has to be or it will be open by default) + if s:lineStop - s:lineStart >= 1 + " Remember it as an open fold + let s:foldsOpenedList{s:openFoldListItems} = s:lineStart + let s:openFoldListItems = s:openFoldListItems + 1 + endif + endif + endif + + " If the cursor is inside the fold, it needs to be opened + if s:lineStart <= s:savedCursor && s:lineStop >= s:savedCursor + " Remember it as an open fold + let s:foldsOpenedList{s:openFoldListItems} = s:lineStart + let s:openFoldListItems = s:openFoldListItems + 1 + endif + + elseif s:foldingMode == s:MODE_CREATE_FOLDS + " Correct lineStop if needed (the script might have mistaken lines + " beyond the file's scope for trailing empty lines) + if s:lineStop > s:fileLineCount + let s:lineStop = s:fileLineCount + endif + " Create the actual fold! + exec s:lineStart . "," . s:lineStop . "fold" + let s:foldsCreated = s:foldsCreated + 1 + endif +endfunction +" }}} +function! s:FindPureBlockStart(startPattern) " {{{ + " When the startPattern is 'function', this following search will match: + " + " function foo($bar) { function foo($bar) + " { + " + " function foo($bar) function foo($bar1, + " .. { $bar2) + " { + " + "return search(a:startPattern . '.*\%[\n].*{', 'W') + "return search(a:startPattern . '.*\%[\n].*\%[\n].*{', 'bW') + + " This function can match the line its on *again* if the cursor was + " restored.. hence we search twice if needed.. + let currentLine = line('.') + let line = search(a:startPattern . '.*\(\%[\n].*\)\{,10\}{', 'bW') + if currentLine == line + let line = search(a:startPattern . '.*\(\%[\n].*\)\{,10\}{', 'bW') + endif + return line +endfunction +" }}} +function! s:FindPatternStart(startPattern) " {{{ + " This function can match the line its on *again* if the cursor was + " restored.. hence we search twice if needed.. + let currentLine = line('.') + let line = search(a:startPattern, 'bW') + if currentLine == line + let line = search(a:startPattern, 'bW') + endif + return line +endfunction +" }}} +function! s:FindOptionalPHPDocComment() " {{{ + " Is searching for PHPDoc disabled? + if s:currentPhpDocMode == s:FOLD_WITHOUT_PHPDOC + " .. Return the original Fold's start + return s:lineStart + endif + + " Skipover 'empty' lines in search for PhpDoc + let s:counter = 0 + let s:currentLine = s:lineStart - 1 + while s:counter < s:searchPhpDocLineCount + let line = getline(s:currentLine) + if (matchstr(line, '^\s*$') == line) + let s:currentLine = s:currentLine - 1 + endif + let s:counter = s:counter + 1 + endwhile + + " Is there a closing C style */ on the above line? + let checkLine = s:currentLine + if strridx(getline(checkLine), "\*\/") != -1 + " Then search for the matching C style /* opener + while 1 + if strridx(getline(checkLine), "\/\*") != -1 + " Only continue adjusting the Fold's start if it really is PHPdoc.. + " (which is characterized by a double asterisk, like /**) + if strridx(getline(checkLine), "\/\*\*") != -1 + " Also only continue adjusting if the PHPdoc opener does + " not contain a '/**#@+'. Those type of comments are + " supposed to match with a #@- .. + if strridx(getline(checkLine), '#@+') == -1 + " .. Return this as the Fold's start + return checkLine + else + break + endif + else + break + endif + endif + let checkLine = checkLine - 1 + endwhile + endif + " .. Return the original Fold's start + return s:lineStart +endfunction +" }}} +function! s:FindPureBlockEnd(startPair, endPair, searchStartPairFirst, ...) " {{{ + " Place Cursor on the opening pair/brace? + if a:searchStartPairFirst == s:SEARCH_PAIR_START_FIRST + let line = search(a:startPair, 'W') + endif + + " Search for the entangled closing brace + " call cursor(line, 1) " set the cursor to the start of the lnum line + if s:extensiveBracketChecking == s:TRUE + let line = searchpair(a:startPair, a:startPair, a:endPair, 'W', 'SkipMatch()') + else + let line = searchpair(a:startPair, a:startPair, a:endPair, 'W') + endif + if line == 0 + let line = search(a:endPair, 'W') + endif + if line == 0 + " Return error + return 0 + endif + + " If the fold exceeds more than one line, and searching for empty lines is + " not disabled.. + let foldExceedsOneLine = line - s:lineStart >= 1 + if a:0 == 1 + let emptyLinesNotDisabled = a:1 + else + let emptyLinesNotDisabled = s:TRUE + endif + if foldExceedsOneLine && emptyLinesNotDisabled + " Then be greedy with extra 'trailing' empty line(s) + let s:counter = 0 + while s:counter < s:searchEmptyLinesPostfixing + let linestr = getline(line + 1) + if (matchstr(linestr, '^\s*$') == linestr) + let line = line + 1 + endif + let s:counter = s:counter + 1 + endwhile + endif + + return line +endfunction +" }}} +function! s:FindPatternEnd(endPattern) " {{{ + let line = search(a:endPattern, 'W') + + " If the fold exceeds more than one line + if line - s:lineStart >= 1 + " Then be greedy with extra 'trailing' empty line(s) + let s:counter = 0 + while s:counter < s:searchEmptyLinesPostfixing + let linestr = getline(line + 1) + if (matchstr(linestr, '^\s*$') == linestr) + let line = line + 1 + endif + let s:counter = s:counter + 1 + endwhile + endif + + return line +endfunction +" }}} + +function! PHPFoldText() " {{{ + let currentLine = v:foldstart + let lines = (v:foldend - v:foldstart + 1) + let lineString = getline(currentLine) + " See if we folded a marker + if strridx(lineString, "{{{") != -1 " }}} + " Is there text after the fold opener? + if (matchstr(lineString, '^.*{{{..*$') == lineString) " }}} + " Then only show that text + let lineString = substitute(lineString, '^.*{{{', '', 'g') " }}} + " There is text before the fold opener + else + " Try to strip away the remainder + let lineString = substitute(lineString, '\s*{{{.*$', '', 'g') " }}} + endif + " See if we folded a DocBlock + elseif strridx(lineString, '#@+') != -1 + " Is there text after the #@+ piece? + if (matchstr(lineString, '^.*#@+..*$') == lineString) + " Then show that text + let lineString = substitute(lineString, '^.*#@+', '', 'g') . ' ' . g:phpDocBlockIncludedPostfix + " There is nothing? + else + " Use the next line.. + let lineString = getline(currentLine + 1) . ' ' . g:phpDocBlockIncludedPostfix + endif + " See if we folded an API comment block + elseif strridx(lineString, "\/\*\*") != -1 + " (I can't get search() or searchpair() to work.., therefore the + " following loop) + let s:state = 0 + while currentLine < v:foldend + let line = getline(currentLine) + if s:state == 0 && strridx(line, "\*\/") != -1 + " Found the end, now we need to find the first not-empty line + let s:state = 1 + elseif s:state == 1 && (matchstr(line, '^\s*$') != line) + " Found the line to display in fold! + break + endif + let currentLine = currentLine + 1 + endwhile + let lineString = getline(currentLine) + endif + + " Some common replaces... + " if currentLine != v:foldend + let lineString = substitute(lineString, '/\*\|\*/\d\=', '', 'g') + let lineString = substitute(lineString, '^\s*\*\?\s*', '', 'g') + let lineString = substitute(lineString, '{$', '', 'g') + let lineString = substitute(lineString, '($', '(..);', 'g') + " endif + + " Emulates printf("%3d", lines).. + if lines < 10 + let lines = " " . lines + elseif lines < 100 + let lines = " " . lines + endif + + " Append an (a) if there is PhpDoc in the fold (a for API) + if currentLine != v:foldstart + let lineString = lineString . " " . g:phpDocIncludedPostfix . " " + endif + + " Return the foldtext + return "+--".lines." lines: " . lineString +endfunction +" }}} +function! SkipMatch() " {{{ +" This function is modified from a PHP indent file by John Wellesz +" found here: http://www.vim.org/scripts/script.php?script_id=1120 + if (!s:synIDattr_exists) + return 0 + endif + let synname = synIDattr(synID(line("."), col("."), 0), "name") + if synname == "phpParent" || synname == "javaScriptBraces" || synname == "phpComment" + return 0 + else + return 1 + endif +endfun +" }}} + +" " Check filetype == php before automatically creating (fast) folds {{{1 +" function! s:CheckAutocmdEnablePHPFold() +" if &filetype == "php" && ! g:DisableAutoPHPFolding +" call s:EnableFastPHPFolds() +" endif +" endfunction +" " }}} + +" " Call CheckAutocmdEnablePHPFold on BufReadPost {{{1 +" augroup SetPhpFolds +" au! +" au BufReadPost * call s:CheckAutocmdEnablePHPFold() +" augroup END +" " }}} + +" ftplugin section +if !get(g:, 'DisableAutoPHPFolding', 0) + call s:EnableFastPHPFolds() +endif + +let &cpo = s:save_cpo +unlet s:save_cpo +" vim:ft=vim:foldmethod=marker:nowrap:tabstop=4:shiftwidth=4 diff --git a/.vimrc b/.vimrc index f09a89b..2d0ef4d 100644 --- a/.vimrc +++ b/.vimrc @@ -40,9 +40,9 @@ set background=dark " Uncomment the following to have Vim load indentation rules and plugins " according to the detected filetype. -"if has("autocmd") -" filetype plugin indent on -"endif +if has("autocmd") + filetype plugin on +endif " The following are commented out as they cause vim to behave a lot " differently from regular Vi. They are highly recommended though.