8000 GitHub - abarker/cyfolds: Fast, correct API-view Vim folding for Python, written in Cython. · GitHub
[go: up one dir, main page]

Skip to content
abarkerPublic

About

Fast, correct API-view Vim folding for Python, written in Cython.

Topics

Resources

Stars

12 stars

Watchers

2 watching

Forks

Latest commit

 

History

187 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Cyfolds

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 pure-Python version is also included and is used automatically if the Cython version has not been compiled. The compiled version is roughly 20 times faster: on a 9,600-line test file it calculates the folds in about 5 milliseconds, versus about 90 milliseconds for the pure-Python version. The pure-Python version is fast enough for most files, but compiling is recommended for large files.

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 docstrings 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.

Installation

Cyfolds requires a Vim that is compiled with Python 3 support and timer support. It has been tested with Vim 9.1 and Python 3.12 on Ubuntu 24.04, and earlier with Vim 8.0 and Neovim 0.2.2 on Ubuntu Linux and 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. To check that your Vim has Python 3 support, run vim --version and look for +python3.

  1. Clone this GitHub repo into your Vim packages directory. If you use the builtin Vim 8+ package manager:

    git clone https://github.com/abarker/cyfolds.git ~/.vim/pack/plugins/start/cyfolds

    If you use pathogen:

    git clone https://github.com/abarker/cyfolds.git ~/.vim/bundle/cyfolds

    On Windows, use ~/vimfiles in place of ~/.vim.

    The plugin should now work, using the pure-Python version. The remaining steps compile the faster Cython version, and are optional.

  2. Compiling requires a C compiler and the Python development headers. 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 macOS systems the Cython install page suggests Apple's Xcode compiler: https://developer.apple.com/.

  3. Compiling also requires the Cython and setuptools Python packages. They are only needed to compile, not to run the plugin. The compiled module only works with the Python version used to compile it, so that must be the same Python version that Vim uses (run :py3 import sys; print(sys.version) in Vim to see it). Cython 0.29 and 3.x both work.

    Use whichever of these ways to install them fits your Python setup:

    • On Ubuntu or Debian, install the system packages:

      sudo apt-get install cython3 python3-setuptools

      Then use the system Python, /usr/bin/python3, to compile in the next step.

    • If you work in a virtual environment based on the same Python version that Vim uses, install them into it:

      pip install cython setuptools
    • Otherwise, create a temporary virtual environment just for compiling:

      python3 -m venv ~/cyfolds-build-venv
      ~/cyfolds-build-venv/bin/pip install cython setuptools

      Then use ~/cyfolds-build-venv/bin/python to compile in the next step. (On Windows it is ~/cyfolds-build-venv/Scripts/python.) The virtual environment can be deleted after compiling.

    Note that pip install --user is refused on many recent Linux distributions, and in virtual environments.

  4. Run the compile.py script in the repo's python3 subdirectory, using the Python from the previous step:

    python3 ~/.vim/pack/plugins/start/cyfolds/python3/compile.py

    The script prints the Python version it compiled for, and offers to remove the intermediate build files. You can alternately run

    python3 setup.py build_ext --inplace

    from inside the python3 directory. To modify the compile options, look in the setup.py file.

The plugin is now ready to use in Vim. If the compiled module does not match Vim's Python version, the pure-Python version is used instead.

Configuration

Turn on folding in Vim, and plugins in general if you haven't already:

set foldenable
filetype plugin on

Foldlevels are calculated from the indentation divided by the shiftwidth setting, so shiftwidth should match the indentation used in the code. Usually set shiftwidth=4 is used for Python code. Code with other indentations, such as three spaces, is still folded correctly.

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.

New key mappings

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 the foldmethod is set to manual folds always need to be explicitly updated either with zuz or one of the Vim commands. When the foldmethod is set to expr folds are updated after inserts but can still get messed up and require updating (for example, when deleting characters with x or lines with dd, 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-in zx and zX commands, which always reset the open/closed states of folds according to foldlevel and which do not work with manual foldmethod.)

    The zuz command sets foldenable locally for the window if it is not already set. The key sequence is mapped to the function call CyfoldsForceFoldUpdate().

  • The z, key sequence toggles the foldmethod setting between expr and manual. 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. with zuz. Folds are automatically updated upon toggling to the expr method, but not on toggling to the manual 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 sets foldenable locally for the window if it is not already set. The key sequence is mapped to the function call CyfoldsToggleManualFolds().

Customizable settings

Keywords to trigger folding

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().

Number of docstring lines left unfolded

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.

Other settings

  • This setting will change the default of Cyfolds starting with foldmethod=manual to starting with foldmethod=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 to manual. To then switch to using folding you need to explicitly force the folds to be updated, such as with zuz or z,.

  • 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, with set foldlevelstart=1 in the .vimrc. The builtin zm and zr 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 builtin zo or za commands to open folded, toplevel, non-class elements. The SuperFoldToggle 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 and zX 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). The setting also applies to the automatic fold update on leaving insert mode with the expr foldmethod; with the default of 0, other windows showing the same buffer keep their old folds until they are updated, such as with zuz.

  • To completely disable loading of the Cyfolds plugin use this in your .vimrc:

    let cyfolds = 0

Example settings

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.

Interaction with other plugins

vim-stay

The vim-stay plugin, which persists the state of the folds across Vim invocations, can be used along with this plugin.

FastFold

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']

Running the tests

The tests use pytest. From the repo's root directory, run:

pytest test

The tests check both the pure-Python version and the compiled Cython version (when it has been compiled), and also run the plugin in a headless Vim if one with Python 3 support is installed.

About

Fast, correct API-view Vim folding for Python, written in Cython.

Topics

Resources

Stars

12 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

0