vim/ftplugin/lisp/lispfolding.vim @ f13a2b1ddedf

Do some horrifying things with lisp folding
author Steve Losh <steve@stevelosh.com>
date Fri, 15 Apr 2016 18:06:03 +0000
parents 70a831232f0f
children 08965030b28b
" if exists('loaded_lispfolding') || &cp
"     finish
" endif
let loaded_lispfolding=1

let s:lispfold_flet_re = '\vlabels|flet'

function! LispFoldingFormIsFlet()
    " Return whether the form the cursor is on is a fletlike.
    let old_z = @z

    " Yank the next word.
    normal! l
    silent normal! "zyiw
    let word = @z

    let @z = old_z

    " See if that next word is a fletlike thing.
    if word =~ s:lispfold_flet_re
        return 1
    else
        return 0
    endif
endfunction

function! LispFoldingStartFlet(lnum)
    " Return 1 when the given line is the start of a multi-line flet'ed
    " function definition.  We want to fold those.  Return -1 if it's
    " a single-line fletted definition.  Return 0 if it's neither.
    "
    " Relies on things being indented correctly to help the speed.
    "
    " Basically this thing is a total shitshow, turn back now.
    "
    " TODO: the function definitions have to be indented, fix that
    let l = getline(a:lnum)
    let save_cursor = getpos('.')

    try
        " A foldable flet looks like this:
        "
        " (flet
        "     ((foo ()
        "        ...)
        "      (bar ()
        "        ...))
        "   body)
        "

        " Make sure the cursor's on the current line.
        call setpos('.', [0, a:lnum, 1, 1])

        " Check if the line starts with ( or ((, and move to the appropriate
        " "start of the function form" character.
        if l =~ '\v^\s\s\s\s+\(\(\k+( \(|$)'
            normal! ^l
        elseif l =~ '\v^\s\s\s\s\s+\(\k+( \(|$)'
            normal! ^
        else
            return 0
        endif

        let save_start = getpos('.')

        " Pop up two levels in the paren stack.
        " TODO: make sure we actually do, not that it matters in practice
        call searchpair('(', '', ')', 'b')
        call searchpair('(', '', ')', 'b')

        if !LispFoldingFormIsFlet()
            return 0
        endif

        " We know this is a fletthing, but if it only spans one line, bail.
        call setpos('.', save_start)
        if searchpairpos('(', '', ')')[0] == a:lnum
            return -1
        end

        " congrats, u made it
        return 1
    finally
        call setpos('.', save_cursor)
    endtry
endfunction

function! LispFoldingEndFlet(lnum)
    " Return whether we're at the last line of a multi-line fletted function.
    let l = getline(a:lnum)
    let save_cursor = getpos('.')

    try
        if l =~ '\v\)\)$'
            call setpos('.', [0, a:lnum, len(l) - 1, 1])
            let start_line = searchpairpos('(', '', ')', 'b')[0]

            let r = LispFoldingStartFlet(start_line)
            if r == 1
                return 1
            elseif r == -1
                return 0
            endif
        endif

        if l =~ '\v\)$'
            call setpos('.', [0, a:lnum, len(l), 1])
            let start_line = searchpairpos('(', '', ')', 'b')[0]

            if LispFoldingStartFlet(start_line) == 1
                return 1
            endif
        endif

        return 0
    finally
        call setpos('.', save_cursor)
    endtry
endfunction

function! GetLispFold(lnum)
    let inline_fn_comment_re = '\v^ *;;( .*)?$'

    if getline(a:lnum) =~ '^;;;; '
        " Never fold top-level header comments
        return "0"
    elseif getline(a:lnum) =~ '^;;; '
        " Subheader top level comments should get folded together in level 1
        return "1"
    elseif getline(a:lnum) =~ inline_fn_comment_re
        " Inline function comments should increment the fold level
        let prev = getline(a:lnum - 1) =~ inline_fn_comment_re
        let next = getline(a:lnum + 1) =~ inline_fn_comment_re

        if (!prev) && next
            return "a1"
        elseif prev && (!next)
            return "s1"
        else
            return "="
        endif
    elseif getline(a:lnum) =~ '^; '
        " don't include commentary-commented lines in deeper folds than necessary
        return "-1"
    elseif getline(a:lnum) =~ '^(test '
        " (test ...) folds at the top level
        return ">1"
    elseif getline(a:lnum) =~ '^(def\S\+ '
        " fuck it just fold everything that looks kinda deffy
        return ">1"
    elseif getline(a:lnum) =~ '^$' && getline(a:lnum - 1) =~ '^$'
        return "0"
    elseif getline(a:lnum) =~ '^$'
        " Single blank lines fold along with the previous line, so that the
        " blank line after a defun gets folded into the defun.
        return "="
    elseif LispFoldingStartFlet(a:lnum) == 1
        " if this is a function definition in a labels/flet/etc, we want to
        " start a new deeper fold
        return ">2"
    elseif LispFoldingEndFlet(a:lnum)
        " if this is the END of a flet definition, we should pop a foldlevel
        return "<2"
    else
        return "="
    endif
endfunction

function! TurnOnLispFolding()
    setlocal foldexpr=GetLispFold(v:lnum)
    setlocal foldmethod=expr
    " nnoremap <buffer> qq :echo GetLispFold(getpos('.')[1])<cr>
endfunction