Created
December 23, 2011 18:04
-
-
Save ndreas/1514948 to your computer and use it in GitHub Desktop.
Homecooked Vim indent script for HTML
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if exists("b:did_indent") | |
finish | |
endif | |
let b:did_indent = 1 | |
setlocal indentexpr=HtmlIndentGet(v:lnum) | |
setlocal indentkeys=o,O,*<Return>,<>>,<<>,/,{,} | |
if exists('*HtmlIndentGet') | finish | endif | |
" | |
" Loads an external indent file and returns the indentexpression | |
fun! s:LoadExternalIndent(type) | |
unlet! b:did_indent | |
exe "runtime! indent/" . a:type . ".vim" | |
let indexp = &l:indentexpr | |
setlocal indentexpr=HtmlIndentGet(v:lnum) | |
return indexp | |
endfun | |
" Tags with forbidden end tags | |
let s:forbidden_end_tag_regex = '\<\(area\|base\|basefont\|br\|col\|frame\|hr\|img\|input\|isindex\|link\|meta\|param\)\>' | |
" Tags with optional end tags | |
" let s:optional_end_tag_regex = '\<\(body\|colgroup\|dd\|dt\|head\|html\|li\|option\|p\|tbody\|td\|tfoot\|th\|thead\|tr\)\>' | |
" Tags with special end tags (that do not count as indent contributions) | |
let s:special_end_tag_regex = '/\(pre\|script\|style\)\>' | |
" Checks if the character on the given line and column is an HTML bracket | |
fun! s:IsHtmlBracket(line, col) | |
" The syntax name of a bracket is htmlTag or htmlEndTag | |
return synIDattr(synID(a:line, a:col, 0), 'name') =~ 'html\(End\)\?Tag' | |
endfun | |
" Counts the number of tags contributing to the indent | |
fun! s:CountContributingTags(lnum, count_start) | |
if a:count_start | |
" Match start tags | |
let pat = '<\a\+' | |
else | |
" Match end tags | |
let pat = '</\a\+' | |
end | |
let line = getline(a:lnum) | |
" Number of tags contributing to the indent | |
let nvalid = 0 | |
" The current tag to be processed | |
let curhit = 1 | |
let hit = matchstr(line, pat) | |
while hit != "" | |
if s:IsHtmlBracket(a:lnum, match(line, pat, 0, curhit) + 1) | |
if a:count_start | |
"if hit =~? s:optional_end_tag_regex | |
" " Check if the tag has an end tag | |
" if searchpairpos(hit . '\a\@!', '', '</' . strpart(hit, 1) . '\a\@!', 'nW') != [0, 0] | |
" let nvalid += 1 | |
" endif | |
if hit !~? s:forbidden_end_tag_regex | |
" Do not count tags without end tags | |
let nvalid += 1 | |
endif | |
elseif hit !~? s:special_end_tag_regex | |
" Filter special end tags | |
let nvalid += 1 | |
end | |
endif | |
let curhit += 1 | |
let hit = matchstr(line, pat, 0, curhit) | |
endwhile | |
return nvalid | |
endfun | |
" Returns the balance of HTML brackets on the given line | |
" The balance is negative if there are more closing brackets than opening, | |
" and positive if there are more opening brackets | |
fun! s:GetBracketBalance(lnum) | |
let balance = 0 | |
let line = getline(a:lnum) | |
" Match all brackets | |
let idx = match(line, '[<>]') | |
while idx != -1 | |
if s:IsHtmlBracket(a:lnum, idx + 1) | |
if line[idx] == '<' | |
let balance += 1 | |
elseif line[idx] == '>' | |
let balance -= 1 | |
endif | |
endif | |
let idx = match(line, '[<>]', idx + 1) | |
endwhile | |
return balance | |
endfun | |
" Returns the indent for aligning attributes when a tag on the given line is | |
" unclosed | |
fun! s:GetAttributeIndent(lnum) | |
let indent = 0 | |
let line = getline(a:lnum) | |
" Find the tag start (the last opening html bracket) | |
let idx = strridx(line, '<') | |
while !s:IsHtmlBracket(a:lnum, idx + 1) | |
let idx = strridx(line, '<', idx - 1) | |
endwhile | |
" tag start length of tag name space after tag | |
return idx + strlen(matchstr(line, '<\a\+', idx)) + 1 | |
endfun | |
" Returns the line that starts a tag that closes on the given line | |
fun! s:GetTagStartLine(lnum) | |
let line = getline(a:lnum) | |
" Find the tag end (the first closing html bracket) | |
let idx = stridx(line, '>') | |
while !s:IsHtmlBracket(a:lnum, idx + 1) | |
let idx = stridx(line, '>', idx + 1) | |
endwhile | |
" Store the current position for later restoring | |
let cline = line('.') | |
let ccol = col('.') | |
" Move inside the unclosed tag | |
call cursor(a:lnum, 1) | |
" Search for the start | |
let [start_lnum, start_col] = searchpairpos('<', '', '>', 'nWb', | |
\ 'synIDattr(synID(line("."), col("."), 0), "name") !~? "html\\(End\\)\\?Tag"') | |
call cursor(cline, ccol) | |
return start_lnum | |
endfun | |
fun! HtmlIndentGet(lnum) | |
" No indent in pre tags | |
if getline(a:lnum) =~ '\c</pre>' | |
\ || 0 < searchpair('\c<pre>', '', '\c</pre>', 'nWb') | |
\ || 0 < searchpair('\c<pre>', '', '\c</pre>', 'nW') | |
return -1 | |
endif | |
" Javascript indentation | |
if synIDattr(synID(a:lnum, 1, 1), 'name') =~ 'java.*' && | |
\ synIDattr(synID(a:lnum, strlen(getline(a:lnum)), 1), 'name') | |
\ =~ 'java.*' | |
if !exists('b:javascript_indentexpr') | |
let b:javascript_indentexpr = <SID>LoadExternalIndent("javascript") | |
endif | |
exe "let ind = " . b:javascript_indentexpr | |
return ind | |
endif | |
" CSS indentation | |
if synIDattr(synID(a:lnum, 1, 1), 'name') =~ 'css.*' && | |
\ synIDattr(synID(a:lnum, strlen(getline(a:lnum)), 1), 'name') | |
\ =~ 'css.*' | |
if !exists('b:css_indentexpr') | |
let b:css_indentexpr = <SID>LoadExternalIndent("css") | |
endif | |
exe "let ind = " . b:css_indentexpr | |
return ind | |
endif | |
let plnum = prevnonblank(a:lnum - 1) | |
" First line | |
if plnum == 0 | return 0 | endif | |
let bb = s:GetBracketBalance(plnum) | |
" Tags on the current line can only remove indent | |
let ccnt = s:CountContributingTags(a:lnum, 0) | |
let ccnt -= s:CountContributingTags(a:lnum, 1) | |
if ccnt < 0 | let ccnt = 0 | endif | |
if bb > 0 | |
" The line above starts an unclosed tag, so the indent should align the | |
" attributes | |
return s:GetAttributeIndent(plnum) - (&sw * ccnt) | |
else | |
let pscnt = 0 | |
let baseindent = indent(plnum) | |
" Tags on the line above can only add indent | |
let pcnt = s:CountContributingTags(plnum, 1) | |
let pcnt -= s:CountContributingTags(plnum, 0) | |
if pcnt < 0 | let pcnt = 0 | endif | |
if bb < 0 | |
" The line above closes a previously unclosed | |
let pslnum = s:GetTagStartLine(plnum) | |
" Tags on lines above can only add indent | |
let pscnt = s:CountContributingTags(pslnum, 1) | |
let pscnt -= s:CountContributingTags(pslnum, 0) | |
if pscnt < 0 | let pscnt = 0 | endif | |
let baseindent = indent(pslnum) | |
endif | |
return baseindent + (&sw * ( pscnt + pcnt - ccnt )) | |
endif | |
return -1 | |
endfun |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment