vim/ftplugin/lisp/lispfolding.vim @ 54633d8bf6d8

Minor lisp tweaks
author Steve Losh <steve@stevelosh.com>
date Thu, 30 Jun 2016 13:06:28 +0000
parents 774253fee5f6
children 006b6ec5166f
" 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 save_cursor = getpos('.')
    let save_search = @/
    let inline_fn_comment_re = '\v^ *;;( .*)?$'

    try
        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) =~ '^(declaim\( \|$\)'
            " fold top level declaims
            return ">1"
        elseif getline(a:lnum) =~ '^(eval-when\( \|$\)'
            " fold top level eval-when
            return ">1"
        elseif getline(a:lnum) =~ '^(def'
            " 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
    finally
        call setpos('.', save_cursor)
        let @/ = save_search
    endtry
endfunction

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