" Vim indent file
" Language: Mako
" Author: Scott Torborg <storborg@mit.edu>
" Version: 0.4
" License: Do What The Fuck You Want To Public License (WTFPL)
"
" ---------------------------------------------------------------------------
"
"            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
"                    Version 2, December 2004
"
" Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
"
" Everyone is permitted to copy and distribute verbatim or modified
" copies of this license document, and changing it is allowed as long
" as the name is changed.
"
"            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
"   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
"
"  0. You just DO WHAT THE FUCK YOU WANT TO.
"
" ---------------------------------------------------------------------------
"
" This script does more useful indenting for Mako HTML templates. It indents
" inside of control blocks, defs, etc. Note that this indenting style will
" sacrifice readability of the output text for the sake of readability of the
" template.
"
" We'll use HTML indenting globally, python inside <% %> blocks. Inspired by
" the excellent PHP + HTML indentation files such as php.vim by Pim Snel.
"
" Changelog:
"       0.4 - 5 March 2010
"       - Added license information
"       0.3 - 15 September 2009
"       - Added explicit indenting for ## comments, fixed unindenting count,
"       thanks to Mike Lewis (@MikeRLewis) for this
"       0.2 - 15 June 2009
"       - Fixed issue where opening and closing mako tags on the same line
"       would cause incorrect indenting
"       0.1 - 06 June 2009
"       - Initial public release of mako indent file

let sw=2    " default shiftwidth of 2 spaces

if exists("b:did_indent")
    finish
endif
let b:did_indent = 1

setlocal nosmartindent
setlocal noautoindent
setlocal nocindent
setlocal nolisp

setlocal indentexpr=GetMakoIndent()
setlocal indentkeys+=*<Return>,<>>,<bs>,end,:

" Only define the function once.
if exists("*GetMakoIndent")
    finish
endif

if exists('g:html_indent_tags')
    unlet g:html_indent_tags
endif

function IsInsidePythonBlock(startline)
    " Loop until we get a line that's either <% or %>
    let lnum = a:startline
    while getline(lnum) !~ '\(%>\|<%\)$' && lnum > 0
        let lnum = lnum - 1
    endwhile

    " lnum points to the last control. If it's a <% then we're inside an
    " embedded python block, otherwise we're not.
    return getline(lnum) =~ '<%$'
endfunction

function GetMakoIndent()
    " Find a non-empty line above the current line
    let lnum = prevnonblank(v:lnum - 1)
    
    " Hit the start of the file, use zero indent.
    if lnum == 0
        return 0
    endif
    
    let line = getline(lnum)        " last line
    let cline = getline(v:lnum)     " current line
    let pline = getline(lnum - 1)   " previous to last line
    let ind = indent(lnum)
	if line =~ '^\s*##'
		return indent(lnum)
	end
    
    let restore_ic=&ic
    let &ic=1 " ignore case
    
    let ind = <SID>HtmlIndentSum(lnum, -1)
	let ind = <SID>HtmlIndentSum(lnum, -1)
	let ind = ind + <SID>HtmlIndentSum(v:lnum, 0)
    
	let &ic=restore_ic
	
	let ind = indent(lnum) + (&sw * ind)
    
    " Indent after %anything: or <%anything NOT ending in />
    if line =~ '^\s*%.*:\s*$'
        let ind = ind + &sw
    endif
    
    " Unindent before %end* or </%anything
    if cline =~ '^\s*%\s*end'
        let ind = ind - &sw
    endif
    "
    " Unindent before %else, %except, and %elif
    if cline =~ '^\s*%\s*else' || cline =~ '^\s*%\s*except' || cline =~ '^\s*%\s*elif'
        let ind = ind - &sw
    endif

    " Indent at the beginning of a python control block
    if line =~ '<%$'
        let ind = ind + &sw
    endif
    "
    " Unindent at the end of the python block.
    if cline =~ '^\s*%>$'
        let scanlnum = lnum
        " Scan backwards until we find the beginning of this python block.
        while getline(scanlnum) !~ '<%$' && scanlnum > 0
            let scanlnum = scanlnum - 1
        endwhile
        let ind = indent(scanlnum)
    endif

    " If we're inside a python block and the previous line ends in a colon,
    " indent.
    if IsInsidePythonBlock(lnum - 1)
        " Indent after :
        if line =~ '\:$'
            let ind = ind + &sw
        endif
    endif
    
    return ind
endfunction


" [-- helper function to assemble tag list --]
fun! <SID>HtmlIndentPush(tag)
    if exists('g:html_indent_tags')
        let g:html_indent_tags = g:html_indent_tags.'\|'.a:tag
    else
        let g:html_indent_tags = a:tag
    endif
endfun

fun! <SID>MakoIndentPush(tag)
    if exists('g:mako_indent_tags')
        let g:mako_indent_tags = g:mako_indent_tags.'\|'.a:tag
    else
        let g:mako_indent_tags = a:tag
    endif
endfun

" [-- <ELEMENT ? - - ...> --]
call <SID>HtmlIndentPush('a')
call <SID>HtmlIndentPush('abbr')
call <SID>HtmlIndentPush('acronym')
call <SID>HtmlIndentPush('address')
call <SID>HtmlIndentPush('b')
call <SID>HtmlIndentPush('bdo')
call <SID>HtmlIndentPush('big')
call <SID>HtmlIndentPush('blockquote')
call <SID>HtmlIndentPush('button')
call <SID>HtmlIndentPush('caption')
call <SID>HtmlIndentPush('center')
call <SID>HtmlIndentPush('cite')
call <SID>HtmlIndentPush('code')
call <SID>HtmlIndentPush('colgroup')
call <SID>HtmlIndentPush('del')
call <SID>HtmlIndentPush('dfn')
call <SID>HtmlIndentPush('dir')
call <SID>HtmlIndentPush('div')
call <SID>HtmlIndentPush('dl')
call <SID>HtmlIndentPush('em')
call <SID>HtmlIndentPush('fieldset')
call <SID>HtmlIndentPush('font')
call <SID>HtmlIndentPush('form')
call <SID>HtmlIndentPush('frameset')
call <SID>HtmlIndentPush('h1')
call <SID>HtmlIndentPush('h2')
call <SID>HtmlIndentPush('h3')
call <SID>HtmlIndentPush('h4')
call <SID>HtmlIndentPush('h5')
call <SID>HtmlIndentPush('h6')
call <SID>HtmlIndentPush('i')
call <SID>HtmlIndentPush('iframe')
call <SID>HtmlIndentPush('ins')
call <SID>HtmlIndentPush('kbd')
call <SID>HtmlIndentPush('label')
call <SID>HtmlIndentPush('legend')
call <SID>HtmlIndentPush('map')
call <SID>HtmlIndentPush('menu')
call <SID>HtmlIndentPush('noframes')
call <SID>HtmlIndentPush('noscript')
call <SID>HtmlIndentPush('object')
call <SID>HtmlIndentPush('ol')
call <SID>HtmlIndentPush('optgroup')
call <SID>HtmlIndentPush('pre')
call <SID>HtmlIndentPush('q')
call <SID>HtmlIndentPush('s')
call <SID>HtmlIndentPush('samp')
call <SID>HtmlIndentPush('script')
call <SID>HtmlIndentPush('select')
call <SID>HtmlIndentPush('small')
call <SID>HtmlIndentPush('span')
call <SID>HtmlIndentPush('strong')
call <SID>HtmlIndentPush('style')
call <SID>HtmlIndentPush('sub')
call <SID>HtmlIndentPush('sup')
call <SID>HtmlIndentPush('table')
call <SID>HtmlIndentPush('textarea')
call <SID>HtmlIndentPush('title')
call <SID>HtmlIndentPush('tt')
call <SID>HtmlIndentPush('u')
call <SID>HtmlIndentPush('ul')
call <SID>HtmlIndentPush('var')

" For some reason the default HTML indentation script doesn't consider these
" elements to be worthy of indentation.
call <SID>HtmlIndentPush('p')
call <SID>HtmlIndentPush('dt')
call <SID>HtmlIndentPush('dd')


" [-- <ELEMENT ? O O ...> --]
if !exists('g:html_indent_strict')
    call <SID>HtmlIndentPush('body')
    call <SID>HtmlIndentPush('head')
    call <SID>HtmlIndentPush('html')
    call <SID>HtmlIndentPush('tbody')
endif


" [-- <ELEMENT ? O - ...> --]
if !exists('g:html_indent_strict_table')
    call <SID>HtmlIndentPush('th')
    call <SID>HtmlIndentPush('td')
    call <SID>HtmlIndentPush('tr')
    call <SID>HtmlIndentPush('tfoot')
    call <SID>HtmlIndentPush('thead')
endif

" [-- <Mako Elements> --]
call <SID>MakoIndentPush('%def')
call <SID>MakoIndentPush('%block')
call <SID>MakoIndentPush('%call')
call <SID>MakoIndentPush('%doc')
call <SID>MakoIndentPush('%text')
call <SID>MakoIndentPush('%.\+:.\+')

delfun <SID>HtmlIndentPush
delfun <SID>MakoIndentPush

set cpo-=C

" [-- get number of regex matches in a string --]
fun! <SID>MatchCount(expr, pat)
    let mpos = 0
    let mcount = 0
    let expr = a:expr
    while (mpos > -1)
        let mend = matchend(expr, a:pat)
        if mend > -1
            let mcount = mcount + 1
        endif
        if mend == mpos
            let mpos = mpos + 1
        else
            let mpos = mend
        endif
        let expr = strpart(expr, mpos)
    endwhile
    return mcount
endfun

" [-- count indent-increasing tags of line a:lnum --]
fun! <SID>HtmlIndentOpen(lnum)
    let s = substitute('x'.getline(a:lnum),
    \ '.\{-}\(\(<\)\('.g:html_indent_tags.'\)\>\)', "\1", 'g')
    let s = substitute(s, "[^\1].*$", '', '')
    return strlen(s)
endfun

" [-- count indent-decreasing tags of line a:lnum --]
fun! <SID>HtmlIndentClose(lnum)
    let s = substitute('x'.getline(a:lnum),
    \ '.\{-}\(\(<\)/\('.g:html_indent_tags.'\)\>>\)', "\1", 'g')
    let s = substitute(s, "[^\1].*$", '', '')
    return strlen(s)
endfun

" [-- count indent-increasing mako tags of line a:lnum --]
fun! <SID>MakoIndentOpen(lnum)
    let s = substitute('x'.getline(a:lnum),
    \ '.\{-}\(\(<\)\('.g:mako_indent_tags.'\)\>\)', "\1", 'g')
    let s = substitute(s, "[^\1].*$", '', '')
    return strlen(s)
endfun

" [-- count indent-decreasing mako tags of line a:lnum --]
fun! <SID>MakoIndentClose(lnum)
    let mcount = <SID>MatchCount(getline(a:lnum), '</\('.g:mako_indent_tags.'\)>')
    let mcount = mcount + <SID>MatchCount(getline(a:lnum), '<\('.g:mako_indent_tags.'\)[^>]*/>')
    return mcount
endfun

" [-- count indent-increasing '{' of (java|css) line a:lnum --]
fun! <SID>HtmlIndentOpenAlt(lnum)
    return strlen(substitute(getline(a:lnum), '[^{]\+', '', 'g'))
endfun

" [-- count indent-decreasing '}' of (java|css) line a:lnum --]
fun! <SID>HtmlIndentCloseAlt(lnum)
    return strlen(substitute(getline(a:lnum), '[^}]\+', '', 'g'))
endfun

" [-- return the sum of indents respecting the syntax of a:lnum --]
fun! <SID>HtmlIndentSum(lnum, style)
    let open = <SID>HtmlIndentOpen(a:lnum) + <SID>MakoIndentOpen(a:lnum)
    let close = <SID>HtmlIndentClose(a:lnum) + <SID>MakoIndentClose(a:lnum)
    if a:style == match(getline(a:lnum), '^\s*</')
        if a:style == match(getline(a:lnum), '^\s*</\('.g:html_indent_tags.'\|'.g:mako_indent_tags.'\)')
            if 0 != open || 0 != close
                return open - close
            endif
        endif
    endif
    if '' != &syntax &&
	\ synIDattr(synID(a:lnum, 1, 1), 'name') =~ '\(css\|java\).*' &&
	\ synIDattr(synID(a:lnum, strlen(getline(a:lnum)) - 1, 1), 'name')
	\ =~ '\(css\|java\).*'
	if a:style == match(getline(a:lnum), '^\s*}')
	    return <SID>HtmlIndentOpenAlt(a:lnum) - <SID>HtmlIndentCloseAlt(a:lnum)
	endif
    endif
    return 0
endfun

" vim: set ts=4 sw=4: