Skip to content

Instantly share code, notes, and snippets.

@ndreas
Created December 23, 2011 18:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ndreas/1514948 to your computer and use it in GitHub Desktop.
Save ndreas/1514948 to your computer and use it in GitHub Desktop.
Homecooked Vim indent script for HTML
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