blob: 1d2043ae9e5ccf11c652d0eb37c1b7bbbc4b9ec0 [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 Moolenaar63b74a82019-03-24 15:09:13 +01005" Last Change: 2019 Mar 20
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 Moolenaarb5b75622018-03-09 22:22:21 +010059" Pattern to match the name of a tag, including custom elements.
60let s:tagname = '\w\+\(-\w\+\)*'
61
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +020062" Check and process settings from b:html_indent and g:html_indent... variables.
63" Prefer using buffer-local settings over global settings, so that there can
64" be defaults for all HTML files and exceptions for specific types of HTML
65" files.
66func! HtmlIndent_CheckUserSettings()
67 "{{{
68 let inctags = ''
69 if exists("b:html_indent_inctags")
70 let inctags = b:html_indent_inctags
71 elseif exists("g:html_indent_inctags")
72 let inctags = g:html_indent_inctags
73 endif
74 let b:hi_tags = {}
75 if len(inctags) > 0
76 call s:AddITags(b:hi_tags, split(inctags, ","))
77 endif
Bram Moolenaar071d4272004-06-13 20:20:40 +000078
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +020079 let autotags = ''
80 if exists("b:html_indent_autotags")
81 let autotags = b:html_indent_autotags
82 elseif exists("g:html_indent_autotags")
83 let autotags = g:html_indent_autotags
84 endif
85 let b:hi_removed_tags = {}
Bram Moolenaar541f92d2015-06-19 13:27:23 +020086 if len(autotags) > 0
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +020087 call s:RemoveITags(b:hi_removed_tags, split(autotags, ","))
88 endif
89
90 " Syntax names indicating being inside a string of an attribute value.
91 let string_names = []
92 if exists("b:html_indent_string_names")
93 let string_names = b:html_indent_string_names
94 elseif exists("g:html_indent_string_names")
95 let string_names = g:html_indent_string_names
96 endif
97 let b:hi_insideStringNames = ['htmlString']
98 if len(string_names) > 0
99 for s in string_names
100 call add(b:hi_insideStringNames, s)
101 endfor
102 endif
103
104 " Syntax names indicating being inside a tag.
105 let tag_names = []
106 if exists("b:html_indent_tag_names")
107 let tag_names = b:html_indent_tag_names
108 elseif exists("g:html_indent_tag_names")
109 let tag_names = g:html_indent_tag_names
110 endif
111 let b:hi_insideTagNames = ['htmlTag', 'htmlScriptTag']
112 if len(tag_names) > 0
113 for s in tag_names
114 call add(b:hi_insideTagNames, s)
115 endfor
116 endif
117
118 let indone = {"zero": 0
119 \,"auto": "indent(prevnonblank(v:lnum-1))"
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200120 \,"inc": "b:hi_indent.blocktagind + shiftwidth()"}
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200121
122 let script1 = ''
123 if exists("b:html_indent_script1")
124 let script1 = b:html_indent_script1
125 elseif exists("g:html_indent_script1")
126 let script1 = g:html_indent_script1
127 endif
128 if len(script1) > 0
129 let b:hi_js1indent = get(indone, script1, indone.zero)
130 else
131 let b:hi_js1indent = 0
132 endif
133
134 let style1 = ''
135 if exists("b:html_indent_style1")
136 let style1 = b:html_indent_style1
137 elseif exists("g:html_indent_style1")
138 let style1 = g:html_indent_style1
139 endif
140 if len(style1) > 0
141 let b:hi_css1indent = get(indone, style1, indone.zero)
142 else
143 let b:hi_css1indent = 0
144 endif
145
146 if !exists('b:html_indent_line_limit')
147 if exists('g:html_indent_line_limit')
148 let b:html_indent_line_limit = g:html_indent_line_limit
149 else
150 let b:html_indent_line_limit = 200
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200151 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200152 endif
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200153endfunc "}}}
Bram Moolenaar071d4272004-06-13 20:20:40 +0000154
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200155" Init Script Vars
156"{{{
157let b:hi_lasttick = 0
158let b:hi_newstate = {}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200159let s:countonly = 0
160 "}}}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200161
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200162" Fill the s:indent_tags dict with known tags.
163" The key is "tagname" or "/tagname". {{{
164" The value is:
165" 1 opening tag
166" 2 "pre"
167" 3 "script"
168" 4 "style"
169" 5 comment start
Bram Moolenaarca635012015-09-25 20:34:21 +0200170" 6 conditional comment start
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200171" -1 closing tag
172" -2 "/pre"
173" -3 "/script"
174" -4 "/style"
175" -5 comment end
Bram Moolenaarca635012015-09-25 20:34:21 +0200176" -6 conditional comment end
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200177let s:indent_tags = {}
Bram Moolenaarca635012015-09-25 20:34:21 +0200178let s:endtags = [0,0,0,0,0,0,0] " long enough for the highest index
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200179"}}}
180
181" Add a list of tag names for a pair of <tag> </tag> to "tags".
182func! s:AddITags(tags, taglist)
183 "{{{
184 for itag in a:taglist
185 let a:tags[itag] = 1
186 let a:tags['/' . itag] = -1
187 endfor
188endfunc "}}}
189
190" Take a list of tag name pairs that are not to be used as tag pairs.
191func! s:RemoveITags(tags, taglist)
192 "{{{
193 for itag in a:taglist
194 let a:tags[itag] = 1
195 let a:tags['/' . itag] = 1
196 endfor
197endfunc "}}}
198
199" Add a block tag, that is a tag with a different kind of indenting.
200func! s:AddBlockTag(tag, id, ...)
201 "{{{
202 if !(a:id >= 2 && a:id < len(s:endtags))
203 echoerr 'AddBlockTag ' . a:id
204 return
205 endif
206 let s:indent_tags[a:tag] = a:id
207 if a:0 == 0
208 let s:indent_tags['/' . a:tag] = -a:id
209 let s:endtags[a:id] = "</" . a:tag . ">"
210 else
211 let s:indent_tags[a:1] = -a:id
212 let s:endtags[a:id] = a:1
213 endif
214endfunc "}}}
215
216" Add known tag pairs.
217" Self-closing tags and tags that are sometimes {{{
218" self-closing (e.g., <p>) are not here (when encountering </p> we can find
Bram Moolenaard47d5222018-12-09 20:43:55 +0100219" the matching <p>, but not the other way around).
220" Known self-closing tags: " 'p', 'img', 'source', 'area', 'keygen', 'track',
221" 'wbr'.
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200222" Old HTML tags:
223call s:AddITags(s:indent_tags, [
224 \ 'a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big',
225 \ 'blockquote', 'body', 'button', 'caption', 'center', 'cite', 'code',
226 \ 'colgroup', 'del', 'dfn', 'dir', 'div', 'dl', 'em', 'fieldset', 'font',
227 \ 'form', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'html',
228 \ 'i', 'iframe', 'ins', 'kbd', 'label', 'legend', 'li',
229 \ 'map', 'menu', 'noframes', 'noscript', 'object', 'ol',
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200230 \ 'optgroup', 'q', 's', 'samp', 'select', 'small', 'span', 'strong', 'sub',
231 \ 'sup', 'table', 'textarea', 'title', 'tt', 'u', 'ul', 'var', 'th', 'td',
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200232 \ 'tr', 'tbody', 'tfoot', 'thead'])
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200233
Bram Moolenaar939a1ab2016-04-10 01:31:25 +0200234" New HTML5 elements:
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200235call s:AddITags(s:indent_tags, [
Bram Moolenaard47d5222018-12-09 20:43:55 +0100236 \ 'article', 'aside', 'audio', 'bdi', 'canvas', 'command', 'data',
237 \ 'datalist', 'details', 'dialog', 'embed', 'figcaption', 'figure',
238 \ 'footer', 'header', 'hgroup', 'main', 'mark', 'meter', 'nav', 'output',
239 \ 'picture', 'progress', 'rp', 'rt', 'ruby', 'section', 'summary',
240 \ 'svg', 'time', 'video'])
Bram Moolenaard8b77f72015-03-05 21:21:19 +0100241
242" Tags added for web components:
243call s:AddITags(s:indent_tags, [
244 \ 'content', 'shadow', 'template'])
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200245"}}}
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200246
247" Add Block Tags: these contain alien content
248"{{{
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200249call s:AddBlockTag('pre', 2)
250call s:AddBlockTag('script', 3)
251call s:AddBlockTag('style', 4)
252call s:AddBlockTag('<!--', 5, '-->')
Bram Moolenaarca635012015-09-25 20:34:21 +0200253call s:AddBlockTag('<!--[', 6, '![endif]-->')
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200254"}}}
255
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200256" Return non-zero when "tagname" is an opening tag, not being a block tag, for
257" which there should be a closing tag. Can be used by scripts that include
258" HTML indenting.
259func! HtmlIndent_IsOpenTag(tagname)
260 "{{{
261 if get(s:indent_tags, a:tagname) == 1
262 return 1
263 endif
264 return get(b:hi_tags, a:tagname) == 1
265endfunc "}}}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200266
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200267" Get the value for "tagname", taking care of buffer-local tags.
268func! s:get_tag(tagname)
269 "{{{
270 let i = get(s:indent_tags, a:tagname)
271 if (i == 1 || i == -1) && get(b:hi_removed_tags, a:tagname) != 0
272 return 0
273 endif
274 if i == 0
275 let i = get(b:hi_tags, a:tagname)
276 endif
277 return i
278endfunc "}}}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200279
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200280" Count the number of start and end tags in "text".
281func! s:CountITags(text)
282 "{{{
283 " Store the result in s:curind and s:nextrel.
284 let s:curind = 0 " relative indent steps for current line [unit &sw]:
285 let s:nextrel = 0 " relative indent steps for next line [unit &sw]:
286 let s:block = 0 " assume starting outside of a block
287 let s:countonly = 1 " don't change state
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100288 call substitute(a:text, '<\zs/\=' . s:tagname . '\>\|<!--\[\|\[endif\]-->\|<!--\|-->', '\=s:CheckTag(submatch(0))', 'g')
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200289 let s:countonly = 0
290endfunc "}}}
291
292" Count the number of start and end tags in text.
293func! s:CountTagsAndState(text)
294 "{{{
295 " Store the result in s:curind and s:nextrel. Update b:hi_newstate.block.
296 let s:curind = 0 " relative indent steps for current line [unit &sw]:
297 let s:nextrel = 0 " relative indent steps for next line [unit &sw]:
298
299 let s:block = b:hi_newstate.block
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100300 let tmp = substitute(a:text, '<\zs/\=' . s:tagname . '\>\|<!--\[\|\[endif\]-->\|<!--\|-->', '\=s:CheckTag(submatch(0))', 'g')
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200301 if s:block == 3
302 let b:hi_newstate.scripttype = s:GetScriptType(matchstr(tmp, '\C.*<SCRIPT\>\zs[^>]*'))
303 endif
304 let b:hi_newstate.block = s:block
305endfunc "}}}
306
307" Used by s:CountITags() and s:CountTagsAndState().
308func! s:CheckTag(itag)
309 "{{{
310 " Returns an empty string or "SCRIPT".
311 " a:itag can be "tag" or "/tag" or "<!--" or "-->"
Bram Moolenaard8b77f72015-03-05 21:21:19 +0100312 if (s:CheckCustomTag(a:itag))
313 return ""
314 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200315 let ind = s:get_tag(a:itag)
316 if ind == -1
317 " closing tag
318 if s:block != 0
319 " ignore itag within a block
320 return ""
321 endif
322 if s:nextrel == 0
323 let s:curind -= 1
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200324 else
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200325 let s:nextrel -= 1
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200326 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200327 elseif ind == 1
328 " opening tag
329 if s:block != 0
330 return ""
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200331 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200332 let s:nextrel += 1
333 elseif ind != 0
334 " block-tag (opening or closing)
335 return s:CheckBlockTag(a:itag, ind)
336 " else ind==0 (other tag found): keep indent
337 endif
338 return ""
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200339endfunc "}}}
340
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200341" Used by s:CheckTag(). Returns an empty string or "SCRIPT".
342func! s:CheckBlockTag(blocktag, ind)
343 "{{{
344 if a:ind > 0
345 " a block starts here
346 if s:block != 0
347 " already in a block (nesting) - ignore
348 " especially ignore comments after other blocktags
349 return ""
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200350 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200351 let s:block = a:ind " block type
352 if s:countonly
353 return ""
354 endif
355 let b:hi_newstate.blocklnr = v:lnum
356 " save allover indent for the endtag
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200357 let b:hi_newstate.blocktagind = b:hi_indent.baseindent + (s:nextrel + s:curind) * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200358 if a:ind == 3
359 return "SCRIPT" " all except this must be lowercase
360 " line is to be checked again for the type attribute
361 endif
362 else
363 let s:block = 0
364 " we get here if starting and closing a block-tag on the same line
365 endif
366 return ""
367endfunc "}}}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200368
Bram Moolenaard8b77f72015-03-05 21:21:19 +0100369" Used by s:CheckTag().
370func! s:CheckCustomTag(ctag)
371 "{{{
372 " Returns 1 if ctag is the tag for a custom element, 0 otherwise.
373 " a:ctag can be "tag" or "/tag" or "<!--" or "-->"
374 let pattern = '\%\(\w\+-\)\+\w\+'
375 if match(a:ctag, pattern) == -1
376 return 0
377 endif
378 if matchstr(a:ctag, '\/\ze.\+') == "/"
379 " closing tag
380 if s:block != 0
381 " ignore ctag within a block
382 return 1
383 endif
384 if s:nextrel == 0
385 let s:curind -= 1
386 else
387 let s:nextrel -= 1
388 endif
389 else
390 " opening tag
391 if s:block != 0
392 return 1
393 endif
394 let s:nextrel += 1
395 endif
396 return 1
397endfunc "}}}
398
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200399" Return the <script> type: either "javascript" or ""
400func! s:GetScriptType(str)
401 "{{{
402 if a:str == "" || a:str =~ "java"
403 return "javascript"
404 else
405 return ""
406 endif
407endfunc "}}}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200408
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200409" Look back in the file, starting at a:lnum - 1, to compute a state for the
410" start of line a:lnum. Return the new state.
411func! s:FreshState(lnum)
412 "{{{
413 " A state is to know ALL relevant details about the
414 " lines 1..a:lnum-1, initial calculating (here!) can be slow, but updating is
415 " fast (incremental).
416 " TODO: this should be split up in detecting the block type and computing the
417 " indent for the block type, so that when we do not know the indent we do
418 " not need to clear the whole state and re-detect the block type again.
419 " State:
420 " lnum last indented line == prevnonblank(a:lnum - 1)
421 " block = 0 a:lnum located within special tag: 0:none, 2:<pre>,
Bram Moolenaarca635012015-09-25 20:34:21 +0200422 " 3:<script>, 4:<style>, 5:<!--, 6:<!--[
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200423 " baseindent use this indent for line a:lnum as a start - kind of
424 " autoindent (if block==0)
425 " scripttype = '' type attribute of a script tag (if block==3)
426 " blocktagind indent for current opening (get) and closing (set)
427 " blocktag (if block!=0)
428 " blocklnr lnum of starting blocktag (if block!=0)
429 " inattr line {lnum} starts with attributes of a tag
430 let state = {}
431 let state.lnum = prevnonblank(a:lnum - 1)
432 let state.scripttype = ""
433 let state.blocktagind = -1
434 let state.block = 0
435 let state.baseindent = 0
436 let state.blocklnr = 0
437 let state.inattr = 0
438
439 if state.lnum == 0
440 return state
441 endif
442
443 " Heuristic:
444 " remember startline state.lnum
445 " look back for <pre, </pre, <script, </script, <style, </style tags
446 " remember stopline
447 " if opening tag found,
448 " assume a:lnum within block
449 " else
450 " look back in result range (stopline, startline) for comment
451 " \ delimiters (<!--, -->)
452 " if comment opener found,
453 " assume a:lnum within comment
454 " else
455 " assume usual html for a:lnum
456 " if a:lnum-1 has a closing comment
457 " look back to get indent of comment opener
458 " FI
459
460 " look back for a blocktag
Bram Moolenaarca635012015-09-25 20:34:21 +0200461 let stopline2 = v:lnum + 1
462 if has_key(b:hi_indent, 'block') && b:hi_indent.block > 5
463 let [stopline2, stopcol2] = searchpos('<!--', 'bnW')
464 endif
465 let [stopline, stopcol] = searchpos('\c<\zs\/\=\%(pre\>\|script\>\|style\>\)', "bnW")
466 if stopline > 0 && stopline < stopline2
467 " ugly ... why isn't there searchstr()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200468 let tagline = tolower(getline(stopline))
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200469 let blocktag = matchstr(tagline, '\/\=\%(pre\>\|script\>\|style\>\)', stopcol - 1)
470 if blocktag[0] != "/"
471 " opening tag found, assume a:lnum within block
472 let state.block = s:indent_tags[blocktag]
473 if state.block == 3
474 let state.scripttype = s:GetScriptType(matchstr(tagline, '\>[^>]*', stopcol))
475 endif
476 let state.blocklnr = stopline
477 " check preceding tags in the line:
478 call s:CountITags(tagline[: stopcol-2])
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200479 let state.blocktagind = indent(stopline) + (s:curind + s:nextrel) * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200480 return state
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200481 elseif stopline == state.lnum
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200482 " handle special case: previous line (= state.lnum) contains a
483 " closing blocktag which is preceded by line-noise;
484 " blocktag == "/..."
485 let swendtag = match(tagline, '^\s*</') >= 0
486 if !swendtag
Bram Moolenaarca635012015-09-25 20:34:21 +0200487 let [bline, bcol] = searchpos('<'.blocktag[1:].'\>', "bnW")
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200488 call s:CountITags(tolower(getline(bline)[: bcol-2]))
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200489 let state.baseindent = indent(bline) + (s:curind + s:nextrel) * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200490 return state
491 endif
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200492 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200493 endif
Bram Moolenaarca635012015-09-25 20:34:21 +0200494 if stopline > stopline2
495 let stopline = stopline2
496 let stopcol = stopcol2
497 endif
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200498
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200499 " else look back for comment
Bram Moolenaarca635012015-09-25 20:34:21 +0200500 let [comlnum, comcol, found] = searchpos('\(<!--\[\)\|\(<!--\)\|-->', 'bpnW', stopline)
501 if found == 2 || found == 3
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200502 " comment opener found, assume a:lnum within comment
Bram Moolenaarca635012015-09-25 20:34:21 +0200503 let state.block = (found == 3 ? 5 : 6)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200504 let state.blocklnr = comlnum
505 " check preceding tags in the line:
506 call s:CountITags(tolower(getline(comlnum)[: comcol-2]))
Bram Moolenaarca635012015-09-25 20:34:21 +0200507 if found == 2
508 let state.baseindent = b:hi_indent.baseindent
509 endif
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200510 let state.blocktagind = indent(comlnum) + (s:curind + s:nextrel) * shiftwidth()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200511 return state
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200512 endif
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200513
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200514 " else within usual HTML
515 let text = tolower(getline(state.lnum))
516
517 " Check a:lnum-1 for closing comment (we need indent from the opening line).
518 " Not when other tags follow (might be --> inside a string).
519 let comcol = stridx(text, '-->')
520 if comcol >= 0 && match(text, '[<>]', comcol) <= 0
521 call cursor(state.lnum, comcol + 1)
522 let [comlnum, comcol] = searchpos('<!--', 'bW')
523 if comlnum == state.lnum
524 let text = text[: comcol-2]
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200525 else
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200526 let text = tolower(getline(comlnum)[: comcol-2])
Bram Moolenaar071d4272004-06-13 20:20:40 +0000527 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200528 call s:CountITags(text)
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200529 let state.baseindent = indent(comlnum) + (s:curind + s:nextrel) * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200530 " TODO check tags that follow "-->"
531 return state
532 endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000533
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200534 " Check if the previous line starts with end tag.
535 let swendtag = match(text, '^\s*</') >= 0
536
537 " If previous line ended in a closing tag, line up with the opening tag.
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100538 if !swendtag && text =~ '</' . s:tagname . '\s*>\s*$'
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200539 call cursor(state.lnum, 99999)
Bram Moolenaar7b61a542014-08-23 15:31:19 +0200540 normal! F<
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200541 let start_lnum = HtmlIndent_FindStartTag()
542 if start_lnum > 0
543 let state.baseindent = indent(start_lnum)
544 if col('.') > 2
545 " check for tags before the matching opening tag.
546 let text = getline(start_lnum)
547 let swendtag = match(text, '^\s*</') >= 0
548 call s:CountITags(text[: col('.') - 2])
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200549 let state.baseindent += s:nextrel * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200550 if !swendtag
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200551 let state.baseindent += s:curind * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200552 endif
553 endif
554 return state
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200555 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200556 endif
557
558 " Else: no comments. Skip backwards to find the tag we're inside.
559 let [state.lnum, found] = HtmlIndent_FindTagStart(state.lnum)
560 " Check if that line starts with end tag.
561 let text = getline(state.lnum)
562 let swendtag = match(text, '^\s*</') >= 0
563 call s:CountITags(tolower(text))
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200564 let state.baseindent = indent(state.lnum) + s:nextrel * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200565 if !swendtag
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200566 let state.baseindent += s:curind * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200567 endif
568 return state
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200569endfunc "}}}
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200570
571" Indent inside a <pre> block: Keep indent as-is.
572func! s:Alien2()
573 "{{{
574 return -1
575endfunc "}}}
576
577" Return the indent inside a <script> block for javascript.
578func! s:Alien3()
579 "{{{
580 let lnum = prevnonblank(v:lnum - 1)
581 while lnum > 1 && getline(lnum) =~ '^\s*/[/*]'
582 " Skip over comments to avoid that cindent() aligns with the <script> tag
583 let lnum = prevnonblank(lnum - 1)
584 endwhile
585 if lnum == b:hi_indent.blocklnr
586 " indent for the first line after <script>
587 return eval(b:hi_js1indent)
588 endif
589 if b:hi_indent.scripttype == "javascript"
Bram Moolenaar690afe12017-01-28 18:34:47 +0100590 return GetJavascriptIndent()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200591 else
592 return -1
593 endif
594endfunc "}}}
595
596" Return the indent inside a <style> block.
597func! s:Alien4()
598 "{{{
599 if prevnonblank(v:lnum-1) == b:hi_indent.blocklnr
600 " indent for first content line
601 return eval(b:hi_css1indent)
602 endif
603 return s:CSSIndent()
604endfunc "}}}
605
606" Indending inside a <style> block. Returns the indent.
607func! s:CSSIndent()
608 "{{{
609 " This handles standard CSS and also Closure stylesheets where special lines
610 " start with @.
611 " When the line starts with '*' or the previous line starts with "/*"
612 " and does not end in "*/", use C indenting to format the comment.
613 " Adopted $VIMRUNTIME/indent/css.vim
614 let curtext = getline(v:lnum)
615 if curtext =~ '^\s*[*]'
616 \ || (v:lnum > 1 && getline(v:lnum - 1) =~ '\s*/\*'
617 \ && getline(v:lnum - 1) !~ '\*/\s*$')
618 return cindent(v:lnum)
619 endif
620
621 let min_lnum = b:hi_indent.blocklnr
622 let prev_lnum = s:CssPrevNonComment(v:lnum - 1, min_lnum)
623 let [prev_lnum, found] = HtmlIndent_FindTagStart(prev_lnum)
624 if prev_lnum <= min_lnum
625 " Just below the <style> tag, indent for first content line after comments.
626 return eval(b:hi_css1indent)
627 endif
628
Bram Moolenaarba3ff532018-11-04 14:45:49 +0100629 " If the current line starts with "}" align with its match.
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200630 if curtext =~ '^\s*}'
631 call cursor(v:lnum, 1)
632 try
633 normal! %
634 " Found the matching "{", align with it after skipping unfinished lines.
635 let align_lnum = s:CssFirstUnfinished(line('.'), min_lnum)
636 return indent(align_lnum)
637 catch
638 " can't find it, try something else, but it's most likely going to be
639 " wrong
640 endtry
641 endif
642
643 " add indent after {
644 let brace_counts = HtmlIndent_CountBraces(prev_lnum)
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200645 let extra = brace_counts.c_open * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200646
647 let prev_text = getline(prev_lnum)
648 let below_end_brace = prev_text =~ '}\s*$'
649
650 " Search back to align with the first line that's unfinished.
651 let align_lnum = s:CssFirstUnfinished(prev_lnum, min_lnum)
652
653 " Handle continuation lines if aligning with previous line and not after a
654 " "}".
655 if extra == 0 && align_lnum == prev_lnum && !below_end_brace
656 let prev_hasfield = prev_text =~ '^\s*[a-zA-Z0-9-]\+:'
657 let prev_special = prev_text =~ '^\s*\(/\*\|@\)'
658 if curtext =~ '^\s*\(/\*\|@\)'
659 " if the current line is not a comment or starts with @ (used by template
660 " systems) reduce indent if previous line is a continuation line
661 if !prev_hasfield && !prev_special
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200662 let extra = -shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200663 endif
664 else
665 let cur_hasfield = curtext =~ '^\s*[a-zA-Z0-9-]\+:'
666 let prev_unfinished = s:CssUnfinished(prev_text)
Bram Moolenaarfc65cab2018-08-28 22:58:02 +0200667 if prev_unfinished
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200668 " Continuation line has extra indent if the previous line was not a
669 " continuation line.
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200670 let extra = shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200671 " Align with @if
672 if prev_text =~ '^\s*@if '
673 let extra = 4
674 endif
675 elseif cur_hasfield && !prev_hasfield && !prev_special
676 " less indent below a continuation line
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200677 let extra = -shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200678 endif
679 endif
680 endif
681
682 if below_end_brace
683 " find matching {, if that line starts with @ it's not the start of a rule
684 " but something else from a template system
685 call cursor(prev_lnum, 1)
686 call search('}\s*$')
687 try
688 normal! %
689 " Found the matching "{", align with it.
690 let align_lnum = s:CssFirstUnfinished(line('.'), min_lnum)
691 let special = getline(align_lnum) =~ '^\s*@'
692 catch
693 let special = 0
694 endtry
695 if special
696 " do not reduce indent below @{ ... }
697 if extra < 0
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200698 let extra += shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200699 endif
700 else
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200701 let extra -= (brace_counts.c_close - (prev_text =~ '^\s*}')) * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200702 endif
703 endif
704
705 " if no extra indent yet...
706 if extra == 0
707 if brace_counts.p_open > brace_counts.p_close
708 " previous line has more ( than ): add a shiftwidth
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200709 let extra = shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200710 elseif brace_counts.p_open < brace_counts.p_close
711 " previous line has more ) than (: subtract a shiftwidth
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200712 let extra = -shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200713 endif
714 endif
715
716 return indent(align_lnum) + extra
717endfunc "}}}
718
719" Inside <style>: Whether a line is unfinished.
Bram Moolenaarfc65cab2018-08-28 22:58:02 +0200720" tag:
721" tag: blah
722" tag: blah &&
723" tag: blah ||
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200724func! s:CssUnfinished(text)
725 "{{{
Bram Moolenaarfc65cab2018-08-28 22:58:02 +0200726 return a:text =~ '\(||\|&&\|:\|\k\)\s*$'
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200727endfunc "}}}
728
729" Search back for the first unfinished line above "lnum".
730func! s:CssFirstUnfinished(lnum, min_lnum)
731 "{{{
732 let align_lnum = a:lnum
733 while align_lnum > a:min_lnum && s:CssUnfinished(getline(align_lnum - 1))
734 let align_lnum -= 1
735 endwhile
736 return align_lnum
737endfunc "}}}
738
739" Find the non-empty line at or before "lnum" that is not a comment.
740func! s:CssPrevNonComment(lnum, stopline)
741 "{{{
742 " caller starts from a line a:lnum + 1 that is not a comment
743 let lnum = prevnonblank(a:lnum)
744 while 1
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200745 let ccol = match(getline(lnum), '\*/')
746 if ccol < 0
Bram Moolenaar3e496b02016-09-25 22:11:48 +0200747 " No comment end thus it's something else.
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200748 return lnum
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200749 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200750 call cursor(lnum, ccol + 1)
751 " Search back for the /* that starts the comment
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200752 let lnum = search('/\*', 'bW', a:stopline)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200753 if indent(".") == virtcol(".") - 1
754 " The found /* is at the start of the line. Now go back to the line
755 " above it and again check if it is a comment.
756 let lnum = prevnonblank(lnum - 1)
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200757 else
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200758 " /* is after something else, thus it's not a comment line.
759 return lnum
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200760 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200761 endwhile
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200762endfunc "}}}
763
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200764" Check the number of {} and () in line "lnum". Return a dict with the counts.
765func! HtmlIndent_CountBraces(lnum)
766 "{{{
767 let brs = substitute(getline(a:lnum), '[''"].\{-}[''"]\|/\*.\{-}\*/\|/\*.*$\|[^{}()]', '', 'g')
768 let c_open = 0
769 let c_close = 0
770 let p_open = 0
771 let p_close = 0
772 for brace in split(brs, '\zs')
773 if brace == "{"
774 let c_open += 1
775 elseif brace == "}"
776 if c_open > 0
777 let c_open -= 1
778 else
779 let c_close += 1
780 endif
781 elseif brace == '('
782 let p_open += 1
783 elseif brace == ')'
784 if p_open > 0
785 let p_open -= 1
786 else
787 let p_close += 1
788 endif
789 endif
790 endfor
791 return {'c_open': c_open,
792 \ 'c_close': c_close,
793 \ 'p_open': p_open,
794 \ 'p_close': p_close}
795endfunc "}}}
796
797" Return the indent for a comment: <!-- -->
798func! s:Alien5()
799 "{{{
800 let curtext = getline(v:lnum)
801 if curtext =~ '^\s*\zs-->'
802 " current line starts with end of comment, line up with comment start.
803 call cursor(v:lnum, 0)
804 let lnum = search('<!--', 'b')
805 if lnum > 0
806 " TODO: what if <!-- is not at the start of the line?
807 return indent(lnum)
808 endif
809
810 " Strange, can't find it.
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200811 return -1
Bram Moolenaar946e27a2014-06-25 18:50:27 +0200812 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200813
814 let prevlnum = prevnonblank(v:lnum - 1)
815 let prevtext = getline(prevlnum)
816 let idx = match(prevtext, '^\s*\zs<!--')
817 if idx >= 0
818 " just below comment start, add a shiftwidth
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200819 return idx + shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200820 endif
821
822 " Some files add 4 spaces just below a TODO line. It's difficult to detect
823 " the end of the TODO, so let's not do that.
824
825 " Align with the previous non-blank line.
826 return indent(prevlnum)
Bram Moolenaar946e27a2014-06-25 18:50:27 +0200827endfunc "}}}
828
Bram Moolenaarca635012015-09-25 20:34:21 +0200829" Return the indent for conditional comment: <!--[ ![endif]-->
830func! s:Alien6()
831 "{{{
832 let curtext = getline(v:lnum)
833 if curtext =~ '\s*\zs<!\[endif\]-->'
834 " current line starts with end of comment, line up with comment start.
835 let lnum = search('<!--', 'bn')
836 if lnum > 0
837 return indent(lnum)
838 endif
839 endif
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200840 return b:hi_indent.baseindent + shiftwidth()
Bram Moolenaarca635012015-09-25 20:34:21 +0200841endfunc "}}}
842
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200843" When the "lnum" line ends in ">" find the line containing the matching "<".
844func! HtmlIndent_FindTagStart(lnum)
845 "{{{
846 " Avoids using the indent of a continuation line.
847 " Moves the cursor.
848 " Return two values:
849 " - the matching line number or "lnum".
850 " - a flag indicating whether we found the end of a tag.
851 " This method is global so that HTML-like indenters can use it.
852 " To avoid matching " > " or " < " inside a string require that the opening
853 " "<" is followed by a word character and the closing ">" comes after a
854 " non-white character.
855 let idx = match(getline(a:lnum), '\S>\s*$')
856 if idx > 0
857 call cursor(a:lnum, idx)
858 let lnum = searchpair('<\w', '' , '\S>', 'bW', '', max([a:lnum - b:html_indent_line_limit, 0]))
859 if lnum > 0
860 return [lnum, 1]
Bram Moolenaar071d4272004-06-13 20:20:40 +0000861 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200862 endif
863 return [a:lnum, 0]
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200864endfunc "}}}
Bram Moolenaar071d4272004-06-13 20:20:40 +0000865
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200866" Find the unclosed start tag from the current cursor position.
867func! HtmlIndent_FindStartTag()
868 "{{{
869 " The cursor must be on or before a closing tag.
870 " If found, positions the cursor at the match and returns the line number.
871 " Otherwise returns 0.
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100872 let tagname = matchstr(getline('.')[col('.') - 1:], '</\zs' . s:tagname . '\ze')
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200873 let start_lnum = searchpair('<' . tagname . '\>', '', '</' . tagname . '\>', 'bW')
874 if start_lnum > 0
875 return start_lnum
876 endif
877 return 0
878endfunc "}}}
Bram Moolenaar071d4272004-06-13 20:20:40 +0000879
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200880" Moves the cursor from a "<" to the matching ">".
881func! HtmlIndent_FindTagEnd()
882 "{{{
883 " Call this with the cursor on the "<" of a start tag.
884 " This will move the cursor to the ">" of the matching end tag or, when it's
885 " a self-closing tag, to the matching ">".
886 " Limited to look up to b:html_indent_line_limit lines away.
887 let text = getline('.')
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100888 let tagname = matchstr(text, s:tagname . '\|!--', col('.'))
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200889 if tagname == '!--'
890 call search('--\zs>')
891 elseif s:get_tag('/' . tagname) != 0
892 " tag with a closing tag, find matching "</tag>"
893 call searchpair('<' . tagname, '', '</' . tagname . '\zs>', 'W', '', line('.') + b:html_indent_line_limit)
894 else
895 " self-closing tag, find the ">"
896 call search('\S\zs>')
897 endif
898endfunc "}}}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200899
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200900" Indenting inside a start tag. Return the correct indent or -1 if unknown.
901func! s:InsideTag(foundHtmlString)
902 "{{{
903 if a:foundHtmlString
904 " Inside an attribute string.
Bram Moolenaar63b74a82019-03-24 15:09:13 +0100905 " Align with the opening quote or use an external function.
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200906 let lnum = v:lnum - 1
907 if lnum > 1
908 if exists('b:html_indent_tag_string_func')
909 return b:html_indent_tag_string_func(lnum)
910 endif
Bram Moolenaar63b74a82019-03-24 15:09:13 +0100911 " If there is a double quote in the previous line, indent with the
912 " character after it.
913 if getline(lnum) =~ '"'
914 call cursor(lnum, 0)
915 normal f"
916 return virtcol('.')
917 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200918 return indent(lnum)
919 endif
920 endif
921
922 " Should be another attribute: " attr="val". Align with the previous
923 " attribute start.
924 let lnum = v:lnum
925 while lnum > 1
926 let lnum -= 1
927 let text = getline(lnum)
928 " Find a match with one of these, align with "attr":
929 " attr=
930 " <tag attr=
931 " text<tag attr=
932 " <tag>text</tag>text<tag attr=
933 " For long lines search for the first match, finding the last match
934 " gets very slow.
935 if len(text) < 300
936 let idx = match(text, '.*\s\zs[_a-zA-Z0-9-]\+="')
937 else
938 let idx = match(text, '\s\zs[_a-zA-Z0-9-]\+="')
939 endif
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100940 if idx == -1
941 " try <tag attr
942 let idx = match(text, '<' . s:tagname . '\s\+\zs\w')
943 endif
944 if idx == -1
Bram Moolenaard47d5222018-12-09 20:43:55 +0100945 " after just "<tag" indent one level more
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100946 let idx = match(text, '<' . s:tagname . '$')
947 if idx >= 0
948 call cursor(lnum, idx)
949 return virtcol('.') + shiftwidth()
950 endif
951 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200952 if idx > 0
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100953 " Found the attribute to align with.
954 call cursor(lnum, idx)
955 return virtcol('.')
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200956 endif
957 endwhile
958 return -1
959endfunc "}}}
960
961" THE MAIN INDENT FUNCTION. Return the amount of indent for v:lnum.
962func! HtmlIndent()
963 "{{{
Bram Moolenaar9da7ff72015-01-14 12:52:36 +0100964 if prevnonblank(v:lnum - 1) < 1
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200965 " First non-blank line has no indent.
966 return 0
967 endif
968
969 let curtext = tolower(getline(v:lnum))
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200970 let indentunit = shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200971
972 let b:hi_newstate = {}
973 let b:hi_newstate.lnum = v:lnum
974
975 " When syntax HL is enabled, detect we are inside a tag. Indenting inside
976 " a tag works very differently. Do not do this when the line starts with
977 " "<", it gets the "htmlTag" ID but we are not inside a tag then.
978 if curtext !~ '^\s*<'
Bram Moolenaar7b61a542014-08-23 15:31:19 +0200979 normal! ^
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200980 let stack = synstack(v:lnum, col('.')) " assumes there are no tabs
981 let foundHtmlString = 0
982 for synid in reverse(stack)
983 let name = synIDattr(synid, "name")
984 if index(b:hi_insideStringNames, name) >= 0
985 let foundHtmlString = 1
986 elseif index(b:hi_insideTagNames, name) >= 0
987 " Yes, we are inside a tag.
988 let indent = s:InsideTag(foundHtmlString)
989 if indent >= 0
990 " Do not keep the state. TODO: could keep the block type.
991 let b:hi_indent.lnum = 0
992 return indent
993 endif
994 endif
995 endfor
996 endif
997
998 " does the line start with a closing tag?
999 let swendtag = match(curtext, '^\s*</') >= 0
1000
1001 if prevnonblank(v:lnum - 1) == b:hi_indent.lnum && b:hi_lasttick == b:changedtick - 1
1002 " use state (continue from previous line)
1003 else
1004 " start over (know nothing)
1005 let b:hi_indent = s:FreshState(v:lnum)
1006 endif
1007
1008 if b:hi_indent.block >= 2
1009 " within block
1010 let endtag = s:endtags[b:hi_indent.block]
1011 let blockend = stridx(curtext, endtag)
1012 if blockend >= 0
1013 " block ends here
1014 let b:hi_newstate.block = 0
1015 " calc indent for REST OF LINE (may start more blocks):
1016 call s:CountTagsAndState(strpart(curtext, blockend + strlen(endtag)))
1017 if swendtag && b:hi_indent.block != 5
1018 let indent = b:hi_indent.blocktagind + s:curind * indentunit
1019 let b:hi_newstate.baseindent = indent + s:nextrel * indentunit
1020 else
1021 let indent = s:Alien{b:hi_indent.block}()
1022 let b:hi_newstate.baseindent = b:hi_indent.blocktagind + s:nextrel * indentunit
1023 endif
1024 else
1025 " block continues
1026 " indent this line with alien method
1027 let indent = s:Alien{b:hi_indent.block}()
1028 endif
1029 else
1030 " not within a block - within usual html
1031 let b:hi_newstate.block = b:hi_indent.block
1032 if swendtag
1033 " The current line starts with an end tag, align with its start tag.
1034 call cursor(v:lnum, 1)
1035 let start_lnum = HtmlIndent_FindStartTag()
1036 if start_lnum > 0
1037 " check for the line starting with something inside a tag:
1038 " <sometag <- align here
1039 " attr=val><open> not here
1040 let text = getline(start_lnum)
1041 let angle = matchstr(text, '[<>]')
1042 if angle == '>'
1043 call cursor(start_lnum, 1)
1044 normal! f>%
1045 let start_lnum = line('.')
1046 let text = getline(start_lnum)
1047 endif
1048
1049 let indent = indent(start_lnum)
1050 if col('.') > 2
1051 let swendtag = match(text, '^\s*</') >= 0
1052 call s:CountITags(text[: col('.') - 2])
Bram Moolenaar3ec574f2017-06-13 18:12:01 +02001053 let indent += s:nextrel * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +02001054 if !swendtag
Bram Moolenaar3ec574f2017-06-13 18:12:01 +02001055 let indent += s:curind * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +02001056 endif
1057 endif
1058 else
1059 " not sure what to do
1060 let indent = b:hi_indent.baseindent
1061 endif
1062 let b:hi_newstate.baseindent = indent
1063 else
1064 call s:CountTagsAndState(curtext)
1065 let indent = b:hi_indent.baseindent
1066 let b:hi_newstate.baseindent = indent + (s:curind + s:nextrel) * indentunit
1067 endif
1068 endif
1069
1070 let b:hi_lasttick = b:changedtick
1071 call extend(b:hi_indent, b:hi_newstate, "force")
1072 return indent
1073endfunc "}}}
1074
1075" Check user settings when loading this script the first time.
Bram Moolenaarec7944a2013-06-12 21:29:15 +02001076call HtmlIndent_CheckUserSettings()
Bram Moolenaar071d4272004-06-13 20:20:40 +00001077
Bram Moolenaar91170f82006-05-05 21:15:17 +00001078let &cpo = s:cpo_save
1079unlet s:cpo_save
1080
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +02001081" vim: fdm=marker ts=8 sw=2 tw=78