blob: bece6614b88f29cdcade873037880ba4547ebb71 [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 Moolenaarab943432018-03-29 18:27:07 +02005" Last Change: 2018 Mar 28
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 Moolenaarab943432018-03-29 18:27:07 +0200219" the matching <p>, but not the other way around). Known self-closing tags:
220" 'p', 'img', 'source'.
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200221" Old HTML tags:
222call s:AddITags(s:indent_tags, [
223 \ 'a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big',
224 \ 'blockquote', 'body', 'button', 'caption', 'center', 'cite', 'code',
225 \ 'colgroup', 'del', 'dfn', 'dir', 'div', 'dl', 'em', 'fieldset', 'font',
226 \ 'form', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'html',
227 \ 'i', 'iframe', 'ins', 'kbd', 'label', 'legend', 'li',
228 \ 'map', 'menu', 'noframes', 'noscript', 'object', 'ol',
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200229 \ 'optgroup', 'q', 's', 'samp', 'select', 'small', 'span', 'strong', 'sub',
230 \ 'sup', 'table', 'textarea', 'title', 'tt', 'u', 'ul', 'var', 'th', 'td',
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200231 \ 'tr', 'tbody', 'tfoot', 'thead'])
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200232
Bram Moolenaar939a1ab2016-04-10 01:31:25 +0200233" New HTML5 elements:
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200234call s:AddITags(s:indent_tags, [
235 \ 'area', 'article', 'aside', 'audio', 'bdi', 'canvas',
Bram Moolenaar939a1ab2016-04-10 01:31:25 +0200236 \ 'command', 'data', 'datalist', 'details', 'embed', 'figcaption',
Bram Moolenaar98ef2332018-03-18 14:44:37 +0100237 \ 'figure', 'footer', 'header', 'keygen', 'main', 'mark', 'meter',
Bram Moolenaarab943432018-03-29 18:27:07 +0200238 \ 'nav', 'output', 'picture', 'progress', 'rp', 'rt', 'ruby', 'section',
Bram Moolenaar98ef2332018-03-18 14:44:37 +0100239 \ 'summary', 'svg', 'time', 'track', 'video', 'wbr'])
Bram Moolenaard8b77f72015-03-05 21:21:19 +0100240
241" Tags added for web components:
242call s:AddITags(s:indent_tags, [
243 \ 'content', 'shadow', 'template'])
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200244"}}}
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200245
246" Add Block Tags: these contain alien content
247"{{{
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200248call s:AddBlockTag('pre', 2)
249call s:AddBlockTag('script', 3)
250call s:AddBlockTag('style', 4)
251call s:AddBlockTag('<!--', 5, '-->')
Bram Moolenaarca635012015-09-25 20:34:21 +0200252call s:AddBlockTag('<!--[', 6, '![endif]-->')
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200253"}}}
254
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200255" Return non-zero when "tagname" is an opening tag, not being a block tag, for
256" which there should be a closing tag. Can be used by scripts that include
257" HTML indenting.
258func! HtmlIndent_IsOpenTag(tagname)
259 "{{{
260 if get(s:indent_tags, a:tagname) == 1
261 return 1
262 endif
263 return get(b:hi_tags, a:tagname) == 1
264endfunc "}}}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200265
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200266" Get the value for "tagname", taking care of buffer-local tags.
267func! s:get_tag(tagname)
268 "{{{
269 let i = get(s:indent_tags, a:tagname)
270 if (i == 1 || i == -1) && get(b:hi_removed_tags, a:tagname) != 0
271 return 0
272 endif
273 if i == 0
274 let i = get(b:hi_tags, a:tagname)
275 endif
276 return i
277endfunc "}}}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200278
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200279" Count the number of start and end tags in "text".
280func! s:CountITags(text)
281 "{{{
282 " Store the result in s:curind and s:nextrel.
283 let s:curind = 0 " relative indent steps for current line [unit &sw]:
284 let s:nextrel = 0 " relative indent steps for next line [unit &sw]:
285 let s:block = 0 " assume starting outside of a block
286 let s:countonly = 1 " don't change state
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100287 call substitute(a:text, '<\zs/\=' . s:tagname . '\>\|<!--\[\|\[endif\]-->\|<!--\|-->', '\=s:CheckTag(submatch(0))', 'g')
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200288 let s:countonly = 0
289endfunc "}}}
290
291" Count the number of start and end tags in text.
292func! s:CountTagsAndState(text)
293 "{{{
294 " Store the result in s:curind and s:nextrel. Update b:hi_newstate.block.
295 let s:curind = 0 " relative indent steps for current line [unit &sw]:
296 let s:nextrel = 0 " relative indent steps for next line [unit &sw]:
297
298 let s:block = b:hi_newstate.block
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100299 let tmp = substitute(a:text, '<\zs/\=' . s:tagname . '\>\|<!--\[\|\[endif\]-->\|<!--\|-->', '\=s:CheckTag(submatch(0))', 'g')
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200300 if s:block == 3
301 let b:hi_newstate.scripttype = s:GetScriptType(matchstr(tmp, '\C.*<SCRIPT\>\zs[^>]*'))
302 endif
303 let b:hi_newstate.block = s:block
304endfunc "}}}
305
306" Used by s:CountITags() and s:CountTagsAndState().
307func! s:CheckTag(itag)
308 "{{{
309 " Returns an empty string or "SCRIPT".
310 " a:itag can be "tag" or "/tag" or "<!--" or "-->"
Bram Moolenaard8b77f72015-03-05 21:21:19 +0100311 if (s:CheckCustomTag(a:itag))
312 return ""
313 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200314 let ind = s:get_tag(a:itag)
315 if ind == -1
316 " closing tag
317 if s:block != 0
318 " ignore itag within a block
319 return ""
320 endif
321 if s:nextrel == 0
322 let s:curind -= 1
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200323 else
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200324 let s:nextrel -= 1
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200325 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200326 elseif ind == 1
327 " opening tag
328 if s:block != 0
329 return ""
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200330 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200331 let s:nextrel += 1
332 elseif ind != 0
333 " block-tag (opening or closing)
334 return s:CheckBlockTag(a:itag, ind)
335 " else ind==0 (other tag found): keep indent
336 endif
337 return ""
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200338endfunc "}}}
339
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200340" Used by s:CheckTag(). Returns an empty string or "SCRIPT".
341func! s:CheckBlockTag(blocktag, ind)
342 "{{{
343 if a:ind > 0
344 " a block starts here
345 if s:block != 0
346 " already in a block (nesting) - ignore
347 " especially ignore comments after other blocktags
348 return ""
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200349 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200350 let s:block = a:ind " block type
351 if s:countonly
352 return ""
353 endif
354 let b:hi_newstate.blocklnr = v:lnum
355 " save allover indent for the endtag
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200356 let b:hi_newstate.blocktagind = b:hi_indent.baseindent + (s:nextrel + s:curind) * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200357 if a:ind == 3
358 return "SCRIPT" " all except this must be lowercase
359 " line is to be checked again for the type attribute
360 endif
361 else
362 let s:block = 0
363 " we get here if starting and closing a block-tag on the same line
364 endif
365 return ""
366endfunc "}}}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200367
Bram Moolenaard8b77f72015-03-05 21:21:19 +0100368" Used by s:CheckTag().
369func! s:CheckCustomTag(ctag)
370 "{{{
371 " Returns 1 if ctag is the tag for a custom element, 0 otherwise.
372 " a:ctag can be "tag" or "/tag" or "<!--" or "-->"
373 let pattern = '\%\(\w\+-\)\+\w\+'
374 if match(a:ctag, pattern) == -1
375 return 0
376 endif
377 if matchstr(a:ctag, '\/\ze.\+') == "/"
378 " closing tag
379 if s:block != 0
380 " ignore ctag within a block
381 return 1
382 endif
383 if s:nextrel == 0
384 let s:curind -= 1
385 else
386 let s:nextrel -= 1
387 endif
388 else
389 " opening tag
390 if s:block != 0
391 return 1
392 endif
393 let s:nextrel += 1
394 endif
395 return 1
396endfunc "}}}
397
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200398" Return the <script> type: either "javascript" or ""
399func! s:GetScriptType(str)
400 "{{{
401 if a:str == "" || a:str =~ "java"
402 return "javascript"
403 else
404 return ""
405 endif
406endfunc "}}}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200407
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200408" Look back in the file, starting at a:lnum - 1, to compute a state for the
409" start of line a:lnum. Return the new state.
410func! s:FreshState(lnum)
411 "{{{
412 " A state is to know ALL relevant details about the
413 " lines 1..a:lnum-1, initial calculating (here!) can be slow, but updating is
414 " fast (incremental).
415 " TODO: this should be split up in detecting the block type and computing the
416 " indent for the block type, so that when we do not know the indent we do
417 " not need to clear the whole state and re-detect the block type again.
418 " State:
419 " lnum last indented line == prevnonblank(a:lnum - 1)
420 " block = 0 a:lnum located within special tag: 0:none, 2:<pre>,
Bram Moolenaarca635012015-09-25 20:34:21 +0200421 " 3:<script>, 4:<style>, 5:<!--, 6:<!--[
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200422 " baseindent use this indent for line a:lnum as a start - kind of
423 " autoindent (if block==0)
424 " scripttype = '' type attribute of a script tag (if block==3)
425 " blocktagind indent for current opening (get) and closing (set)
426 " blocktag (if block!=0)
427 " blocklnr lnum of starting blocktag (if block!=0)
428 " inattr line {lnum} starts with attributes of a tag
429 let state = {}
430 let state.lnum = prevnonblank(a:lnum - 1)
431 let state.scripttype = ""
432 let state.blocktagind = -1
433 let state.block = 0
434 let state.baseindent = 0
435 let state.blocklnr = 0
436 let state.inattr = 0
437
438 if state.lnum == 0
439 return state
440 endif
441
442 " Heuristic:
443 " remember startline state.lnum
444 " look back for <pre, </pre, <script, </script, <style, </style tags
445 " remember stopline
446 " if opening tag found,
447 " assume a:lnum within block
448 " else
449 " look back in result range (stopline, startline) for comment
450 " \ delimiters (<!--, -->)
451 " if comment opener found,
452 " assume a:lnum within comment
453 " else
454 " assume usual html for a:lnum
455 " if a:lnum-1 has a closing comment
456 " look back to get indent of comment opener
457 " FI
458
459 " look back for a blocktag
Bram Moolenaarca635012015-09-25 20:34:21 +0200460 let stopline2 = v:lnum + 1
461 if has_key(b:hi_indent, 'block') && b:hi_indent.block > 5
462 let [stopline2, stopcol2] = searchpos('<!--', 'bnW')
463 endif
464 let [stopline, stopcol] = searchpos('\c<\zs\/\=\%(pre\>\|script\>\|style\>\)', "bnW")
465 if stopline > 0 && stopline < stopline2
466 " ugly ... why isn't there searchstr()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200467 let tagline = tolower(getline(stopline))
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200468 let blocktag = matchstr(tagline, '\/\=\%(pre\>\|script\>\|style\>\)', stopcol - 1)
469 if blocktag[0] != "/"
470 " opening tag found, assume a:lnum within block
471 let state.block = s:indent_tags[blocktag]
472 if state.block == 3
473 let state.scripttype = s:GetScriptType(matchstr(tagline, '\>[^>]*', stopcol))
474 endif
475 let state.blocklnr = stopline
476 " check preceding tags in the line:
477 call s:CountITags(tagline[: stopcol-2])
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200478 let state.blocktagind = indent(stopline) + (s:curind + s:nextrel) * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200479 return state
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200480 elseif stopline == state.lnum
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200481 " handle special case: previous line (= state.lnum) contains a
482 " closing blocktag which is preceded by line-noise;
483 " blocktag == "/..."
484 let swendtag = match(tagline, '^\s*</') >= 0
485 if !swendtag
Bram Moolenaarca635012015-09-25 20:34:21 +0200486 let [bline, bcol] = searchpos('<'.blocktag[1:].'\>', "bnW")
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200487 call s:CountITags(tolower(getline(bline)[: bcol-2]))
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200488 let state.baseindent = indent(bline) + (s:curind + s:nextrel) * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200489 return state
490 endif
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200491 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200492 endif
Bram Moolenaarca635012015-09-25 20:34:21 +0200493 if stopline > stopline2
494 let stopline = stopline2
495 let stopcol = stopcol2
496 endif
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200497
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200498 " else look back for comment
Bram Moolenaarca635012015-09-25 20:34:21 +0200499 let [comlnum, comcol, found] = searchpos('\(<!--\[\)\|\(<!--\)\|-->', 'bpnW', stopline)
500 if found == 2 || found == 3
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200501 " comment opener found, assume a:lnum within comment
Bram Moolenaarca635012015-09-25 20:34:21 +0200502 let state.block = (found == 3 ? 5 : 6)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200503 let state.blocklnr = comlnum
504 " check preceding tags in the line:
505 call s:CountITags(tolower(getline(comlnum)[: comcol-2]))
Bram Moolenaarca635012015-09-25 20:34:21 +0200506 if found == 2
507 let state.baseindent = b:hi_indent.baseindent
508 endif
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200509 let state.blocktagind = indent(comlnum) + (s:curind + s:nextrel) * shiftwidth()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200510 return state
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200511 endif
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200512
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200513 " else within usual HTML
514 let text = tolower(getline(state.lnum))
515
516 " Check a:lnum-1 for closing comment (we need indent from the opening line).
517 " Not when other tags follow (might be --> inside a string).
518 let comcol = stridx(text, '-->')
519 if comcol >= 0 && match(text, '[<>]', comcol) <= 0
520 call cursor(state.lnum, comcol + 1)
521 let [comlnum, comcol] = searchpos('<!--', 'bW')
522 if comlnum == state.lnum
523 let text = text[: comcol-2]
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200524 else
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200525 let text = tolower(getline(comlnum)[: comcol-2])
Bram Moolenaar071d4272004-06-13 20:20:40 +0000526 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200527 call s:CountITags(text)
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200528 let state.baseindent = indent(comlnum) + (s:curind + s:nextrel) * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200529 " TODO check tags that follow "-->"
530 return state
531 endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000532
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200533 " Check if the previous line starts with end tag.
534 let swendtag = match(text, '^\s*</') >= 0
535
536 " If previous line ended in a closing tag, line up with the opening tag.
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100537 if !swendtag && text =~ '</' . s:tagname . '\s*>\s*$'
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200538 call cursor(state.lnum, 99999)
Bram Moolenaar7b61a542014-08-23 15:31:19 +0200539 normal! F<
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200540 let start_lnum = HtmlIndent_FindStartTag()
541 if start_lnum > 0
542 let state.baseindent = indent(start_lnum)
543 if col('.') > 2
544 " check for tags before the matching opening tag.
545 let text = getline(start_lnum)
546 let swendtag = match(text, '^\s*</') >= 0
547 call s:CountITags(text[: col('.') - 2])
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200548 let state.baseindent += s:nextrel * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200549 if !swendtag
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200550 let state.baseindent += s:curind * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200551 endif
552 endif
553 return state
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200554 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200555 endif
556
557 " Else: no comments. Skip backwards to find the tag we're inside.
558 let [state.lnum, found] = HtmlIndent_FindTagStart(state.lnum)
559 " Check if that line starts with end tag.
560 let text = getline(state.lnum)
561 let swendtag = match(text, '^\s*</') >= 0
562 call s:CountITags(tolower(text))
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200563 let state.baseindent = indent(state.lnum) + s:nextrel * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200564 if !swendtag
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200565 let state.baseindent += s:curind * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200566 endif
567 return state
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200568endfunc "}}}
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200569
570" Indent inside a <pre> block: Keep indent as-is.
571func! s:Alien2()
572 "{{{
573 return -1
574endfunc "}}}
575
576" Return the indent inside a <script> block for javascript.
577func! s:Alien3()
578 "{{{
579 let lnum = prevnonblank(v:lnum - 1)
580 while lnum > 1 && getline(lnum) =~ '^\s*/[/*]'
581 " Skip over comments to avoid that cindent() aligns with the <script> tag
582 let lnum = prevnonblank(lnum - 1)
583 endwhile
584 if lnum == b:hi_indent.blocklnr
585 " indent for the first line after <script>
586 return eval(b:hi_js1indent)
587 endif
588 if b:hi_indent.scripttype == "javascript"
Bram Moolenaar690afe12017-01-28 18:34:47 +0100589 return GetJavascriptIndent()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200590 else
591 return -1
592 endif
593endfunc "}}}
594
595" Return the indent inside a <style> block.
596func! s:Alien4()
597 "{{{
598 if prevnonblank(v:lnum-1) == b:hi_indent.blocklnr
599 " indent for first content line
600 return eval(b:hi_css1indent)
601 endif
602 return s:CSSIndent()
603endfunc "}}}
604
605" Indending inside a <style> block. Returns the indent.
606func! s:CSSIndent()
607 "{{{
608 " This handles standard CSS and also Closure stylesheets where special lines
609 " start with @.
610 " When the line starts with '*' or the previous line starts with "/*"
611 " and does not end in "*/", use C indenting to format the comment.
612 " Adopted $VIMRUNTIME/indent/css.vim
613 let curtext = getline(v:lnum)
614 if curtext =~ '^\s*[*]'
615 \ || (v:lnum > 1 && getline(v:lnum - 1) =~ '\s*/\*'
616 \ && getline(v:lnum - 1) !~ '\*/\s*$')
617 return cindent(v:lnum)
618 endif
619
620 let min_lnum = b:hi_indent.blocklnr
621 let prev_lnum = s:CssPrevNonComment(v:lnum - 1, min_lnum)
622 let [prev_lnum, found] = HtmlIndent_FindTagStart(prev_lnum)
623 if prev_lnum <= min_lnum
624 " Just below the <style> tag, indent for first content line after comments.
625 return eval(b:hi_css1indent)
626 endif
627
Bram Moolenaarba3ff532018-11-04 14:45:49 +0100628 " If the current line starts with "}" align with its match.
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200629 if curtext =~ '^\s*}'
630 call cursor(v:lnum, 1)
631 try
632 normal! %
633 " Found the matching "{", align with it after skipping unfinished lines.
634 let align_lnum = s:CssFirstUnfinished(line('.'), min_lnum)
635 return indent(align_lnum)
636 catch
637 " can't find it, try something else, but it's most likely going to be
638 " wrong
639 endtry
640 endif
641
642 " add indent after {
643 let brace_counts = HtmlIndent_CountBraces(prev_lnum)
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200644 let extra = brace_counts.c_open * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200645
646 let prev_text = getline(prev_lnum)
647 let below_end_brace = prev_text =~ '}\s*$'
648
649 " Search back to align with the first line that's unfinished.
650 let align_lnum = s:CssFirstUnfinished(prev_lnum, min_lnum)
651
652 " Handle continuation lines if aligning with previous line and not after a
653 " "}".
654 if extra == 0 && align_lnum == prev_lnum && !below_end_brace
655 let prev_hasfield = prev_text =~ '^\s*[a-zA-Z0-9-]\+:'
656 let prev_special = prev_text =~ '^\s*\(/\*\|@\)'
657 if curtext =~ '^\s*\(/\*\|@\)'
658 " if the current line is not a comment or starts with @ (used by template
659 " systems) reduce indent if previous line is a continuation line
660 if !prev_hasfield && !prev_special
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200661 let extra = -shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200662 endif
663 else
664 let cur_hasfield = curtext =~ '^\s*[a-zA-Z0-9-]\+:'
665 let prev_unfinished = s:CssUnfinished(prev_text)
Bram Moolenaarfc65cab2018-08-28 22:58:02 +0200666 if prev_unfinished
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200667 " Continuation line has extra indent if the previous line was not a
668 " continuation line.
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200669 let extra = shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200670 " Align with @if
671 if prev_text =~ '^\s*@if '
672 let extra = 4
673 endif
674 elseif cur_hasfield && !prev_hasfield && !prev_special
675 " less indent below a continuation line
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200676 let extra = -shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200677 endif
678 endif
679 endif
680
681 if below_end_brace
682 " find matching {, if that line starts with @ it's not the start of a rule
683 " but something else from a template system
684 call cursor(prev_lnum, 1)
685 call search('}\s*$')
686 try
687 normal! %
688 " Found the matching "{", align with it.
689 let align_lnum = s:CssFirstUnfinished(line('.'), min_lnum)
690 let special = getline(align_lnum) =~ '^\s*@'
691 catch
692 let special = 0
693 endtry
694 if special
695 " do not reduce indent below @{ ... }
696 if extra < 0
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200697 let extra += shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200698 endif
699 else
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200700 let extra -= (brace_counts.c_close - (prev_text =~ '^\s*}')) * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200701 endif
702 endif
703
704 " if no extra indent yet...
705 if extra == 0
706 if brace_counts.p_open > brace_counts.p_close
707 " previous line has more ( than ): add a shiftwidth
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200708 let extra = shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200709 elseif brace_counts.p_open < brace_counts.p_close
710 " previous line has more ) than (: subtract a shiftwidth
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200711 let extra = -shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200712 endif
713 endif
714
715 return indent(align_lnum) + extra
716endfunc "}}}
717
718" Inside <style>: Whether a line is unfinished.
Bram Moolenaarfc65cab2018-08-28 22:58:02 +0200719" tag:
720" tag: blah
721" tag: blah &&
722" tag: blah ||
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200723func! s:CssUnfinished(text)
724 "{{{
Bram Moolenaarfc65cab2018-08-28 22:58:02 +0200725 return a:text =~ '\(||\|&&\|:\|\k\)\s*$'
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200726endfunc "}}}
727
728" Search back for the first unfinished line above "lnum".
729func! s:CssFirstUnfinished(lnum, min_lnum)
730 "{{{
731 let align_lnum = a:lnum
732 while align_lnum > a:min_lnum && s:CssUnfinished(getline(align_lnum - 1))
733 let align_lnum -= 1
734 endwhile
735 return align_lnum
736endfunc "}}}
737
738" Find the non-empty line at or before "lnum" that is not a comment.
739func! s:CssPrevNonComment(lnum, stopline)
740 "{{{
741 " caller starts from a line a:lnum + 1 that is not a comment
742 let lnum = prevnonblank(a:lnum)
743 while 1
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200744 let ccol = match(getline(lnum), '\*/')
745 if ccol < 0
Bram Moolenaar3e496b02016-09-25 22:11:48 +0200746 " No comment end thus it's something else.
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200747 return lnum
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200748 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200749 call cursor(lnum, ccol + 1)
750 " Search back for the /* that starts the comment
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200751 let lnum = search('/\*', 'bW', a:stopline)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200752 if indent(".") == virtcol(".") - 1
753 " The found /* is at the start of the line. Now go back to the line
754 " above it and again check if it is a comment.
755 let lnum = prevnonblank(lnum - 1)
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200756 else
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200757 " /* is after something else, thus it's not a comment line.
758 return lnum
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200759 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200760 endwhile
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200761endfunc "}}}
762
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200763" Check the number of {} and () in line "lnum". Return a dict with the counts.
764func! HtmlIndent_CountBraces(lnum)
765 "{{{
766 let brs = substitute(getline(a:lnum), '[''"].\{-}[''"]\|/\*.\{-}\*/\|/\*.*$\|[^{}()]', '', 'g')
767 let c_open = 0
768 let c_close = 0
769 let p_open = 0
770 let p_close = 0
771 for brace in split(brs, '\zs')
772 if brace == "{"
773 let c_open += 1
774 elseif brace == "}"
775 if c_open > 0
776 let c_open -= 1
777 else
778 let c_close += 1
779 endif
780 elseif brace == '('
781 let p_open += 1
782 elseif brace == ')'
783 if p_open > 0
784 let p_open -= 1
785 else
786 let p_close += 1
787 endif
788 endif
789 endfor
790 return {'c_open': c_open,
791 \ 'c_close': c_close,
792 \ 'p_open': p_open,
793 \ 'p_close': p_close}
794endfunc "}}}
795
796" Return the indent for a comment: <!-- -->
797func! s:Alien5()
798 "{{{
799 let curtext = getline(v:lnum)
800 if curtext =~ '^\s*\zs-->'
801 " current line starts with end of comment, line up with comment start.
802 call cursor(v:lnum, 0)
803 let lnum = search('<!--', 'b')
804 if lnum > 0
805 " TODO: what if <!-- is not at the start of the line?
806 return indent(lnum)
807 endif
808
809 " Strange, can't find it.
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200810 return -1
Bram Moolenaar946e27a2014-06-25 18:50:27 +0200811 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200812
813 let prevlnum = prevnonblank(v:lnum - 1)
814 let prevtext = getline(prevlnum)
815 let idx = match(prevtext, '^\s*\zs<!--')
816 if idx >= 0
817 " just below comment start, add a shiftwidth
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200818 return idx + shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200819 endif
820
821 " Some files add 4 spaces just below a TODO line. It's difficult to detect
822 " the end of the TODO, so let's not do that.
823
824 " Align with the previous non-blank line.
825 return indent(prevlnum)
Bram Moolenaar946e27a2014-06-25 18:50:27 +0200826endfunc "}}}
827
Bram Moolenaarca635012015-09-25 20:34:21 +0200828" Return the indent for conditional comment: <!--[ ![endif]-->
829func! s:Alien6()
830 "{{{
831 let curtext = getline(v:lnum)
832 if curtext =~ '\s*\zs<!\[endif\]-->'
833 " current line starts with end of comment, line up with comment start.
834 let lnum = search('<!--', 'bn')
835 if lnum > 0
836 return indent(lnum)
837 endif
838 endif
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200839 return b:hi_indent.baseindent + shiftwidth()
Bram Moolenaarca635012015-09-25 20:34:21 +0200840endfunc "}}}
841
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200842" When the "lnum" line ends in ">" find the line containing the matching "<".
843func! HtmlIndent_FindTagStart(lnum)
844 "{{{
845 " Avoids using the indent of a continuation line.
846 " Moves the cursor.
847 " Return two values:
848 " - the matching line number or "lnum".
849 " - a flag indicating whether we found the end of a tag.
850 " This method is global so that HTML-like indenters can use it.
851 " To avoid matching " > " or " < " inside a string require that the opening
852 " "<" is followed by a word character and the closing ">" comes after a
853 " non-white character.
854 let idx = match(getline(a:lnum), '\S>\s*$')
855 if idx > 0
856 call cursor(a:lnum, idx)
857 let lnum = searchpair('<\w', '' , '\S>', 'bW', '', max([a:lnum - b:html_indent_line_limit, 0]))
858 if lnum > 0
859 return [lnum, 1]
Bram Moolenaar071d4272004-06-13 20:20:40 +0000860 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200861 endif
862 return [a:lnum, 0]
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200863endfunc "}}}
Bram Moolenaar071d4272004-06-13 20:20:40 +0000864
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200865" Find the unclosed start tag from the current cursor position.
866func! HtmlIndent_FindStartTag()
867 "{{{
868 " The cursor must be on or before a closing tag.
869 " If found, positions the cursor at the match and returns the line number.
870 " Otherwise returns 0.
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100871 let tagname = matchstr(getline('.')[col('.') - 1:], '</\zs' . s:tagname . '\ze')
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200872 let start_lnum = searchpair('<' . tagname . '\>', '', '</' . tagname . '\>', 'bW')
873 if start_lnum > 0
874 return start_lnum
875 endif
876 return 0
877endfunc "}}}
Bram Moolenaar071d4272004-06-13 20:20:40 +0000878
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200879" Moves the cursor from a "<" to the matching ">".
880func! HtmlIndent_FindTagEnd()
881 "{{{
882 " Call this with the cursor on the "<" of a start tag.
883 " This will move the cursor to the ">" of the matching end tag or, when it's
884 " a self-closing tag, to the matching ">".
885 " Limited to look up to b:html_indent_line_limit lines away.
886 let text = getline('.')
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100887 let tagname = matchstr(text, s:tagname . '\|!--', col('.'))
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200888 if tagname == '!--'
889 call search('--\zs>')
890 elseif s:get_tag('/' . tagname) != 0
891 " tag with a closing tag, find matching "</tag>"
892 call searchpair('<' . tagname, '', '</' . tagname . '\zs>', 'W', '', line('.') + b:html_indent_line_limit)
893 else
894 " self-closing tag, find the ">"
895 call search('\S\zs>')
896 endif
897endfunc "}}}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200898
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200899" Indenting inside a start tag. Return the correct indent or -1 if unknown.
900func! s:InsideTag(foundHtmlString)
901 "{{{
902 if a:foundHtmlString
903 " Inside an attribute string.
904 " Align with the previous line or use an external function.
905 let lnum = v:lnum - 1
906 if lnum > 1
907 if exists('b:html_indent_tag_string_func')
908 return b:html_indent_tag_string_func(lnum)
909 endif
910 return indent(lnum)
911 endif
912 endif
913
914 " Should be another attribute: " attr="val". Align with the previous
915 " attribute start.
916 let lnum = v:lnum
917 while lnum > 1
918 let lnum -= 1
919 let text = getline(lnum)
920 " Find a match with one of these, align with "attr":
921 " attr=
922 " <tag attr=
923 " text<tag attr=
924 " <tag>text</tag>text<tag attr=
925 " For long lines search for the first match, finding the last match
926 " gets very slow.
927 if len(text) < 300
928 let idx = match(text, '.*\s\zs[_a-zA-Z0-9-]\+="')
929 else
930 let idx = match(text, '\s\zs[_a-zA-Z0-9-]\+="')
931 endif
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100932 if idx == -1
933 " try <tag attr
934 let idx = match(text, '<' . s:tagname . '\s\+\zs\w')
935 endif
936 if idx == -1
937 " after just <tag indent one level more
938 let idx = match(text, '<' . s:tagname . '$')
939 if idx >= 0
940 call cursor(lnum, idx)
941 return virtcol('.') + shiftwidth()
942 endif
943 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200944 if idx > 0
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100945 " Found the attribute to align with.
946 call cursor(lnum, idx)
947 return virtcol('.')
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200948 endif
949 endwhile
950 return -1
951endfunc "}}}
952
953" THE MAIN INDENT FUNCTION. Return the amount of indent for v:lnum.
954func! HtmlIndent()
955 "{{{
Bram Moolenaar9da7ff72015-01-14 12:52:36 +0100956 if prevnonblank(v:lnum - 1) < 1
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200957 " First non-blank line has no indent.
958 return 0
959 endif
960
961 let curtext = tolower(getline(v:lnum))
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200962 let indentunit = shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200963
964 let b:hi_newstate = {}
965 let b:hi_newstate.lnum = v:lnum
966
967 " When syntax HL is enabled, detect we are inside a tag. Indenting inside
968 " a tag works very differently. Do not do this when the line starts with
969 " "<", it gets the "htmlTag" ID but we are not inside a tag then.
970 if curtext !~ '^\s*<'
Bram Moolenaar7b61a542014-08-23 15:31:19 +0200971 normal! ^
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200972 let stack = synstack(v:lnum, col('.')) " assumes there are no tabs
973 let foundHtmlString = 0
974 for synid in reverse(stack)
975 let name = synIDattr(synid, "name")
976 if index(b:hi_insideStringNames, name) >= 0
977 let foundHtmlString = 1
978 elseif index(b:hi_insideTagNames, name) >= 0
979 " Yes, we are inside a tag.
980 let indent = s:InsideTag(foundHtmlString)
981 if indent >= 0
982 " Do not keep the state. TODO: could keep the block type.
983 let b:hi_indent.lnum = 0
984 return indent
985 endif
986 endif
987 endfor
988 endif
989
990 " does the line start with a closing tag?
991 let swendtag = match(curtext, '^\s*</') >= 0
992
993 if prevnonblank(v:lnum - 1) == b:hi_indent.lnum && b:hi_lasttick == b:changedtick - 1
994 " use state (continue from previous line)
995 else
996 " start over (know nothing)
997 let b:hi_indent = s:FreshState(v:lnum)
998 endif
999
1000 if b:hi_indent.block >= 2
1001 " within block
1002 let endtag = s:endtags[b:hi_indent.block]
1003 let blockend = stridx(curtext, endtag)
1004 if blockend >= 0
1005 " block ends here
1006 let b:hi_newstate.block = 0
1007 " calc indent for REST OF LINE (may start more blocks):
1008 call s:CountTagsAndState(strpart(curtext, blockend + strlen(endtag)))
1009 if swendtag && b:hi_indent.block != 5
1010 let indent = b:hi_indent.blocktagind + s:curind * indentunit
1011 let b:hi_newstate.baseindent = indent + s:nextrel * indentunit
1012 else
1013 let indent = s:Alien{b:hi_indent.block}()
1014 let b:hi_newstate.baseindent = b:hi_indent.blocktagind + s:nextrel * indentunit
1015 endif
1016 else
1017 " block continues
1018 " indent this line with alien method
1019 let indent = s:Alien{b:hi_indent.block}()
1020 endif
1021 else
1022 " not within a block - within usual html
1023 let b:hi_newstate.block = b:hi_indent.block
1024 if swendtag
1025 " The current line starts with an end tag, align with its start tag.
1026 call cursor(v:lnum, 1)
1027 let start_lnum = HtmlIndent_FindStartTag()
1028 if start_lnum > 0
1029 " check for the line starting with something inside a tag:
1030 " <sometag <- align here
1031 " attr=val><open> not here
1032 let text = getline(start_lnum)
1033 let angle = matchstr(text, '[<>]')
1034 if angle == '>'
1035 call cursor(start_lnum, 1)
1036 normal! f>%
1037 let start_lnum = line('.')
1038 let text = getline(start_lnum)
1039 endif
1040
1041 let indent = indent(start_lnum)
1042 if col('.') > 2
1043 let swendtag = match(text, '^\s*</') >= 0
1044 call s:CountITags(text[: col('.') - 2])
Bram Moolenaar3ec574f2017-06-13 18:12:01 +02001045 let indent += s:nextrel * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +02001046 if !swendtag
Bram Moolenaar3ec574f2017-06-13 18:12:01 +02001047 let indent += s:curind * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +02001048 endif
1049 endif
1050 else
1051 " not sure what to do
1052 let indent = b:hi_indent.baseindent
1053 endif
1054 let b:hi_newstate.baseindent = indent
1055 else
1056 call s:CountTagsAndState(curtext)
1057 let indent = b:hi_indent.baseindent
1058 let b:hi_newstate.baseindent = indent + (s:curind + s:nextrel) * indentunit
1059 endif
1060 endif
1061
1062 let b:hi_lasttick = b:changedtick
1063 call extend(b:hi_indent, b:hi_newstate, "force")
1064 return indent
1065endfunc "}}}
1066
1067" Check user settings when loading this script the first time.
Bram Moolenaarec7944a2013-06-12 21:29:15 +02001068call HtmlIndent_CheckUserSettings()
Bram Moolenaar071d4272004-06-13 20:20:40 +00001069
Bram Moolenaar91170f82006-05-05 21:15:17 +00001070let &cpo = s:cpo_save
1071unlet s:cpo_save
1072
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +02001073" vim: fdm=marker ts=8 sw=2 tw=78