Cyfolds is a Vim plugin to calculate syntax-aware folds for Python files. While some Python-folding plugins aim to fold as much text as possible, Cyfolds retains some context around the folds. In particular, the full function/class parameter list is always left unfolded and the function/class docstrings can optionally be left fully or partially unfolded. This gives something like an API view of the code.
The plugin is written in Cython and compiles to optimized C code for fast performance. The full file is parsed to find the syntax, so no heuristics are needed and the folding is always computed correctly.
A screenshot of some example code with folding is shown here:
Folding can be customized to occur for various keywords, and the number of
docstring lines to show can be modified. By default all the text in docstrings
is left unfolded after definitions with the class
, def
, or async
def
keywords, and full module doctrings are shown.
Cyfolds turns off folding in insert mode and restores it on leaving insert mode. This is because by default, when in insert mode, Vim updates the folds on every character. That is slow and is not really needed.
Cyfolds requires a Vim that is compiled with Python 3 support and timer support. It has currently only been compiled and tested on Vim 8.0 and Neovim 0.2.2 on Ubuntu Linux and on Vim 7.4 on Mint Linux. It should work with any recent Linux and Vim distribution, as well as on Windows with a recent Vim.
If you use the builtin Vim 8 package manager just clone this GitHub repo into your
~/.vim/pack/plugins/start
directory. Similarly, if you use pathogen just clone this GitHub repo into the~/.vim/bundle
directory.The C code produced by Cython needs to be compiled before use. In order to compile the code you need to have a C compiler installed. On Ubuntu or Debian systems you can type:
sudo apt-get install build-essential python3-dev
On Windows the free MinGW compiler is one option. To install it, see https://cython.readthedocs.io/en/latest/src/tutorial/appendix.html. For Mac OS X systems the Cython install page suggests Apple's XCode compiler: https://developer.apple.com/.
After you have the compiler set up, the Python build requirements are Cython and setuptools. This command installs them:
pip install "cython<3.0" setuptools --user --upgrade
Note that the plugin does not work with Cython 3.0 for some reason (string handling was changed).
Now change directories and go to the cloned repo and into the
python3
subdirectory. Run the Python script namedcompile.py
that is located in that directory:python3 ./compile.py
In Linux just
./compile.py
should work. You can alternately runpython3 setup.py build_ext --inplace
directly from the command line. To modify the compile options, look in the
setup.py
file.
The plugin is now ready to use in Vim.
Turn on folding in Vim, and plugins in general if you haven't already:
set foldenable
filetype plugin on
Python indentations are assumed to occur at multiples of the value of
the shiftwidth
setting. Usually set shiftwidth=4
is used for Python code.
These commands can go into your .vimrc
to always be set. Python files
should then appear in Vim with Cyfolds folding, set to the default parameters.
See below for the available parameter settings and example .vimrc
settings
which provide a good starting point.
In addition to the usual Vim folding keys (see :help fold-commands
in Vim),
Cyfolds adds two new key bindings:
The
zuz
key sequence is used to force the folds to be updated. (This is the same as the FastFold mapping, but only applies in Python code.) When thefoldmethod
is set tomanual
folds always need to be explicitly updated either withzuz
or one of the Vim commands. When thefoldmethod
is set toexpr
folds are updated after inserts but can still get messed up and require updating (for example, when deleting characters withx
or lines withdd
, since those change events do not trigger Vim to update the folds).The
zuz
command updates all the folds, returning the folding method to whatever method it was set to before the command. The states of the folds, open or closed, are unchanged except for folds created or removed by the updating itself. (This is unlike the built-inzx
andzX
commands, which always reset the open/closed states of folds according tofoldlevel
and which do not work with manual foldmethod.)The
zuz
command setsfoldenable
locally for the window if it is not already set. The key sequence is mapped to the function callCyfoldsForceFoldUpdate()
.The
z,
key sequence toggles thefoldmethod
setting betweenexpr
andmanual
. By default Cyfolds starts with the foldmethod set to manual. With the expr foldmethod folds are automatically updated upon leaving insert mode. With the manual foldmethod there is no automatic fold updating; all updating must be done explicitly, e.g. withzuz
. Folds are automatically updated upon toggling to theexpr
method, but not on toggling to themanual
method. The existing folds and their states are left unchanged except for changes due to the update operation itself.The manual foldmethod is best for doing heavy, fast editing with a lot of switching in and out of insert mode. With the expr method there can be a small but noticeable delay in quickly moving in and out of insert mode, depending on the editing speed and the computer's speed.
The
z,
command setsfoldenable
locally for the window if it is not already set. The key sequence is mapped to the function callCyfoldsToggleManualFolds()
.
You can define which particular keywords have folds after them by setting this configuration variable:
let cyfolds_fold_keywords = 'class,def,async def'
The default values are shown above. For Cython folding, for example, you can set it to:
let cyfolds_fold_keywords = 'class,def,async def,cclass,cdef,cpdef'
Any keyword which starts a line and where the statement ends in a colon can be used. The list of all such keywords in Python is:
'class,def,async def,while,for,if,else,elif,with,try,except,finally'
If a docstring appears immediately after any such definition it will remain unfolded just under the opening statement.
This list can be reset dynamically (to the new values set in the global
variable) by running :call CyfoldsUpdateFoldKeywords()
.
The number of lines to keep unfolded in module docstrings (and other freestanding docstrings) can be set by a command such as:
let cyfolds_lines_of_module_docstrings = -1
The default value -1 always keeps the full module docstring unfolded. Nonnegative numbers keep that many lines open, not including the last line which is never folded.
The number of lines to keep unfolded in docstrings under keywords such as
def
and class
can similarly be set by a command such as:
let cyfolds_lines_of_fun_and_class_docstrings = -1
The default value of -1 keeps the full docstring unfolded while the function or class code just below it is folded.
This setting will change the default of Cyfolds starting with
foldmethod=manual
to starting withfoldmethod=expr
:let cyfolds_start_in_manual_method = 0
To disable automatic fold calculations (and initial folding) on opening a Python buffer you can use:
let cyfolds_no_initial_fold_calc = 1
This setting is useful if you only sometimes use folds and do not want the fold calculations to happen automatically (a very small slowdown on startup). This setting also causes Cyfolds to start with
foldmethod
set tomanual
. To then switch to using folding you need to explicitly force the folds to be updated, such as withzuz
orz,
.To also fix syntax highlighting on all fold updates, from the start of the file, use this setting (the default is 0, no syntax fixing):
let cyfolds_fix_syntax_highlighting_on_update = 1
To increase the foldlevel of all toplevel (module-scope, with indent 0) elements except for classes, use:
let cyfolds_increase_toplevel_non_class_foldlevels = 1
This is nice because when the
foldlevel
value is 0 all the module-level elements are folded, but when it is 1 all the elements except classes are folded. This puts module-level functions and class methods at the same level of folding, which gives a nice API view. This works well, for example, withset foldlevelstart=1
in the.vimrc
. The builtinzm
andzr
commands can be used to go back and forth between the views.The only minor downside is that when
foldlevel
is 0 it takes two applications of the builtinzo
orza
commands to open folded, toplevel, non-class elements. TheSuperFoldToggle
function, described below, does not have this problem.To define the fold-updating function to update all the windows for the current buffer instead of just updating the current window, use:
let cyfolds_update_all_windows_for_buffer = 1
The default is 0, to only update the folds in the current window. That is essentially what the built-in
zx
andzX
commands do. Updating all the windows for the current buffer is convenient when you have multiple windows for a buffer. It is only slightly slower than only updating the current buffer (the folds for each such window need to be set, but they only need to be calculated once).To completely disable loading of the Cyfolds plugin use this in your
.vimrc
:let cyfolds = 0
In Vim folding the foldlevel
setting determines which folds are open by
default and which are closed. Any folds with a level less than foldlevel
are open by default. So when foldlevel
equals 0 all folds are closed by
default, and when it equals 99 all folds are open by default. The
foldlevel
value is increased by the Vim commands zr
and zR
( reduce folding), and decreased by the commands zm
and zM
(more
folding). The foldlevelstart
setting is used to set the initial foldlevel
when files are opened.
Cyfolds sets the foldlevels of folded lines to the indent level divided by the
shiftwidth (except for freestanding docstrings, where folds have one extra
level added to that value). So the lines at the first level of indent always
have foldlevel 0, foldable lines on the second level of indent have foldlevel
1, etc. Setting foldlevel
to 1, for example, will keep all folds for class
and function definitions at the first indent level (0) open and close all the
folds at higher indent levels (such as the methods of a class at 0-level).
Setting foldlevel
to 2 will keep foldable lines at the first and second
level of indent unfolded, and so forth. The same holds true for indents due to
keywords which are not set to be folded (like, say, with
). For consistency
the folds inside them are nevertheless at the higher foldlevel.
These are the .vimrc
settings I'm currently using:
" Cyfolds settings.
let cyfolds = 1 " Enable or disable loading the plugin.
"let cyfolds_fold_keywords = "class,def,async def,cclass,cdef,cpdef" " Cython.
let cyfolds_fold_keywords = "class,def,async def" " Python default.
let cyfolds_lines_of_module_docstrings = 20 " Lines to keep unfolded, -1 means keep all.
let cyfolds_lines_of_fun_and_class_docstrings = -1 " Lines to keep, -1 means keep all.
let cyfolds_start_in_manual_method = 1 " Default is to start in manual mode.
let cyfolds_no_initial_fold_calc = 0 " Whether to skip initial fold calculations.
let cyfolds_fix_syntax_highlighting_on_update = 1 " Redo syntax highlighting on all updates.
let cyfolds_update_all_windows_for_buffer = 1 " Update all windows for buffer, not just current.
let cyfolds_increase_toplevel_non_class_foldlevels = 0
" General folding settings.
set foldenable " Enable folding and show the current folds.
"set nofoldenable " Disable folding and show normal, unfolded text.
set foldcolumn=0 " The width of the fold-info column on the left, default is 0
set foldlevelstart=-1 " The initial foldlevel; 0 closes all, 99 closes none, -1 default.
set foldminlines=0 " Minimum number of lines in a fold; don't fold small things.
"set foldmethod=manual " Set for other file types if desired; Cyfolds ignores it for Python.
If you want to define any of the builtin folding settings for Python files
only, assuming they take local values, you could alternately use autocommands
in your .vimrc
, calling setlocal
. For example, to start with top-level
functions and classes unfolded, but only in Python files, you could use:
autocmd FileType python setlocal foldlevel=1
Sometimes opening visible folds with a higher fold level can take several
applications of the builtin zo
or za
commands. To force all folds to
open or close immediately I define this fold-toggling function in my .vimrc
file and bind it to the normal-mode space bar key (alternately, za
or any
other key could be remapped):
function! SuperFoldToggle()
" Force the fold on the current line to immediately open or close. Unlike za
" and zo it only takes one application to open any fold. Unlike zO it does
" not open recursively, it only opens the current fold.
if foldclosed('.') == -1
silent! foldclose
else
while foldclosed('.') != -1
silent! foldopen
endwhile
endif
endfunction
" This sets the space bar to toggle folding and unfolding in normal mode.
nnoremap <silent> <space> :call SuperFoldToggle()<CR>
While generally not recommended unless you have a very fast computer, Cyfolds
with the setting below, along with the expr folding method, gives the ideal
folding behavior. It resets the folds after any changes to the text, such as
from deleting and undoing, and after any inserts. Unfortunately it can be too
slow to use with, for example, repeated x
commands to delete words and
repeated u
commands for multiple undos.
" Not recommended in general.
autocmd TextChanged *.py call CyfoldsForceFoldUpdate()
Finally, some Vim color themes have poor settings for the foldline (the visible
line that appears for closed folds) and the foldcolumn (the optional left-side
gutter that appears when foldcolumn
is set greater than the default value
of 0). The colors can sometimes be glaring and distracting. I prefer the
background of the foldline to match the normal background. These are the two
Vim highlighting settings for folds. Use your own colors, obviously:
" Folding
" -------
highlight Folded guibg=#0e0e0e guifg=Grey30 gui=NONE cterm=NONE
highlight FoldColumn guibg=#0e0e0e guifg=Grey30 gui=NONE cterm=NONE
Set the ctermfg
and ctermbg
instead of (or in addition to) guifg
and guibg
if your setup uses those.
The vim-stay plugin, which persists the state of the folds across Vim invocations, can be used along with this plugin.
FastFold does not seem to interfere with Cyfolds and vice versa outside a
Python buffer. FastFold with Cyfolds in a Python buffer does introduce a very
slight delay when opening and closing folds. That is because it remaps the
folding/unfolding keys to update the folds each time. Disabling FastFold for
Python files eliminates this delay (but also the automatic fold updating on
those fold commands). Cyfolds handles things like suppressing fold updates in
insert mode and forcing updates (zuz
) by itself, so turning off FastFold for
Python buffers is recommended. The FastFold .vimrc
command for that is:
let fastfold_skip_filetypes=['python']