Vim: Add SimpylFold

I keep fucking making these the wrong way and it's going to bite me in the ass. Submodules, self. SUBMODULES. SUBMODULES.
This commit is contained in:
Salt 2017-11-30 17:30:19 -06:00
parent 9aa3eaaed7
commit e6f90a24d5
10 changed files with 600 additions and 0 deletions

View File

@ -0,0 +1,3 @@
policies:
ProhibitImplicitScopeVariable:
enabled: false

View File

@ -0,0 +1,9 @@
# This is the list of SimpylFold authors for copyright purposes.
#
# This does not necessarily list everyone who has contributed code, since in
# some cases, their employer may be the copyright holder. To see the full list
# of contributors, see the revision history in source control.
Taylor Hedberg
Google Inc.
nfnty

View File

@ -0,0 +1,25 @@
Copyright 2012-2017 the SimpylFold authors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
- Neither the name of Taylor M. Hedberg nor the names of other contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,80 @@
SimpylFold
==========
Because of its reliance on significant whitespace rather than explicit block
delimiters, properly folding Python code can be tricky. The Python syntax
definition that comes bundled with Vim doesn't contain any fold directives at
all, and the simplest workaround is to `set foldmethod=indent`, which usually
ends up folding a lot more than you really want it to.
There's no shortage of Vim plugins for improved Python folding, but most seem
to suffer from cobbled-together algorithms with bizarre, intractable bugs
in the corner cases. SimpylFold aims to be exactly what its name suggests:
simple, correct folding for Python.
It's nothing more than it needs to be: it properly folds class and
function/method definitions, and leaves your loops and conditional blocks
untouched. There's no BS involved: no screwing around with unrelated options
(which several of the other plugins do), no choice of algorithms to scratch
your head over (there's only one that's correct); it just works, simply.
Installation
------------
Use one of the following plugin managers:
* [dein](https://github.com/Shougo/dein.vim)
* [vim-plug](https://github.com/junegunn/vim-plug)
* [vundle](https://github.com/VundleVim/Vundle.vim)
* [pathogen](https://github.com/tpope/vim-pathogen)
Also strongly recommend using [FastFold](https://github.com/Konfekt/FastFold)
due to Vim's folding being extremely slow by default.
Configuration
-------------
No configuration is necessary. However, there are a few configurable options.
### Option variables
Set variable to `1` to enable or `0` to disable.
For example to enable docstring preview in fold text you can add the
following command to your `~/.config/nvim/init.vim` or `~/.vimrc`:
```vim
let g:SimpylFold_docstring_preview = 1
```
| Variable | Description | Default |
| -------------------------------- | ------------------------------ | ------- |
| `g:SimpylFold_docstring_preview` | Preview docstring in fold text | `0` |
| `g:SimpylFold_fold_docstring` | Fold docstrings | `1` |
| `b:SimpylFold_fold_docstring` | Fold docstrings (buffer local) | `1` |
| `g:SimpylFold_fold_import` | Fold imports | `1` |
| `b:SimpylFold_fold_import` | Fold imports (buffer local) | `1` |
### Commands
There are also a few buffer local commands for fast toggling:
| Command | Description |
| ----------------------- | ------------------------- |
| `SimpylFoldDocstrings` | Enable docstring folding |
| `SimpylFoldDocstrings!` | Disable docstring folding |
| `SimpylFoldImports` | Enable import folding |
| `SimpylFoldImports!` | Disable import folding |
Usage
-----
Use Vim's built-in folding commands to expand and collapse folds.
The most basic commands are `zc` to close a fold and `zo` to open one.
See `:help fold-commands` for full documentation.
Bugs
----
If you find any bugs, please report them and submit pull requests on GitHub!
Simple is nice, but simple and correct is much better.
Happy hacking!

View File

@ -0,0 +1,389 @@
let s:blank_re = '^\s*$'
let s:comment_re = '^\s*#'
let s:multi_def_end_re = ')\%(\s*->\s*\S\+[^#]*\)\?:\s*\%(#.*\)\?$'
let s:multi_def_end_solo_re = '^\s*)\%(\s*->\s*\S\+[^#]*\)\?:\s*\%(#.*\)\?$'
let s:docstring_re = '^\s*[bBfFrRuU]\{0,2}\\\@<!\(''''''\|"""\|[''"]\)'
let s:string_start_re = '[bBfFrRuU]\{0,2}\\\@<!\%(''''''\|"""\|[''"]\)'
let s:string_prefix_re = '[bBfFrRuU]\{0,2}'
let s:line_cont_re = '\\$'
let s:import_start_re = '^\s*\%(from\|import\)'
let s:import_cont_re = '\%(from.*\((\)[^)]*\|.*\(\\\)\)$'
let s:import_end_paren_re = ')\s*$'
let s:import_end_esc_re = '[^\\]$'
" Initialize buffer
function! SimpylFold#BufferInit() abort
if &filetype ==# 'pyrex' || &filetype ==# 'cython'
let b:SimpylFold_def_re =
\ '\v^\s*%(%(class|%(async\s+)?def|cdef|cpdef|ctypedef)\s+\w+)|cdef\s*:'
else
let b:SimpylFold_def_re =
\ '\v^\s*%(class|%(async\s+)?def)\s+\w+|if\s+__name__\s*\=\=\s*%("__main__"|''__main__'')\s*:'
endif
if !exists('b:SimpylFold_fold_docstring')
let b:SimpylFold_fold_docstring =
\ !exists('g:SimpylFold_fold_docstring') || g:SimpylFold_fold_docstring
endif
if !exists('b:SimpylFold_fold_import')
let b:SimpylFold_fold_import =
\ !exists('g:SimpylFold_fold_import') || g:SimpylFold_fold_import
endif
endfunction
" Get spaces per indent setting
function! s:indent_spaces() abort
if &softtabstop > 0
return &softtabstop
elseif &softtabstop < 0 && &shiftwidth > 0
return &shiftwidth
endif
return &tabstop
endfunction
" Calculate indent
function! s:indent(line, ind_spaces) abort
let ind = matchend(a:line, '^ *') / a:ind_spaces
if ind == 0
let ind = matchend(a:line, '^\t*')
endif
" Fix indent for solo def multiline endings
if a:line =~# s:multi_def_end_solo_re
return ind + 1
endif
return ind
endfunction
function! s:defs_stack_prune(cache, defs_stack, ind) abort
for idx in range(len(a:defs_stack))
let ind_stack = a:cache[(a:defs_stack[idx])]['indent']
if a:ind == ind_stack
return a:defs_stack[(idx + 1):]
elseif a:ind > ind_stack
return a:defs_stack[(idx):]
endif
endfor
return []
endfunction
" Adjust previous blanks and comments
function! s:blanks_adj(cache, lnum, foldlevel) abort
let lnum_prev = a:lnum - 1
while lnum_prev != 0 && (
\ a:cache[lnum_prev]['is_blank'] || (
\ a:cache[lnum_prev]['is_comment'] &&
\ a:cache[lnum_prev]['indent'] <= a:cache[(a:lnum)]['indent']
\ )
\ )
let a:cache[lnum_prev]['foldexpr'] = a:foldlevel
let lnum_prev -= 1
endwhile
endfunction
" Check if previous lines are blanks or comments
function! s:are_lines_prev_blank(cache, lnum) abort
let lnum_prev = a:lnum - 1
while lnum_prev != 0
if !a:cache[lnum_prev]['is_blank'] && !a:cache[lnum_prev]['is_comment']
return 0
endif
let lnum_prev -= 1
endwhile
return 1
endfunction
" Compatibility shim
" 1.1x slower when `matchstrpos` exists
" 2.5x slower otherwise
let s:exists_matchstrpos = exists('*matchstrpos')
function! s:matchstrpos(expr, pat) abort
if s:exists_matchstrpos
return matchstrpos(a:expr, a:pat)
else
return [matchstr(a:expr, a:pat), match(a:expr, a:pat), matchend(a:expr, a:pat)]
endif
endfunction
" Multiline string parsing
" Returns:
" - bool: In string?
" - bool: Single quoted?
" - bool: Found multiple strings?
" - string: End regex.
" - string: Everything before first match.
function! s:multi_string(line, first_re, in_string) abort
" 2x performance for general case
if a:line !~# '[''"]'
return [a:in_string, 0, 0, '', '']
endif
let string_match = s:matchstrpos(a:line, a:first_re)
if string_match[1] == -1
return [a:in_string, 0, 0, '', '']
endif
" Anything before first match?
if string_match[1] >= 1
let before_first = a:line[:(string_match[1] - 1)]
else
let before_first = ''
endif
let in_string = a:in_string
let next_re = ''
let line_slice = a:line
let found_ends = 0
while string_match[1] != -1
if in_string
let in_string = 0
let found_ends += 1
let next_re = s:string_start_re
else
let in_string = 1
let quotes = string_match[0][matchend(string_match[0], s:string_prefix_re):]
let next_re = '\\\@<!' . quotes
endif
let line_slice = line_slice[(string_match[2]):]
if empty(line_slice)
break
endif
let string_match = s:matchstrpos(line_slice, next_re)
endwhile
if in_string
" Check if in single quoted string and line continues
let single_quoted = quotes =~# '^[''"]$'
if single_quoted && line_slice !~# s:line_cont_re
return [0, single_quoted, (found_ends >= 1), '', before_first]
else
return [1, single_quoted, (found_ends >= 1), next_re, before_first]
endif
else
return [0, 0, (found_ends >= 2), '', before_first]
endif
endfunction
" Create a new cache
function! s:cache() abort
let cache = [{}] " With padding for lnum offset
let lines = getbufline(bufnr('%'), 1, '$')
let lnum_last = len(lines)
call insert(lines, '') " Padding for lnum offset
let ind_spaces = s:indent_spaces()
let defs_stack = []
let ind_def = -1
let in_string = 0
let docstring_start = -1
let in_import = 0
let was_import = 0
for lnum in range(1, lnum_last)
let line = lines[lnum]
" Multiline strings
if in_string
let foldlevel = len(defs_stack)
call add(cache, {'is_blank': 0, 'is_comment': 0, 'foldexpr': foldlevel})
let string_match = s:multi_string(line, string_end_re, 1)
if string_match[0]
" Starting new multiline string?
if string_match[2]
let in_string_single = string_match[1]
let string_end_re = string_match[3]
let docstring_start = -1 " Invalid docstring
elseif in_string_single && line !~# s:line_cont_re
let in_string = 0
endif
else
if docstring_start != -1
let foldlevel += 1
let cache[docstring_start]['foldexpr'] = '>' . foldlevel
for lnum_docstring in range((docstring_start + 1), lnum)
let cache[lnum_docstring]['foldexpr'] = foldlevel
endfor
let docstring_start = -1
endif
let in_string = 0
endif
continue
endif
" Blank lines
if line =~# s:blank_re
if lnum == lnum_last
call add(cache, {'is_blank': 1, 'is_comment': 0, 'foldexpr': 0})
call s:blanks_adj(cache, lnum, 0)
else
call add(cache, {'is_blank': 1, 'is_comment': 0, 'foldexpr': len(defs_stack)})
endif
continue
endif
let ind = s:indent(line, ind_spaces)
" Comments
if line =~# s:comment_re
call add(cache, {'is_blank': 0, 'is_comment': 1, 'indent': ind})
let foldlevel = 0
let defs_stack_len = len(defs_stack)
for idx in range(defs_stack_len)
if ind > cache[defs_stack[idx]]['indent']
let foldlevel = defs_stack_len - idx
break
endif
endfor
let cache[lnum]['foldexpr'] = foldlevel
call s:blanks_adj(cache, lnum, foldlevel)
continue
endif
call add(cache, {'is_blank': 0, 'is_comment': 0,
\ 'is_def': line =~# b:SimpylFold_def_re, 'indent': ind})
" Definitions
if cache[lnum]['is_def']
if empty(defs_stack)
let defs_stack = [lnum]
elseif ind == ind_def
let defs_stack[0] = lnum
elseif ind > ind_def
call insert(defs_stack, lnum)
elseif ind < ind_def
let defs_stack = [lnum] + s:defs_stack_prune(cache, defs_stack, ind)
endif
let foldlevel = len(defs_stack) - 1
let ind_def = ind
call s:blanks_adj(cache, lnum, foldlevel)
let cache[lnum]['foldexpr'] = '>' . (foldlevel + 1)
continue
endif
" Everything else
if !empty(defs_stack)
if ind == ind_def
let defs_stack = defs_stack[1:]
let ind_def = cache[defs_stack[0]]['indent']
elseif ind < ind_def
let defs_stack = s:defs_stack_prune(cache, defs_stack, ind)
if !empty(defs_stack)
let ind_def = cache[defs_stack[0]]['indent']
else
let ind_def = -1
endif
endif
endif
let foldlevel = len(defs_stack)
" Multiline strings start
let string_match = s:multi_string(line, s:string_start_re, 0)
if string_match[0]
let in_string = 1
let in_string_single = string_match[1]
let string_end_re = string_match[3]
" Docstrings
if b:SimpylFold_fold_docstring && !string_match[2] && string_match[4] =~# s:blank_re
let lnum_prev = lnum - 1
if lnum == 1 || s:are_lines_prev_blank(cache, lnum) || (
\ !cache[lnum_prev]['is_blank'] && !cache[lnum_prev]['is_comment'] && (
\ cache[lnum_prev]['is_def'] ||
\ lines[lnum_prev] =~# s:multi_def_end_re
\ )
\ )
let docstring_start = lnum
endif
endif
let cache[lnum]['foldexpr'] = foldlevel
continue
endif
" Imports
if b:SimpylFold_fold_import
if in_import
if line =~# import_end_re
let in_import = 0
endif
call s:blanks_adj(cache, lnum, foldlevel + 1)
let cache[lnum]['foldexpr'] = foldlevel + 1
continue
elseif match(line, s:import_start_re) != -1
let import_cont_match = matchlist(line, s:import_cont_re)
if !empty(import_cont_match)
if import_cont_match[1] ==# '('
let import_end_re = s:import_end_paren_re
let in_import = 1
elseif import_cont_match[2] ==# '\'
let import_end_re = s:import_end_esc_re
let in_import = 1
endif
endif
if was_import
call s:blanks_adj(cache, lnum, foldlevel + 1)
let cache[lnum]['foldexpr'] = foldlevel + 1
else
let cache[lnum]['foldexpr'] = '>' . (foldlevel + 1)
endif
let was_import = 1
continue
else
let was_import = 0
endif
endif
" Normal
call s:blanks_adj(cache, lnum, foldlevel)
let cache[lnum]['foldexpr'] = foldlevel
endfor
return cache
endfunction
" Compute foldexpr for Python code
function! SimpylFold#FoldExpr(lnum) abort
if !exists('b:SimpylFold_cache')
let b:SimpylFold_cache = s:cache()
endif
return b:SimpylFold_cache[(a:lnum)]['foldexpr']
endfunction
" Recache the buffer
function! SimpylFold#Recache() abort
if exists('b:SimpylFold_cache')
unlet b:SimpylFold_cache
endif
endfunction
" Compute foldtext by obtaining the first line of the docstring for
" the folded class or function, if any exists
function! SimpylFold#FoldText() abort
let lnum = v:foldstart
let line = getline(lnum)
let string_match = matchlist(line, s:docstring_re)
" Docstring folds
if !empty(string_match)
let docstring = substitute(line, s:docstring_re, '', '')
if docstring !~# s:blank_re
return ''
endif
let docstring = getline(nextnonblank(lnum + 1))
" Definition folds
else
let lnum = nextnonblank(lnum + 1)
let line = getline(lnum)
let string_match = matchlist(line, s:docstring_re)
if empty(string_match)
return ''
endif
let docstring = substitute(line, s:docstring_re, '', '')
if docstring =~# s:blank_re
let docstring = getline(nextnonblank(lnum + 1))
endif
endif
return ' ' . substitute(docstring, '^\s*\|\s*$\|' . string_match[1] . '\s*$', '', 'g')
endfunction

View File

@ -0,0 +1,69 @@
*SimpylFold.txt* No-BS Python code folding for Vim
==============================================================================
Introduction *SimpylFold*
Because of its reliance on significant whitespace rather than explicit block
delimiters, properly folding Python code can be tricky. The Python syntax
definition that comes bundled with Vim doesn't contain any fold directives at
all, and the simplest workaround is to `set foldmethod=indent`, which usually
ends up folding a lot more than you really want it to.
There's no shortage of Vim plugins for improved Python folding, but most seem
to suffer from cobbled-together algorithms with bizarre, intractable bugs
in the corner cases. SimpylFold aims to be exactly what its name suggests:
simple, correct folding for Python.
It's nothing more than it needs to be: it properly folds class and
function/method definitions, and leaves your loops and conditional blocks
untouched. There's no BS involved: no screwing around with unrelated options
(which several of the other plugins do), no choice of algorithms to scratch
your head over (there's only one that's correct); it just works, simply.
==============================================================================
Configuration *SimpylFold-configuration*
No configuration is necessary. However, there are a few configurable options.
Option variables
----------------
Set variable to `1` to enable or `0` to disable.
For example to enable docstring preview in fold text you can add the
following command to your `~/.config/nvim/init.vim` or `~/.vimrc`: >
let g:SimpylFold_docstring_preview = 1
<
| Variable | Description | Default |
| ------------------------------- | ------------------------------ | ------- |
| `g:SimpylFold_docstring_preview` | Preview docstring in fold text | `0` |
| `g:SimpylFold_fold_docstring` | Fold docstrings | `1` |
| `b:SimpylFold_fold_docstring` | Fold docstrings (buffer local) | `1` |
| `g:SimpylFold_fold_import` | Fold imports | `1` |
| `b:SimpylFold_fold_import` | Fold imports (buffer local) | `1` |
Commands
--------
There are also a few buffer local commands for fast toggling:
| Command | Description |
| ----------------------- | ------------------------- |
| `SimpylFoldDocstrings` | Enable docstring folding |
| `SimpylFoldDocstrings!` | Disable docstring folding |
| `SimpylFoldImports` | Enable import folding |
| `SimpylFoldImports!` | Disable import folding |
==============================================================================
Usage *SimpylFold-usage*
Use Vim's built-in folding commands to expand and collapse folds.
The most basic commands are `zc` to close a fold and `zo` to open one.
See `:help fold-commands` for full documentation.
==============================================================================
Bugs *SimpylFold-bugs*
If you find any bugs, please report them and submit pull requests on GitHub!
Simple is nice, but simple and correct is much better.

View File

@ -0,0 +1 @@
python

View File

@ -0,0 +1,16 @@
if exists('b:loaded_SimpylFold')
finish
endif
let b:loaded_SimpylFold = 1
call SimpylFold#BufferInit()
setlocal foldexpr=SimpylFold#FoldExpr(v:lnum)
setlocal foldmethod=expr
augroup SimpylFold
autocmd TextChanged,InsertLeave <buffer> call SimpylFold#Recache()
augroup END
if exists('g:SimpylFold_docstring_preview') && g:SimpylFold_docstring_preview
setlocal foldtext=foldtext()\ .\ SimpylFold#FoldText()
endif

View File

@ -0,0 +1,7 @@
if exists('g:loaded_SimpylFold')
finish
endif
let g:loaded_SimpylFold = 1
command! -bang SimpylFoldDocstrings let b:SimpylFold_fold_docstring = <bang>1 | call SimpylFold#Recache()
command! -bang SimpylFoldImports let b:SimpylFold_fold_import = <bang>1 | call SimpylFold#Recache()

@ -0,0 +1 @@
Subproject commit e8e144d446603fbaebf23c54491f43ccd79b65f4