SimpylFold
SimpylFold copied to clipboard
Porperly indented folds
Python focuses on indents for readability, thus I think the fold-text should respect the indent for readability. The indent based on the fold-level as we get here is in my opinion intrusive.
I use a custom fold text putting the fold information at the end of the line and indenting the line as it would be without a fold. I do it with the following fold text
function! CustomFoldText()
"get first non-blank line
let l:fs = v:foldstart
while getline(l:fs) =~ '^\s*$' | let l:fs = nextnonblank(l:fs + 1)
endwhile
if l:fs > v:foldend
let l:line = getline(v:foldstart)
else
let l:line = substitute(getline(l:fs), '\t', repeat(' ', &tabstop), 'g')
endif
"strip foldmarkers
let l:markexpr = escape(substitute(&foldmarker, ',', '|', 'g'),'{')
" TODO: check comment sign for filetype, add first line if comment
let l:whitespace = '(\w\s*)?["|#]\s*|\s*$'
let l:strip_line = substitute(l:line, '\v'.l:markexpr.'|'.l:whitespace, '', 'g')
let l:strip_line = substitute(l:strip_line, '\v'.l:whitespace, '', 'g')
let l:strip_line = substitute(l:strip_line, '\v^(\s*)', '\1<', '').'>'
let l:w = winwidth(0) - &foldcolumn - (&number ? 8 : 0)
let l:foldSize = 1 + v:foldend - v:foldstart
let l:foldSizeStr = ' ' . l:foldSize . ' lines '
let l:foldLevelStr = repeat('+--', v:foldlevel)
let l:lineCount = line('$')
let l:foldPercentage = printf('[%.1f', (l:foldSize*1.0)/l:lineCount*100) . '%] '
let l:expansionString = repeat('.', l:w - strwidth(l:foldSizeStr.strip_line.foldLevelStr.foldPercentage))
return l:strip_line . l:expansionString . l:foldSizeStr . l:foldPercentage . l:foldLevelStr
endfunction
set foldtext=CustomFoldText()
Sadly I have no idea how to get the same thing with your awesome docstring preview. (I am still amazed that I somehow manage to get the proper folds, but I have no idea what I have done).
Not quite sure what you're trying to accomplish. This is how we do it: https://github.com/tmhedberg/SimpylFold/blob/master/autoload/SimpylFold.vim#L364-L389
A picture tells more than words:

