Files
2018-09-02 21:27:23 +02:00

291 lines
9.3 KiB
VimL

" ingo/folds.vim: Functions for dealing with folds.
"
" DEPENDENCIES:
"
" Copyright: (C) 2008-2018 Ingo Karkat
" The VIM LICENSE applies to this script; see ':help copyright'.
"
" Maintainer: Ingo Karkat <ingo@karkat.de>
function! s:FoldBorder( lnum, direction )
let l:foldBorder = (a:direction < 0 ? foldclosed(a:lnum) : foldclosedend(a:lnum))
return (l:foldBorder == -1 ? a:lnum : l:foldBorder)
endfunction
function! ingo#folds#RelativeWindowLine( lnum, count, direction, ... )
"******************************************************************************
"* PURPOSE:
" Determine the line number a:count visible (i.e. not folded) lines away from
" a:lnum, including all lines in closed folds.
"* ASSUMPTIONS / PRECONDITIONS:
" None.
"* EFFECTS / POSTCONDITIONS:
" None.
"* INPUTS:
" a:lnum Line number to base the calculation on.
" a:count Number of visible lines away from a:lnum.
" a:direction -1 for upward, 1 for downward relative movement of a:count lines
" a:folddirection for a fold at the target, return the fold start lnum when
" -1, or the fold end lnum when 1. Defaults to a:direction,
" which amounts to the maximum covered lines, i.e. for upward
" movement, the fold start, for downward movement, the fold
" end
"* RETURN VALUES:
" line number, or -1 if the relative line is out of the range of the lines in
" the buffer.
"******************************************************************************
let l:lnum = a:lnum
let l:count = a:count
while l:count > 0
let l:lnum = s:FoldBorder(l:lnum, a:direction) + a:direction
if a:direction < 0 && l:lnum < 1 || a:direction > 0 && l:lnum > line('$')
return -1
endif
let l:count -= 1
endwhile
return s:FoldBorder(l:lnum, (a:0 ? a:1 : a:direction))
endfunction
function! ingo#folds#NextVisibleLine( lnum, direction )
"******************************************************************************
"* PURPOSE:
" Determine the line number of the next visible (i.e. not folded) line.
"* ASSUMPTIONS / PRECONDITIONS:
" None.
"* EFFECTS / POSTCONDITIONS:
" None.
"* INPUTS:
" a:lnum Line number to base the calculation on. When this one isn't folded,
" it is returned.
" a:direction -1 for upward, 1 for downward relative movement
"* RETURN VALUES:
" line number, of -1 if there is no more visible line in that direction of the
" buffer.
"******************************************************************************
let l:lnum = a:lnum
while l:lnum > 0 && l:lnum <= line('$')
let l:borderLnum = (a:direction < 0 ? foldclosed(l:lnum) : foldclosedend(l:lnum))
if l:borderLnum == -1
return l:lnum
else
let l:lnum = l:borderLnum + a:direction
endif
endwhile
return -1
endfunction
function! ingo#folds#LastVisibleLine( lnum, direction )
"******************************************************************************
"* PURPOSE:
" Determine the line number of the last visible (i.e. not folded) line.
"* ASSUMPTIONS / PRECONDITIONS:
" None.
"* EFFECTS / POSTCONDITIONS:
" None.
"* INPUTS:
" a:lnum Line number to base the calculation on.
" a:direction -1 for upward, 1 for downward relative movement
"* RETURN VALUES:
" line number, of -1 if there is no more visible line in that direction of the
" buffer.
"******************************************************************************
let l:lnum = ingo#folds#NextVisibleLine(a:lnum, a:direction)
if l:lnum == -1
return l:lnum
endif
while l:lnum > 0 && l:lnum <= line('$')
if foldclosed(l:lnum) != -1
break
endif
let l:lnum += a:direction
endwhile
return l:lnum - a:direction
endfunction
function! ingo#folds#NextClosedLine( lnum, direction )
"******************************************************************************
"* PURPOSE:
" Determine the line number of the next closed (i.e. folded) line.
"* ASSUMPTIONS / PRECONDITIONS:
" None.
"* EFFECTS / POSTCONDITIONS:
" None.
"* INPUTS:
" a:lnum Line number to base the calculation on. When this one is folded, it
" is returned.
" a:direction -1 for upward, 1 for downward relative movement
"* RETURN VALUES:
" line number, of -1 if there is no more folded line in that direction of the
" buffer.
"******************************************************************************
let l:lnum = a:lnum
while l:lnum > 0 && l:lnum <= line('$')
if foldclosed(l:lnum) != -1
return l:lnum
endif
let l:lnum += a:direction
endwhile
return -1
endfunction
function! ingo#folds#LastClosedLine( lnum, direction )
"******************************************************************************
"* PURPOSE:
" Determine the line number of the last closed (i.e. folded) line. Unlike
" foldclosedend(), considers multiple adjacent closed folds as one unit.
"* ASSUMPTIONS / PRECONDITIONS:
" None.
"* EFFECTS / POSTCONDITIONS:
" None.
"* INPUTS:
" a:lnum Line number to base the calculation on.
" a:direction -1 for upward, 1 for downward relative movement
"* RETURN VALUES:
" line number, of -1 if there is no more folded line in that direction of the
" buffer.
"******************************************************************************
let l:lnum = ingo#folds#NextClosedLine(a:lnum, a:direction)
if l:lnum == -1
return l:lnum
endif
while l:lnum > 0 && l:lnum <= line('$')
let l:borderLnum = (a:direction < 0 ? foldclosed(l:lnum) : foldclosedend(l:lnum))
if l:borderLnum == -1
break
endif
let l:lnum = l:borderLnum + a:direction
endwhile
return l:lnum - a:direction
endfunction
function! ingo#folds#GetClosedFolds( startLnum, endLnum )
"******************************************************************************
"* PURPOSE:
" Determine the ranges of closed folds within the passed range.
"* ASSUMPTIONS / PRECONDITIONS:
" None.
"* EFFECTS / POSTCONDITIONS:
" None.
"* INPUTS:
" a:startLnum First line of the range.
" a:endLnum Last line of the range.
"* RETURN VALUES:
" List of [foldStartLnum, foldEndLnum] elements.
"******************************************************************************
let l:folds = []
let l:lnum = a:startLnum
while l:lnum <= a:endLnum
let l:foldEndLnum = foldclosedend(l:lnum)
if l:foldEndLnum == -1
let l:lnum += 1
else
call add(l:folds, [l:lnum, l:foldEndLnum])
let l:lnum = l:foldEndLnum + 1
endif
endwhile
return l:folds
endfunction
function! ingo#folds#FoldedLines( startLine, endLine )
"******************************************************************************
"* PURPOSE:
" Determine the number of lines in the passed range that lie hidden in a
" closed fold; that is, everything but the first line of a closed fold.
"* ASSUMPTIONS / PRECONDITIONS:
" None.
"* EFFECTS / POSTCONDITIONS:
" None.
"* INPUTS:
" a:startLnum First line of the range.
" a:endLnum Last line of the range.
"* RETURN VALUES:
" Returns [ number of folds in range, number of folded away (i.e. invisible)
" lines ]. Sum both values to get the total number of lines in a fold in the
" passed range.
"******************************************************************************
let l:foldCnt = 0
let l:foldedAwayLines = 0
let l:line = a:startLine
while l:line < a:endLine
if foldclosed(l:line) == l:line
let l:foldCnt += 1
let l:foldend = foldclosedend(l:line)
let l:foldedAwayLines += (l:foldend > a:endLine ? a:endLine : l:foldend) - l:line
let l:line = l:foldend
endif
let l:line += 1
endwhile
return [ l:foldCnt, l:foldedAwayLines ]
endfunction
function! ingo#folds#GetOpenFoldRange( lnum )
"******************************************************************************
"* PURPOSE:
" Determine the range of the open fold around a:lnum.
"* ASSUMPTIONS / PRECONDITIONS:
" None.
"* EFFECTS / POSTCONDITIONS:
" None.
"* INPUTS:
" a:lnum Line number to be considered.
"* RETURN VALUES:
" [startLnum, endLnum] of the fold. If the line is fully in closed fold(s) or
" not inside a fold at all, returns the entire range of the buffer.
"******************************************************************************
if foldlevel(a:lnum) == 0
" No fold at that line.
return [1, line('$')]
endif
let l:save_view = winsaveview()
try
let [l:originalClosedStartLnum, l:originalClosedEndLnum] = [foldclosed(a:lnum), foldclosedend(a:lnum)]
execute a:lnum . 'foldclose'
let l:isAtBeginningOfCurrentFold = (foldclosed(a:lnum) == a:lnum)
if foldclosed(a:lnum) == l:originalClosedStartLnum && foldclosedend(a:lnum) == l:originalClosedEndLnum
" The :foldclose didn't have any noticeable effect; either the line
" is on a toplevel closed fold, or on an nested open, same-size fold
" (which we'll leave closed as a side effect).
else
execute a:lnum . 'foldopen'
endif
if l:isAtBeginningOfCurrentFold
" [z would jump to the beginning of the previous open fold, and
" we've already determined the start of the open fold, anyway.
let l:startLnum = a:lnum
else
silent! execute a:lnum . 'normal! [z'
let l:startLnum = line('.')
endif
silent! execute a:lnum . 'normal! ]z'
let l:endLnum = line('.')
if l:endLnum == l:startLnum
" The cursor didn't move; there's no open fold, so return the whole
" buffer.
return [1, line('$')]
endif
return [l:startLnum, l:endLnum]
finally
call winrestview(l:save_view)
endtry
endfunction
" vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax :