blob: b105dcabc9f708809a6acb307e426485f0446146 [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 Moolenaarb5b75622018-03-09 22:22:21 +01005" Last Change: 2018 Mar 09
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
219" the matching <p>, but not the other way around).
220" Old HTML tags:
221call s:AddITags(s:indent_tags, [
222 \ 'a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big',
223 \ 'blockquote', 'body', 'button', 'caption', 'center', 'cite', 'code',
224 \ 'colgroup', 'del', 'dfn', 'dir', 'div', 'dl', 'em', 'fieldset', 'font',
225 \ 'form', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'html',
226 \ 'i', 'iframe', 'ins', 'kbd', 'label', 'legend', 'li',
227 \ 'map', 'menu', 'noframes', 'noscript', 'object', 'ol',
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200228 \ 'optgroup', 'q', 's', 'samp', 'select', 'small', 'span', 'strong', 'sub',
229 \ 'sup', 'table', 'textarea', 'title', 'tt', 'u', 'ul', 'var', 'th', 'td',
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200230 \ 'tr', 'tbody', 'tfoot', 'thead'])
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200231
Bram Moolenaar939a1ab2016-04-10 01:31:25 +0200232" New HTML5 elements:
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200233call s:AddITags(s:indent_tags, [
234 \ 'area', 'article', 'aside', 'audio', 'bdi', 'canvas',
Bram Moolenaar939a1ab2016-04-10 01:31:25 +0200235 \ 'command', 'data', 'datalist', 'details', 'embed', 'figcaption',
236 \ 'figure', 'footer', 'header', 'keygen', 'mark', 'meter', 'nav', 'output',
237 \ 'progress', 'rp', 'rt', 'ruby', 'section', 'source', 'summary', 'svg',
238 \ 'time', 'track', 'video', 'wbr'])
Bram Moolenaard8b77f72015-03-05 21:21:19 +0100239
240" Tags added for web components:
241call s:AddITags(s:indent_tags, [
242 \ 'content', 'shadow', 'template'])
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200243"}}}
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200244
245" Add Block Tags: these contain alien content
246"{{{
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200247call s:AddBlockTag('pre', 2)
248call s:AddBlockTag('script', 3)
249call s:AddBlockTag('style', 4)
250call s:AddBlockTag('<!--', 5, '-->')
Bram Moolenaarca635012015-09-25 20:34:21 +0200251call s:AddBlockTag('<!--[', 6, '![endif]-->')
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200252"}}}
253
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200254" Return non-zero when "tagname" is an opening tag, not being a block tag, for
255" which there should be a closing tag. Can be used by scripts that include
256" HTML indenting.
257func! HtmlIndent_IsOpenTag(tagname)
258 "{{{
259 if get(s:indent_tags, a:tagname) == 1
260 return 1
261 endif
262 return get(b:hi_tags, a:tagname) == 1
263endfunc "}}}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200264
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200265" Get the value for "tagname", taking care of buffer-local tags.
266func! s:get_tag(tagname)
267 "{{{
268 let i = get(s:indent_tags, a:tagname)
269 if (i == 1 || i == -1) && get(b:hi_removed_tags, a:tagname) != 0
270 return 0
271 endif
272 if i == 0
273 let i = get(b:hi_tags, a:tagname)
274 endif
275 return i
276endfunc "}}}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200277
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200278" Count the number of start and end tags in "text".
279func! s:CountITags(text)
280 "{{{
281 " Store the result in s:curind and s:nextrel.
282 let s:curind = 0 " relative indent steps for current line [unit &sw]:
283 let s:nextrel = 0 " relative indent steps for next line [unit &sw]:
284 let s:block = 0 " assume starting outside of a block
285 let s:countonly = 1 " don't change state
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100286 call substitute(a:text, '<\zs/\=' . s:tagname . '\>\|<!--\[\|\[endif\]-->\|<!--\|-->', '\=s:CheckTag(submatch(0))', 'g')
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200287 let s:countonly = 0
288endfunc "}}}
289
290" Count the number of start and end tags in text.
291func! s:CountTagsAndState(text)
292 "{{{
293 " Store the result in s:curind and s:nextrel. Update b:hi_newstate.block.
294 let s:curind = 0 " relative indent steps for current line [unit &sw]:
295 let s:nextrel = 0 " relative indent steps for next line [unit &sw]:
296
297 let s:block = b:hi_newstate.block
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100298 let tmp = substitute(a:text, '<\zs/\=' . s:tagname . '\>\|<!--\[\|\[endif\]-->\|<!--\|-->', '\=s:CheckTag(submatch(0))', 'g')
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200299 if s:block == 3
300 let b:hi_newstate.scripttype = s:GetScriptType(matchstr(tmp, '\C.*<SCRIPT\>\zs[^>]*'))
301 endif
302 let b:hi_newstate.block = s:block
303endfunc "}}}
304
305" Used by s:CountITags() and s:CountTagsAndState().
306func! s:CheckTag(itag)
307 "{{{
308 " Returns an empty string or "SCRIPT".
309 " a:itag can be "tag" or "/tag" or "<!--" or "-->"
Bram Moolenaard8b77f72015-03-05 21:21:19 +0100310 if (s:CheckCustomTag(a:itag))
311 return ""
312 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200313 let ind = s:get_tag(a:itag)
314 if ind == -1
315 " closing tag
316 if s:block != 0
317 " ignore itag within a block
318 return ""
319 endif
320 if s:nextrel == 0
321 let s:curind -= 1
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200322 else
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200323 let s:nextrel -= 1
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200324 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200325 elseif ind == 1
326 " opening tag
327 if s:block != 0
328 return ""
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200329 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200330 let s:nextrel += 1
331 elseif ind != 0
332 " block-tag (opening or closing)
333 return s:CheckBlockTag(a:itag, ind)
334 " else ind==0 (other tag found): keep indent
335 endif
336 return ""
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200337endfunc "}}}
338
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200339" Used by s:CheckTag(). Returns an empty string or "SCRIPT".
340func! s:CheckBlockTag(blocktag, ind)
341 "{{{
342 if a:ind > 0
343 " a block starts here
344 if s:block != 0
345 " already in a block (nesting) - ignore
346 " especially ignore comments after other blocktags
347 return ""
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200348 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200349 let s:block = a:ind " block type
350 if s:countonly
351 return ""
352 endif
353 let b:hi_newstate.blocklnr = v:lnum
354 " save allover indent for the endtag
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200355 let b:hi_newstate.blocktagind = b:hi_indent.baseindent + (s:nextrel + s:curind) * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200356 if a:ind == 3
357 return "SCRIPT" " all except this must be lowercase
358 " line is to be checked again for the type attribute
359 endif
360 else
361 let s:block = 0
362 " we get here if starting and closing a block-tag on the same line
363 endif
364 return ""
365endfunc "}}}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200366
Bram Moolenaard8b77f72015-03-05 21:21:19 +0100367" Used by s:CheckTag().
368func! s:CheckCustomTag(ctag)
369 "{{{
370 " Returns 1 if ctag is the tag for a custom element, 0 otherwise.
371 " a:ctag can be "tag" or "/tag" or "<!--" or "-->"
372 let pattern = '\%\(\w\+-\)\+\w\+'
373 if match(a:ctag, pattern) == -1
374 return 0
375 endif
376 if matchstr(a:ctag, '\/\ze.\+') == "/"
377 " closing tag
378 if s:block != 0
379 " ignore ctag within a block
380 return 1
381 endif
382 if s:nextrel == 0
383 let s:curind -= 1
384 else
385 let s:nextrel -= 1
386 endif
387 else
388 " opening tag
389 if s:block != 0
390 return 1
391 endif
392 let s:nextrel += 1
393 endif
394 return 1
395endfunc "}}}
396
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200397" Return the <script> type: either "javascript" or ""
398func! s:GetScriptType(str)
399 "{{{
400 if a:str == "" || a:str =~ "java"
401 return "javascript"
402 else
403 return ""
404 endif
405endfunc "}}}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200406
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200407" Look back in the file, starting at a:lnum - 1, to compute a state for the
408" start of line a:lnum. Return the new state.
409func! s:FreshState(lnum)
410 "{{{
411 " A state is to know ALL relevant details about the
412 " lines 1..a:lnum-1, initial calculating (here!) can be slow, but updating is
413 " fast (incremental).
414 " TODO: this should be split up in detecting the block type and computing the
415 " indent for the block type, so that when we do not know the indent we do
416 " not need to clear the whole state and re-detect the block type again.
417 " State:
418 " lnum last indented line == prevnonblank(a:lnum - 1)
419 " block = 0 a:lnum located within special tag: 0:none, 2:<pre>,
Bram Moolenaarca635012015-09-25 20:34:21 +0200420 " 3:<script>, 4:<style>, 5:<!--, 6:<!--[
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200421 " baseindent use this indent for line a:lnum as a start - kind of
422 " autoindent (if block==0)
423 " scripttype = '' type attribute of a script tag (if block==3)
424 " blocktagind indent for current opening (get) and closing (set)
425 " blocktag (if block!=0)
426 " blocklnr lnum of starting blocktag (if block!=0)
427 " inattr line {lnum} starts with attributes of a tag
428 let state = {}
429 let state.lnum = prevnonblank(a:lnum - 1)
430 let state.scripttype = ""
431 let state.blocktagind = -1
432 let state.block = 0
433 let state.baseindent = 0
434 let state.blocklnr = 0
435 let state.inattr = 0
436
437 if state.lnum == 0
438 return state
439 endif
440
441 " Heuristic:
442 " remember startline state.lnum
443 " look back for <pre, </pre, <script, </script, <style, </style tags
444 " remember stopline
445 " if opening tag found,
446 " assume a:lnum within block
447 " else
448 " look back in result range (stopline, startline) for comment
449 " \ delimiters (<!--, -->)
450 " if comment opener found,
451 " assume a:lnum within comment
452 " else
453 " assume usual html for a:lnum
454 " if a:lnum-1 has a closing comment
455 " look back to get indent of comment opener
456 " FI
457
458 " look back for a blocktag
Bram Moolenaarca635012015-09-25 20:34:21 +0200459 let stopline2 = v:lnum + 1
460 if has_key(b:hi_indent, 'block') && b:hi_indent.block > 5
461 let [stopline2, stopcol2] = searchpos('<!--', 'bnW')
462 endif
463 let [stopline, stopcol] = searchpos('\c<\zs\/\=\%(pre\>\|script\>\|style\>\)', "bnW")
464 if stopline > 0 && stopline < stopline2
465 " ugly ... why isn't there searchstr()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200466 let tagline = tolower(getline(stopline))
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200467 let blocktag = matchstr(tagline, '\/\=\%(pre\>\|script\>\|style\>\)', stopcol - 1)
468 if blocktag[0] != "/"
469 " opening tag found, assume a:lnum within block
470 let state.block = s:indent_tags[blocktag]
471 if state.block == 3
472 let state.scripttype = s:GetScriptType(matchstr(tagline, '\>[^>]*', stopcol))
473 endif
474 let state.blocklnr = stopline
475 " check preceding tags in the line:
476 call s:CountITags(tagline[: stopcol-2])
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200477 let state.blocktagind = indent(stopline) + (s:curind + s:nextrel) * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200478 return state
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200479 elseif stopline == state.lnum
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200480 " handle special case: previous line (= state.lnum) contains a
481 " closing blocktag which is preceded by line-noise;
482 " blocktag == "/..."
483 let swendtag = match(tagline, '^\s*</') >= 0
484 if !swendtag
Bram Moolenaarca635012015-09-25 20:34:21 +0200485 let [bline, bcol] = searchpos('<'.blocktag[1:].'\>', "bnW")
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200486 call s:CountITags(tolower(getline(bline)[: bcol-2]))
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200487 let state.baseindent = indent(bline) + (s:curind + s:nextrel) * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200488 return state
489 endif
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200490 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200491 endif
Bram Moolenaarca635012015-09-25 20:34:21 +0200492 if stopline > stopline2
493 let stopline = stopline2
494 let stopcol = stopcol2
495 endif
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200496
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200497 " else look back for comment
Bram Moolenaarca635012015-09-25 20:34:21 +0200498 let [comlnum, comcol, found] = searchpos('\(<!--\[\)\|\(<!--\)\|-->', 'bpnW', stopline)
499 if found == 2 || found == 3
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200500 " comment opener found, assume a:lnum within comment
Bram Moolenaarca635012015-09-25 20:34:21 +0200501 let state.block = (found == 3 ? 5 : 6)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200502 let state.blocklnr = comlnum
503 " check preceding tags in the line:
504 call s:CountITags(tolower(getline(comlnum)[: comcol-2]))
Bram Moolenaarca635012015-09-25 20:34:21 +0200505 if found == 2
506 let state.baseindent = b:hi_indent.baseindent
507 endif
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200508 let state.blocktagind = indent(comlnum) + (s:curind + s:nextrel) * shiftwidth()
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200509 return state
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200510 endif
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200511
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200512 " else within usual HTML
513 let text = tolower(getline(state.lnum))
514
515 " Check a:lnum-1 for closing comment (we need indent from the opening line).
516 " Not when other tags follow (might be --> inside a string).
517 let comcol = stridx(text, '-->')
518 if comcol >= 0 && match(text, '[<>]', comcol) <= 0
519 call cursor(state.lnum, comcol + 1)
520 let [comlnum, comcol] = searchpos('<!--', 'bW')
521 if comlnum == state.lnum
522 let text = text[: comcol-2]
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200523 else
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200524 let text = tolower(getline(comlnum)[: comcol-2])
Bram Moolenaar071d4272004-06-13 20:20:40 +0000525 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200526 call s:CountITags(text)
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200527 let state.baseindent = indent(comlnum) + (s:curind + s:nextrel) * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200528 " TODO check tags that follow "-->"
529 return state
530 endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000531
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200532 " Check if the previous line starts with end tag.
533 let swendtag = match(text, '^\s*</') >= 0
534
535 " If previous line ended in a closing tag, line up with the opening tag.
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100536 if !swendtag && text =~ '</' . s:tagname . '\s*>\s*$'
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200537 call cursor(state.lnum, 99999)
Bram Moolenaar7b61a542014-08-23 15:31:19 +0200538 normal! F<
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200539 let start_lnum = HtmlIndent_FindStartTag()
540 if start_lnum > 0
541 let state.baseindent = indent(start_lnum)
542 if col('.') > 2
543 " check for tags before the matching opening tag.
544 let text = getline(start_lnum)
545 let swendtag = match(text, '^\s*</') >= 0
546 call s:CountITags(text[: col('.') - 2])
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200547 let state.baseindent += s:nextrel * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200548 if !swendtag
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200549 let state.baseindent += s:curind * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200550 endif
551 endif
552 return state
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200553 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200554 endif
555
556 " Else: no comments. Skip backwards to find the tag we're inside.
557 let [state.lnum, found] = HtmlIndent_FindTagStart(state.lnum)
558 " Check if that line starts with end tag.
559 let text = getline(state.lnum)
560 let swendtag = match(text, '^\s*</') >= 0
561 call s:CountITags(tolower(text))
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200562 let state.baseindent = indent(state.lnum) + s:nextrel * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200563 if !swendtag
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200564 let state.baseindent += s:curind * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200565 endif
566 return state
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200567endfunc "}}}
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200568
569" Indent inside a <pre> block: Keep indent as-is.
570func! s:Alien2()
571 "{{{
572 return -1
573endfunc "}}}
574
575" Return the indent inside a <script> block for javascript.
576func! s:Alien3()
577 "{{{
578 let lnum = prevnonblank(v:lnum - 1)
579 while lnum > 1 && getline(lnum) =~ '^\s*/[/*]'
580 " Skip over comments to avoid that cindent() aligns with the <script> tag
581 let lnum = prevnonblank(lnum - 1)
582 endwhile
583 if lnum == b:hi_indent.blocklnr
584 " indent for the first line after <script>
585 return eval(b:hi_js1indent)
586 endif
587 if b:hi_indent.scripttype == "javascript"
Bram Moolenaar690afe12017-01-28 18:34:47 +0100588 return GetJavascriptIndent()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200589 else
590 return -1
591 endif
592endfunc "}}}
593
594" Return the indent inside a <style> block.
595func! s:Alien4()
596 "{{{
597 if prevnonblank(v:lnum-1) == b:hi_indent.blocklnr
598 " indent for first content line
599 return eval(b:hi_css1indent)
600 endif
601 return s:CSSIndent()
602endfunc "}}}
603
604" Indending inside a <style> block. Returns the indent.
605func! s:CSSIndent()
606 "{{{
607 " This handles standard CSS and also Closure stylesheets where special lines
608 " start with @.
609 " When the line starts with '*' or the previous line starts with "/*"
610 " and does not end in "*/", use C indenting to format the comment.
611 " Adopted $VIMRUNTIME/indent/css.vim
612 let curtext = getline(v:lnum)
613 if curtext =~ '^\s*[*]'
614 \ || (v:lnum > 1 && getline(v:lnum - 1) =~ '\s*/\*'
615 \ && getline(v:lnum - 1) !~ '\*/\s*$')
616 return cindent(v:lnum)
617 endif
618
619 let min_lnum = b:hi_indent.blocklnr
620 let prev_lnum = s:CssPrevNonComment(v:lnum - 1, min_lnum)
621 let [prev_lnum, found] = HtmlIndent_FindTagStart(prev_lnum)
622 if prev_lnum <= min_lnum
623 " Just below the <style> tag, indent for first content line after comments.
624 return eval(b:hi_css1indent)
625 endif
626
627 " If the current line starts with "}" align with it's match.
628 if curtext =~ '^\s*}'
629 call cursor(v:lnum, 1)
630 try
631 normal! %
632 " Found the matching "{", align with it after skipping unfinished lines.
633 let align_lnum = s:CssFirstUnfinished(line('.'), min_lnum)
634 return indent(align_lnum)
635 catch
636 " can't find it, try something else, but it's most likely going to be
637 " wrong
638 endtry
639 endif
640
641 " add indent after {
642 let brace_counts = HtmlIndent_CountBraces(prev_lnum)
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200643 let extra = brace_counts.c_open * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200644
645 let prev_text = getline(prev_lnum)
646 let below_end_brace = prev_text =~ '}\s*$'
647
648 " Search back to align with the first line that's unfinished.
649 let align_lnum = s:CssFirstUnfinished(prev_lnum, min_lnum)
650
651 " Handle continuation lines if aligning with previous line and not after a
652 " "}".
653 if extra == 0 && align_lnum == prev_lnum && !below_end_brace
654 let prev_hasfield = prev_text =~ '^\s*[a-zA-Z0-9-]\+:'
655 let prev_special = prev_text =~ '^\s*\(/\*\|@\)'
656 if curtext =~ '^\s*\(/\*\|@\)'
657 " if the current line is not a comment or starts with @ (used by template
658 " systems) reduce indent if previous line is a continuation line
659 if !prev_hasfield && !prev_special
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200660 let extra = -shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200661 endif
662 else
663 let cur_hasfield = curtext =~ '^\s*[a-zA-Z0-9-]\+:'
664 let prev_unfinished = s:CssUnfinished(prev_text)
665 if !cur_hasfield && (prev_hasfield || prev_unfinished)
666 " Continuation line has extra indent if the previous line was not a
667 " continuation line.
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200668 let extra = shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200669 " Align with @if
670 if prev_text =~ '^\s*@if '
671 let extra = 4
672 endif
673 elseif cur_hasfield && !prev_hasfield && !prev_special
674 " less indent below a continuation line
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200675 let extra = -shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200676 endif
677 endif
678 endif
679
680 if below_end_brace
681 " find matching {, if that line starts with @ it's not the start of a rule
682 " but something else from a template system
683 call cursor(prev_lnum, 1)
684 call search('}\s*$')
685 try
686 normal! %
687 " Found the matching "{", align with it.
688 let align_lnum = s:CssFirstUnfinished(line('.'), min_lnum)
689 let special = getline(align_lnum) =~ '^\s*@'
690 catch
691 let special = 0
692 endtry
693 if special
694 " do not reduce indent below @{ ... }
695 if extra < 0
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200696 let extra += shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200697 endif
698 else
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200699 let extra -= (brace_counts.c_close - (prev_text =~ '^\s*}')) * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200700 endif
701 endif
702
703 " if no extra indent yet...
704 if extra == 0
705 if brace_counts.p_open > brace_counts.p_close
706 " previous line has more ( than ): add a shiftwidth
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200707 let extra = shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200708 elseif brace_counts.p_open < brace_counts.p_close
709 " previous line has more ) than (: subtract a shiftwidth
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200710 let extra = -shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200711 endif
712 endif
713
714 return indent(align_lnum) + extra
715endfunc "}}}
716
717" Inside <style>: Whether a line is unfinished.
718func! s:CssUnfinished(text)
719 "{{{
720 return a:text =~ '\s\(||\|&&\|:\)\s*$'
721endfunc "}}}
722
723" Search back for the first unfinished line above "lnum".
724func! s:CssFirstUnfinished(lnum, min_lnum)
725 "{{{
726 let align_lnum = a:lnum
727 while align_lnum > a:min_lnum && s:CssUnfinished(getline(align_lnum - 1))
728 let align_lnum -= 1
729 endwhile
730 return align_lnum
731endfunc "}}}
732
733" Find the non-empty line at or before "lnum" that is not a comment.
734func! s:CssPrevNonComment(lnum, stopline)
735 "{{{
736 " caller starts from a line a:lnum + 1 that is not a comment
737 let lnum = prevnonblank(a:lnum)
738 while 1
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200739 let ccol = match(getline(lnum), '\*/')
740 if ccol < 0
Bram Moolenaar3e496b02016-09-25 22:11:48 +0200741 " No comment end thus it's something else.
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200742 return lnum
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200743 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200744 call cursor(lnum, ccol + 1)
745 " Search back for the /* that starts the comment
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200746 let lnum = search('/\*', 'bW', a:stopline)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200747 if indent(".") == virtcol(".") - 1
748 " The found /* is at the start of the line. Now go back to the line
749 " above it and again check if it is a comment.
750 let lnum = prevnonblank(lnum - 1)
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200751 else
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200752 " /* is after something else, thus it's not a comment line.
753 return lnum
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200754 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200755 endwhile
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200756endfunc "}}}
757
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200758" Check the number of {} and () in line "lnum". Return a dict with the counts.
759func! HtmlIndent_CountBraces(lnum)
760 "{{{
761 let brs = substitute(getline(a:lnum), '[''"].\{-}[''"]\|/\*.\{-}\*/\|/\*.*$\|[^{}()]', '', 'g')
762 let c_open = 0
763 let c_close = 0
764 let p_open = 0
765 let p_close = 0
766 for brace in split(brs, '\zs')
767 if brace == "{"
768 let c_open += 1
769 elseif brace == "}"
770 if c_open > 0
771 let c_open -= 1
772 else
773 let c_close += 1
774 endif
775 elseif brace == '('
776 let p_open += 1
777 elseif brace == ')'
778 if p_open > 0
779 let p_open -= 1
780 else
781 let p_close += 1
782 endif
783 endif
784 endfor
785 return {'c_open': c_open,
786 \ 'c_close': c_close,
787 \ 'p_open': p_open,
788 \ 'p_close': p_close}
789endfunc "}}}
790
791" Return the indent for a comment: <!-- -->
792func! s:Alien5()
793 "{{{
794 let curtext = getline(v:lnum)
795 if curtext =~ '^\s*\zs-->'
796 " current line starts with end of comment, line up with comment start.
797 call cursor(v:lnum, 0)
798 let lnum = search('<!--', 'b')
799 if lnum > 0
800 " TODO: what if <!-- is not at the start of the line?
801 return indent(lnum)
802 endif
803
804 " Strange, can't find it.
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200805 return -1
Bram Moolenaar946e27a2014-06-25 18:50:27 +0200806 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200807
808 let prevlnum = prevnonblank(v:lnum - 1)
809 let prevtext = getline(prevlnum)
810 let idx = match(prevtext, '^\s*\zs<!--')
811 if idx >= 0
812 " just below comment start, add a shiftwidth
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200813 return idx + shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200814 endif
815
816 " Some files add 4 spaces just below a TODO line. It's difficult to detect
817 " the end of the TODO, so let's not do that.
818
819 " Align with the previous non-blank line.
820 return indent(prevlnum)
Bram Moolenaar946e27a2014-06-25 18:50:27 +0200821endfunc "}}}
822
Bram Moolenaarca635012015-09-25 20:34:21 +0200823" Return the indent for conditional comment: <!--[ ![endif]-->
824func! s:Alien6()
825 "{{{
826 let curtext = getline(v:lnum)
827 if curtext =~ '\s*\zs<!\[endif\]-->'
828 " current line starts with end of comment, line up with comment start.
829 let lnum = search('<!--', 'bn')
830 if lnum > 0
831 return indent(lnum)
832 endif
833 endif
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200834 return b:hi_indent.baseindent + shiftwidth()
Bram Moolenaarca635012015-09-25 20:34:21 +0200835endfunc "}}}
836
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200837" When the "lnum" line ends in ">" find the line containing the matching "<".
838func! HtmlIndent_FindTagStart(lnum)
839 "{{{
840 " Avoids using the indent of a continuation line.
841 " Moves the cursor.
842 " Return two values:
843 " - the matching line number or "lnum".
844 " - a flag indicating whether we found the end of a tag.
845 " This method is global so that HTML-like indenters can use it.
846 " To avoid matching " > " or " < " inside a string require that the opening
847 " "<" is followed by a word character and the closing ">" comes after a
848 " non-white character.
849 let idx = match(getline(a:lnum), '\S>\s*$')
850 if idx > 0
851 call cursor(a:lnum, idx)
852 let lnum = searchpair('<\w', '' , '\S>', 'bW', '', max([a:lnum - b:html_indent_line_limit, 0]))
853 if lnum > 0
854 return [lnum, 1]
Bram Moolenaar071d4272004-06-13 20:20:40 +0000855 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200856 endif
857 return [a:lnum, 0]
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200858endfunc "}}}
Bram Moolenaar071d4272004-06-13 20:20:40 +0000859
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200860" Find the unclosed start tag from the current cursor position.
861func! HtmlIndent_FindStartTag()
862 "{{{
863 " The cursor must be on or before a closing tag.
864 " If found, positions the cursor at the match and returns the line number.
865 " Otherwise returns 0.
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100866 let tagname = matchstr(getline('.')[col('.') - 1:], '</\zs' . s:tagname . '\ze')
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200867 let start_lnum = searchpair('<' . tagname . '\>', '', '</' . tagname . '\>', 'bW')
868 if start_lnum > 0
869 return start_lnum
870 endif
871 return 0
872endfunc "}}}
Bram Moolenaar071d4272004-06-13 20:20:40 +0000873
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200874" Moves the cursor from a "<" to the matching ">".
875func! HtmlIndent_FindTagEnd()
876 "{{{
877 " Call this with the cursor on the "<" of a start tag.
878 " This will move the cursor to the ">" of the matching end tag or, when it's
879 " a self-closing tag, to the matching ">".
880 " Limited to look up to b:html_indent_line_limit lines away.
881 let text = getline('.')
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100882 let tagname = matchstr(text, s:tagname . '\|!--', col('.'))
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200883 if tagname == '!--'
884 call search('--\zs>')
885 elseif s:get_tag('/' . tagname) != 0
886 " tag with a closing tag, find matching "</tag>"
887 call searchpair('<' . tagname, '', '</' . tagname . '\zs>', 'W', '', line('.') + b:html_indent_line_limit)
888 else
889 " self-closing tag, find the ">"
890 call search('\S\zs>')
891 endif
892endfunc "}}}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200893
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200894" Indenting inside a start tag. Return the correct indent or -1 if unknown.
895func! s:InsideTag(foundHtmlString)
896 "{{{
897 if a:foundHtmlString
898 " Inside an attribute string.
899 " Align with the previous line or use an external function.
900 let lnum = v:lnum - 1
901 if lnum > 1
902 if exists('b:html_indent_tag_string_func')
903 return b:html_indent_tag_string_func(lnum)
904 endif
905 return indent(lnum)
906 endif
907 endif
908
909 " Should be another attribute: " attr="val". Align with the previous
910 " attribute start.
911 let lnum = v:lnum
912 while lnum > 1
913 let lnum -= 1
914 let text = getline(lnum)
915 " Find a match with one of these, align with "attr":
916 " attr=
917 " <tag attr=
918 " text<tag attr=
919 " <tag>text</tag>text<tag attr=
920 " For long lines search for the first match, finding the last match
921 " gets very slow.
922 if len(text) < 300
923 let idx = match(text, '.*\s\zs[_a-zA-Z0-9-]\+="')
924 else
925 let idx = match(text, '\s\zs[_a-zA-Z0-9-]\+="')
926 endif
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100927 if idx == -1
928 " try <tag attr
929 let idx = match(text, '<' . s:tagname . '\s\+\zs\w')
930 endif
931 if idx == -1
932 " after just <tag indent one level more
933 let idx = match(text, '<' . s:tagname . '$')
934 if idx >= 0
935 call cursor(lnum, idx)
936 return virtcol('.') + shiftwidth()
937 endif
938 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200939 if idx > 0
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100940 " Found the attribute to align with.
941 call cursor(lnum, idx)
942 return virtcol('.')
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200943 endif
944 endwhile
945 return -1
946endfunc "}}}
947
948" THE MAIN INDENT FUNCTION. Return the amount of indent for v:lnum.
949func! HtmlIndent()
950 "{{{
Bram Moolenaar9da7ff72015-01-14 12:52:36 +0100951 if prevnonblank(v:lnum - 1) < 1
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200952 " First non-blank line has no indent.
953 return 0
954 endif
955
956 let curtext = tolower(getline(v:lnum))
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200957 let indentunit = shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200958
959 let b:hi_newstate = {}
960 let b:hi_newstate.lnum = v:lnum
961
962 " When syntax HL is enabled, detect we are inside a tag. Indenting inside
963 " a tag works very differently. Do not do this when the line starts with
964 " "<", it gets the "htmlTag" ID but we are not inside a tag then.
965 if curtext !~ '^\s*<'
Bram Moolenaar7b61a542014-08-23 15:31:19 +0200966 normal! ^
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200967 let stack = synstack(v:lnum, col('.')) " assumes there are no tabs
968 let foundHtmlString = 0
969 for synid in reverse(stack)
970 let name = synIDattr(synid, "name")
971 if index(b:hi_insideStringNames, name) >= 0
972 let foundHtmlString = 1
973 elseif index(b:hi_insideTagNames, name) >= 0
974 " Yes, we are inside a tag.
975 let indent = s:InsideTag(foundHtmlString)
976 if indent >= 0
977 " Do not keep the state. TODO: could keep the block type.
978 let b:hi_indent.lnum = 0
979 return indent
980 endif
981 endif
982 endfor
983 endif
984
985 " does the line start with a closing tag?
986 let swendtag = match(curtext, '^\s*</') >= 0
987
988 if prevnonblank(v:lnum - 1) == b:hi_indent.lnum && b:hi_lasttick == b:changedtick - 1
989 " use state (continue from previous line)
990 else
991 " start over (know nothing)
992 let b:hi_indent = s:FreshState(v:lnum)
993 endif
994
995 if b:hi_indent.block >= 2
996 " within block
997 let endtag = s:endtags[b:hi_indent.block]
998 let blockend = stridx(curtext, endtag)
999 if blockend >= 0
1000 " block ends here
1001 let b:hi_newstate.block = 0
1002 " calc indent for REST OF LINE (may start more blocks):
1003 call s:CountTagsAndState(strpart(curtext, blockend + strlen(endtag)))
1004 if swendtag && b:hi_indent.block != 5
1005 let indent = b:hi_indent.blocktagind + s:curind * indentunit
1006 let b:hi_newstate.baseindent = indent + s:nextrel * indentunit
1007 else
1008 let indent = s:Alien{b:hi_indent.block}()
1009 let b:hi_newstate.baseindent = b:hi_indent.blocktagind + s:nextrel * indentunit
1010 endif
1011 else
1012 " block continues
1013 " indent this line with alien method
1014 let indent = s:Alien{b:hi_indent.block}()
1015 endif
1016 else
1017 " not within a block - within usual html
1018 let b:hi_newstate.block = b:hi_indent.block
1019 if swendtag
1020 " The current line starts with an end tag, align with its start tag.
1021 call cursor(v:lnum, 1)
1022 let start_lnum = HtmlIndent_FindStartTag()
1023 if start_lnum > 0
1024 " check for the line starting with something inside a tag:
1025 " <sometag <- align here
1026 " attr=val><open> not here
1027 let text = getline(start_lnum)
1028 let angle = matchstr(text, '[<>]')
1029 if angle == '>'
1030 call cursor(start_lnum, 1)
1031 normal! f>%
1032 let start_lnum = line('.')
1033 let text = getline(start_lnum)
1034 endif
1035
1036 let indent = indent(start_lnum)
1037 if col('.') > 2
1038 let swendtag = match(text, '^\s*</') >= 0
1039 call s:CountITags(text[: col('.') - 2])
Bram Moolenaar3ec574f2017-06-13 18:12:01 +02001040 let indent += s:nextrel * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +02001041 if !swendtag
Bram Moolenaar3ec574f2017-06-13 18:12:01 +02001042 let indent += s:curind * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +02001043 endif
1044 endif
1045 else
1046 " not sure what to do
1047 let indent = b:hi_indent.baseindent
1048 endif
1049 let b:hi_newstate.baseindent = indent
1050 else
1051 call s:CountTagsAndState(curtext)
1052 let indent = b:hi_indent.baseindent
1053 let b:hi_newstate.baseindent = indent + (s:curind + s:nextrel) * indentunit
1054 endif
1055 endif
1056
1057 let b:hi_lasttick = b:changedtick
1058 call extend(b:hi_indent, b:hi_newstate, "force")
1059 return indent
1060endfunc "}}}
1061
1062" Check user settings when loading this script the first time.
Bram Moolenaarec7944a2013-06-12 21:29:15 +02001063call HtmlIndent_CheckUserSettings()
Bram Moolenaar071d4272004-06-13 20:20:40 +00001064
Bram Moolenaar91170f82006-05-05 21:15:17 +00001065let &cpo = s:cpo_save
1066unlet s:cpo_save
1067
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +02001068" vim: fdm=marker ts=8 sw=2 tw=78