If you look at the left site, you see that the text in the folds is properly indented. Everything starts where it would start if it was not folded. On the right sight things start somewhere, depending on the number of folded lines. I find that really hard to read. Blocks of code should be recognizable by their indent in Python.
I would like to have your nice fold-text, showing the doc-strings, but properly indented like my patchwork fold-text. Sadly I don't know enough Vimscript to do it myself.
@DerWeh : I used your fold function but I'd have to say that I like SimpyIFold more. Maybe because of colorscheme or something else, have to look on the right to get number of lines cause distracted.
@tuyenpm9 I agree that it is a matter of preference (and my fold-text surely isn't optimal, neither is my colorscheme). I myself am hardly ever interested in the number of folded lines. Either I am interested in the folded code and look into it, or a fold it away to get a better overview.
If I look to the class StyleGuide for example, I can immediately tell on the left side, which methods belong to it and when the new function starts. On the right side however it is not clear at all (at least to me). Which methods belong to the class? Is ignore_code defined in the method exclude or is it directly defined in StyleGuide? Of course, you can tell from the semantics that the first variant makes no sense. But It could also be an error, and you have to read to figure it out instead of looking.
I think the option to choose a fold text putting the numbers to the right and thus keeping the indent should be given. Python is indent-based and that should be respected.
There a probably some issues with my function. It was mostly try and error until I found the result to be acceptable.
@tuyenpm9 I am not exactly sure what you mean with incorrect, it is what I intended. The number of +-- represents the fold level:
let l:foldLevelStr = repeat('+--', v:foldlevel)
But I realized the code doesn't respect line numbers and the sign column, here issues might arise.
Likely there are more readable possibilities to do it, I simply didn't care.
The issue with <>..... looks like this got stripped that shouldn't have been stripped.
let l:markexpr = escape(substitute(&foldmarker, ',', '|', 'g'),'{')
" TODO: check comment sign for filetype, add first line if comment
let l:whitespace = '(\w\s*)?["|#]\s*|\s*$'
let l:strip_line = substitute(l:line, '\v'.l:markexpr.'|'.l:whitespace, '', 'g')
let l:strip_line = substitute(l:strip_line, '\v'.l:whitespace, '', 'g')
let l:strip_line = substitute(l:strip_line, '\v^(\s*)', '\1<', '').'>'
Does the stripping.
let l:markexpr = escape(substitute(&foldmarker, ',', '|', 'g'),'{')removes fold marker. As we don't use them it likely can be dropped together withlet l:strip_line = substitute(l:line, '\v'.l:markexpr.'|'.l:whitespace, '', 'g')let l:strip_line = substitute(l:strip_line, '\v^(\s*)', '\1<', '').'>'adds the< >around the text, it can also be droppedlet l:whitespace = '(\w\s*)?["|#]\s*|\s*$'likely is the culprit why you don't see anything. If you have an `empty' line just containing a comment marker#or the beginning of a multi-line string"""it indeed doesn't show the right thing.
I'm considering use your fold method but without 12 lines [4.3%] +-- portion. If we can write a function that piece of information occurs when cursor on folded line at command bar is good :)
A big plus that your function strip """ in docstring.
I worked a bit on the fold text and came up with something rather satisfying:
scriptencoding utf-8
let s:docstring_re = '^\s*[bBfFrRuU]\{0,2}\\\@<!\(''''''\|"""\|[''"]\)'
let s:docstring_idedentifier_re = '[bBfFrRuU]\{0,2}\\\@<!\(''''''\|"""\|[''"]\)'
function! foldtext#python() abort
let l:docstring = SimpylFold#FoldText()
let l:lnum = v:foldstart
" get first non-blank line
while getline(l:lnum) =~# '^\s*$' | let l:lnum = nextnonblank(l:lnum + 1)
endwhile
if l:lnum > v:foldend
let l:line = getline(v:foldstart)
else
let l:line = substitute(getline(l:lnum), '\t', repeat(' ', &tabstop), 'g')
endif
" if line is docstring than strip, is clear from that folding that it is docstring
let l:is_docstring = !empty(matchlist(l:line, s:docstring_re))
if l:is_docstring
let l:line = substitute(l:line, s:docstring_idedentifier_re, '⟨', '')
" let l:docstring = substitute(l:docstring, '^\s', '', '')
let l:docstring = l:docstring[1:] " split leading whitespace
endif
let l:width = winwidth(0) - &foldcolumn - (&number ? 8 : 0) " available space
let l:foldSize = 1 + v:foldend - v:foldstart
let l:foldSizeStr = ' ' . l:foldSize . ' '
let l:foldLevelStr = repeat('+--', v:foldlevel)
let l:lineCount = line('$')
let l:foldPercentage = printf('[%.1f', (l:foldSize*1.0)/l:lineCount*100) . '%] '
" space remaining space to be filled
let l:space = l:width - strwidth(l:foldSizeStr.l:line.l:foldLevelStr.l:foldPercentage.l:docstring)
if l:space < 0
" trim part of docstring that doesn't fit
return l:line . l:docstring[:l:space-1] . '⟩' . l:foldSizeStr . l:foldPercentage . l:foldLevelStr
else
" fill empty space
let l:expansionString = repeat('•', l:space)
return l:line . l:docstring . l:expansionString . l:foldSizeStr . l:foldPercentage . l:foldLevelStr
endif
endfunction
Again you can strip the parts you don't like, especially the Unicode chars. They are purely cosmetic.
Here is what it looks like:

The indent is preserved, to long doc-strings will be cut to fit, and doc-string only lines start with a <. However I don't really know if it is a good idea to keep the <.
@DerWeh : Look perfect to me :), will try.
EDIT: just tried, thanks a million :), extractly what I need for years. happy new year 2019 early. 💃
if we set SimpylFold options (let g:SimpylFold_fold_docstring = 1 for e.g) , SimpylFold is used instead of our custome fold (custom fold in vimrc).
I'm looking for only fold python docstring by default when open a python file.
Is there any way to only fold python docstring on python when open? but using this customfold.
EDIT: can achieve this by using modeline with foldenable and foldmethod=syntax
or may be a mapping will be better solution.
I'm also curious about the ability of setting guifg and guibg color of spaces for e.g let l:expansionString = repeat(' ', l:space) so they have the same color as colorscheme's background - Here I want to only highlight words and all spaces has same color as colorscheme's background, so we can feel less distracted.
I can't find L/N special char in your custom fold, how to have them?
@tuyenpm9 The L/N charter is a Unicode character: . I think FireFox's default font can't display it. But it works if you just copy the cryptic symbol into Vim, as long as you have a font able to display it. (I personally use nerdfonts, so I have all the symbols).
I use setlocal foldlevel=1 for python with SimpyFold, this does a decent job, as top-level functions are not folded.
I agree, setting the colors would increase readability. However, I think you have to modify how your colorsheme highlights folds to achieve this. I have no idea how to do this, but I am interested if you find a solution.
The only thing I can come up with would be setting :highlight Folded guibg=NONE ctermbg=NONE and then defining a new syntax element with a regular expression, maybe looking for your special character, which should only appear in folds.
@DerWeh : I do trick in python.vim and use foldmethod=syntax for python file.
It looks weird to me when set guibg to colorscheme's background color, I raised a question about this here.