X Tutup
" Notice that folding is based on single line so complex regular expressions " that take previous line into consideration are not fit for the job. " Regex definitions for correct folding let s:def_regex = g:pymode_folding_regex let s:blank_regex = '^\s*$' " Spyder, a very popular IDE for python has a template which includes " '@author:' ; thus the regex below. let s:decorator_regex = '^\s*@\(author:\)\@!' let s:docstring_line_regex = '^\s*[uUrR]\=\("""\|''''''\).\+\1\s*$' let s:docstring_begin_regex = '^\s*[uUrR]\=\%("""\|''''''\).*\S' let s:docstring_end_regex = '\%("""\|''''''\)\s*$' " This one is needed for the while loop to count for opening and closing " docstrings. let s:docstring_general_regex = '\%("""\|''''''\)' let s:symbol = matchstr(&fillchars, 'fold:\zs.') " handles multibyte characters if s:symbol == '' let s:symbol = ' ' endif " '''''''' fun! pymode#folding#text() " {{{ let fs = v:foldstart while getline(fs) !~ s:def_regex && getline(fs) !~ s:docstring_begin_regex let fs = nextnonblank(fs + 1) endwhile if getline(fs) =~ s:docstring_end_regex && getline(fs) =~ s:docstring_begin_regex let fs = nextnonblank(fs + 1) endif let line = getline(fs) let has_numbers = &number || &relativenumber let nucolwidth = &fdc + has_numbers * &numberwidth let windowwidth = winwidth(0) - nucolwidth - 6 let foldedlinecount = v:foldend - v:foldstart " expand tabs into spaces let onetab = strpart(' ', 0, &tabstop) let line = substitute(line, '\t', onetab, 'g') let line = strpart(line, 0, windowwidth - 2 -len(foldedlinecount)) let line = substitute(line, '[uUrR]\=\%("""\|''''''\)', '', '') let fillcharcount = windowwidth - len(line) - len(foldedlinecount) + 1 return line . ' ' . repeat(s:symbol, fillcharcount) . ' ' . foldedlinecount endfunction "}}} fun! pymode#folding#expr(lnum) "{{{ let l:return_value = pymode#folding#foldcase(a:lnum)['foldlevel'] return l:return_value endfunction "}}} fun! pymode#folding#foldcase(lnum) "{{{ " Return a dictionary with a brief description of the foldcase and the " evaluated foldlevel: {'foldcase': 'case description', 'foldlevel': 1}. let l:foldcase = 'general' let l:foldlevel = 0 let line = getline(a:lnum) let indent = indent(a:lnum) let prev_line = getline(a:lnum - 1) let next_line = getline(a:lnum + 1) " Decorators {{{ if line =~ s:decorator_regex let l:foldcase = 'decorator declaration' let l:foldlevel = '>'.(indent / &shiftwidth + 1) return {'foldcase': l:foldcase, 'foldlevel': l:foldlevel} endif "}}} " Definition {{{ if line =~ s:def_regex " TODO: obscure case. " If indent of this line is greater or equal than line below " and previous non blank line does not end with : (that is, is not a " definition) " Keep the same indentation " xxx " if indent(a:lnum) >= indent(a:lnum+1) " xxx " \ && getline(prevnonblank(a:lnum)) !~ ':\s*$' " xxx " let l:foldcase = 'definition' " xxx " let l:foldlevel = '=' " xxx " return {'foldcase': l:foldcase, 'foldlevel': l:foldlevel} " xxx " endif " Check if last decorator is before the last def let decorated = 0 let lnum = a:lnum - 1 while lnum > 0 if getline(lnum) =~ s:def_regex break elseif getline(lnum) =~ s:decorator_regex let decorated = 1 break endif let lnum -= 1 endwhile if decorated let l:foldcase = 'decorated function declaration' let l:foldlevel = '=' else let l:foldcase = 'function declaration' let l:foldlevel = '>'.(indent / &shiftwidth + 1) endif return {'foldcase': l:foldcase, 'foldlevel': l:foldlevel} endif "}}} " Docstrings {{{ " TODO: A while loop now counts the number of open and closed folding in " order to determine if it is a closing or opening folding. " It is working but looks like it is an overkill. " Notice that an effect of this is that other docstring matches will not " be one liners. if line =~ s:docstring_line_regex let l:foldcase = 'one-liner docstring' let l:foldlevel = '=' return {'foldcase': l:foldcase, 'foldlevel': l:foldlevel} endif if line =~ s:docstring_begin_regex if s:Is_opening_folding(a:lnum) let l:foldcase = 'open multiline docstring' let l:foldlevel = 'a1' endif return {'foldcase': l:foldcase, 'foldlevel': l:foldlevel} endif if line =~ s:docstring_end_regex if !s:Is_opening_folding(a:lnum) let l:foldcase = 'close multiline docstring' let l:foldlevel = 's1' endif return {'foldcase': l:foldcase, 'foldlevel': l:foldlevel} endif "}}} " Blocks. {{{ let s:save_cursor = getcurpos() let line_block_start = s:BlockStart(a:lnum) let line_block_end = s:BlockEnd(a:lnum) let prev_line_block_start = s:BlockStart(a:lnum - 1) if line !~ s:blank_regex if line_block_start == prev_line_block_start \ || a:lnum - line_block_start == 1 let l:foldcase = 'non blank line; first line of block or part of it' let l:foldlevel = '=' elseif indent < indent(prevnonblank(a:lnum - 1)) if indent == 0 let l:foldcase = 'non blank line; zero indent' let l:foldlevel = 0 else let l:foldcase = 'non blank line; non zero indent' let l:foldlevel = indent(line_block_start) / &shiftwidth + 1 endif endif call setpos('.', s:save_cursor) return {'foldcase': l:foldcase, 'foldlevel': l:foldlevel} else call setpos('.', s:save_cursor) endif " endif " }}} " Blank Line {{{ " Comments: cases of blank lines: " 1. After non blank line: gets folded with previous line. " 1. Just after a block; in this case it gets folded with the block. " 1. Between docstrings and imports. " 1. Inside docstrings. " 2. Inside functions/methods. " 3. Between functions/methods. if line =~ s:blank_regex if prev_line !~ s:blank_regex let l:foldcase = 'blank line after non blank line' let l:foldlevel = '=' return {'foldcase': l:foldcase, 'foldlevel': l:foldlevel} elseif a:lnum > line_block_start && a:lnum < line_block_end let l:foldcase = 'blank line inside block' let l:foldlevel = '=' return {'foldcase': l:foldcase, 'foldlevel': l:foldlevel} endif " if prev_line =~ s:blank_regex " if indent(a:lnum + 1) == 0 && next_line !~ s:blank_regex && next_line !~ s:docstring_general_regex " if s:Is_opening_folding(a:lnum) " let l:foldcase = 'case 1' " let l:foldlevel = '=' " return {'foldcase': l:foldcase, 'foldlevel': l:foldlevel} " else " let l:foldcase = 'case 2' " let l:foldlevel = 0 " return {'foldcase': l:foldcase, 'foldlevel': l:foldlevel} " endif " endif " let l:foldcase = 'case 3' " let l:foldlevel = -1 " return {'foldcase': l:foldcase, 'foldlevel': l:foldlevel} " else " let l:foldcase = 'case 4' " let l:foldlevel = '=' " return {'foldcase': l:foldcase, 'foldlevel': l:foldlevel} " endif endif " }}} return {'foldcase': l:foldcase, 'foldlevel': l:foldlevel} endfunction "}}} fun! s:BlockStart(lnum) "{{{ " Returns the definition statement line which encloses the current line. let line = getline(a:lnum) if line !~ s:blank_regex let l:inferred_indent = indent(a:lnum) else let l:inferred_indent = prevnonblank(a:lnum) endif " Note: Make sure to reset cursor position after using this function. call cursor(a:lnum, 0) " In case the end of the block is indented to a higher level than the def " statement plus one shiftwidth, we need to find the indent level at the " bottom of that if/for/try/while/etc. block. " Flags from searchpos() (same as search()): " b: search Backward instead of forward " n: do Not move the cursor " W: don't Wrap around the end of the file let previous_definition = searchpos(s:def_regex, 'bnW') while previous_definition[0] != 1 && previous_definition != [0, 0] \ && indent(previous_definition[0]) >= l:inferred_indent let previous_definition = searchpos(s:def_regex, 'bnW') call cursor(previous_definition[0] - 1, 0) endwhile let last_def = previous_definition[0] if last_def call cursor(last_def, 0) let last_def_indent = indent(last_def) call cursor(last_def, 0) let next_stmt_at_def_indent = searchpos('\v^\s{'.last_def_indent.'}[^[:space:]#]', 'nW')[0] else let next_stmt_at_def_indent = -1 endif " Now find the class/def one shiftwidth lower than the start of the " aforementioned indent block. if next_stmt_at_def_indent && (next_stmt_at_def_indent < a:lnum) let max_indent = max([indent(next_stmt_at_def_indent) - &shiftwidth, 0]) else let max_indent = max([indent(prevnonblank(a:lnum)) - &shiftwidth, 0]) endif let result = searchpos('\v^\s{,'.max_indent.'}(def |class )\w', 'bcnW')[0] return result endfunction "}}} function! Blockstart(x) let save_cursor = getcurpos() return s:BlockStart(a:x) call setpos('.', save_cursor) endfunction fun! s:BlockEnd(lnum) "{{{ " Note: Make sure to reset cursor position after using this function. call cursor(a:lnum, 0) return searchpos('\v^\s{,'.indent('.').'}\S', 'nW')[0] - 1 endfunction "}}} function! Blockend(lnum) let save_cursor = getcurpos() return s:BlockEnd(a:lnum) call setpos('.', save_cursor) endfunction function! s:Is_opening_folding(lnum) "{{{ " Helper function to see if multi line docstring is opening or closing. " Cache the result so the loop runs only once per change. if get(b:, 'fold_changenr', -1) == changenr() return b:fold_cache[a:lnum - 1] "If odd then it is an opening else let b:fold_changenr = changenr() let b:fold_cache = [] endif " To be analized if odd/even to inform if it is opening or closing. let fold_odd_even = 0 " To inform is already has an open docstring. let has_open_docstring = 0 " To help skipping ''' and """ which are not docstrings. let extra_docstrings = 0 " The idea of this part of the function is to identify real docstrings and " not just triple quotes (that could be a regular string). " Iterater over all lines from the start until current line (inclusive) for i in range(1, line('$')) let i_line = getline(i) if i_line =~ s:docstring_begin_regex && ! has_open_docstring " This causes the loop to continue if there is a triple quote which " is not a docstring. if extra_docstrings > 0 let extra_docstrings = extra_docstrings - 1 else let has_open_docstring = 1 let fold_odd_even = fold_odd_even + 1 endif " If it is an end doc and has an open docstring. elseif i_line =~ s:docstring_end_regex && has_open_docstring let has_open_docstring = 0 let fold_odd_even = fold_odd_even + 1 elseif i_line =~ s:docstring_general_regex let extra_docstrings = extra_docstrings + 1 endif call add(b:fold_cache, fold_odd_even % 2) endfor return b:fold_cache[a:lnum] endfunction "}}} " vim: fdm=marker:fdl=0
X Tutup