"" Guard if exists('g:autoloaded_unobtrusive_folds') || &compatible finish endif let g:autoloaded_unobtrusive_folds = 1 "" Configuration "" " @section Configuration, config " Since this method of folding is more involved than the built-in ones it can " be slow for very big files. Options are provided to mitigate this. """ g:unobtrusive_fold_max_lines if !exists('g:unobtrusive_fold_max_lines') "" " Set to non-zero value to make the commands below do nothing if the number " of lines in the current buffer is higher than the given value. Defaults to " 0. let g:unobtrusive_fold_max_lines = 0 endif """ g:unobtrusive_fold_max_prevlines if !exists('g:unobtrusive_fold_max_prevlines') "" " Set to non-zero value to look at at most this many previous lines when " calculating folds. Defaults to 0. let g:unobtrusive_fold_max_prevlines = 0 endif "" Constants let s:pattern_prefix = '\V\c\^\s\*' "" Functions """ #text() "" " @public " Set 'foldtext' to this function to get an improved version of " `getline(v:foldstart)`. Leading tabs are expanded to 'tabstop' spaces. If " 'fillchars' `fold:` is not space, the number of lines included in the fold is " printed, padded with `fold:` so that it lines up with one column short of the " right edge of the window, or 'textwidth' (if not 0), whichever is smaller. " The empty column at the far right is nice for people who set 'colorcolumn' to " `+1`. The rest of the line is cleared (i.e. not filled automatically by Vim " with 'fillchars' `fold:`). A good value for `fold:` might be the box drawing " character `─`. function! unobtrusive_fold#text() abort let line = getline(v:foldstart) let line = substitute(line, '\t', repeat(' ', &l:tabstop), 'g') let fillchar = matchstr(&fillchars, '.*\(^\|,\)fold:\zs.\ze') if empty(fillchar) let fillchar = '-' endif if fillchar !=# ' ' let foldlines = v:foldend - v:foldstart + 1 let signs = len(split(execute('sign place buffer=' . bufnr()), '\n')) - 1 let numbers = &l:number ? line('$') : &l:relativenumber ? winheight(0) : 0 let winwidth = winwidth(0) \ - &l:foldcolumn \ - (signs && &l:signcolumn ==# 'auto' || &l:signcolumn ==# 'yes' ? 2 : 0) \ - (numbers ? max([&l:numberwidth, strlen(numbers) + 1]) : 0) let width = winwidth - 1 if &l:textwidth let width = min([width, &l:textwidth]) endif let fill = repeat(fillchar, width - strlen(line) - strlen(foldlines) - 2) let pad = repeat(' ', winwidth - width) let line = line . ' ' . fill . ' ' . foldlines . pad endif return line endfunction """ #comment() function! unobtrusive_fold#comment(bang) abort if empty(&l:commentstring) return endif let commentstart = escape(split(&l:commentstring, '\s*%s')[0], '\') let commentlast = escape(commentstart[-1:], '\') call unobtrusive_fold#set( \ a:bang, \ commentstart . '\zs' . commentlast . '\+\ze', \ commentstart, \ [] \ ) endfunction """ #char() function! unobtrusive_fold#char(bang, char) abort let char = escape(a:char, '\') call unobtrusive_fold#set( \ a:bang, \ '\zs' . char . '\+\ze', \ '', \ ['Comment', 'Code', 'Snippet'] \ ) endfunction """ #set() function! unobtrusive_fold#set(bang, pattern, commentstart, ignore) abort if g:unobtrusive_fold_max_lines && line('$') > g:unobtrusive_fold_max_lines return endif let w:unobtrusive_fold_indent = !empty(a:bang) let w:unobtrusive_fold_pattern = a:pattern . '\s\*\S' let w:unobtrusive_fold_ignore = a:ignore let w:unobtrusive_fold_commentstart = a:commentstart let &l:foldexpr = 'unobtrusive_fold#expr()' let &l:foldmethod = 'expr' endfunction """ s:foldlevel() function! s:foldlevel(lnum) abort let foldlevel = strlen(matchstr( \ getline(a:lnum), \ s:pattern_prefix . w:unobtrusive_fold_pattern \ )) if foldlevel && !empty(w:unobtrusive_fold_ignore) for syntax in map(synstack(a:lnum, 1), 'synIDattr(v:val, "name")') for ignore in w:unobtrusive_fold_ignore if syntax =~# '\v' . '(^|[a-z])' . ignore . '([A-Z]|$)' return 0 endif endfor endfor endif return foldlevel endfunction """ s:indent() function! s:indent(lnum, ...) abort if (a:lnum < 1 && line('$') < a:lnum) || (a:0 && s:foldlevel(a:lnum)) return 0 endif return !empty(getline(a:lnum)) + indent(a:lnum) / shiftwidth() endfunction """ s:prevfoldlevelbase() function! s:prevfoldlevelbase(lnum) abort let stopline = g:unobtrusive_fold_max_prevlines \ ? max(1, a:lnum - g:unobtrusive_fold_max_prevlines) \ : 0 let pattern_column = '\%<' . (indent(a:lnum) + 2) . 'v' let pos = getpos('.') call cursor(a:lnum, 1) let prev = search( \ s:pattern_prefix . pattern_column . w:unobtrusive_fold_pattern, \ 'bW', \ stopline \ ) call setpos('.', pos) if !prev return [0, 0] endif let foldlevel = s:foldlevel(prev) return [foldlevel, foldlevel - indent(prev) / shiftwidth()] endfunction """ s:nextnonblank() function! s:nextnonblank(lnum) if empty(w:unobtrusive_fold_commentstart) return nextnonblank(a:lnum) endif let pos = getpos('.') call cursor(a:lnum, 1) let next = search( \ '\V\^\(' . w:unobtrusive_fold_commentstart . '\[[:punct:]]\*\$\)\@!\.', \ 'Wc' \ ) call setpos('.', pos) return next endfunction """ #expr() function! unobtrusive_fold#expr() abort """" Empty line if empty(getline(v:lnum)) return '=' endif """" Marker... """" ...start let foldlevel = s:foldlevel(v:lnum) if foldlevel return '>' . foldlevel endif """" ...end... let next = s:nextnonblank(v:lnum + 1) """" ...foldlevel let nextfoldlevel = s:foldlevel(next) if nextfoldlevel let prevfoldlevel = s:prevfoldlevelbase(v:lnum)[0] return '<' . (min([nextfoldlevel, prevfoldlevel]) + 1) endif """" ...indent if indent(next) < indent(v:lnum) let prevfoldlevel = s:prevfoldlevelbase(v:lnum)[0] let nextfoldlevel = s:prevfoldlevelbase(next)[0] if nextfoldlevel < prevfoldlevel return '<' . (nextfoldlevel + 1) endif endif """" Indent... if w:unobtrusive_fold_indent let indent = s:indent(v:lnum) let nextindent = s:indent(next) let aboveindent = s:indent(v:lnum - 1, 1) let belowindent = s:indent(v:lnum + 1, 1) """" ...start if aboveindent < indent && (belowindent >= indent || nextindent > indent) let prevbase = s:prevfoldlevelbase(v:lnum)[1] return '>' . (prevbase + indent) endif """" ...end if belowindent < indent && (aboveindent || belowindent) let prevbase = s:prevfoldlevelbase(v:lnum)[1] return '<' . (prevbase + min([indent, nextindent + !!belowindent])) endif endif """" No change return '=' endfunction """ #debug() function! unobtrusive_fold#debug() abort let lines = range(1, line('$')) let exprs = map(copy(lines), 'execute("let v:lnum=v:val|echon ".&foldexpr)') for l in lines let e = exprs[l-1] if len(e) == 1 let exprs[l-1] = e =~# '^[0-9]$' ? ' '.e : e.' ' endif endfor let levels = map(copy(lines), 'foldlevel(v:val)') let debugs = map(copy(lines), '"[".exprs[v:val-1]."|".levels[v:val-1]."]"') let pos = getpos('.') call setreg('"', join(debugs, "\n"), 'b') normal! gg0""P call setpos('.', pos) endfunction