blob: 601aad89fd96631b012de3a660fc222415a15012 [file] [log] [blame]
Bram Moolenaarec7944a2013-06-12 21:29:15 +02001" Vim indent script for HTML
2" General: "{{{
3" File: html.vim (Vimscript #2075)
4" Author: Andy Wokula <anwoku@yahoo.de>
5" Last Change: 2013 Jun 12
Bram Moolenaar52b91d82013-06-15 21:39:51 +02006" Rev Days: 13
7" Version: 0.9
Bram Moolenaarec7944a2013-06-12 21:29:15 +02008" Vim Version: Vim7
9" Description:
10" Improved version of the distributed html indent script, faster on a
11" range of lines.
12"
13" Credits:
14" indent/html.vim (2006 Jun 05) from J. Zellner
15" indent/css.vim (2006 Dec 20) from N. Weibull
16"
17" History:
Bram Moolenaar52b91d82013-06-15 21:39:51 +020018" 2012 Oct 21 (v0.9) added support for shiftwidth()
19" 2011 Sep 09 (v0.8) added HTML5 tags (thx to J. Zuckerman)
20" 2008 Apr 28 (v0.6) revised customization
21" 2008 Mar 09 (v0.5) fixed 'indk' issue (thx to C.J. Robinson)
Bram Moolenaarec7944a2013-06-12 21:29:15 +020022" }}}
Bram Moolenaar071d4272004-06-13 20:20:40 +000023
Bram Moolenaarec7944a2013-06-12 21:29:15 +020024" Init Folklore, check user settings (2nd time ++) "{{{
Bram Moolenaar071d4272004-06-13 20:20:40 +000025if exists("b:did_indent")
26 finish
27endif
28let b:did_indent = 1
29
Bram Moolenaarec7944a2013-06-12 21:29:15 +020030setlocal indentexpr=HtmlIndent()
31setlocal indentkeys=o,O,<Return>,<>>,{,},!^F
Bram Moolenaar071d4272004-06-13 20:20:40 +000032
Bram Moolenaarec7944a2013-06-12 21:29:15 +020033let b:indent = {"lnum": -1}
34let b:undo_indent = "set inde< indk<| unlet b:indent"
Bram Moolenaar071d4272004-06-13 20:20:40 +000035
Bram Moolenaarec7944a2013-06-12 21:29:15 +020036" Load Once:
37if exists("*HtmlIndent")
38 call HtmlIndent_CheckUserSettings()
39 finish
Bram Moolenaar071d4272004-06-13 20:20:40 +000040endif
41
Bram Moolenaar52b91d82013-06-15 21:39:51 +020042" Patch 7.3.694
43if exists('*shiftwidth')
44 let s:ShiftWidth = function('shiftwidth')
45else
46 func! s:ShiftWidth()
47 return &shiftwidth
48 endfunc
49endif
50
Bram Moolenaar91170f82006-05-05 21:15:17 +000051let s:cpo_save = &cpo
Bram Moolenaar071d4272004-06-13 20:20:40 +000052set cpo-=C
Bram Moolenaarec7944a2013-06-12 21:29:15 +020053"}}}
Bram Moolenaar071d4272004-06-13 20:20:40 +000054
Bram Moolenaarec7944a2013-06-12 21:29:15 +020055func! HtmlIndent_CheckUserSettings() "{{{
56 if exists("g:html_indent_inctags")
57 call s:AddITags(split(g:html_indent_inctags, ","))
Bram Moolenaar071d4272004-06-13 20:20:40 +000058 endif
Bram Moolenaarec7944a2013-06-12 21:29:15 +020059 if exists("g:html_indent_autotags")
60 call s:RemoveITags(split(g:html_indent_autotags, ","))
Bram Moolenaar071d4272004-06-13 20:20:40 +000061 endif
62
Bram Moolenaarec7944a2013-06-12 21:29:15 +020063 let indone = {"zero": 0
64 \,"auto": "indent(prevnonblank(v:lnum-1))"
Bram Moolenaar52b91d82013-06-15 21:39:51 +020065 \,"inc": "b:indent.blocktagind + s:ShiftWidth()"}
Bram Moolenaarec7944a2013-06-12 21:29:15 +020066 if exists("g:html_indent_script1")
67 let s:js1indent = get(indone, g:html_indent_script1, indone.zero)
68 endif
69 if exists("g:html_indent_style1")
70 let s:css1indent = get(indone, g:html_indent_style1, indone.zero)
71 endif
72endfunc "}}}
Bram Moolenaar071d4272004-06-13 20:20:40 +000073
Bram Moolenaarec7944a2013-06-12 21:29:15 +020074" Init Script Vars "{{{
75let s:usestate = 1
76let s:css1indent = 0
77let s:js1indent = 0
78" not to be changed:
79let s:endtags = [0,0,0,0,0,0,0,0] " some places unused
80let s:newstate = {}
81let s:countonly = 0
82 "}}}
83func! s:AddITags(taglist) "{{{
84 for itag in a:taglist
85 let s:indent_tags[itag] = 1
86 let s:indent_tags['/'.itag] = -1
87 endfor
88endfunc "}}}
89func! s:AddBlockTag(tag, id, ...) "{{{
90 if !(a:id >= 2 && a:id < 2+len(s:endtags))
91 return
92 endif
93 let s:indent_tags[a:tag] = a:id
94 if a:0 == 0
95 let s:indent_tags['/'.a:tag] = -a:id
96 let s:endtags[a:id-2] = "</".a:tag.">"
97 else
98 let s:indent_tags[a:1] = -a:id
99 let s:endtags[a:id-2] = a:1
100 endif
101endfunc "}}}
102func! s:RemoveITags(taglist) "{{{
103 " remove itags (protect blocktags from being removed)
104 for itag in a:taglist
105 if !has_key(s:indent_tags, itag) || s:indent_tags[itag] != 1
106 continue
Bram Moolenaar91170f82006-05-05 21:15:17 +0000107 endif
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200108 unlet s:indent_tags[itag]
109 if itag =~ '^\w\+$'
110 unlet s:indent_tags["/".itag]
111 endif
112 endfor
113endfunc "}}}
114" Add Indent Tags: {{{
115if !exists("s:indent_tags")
116 let s:indent_tags = {}
117endif
118
119" old tags:
120call s:AddITags(['a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big',
121 \ 'blockquote', 'button', 'caption', 'center', 'cite', 'code', 'colgroup',
122 \ 'del', 'dfn', 'dir', 'div', 'dl', 'em', 'fieldset', 'font', 'form',
123 \ 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'i', 'iframe', 'ins', 'kbd',
124 \ 'label', 'legend', 'map', 'menu', 'noframes', 'noscript', 'object', 'ol',
125 \ 'optgroup', 'q', 's', 'samp', 'select', 'small', 'span', 'strong', 'sub',
126 \ 'sup', 'table', 'textarea', 'title', 'tt', 'u', 'ul', 'var', 'th', 'td',
127 \ 'tr', 'tfoot', 'thead'])
128
129" tags added 2011 Sep 09 (especially HTML5 tags):
130call s:AddITags(['area', 'article', 'aside', 'audio', 'bdi', 'canvas',
131 \ 'command', 'datalist', 'details', 'embed', 'figure', 'footer',
132 \ 'header', 'group', 'keygen', 'mark', 'math', 'meter', 'nav', 'output',
133 \ 'progress', 'ruby', 'section', 'svg', 'texture', 'time', 'video',
134 \ 'wbr', 'text'])
135
136"}}}
137" Add Block Tags: contain alien content "{{{
138call s:AddBlockTag('pre', 2)
139call s:AddBlockTag('script', 3)
140call s:AddBlockTag('style', 4)
141call s:AddBlockTag('<!--', 5, '-->')
142"}}}
143
144func! s:CountITags(...) "{{{
145
146 " relative indent steps for current line [unit &sw]:
147 let s:curind = 0
148 " relative indent steps for next line [unit &sw]:
149 let s:nextrel = 0
150
151 if a:0==0
152 let s:block = s:newstate.block
153 let tmpline = substitute(s:curline, '<\zs\/\=\w\+\>\|<!--\|-->', '\=s:CheckTag(submatch(0))', 'g')
154 if s:block == 3
155 let s:newstate.scripttype = s:GetScriptType(matchstr(tmpline, '\C.*<SCRIPT\>\zs[^>]*'))
156 endif
157 let s:newstate.block = s:block
158 else
159 let s:block = 0 " assume starting outside of a block
160 let s:countonly = 1 " don't change state
161 let tmpline = substitute(s:altline, '<\zs\/\=\w\+\>\|<!--\|-->', '\=s:CheckTag(submatch(0))', 'g')
162 let s:countonly = 0
163 endif
164endfunc "}}}
165func! s:CheckTag(itag) "{{{
166 " "tag" or "/tag" or "<!--" or "-->"
167 let ind = get(s:indent_tags, a:itag)
168 if ind == -1
169 " closing tag
170 if s:block != 0
171 " ignore itag within a block
172 return "foo"
173 endif
174 if s:nextrel == 0
175 let s:curind -= 1
176 else
177 let s:nextrel -= 1
178 endif
179 " if s:curind >= 1
180 " let s:curind -= 1
181 " else
182 " let s:nextrel -= 1
183 " endif
184 elseif ind == 1
185 " opening tag
186 if s:block != 0
187 return "foo"
188 endif
189 let s:nextrel += 1
190 elseif ind != 0
191 " block-tag (opening or closing)
192 return s:Blocktag(a:itag, ind)
193 endif
194 " else ind==0 (other tag found): keep indent
195 return "foo" " no matter
196endfunc "}}}
197func! s:Blocktag(blocktag, ind) "{{{
198 if a:ind > 0
199 " a block starts here
200 if s:block != 0
201 " already in a block (nesting) - ignore
202 " especially ignore comments after other blocktags
203 return "foo"
204 endif
205 let s:block = a:ind " block type
206 if s:countonly
207 return "foo"
208 endif
209 let s:newstate.blocklnr = v:lnum
210 " save allover indent for the endtag
Bram Moolenaar52b91d82013-06-15 21:39:51 +0200211 let s:newstate.blocktagind = b:indent.baseindent + (s:nextrel + s:curind) * s:ShiftWidth()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200212 if a:ind == 3
213 return "SCRIPT" " all except this must be lowercase
214 " line is to be checked again for the type attribute
215 endif
216 else
217 let s:block = 0
218 " we get here if starting and closing block-tag on same line
219 endif
220 return "foo"
221endfunc "}}}
222func! s:GetScriptType(str) "{{{
223 if a:str == "" || a:str =~ "java"
224 return "javascript"
225 else
226 return ""
227 endif
228endfunc "}}}
229
230func! s:FreshState(lnum) "{{{
231 " Look back in the file (lines 1 to a:lnum-1) to calc a state for line
232 " a:lnum. A state is to know ALL relevant details about the lines
233 " 1..a:lnum-1, initial calculating (here!) can be slow, but updating is
234 " fast (incremental).
235 " State:
236 " lnum last indented line == prevnonblank(a:lnum - 1)
237 " block = 0 a:lnum located within special tag: 0:none, 2:<pre>,
238 " 3:<script>, 4:<style>, 5:<!--
239 " baseindent use this indent for line a:lnum as a start - kind of
240 " autoindent (if block==0)
241 " scripttype = '' type attribute of a script tag (if block==3)
242 " blocktagind indent for current opening (get) and closing (set)
243 " blocktag (if block!=0)
244 " blocklnr lnum of starting blocktag (if block!=0)
245 " inattr line {lnum} starts with attributes of a tag
246 let state = {}
247 let state.lnum = prevnonblank(a:lnum - 1)
248 let state.scripttype = ""
249 let state.blocktagind = -1
250 let state.block = 0
251 let state.baseindent = 0
252 let state.blocklnr = 0
253 let state.inattr = 0
254
255 if state.lnum == 0
256 return state
257 endif
258
259 " Heuristic:
260 " remember startline state.lnum
261 " look back for <pre, </pre, <script, </script, <style, </style tags
262 " remember stopline
263 " if opening tag found,
264 " assume a:lnum within block
265 " else
266 " look back in result range (stopline, startline) for comment
267 " \ delimiters (<!--, -->)
268 " if comment opener found,
269 " assume a:lnum within comment
270 " else
271 " assume usual html for a:lnum
272 " if a:lnum-1 has a closing comment
273 " look back to get indent of comment opener
274 " FI
275
276 " look back for blocktag
277 call cursor(a:lnum, 1)
278 let [stopline, stopcol] = searchpos('\c<\zs\/\=\%(pre\>\|script\>\|style\>\)', "bW")
279 " fugly ... why isn't there searchstr()
280 let tagline = tolower(getline(stopline))
281 let blocktag = matchstr(tagline, '\/\=\%(pre\>\|script\>\|style\>\)', stopcol-1)
282 if stopline > 0 && blocktag[0] != "/"
283 " opening tag found, assume a:lnum within block
284 let state.block = s:indent_tags[blocktag]
285 if state.block == 3
286 let state.scripttype = s:GetScriptType(matchstr(tagline, '\>[^>]*', stopcol))
287 endif
288 let state.blocklnr = stopline
289 " check preceding tags in the line:
290 let s:altline = tagline[: stopcol-2]
291 call s:CountITags(1)
Bram Moolenaar52b91d82013-06-15 21:39:51 +0200292 let state.blocktagind = indent(stopline) + (s:curind + s:nextrel) * s:ShiftWidth()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200293 return state
294 elseif stopline == state.lnum
295 " handle special case: previous line (= state.lnum) contains a
296 " closing blocktag which is preceded by line-noise;
297 " blocktag == "/..."
298 let swendtag = match(tagline, '^\s*</') >= 0
299 if !swendtag
300 let [bline, bcol] = searchpos('<'.blocktag[1:].'\>', "bW")
301 let s:altline = tolower(getline(bline)[: bcol-2])
302 call s:CountITags(1)
Bram Moolenaar52b91d82013-06-15 21:39:51 +0200303 let state.baseindent = indent(bline) + (s:nextrel+s:curline) * s:ShiftWidth()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200304 return state
305 endif
306 endif
307
308 " else look back for comment
309 call cursor(a:lnum, 1)
310 let [comline, comcol, found] = searchpos('\(<!--\)\|-->', 'bpW', stopline)
311 if found == 2
312 " comment opener found, assume a:lnum within comment
313 let state.block = 5
314 let state.blocklnr = comline
315 " check preceding tags in the line:
316 let s:altline = tolower(getline(comline)[: comcol-2])
317 call s:CountITags(1)
Bram Moolenaar52b91d82013-06-15 21:39:51 +0200318 let state.blocktagind = indent(comline) + (s:curind + s:nextrel) * s:ShiftWidth()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200319 return state
320 endif
321
322 " else within usual html
323 let s:altline = tolower(getline(state.lnum))
324 " check a:lnum-1 for closing comment (we need indent from the opening line)
325 let comcol = stridx(s:altline, '-->')
326 if comcol >= 0
327 call cursor(state.lnum, comcol+1)
328 let [comline, comcol] = searchpos('<!--', 'bW')
329 if comline == state.lnum
330 let s:altline = s:altline[: comcol-2]
331 else
332 let s:altline = tolower(getline(comline)[: comcol-2])
333 endif
334 call s:CountITags(1)
Bram Moolenaar52b91d82013-06-15 21:39:51 +0200335 let state.baseindent = indent(comline) + (s:nextrel+s:curline) * s:ShiftWidth()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200336 return state
337 " TODO check tags that follow "-->"
338 endif
339
340 " else no comments
341 call s:CountITags(1)
Bram Moolenaar52b91d82013-06-15 21:39:51 +0200342 let state.baseindent = indent(state.lnum) + s:nextrel * s:ShiftWidth()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200343 " line starts with end tag
344 let swendtag = match(s:altline, '^\s*</') >= 0
345 if !swendtag
Bram Moolenaar52b91d82013-06-15 21:39:51 +0200346 let state.baseindent += s:curind * s:ShiftWidth()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200347 endif
348 return state
349endfunc "}}}
350
351func! s:Alien2() "{{{
352 " <pre> block
353 return -1
354endfunc "}}}
355func! s:Alien3() "{{{
356 " <script> javascript
357 if prevnonblank(v:lnum-1) == b:indent.blocklnr
358 " indent for the first line after <script>
359 return eval(s:js1indent)
360 endif
361 if b:indent.scripttype == "javascript"
362 return cindent(v:lnum)
363 else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000364 return -1
365 endif
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200366endfunc "}}}
367func! s:Alien4() "{{{
368 " <style>
369 if prevnonblank(v:lnum-1) == b:indent.blocklnr
370 " indent for first content line
371 return eval(s:css1indent)
372 endif
373 return s:CSSIndent()
374endfunc
Bram Moolenaar071d4272004-06-13 20:20:40 +0000375
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200376func! s:CSSIndent() "{{{
377 " adopted $VIMRUNTIME/indent/css.vim
378 if getline(v:lnum) =~ '^\s*[*}]'
379 return cindent(v:lnum)
380 endif
381 let minline = b:indent.blocklnr
382 let pnum = s:css_prevnoncomment(v:lnum - 1, minline)
383 if pnum <= minline
384 " < is to catch errors
385 " indent for first content line after comments
386 return eval(s:css1indent)
387 endif
Bram Moolenaar52b91d82013-06-15 21:39:51 +0200388 let ind = indent(pnum) + s:css_countbraces(pnum, 1) * s:ShiftWidth()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200389 let pline = getline(pnum)
390 if pline =~ '}\s*$'
Bram Moolenaar52b91d82013-06-15 21:39:51 +0200391 let ind -= (s:css_countbraces(pnum, 0) - (pline =~ '^\s*}')) * s:ShiftWidth()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200392 endif
393 return ind
394endfunc "}}}
395func! s:css_prevnoncomment(lnum, stopline) "{{{
396 " caller starts from a line a:lnum-1 that is not a comment
397 let lnum = prevnonblank(a:lnum)
398 let ccol = match(getline(lnum), '\*/')
399 if ccol < 0
400 return lnum
401 endif
402 call cursor(lnum, ccol+1)
403 let lnum = search('/\*', 'bW', a:stopline)
404 if indent(".") == virtcol(".")-1
405 return prevnonblank(lnum-1)
406 else
407 return lnum
408 endif
409endfunc "}}}
410func! s:css_countbraces(lnum, count_open) "{{{
411 let brs = substitute(getline(a:lnum),'[''"].\{-}[''"]\|/\*.\{-}\*/\|/\*.*$\|[^{}]','','g')
412 let n_open = 0
413 let n_close = 0
414 for brace in split(brs, '\zs')
415 if brace == "{"
416 let n_open += 1
417 elseif brace == "}"
418 if n_open > 0
419 let n_open -= 1
420 else
421 let n_close += 1
Bram Moolenaar91170f82006-05-05 21:15:17 +0000422 endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000423 endif
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200424 endfor
425 return a:count_open ? n_open : n_close
426endfunc "}}}
427
428"}}}
429func! s:Alien5() "{{{
430 " <!-- -->
431 return -1
432endfunc "}}}
433
434func! HtmlIndent() "{{{
435 let s:curline = tolower(getline(v:lnum))
Bram Moolenaar52b91d82013-06-15 21:39:51 +0200436 let indentunit = s:ShiftWidth()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200437
438 let s:newstate = {}
439 let s:newstate.lnum = v:lnum
440
Bram Moolenaar52b91d82013-06-15 21:39:51 +0200441 " does the line start with a closing tag?
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200442 let swendtag = match(s:curline, '^\s*</') >= 0
443
444 if prevnonblank(v:lnum-1) == b:indent.lnum && s:usestate
445 " use state (continue from previous line)
446 else
447 " start over (know nothing)
448 let b:indent = s:FreshState(v:lnum)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000449 endif
450
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200451 if b:indent.block >= 2
452 " within block
453 let endtag = s:endtags[b:indent.block-2]
454 let blockend = stridx(s:curline, endtag)
455 if blockend >= 0
456 " block ends here
457 let s:newstate.block = 0
458 " calc indent for REST OF LINE (may start more blocks):
459 let s:curline = strpart(s:curline, blockend+strlen(endtag))
460 call s:CountITags()
461 if swendtag && b:indent.block != 5
Bram Moolenaar52b91d82013-06-15 21:39:51 +0200462 let indent = b:indent.blocktagind + s:curind * indentunit
463 let s:newstate.baseindent = indent + s:nextrel * indentunit
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200464 else
465 let indent = s:Alien{b:indent.block}()
Bram Moolenaar52b91d82013-06-15 21:39:51 +0200466 let s:newstate.baseindent = b:indent.blocktagind + s:nextrel * indentunit
Bram Moolenaar91170f82006-05-05 21:15:17 +0000467 endif
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200468 call extend(b:indent, s:newstate, "force")
469 return indent
470 else
471 " block continues
472 " indent this line with alien method
473 let indent = s:Alien{b:indent.block}()
474 call extend(b:indent, s:newstate, "force")
475 return indent
Bram Moolenaar071d4272004-06-13 20:20:40 +0000476 endif
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200477 else
478 " not within a block - within usual html
479 " if < 2 then always 0
480 let s:newstate.block = b:indent.block
481 call s:CountITags()
482 if swendtag
Bram Moolenaar52b91d82013-06-15 21:39:51 +0200483 let indent = b:indent.baseindent + s:curind * indentunit
484 let s:newstate.baseindent = indent + s:nextrel * indentunit
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200485 else
486 let indent = b:indent.baseindent
Bram Moolenaar52b91d82013-06-15 21:39:51 +0200487 let s:newstate.baseindent = indent + (s:curind + s:nextrel) * indentunit
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200488 endif
489 call extend(b:indent, s:newstate, "force")
490 return indent
Bram Moolenaar071d4272004-06-13 20:20:40 +0000491 endif
492
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200493endfunc "}}}
Bram Moolenaar071d4272004-06-13 20:20:40 +0000494
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200495" check user settings (first time), clear cpo, Modeline: {{{1
Bram Moolenaar071d4272004-06-13 20:20:40 +0000496
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200497" DEBUG:
498com! -nargs=* IndHtmlLocal <args>
499
500call HtmlIndent_CheckUserSettings()
Bram Moolenaar071d4272004-06-13 20:20:40 +0000501
Bram Moolenaar91170f82006-05-05 21:15:17 +0000502let &cpo = s:cpo_save
503unlet s:cpo_save
504
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200505" vim:set fdm=marker ts=8: