blob: 37697841fddddf872f221ca093783d873d984a01 [file] [log] [blame]
Bram Moolenaarec7944a2013-06-12 21:29:15 +02001" Vim indent script for HTML
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +02002" Header: "{{{
3" Maintainer: Bram Moolenaar
4" Original Author: Andy Wokula <anwoku@yahoo.de>
Bram Moolenaar3ec574f2017-06-13 18:12:01 +02005" Last Change: 2017 Jun 13
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +02006" Version: 1.0
7" Description: HTML indent script with cached state for faster indenting on a
8" range of lines.
9" Supports template systems through hooks.
10" Supports Closure stylesheets.
Bram Moolenaarec7944a2013-06-12 21:29:15 +020011"
12" Credits:
13" indent/html.vim (2006 Jun 05) from J. Zellner
14" indent/css.vim (2006 Dec 20) from N. Weibull
15"
16" History:
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +020017" 2014 June (v1.0) overhaul (Bram)
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 Moolenaar8bb1c3e2014-07-04 16:43:17 +020022"}}}
Bram Moolenaar071d4272004-06-13 20:20:40 +000023
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +020024" Init Folklore, check user settings (2nd time ++)
25if exists("b:did_indent") "{{{
26 finish
Bram Moolenaar071d4272004-06-13 20:20:40 +000027endif
Bram Moolenaar690afe12017-01-28 18:34:47 +010028
29" Load the Javascript indent script first, it defines GetJavascriptIndent().
30" Undo the rest.
31" Load base python indent.
32if !exists('*GetJavascriptIndent')
33 runtime! indent/javascript.vim
34endif
Bram Moolenaar071d4272004-06-13 20:20:40 +000035let b:did_indent = 1
36
Bram Moolenaarec7944a2013-06-12 21:29:15 +020037setlocal indentexpr=HtmlIndent()
38setlocal indentkeys=o,O,<Return>,<>>,{,},!^F
Bram Moolenaar071d4272004-06-13 20:20:40 +000039
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +020040" Needed for % to work when finding start/end of a tag.
Bram Moolenaar946e27a2014-06-25 18:50:27 +020041setlocal matchpairs+=<:>
42
Bram Moolenaar690afe12017-01-28 18:34:47 +010043let b:undo_indent = "setlocal inde< indk<"
Bram Moolenaar071d4272004-06-13 20:20:40 +000044
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +020045" b:hi_indent keeps state to speed up indenting consecutive lines.
46let b:hi_indent = {"lnum": -1}
47
48"""""" Code below this is loaded only once. """""
49if exists("*HtmlIndent") && !exists('g:force_reload_html')
50 call HtmlIndent_CheckUserSettings()
51 finish
Bram Moolenaar071d4272004-06-13 20:20:40 +000052endif
53
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +020054" Allow for line continuation below.
Bram Moolenaar91170f82006-05-05 21:15:17 +000055let s:cpo_save = &cpo
Bram Moolenaar071d4272004-06-13 20:20:40 +000056set cpo-=C
Bram Moolenaarec7944a2013-06-12 21:29:15 +020057"}}}
Bram Moolenaar071d4272004-06-13 20:20:40 +000058
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +020059" Check and process settings from b:html_indent and g:html_indent... variables.
60" Prefer using buffer-local settings over global settings, so that there can
61" be defaults for all HTML files and exceptions for specific types of HTML
62" files.
63func! HtmlIndent_CheckUserSettings()
64 "{{{
65 let inctags = ''
66 if exists("b:html_indent_inctags")
67 let inctags = b:html_indent_inctags
68 elseif exists("g:html_indent_inctags")
69 let inctags = g:html_indent_inctags
70 endif
71 let b:hi_tags = {}
72 if len(inctags) > 0
73 call s:AddITags(b:hi_tags, split(inctags, ","))
74 endif
Bram Moolenaar071d4272004-06-13 20:20:40 +000075
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +020076 let autotags = ''
77 if exists("b:html_indent_autotags")
78 let autotags = b:html_indent_autotags
79 elseif exists("g:html_indent_autotags")
80 let autotags = g:html_indent_autotags
81 endif
82 let b:hi_removed_tags = {}
Bram Moolenaar541f92d2015-06-19 13:27:23 +020083 if len(autotags) > 0
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +020084 call s:RemoveITags(b:hi_removed_tags, split(autotags, ","))
85 endif
86
87 " Syntax names indicating being inside a string of an attribute value.
88 let string_names = []
89 if exists("b:html_indent_string_names")
90 let string_names = b:html_indent_string_names
91 elseif exists("g:html_indent_string_names")
92 let string_names = g:html_indent_string_names
93 endif
94 let b:hi_insideStringNames = ['htmlString']
95 if len(string_names) > 0
96 for s in string_names
97 call add(b:hi_insideStringNames, s)
98 endfor
99 endif
100
101 " Syntax names indicating being inside a tag.
102 let tag_names = []
103 if exists("b:html_indent_tag_names")
104 let tag_names = b:html_indent_tag_names
105 elseif exists("g:html_indent_tag_names")
106 let tag_names = g:html_indent_tag_names
107 endif
108 let b:hi_insideTagNames = ['htmlTag', 'htmlScriptTag']
109 if len(tag_names) > 0
110 for s in tag_names
111 call add(b:hi_insideTagNames, s)
112 endfor
113 endif
114
115 let indone = {"zero": 0
116 \,"auto": "indent(prevnonblank(v:lnum-1))"
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200117 \,"inc": "b:hi_indent.blocktagind + shiftwidth()"}
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200118
119 let script1 = ''
120 if exists("b:html_indent_script1")
121 let script1 = b:html_indent_script1
122 elseif exists("g:html_indent_script1")
123 let script1 = g:html_indent_script1
124 endif
125 if len(script1) > 0
126 let b:hi_js1indent = get(indone, script1, indone.zero)
127 else
128 let b:hi_js1indent = 0
129 endif
130
131 let style1 = ''
132 if exists("b:html_indent_style1")
133 let style1 = b:html_indent_style1
134 elseif exists("g:html_indent_style1")
135 let style1 = g:html_indent_style1
136 endif
137 if len(style1) > 0
138 let b:hi_css1indent = get(indone, style1, indone.zero)
139 else
140 let b:hi_css1indent = 0
141 endif
142
143 if !exists('b:html_indent_line_limit')
144 if exists('g:html_indent_line_limit')
145 let b:html_indent_line_limit = g:html_indent_line_limit
146 else
147 let b:html_indent_line_limit = 200
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200148 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200149 endif
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200150endfunc "}}}
Bram Moolenaar071d4272004-06-13 20:20:40 +0000151
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200152" Init Script Vars
153"{{{
154let b:hi_lasttick = 0
155let b:hi_newstate = {}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200156let s:countonly = 0
157 "}}}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200158
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200159" Fill the s:indent_tags dict with known tags.
160" The key is "tagname" or "/tagname". {{{
161" The value is:
162" 1 opening tag
163" 2 "pre"
164" 3 "script"
165" 4 "style"
166" 5 comment start
Bram Moolenaarca635012015-09-25 20:34:21 +0200167" 6 conditional comment start
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200168" -1 closing tag
169" -2 "/pre"
170" -3 "/script"
171" -4 "/style"
172" -5 comment end
Bram Moolenaarca635012015-09-25 20:34:21 +0200173" -6 conditional comment end
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200174let s:indent_tags = {}
Bram Moolenaarca635012015-09-25 20:34:21 +0200175let s:endtags = [0,0,0,0,0,0,0] " long enough for the highest index
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200176"}}}
177
178" Add a list of tag names for a pair of <tag> </tag> to "tags".
179func! s:AddITags(tags, taglist)
180 "{{{
181 for itag in a:taglist
182 let a:tags[itag] = 1
183 let a:tags['/' . itag] = -1
184 endfor
185endfunc "}}}
186
187" Take a list of tag name pairs that are not to be used as tag pairs.
188func! s:RemoveITags(tags, taglist)
189 "{{{
190 for itag in a:taglist
191 let a:tags[itag] = 1
192 let a:tags['/' . itag] = 1
193 endfor
194endfunc "}}}
195
196" Add a block tag, that is a tag with a different kind of indenting.
197func! s:AddBlockTag(tag, id, ...)
198 "{{{
199 if !(a:id >= 2 && a:id < len(s:endtags))
200 echoerr 'AddBlockTag ' . a:id
201 return
202 endif
203 let s:indent_tags[a:tag] = a:id
204 if a:0 == 0
205 let s:indent_tags['/' . a:tag] = -a:id
206 let s:endtags[a:id] = "</" . a:tag . ">"
207 else
208 let s:indent_tags[a:1] = -a:id
209 let s:endtags[a:id] = a:1
210 endif
211endfunc "}}}
212
213" Add known tag pairs.
214" Self-closing tags and tags that are sometimes {{{
215" self-closing (e.g., <p>) are not here (when encountering </p> we can find
216" the matching <p>, but not the other way around).
217" Old HTML tags:
218call s:AddITags(s:indent_tags, [
219 \ 'a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big',
220 \ 'blockquote', 'body', 'button', 'caption', 'center', 'cite', 'code',
221 \ 'colgroup', 'del', 'dfn', 'dir', 'div', 'dl', 'em', 'fieldset', 'font',
222 \ 'form', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'html',
223 \ 'i', 'iframe', 'ins', 'kbd', 'label', 'legend', 'li',
224 \ 'map', 'menu', 'noframes', 'noscript', 'object', 'ol',
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200225 \ 'optgroup', 'q', 's', 'samp', 'select', 'small', 'span', 'strong', 'sub',
226 \ 'sup', 'table', 'textarea', 'title', 'tt', 'u', 'ul', 'var', 'th', 'td',
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200227 \ 'tr', 'tbody', 'tfoot', 'thead'])
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200228
Bram Moolenaar939a1ab2016-04-10 01:31:25 +0200229" New HTML5 elements:
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200230call s:AddITags(s:indent_tags, [
231 \ 'area', 'article', 'aside', 'audio', 'bdi', 'canvas',
Bram Moolenaar939a1ab2016-04-10 01:31:25 +0200232 \ 'command', 'data', 'datalist', 'details', 'embed', 'figcaption',
233 \ 'figure', 'footer', 'header', 'keygen', 'mark', 'meter', 'nav', 'output',
234 \ 'progress', 'rp', 'rt', 'ruby', 'section', 'source', 'summary', 'svg',
235 \ 'time', 'track', 'video', 'wbr'])
Bram Moolenaard8b77f72015-03-05 21:21:19 +0100236
237" Tags added for web components:
238call s:AddITags(s:indent_tags, [
239 \ 'content', 'shadow', 'template'])
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200240"}}}
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200241
242" Add Block Tags: these contain alien content
243"{{{
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200244call s:AddBlockTag('pre', 2)
245call s:AddBlockTag('script', 3)
246call s:AddBlockTag('style', 4)
247call s:AddBlockTag('<!--', 5, '-->')
Bram Moolenaarca635012015-09-25 20:34:21 +0200248call s:AddBlockTag('<!--[', 6, '![endif]-->')
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200249"}}}
250
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200251" Return non-zero when "tagname" is an opening tag, not being a block tag, for
252" which there should be a closing tag. Can be used by scripts that include
253" HTML indenting.
254func! HtmlIndent_IsOpenTag(tagname)
255 "{{{
256 if get(s:indent_tags, a:tagname) == 1
257 return 1
258 endif
259 return get(b:hi_tags, a:tagname) == 1
260endfunc "}}}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200261
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200262" Get the value for "tagname", taking care of buffer-local tags.
263func! s:get_tag(tagname)
264 "{{{
265 let i = get(s:indent_tags, a:tagname)
266 if (i == 1 || i == -1) && get(b:hi_removed_tags, a:tagname) != 0
267 return 0
268 endif
269 if i == 0
270 let i = get(b:hi_tags, a:tagname)
271 endif
272 return i
273endfunc "}}}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200274
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200275" Count the number of start and end tags in "text".
276func! s:CountITags(text)
277 "{{{
278 " Store the result in s:curind and s:nextrel.
279 let s:curind = 0 " relative indent steps for current line [unit &sw]:
280 let s:nextrel = 0 " relative indent steps for next line [unit &sw]:
281 let s:block = 0 " assume starting outside of a block
282 let s:countonly = 1 " don't change state
Bram Moolenaarca635012015-09-25 20:34:21 +0200283 call substitute(a:text, '<\zs/\=\w\+\(-\w\+\)*\>\|<!--\[\|\[endif\]-->\|<!--\|-->', '\=s:CheckTag(submatch(0))', 'g')
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200284 let s:countonly = 0
285endfunc "}}}
286
287" Count the number of start and end tags in text.
288func! s:CountTagsAndState(text)
289 "{{{
290 " Store the result in s:curind and s:nextrel. Update b:hi_newstate.block.
291 let s:curind = 0 " relative indent steps for current line [unit &sw]:
292 let s:nextrel = 0 " relative indent steps for next line [unit &sw]:
293
294 let s:block = b:hi_newstate.block
Bram Moolenaarca635012015-09-25 20:34:21 +0200295 let tmp = substitute(a:text, '<\zs/\=\w\+\(-\w\+\)*\>\|<!--\[\|\[endif\]-->\|<!--\|-->', '\=s:CheckTag(submatch(0))', 'g')
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200296 if s:block == 3
297 let b:hi_newstate.scripttype = s:GetScriptType(matchstr(tmp, '\C.*<SCRIPT\>\zs[^>]*'))
298 endif
299 let b:hi_newstate.block = s:block
300endfunc "}}}
301
302" Used by s:CountITags() and s:CountTagsAndState().
303func! s:CheckTag(itag)
304 "{{{
305 " Returns an empty string or "SCRIPT".
306 " a:itag can be "tag" or "/tag" or "<!--" or "-->"
Bram Moolenaard8b77f72015-03-05 21:21:19 +0100307 if (s:CheckCustomTag(a:itag))
308 return ""
309 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200310 let ind = s:get_tag(a:itag)
311 if ind == -1
312 " closing tag
313 if s:block != 0
314 " ignore itag within a block
315 return ""
316 endif
317 if s:nextrel == 0
318 let s:curind -= 1
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200319 else
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200320 let s:nextrel -= 1
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200321 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200322 elseif ind == 1
323 " opening tag
324 if s:block != 0
325 return ""
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200326 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200327 let s:nextrel += 1
328 elseif ind != 0
329 " block-tag (opening or closing)
330 return s:CheckBlockTag(a:itag, ind)
331 " else ind==0 (other tag found): keep indent
332 endif
333 return ""
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200334endfunc "}}}
335
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200336" Used by s:CheckTag(). Returns an empty string or "SCRIPT".
337func! s:CheckBlockTag(blocktag, ind)
338 "{{{
339 if a:ind > 0
340 " a block starts here
341 if s:block != 0
342 " already in a block (nesting) - ignore
343 " especially ignore comments after other blocktags
344 return ""
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200345 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200346 let s:block = a:ind " block type
347 if s:countonly
348 return ""
349 endif
350 let b:hi_newstate.blocklnr = v:lnum
351 " save allover indent for the endtag
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200352 let b:hi_newstate.blocktagind = b:hi_indent.baseindent + (s:nextrel + s:curind) * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200353 if a:ind == 3
354 return "SCRIPT" " all except this must be lowercase
355 " line is to be checked again for the type attribute
356 endif
357 else
358 let s:block = 0
359 " we get here if starting and closing a block-tag on the same line
360 endif
361 return ""
362endfunc "}}}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200363
Bram Moolenaard8b77f72015-03-05 21:21:19 +0100364" Used by s:CheckTag().
365func! s:CheckCustomTag(ctag)
366 "{{{
367 " Returns 1 if ctag is the tag for a custom element, 0 otherwise.
368 " a:ctag can be "tag" or "/tag" or "<!--" or "-->"
369 let pattern = '\%\(\w\+-\)\+\w\+'
370 if match(a:ctag, pattern) == -1
371 return 0
372 endif
373 if matchstr(a:ctag, '\/\ze.\+') == "/"
374 " closing tag
375 if s:block != 0
376 " ignore ctag within a block
377 return 1
378 endif
379 if s:nextrel == 0
380 let s:curind -= 1
381 else
382 let s:nextrel -= 1
383 endif
384 else
385 " opening tag
386 if s:block != 0
387 return 1
388 endif
389 let s:nextrel += 1
390 endif
391 return 1
392endfunc "}}}
393
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200394" Return the <script> type: either "javascript" or ""
395func! s:GetScriptType(str)
396 "{{{
397 if a:str == "" || a:str =~ "java"
398 return "javascript"
399 else
400 return ""
401 endif
402endfunc "}}}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200403
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200404" Look back in the file, starting at a:lnum - 1, to compute a state for the
405" start of line a:lnum. Return the new state.
406func! s:FreshState(lnum)
407 "{{{
408 " A state is to know ALL relevant details about the
409 " lines 1..a:lnum-1, initial calculating (here!) can be slow, but updating is
410 " fast (incremental).
411 " TODO: this should be split up in detecting the block type and computing the
412 " indent for the block type, so that when we do not know the indent we do
413 " not need to clear the whole state and re-detect the block type again.
414 " State:
415 " lnum last indented line == prevnonblank(a:lnum - 1)
416 " block = 0 a:lnum located within special tag: 0:none, 2:<pre>,
Bram Moolenaarca635012015-09-25 20:34:21 +0200417 " 3:<script>, 4:<style>, 5:<!--, 6:<!--[
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200418 " baseindent use this indent for line a:lnum as a start - kind of
419 " autoindent (if block==0)
420 " scripttype = '' type attribute of a script tag (if block==3)
421 " blocktagind indent for current opening (get) and closing (set)
422 " blocktag (if block!=0)
423 " blocklnr lnum of starting blocktag (if block!=0)
424 " inattr line {lnum} starts with attributes of a tag
425 let state = {}
426 let state.lnum = prevnonblank(a:lnum - 1)
427 let state.scripttype = ""
428 let state.blocktagind = -1
429 let state.block = 0
430 let state.baseindent = 0
431 let state.blocklnr = 0
432 let state.inattr = 0
433
434 if state.lnum == 0
435 return state
436 endif
437
438 " Heuristic:
439 " remember startline state.lnum
440 " look back for <pre, </pre, <script, </script, <style, </style tags
441 " remember stopline
442 " if opening tag found,
443 " assume a:lnum within block
444 " else
445 " look back in result range (stopline, startline) for comment
446 " \ delimiters (<!--, -->)
447 " if comment opener found,
448 " assume a:lnum within comment
449 " else
450 " assume usual html for a:lnum
451 " if a:lnum-1 has a closing comment
452 " look back to get indent of comment opener
453 " FI
454
455 " look back for a blocktag
Bram Moolenaarca635012015-09-25 20:34:21 +0200456 let stopline2 = v:lnum + 1
457 if has_key(b:hi_indent, 'block') && b:hi_indent.block > 5
458 let [stopline2, stopcol2] = searchpos('<!--', 'bnW')
459 endif
460 let [stopline, stopcol] = searchpos('\c<\zs\/\=\%(pre\>\|script\>\|style\>\)', "bnW")
461 if stopline > 0 && stopline < stopline2
462 " ugly ... why isn't there searchstr()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200463 let tagline = tolower(getline(stopline))
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200464 let blocktag = matchstr(tagline, '\/\=\%(pre\>\|script\>\|style\>\)', stopcol - 1)
465 if blocktag[0] != "/"
466 " opening tag found, assume a:lnum within block
467 let state.block = s:indent_tags[blocktag]
468 if state.block == 3
469 let state.scripttype = s:GetScriptType(matchstr(tagline, '\>[^>]*', stopcol))
470 endif
471 let state.blocklnr = stopline
472 " check preceding tags in the line:
473 call s:CountITags(tagline[: stopcol-2])
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200474 let state.blocktagind = indent(stopline) + (s:curind + s:nextrel) * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200475 return state
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200476 elseif stopline == state.lnum
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200477 " handle special case: previous line (= state.lnum) contains a
478 " closing blocktag which is preceded by line-noise;
479 " blocktag == "/..."
480 let swendtag = match(tagline, '^\s*</') >= 0
481 if !swendtag
Bram Moolenaarca635012015-09-25 20:34:21 +0200482 let [bline, bcol] = searchpos('<'.blocktag[1:].'\>', "bnW")
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200483 call s:CountITags(tolower(getline(bline)[: bcol-2]))
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200484 let state.baseindent = indent(bline) + (s:curind + s:nextrel) * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200485 return state
486 endif
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200487 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200488 endif
Bram Moolenaarca635012015-09-25 20:34:21 +0200489 if stopline > stopline2
490 let stopline = stopline2
491 let stopcol = stopcol2
492 endif
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200493
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200494 " else look back for comment
Bram Moolenaarca635012015-09-25 20:34:21 +0200495 let [comlnum, comcol, found] = searchpos('\(<!--\[\)\|\(<!--\)\|-->', 'bpnW', stopline)
496 if found == 2 || found == 3
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200497 " comment opener found, assume a:lnum within comment
Bram Moolenaarca635012015-09-25 20:34:21 +0200498 let state.block = (found == 3 ? 5 : 6)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200499 let state.blocklnr = comlnum
500 " check preceding tags in the line:
501 call s:CountITags(tolower(getline(comlnum)[: comcol-2]))
Bram Moolenaarca635012015-09-25 20:34:21 +0200502 if found == 2
503 let state.baseindent = b:hi_indent.baseindent
504 endif
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200505 let state.blocktagind = indent(comlnum) + (s:curind + s:nextrel) * shiftwidth()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200506 return state
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200507 endif
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200508
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200509 " else within usual HTML
510 let text = tolower(getline(state.lnum))
511
512 " Check a:lnum-1 for closing comment (we need indent from the opening line).
513 " Not when other tags follow (might be --> inside a string).
514 let comcol = stridx(text, '-->')
515 if comcol >= 0 && match(text, '[<>]', comcol) <= 0
516 call cursor(state.lnum, comcol + 1)
517 let [comlnum, comcol] = searchpos('<!--', 'bW')
518 if comlnum == state.lnum
519 let text = text[: comcol-2]
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200520 else
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200521 let text = tolower(getline(comlnum)[: comcol-2])
Bram Moolenaar071d4272004-06-13 20:20:40 +0000522 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200523 call s:CountITags(text)
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200524 let state.baseindent = indent(comlnum) + (s:curind + s:nextrel) * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200525 " TODO check tags that follow "-->"
526 return state
527 endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000528
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200529 " Check if the previous line starts with end tag.
530 let swendtag = match(text, '^\s*</') >= 0
531
532 " If previous line ended in a closing tag, line up with the opening tag.
533 if !swendtag && text =~ '</\w\+\s*>\s*$'
534 call cursor(state.lnum, 99999)
Bram Moolenaar7b61a542014-08-23 15:31:19 +0200535 normal! F<
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200536 let start_lnum = HtmlIndent_FindStartTag()
537 if start_lnum > 0
538 let state.baseindent = indent(start_lnum)
539 if col('.') > 2
540 " check for tags before the matching opening tag.
541 let text = getline(start_lnum)
542 let swendtag = match(text, '^\s*</') >= 0
543 call s:CountITags(text[: col('.') - 2])
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200544 let state.baseindent += s:nextrel * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200545 if !swendtag
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200546 let state.baseindent += s:curind * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200547 endif
548 endif
549 return state
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200550 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200551 endif
552
553 " Else: no comments. Skip backwards to find the tag we're inside.
554 let [state.lnum, found] = HtmlIndent_FindTagStart(state.lnum)
555 " Check if that line starts with end tag.
556 let text = getline(state.lnum)
557 let swendtag = match(text, '^\s*</') >= 0
558 call s:CountITags(tolower(text))
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200559 let state.baseindent = indent(state.lnum) + s:nextrel * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200560 if !swendtag
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200561 let state.baseindent += s:curind * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200562 endif
563 return state
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200564endfunc "}}}
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200565
566" Indent inside a <pre> block: Keep indent as-is.
567func! s:Alien2()
568 "{{{
569 return -1
570endfunc "}}}
571
572" Return the indent inside a <script> block for javascript.
573func! s:Alien3()
574 "{{{
575 let lnum = prevnonblank(v:lnum - 1)
576 while lnum > 1 && getline(lnum) =~ '^\s*/[/*]'
577 " Skip over comments to avoid that cindent() aligns with the <script> tag
578 let lnum = prevnonblank(lnum - 1)
579 endwhile
580 if lnum == b:hi_indent.blocklnr
581 " indent for the first line after <script>
582 return eval(b:hi_js1indent)
583 endif
584 if b:hi_indent.scripttype == "javascript"
Bram Moolenaar690afe12017-01-28 18:34:47 +0100585 return GetJavascriptIndent()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200586 else
587 return -1
588 endif
589endfunc "}}}
590
591" Return the indent inside a <style> block.
592func! s:Alien4()
593 "{{{
594 if prevnonblank(v:lnum-1) == b:hi_indent.blocklnr
595 " indent for first content line
596 return eval(b:hi_css1indent)
597 endif
598 return s:CSSIndent()
599endfunc "}}}
600
601" Indending inside a <style> block. Returns the indent.
602func! s:CSSIndent()
603 "{{{
604 " This handles standard CSS and also Closure stylesheets where special lines
605 " start with @.
606 " When the line starts with '*' or the previous line starts with "/*"
607 " and does not end in "*/", use C indenting to format the comment.
608 " Adopted $VIMRUNTIME/indent/css.vim
609 let curtext = getline(v:lnum)
610 if curtext =~ '^\s*[*]'
611 \ || (v:lnum > 1 && getline(v:lnum - 1) =~ '\s*/\*'
612 \ && getline(v:lnum - 1) !~ '\*/\s*$')
613 return cindent(v:lnum)
614 endif
615
616 let min_lnum = b:hi_indent.blocklnr
617 let prev_lnum = s:CssPrevNonComment(v:lnum - 1, min_lnum)
618 let [prev_lnum, found] = HtmlIndent_FindTagStart(prev_lnum)
619 if prev_lnum <= min_lnum
620 " Just below the <style> tag, indent for first content line after comments.
621 return eval(b:hi_css1indent)
622 endif
623
624 " If the current line starts with "}" align with it's match.
625 if curtext =~ '^\s*}'
626 call cursor(v:lnum, 1)
627 try
628 normal! %
629 " Found the matching "{", align with it after skipping unfinished lines.
630 let align_lnum = s:CssFirstUnfinished(line('.'), min_lnum)
631 return indent(align_lnum)
632 catch
633 " can't find it, try something else, but it's most likely going to be
634 " wrong
635 endtry
636 endif
637
638 " add indent after {
639 let brace_counts = HtmlIndent_CountBraces(prev_lnum)
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200640 let extra = brace_counts.c_open * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200641
642 let prev_text = getline(prev_lnum)
643 let below_end_brace = prev_text =~ '}\s*$'
644
645 " Search back to align with the first line that's unfinished.
646 let align_lnum = s:CssFirstUnfinished(prev_lnum, min_lnum)
647
648 " Handle continuation lines if aligning with previous line and not after a
649 " "}".
650 if extra == 0 && align_lnum == prev_lnum && !below_end_brace
651 let prev_hasfield = prev_text =~ '^\s*[a-zA-Z0-9-]\+:'
652 let prev_special = prev_text =~ '^\s*\(/\*\|@\)'
653 if curtext =~ '^\s*\(/\*\|@\)'
654 " if the current line is not a comment or starts with @ (used by template
655 " systems) reduce indent if previous line is a continuation line
656 if !prev_hasfield && !prev_special
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200657 let extra = -shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200658 endif
659 else
660 let cur_hasfield = curtext =~ '^\s*[a-zA-Z0-9-]\+:'
661 let prev_unfinished = s:CssUnfinished(prev_text)
662 if !cur_hasfield && (prev_hasfield || prev_unfinished)
663 " Continuation line has extra indent if the previous line was not a
664 " continuation line.
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200665 let extra = shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200666 " Align with @if
667 if prev_text =~ '^\s*@if '
668 let extra = 4
669 endif
670 elseif cur_hasfield && !prev_hasfield && !prev_special
671 " less indent below a continuation line
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200672 let extra = -shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200673 endif
674 endif
675 endif
676
677 if below_end_brace
678 " find matching {, if that line starts with @ it's not the start of a rule
679 " but something else from a template system
680 call cursor(prev_lnum, 1)
681 call search('}\s*$')
682 try
683 normal! %
684 " Found the matching "{", align with it.
685 let align_lnum = s:CssFirstUnfinished(line('.'), min_lnum)
686 let special = getline(align_lnum) =~ '^\s*@'
687 catch
688 let special = 0
689 endtry
690 if special
691 " do not reduce indent below @{ ... }
692 if extra < 0
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200693 let extra += shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200694 endif
695 else
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200696 let extra -= (brace_counts.c_close - (prev_text =~ '^\s*}')) * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200697 endif
698 endif
699
700 " if no extra indent yet...
701 if extra == 0
702 if brace_counts.p_open > brace_counts.p_close
703 " previous line has more ( than ): add a shiftwidth
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200704 let extra = shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200705 elseif brace_counts.p_open < brace_counts.p_close
706 " previous line has more ) than (: subtract a shiftwidth
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200707 let extra = -shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200708 endif
709 endif
710
711 return indent(align_lnum) + extra
712endfunc "}}}
713
714" Inside <style>: Whether a line is unfinished.
715func! s:CssUnfinished(text)
716 "{{{
717 return a:text =~ '\s\(||\|&&\|:\)\s*$'
718endfunc "}}}
719
720" Search back for the first unfinished line above "lnum".
721func! s:CssFirstUnfinished(lnum, min_lnum)
722 "{{{
723 let align_lnum = a:lnum
724 while align_lnum > a:min_lnum && s:CssUnfinished(getline(align_lnum - 1))
725 let align_lnum -= 1
726 endwhile
727 return align_lnum
728endfunc "}}}
729
730" Find the non-empty line at or before "lnum" that is not a comment.
731func! s:CssPrevNonComment(lnum, stopline)
732 "{{{
733 " caller starts from a line a:lnum + 1 that is not a comment
734 let lnum = prevnonblank(a:lnum)
735 while 1
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200736 let ccol = match(getline(lnum), '\*/')
737 if ccol < 0
Bram Moolenaar3e496b02016-09-25 22:11:48 +0200738 " No comment end thus it's something else.
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200739 return lnum
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200740 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200741 call cursor(lnum, ccol + 1)
742 " Search back for the /* that starts the comment
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200743 let lnum = search('/\*', 'bW', a:stopline)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200744 if indent(".") == virtcol(".") - 1
745 " The found /* is at the start of the line. Now go back to the line
746 " above it and again check if it is a comment.
747 let lnum = prevnonblank(lnum - 1)
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200748 else
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200749 " /* is after something else, thus it's not a comment line.
750 return lnum
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200751 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200752 endwhile
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200753endfunc "}}}
754
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200755" Check the number of {} and () in line "lnum". Return a dict with the counts.
756func! HtmlIndent_CountBraces(lnum)
757 "{{{
758 let brs = substitute(getline(a:lnum), '[''"].\{-}[''"]\|/\*.\{-}\*/\|/\*.*$\|[^{}()]', '', 'g')
759 let c_open = 0
760 let c_close = 0
761 let p_open = 0
762 let p_close = 0
763 for brace in split(brs, '\zs')
764 if brace == "{"
765 let c_open += 1
766 elseif brace == "}"
767 if c_open > 0
768 let c_open -= 1
769 else
770 let c_close += 1
771 endif
772 elseif brace == '('
773 let p_open += 1
774 elseif brace == ')'
775 if p_open > 0
776 let p_open -= 1
777 else
778 let p_close += 1
779 endif
780 endif
781 endfor
782 return {'c_open': c_open,
783 \ 'c_close': c_close,
784 \ 'p_open': p_open,
785 \ 'p_close': p_close}
786endfunc "}}}
787
788" Return the indent for a comment: <!-- -->
789func! s:Alien5()
790 "{{{
791 let curtext = getline(v:lnum)
792 if curtext =~ '^\s*\zs-->'
793 " current line starts with end of comment, line up with comment start.
794 call cursor(v:lnum, 0)
795 let lnum = search('<!--', 'b')
796 if lnum > 0
797 " TODO: what if <!-- is not at the start of the line?
798 return indent(lnum)
799 endif
800
801 " Strange, can't find it.
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200802 return -1
Bram Moolenaar946e27a2014-06-25 18:50:27 +0200803 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200804
805 let prevlnum = prevnonblank(v:lnum - 1)
806 let prevtext = getline(prevlnum)
807 let idx = match(prevtext, '^\s*\zs<!--')
808 if idx >= 0
809 " just below comment start, add a shiftwidth
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200810 return idx + shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200811 endif
812
813 " Some files add 4 spaces just below a TODO line. It's difficult to detect
814 " the end of the TODO, so let's not do that.
815
816 " Align with the previous non-blank line.
817 return indent(prevlnum)
Bram Moolenaar946e27a2014-06-25 18:50:27 +0200818endfunc "}}}
819
Bram Moolenaarca635012015-09-25 20:34:21 +0200820" Return the indent for conditional comment: <!--[ ![endif]-->
821func! s:Alien6()
822 "{{{
823 let curtext = getline(v:lnum)
824 if curtext =~ '\s*\zs<!\[endif\]-->'
825 " current line starts with end of comment, line up with comment start.
826 let lnum = search('<!--', 'bn')
827 if lnum > 0
828 return indent(lnum)
829 endif
830 endif
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200831 return b:hi_indent.baseindent + shiftwidth()
Bram Moolenaarca635012015-09-25 20:34:21 +0200832endfunc "}}}
833
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200834" When the "lnum" line ends in ">" find the line containing the matching "<".
835func! HtmlIndent_FindTagStart(lnum)
836 "{{{
837 " Avoids using the indent of a continuation line.
838 " Moves the cursor.
839 " Return two values:
840 " - the matching line number or "lnum".
841 " - a flag indicating whether we found the end of a tag.
842 " This method is global so that HTML-like indenters can use it.
843 " To avoid matching " > " or " < " inside a string require that the opening
844 " "<" is followed by a word character and the closing ">" comes after a
845 " non-white character.
846 let idx = match(getline(a:lnum), '\S>\s*$')
847 if idx > 0
848 call cursor(a:lnum, idx)
849 let lnum = searchpair('<\w', '' , '\S>', 'bW', '', max([a:lnum - b:html_indent_line_limit, 0]))
850 if lnum > 0
851 return [lnum, 1]
Bram Moolenaar071d4272004-06-13 20:20:40 +0000852 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200853 endif
854 return [a:lnum, 0]
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200855endfunc "}}}
Bram Moolenaar071d4272004-06-13 20:20:40 +0000856
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200857" Find the unclosed start tag from the current cursor position.
858func! HtmlIndent_FindStartTag()
859 "{{{
860 " The cursor must be on or before a closing tag.
861 " If found, positions the cursor at the match and returns the line number.
862 " Otherwise returns 0.
863 let tagname = matchstr(getline('.')[col('.') - 1:], '</\zs\w\+\ze')
864 let start_lnum = searchpair('<' . tagname . '\>', '', '</' . tagname . '\>', 'bW')
865 if start_lnum > 0
866 return start_lnum
867 endif
868 return 0
869endfunc "}}}
Bram Moolenaar071d4272004-06-13 20:20:40 +0000870
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200871" Moves the cursor from a "<" to the matching ">".
872func! HtmlIndent_FindTagEnd()
873 "{{{
874 " Call this with the cursor on the "<" of a start tag.
875 " This will move the cursor to the ">" of the matching end tag or, when it's
876 " a self-closing tag, to the matching ">".
877 " Limited to look up to b:html_indent_line_limit lines away.
878 let text = getline('.')
879 let tagname = matchstr(text, '\w\+\|!--', col('.'))
880 if tagname == '!--'
881 call search('--\zs>')
882 elseif s:get_tag('/' . tagname) != 0
883 " tag with a closing tag, find matching "</tag>"
884 call searchpair('<' . tagname, '', '</' . tagname . '\zs>', 'W', '', line('.') + b:html_indent_line_limit)
885 else
886 " self-closing tag, find the ">"
887 call search('\S\zs>')
888 endif
889endfunc "}}}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200890
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200891" Indenting inside a start tag. Return the correct indent or -1 if unknown.
892func! s:InsideTag(foundHtmlString)
893 "{{{
894 if a:foundHtmlString
895 " Inside an attribute string.
896 " Align with the previous line or use an external function.
897 let lnum = v:lnum - 1
898 if lnum > 1
899 if exists('b:html_indent_tag_string_func')
900 return b:html_indent_tag_string_func(lnum)
901 endif
902 return indent(lnum)
903 endif
904 endif
905
906 " Should be another attribute: " attr="val". Align with the previous
907 " attribute start.
908 let lnum = v:lnum
909 while lnum > 1
910 let lnum -= 1
911 let text = getline(lnum)
912 " Find a match with one of these, align with "attr":
913 " attr=
914 " <tag attr=
915 " text<tag attr=
916 " <tag>text</tag>text<tag attr=
917 " For long lines search for the first match, finding the last match
918 " gets very slow.
919 if len(text) < 300
920 let idx = match(text, '.*\s\zs[_a-zA-Z0-9-]\+="')
921 else
922 let idx = match(text, '\s\zs[_a-zA-Z0-9-]\+="')
923 endif
924 if idx > 0
925 " Found the attribute. TODO: assumes spaces, no Tabs.
926 return idx
927 endif
928 endwhile
929 return -1
930endfunc "}}}
931
932" THE MAIN INDENT FUNCTION. Return the amount of indent for v:lnum.
933func! HtmlIndent()
934 "{{{
Bram Moolenaar9da7ff72015-01-14 12:52:36 +0100935 if prevnonblank(v:lnum - 1) < 1
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200936 " First non-blank line has no indent.
937 return 0
938 endif
939
940 let curtext = tolower(getline(v:lnum))
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200941 let indentunit = shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200942
943 let b:hi_newstate = {}
944 let b:hi_newstate.lnum = v:lnum
945
946 " When syntax HL is enabled, detect we are inside a tag. Indenting inside
947 " a tag works very differently. Do not do this when the line starts with
948 " "<", it gets the "htmlTag" ID but we are not inside a tag then.
949 if curtext !~ '^\s*<'
Bram Moolenaar7b61a542014-08-23 15:31:19 +0200950 normal! ^
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200951 let stack = synstack(v:lnum, col('.')) " assumes there are no tabs
952 let foundHtmlString = 0
953 for synid in reverse(stack)
954 let name = synIDattr(synid, "name")
955 if index(b:hi_insideStringNames, name) >= 0
956 let foundHtmlString = 1
957 elseif index(b:hi_insideTagNames, name) >= 0
958 " Yes, we are inside a tag.
959 let indent = s:InsideTag(foundHtmlString)
960 if indent >= 0
961 " Do not keep the state. TODO: could keep the block type.
962 let b:hi_indent.lnum = 0
963 return indent
964 endif
965 endif
966 endfor
967 endif
968
969 " does the line start with a closing tag?
970 let swendtag = match(curtext, '^\s*</') >= 0
971
972 if prevnonblank(v:lnum - 1) == b:hi_indent.lnum && b:hi_lasttick == b:changedtick - 1
973 " use state (continue from previous line)
974 else
975 " start over (know nothing)
976 let b:hi_indent = s:FreshState(v:lnum)
977 endif
978
979 if b:hi_indent.block >= 2
980 " within block
981 let endtag = s:endtags[b:hi_indent.block]
982 let blockend = stridx(curtext, endtag)
983 if blockend >= 0
984 " block ends here
985 let b:hi_newstate.block = 0
986 " calc indent for REST OF LINE (may start more blocks):
987 call s:CountTagsAndState(strpart(curtext, blockend + strlen(endtag)))
988 if swendtag && b:hi_indent.block != 5
989 let indent = b:hi_indent.blocktagind + s:curind * indentunit
990 let b:hi_newstate.baseindent = indent + s:nextrel * indentunit
991 else
992 let indent = s:Alien{b:hi_indent.block}()
993 let b:hi_newstate.baseindent = b:hi_indent.blocktagind + s:nextrel * indentunit
994 endif
995 else
996 " block continues
997 " indent this line with alien method
998 let indent = s:Alien{b:hi_indent.block}()
999 endif
1000 else
1001 " not within a block - within usual html
1002 let b:hi_newstate.block = b:hi_indent.block
1003 if swendtag
1004 " The current line starts with an end tag, align with its start tag.
1005 call cursor(v:lnum, 1)
1006 let start_lnum = HtmlIndent_FindStartTag()
1007 if start_lnum > 0
1008 " check for the line starting with something inside a tag:
1009 " <sometag <- align here
1010 " attr=val><open> not here
1011 let text = getline(start_lnum)
1012 let angle = matchstr(text, '[<>]')
1013 if angle == '>'
1014 call cursor(start_lnum, 1)
1015 normal! f>%
1016 let start_lnum = line('.')
1017 let text = getline(start_lnum)
1018 endif
1019
1020 let indent = indent(start_lnum)
1021 if col('.') > 2
1022 let swendtag = match(text, '^\s*</') >= 0
1023 call s:CountITags(text[: col('.') - 2])
Bram Moolenaar3ec574f2017-06-13 18:12:01 +02001024 let indent += s:nextrel * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +02001025 if !swendtag
Bram Moolenaar3ec574f2017-06-13 18:12:01 +02001026 let indent += s:curind * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +02001027 endif
1028 endif
1029 else
1030 " not sure what to do
1031 let indent = b:hi_indent.baseindent
1032 endif
1033 let b:hi_newstate.baseindent = indent
1034 else
1035 call s:CountTagsAndState(curtext)
1036 let indent = b:hi_indent.baseindent
1037 let b:hi_newstate.baseindent = indent + (s:curind + s:nextrel) * indentunit
1038 endif
1039 endif
1040
1041 let b:hi_lasttick = b:changedtick
1042 call extend(b:hi_indent, b:hi_newstate, "force")
1043 return indent
1044endfunc "}}}
1045
1046" Check user settings when loading this script the first time.
Bram Moolenaarec7944a2013-06-12 21:29:15 +02001047call HtmlIndent_CheckUserSettings()
Bram Moolenaar071d4272004-06-13 20:20:40 +00001048
Bram Moolenaar91170f82006-05-05 21:15:17 +00001049let &cpo = s:cpo_save
1050unlet s:cpo_save
1051
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +02001052" vim: fdm=marker ts=8 sw=2 tw=78