Skip to content

Commit d113cff

Browse files
authored
Merge pull request python-mode#715 from fmv1992/develop
improved folding in various cases
2 parents f7ccee5 + e3f714b commit d113cff

File tree

2 files changed

+105
-17
lines changed

2 files changed

+105
-17
lines changed

autoload/pymode/folding.vim

Lines changed: 104 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
" Python-mode folding functions
22

3+
" Notice that folding is based on single line so complex regular expressions
4+
" that take previous line into consideration are not fit for the job.
35

6+
" Regex definitions for correct folding
47
let s:def_regex = g:pymode_folding_regex
58
let s:blank_regex = '^\s*$'
6-
let s:decorator_regex = '^\s*@'
7-
let s:doc_begin_regex = '^\s*\%("""\|''''''\)'
9+
" Spyder, a very popular IDE for python has a template which includes
10+
" '@author:' ; thus the regex below.
11+
let s:decorator_regex = '^\s*@\(author:\)\@!'
12+
let s:doc_begin_regex = '^\s*[uU]\=\%("""\|''''''\)'
813
let s:doc_end_regex = '\%("""\|''''''\)\s*$'
9-
let s:doc_line_regex = '^\s*\("""\|''''''\).\+\1\s*$'
14+
" This one is needed for the while loop to count for opening and closing
15+
" docstrings.
16+
let s:doc_general_regex = '\%("""\|''''''\)'
17+
let s:doc_line_regex = '^\s*[uU]\=\("""\|''''''\).\+\1\s*$'
1018
let s:symbol = matchstr(&fillchars, 'fold:\zs.') " handles multibyte characters
1119
if s:symbol == ''
1220
let s:symbol = ' '
1321
endif
22+
" ''''''''
1423

1524

1625
fun! pymode#folding#text() " {{{
@@ -33,24 +42,29 @@ fun! pymode#folding#text() " {{{
3342
let line = substitute(line, '\t', onetab, 'g')
3443

3544
let line = strpart(line, 0, windowwidth - 2 -len(foldedlinecount))
36-
let line = substitute(line, '\%("""\|''''''\)', '', '')
45+
let line = substitute(line, '[uU]\=\%("""\|''''''\)', '', '')
3746
let fillcharcount = windowwidth - len(line) - len(foldedlinecount) + 1
3847
return line . ' ' . repeat(s:symbol, fillcharcount) . ' ' . foldedlinecount
3948
endfunction "}}}
4049

41-
4250
fun! pymode#folding#expr(lnum) "{{{
4351

4452
let line = getline(a:lnum)
4553
let indent = indent(a:lnum)
4654
let prev_line = getline(a:lnum - 1)
55+
let next_line = getline(a:lnum + 1)
4756

57+
" Decorators {{{
4858
if line =~ s:decorator_regex
4959
return ">".(indent / &shiftwidth + 1)
50-
endif
60+
endif "}}}
5161

62+
" Definition {{{
5263
if line =~ s:def_regex
53-
" single line def
64+
" If indent of this line is greater or equal than line below
65+
" and previous non blank line does not end with : (that is, is not a
66+
" definition)
67+
" Keep the same indentation
5468
if indent(a:lnum) >= indent(a:lnum+1) && getline(prevnonblank(a:lnum)) !~ ':\s*$'
5569
return '='
5670
endif
@@ -71,16 +85,35 @@ fun! pymode#folding#expr(lnum) "{{{
7185
else
7286
return ">".(indent / &shiftwidth + 1)
7387
endif
74-
endif
88+
endif "}}}
7589

76-
if line =~ s:doc_begin_regex && line !~ s:doc_line_regex && prev_line =~ s:def_regex
77-
return ">".(indent / &shiftwidth + 1)
90+
" Docstrings {{{
91+
92+
" TODO: A while loop now counts the number of open and closed folding in
93+
" order to determine if it is a closing or opening folding.
94+
" It is working but looks like it is an overkill.
95+
96+
" Notice that an effect of this is that other docstring matches will not
97+
" be one liners.
98+
if line =~ s:doc_line_regex
99+
return "="
78100
endif
79101

80-
if line =~ s:doc_end_regex && line !~ s:doc_line_regex
81-
return "<".(indent / &shiftwidth + 1)
102+
if line =~ s:doc_begin_regex
103+
" echom 'just entering'
104+
if s:Is_opening_folding(a:lnum)
105+
" echom 'entering at line ' . a:lnum
106+
return ">".(indent / &shiftwidth + 1)
107+
endif
82108
endif
109+
if line =~ s:doc_end_regex
110+
if !s:Is_opening_folding(a:lnum)
111+
" echom 'leaving at line ' . a:lnum
112+
return "<".(indent / &shiftwidth + 1)
113+
endif
114+
endif "}}}
83115

116+
" Nested Definitions {{{
84117
" Handle nested defs but only for files shorter than
85118
" g:pymode_folding_nest_limit lines due to performance concerns
86119
if line('$') < g:pymode_folding_nest_limit && indent(prevnonblank(a:lnum))
@@ -125,18 +158,25 @@ fun! pymode#folding#expr(lnum) "{{{
125158
finally
126159
call setpos('.', curpos)
127160
endtry
128-
endif
161+
endif " }}}
129162

163+
" Blank Line {{{
130164
if line =~ s:blank_regex
131165
if prev_line =~ s:blank_regex
132-
if indent(a:lnum + 1) == 0 && getline(a:lnum + 1) !~ s:blank_regex
133-
return 0
166+
if indent(a:lnum + 1) == 0 && next_line !~ s:blank_regex && next_line !~ s:doc_general_regex
167+
if s:Is_opening_folding(a:lnum)
168+
" echom a:lnum
169+
return "="
170+
else
171+
" echom "not " . a:lnum
172+
return 0
173+
endif
134174
endif
135175
return -1
136176
else
137177
return '='
138178
endif
139-
endif
179+
endif " }}}
140180

141181
return '='
142182

@@ -174,4 +214,52 @@ fun! s:BlockEnd(lnum) "{{{
174214
return searchpos('\v^\s{,'.indent('.').'}\S', 'nW')[0] - 1
175215
endfunction "}}}
176216

217+
function! s:Is_opening_folding(lnum) "{{{
218+
" Helper function to see if docstring is opening or closing
219+
let number_of_folding = 0 " To be analized if odd/even to inform if it is opening or closing.
220+
let has_open_docstring = 0 " To inform is already has an open docstring.
221+
let extra_docstrings = 0 " To help skipping ''' and """ which are not docstrings
222+
223+
" The idea of this part of the function is to identify real docstrings and
224+
" not just triple quotes (that could be a regular string).
225+
"
226+
" Iterater over all lines from the start until current line (inclusive)
227+
for i in range(1, a:lnum)
228+
let i_line = getline(i)
229+
230+
if i_line =~ s:doc_line_regex
231+
" echom "case 00 on line " . i
232+
continue
233+
endif
234+
235+
if i_line =~ s:doc_begin_regex && ! has_open_docstring
236+
" echom "case 01 on line " . i
237+
" This causes the loop to continue if there is a triple quote which
238+
" is not a docstring.
239+
if extra_docstrings > 0
240+
let extra_docstrings = extra_docstrings - 1
241+
continue
242+
else
243+
let has_open_docstring = 1
244+
let number_of_folding = number_of_folding + 1
245+
endif
246+
" If it is an end doc and has an open docstring.
247+
elseif i_line =~ s:doc_end_regex && has_open_docstring
248+
" echom "case 02 on line " . i
249+
let has_open_docstring = 0
250+
let number_of_folding = number_of_folding + 1
251+
252+
elseif i_line =~ s:doc_general_regex
253+
" echom "extra docstrings on line " . i
254+
let extra_docstrings = extra_docstrings + 1
255+
endif
256+
endfor
257+
258+
if fmod(number_of_folding, 2) == 1 "If odd then it is an opening
259+
return 1
260+
else
261+
return 0
262+
endif
263+
endfunction "}}}
264+
177265
" vim: fdm=marker:fdl=0

plugin/pymode.vim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ call pymode#default("g:pymode_folding", 1)
3939
" Maximum file length to check for nested class/def statements
4040
call pymode#default("g:pymode_folding_nest_limit", 1000)
4141
" Change for folding customization (by example enable fold for 'if', 'for')
42-
call pymode#default("g:pymode_folding_regex", '^\s*\%(class\|def\|async\s\+def\) \w\+')
42+
call pymode#default("g:pymode_folding_regex", '^\s*\%(class\|def\|async\s\+def\) .\+\(:\s\+\w\)\@!')
4343

4444
" Enable/disable python motion operators
4545
call pymode#default("g:pymode_motion", 1)

0 commit comments

Comments
 (0)