├── .github └── ISSUE_TEMPLATE │ └── bug-report.md ├── .gitignore ├── README.md ├── VimFlavor ├── addon-info.json ├── autoload └── lh │ └── c │ └── fold.vim ├── doc └── screencast-vim-fold.gif ├── ftplugin └── c │ └── c-fold.vim ├── macros └── trivial.fold.vim ├── mkVba └── mk-vimfold4c.vim └── tests └── lh ├── test-fold-issue3-2.cpp ├── test-fold-issue3.cpp ├── test-fold-issue5.cpp ├── test-fold.cpp └── trivial-test.fold /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us fix, or improve folding 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Paste a code which is incorrectly folded. 12 | 13 | **Expected behavior** 14 | A clear and concise description of what you expected to happen. 15 | 16 | **Screenshots** 17 | If applicable, add screenshots to help explain your problem. 18 | In that case I'll also be interested in a screenshot after a: 19 | 20 | ```vim 21 | :call lh#c#fold#verbose(2) 22 | zX 23 | zR 24 | ``` 25 | 26 | And in the result of 27 | 28 | ```vim 29 | :echo map(range(1,line('$')), 'lh#c#fold#expr(v:val)') 30 | ``` 31 | 32 | **Additional context** 33 | Add any other context about the problem here. In particular your settings may be of help: 34 | 35 | - what are the value of `g:fold_options` and of `b:fold_options` 36 | - what does `:set` display -- you can copy it into your clipboard with `:let @+ = join(lh#askvim#execute('set'), "\n")` 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*.sw* 2 | tags 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | VimFold4C 2 | ========= 3 | 4 | Reactive vim fold plugin for C & C++ (and similar languages). 5 | 6 | Unlike folding on 7 | [syntax](http://vimhelp.appspot.com/fold.txt.html#fold%2dsyntax) or on 8 | [indent](http://vimhelp.appspot.com/fold.txt.html#fold%2dindent), this script 9 | tries to correctly detect the fold boundaries. 10 | 11 | [![Last release](https://img.shields.io/github/tag/LucHermitte/VimFold4C.svg)](https://github.com/LucHermitte/VimFold4C/releases) [![Project Stats](https://www.openhub.net/p/21020/widgets/project_thin_badge.gif)](https://www.openhub.net/p/21020) 12 | 13 | ## Features 14 | 15 | The foldtext displayed will also try to be as pertinent as possible: 16 | - Correctly indented 17 | - Strip spaces in parenthesis 18 | - Strip `scopes::` (optional) 19 | - Multiple subsequent (consecutive ?) `#include` will be condensed into one line 20 | - `#if` & co are folded 21 | - When the fold text line to display is too long, simplifications are operated: 22 | - Template parameters may be discarded 23 | - Initialisation-lists are replaced with `....` 24 | - Parameter names are discarded (this requires [lh-cpp](https://github.com/LucHermitte/lh-cpp)). 25 | - Last parameters are discarded 26 | - fold any instruction, or declaration, spanning on several lines (`cout`, 27 | `printf`, `log(stuff << stuff)` 28 | 29 | 30 | ## Note 31 | In order to keep the plugin reactive, I had to introduce a few hacks that 32 | diminish the precision of the incremental algorithm used to detect fold 33 | boundaries. 34 | As a consequence, sometimes lines are folded in a very strange way. 35 | In order to fix it, use `zx` or `zX` to reset all fold boundaries. 36 | 37 | ## Demo 38 | 39 | Here is a little screencast to see how things are displayed with VimFold4C. 40 | 41 | ![VimFold4C demo](doc/screencast-vim-fold.gif "VimFold4C demo") 42 | 43 | Note: the code comes from unrelated 44 | [experiment of mine](https://github.com/LucHermitte/NamedParameter). 45 | 46 | ## Options 47 | 48 | ### How to set them (syntax) 49 | 50 | You can set local or global options to tune the behaviour of this fold-plugin. 51 | ```vim 52 | " In the .vimrc 53 | let g:fold_options = { 54 | \ 'fallback_method' : { 'line_threshold' : 2000, 'method' : 'syntax' }, 55 | \ 'fold_blank': 0, 56 | \ 'fold_includes': 0, 57 | \ 'max_foldline_length': 'win', 58 | \ 'merge_comments' : 1, 59 | \ 'show_if_and_else': 1, 60 | \ 'strip_namespaces': 1, 61 | \ 'strip_template_arguments': 1 62 | \ } 63 | ``` 64 | or from a [local_vimrc plugin](https://github.com/LucHermitte/local_vimrc): 65 | ```vim 66 | let b:fold_options = { 67 | \ 'fallback_method' : { 'line_threshold' : 2000, 'method' : 'syntax' }, 68 | \ 'fold_blank': 1, 69 | \ 'fold_includes': 1, 70 | \ 'ignored_doxygen_fields': ['class', 'ingroup', 'function', 'def', 'defgroup', 'exception', 'headerfile', 'namespace', 'property', 'fn', 'var'], 71 | \ 'max_foldline_length': 'win', 72 | \ 'merge_comments' : 0, 73 | \ 'show_if_and_else': 1, 74 | \ 'strip_namespaces': 1, 75 | \ 'strip_template_arguments': 1 76 | \ } 77 | 78 | ``` 79 | 80 | ### Available options 81 | The 82 | [options](https://github.com/LucHermitte/lh-vim-lib/blob/master/doc/Options.md) are: 83 | 84 | - `fallback_method` (default: `{'line_threshold': 0}`) tells to use another 85 | fold method when the number of lines in the current file is greater to the 86 | given threshold. 87 | In that case use `fallback_method.method` on the current buffer as 88 | [fold method](http://vimhelp.appspot.com/options.txt.html#%27foldmethod%27) 89 | -- (default: [`"syntax"`](http://vimhelp.appspot.com/fold.txt.html#fold%2dsyntax)). 90 | This option is ignored if the threshold equals 0. 91 | 92 | - `fold_blank` (default: _true_) tells to fold blanks lines with the lines 93 | preceding them. 94 | 95 | - `fold_includes` (default: _true_) tells to fold blocks of `#include` directives. 96 | 97 | - `ignored_doxygen_fields` (default: `['class', 'ingroup', 'function', 'def', 98 | 'defgroup', 'exception', 'headerfile', 'namespace', 'property', 'fn', 99 | 'var']`) list of doxygen keywords that shall be ignored when computing the 100 | folded text -- when `merge_comments == 0` 101 | 102 | - `max_foldline_length` (default: _"win"_) specifies the maximum line length 103 | of the fold text. The possibile values are: 104 | - _"win"_: stops at current window width 105 | - _"tw"_: stops at current [`'textwidth'`](http://vimhelp.appspot.com/options.txt.html#%27tw%27) column 106 | - number: hardcoded maximum number of characters to keep. 107 | 108 | - `merge_comments` (default: 1) specifies whether comments shall be folded 109 | together with the code or separativelly. 110 | 111 | - `strip_namespaces` (default: _true_) tells to strip scopes like `std::` or 112 | `boost::filesystem::` from the fold text generated. 113 | 114 | - `strip_template_arguments` (default: _true_) strips template arguments from 115 | the fold text generated if the text would be too long for the current window 116 | width 117 | 118 | - `show_if_and_else` (which is currently hard-coded to _true_) requires to have 119 | two folds on 120 | 121 | ```c 122 | if (foo) { 123 | foo_action(); 124 | } else { 125 | bar_action(); 126 | } 127 | ``` 128 | 129 | instead of the single fold we have when using `indent` _foldmethod_ (or was it 130 | the `syntax` one ?). 131 | 132 | #### Note 133 | Do not set the 134 | [`'foldmethod'`](http://vimhelp.appspot.com/options.txt.html#%27foldmethod%27) 135 | option in a ftplugin, or in an autocommand. VimFold4C already takes care of 136 | setting it to [`expr`](http://vimhelp.appspot.com/fold.txt.html#fold%2dexpr). 137 | 138 | ## Requirements / Installation 139 | 140 | * Requirements: Vim 7.+, [lh-vim-lib](http://github.com/LucHermitte/lh-vim-lib) 4.2.0+ 141 | 142 | * With [vim-addon-manager](https://github.com/MarcWeber/vim-addon-manager), install VimFold4C. 143 | 144 | ```vim 145 | ActivateAddons VimFold4C 146 | ``` 147 | 148 | * or with [vim-flavor](http://github.com/kana/vim-flavor) which also supports 149 | dependencies: 150 | 151 | ``` 152 | flavor 'LucHermitte/VimFold4C' 153 | ``` 154 | 155 | * When installing [lh-cpp](http://github.com/LucHermitte/lh-cpp) with 156 | [vim-addon-manager](https://github.com/MarcWeber/vim-addon-manager), or 157 | other plugin managers based on 158 | [vim-pi](https://bitbucket.org/vimcommunity/vim-pi), or with 159 | [vim-flavor](http://github.com/kana/vim-flavor) this fold-plugin will get 160 | automatically installed. 161 | 162 | ```vim 163 | ActivateAddons lh-cpp 164 | " Or just this one (and soon as I register it in vim-pi): 165 | ActivateAddons VimFold4C 166 | ``` 167 | 168 | This is the preferred method because of the various dependencies, and 169 | because VimFold4C will do a better job if lh-cpp is installed and detected. 170 | 171 | * or with Vundle/NeoBundle (expecting I haven't forgotten anything): 172 | 173 | ```vim 174 | Bundle 'LucHermitte/lh-vim-lib' 175 | Bundle 'LucHermitte/VimFold4C' 176 | ``` 177 | 178 | So far, it is only triggered for C and C++. It should be easy to use it from 179 | C#, Java, and other languages with C like syntax: a 180 | 181 | ```vim 182 | runtime ftplugin/c/c-fold.vim 183 | ``` 184 | 185 | from a C#/Java/... ftplugin should do the trick. 186 | 187 | However, I'm unlikely to handle specials cases in those languages. 188 | 189 | ## TO DO 190 | There is still a lot to be done: 191 | 192 | - [optional] Fold visibilities 193 | - `#include` 194 | - [optional] cut the foldtext line when it's too long to fit 195 | - [optional] strip the dirname of each included file to build the foldtext 196 | line. 197 | - Comments 198 | - Correctly handle comments for fold boundaries detection 199 | - [optional] when there is a leading comment, add a summary at the end of the 200 | fold text 201 | - [optional] support a policy for comments handling (integrated to the 202 | following fold, independent fold, not folded) 203 | - use @doxygen tags to build comments foldtext 204 | - File headers shall have a special treatment -> detect 205 | copyrights/licence/... to build the foldtext 206 | - Tests 207 | - Test, Test, and re-test! 208 | - Test with C++11 lambdas 209 | - Control statements 210 | - `switch`/`case` 211 | - increment foldlevel for every `case` 212 | - [optional] merge `case`s that aren't separated by a `break;` 213 | - `do { } while();` requires a specific handling 214 | 215 | ## History 216 | - A long time ago (~2001), Johannes Zellner published a first folding plugin 217 | for C & C++. 218 | - Then, I did some changes (2002-2004), but the result was very slow at the 219 | time. (the last version is still archived in 220 | ) 221 | - Eventually I got tired of the slow execution times and moved back to 222 | `foldmethod=indent`. 223 | 224 | - Here is a new (2014) version almost entirely rewritten, that I hope will 225 | be fast enough to be usable. 226 | -------------------------------------------------------------------------------- /VimFlavor: -------------------------------------------------------------------------------- 1 | flavor 'LucHermitte/lh-vim-lib', '>= 4.2.0' 2 | -------------------------------------------------------------------------------- /addon-info.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "VimFold4C", 3 | "version": "dev", 4 | "author" : "Luc Hermitte ", 5 | "maintainer" : "Luc Hermitte ", 6 | "repository" : {"type": "git", "url": "git@github.com:LucHermitte/VimFold4C.git"}, 7 | "dependencies" : { 8 | "lh-vim-lib" : {"type" : "git", "url" : "git@github.com:LucHermitte/lh-vim-lib.git"} 9 | }, 10 | "description" : "Folding for C and C++", 11 | "homepage" : "http://github.com/LucHermitte/VimFold4C" 12 | } 13 | -------------------------------------------------------------------------------- /autoload/lh/c/fold.vim: -------------------------------------------------------------------------------- 1 | "============================================================================= 2 | " File: autoload/lh/c/fold.vim {{{1 3 | " Author: Luc Hermitte 4 | " 5 | " Version: 3.2.0 6 | let s:k_version = 320 7 | " Created: 06th Jan 2002 8 | "------------------------------------------------------------------------ 9 | " Description: 10 | " Core functions for VimFold4C 11 | " 12 | "------------------------------------------------------------------------ 13 | " Installation: 14 | " Drop this file into {rtp}/addons/VimFold4C/autoload/lh/c 15 | " Requires Vim7+ 16 | " }}}1 17 | "============================================================================= 18 | 19 | let s:cpo_save=&cpo 20 | set cpo&vim 21 | "------------------------------------------------------------------------ 22 | " ## Misc Functions {{{1 23 | " # Version {{{2 24 | function! lh#c#fold#version() 25 | return s:k_version 26 | endfunction 27 | 28 | " # Debug {{{2 29 | let s:verbose = get(s:, 'verbose', 0) 30 | function! lh#c#fold#verbose(...) "{{{3 31 | if a:0 > 0 | let s:verbose = a:1 | endif 32 | if s:verbose 33 | sign define Fold0 text=0 texthl=Identifier 34 | for i in range(1, 9) 35 | exe 'sign define Fold'.i.' text=|'.i.' texthl=Identifier' 36 | exe 'sign define Fold'.i.'gt text=>'.i.' texthl=Identifier' 37 | exe 'sign define Fold'.i.'lt text=<'.i.' texthl=Identifier' 38 | endfor 39 | endif 40 | exe 'sign unplace * buffer='.bufnr('%') 41 | return s:verbose 42 | endfunction 43 | 44 | function! s:Log(expr, ...) "{{{3 45 | call call('lh#log#this',[a:expr]+a:000) 46 | endfunction 47 | 48 | function! s:Verbose(expr, ...) "{{{3 49 | if s:verbose >= 2 50 | call call('s:Log',[a:expr]+a:000) 51 | endif 52 | endfunction 53 | 54 | function! lh#c#fold#debug(expr) abort "{{{3 55 | return eval(a:expr) 56 | endfunction 57 | 58 | " Function: lh#c#fold#toggle_balloons() {{{3 59 | function! lh#c#fold#toggle_balloons() abort 60 | if &bexpr == 'lh#c#fold#_balloon_expr()' 61 | call s:balloon_reset.finalize() 62 | call lh#common#WarningMsg('Stop balloon debugging for VimFold4C') 63 | else 64 | let s:balloon_reset = lh#on#exit() 65 | \.restore('&beval') 66 | \.restore('&bexpr') 67 | setlocal beval bexpr=lh#c#fold#_balloon_expr() 68 | call lh#common#WarningMsg('Start balloon debugging for VimFold4C') 69 | endif 70 | endfunction 71 | 72 | " Function: lh#c#fold#_balloon_expr() {{{3 73 | function! lh#c#fold#_balloon_expr() abort 74 | if !exists('b:fold_data') 75 | " This may happen when splitting a window where bexpr is set 76 | " `:sp` copy all local option values, even if the filetype of the 77 | " buffer on't be compatible... 78 | setlocal bexpr< 79 | return 80 | endif 81 | let l = v:beval_lnum 82 | let expr = printf("Debug VimFold4C\nline: %d\nlevel: %d\ndata: [%d, %d]\ninstr: [%d, %d]\ncontext: %s", 83 | \ l, b:fold_data.levels[l], 84 | \ b:fold_data.begin[l], b:fold_data.end[l], 85 | \ b:fold_data.instr_begin[l], b:fold_data.instr_end[l], 86 | \ b:fold_data.context[l] 87 | \ ) 88 | return expr 89 | endfunction 90 | 91 | " # Options {{{2 92 | " let (bpg):fold_options = { 93 | " \ 'fallback_method' : { 'line_threshold' : 2000, 'method' : 'syntax' }, 94 | " \ 'fold_blank': 1, 95 | " \ 'fold_includes': 1, 96 | " \ 'ignored_doxygen_fields' : ['class', 'ingroup', 'function', 'def', 'defgroup', 'exception', 'headerfile', 'namespace', 'property', 'fn', 'var'] 97 | " \ 'max_foldline_length': 'win'/'tw'/42, 98 | " \ 'merge_comments' : 1 99 | " \ 'show_if_and_else': 1, 100 | " \ 'strip_namespaces': 1, 101 | " \ 'strip_template_arguments': 1, 102 | " \ } 103 | function! s:opt_show_if_and_else() abort 104 | return lh#option#get('fold_options.show_if_and_else', 1) 105 | endfunction 106 | function! s:opt_strip_template_arguments() abort 107 | return lh#option#get('fold_options.strip_template_arguments', 1) 108 | endfunction 109 | function! s:opt_strip_namespaces() abort 110 | return lh#option#get('fold_options.strip_namespaces', 1) 111 | endfunction 112 | function! s:opt_fold_blank() abort 113 | " Empty lines shall always not be merged with the previous line 114 | return lh#option#get('fold_options.fold_blank', 1) 115 | endfunction 116 | function! s:opt_fold_includes() abort 117 | return lh#option#get('fold_options.fold_includes', 1) 118 | endfunction 119 | function! s:opt_max_foldline_length() abort 120 | " TODO: optimize this function call 121 | let how = lh#option#get('fold_options.max_foldline_length', 'win') 122 | if type(how) == type(42) 123 | return how - &foldcolumn 124 | elseif how =~ '\ctw\|textwidth' 125 | return &tw - &foldcolumn 126 | else " if how =~ '\cwin\%[dow]' 127 | " I don't check for errors as it could mess vim 128 | return winwidth(winnr()) - &foldcolumn 129 | endif 130 | endfunction 131 | function! s:opt_merge_comments() abort 132 | return lh#option#get('fold_options.merge_comments', 1) 133 | endfunction 134 | let s:k_ignored_doxygen_fields = ['class', 'ingroup', 'function', 'def', 'defgroup', 'exception', 'headerfile', 'namespace', 'property', 'fn', 'var'] 135 | function! s:opt_ignored_doxygen_fields() abort 136 | return lh#option#get('fold_options.ignored_doxygen_fields', s:k_ignored_doxygen_fields) 137 | endfunction 138 | 139 | function! s:line_doesnt_matches_an_ignored_doxygen_field(line) abort 140 | let fields = s:opt_ignored_doxygen_fields() 141 | let matches = map(copy(fields), 'match(a:line, "[@\\\\]".v:val)') 142 | return empty(filter(matches, 'v:val >= 0')) 143 | endfunction 144 | 145 | "------------------------------------------------------------------------ 146 | " ## Exported functions {{{1 147 | " Function: lh#c#fold#expr() {{{2 148 | " Expectation: with i < j, lh#c#fold#expr(i) is called before lh#c#fold#expr(j) 149 | " This way instead of having something recursive to the extreme, we cache fold 150 | " levels from one call to the other. 151 | " It means sometimes we have to refresh everything with zx/zX 152 | function! lh#c#fold#expr(lnum) abort 153 | let opt_merge_comments = s:opt_merge_comments() 154 | let opt_fold_blank = s:opt_fold_blank() 155 | 156 | " 0- Resize b:fold_* arrays to have as many lines as the buffer {{{3 157 | call s:ResizeCache() 158 | 159 | " 1- First obtain the current fold boundaries {{{3 160 | let where_it_starts = b:fold_data.begin[a:lnum] 161 | if where_it_starts == 0 162 | " it's possible the boundaries was never known => compute thems 163 | let where_it_ends = s:WhereInstructionEnds(a:lnum, opt_merge_comments, opt_fold_blank) 164 | let where_it_starts = b:fold_data.begin[a:lnum] 165 | else 166 | " Actually, we can't know when text is changed, the where it starts may 167 | " change 168 | let where_it_ends = b:fold_data.end[a:lnum] 169 | endif 170 | 171 | 172 | " 2- Then return what must be {{{3 173 | let instr_start = b:fold_data.instr_begin[a:lnum] 174 | let instr_lines = s:getline(instr_start, where_it_ends) 175 | 176 | " Case: "} catch|else|... {" & "#elif" & "#else" {{{4 177 | " TODO: use the s:opt_show_if_and_else() option 178 | " -> We check the next line to see whether it closes something before opening 179 | " something new 180 | if a:lnum < line('$') 181 | let instr_line = join(instr_lines, '') 182 | let next_line = getline(a:lnum+1) 183 | if next_line =~ '}.*{' 184 | " assert(where_it_ends < a:lnum+1) 185 | let decr = len(substitute(matchstr(next_line, '^[^{]*'), '[^}]', '', 'g')) 186 | \ + len(substitute(instr_line, '[^}]', '', 'g')) 187 | let b:fold_data.context[a:lnum] = '' 188 | return s:DecrFoldLevel(a:lnum, decr) 189 | elseif next_line =~ '^\s*#\s*\(elif\|else\)' 190 | let decr = 1 191 | \ + len(substitute(instr_line, '[^}]', '', 'g')) 192 | return s:DecrFoldLevel(a:lnum, decr) 193 | endif 194 | endif 195 | 196 | " The lines to analyze {{{4 197 | let lines = getline(where_it_starts, where_it_ends) 198 | let line = getline(where_it_ends) 199 | 200 | " Case: #include {{{4 201 | let fold_includes = s:opt_fold_includes() 202 | if fold_includes && line =~ '^\s*#\s*include' 203 | let b:fold_data.context[a:lnum] = 'include' 204 | if b:fold_data.context[a:lnum-1] == '#if' 205 | " Override #include context with #if 206 | let b:fold_data.context[a:lnum] = b:fold_data.context[a:lnum-1] 207 | return s:KeepFoldLevel(a:lnum) 208 | elseif b:fold_data.context[a:lnum-1] != 'include' 209 | " Start a new #include block 210 | let b:fold_data.context[where_it_starts : where_it_ends] 211 | \ = repeat(['include'], 1 + where_it_ends - where_it_starts) 212 | " And update the include context for the next elements... 213 | if getline(where_it_ends+1) =~ '^\s*#\s*include' 214 | let where_next_ends = s:WhereInstructionEnds(a:lnum+1, opt_merge_comments, opt_fold_blank) 215 | let b:fold_data.context[where_it_starts : where_next_ends] 216 | \ = repeat(['include'], 1 + where_next_ends - where_it_starts) 217 | endif 218 | return s:IncrFoldLevel(a:lnum, 1) 219 | endif 220 | 221 | let where_next_ends = s:WhereInstructionEnds(a:lnum+1, opt_merge_comments, opt_fold_blank) 222 | let next_lines = getline(a:lnum+1, where_next_ends) 223 | if match(next_lines, '^\s*#\s*include') == -1 224 | return s:DecrFoldLevel(a:lnum, 1) 225 | else 226 | let b:fold_data.context[where_it_starts : where_next_ends] 227 | \ = repeat(['include'], 1 + where_next_ends - where_it_starts) 228 | return s:KeepFoldLevel(a:lnum) 229 | endif 230 | elseif b:fold_data.context[a:lnum] == 'include' 231 | if a:lnum == where_it_ends && match(getline(a:lnum+1), '^\s*#\s*include') == -1 232 | " line is the last in the "#include" context, and the next line 233 | " doesn't match "#include" either => end of the fold 234 | return s:DecrFoldLevel(a:lnum, 1) 235 | else 236 | return s:KeepFoldLevel(a:lnum) 237 | endif 238 | endif 239 | 240 | " Clear include context {{{4 241 | " But maintain #if context and ignore #endif context 242 | if b:fold_data.context[a:lnum-1] == '#if' && b:fold_data.context[a:lnum] != '#endif' 243 | let b:fold_data.context[a:lnum] = b:fold_data.context[a:lnum-1] 244 | elseif b:fold_data.context[a:lnum] != '#endif' 245 | let b:fold_data.context[a:lnum] = '' 246 | endif 247 | 248 | " Case: Opening things ? {{{4 249 | " The foldlevel increase can be done only at the start of the instruction 250 | if a:lnum == where_it_starts 251 | if line =~ '^\s*#\s*ifndef' 252 | let symbol = matchstr(line, '^\s*#\s*ifndef\s\+\zs\S\+') 253 | if (s:getline(a:lnum+1) !~ '^\s*#\s*define\s\+'.symbol.'\s*$') || !empty(filter(b:fold_data.context[:a:lnum], 'v:val == "#if"')) 254 | let b:fold_data.context[a:lnum] = '#if' 255 | return s:IncrFoldLevel(a:lnum, 1) 256 | " else: we ignore the first which is likelly an anti-reinclusion 257 | " guard 258 | endif 259 | elseif line =~ '^\s*#\s*if' 260 | let b:fold_data.context[a:lnum] = '#if' 261 | return s:IncrFoldLevel(a:lnum, 1) 262 | endif 263 | elseif line =~ '{[^}]*$' 264 | " Case: opening, but started earlier 265 | " -> already opened -> keep fold level 266 | return s:KeepFoldLevel(a:lnum) 267 | endif 268 | 269 | " Case: "#else", "#elif", "#endif" {{{4 270 | if line =~ '^\s*#\s*\(else\|elif\)' 271 | return s:IncrFoldLevel(a:lnum, 1) 272 | elseif match(lines, '^\s*#\s*endif') >= 0 273 | if a:lnum == where_it_ends 274 | let b:fold_data.context[a:lnum] = '#endif' 275 | return s:DecrFoldLevel(a:lnum, 1) 276 | else 277 | " Register where the #endif ends 278 | let b:fold_data.context[where_it_starts : where_it_ends] 279 | \ = repeat(['#endif'], 1 + where_it_ends - where_it_starts) 280 | endif 281 | endif 282 | if b:fold_data.context[a:lnum] == '#endif' && a:lnum == where_it_ends 283 | return s:DecrFoldLevel(a:lnum, 1) 284 | endif 285 | 286 | " Case: "} ... {" -> "{" // the return of the s:opt_show_if_and_else() {{{4 287 | " TODO: support multiline comments 288 | call map(instr_lines, "substitute(v:val, '^[^{]*}\\ze.*{', '', '')") 289 | 290 | let line = join(instr_lines, '') 291 | let incr = lh#string#count_char(line, '{') " len(substitute(line, '[^{]', '', 'g')) 292 | let decr = lh#string#count_char(line, '}') " len(substitute(line, '[^}]', '', 'g')) 293 | 294 | if incr > decr && a:lnum == where_it_starts 295 | return s:IncrFoldLevel(a:lnum, incr-decr) 296 | elseif decr > incr 297 | if a:lnum != where_it_ends 298 | " Wait till the last moment! 299 | return s:KeepFoldLevel(a:lnum) 300 | else 301 | return s:DecrFoldLevel(a:lnum, decr-incr) 302 | endif 303 | else 304 | " This is where we can detect instructions, or comments, spanning on several lines 305 | " Note: ";" case permits to merge comments with single-line function 306 | " declarations, but also to fold multi-line instructions. At this 307 | " point, I don't see how to distinguish the two cases: functions 308 | " calls and function declarations are quite alike. 309 | " Should it be an option? 310 | if line =~ '\v\{.*\}|;\s*$' 311 | \ || (!opt_merge_comments && join(getline(instr_start, where_it_ends), '') =~ '\v^\s*(/\*.*\*/|//)') 312 | " first case: oneliner that cannot be folded => we left it as it is 313 | if a:lnum == instr_start && a:lnum == where_it_ends | return s:KeepFoldLevel(a:lnum) 314 | elseif a:lnum == instr_start | return s:IncrFoldLevel(a:lnum, 1) 315 | elseif a:lnum == where_it_ends | return s:DecrFoldLevel(a:lnum, 1) " Note: this case cannot happen 316 | endif 317 | endif 318 | return s:KeepFoldLevel(a:lnum) 319 | endif 320 | endfunction 321 | 322 | " Function: lh#c#fold#text() {{{2 323 | function! lh#c#fold#text_(lnum) abort 324 | " options cached 325 | let shall_fold_blank = s:opt_fold_blank() 326 | let ts = s:Build_ts() 327 | 328 | let lnum = s:NextNonCommentNonBlank(a:lnum, shall_fold_blank) 329 | 330 | " Case: Don't merge comment {{{3 331 | if (lnum > a:lnum) && ! s:opt_merge_comments() 332 | " => Extract something like the brief line... 333 | let lines = getline(b:fold_data.begin[a:lnum], b:fold_data.end[a:lnum]) 334 | 335 | let leading_spaces = matchstr(lines[0], '^\s*') 336 | let leading_spaces = substitute(leading_spaces, "\t", ts, 'g') 337 | 338 | let [lead, lead_start, lead_end] = matchstrpos(lines[0], '\v/.[*!]?\ze(\_s|$)') 339 | " Trim line of repeated characters, if any 340 | let lines[0] = substitute(lines[0][lead_end:], '\v(.)\1+\s*$', '', '') 341 | let tail = matchstr(lines[-1], './\ze\s*$') 342 | " Remove leading characters like '*', '///', and so on 343 | call map(lines, 'substitute(v:val, "\\v^\\s*(/\\*[*!]|//!|///|[*])", "", "")') 344 | " Remove leading spaces 345 | call map(lines, 'substitute(v:val, "^\\s*", "", "")') 346 | " * Ignore stuff like \class, @ingroup, ... 347 | let ignored_doxygen_fields = s:opt_ignored_doxygen_fields() 348 | call filter(lines, 's:line_doesnt_matches_an_ignored_doxygen_field(v:val)') 349 | " * Extract brief line 350 | " -> Search the first empty line after the first that is not empty... 351 | let first = match(lines, '.') 352 | let end = index(lines, '', first+1) 353 | " -> Ignore what follows 354 | let lines = lines[first : end] 355 | let line = join(lines, ' ') 356 | let line = substitute(line, '\v\.\zs(\_s|$).*', '', '') 357 | let line = substitute(line, '[\\@]brief ', '', '') 358 | 359 | return leading_spaces . lead.' '.line.' '.tail 360 | endif 361 | 362 | " Case: #include {{{3 363 | if b:fold_data.context[a:lnum] == 'include' 364 | let includes = [] 365 | let lastline = line('$') 366 | while lnum <= lastline && b:fold_data.context[lnum] == 'include' 367 | let includes += [matchstr(getline(lnum), '["<]\zs.*\ze[">]')] 368 | let lnum = s:NextNonCommentNonBlank(lnum+1, shall_fold_blank) 369 | endwhile 370 | return '#include '.join(includes, ' ') 371 | endif 372 | 373 | " Case: #if & co {{{3 374 | " No need: What follows does the work 375 | 376 | " Loop for all the lines in the fold {{{3 377 | let line = '' 378 | let in_macro_ctx = 0 379 | 380 | let lastline = b:fold_data.end[a:lnum] 381 | while lnum <= lastline 382 | let current = getline(lnum) 383 | " Foldmarks will get ignored 384 | let current = substitute(current, '{\{3}\d\=.*$', '', 'g') 385 | " Get rid of C comments 386 | let current = substitute(current, '/\*.*\*/', '', 'g') 387 | 388 | if current =~ '^#\s*\(if\|elif\).*\\$' 389 | let in_macro_ctx = 1 390 | let current = substitute(current, '\s*\\$', '', '') 391 | let break = 0 392 | let lastline = line('$') 393 | elseif in_macro_ctx 394 | let in_macro_ctx = !empty(current) && current[-1] == '\\' 395 | let current = substitute(current, '\s*\\$', '', '') 396 | let break = ! in_macro_ctx 397 | 398 | elseif current =~ '[^:]:[^:]' && current !~ 'for\s*(' 399 | " class XXX : ancestor 400 | " Ignore C++11 for range loops 401 | let current = substitute(current, '\([^:]\):[^:].*$', '\1', 'g') 402 | let break = 1 403 | elseif current =~ '{\s*$' 404 | " ' } else {' 405 | let current = substitute(current, '^\(\s*\)}\s*', '\1', 'g') 406 | let current = substitute(current, '{\s*$', '', 'g') 407 | let break = 1 408 | else 409 | let break = 0 410 | endif 411 | if empty(line) 412 | " preserve indention: substitute leading tabs by spaces 413 | let leading_spaces = matchstr(current, '^\s*') 414 | let leading_spaces = substitute(leading_spaces, "\t", ts, 'g') 415 | endif 416 | 417 | " remove leading and trailing white spaces 418 | let current = matchstr(current, '^\s*\zs.\{-}\ze\s*$') 419 | " let current = substitute(current, '^\s*', '', 'g') 420 | " Manual join(), line by line 421 | if !empty(line) && current !~ '^\s*$' " add a separator 422 | let line .= ' ' 423 | endif 424 | let line .= current 425 | if break 426 | break 427 | endif 428 | " Goto next line 429 | let lnum = s:NextNonCommentNonBlank(lnum + 1, shall_fold_blank) 430 | endwhile 431 | let leading_spaces = get(l:, 'leading_spaces', '') 432 | 433 | " Strip whatever follows "case xxx:" and "default:" {{{3 434 | let line = substitute(line, 435 | \ '^\(\s*\%(case\s\+.\{-}[^:]:\_[^:]\|default\s*:\)\).*', '\1', 'g') 436 | 437 | " Strip spaces within parenthesis {{{3 438 | let line = substitute(line, '\s\{2,}', ' ', 'g') 439 | let line = substitute(line, '(\zs \| \ze)', '', 'g') 440 | 441 | " Strip namespaces 442 | if s:opt_strip_namespaces() 443 | let line = substitute(line, '\<\k\+::', '', 'g') 444 | endif 445 | 446 | " Add Indentation {{{3 447 | let line = leading_spaces . line 448 | 449 | " Strip template parameters {{{3 450 | if s:IsLineTooLong(line) 451 | \ && s:opt_strip_template_arguments() && line =~ '\s*template\s*<' 452 | let c0 = stridx(line, '<') + 1 | let lvl = 1 453 | let c = c0 454 | while c > 0 455 | let c = match(line, '[<>]', c+1) 456 | if line[c] == '<' 457 | let lvl += 1 458 | elseif line[c] == '>' 459 | if lvl == 1 | break | endif 460 | let lvl -= 1 461 | endif 462 | endwhile 463 | " TODO: doesn't work with template specialization 464 | let line = strpart(line, 0, c0) . '...' . strpart(line, c) 465 | endif 466 | 467 | " Replace tabs {{{3 468 | let line = substitute(line, "\t", ' ', 'g') 469 | 470 | " Trim line if too long {{{3 471 | if s:IsLineTooLong(line) 472 | " TODO: factorise option fetching 473 | let max_length = s:opt_max_foldline_length() - &foldcolumn 474 | call s:Verbose('Trimming #%1: %2', a:lnum, line) 475 | let line = s:TrimLongLine(line, max_length) 476 | endif 477 | 478 | " Return the result {{{3 479 | return line 480 | " let lines = v:folddashes . '[' . (v:foldend - v:foldstart + 1) . ']' 481 | " let lines .= repeat(' ', 10 - strlen(lines)) 482 | " return lines . line 483 | endfunction 484 | 485 | function! lh#c#fold#text() abort 486 | " return getline(v:foldstart) " When there is a bug, use this one 487 | return lh#c#fold#text_(v:foldstart) 488 | endfunction 489 | 490 | " Function: lh#c#fold#clear(cmd) {{{2 491 | " b:fold_data.begin: Block of non empty lines before the instruction 492 | " New line => new block 493 | " TODO: merge several comment blocks ? 494 | " b:fold_data.end: Will contain all empty lines that follow 495 | " TODO: special case: do {...} while (); 496 | " 497 | " + special case: #includes 498 | function! lh#c#fold#clear(cmd) abort 499 | call lh#c#fold#verbose(s:verbose) " clear signs 500 | echomsg "Clearing signs for ".expand('%') 501 | let b:fold_data.begin = repeat([0], 2+line('$')) 502 | let b:fold_data.end = copy(b:fold_data.begin) 503 | let b:fold_data.instr_begin = copy(b:fold_data.begin) 504 | let b:fold_data.instr_end = copy(b:fold_data.begin) 505 | let b:fold_data.levels = copy(b:fold_data.begin) 506 | let b:fold_data.context = repeat([''], 2+line('$')) 507 | let b:fold_data.last_updated = 0 508 | exe 'normal! '.a:cmd 509 | endfunction 510 | 511 | "------------------------------------------------------------------------ 512 | " ## Internal functions {{{1 513 | " Function: s:ResizeCache() {{{2 514 | function! s:ResizeCache() abort 515 | let missing = line('$') - len(b:fold_data.levels) + 2 516 | if missing > 0 517 | let to_be_appended = repeat([0], missing) 518 | let b:fold_data.levels += to_be_appended 519 | let b:fold_data.begin += to_be_appended 520 | let b:fold_data.end += to_be_appended 521 | let b:fold_data.instr_begin += to_be_appended 522 | let b:fold_data.instr_end += to_be_appended 523 | let b:fold_data.context += repeat([''], missing) 524 | endif 525 | " @post len(*) == line('$') + 1 526 | endfunction 527 | 528 | " Function: s:CleanLine(line) {{{2 529 | " clean from comments 530 | " TODO: merge with similar functions in lh-cpp and lh-dev 531 | function! s:CleanLine(line) abort 532 | " 1- remove strings 533 | let line = substitute(a:line, '".\{-}[^\\]"', '', 'g') 534 | " 2- remove C Comments 535 | let line = substitute(line, '/\*.\{-}\*/', '', 'g') 536 | " 3- remove C++ Comments 537 | let line = substitute(line, '//.*', '', 'g') 538 | return line 539 | endfunction 540 | 541 | function! s:CleanLineCtx(line, ctx) abort 542 | let line = a:line 543 | if a:ctx.is_in_a_continuing_comment 544 | let p = matchend(line, '\*/') 545 | if p >= 0 546 | let line = line[p:] 547 | let a:ctx.is_in_a_continuing_comment = 0 548 | else 549 | return '' 550 | endif 551 | endif 552 | " 1- remove strings 553 | let line = substitute(line, '".\{-}[^\\]"', '', 'g') 554 | " 2- remove C Comments 555 | let line = substitute(line, '/\*.\{-}\*/', '', 'g') 556 | " 3- remove C++ Comments 557 | let line = substitute(line, '//.*', '', 'g') 558 | " 4- multilines C comments 559 | let p = match(line, '/\*') 560 | if p >= 0 561 | let line = p > 0 ? line[: (p-1)] : '' 562 | let a:ctx.is_in_a_continuing_comment = 1 563 | endif 564 | return line 565 | endfunction 566 | 567 | " Function: s:WhereInstructionEnds() {{{2 568 | " Given a line number, search for something that indicates the end of a 569 | " instruction => ; , {, } 570 | " TODO: 571 | " - Handle special case: "do { ... }\nwhile()\n;" 572 | let s:g_syn_filter_comments = lh#syntax#line_filter('\v\ccomment|doxygen') 573 | 574 | function! s:WhereInstructionEnds(lnum, opt_merge_comments, opt_fold_blank) abort 575 | let last_line = line('$') 576 | let lnum = a:lnum 577 | " let last = lnum 578 | if ! a:opt_merge_comments 579 | " whithin a comment line => search end of the comment line 580 | " else => as usual 581 | let line = s:g_syn_filter_comments.getline_not_matching(lnum) 582 | if line =~ '^\s*$' 583 | " let lnum += 1 584 | " => only whitespaces & comments in the line 585 | " => let's merge with comments 586 | while lnum <= last_line 587 | let line = s:g_syn_filter_comments.getline_not_matching(lnum+1) 588 | if line !~ '^\s*$' 589 | break 590 | endif 591 | let lnum += 1 592 | endwhile 593 | let as_usual = 0 594 | else 595 | " in the other case, let search as usual 596 | let as_usual = 1 597 | endif 598 | else 599 | let as_usual = 1 600 | endif 601 | 602 | while (lnum <= last_line) && as_usual 603 | "" Where the instruction started 604 | " let b:fold_data.begin[lnum] = a:lnum 605 | let line = getline(lnum) 606 | if line =~ '^\s*$' 607 | " break 608 | else 609 | let line = s:getline(lnum) " remove comments & strings 610 | if line =~ '[{}]\|^\s*#\|^\s*\(public\|private\|protected\):\|;\s*$' 611 | " let last = lnum 612 | " Search next non empty line -- why don't I use nextnonblank(lnum)? 613 | if a:opt_fold_blank 614 | while lnum < last_line && getline(lnum+1) =~ '^\s*$' 615 | let lnum += 1 616 | endwhile 617 | endif 618 | " TODO: if there is no error => use this new value 619 | " call lh#assert#value(lnum).equal(max(nextnonblank(last+1)-1, last)) 620 | break 621 | endif 622 | endif 623 | let lnum += 1 624 | endwhile 625 | 626 | " let lnum = min([lnum, last_line]) 627 | " call lh#assert#value(lnum).is_le(last_line+1) 628 | " call lh#assert#value(lnum).is_lt(len(b:fold_data.instr_begin)) 629 | " call lh#assert#value(a:lnum).is_le(lnum) 630 | let b:fold_data.instr_begin[(a:lnum):lnum] = map(b:fold_data.instr_begin[(a:lnum):lnum], 'min([v:val==0 ? (a:lnum) : v:val, a:lnum])') 631 | " let b:fold_data.instr_end[(a:lnum):last] = repeat([last], last-a:lnum+1) 632 | let nb = lnum-a:lnum+1 633 | let b:fold_data.begin[(a:lnum):lnum] = repeat([a:lnum], nb) 634 | let b:fold_data.end[(a:lnum):lnum] = repeat([lnum], nb) 635 | 636 | return lnum 637 | endfunction 638 | 639 | " Function: s:IsACommentLine(lnum) {{{2 640 | function! s:IsACommentLine(lnum, or_blank) abort 641 | let line = getline(a:lnum) 642 | if line =~ '^\s*//'. (a:or_blank ? '\|^\s*$' : '') 643 | " C++ comment line / empty line => continue 644 | return 1 645 | elseif line =~ '\S.*\(//\|/\*.\+\*/\)' 646 | " Not a comment line => break 647 | return 0 648 | else 649 | let id = synIDattr(synID(a:lnum, strlen(line)-1, 0), 'name') 650 | return id =~? 'comment\|doxygen' 651 | endif 652 | endfunction 653 | 654 | " Function: s:NextNonCommentNonBlank(lnum) {{{2 655 | " Comments => ignore them: 656 | " the fold level is determined by the code that follows 657 | function! s:NextNonCommentNonBlank(lnum, or_blank) abort 658 | let lnum = a:lnum 659 | let lastline = line('$') 660 | while (lnum <= lastline) && s:IsACommentLine(lnum, a:or_blank) 661 | let lnum += 1 662 | endwhile 663 | return lnum 664 | endfunction 665 | 666 | " Function: s:Build_ts() {{{2 667 | function! s:Build_ts() abort 668 | if !exists('s:ts_d') || (s:ts_d != &ts) 669 | let s:ts = repeat(' ', &ts) 670 | let s:ts_d = &ts 671 | endif 672 | return s:ts 673 | endfunction 674 | 675 | " Function: s:ShowInstrBegin() {{{2 676 | function! s:ShowInstrBegin() abort 677 | silent sign define Fold text=~~ texthl=Identifier 678 | 679 | let bufnr = bufnr('%') 680 | silent! exe 'sign unplace * buffer='.bufnr 681 | let boi = lh#list#unique_sort(values(b:fold_data.begin)) 682 | for l in boi 683 | silent exe 'sign place '.l.' line='.l.' name=Fold buffer='.bufnr 684 | endfor 685 | endfunction 686 | 687 | " Function: s:IncrFoldLevel(lnum) {{{2 688 | " @pre lnum > 0 689 | " @pre len(b:fold_data.levels) == line('$')+1 690 | function! s:IncrFoldLevel(lnum, nb) abort 691 | let b:fold_data.levels[a:lnum] = b:fold_data.levels[a:lnum-1] + a:nb 692 | if s:verbose 693 | silent exe 'sign place '.a:lnum.' line='.a:lnum.' name=Fold'.b:fold_data.levels[a:lnum].'gt buffer='.bufnr('%') 694 | endif 695 | return '>'.b:fold_data.levels[a:lnum] 696 | endfunction 697 | 698 | " Function: s:DecrFoldLevel(lnum) {{{2 699 | " @pre lnum > 0 700 | " @pre len(b:fold_data.levels) == line('$')+1 701 | function! s:DecrFoldLevel(lnum, nb) abort 702 | let b:fold_data.levels[a:lnum] = max([b:fold_data.levels[a:lnum-1]- a:nb, 0]) 703 | if s:verbose 704 | silent exe 'sign place '.a:lnum.' line='.a:lnum.' name=Fold'.(b:fold_data.levels[a:lnum]+1).'lt buffer='.bufnr('%') 705 | endif 706 | return '<'.(b:fold_data.levels[a:lnum]+1) 707 | endfunction 708 | 709 | " Function: s:KeepFoldLevel(lnum) {{{2 710 | " @pre lnum > 0 711 | " @pre len(b:fold_data.levels) == line('$')+1 712 | function! s:KeepFoldLevel(lnum) abort 713 | let b:fold_data.levels[a:lnum] = b:fold_data.levels[a:lnum-1] 714 | if s:verbose 715 | silent exe 'sign place '.a:lnum.' line='.a:lnum.' name=Fold'.b:fold_data.levels[a:lnum].' buffer='.bufnr('%') 716 | endif 717 | return b:fold_data.levels[a:lnum] 718 | endfunction 719 | 720 | 721 | " Function: s:IsLineTooLong(text) {{{2 722 | function! s:IsLineTooLong(text) abort 723 | return lh#encoding#strlen(a:text) > s:opt_max_foldline_length() 724 | endfunction 725 | 726 | " Function: s:TrimLongLine(line, max_length) {{{2 727 | " @pre lh#encoding#strlen(a:line) > max_length -- unchecked 728 | " TODO: implement a better heuristics that could recognize: 729 | " -[X] initialization lists 730 | " -[X] function declarations 731 | " -[ ] function calls 732 | let s:k_annotations = { 733 | \ 'const' : ' const', 734 | \ 'volatile' : ' volatile', 735 | \ 'overriden': ' override', 736 | \ 'final' : ' final' 737 | \ } 738 | let s:k_proto_pattern = '(.*)\(\s*\(\\|\\|\\|\\|\\|=\s*0\)\)*' 739 | let s:k_has_lhcpp = !empty(globpath(&rtp, 'autoload/lh/cpp/AnalysisLib_Function.vim')) 740 | function! s:TrimLongLine(line, max_length) abort 741 | call s:Verbose('TrimLongLine(%1, %2)', a:line, a:max_length) 742 | let line = a:line 743 | " 1- detect initialization-list (top priority) 744 | let p = match(line, ')\s*:[^:]') 745 | if p > 0 746 | let line = line[:p] 747 | let len = lh#encoding#strlen(line) 748 | call s:Verbose('Initialisation list found; p=%1, strlen(:p)=%2', p, len) 749 | if len > a:max_length - 7 750 | let line = s:TrimLongLine(line, a:max_length-7) 751 | endif 752 | let line .= ' : ....' 753 | return line 754 | endif 755 | 756 | " 2- detect functions signatures (and calls...) 757 | " -- if and only if lh-cpp is detected 758 | if s:k_has_lhcpp && line =~ s:k_proto_pattern 759 | call s:Verbose('Function found') 760 | let indent = matchstr(line, '^\s*') 761 | let proto = lh#cpp#AnalysisLib_Function#AnalysePrototype(a:line) 762 | let elements = [] 763 | if !empty(get(proto, 'return', '')) 764 | let elements += [proto.return] 765 | endif 766 | let elements += [join(proto.name, '::')] 767 | let line = indent . join(elements, ' ') 768 | let line .= '(' . join(map(proto.parameters, 'v:val.type'), ', ') . ')' 769 | let annotations = ['const', 'volatile', 'overriden', 'final'] 770 | call map(annotations, 'get(s:k_annotations, get(proto, v:val, 0) ? v:val : "", "")') 771 | let line = join([line]+annotations, '') 772 | let len = lh#encoding#strlen(line) 773 | if len > a:max_length 774 | let p = stridx(line, ')') 775 | let line = s:TrimLongLine(line[:p-1], a:max_length-(len-p)) . line[p:] 776 | endif 777 | return line 778 | endif 779 | 780 | " n- Default case: trim! 781 | call s:Verbose('Default case') 782 | let line = substitute(line, '\v^(.){'.(a:max_length-4).'}\zs.*', '....', '') 783 | return line 784 | endfunction 785 | 786 | " Function: s:getline(first [, last]) abort {{{2 787 | " This function caches calls to s:CleanLine() as long as the file hasn't been 788 | " modified. 789 | " TODO: support multiline comments 790 | if exists('*undotree') 791 | function! s:getline(...) abort 792 | let ut = undotree() 793 | let time = get(ut.entries, -1, {'time': localtime()}).time 794 | if b:fold_data.last_updated < time 795 | "" s:CleanLine() doesn't handle multi line comments well 796 | " let b:fold_data.lines = [''] + getline(1, '$') 797 | " " TODO: -> s:getSNR 798 | " call map(b:fold_data.lines, 's:CleanLine(v:val)') 799 | "" getline_not_matching is too slow... 800 | " let b:fold_data.lines = [''] + map(range(1, line('$')), 's:g_syn_filter_comments.getline_not_matching(v:val)') 801 | let b:fold_data.lines = [''] + getline(1, '$') 802 | let ctx = {'is_in_a_continuing_comment': 0} 803 | " TODO: -> s:getSNR 804 | call map(b:fold_data.lines, 's:CleanLineCtx(v:val, ctx)') 805 | let b:fold_data.last_updated = time 806 | endif 807 | return a:0 == 1 808 | \ ? b:fold_data.lines[(a:1)] 809 | \ : b:fold_data.lines[(a:1) : (a:2)] 810 | endfunction 811 | else 812 | function! s:getline(...) abort 813 | let res = call('getline', a:000) 814 | if a:0 == 1 815 | return s:CleanLine(res) 816 | else 817 | call map(res, 's:CleanLine(v:val)') 818 | return res 819 | endif 820 | endfunction 821 | endif 822 | 823 | "------------------------------------------------------------------------ 824 | " }}}1 825 | let &cpo=s:cpo_save 826 | "============================================================================= 827 | " vim600: set fdm=marker:sw=2: 828 | -------------------------------------------------------------------------------- /doc/screencast-vim-fold.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LucHermitte/VimFold4C/0a774d3e33100544c8916324ccb062285946e35c/doc/screencast-vim-fold.gif -------------------------------------------------------------------------------- /ftplugin/c/c-fold.vim: -------------------------------------------------------------------------------- 1 | "============================================================================= 2 | " File: ftplugin/c-fold.vim {{{1 3 | " Author: Luc Hermitte 4 | " 5 | " License: GPLv3 with exceptions 6 | " 7 | " Version: 3.2.0 8 | let s:k_version = 320 9 | " Created: 06th Jan 2002 10 | "------------------------------------------------------------------------ 11 | " Description: 12 | " Folding for C and C++ 13 | " 14 | " Unlike folding on syntax or on indent, this script tries to correctly detect 15 | " the fold boundaries, and to display comprehensible foldtext. 16 | " 17 | " See README.md for more information 18 | " 19 | "------------------------------------------------------------------------ 20 | " History: 21 | " - A long time ago (~2001), Johannes Zellner published a first folding plugin 22 | " for C & C++. 23 | " - Then, I did some changes (2002-2004), but the result was very slow at the 24 | " time. (the last version is still archived in 25 | " ) 26 | " - Eventually I got tired of the slow execution times and moved back to 27 | " foldmethod=indent. 28 | " 29 | " - Here is a new (2014) version almost entirelly rewritten, that I hope will 30 | " be fast enough to be usable. 31 | "------------------------------------------------------------------------ 32 | " }}}1 33 | "============================================================================= 34 | 35 | let s:cpo_save=&cpo 36 | set cpo&vim 37 | "------------------------------------------------------------------------ 38 | 39 | " Avoid global reinclusion {{{1 40 | if &cp || (exists("g:loaded_c_fold") 41 | \ && (g:loaded_c_fold >= s:k_version) 42 | \ && !exists('g:force_reload_c_fold')) 43 | " finish 44 | endif 45 | let g:loaded_c_fold = s:k_version 46 | let s:cpo_save=&cpo 47 | set cpo&vim 48 | " Avoid global reinclusion }}}1 49 | "------------------------------------------------------------------------ 50 | " Functions {{{1 51 | function! s:opt_foldmethod() abort 52 | let threshold = lh#option#get('fold_options.fallback_method.line_threshold', 0) 53 | if 0 < threshold && threshold < line('$') 54 | return lh#option#get('fold_options.fallback_method.method', 'syntax') 55 | endif 56 | return 'expr' 57 | endfunction 58 | 59 | "------------------------------------------------------------------------ 60 | " Settings {{{1 61 | 62 | " Settings {{{2 63 | 64 | " Function: s:init() {{{3 65 | function! s:init() abort 66 | let method = s:opt_foldmethod() 67 | if method == 'expr' 68 | setlocal foldexpr=lh#c#fold#expr(v:lnum) 69 | setlocal foldmethod=expr 70 | setlocal foldtext=lh#c#fold#text() 71 | return 1 72 | else 73 | exe 'setlocal foldmethod='.method 74 | return 0 75 | endif 76 | endfunction 77 | 78 | call s:init() 79 | 80 | " Function: s:clear(how) {{{3 81 | function! s:clear(how) abort 82 | return s:init() && lh#c#fold#clear(a:how) 83 | endfunction 84 | 85 | " Script Debugging {{{2 86 | command! -b -nargs=0 ShowInstrBegin call lh#c#fold#debug(s:ShowInstrBegin()) 87 | 88 | " Script Data {{{2 89 | let b:fold_data = {} 90 | let b:fold_data.begin = repeat([0], 1+line('$')) 91 | let b:fold_data.instr_begin = deepcopy(b:fold_data.begin) 92 | let b:fold_data.instr_end = deepcopy(b:fold_data.begin) 93 | let b:fold_data.end = deepcopy(b:fold_data.begin) 94 | let b:fold_data.levels = deepcopy(b:fold_data.begin) 95 | let b:fold_data.context = repeat([''], 1+line('$')) 96 | let b:fold_data.last_updated = 0 97 | 98 | " Mappings {{{1 99 | nnoremap zx :call clear('zx') 100 | nnoremap zX :call clear('zX') 101 | 102 | " To help debug 103 | " nnoremap µ :echo lh#c#fold#expr(line('.')).' -- foldlevels:'.string(b:fold_data.levels[(line('.')-1):line('.')]).' -- @'.line('.').' -- [beg,end;instr_beg,instr_end]:['.b:fold_data.begin[line('.')].','.b:fold_data.end[line('.')].','.b:fold_data.instr_begin[line('.')].','.b:fold_data.instr_end[line('.')].'] --> ctx:'.b:fold_data.context[line('.')] 104 | 105 | 106 | "------------------------------------------------------------------------ 107 | " }}}1 108 | let &cpo=s:cpo_save 109 | "============================================================================= 110 | " vim600: set fdm=marker: 111 | -------------------------------------------------------------------------------- /macros/trivial.fold.vim: -------------------------------------------------------------------------------- 1 | "============================================================================= 2 | " $Id$ 3 | " File: macros/trivial.fold.vim {{{1 4 | " Author: Luc Hermitte 5 | " 6 | " Version: 001 7 | " Created: 19th Sep 2014 8 | " Last Update: $Date$ 9 | "------------------------------------------------------------------------ 10 | " Description: 11 | " Script to test foldexpressions 12 | " 13 | " Just source it on a file with foldexpressions to see how vim handles 14 | " them. 15 | " 16 | "------------------------------------------------------------------------ 17 | " Installation: 18 | " Drop this file into {rtp}/macros 19 | " Requires Vim7+ 20 | " }}}1 21 | "============================================================================= 22 | 23 | let s:cpo_save=&cpo 24 | set cpo&vim 25 | "------------------------------------------------------------------------ 26 | 27 | setlocal foldexpr=TrivialFold(v:lnum) 28 | setlocal foldmethod=expr 29 | 30 | function! TrivialFold(lnum) 31 | return getline(a:lnum) 32 | endfunction 33 | 34 | let &cpo=s:cpo_save 35 | "============================================================================= 36 | " vim600: set fdm=marker: 37 | -------------------------------------------------------------------------------- /mkVba/mk-vimfold4c.vim: -------------------------------------------------------------------------------- 1 | "============================================================================= 2 | " File: mkVba/mk-vimfold4c.vim {{{1 3 | " Author: Luc Hermitte 4 | " 5 | let s:version = '320' 6 | " Version: 3.2.0 7 | " Created: 18th Sep 2014 8 | " Last Update: 06th Nov 2018 9 | "------------------------------------------------------------------------ 10 | " Description: 11 | " Helper script to build vimfold4c tarball archive 12 | " }}}1 13 | "============================================================================= 14 | 15 | let s:project = 'vimfold4c' 16 | cd :p:h 17 | try 18 | let save_rtp = &rtp 19 | let &rtp = expand(':p:h:h').','.&rtp 20 | exe '27,$MkVimball! '.s:project.'-'.s:version 21 | set modifiable 22 | set buftype= 23 | finally 24 | let &rtp = save_rtp 25 | endtry 26 | finish 27 | README.md 28 | addon-info.json 29 | autoload/lh/c/fold.vim 30 | ftplugin/c/c-fold.vim 31 | VimFlavor 32 | -------------------------------------------------------------------------------- /tests/lh/test-fold-issue3-2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | 6 | int main() { 7 | 8 | 9 | 10 | std::cout << 11 | "Hello" << std::endl; 12 | } 13 | 14 | 15 | int max() { 16 | 17 | int c = a + b; 18 | } 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/lh/test-fold-issue3.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | 5 | /** @brief test... 6 | * bla... 7 | */ 8 | int main (int argc, char* argv[]) 9 | { 10 | 11 | #ifndef NDEBUG 12 | std::clog << "NOTE: '" << argv[0] << "' COMPILED ON " << __TIME__ << " " << 13 | __DATE__ << " AS DEBUG VERSION." << std::endl; 14 | #endif 15 | 16 | std::cout << "Hello World" << std::endl; 17 | } 18 | -------------------------------------------------------------------------------- /tests/lh/test-fold-issue5.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() 5 | { 6 | std::vector v = {0, 1, 2, 3, 4, 5}; 7 | for (const int& i : v) 8 | { 9 | std::cout << i << ' '; 10 | } 11 | 12 | for (auto c : google::GetLoggingDirectories()) { 13 | std::clog << c << "\n"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/lh/test-fold.cpp: -------------------------------------------------------------------------------- 1 | #ifndef toto 2 | #define toto 3 | #endif 4 | 5 | 6 | // The includes... 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | // One line class 13 | struct Foo { }; 14 | 15 | struct Bar { 16 | // Multi-lines fn definition, with no body 17 | Bar() 18 | : m_foo(42) 19 | , m_bar("bar") 20 | {} 21 | // one-liner fn definition with no body 22 | ~Bar() {} 23 | 24 | // one-liner fn definition with a body 25 | int getFoo() const { return m_foo;} 26 | // multi-lines fn definition with a body 27 | void setFoo(int foo) { 28 | assert(foo); 29 | m_foo = foo; 30 | } 31 | 32 | private: 33 | int m_foo; 34 | std::string m_bar; 35 | }; 36 | 37 | class MyClass 38 | { 39 | Type var1; 40 | Type var2; 41 | Type var3; 42 | Type var4; 43 | Type var5; 44 | Type var6; 45 | 46 | std::vector some_function2(Type2 argument1, 47 | Type && argument2, 48 | Type const& argument3, 49 | Type argument4, 50 | Type argument5, 51 | Type argument6) const override; 52 | std::vector some_function(Type2 argument1, 53 | Type && argument2, 54 | Type const& argument3, 55 | Type argument4, 56 | Type argument5, 57 | Type argument6) const override { 58 | std::cout << "yep"; 59 | } 60 | MyClass(Type argument1, 61 | Type && argument2, 62 | Type const& argument3, 63 | Type argument4, 64 | Type argument5, 65 | Type argument6) : 66 | var1(argument1), 67 | var2(argument2), 68 | var3(argument3), 69 | var4(argument4), 70 | var5(argument5), 71 | var6(argument6) 72 | {}; 73 | }; 74 | 75 | void control_statements() { 76 | // ifs ... 77 | if (42 == 41) { 78 | std::cout << "WTF?\n"; 79 | } 80 | 81 | if (42 == 41) 82 | std::cout << "WTF?\n"; 83 | 84 | if (42 == 41) 85 | { 86 | std::cout << "WTF?\n"; 87 | } 88 | 89 | if (42 == 41) { 90 | std::cout << "WTF?\n"; 91 | } else { 92 | std::cout << "OK\n"; 93 | } 94 | 95 | if (42 == 41) 96 | std::cout << "WTF?\n"; 97 | else 98 | std::cout << "OK\n"; 99 | 100 | if (42 == 41) 101 | { 102 | std::cout << "WTF?\n"; 103 | } 104 | else 105 | { 106 | std::cout << "OK\n"; 107 | } 108 | 109 | // Whiles 110 | while (foo) { 111 | act; 112 | } 113 | 114 | while (foo) 115 | { 116 | act; 117 | } 118 | 119 | while 120 | (foo) { 121 | act; 122 | } 123 | 124 | while 125 | (foo) 126 | { 127 | act; 128 | } 129 | 130 | // do Whiles 131 | do { 132 | act; 133 | } while (foo) ; 134 | 135 | do 136 | { 137 | act; 138 | } while (foo) ; 139 | 140 | do 141 | { 142 | act; 143 | } 144 | while (foo) ; 145 | 146 | do { 147 | act; 148 | } while 149 | (foo) ; 150 | 151 | 152 | // fors ... 153 | for (std::size_t i=0, N=42; i!=N ; ++i) { 154 | act; 155 | } 156 | 157 | for (std::size_t i=0, N=42; i!=N ; ++i) 158 | { 159 | act; 160 | } 161 | 162 | for (std::size_t i=0, N=42 163 | ; i!=N 164 | ; ++i) 165 | { 166 | act; 167 | } 168 | 169 | for (std::size_t i=0, N=42 ; 170 | i!=N ; 171 | ++i) 172 | { 173 | act; 174 | } 175 | 176 | // switch 177 | switch(expr){ 178 | case c1: 179 | { 180 | code; 181 | break; 182 | } 183 | case c2: 184 | code; 185 | break; 186 | case c3: 187 | case c4: 188 | code; 189 | break; 190 | default: 191 | { 192 | default_code; 193 | break; 194 | } 195 | } 196 | // 197 | // try catch 198 | } 199 | 200 | void embedded_ctrl_stments() 201 | { 202 | if (t1) { 203 | if (t11) { 204 | a11(); 205 | } else if (t12) { 206 | a12(); 207 | } else { 208 | a1x(); 209 | } 210 | } else { 211 | a2(); 212 | } 213 | } 214 | 215 | int typical_main (int argc, char **argv) 216 | { 217 | try { 218 | bool verbose = false; 219 | bool noexec = false; 220 | std::string output_dir; 221 | std::vector files; 222 | 223 | for (int i=1; i!=argc ; ++i) { 224 | const std::string s = argv[i]; 225 | if (argv[i][0] == '-') { 226 | if (s == "-h" || s=="--help" ) { 227 | std::cout << usage(argv[0]) << "\n"; 228 | return EXIT_SUCCESS; 229 | } else if (s == "-n" || s == "--noexec") { 230 | noexec = true; 231 | } else if (s == "-v" || s == "--verbose") { 232 | verbose = true; 233 | } else if (s == "-o" || s == "--output") { 234 | if (i+1 == argc) 235 | throw std::runtime_error(usage(argv[0], "Not enough arguments to specify output directory")); 236 | output_dir = argv[++i]; // yes i is increment. But the bound is made the line before. 237 | } else { 238 | throw std::runtime_error(std::string(argv[0])+": Unexpected option: '"+s+"'"); 239 | } 240 | } else { 241 | files.push_back(s); 242 | } 243 | } 244 | if (files.empty()) 245 | throw std::runtime_error(usage(argv[0], "No file specified")); 246 | if (output_dir.empty() && !noexec) 247 | throw std::runtime_error(usage(argv[0], "Output directory not specified")); 248 | 249 | for (std::vector::const_iterator b = files.begin(), e = files.end() 250 | ; b != e 251 | ; ++b 252 | ) 253 | { 254 | std::cout << b << "\n"; 255 | } 256 | 257 | return EXIT_SUCCESS; 258 | } catch (std::exception const& e) { 259 | std::cerr << e.what() << "\n"; 260 | } 261 | return EXIT_FAILURE; 262 | } 263 | 264 | 265 | // From http://stackoverflow.com/questions/8316765/vim-foldexpr-for-including-multiline-function-signatures/ 266 | void ClassName::FunctionName(LongType1 LongArgument1, 267 | LongType2 LongArgument2, 268 | LongType3 LongArgument3) { 269 | ... 270 | } 271 | 272 | void if_def() { 273 | #if defined(foo) \ 274 | && define(fii) 275 | bla; 276 | # ifdef zzz 277 | zzz; 278 | # endif 279 | bli; 280 | #elif defined(bar) 281 | blie; 282 | # ifndef eee 283 | eee2; 284 | # endif 285 | bla; 286 | #endif 287 | 288 | 289 | #ifdef FOO 290 | if (foo) 291 | { 292 | ct(); 293 | } 294 | #elif defined(BAR) 295 | if (bar) 296 | { 297 | ct(); 298 | } 299 | #else 300 | if (toto) 301 | { 302 | ct(); 303 | } 304 | #endif 305 | 306 | 307 | 308 | 309 | #ifdef FOO 310 | if (foo) 311 | { 312 | ct(); 313 | } 314 | 315 | #elif defined(BAR) 316 | if (bar) 317 | { 318 | ct(); 319 | } 320 | 321 | #else 322 | if (toto) 323 | { 324 | ct(); 325 | } 326 | 327 | #endif 328 | } 329 | -------------------------------------------------------------------------------- /tests/lh/trivial-test.fold: -------------------------------------------------------------------------------- 1 | 0 2 | 0 3 | >1 4 | 1 5 | 1 6 | <1 7 | 0 8 | 0 9 | >1 10 | 1 11 | >2 12 | 2 13 | 2 14 | >3 15 | 3 16 | 3 17 | <2 18 | 1 19 | 1 20 | 21 | --------------------------------------------------------------------------------