blob: d4b91f6421059fc9984c92c4f75db8c567efacaa [file] [log] [blame]
Bram Moolenaarec7944a2013-06-12 21:29:15 +02001" Vim indent script for HTML
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +02002" Maintainer: Bram Moolenaar
3" Original Author: Andy Wokula <anwoku@yahoo.de>
Bram Moolenaar2346a632021-06-13 19:02:49 +02004" Last Change: 2021 Jun 13
Bram Moolenaar7ff78462020-07-10 22:00:53 +02005" Version: 1.0 "{{{
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +02006" Description: HTML indent script with cached state for faster indenting on a
7" range of lines.
8" Supports template systems through hooks.
9" Supports Closure stylesheets.
Bram Moolenaarec7944a2013-06-12 21:29:15 +020010"
11" Credits:
12" indent/html.vim (2006 Jun 05) from J. Zellner
13" indent/css.vim (2006 Dec 20) from N. Weibull
14"
15" History:
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +020016" 2014 June (v1.0) overhaul (Bram)
Bram Moolenaar52b91d82013-06-15 21:39:51 +020017" 2012 Oct 21 (v0.9) added support for shiftwidth()
18" 2011 Sep 09 (v0.8) added HTML5 tags (thx to J. Zuckerman)
19" 2008 Apr 28 (v0.6) revised customization
20" 2008 Mar 09 (v0.5) fixed 'indk' issue (thx to C.J. Robinson)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +020021"}}}
Bram Moolenaar071d4272004-06-13 20:20:40 +000022
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +020023" Init Folklore, check user settings (2nd time ++)
24if exists("b:did_indent") "{{{
25 finish
Bram Moolenaar071d4272004-06-13 20:20:40 +000026endif
Bram Moolenaar690afe12017-01-28 18:34:47 +010027
28" Load the Javascript indent script first, it defines GetJavascriptIndent().
29" Undo the rest.
30" Load base python indent.
31if !exists('*GetJavascriptIndent')
32 runtime! indent/javascript.vim
33endif
Bram Moolenaar071d4272004-06-13 20:20:40 +000034let b:did_indent = 1
35
Bram Moolenaarec7944a2013-06-12 21:29:15 +020036setlocal indentexpr=HtmlIndent()
37setlocal indentkeys=o,O,<Return>,<>>,{,},!^F
Bram Moolenaar071d4272004-06-13 20:20:40 +000038
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +020039" Needed for % to work when finding start/end of a tag.
Bram Moolenaar946e27a2014-06-25 18:50:27 +020040setlocal matchpairs+=<:>
41
Bram Moolenaar690afe12017-01-28 18:34:47 +010042let b:undo_indent = "setlocal inde< indk<"
Bram Moolenaar071d4272004-06-13 20:20:40 +000043
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +020044" b:hi_indent keeps state to speed up indenting consecutive lines.
45let b:hi_indent = {"lnum": -1}
46
47"""""" Code below this is loaded only once. """""
48if exists("*HtmlIndent") && !exists('g:force_reload_html')
49 call HtmlIndent_CheckUserSettings()
50 finish
Bram Moolenaar071d4272004-06-13 20:20:40 +000051endif
52
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +020053" Allow for line continuation below.
Bram Moolenaar91170f82006-05-05 21:15:17 +000054let s:cpo_save = &cpo
Bram Moolenaar071d4272004-06-13 20:20:40 +000055set cpo-=C
Bram Moolenaarec7944a2013-06-12 21:29:15 +020056"}}}
Bram Moolenaar071d4272004-06-13 20:20:40 +000057
Bram Moolenaarb5b75622018-03-09 22:22:21 +010058" Pattern to match the name of a tag, including custom elements.
59let s:tagname = '\w\+\(-\w\+\)*'
60
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +020061" Check and process settings from b:html_indent and g:html_indent... variables.
62" Prefer using buffer-local settings over global settings, so that there can
63" be defaults for all HTML files and exceptions for specific types of HTML
64" files.
Bram Moolenaar2346a632021-06-13 19:02:49 +020065func HtmlIndent_CheckUserSettings()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +020066 "{{{
67 let inctags = ''
68 if exists("b:html_indent_inctags")
69 let inctags = b:html_indent_inctags
70 elseif exists("g:html_indent_inctags")
71 let inctags = g:html_indent_inctags
72 endif
73 let b:hi_tags = {}
74 if len(inctags) > 0
75 call s:AddITags(b:hi_tags, split(inctags, ","))
76 endif
Bram Moolenaar071d4272004-06-13 20:20:40 +000077
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +020078 let autotags = ''
79 if exists("b:html_indent_autotags")
80 let autotags = b:html_indent_autotags
81 elseif exists("g:html_indent_autotags")
82 let autotags = g:html_indent_autotags
83 endif
84 let b:hi_removed_tags = {}
Bram Moolenaar541f92d2015-06-19 13:27:23 +020085 if len(autotags) > 0
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +020086 call s:RemoveITags(b:hi_removed_tags, split(autotags, ","))
87 endif
88
89 " Syntax names indicating being inside a string of an attribute value.
90 let string_names = []
91 if exists("b:html_indent_string_names")
92 let string_names = b:html_indent_string_names
93 elseif exists("g:html_indent_string_names")
94 let string_names = g:html_indent_string_names
95 endif
96 let b:hi_insideStringNames = ['htmlString']
97 if len(string_names) > 0
98 for s in string_names
99 call add(b:hi_insideStringNames, s)
100 endfor
101 endif
102
103 " Syntax names indicating being inside a tag.
104 let tag_names = []
105 if exists("b:html_indent_tag_names")
106 let tag_names = b:html_indent_tag_names
107 elseif exists("g:html_indent_tag_names")
108 let tag_names = g:html_indent_tag_names
109 endif
110 let b:hi_insideTagNames = ['htmlTag', 'htmlScriptTag']
111 if len(tag_names) > 0
112 for s in tag_names
113 call add(b:hi_insideTagNames, s)
114 endfor
115 endif
116
117 let indone = {"zero": 0
118 \,"auto": "indent(prevnonblank(v:lnum-1))"
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200119 \,"inc": "b:hi_indent.blocktagind + shiftwidth()"}
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200120
121 let script1 = ''
122 if exists("b:html_indent_script1")
123 let script1 = b:html_indent_script1
124 elseif exists("g:html_indent_script1")
125 let script1 = g:html_indent_script1
126 endif
127 if len(script1) > 0
128 let b:hi_js1indent = get(indone, script1, indone.zero)
129 else
130 let b:hi_js1indent = 0
131 endif
132
133 let style1 = ''
134 if exists("b:html_indent_style1")
135 let style1 = b:html_indent_style1
136 elseif exists("g:html_indent_style1")
137 let style1 = g:html_indent_style1
138 endif
139 if len(style1) > 0
140 let b:hi_css1indent = get(indone, style1, indone.zero)
141 else
142 let b:hi_css1indent = 0
143 endif
144
145 if !exists('b:html_indent_line_limit')
146 if exists('g:html_indent_line_limit')
147 let b:html_indent_line_limit = g:html_indent_line_limit
148 else
149 let b:html_indent_line_limit = 200
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200150 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200151 endif
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200152endfunc "}}}
Bram Moolenaar071d4272004-06-13 20:20:40 +0000153
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200154" Init Script Vars
155"{{{
156let b:hi_lasttick = 0
157let b:hi_newstate = {}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200158let s:countonly = 0
159 "}}}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200160
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200161" Fill the s:indent_tags dict with known tags.
162" The key is "tagname" or "/tagname". {{{
163" The value is:
164" 1 opening tag
165" 2 "pre"
166" 3 "script"
167" 4 "style"
168" 5 comment start
Bram Moolenaarca635012015-09-25 20:34:21 +0200169" 6 conditional comment start
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200170" -1 closing tag
171" -2 "/pre"
172" -3 "/script"
173" -4 "/style"
174" -5 comment end
Bram Moolenaarca635012015-09-25 20:34:21 +0200175" -6 conditional comment end
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200176let s:indent_tags = {}
Bram Moolenaarca635012015-09-25 20:34:21 +0200177let s:endtags = [0,0,0,0,0,0,0] " long enough for the highest index
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200178"}}}
179
180" Add a list of tag names for a pair of <tag> </tag> to "tags".
Bram Moolenaar2346a632021-06-13 19:02:49 +0200181func s:AddITags(tags, taglist)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200182 "{{{
183 for itag in a:taglist
184 let a:tags[itag] = 1
185 let a:tags['/' . itag] = -1
186 endfor
187endfunc "}}}
188
189" Take a list of tag name pairs that are not to be used as tag pairs.
Bram Moolenaar2346a632021-06-13 19:02:49 +0200190func s:RemoveITags(tags, taglist)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200191 "{{{
192 for itag in a:taglist
193 let a:tags[itag] = 1
194 let a:tags['/' . itag] = 1
195 endfor
196endfunc "}}}
197
198" Add a block tag, that is a tag with a different kind of indenting.
Bram Moolenaar2346a632021-06-13 19:02:49 +0200199func s:AddBlockTag(tag, id, ...)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200200 "{{{
201 if !(a:id >= 2 && a:id < len(s:endtags))
202 echoerr 'AddBlockTag ' . a:id
203 return
204 endif
205 let s:indent_tags[a:tag] = a:id
206 if a:0 == 0
207 let s:indent_tags['/' . a:tag] = -a:id
208 let s:endtags[a:id] = "</" . a:tag . ">"
209 else
210 let s:indent_tags[a:1] = -a:id
211 let s:endtags[a:id] = a:1
212 endif
213endfunc "}}}
214
215" Add known tag pairs.
216" Self-closing tags and tags that are sometimes {{{
217" self-closing (e.g., <p>) are not here (when encountering </p> we can find
Bram Moolenaard47d5222018-12-09 20:43:55 +0100218" the matching <p>, but not the other way around).
219" Known self-closing tags: " 'p', 'img', 'source', 'area', 'keygen', 'track',
220" 'wbr'.
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',
Bram Moolenaar73fef332020-06-21 22:12:03 +0200225 \ 'colgroup', 'dd', 'del', 'dfn', 'dir', 'div', 'dl', 'dt', 'em', 'fieldset', 'font',
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200226 \ '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, [
Bram Moolenaard47d5222018-12-09 20:43:55 +0100235 \ 'article', 'aside', 'audio', 'bdi', 'canvas', 'command', 'data',
236 \ 'datalist', 'details', 'dialog', 'embed', 'figcaption', 'figure',
237 \ 'footer', 'header', 'hgroup', 'main', 'mark', 'meter', 'nav', 'output',
238 \ 'picture', 'progress', 'rp', 'rt', 'ruby', 'section', 'summary',
239 \ 'svg', 'time', 'video'])
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.
Bram Moolenaar2346a632021-06-13 19:02:49 +0200258func HtmlIndent_IsOpenTag(tagname)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200259 "{{{
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.
Bram Moolenaar2346a632021-06-13 19:02:49 +0200267func s:get_tag(tagname)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200268 "{{{
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".
Bram Moolenaar2346a632021-06-13 19:02:49 +0200280func s:CountITags(text)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200281 "{{{
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.
Bram Moolenaar2346a632021-06-13 19:02:49 +0200292func s:CountTagsAndState(text)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200293 "{{{
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().
Bram Moolenaar2346a632021-06-13 19:02:49 +0200307func s:CheckTag(itag)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200308 "{{{
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".
Bram Moolenaar2346a632021-06-13 19:02:49 +0200341func s:CheckBlockTag(blocktag, ind)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200342 "{{{
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().
Bram Moolenaar2346a632021-06-13 19:02:49 +0200369func s:CheckCustomTag(ctag)
Bram Moolenaard8b77f72015-03-05 21:21:19 +0100370 "{{{
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 ""
Bram Moolenaar2346a632021-06-13 19:02:49 +0200399func s:GetScriptType(str)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200400 "{{{
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.
Bram Moolenaar2346a632021-06-13 19:02:49 +0200410func s:FreshState(lnum)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200411 "{{{
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.
Bram Moolenaar2346a632021-06-13 19:02:49 +0200571func s:Alien2()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200572 "{{{
573 return -1
574endfunc "}}}
575
576" Return the indent inside a <script> block for javascript.
Bram Moolenaar2346a632021-06-13 19:02:49 +0200577func s:Alien3()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200578 "{{{
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
Bram Moolenaar2346a632021-06-13 19:02:49 +0200584 if lnum < b:hi_indent.blocklnr
585 " indent for <script> itself
586 return b:hi_indent.blocktagind
587 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200588 if lnum == b:hi_indent.blocklnr
589 " indent for the first line after <script>
590 return eval(b:hi_js1indent)
591 endif
592 if b:hi_indent.scripttype == "javascript"
Bram Moolenaar2346a632021-06-13 19:02:49 +0200593 " indent for further lines
Bram Moolenaar7ff78462020-07-10 22:00:53 +0200594 return eval(b:hi_js1indent) + GetJavascriptIndent()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200595 else
596 return -1
597 endif
598endfunc "}}}
599
600" Return the indent inside a <style> block.
Bram Moolenaar2346a632021-06-13 19:02:49 +0200601func s:Alien4()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200602 "{{{
603 if prevnonblank(v:lnum-1) == b:hi_indent.blocklnr
604 " indent for first content line
605 return eval(b:hi_css1indent)
606 endif
607 return s:CSSIndent()
608endfunc "}}}
609
610" Indending inside a <style> block. Returns the indent.
Bram Moolenaar2346a632021-06-13 19:02:49 +0200611func s:CSSIndent()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200612 "{{{
613 " This handles standard CSS and also Closure stylesheets where special lines
614 " start with @.
615 " When the line starts with '*' or the previous line starts with "/*"
616 " and does not end in "*/", use C indenting to format the comment.
617 " Adopted $VIMRUNTIME/indent/css.vim
618 let curtext = getline(v:lnum)
619 if curtext =~ '^\s*[*]'
620 \ || (v:lnum > 1 && getline(v:lnum - 1) =~ '\s*/\*'
621 \ && getline(v:lnum - 1) !~ '\*/\s*$')
622 return cindent(v:lnum)
623 endif
624
625 let min_lnum = b:hi_indent.blocklnr
626 let prev_lnum = s:CssPrevNonComment(v:lnum - 1, min_lnum)
627 let [prev_lnum, found] = HtmlIndent_FindTagStart(prev_lnum)
628 if prev_lnum <= min_lnum
629 " Just below the <style> tag, indent for first content line after comments.
630 return eval(b:hi_css1indent)
631 endif
632
Bram Moolenaarba3ff532018-11-04 14:45:49 +0100633 " If the current line starts with "}" align with its match.
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200634 if curtext =~ '^\s*}'
635 call cursor(v:lnum, 1)
636 try
637 normal! %
638 " Found the matching "{", align with it after skipping unfinished lines.
639 let align_lnum = s:CssFirstUnfinished(line('.'), min_lnum)
640 return indent(align_lnum)
641 catch
642 " can't find it, try something else, but it's most likely going to be
643 " wrong
644 endtry
645 endif
646
647 " add indent after {
648 let brace_counts = HtmlIndent_CountBraces(prev_lnum)
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200649 let extra = brace_counts.c_open * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200650
651 let prev_text = getline(prev_lnum)
652 let below_end_brace = prev_text =~ '}\s*$'
653
654 " Search back to align with the first line that's unfinished.
655 let align_lnum = s:CssFirstUnfinished(prev_lnum, min_lnum)
656
657 " Handle continuation lines if aligning with previous line and not after a
658 " "}".
659 if extra == 0 && align_lnum == prev_lnum && !below_end_brace
660 let prev_hasfield = prev_text =~ '^\s*[a-zA-Z0-9-]\+:'
661 let prev_special = prev_text =~ '^\s*\(/\*\|@\)'
662 if curtext =~ '^\s*\(/\*\|@\)'
663 " if the current line is not a comment or starts with @ (used by template
664 " systems) reduce indent if previous line is a continuation line
665 if !prev_hasfield && !prev_special
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200666 let extra = -shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200667 endif
668 else
669 let cur_hasfield = curtext =~ '^\s*[a-zA-Z0-9-]\+:'
670 let prev_unfinished = s:CssUnfinished(prev_text)
Bram Moolenaarfc65cab2018-08-28 22:58:02 +0200671 if prev_unfinished
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200672 " Continuation line has extra indent if the previous line was not a
673 " continuation line.
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200674 let extra = shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200675 " Align with @if
676 if prev_text =~ '^\s*@if '
677 let extra = 4
678 endif
679 elseif cur_hasfield && !prev_hasfield && !prev_special
680 " less indent below a continuation line
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200681 let extra = -shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200682 endif
683 endif
684 endif
685
686 if below_end_brace
687 " find matching {, if that line starts with @ it's not the start of a rule
688 " but something else from a template system
689 call cursor(prev_lnum, 1)
690 call search('}\s*$')
691 try
692 normal! %
693 " Found the matching "{", align with it.
694 let align_lnum = s:CssFirstUnfinished(line('.'), min_lnum)
695 let special = getline(align_lnum) =~ '^\s*@'
696 catch
697 let special = 0
698 endtry
699 if special
700 " do not reduce indent below @{ ... }
701 if extra < 0
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200702 let extra += shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200703 endif
704 else
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200705 let extra -= (brace_counts.c_close - (prev_text =~ '^\s*}')) * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200706 endif
707 endif
708
709 " if no extra indent yet...
710 if extra == 0
711 if brace_counts.p_open > brace_counts.p_close
712 " previous line has more ( than ): add a shiftwidth
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200713 let extra = shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200714 elseif brace_counts.p_open < brace_counts.p_close
715 " previous line has more ) than (: subtract a shiftwidth
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200716 let extra = -shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200717 endif
718 endif
719
720 return indent(align_lnum) + extra
721endfunc "}}}
722
723" Inside <style>: Whether a line is unfinished.
Bram Moolenaarfc65cab2018-08-28 22:58:02 +0200724" tag:
725" tag: blah
726" tag: blah &&
727" tag: blah ||
Bram Moolenaar2346a632021-06-13 19:02:49 +0200728func s:CssUnfinished(text)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200729 "{{{
Bram Moolenaarfc65cab2018-08-28 22:58:02 +0200730 return a:text =~ '\(||\|&&\|:\|\k\)\s*$'
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200731endfunc "}}}
732
733" Search back for the first unfinished line above "lnum".
Bram Moolenaar2346a632021-06-13 19:02:49 +0200734func s:CssFirstUnfinished(lnum, min_lnum)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200735 "{{{
736 let align_lnum = a:lnum
737 while align_lnum > a:min_lnum && s:CssUnfinished(getline(align_lnum - 1))
738 let align_lnum -= 1
739 endwhile
740 return align_lnum
741endfunc "}}}
742
743" Find the non-empty line at or before "lnum" that is not a comment.
Bram Moolenaar2346a632021-06-13 19:02:49 +0200744func s:CssPrevNonComment(lnum, stopline)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200745 "{{{
746 " caller starts from a line a:lnum + 1 that is not a comment
747 let lnum = prevnonblank(a:lnum)
748 while 1
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200749 let ccol = match(getline(lnum), '\*/')
750 if ccol < 0
Bram Moolenaar3e496b02016-09-25 22:11:48 +0200751 " No comment end thus it's something else.
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200752 return lnum
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200753 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200754 call cursor(lnum, ccol + 1)
755 " Search back for the /* that starts the comment
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200756 let lnum = search('/\*', 'bW', a:stopline)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200757 if indent(".") == virtcol(".") - 1
758 " The found /* is at the start of the line. Now go back to the line
759 " above it and again check if it is a comment.
760 let lnum = prevnonblank(lnum - 1)
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200761 else
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200762 " /* is after something else, thus it's not a comment line.
763 return lnum
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200764 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200765 endwhile
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200766endfunc "}}}
767
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200768" Check the number of {} and () in line "lnum". Return a dict with the counts.
Bram Moolenaar2346a632021-06-13 19:02:49 +0200769func HtmlIndent_CountBraces(lnum)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200770 "{{{
771 let brs = substitute(getline(a:lnum), '[''"].\{-}[''"]\|/\*.\{-}\*/\|/\*.*$\|[^{}()]', '', 'g')
772 let c_open = 0
773 let c_close = 0
774 let p_open = 0
775 let p_close = 0
776 for brace in split(brs, '\zs')
777 if brace == "{"
778 let c_open += 1
779 elseif brace == "}"
780 if c_open > 0
781 let c_open -= 1
782 else
783 let c_close += 1
784 endif
785 elseif brace == '('
786 let p_open += 1
787 elseif brace == ')'
788 if p_open > 0
789 let p_open -= 1
790 else
791 let p_close += 1
792 endif
793 endif
794 endfor
795 return {'c_open': c_open,
796 \ 'c_close': c_close,
797 \ 'p_open': p_open,
798 \ 'p_close': p_close}
799endfunc "}}}
800
801" Return the indent for a comment: <!-- -->
Bram Moolenaar2346a632021-06-13 19:02:49 +0200802func s:Alien5()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200803 "{{{
804 let curtext = getline(v:lnum)
805 if curtext =~ '^\s*\zs-->'
806 " current line starts with end of comment, line up with comment start.
807 call cursor(v:lnum, 0)
808 let lnum = search('<!--', 'b')
809 if lnum > 0
810 " TODO: what if <!-- is not at the start of the line?
811 return indent(lnum)
812 endif
813
814 " Strange, can't find it.
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200815 return -1
Bram Moolenaar946e27a2014-06-25 18:50:27 +0200816 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200817
818 let prevlnum = prevnonblank(v:lnum - 1)
819 let prevtext = getline(prevlnum)
820 let idx = match(prevtext, '^\s*\zs<!--')
821 if idx >= 0
822 " just below comment start, add a shiftwidth
Bram Moolenaar4072ba52020-12-23 13:56:35 +0100823 return indent(prevlnum) + shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200824 endif
825
826 " Some files add 4 spaces just below a TODO line. It's difficult to detect
827 " the end of the TODO, so let's not do that.
828
829 " Align with the previous non-blank line.
830 return indent(prevlnum)
Bram Moolenaar946e27a2014-06-25 18:50:27 +0200831endfunc "}}}
832
Bram Moolenaarca635012015-09-25 20:34:21 +0200833" Return the indent for conditional comment: <!--[ ![endif]-->
Bram Moolenaar2346a632021-06-13 19:02:49 +0200834func s:Alien6()
Bram Moolenaarca635012015-09-25 20:34:21 +0200835 "{{{
836 let curtext = getline(v:lnum)
837 if curtext =~ '\s*\zs<!\[endif\]-->'
838 " current line starts with end of comment, line up with comment start.
839 let lnum = search('<!--', 'bn')
840 if lnum > 0
841 return indent(lnum)
842 endif
843 endif
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200844 return b:hi_indent.baseindent + shiftwidth()
Bram Moolenaarca635012015-09-25 20:34:21 +0200845endfunc "}}}
846
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200847" When the "lnum" line ends in ">" find the line containing the matching "<".
Bram Moolenaar2346a632021-06-13 19:02:49 +0200848func HtmlIndent_FindTagStart(lnum)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200849 "{{{
850 " Avoids using the indent of a continuation line.
851 " Moves the cursor.
852 " Return two values:
853 " - the matching line number or "lnum".
854 " - a flag indicating whether we found the end of a tag.
855 " This method is global so that HTML-like indenters can use it.
856 " To avoid matching " > " or " < " inside a string require that the opening
857 " "<" is followed by a word character and the closing ">" comes after a
858 " non-white character.
859 let idx = match(getline(a:lnum), '\S>\s*$')
860 if idx > 0
861 call cursor(a:lnum, idx)
862 let lnum = searchpair('<\w', '' , '\S>', 'bW', '', max([a:lnum - b:html_indent_line_limit, 0]))
863 if lnum > 0
864 return [lnum, 1]
Bram Moolenaar071d4272004-06-13 20:20:40 +0000865 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200866 endif
867 return [a:lnum, 0]
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200868endfunc "}}}
Bram Moolenaar071d4272004-06-13 20:20:40 +0000869
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200870" Find the unclosed start tag from the current cursor position.
Bram Moolenaar2346a632021-06-13 19:02:49 +0200871func HtmlIndent_FindStartTag()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200872 "{{{
873 " The cursor must be on or before a closing tag.
874 " If found, positions the cursor at the match and returns the line number.
875 " Otherwise returns 0.
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100876 let tagname = matchstr(getline('.')[col('.') - 1:], '</\zs' . s:tagname . '\ze')
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200877 let start_lnum = searchpair('<' . tagname . '\>', '', '</' . tagname . '\>', 'bW')
878 if start_lnum > 0
879 return start_lnum
880 endif
881 return 0
882endfunc "}}}
Bram Moolenaar071d4272004-06-13 20:20:40 +0000883
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200884" Moves the cursor from a "<" to the matching ">".
Bram Moolenaar2346a632021-06-13 19:02:49 +0200885func HtmlIndent_FindTagEnd()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200886 "{{{
887 " Call this with the cursor on the "<" of a start tag.
888 " This will move the cursor to the ">" of the matching end tag or, when it's
889 " a self-closing tag, to the matching ">".
890 " Limited to look up to b:html_indent_line_limit lines away.
891 let text = getline('.')
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100892 let tagname = matchstr(text, s:tagname . '\|!--', col('.'))
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200893 if tagname == '!--'
894 call search('--\zs>')
895 elseif s:get_tag('/' . tagname) != 0
896 " tag with a closing tag, find matching "</tag>"
897 call searchpair('<' . tagname, '', '</' . tagname . '\zs>', 'W', '', line('.') + b:html_indent_line_limit)
898 else
899 " self-closing tag, find the ">"
900 call search('\S\zs>')
901 endif
902endfunc "}}}
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200903
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200904" Indenting inside a start tag. Return the correct indent or -1 if unknown.
Bram Moolenaar2346a632021-06-13 19:02:49 +0200905func s:InsideTag(foundHtmlString)
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200906 "{{{
907 if a:foundHtmlString
908 " Inside an attribute string.
Bram Moolenaar63b74a82019-03-24 15:09:13 +0100909 " Align with the opening quote or use an external function.
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200910 let lnum = v:lnum - 1
911 if lnum > 1
912 if exists('b:html_indent_tag_string_func')
913 return b:html_indent_tag_string_func(lnum)
914 endif
Bram Moolenaar63b74a82019-03-24 15:09:13 +0100915 " If there is a double quote in the previous line, indent with the
916 " character after it.
917 if getline(lnum) =~ '"'
918 call cursor(lnum, 0)
919 normal f"
920 return virtcol('.')
921 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200922 return indent(lnum)
923 endif
924 endif
925
926 " Should be another attribute: " attr="val". Align with the previous
927 " attribute start.
928 let lnum = v:lnum
929 while lnum > 1
930 let lnum -= 1
931 let text = getline(lnum)
932 " Find a match with one of these, align with "attr":
933 " attr=
934 " <tag attr=
935 " text<tag attr=
936 " <tag>text</tag>text<tag attr=
937 " For long lines search for the first match, finding the last match
938 " gets very slow.
939 if len(text) < 300
940 let idx = match(text, '.*\s\zs[_a-zA-Z0-9-]\+="')
941 else
942 let idx = match(text, '\s\zs[_a-zA-Z0-9-]\+="')
943 endif
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100944 if idx == -1
945 " try <tag attr
946 let idx = match(text, '<' . s:tagname . '\s\+\zs\w')
947 endif
948 if idx == -1
Bram Moolenaar942db232021-02-13 18:14:48 +0100949 " after just "<tag" indent two levels more
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100950 let idx = match(text, '<' . s:tagname . '$')
951 if idx >= 0
Bram Moolenaar942db232021-02-13 18:14:48 +0100952 call cursor(lnum, idx + 1)
953 return virtcol('.') - 1 + shiftwidth() * 2
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100954 endif
955 endif
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200956 if idx > 0
Bram Moolenaarb5b75622018-03-09 22:22:21 +0100957 " Found the attribute to align with.
958 call cursor(lnum, idx)
959 return virtcol('.')
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200960 endif
961 endwhile
962 return -1
963endfunc "}}}
964
965" THE MAIN INDENT FUNCTION. Return the amount of indent for v:lnum.
Bram Moolenaar2346a632021-06-13 19:02:49 +0200966func HtmlIndent()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200967 "{{{
Bram Moolenaar9da7ff72015-01-14 12:52:36 +0100968 if prevnonblank(v:lnum - 1) < 1
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200969 " First non-blank line has no indent.
970 return 0
971 endif
972
973 let curtext = tolower(getline(v:lnum))
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200974 let indentunit = shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200975
976 let b:hi_newstate = {}
977 let b:hi_newstate.lnum = v:lnum
978
979 " When syntax HL is enabled, detect we are inside a tag. Indenting inside
980 " a tag works very differently. Do not do this when the line starts with
981 " "<", it gets the "htmlTag" ID but we are not inside a tag then.
982 if curtext !~ '^\s*<'
Bram Moolenaar7b61a542014-08-23 15:31:19 +0200983 normal! ^
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +0200984 let stack = synstack(v:lnum, col('.')) " assumes there are no tabs
985 let foundHtmlString = 0
986 for synid in reverse(stack)
987 let name = synIDattr(synid, "name")
988 if index(b:hi_insideStringNames, name) >= 0
989 let foundHtmlString = 1
990 elseif index(b:hi_insideTagNames, name) >= 0
991 " Yes, we are inside a tag.
992 let indent = s:InsideTag(foundHtmlString)
993 if indent >= 0
994 " Do not keep the state. TODO: could keep the block type.
995 let b:hi_indent.lnum = 0
996 return indent
997 endif
998 endif
999 endfor
1000 endif
1001
1002 " does the line start with a closing tag?
1003 let swendtag = match(curtext, '^\s*</') >= 0
1004
1005 if prevnonblank(v:lnum - 1) == b:hi_indent.lnum && b:hi_lasttick == b:changedtick - 1
1006 " use state (continue from previous line)
1007 else
1008 " start over (know nothing)
1009 let b:hi_indent = s:FreshState(v:lnum)
1010 endif
1011
1012 if b:hi_indent.block >= 2
1013 " within block
1014 let endtag = s:endtags[b:hi_indent.block]
1015 let blockend = stridx(curtext, endtag)
1016 if blockend >= 0
1017 " block ends here
1018 let b:hi_newstate.block = 0
1019 " calc indent for REST OF LINE (may start more blocks):
1020 call s:CountTagsAndState(strpart(curtext, blockend + strlen(endtag)))
1021 if swendtag && b:hi_indent.block != 5
1022 let indent = b:hi_indent.blocktagind + s:curind * indentunit
1023 let b:hi_newstate.baseindent = indent + s:nextrel * indentunit
1024 else
1025 let indent = s:Alien{b:hi_indent.block}()
1026 let b:hi_newstate.baseindent = b:hi_indent.blocktagind + s:nextrel * indentunit
1027 endif
1028 else
1029 " block continues
1030 " indent this line with alien method
1031 let indent = s:Alien{b:hi_indent.block}()
1032 endif
1033 else
1034 " not within a block - within usual html
1035 let b:hi_newstate.block = b:hi_indent.block
1036 if swendtag
1037 " The current line starts with an end tag, align with its start tag.
1038 call cursor(v:lnum, 1)
1039 let start_lnum = HtmlIndent_FindStartTag()
1040 if start_lnum > 0
1041 " check for the line starting with something inside a tag:
1042 " <sometag <- align here
1043 " attr=val><open> not here
1044 let text = getline(start_lnum)
1045 let angle = matchstr(text, '[<>]')
1046 if angle == '>'
1047 call cursor(start_lnum, 1)
1048 normal! f>%
1049 let start_lnum = line('.')
1050 let text = getline(start_lnum)
1051 endif
1052
1053 let indent = indent(start_lnum)
1054 if col('.') > 2
1055 let swendtag = match(text, '^\s*</') >= 0
1056 call s:CountITags(text[: col('.') - 2])
Bram Moolenaar3ec574f2017-06-13 18:12:01 +02001057 let indent += s:nextrel * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +02001058 if !swendtag
Bram Moolenaar3ec574f2017-06-13 18:12:01 +02001059 let indent += s:curind * shiftwidth()
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +02001060 endif
1061 endif
1062 else
1063 " not sure what to do
1064 let indent = b:hi_indent.baseindent
1065 endif
1066 let b:hi_newstate.baseindent = indent
1067 else
1068 call s:CountTagsAndState(curtext)
1069 let indent = b:hi_indent.baseindent
1070 let b:hi_newstate.baseindent = indent + (s:curind + s:nextrel) * indentunit
1071 endif
1072 endif
1073
1074 let b:hi_lasttick = b:changedtick
1075 call extend(b:hi_indent, b:hi_newstate, "force")
1076 return indent
1077endfunc "}}}
1078
1079" Check user settings when loading this script the first time.
Bram Moolenaarec7944a2013-06-12 21:29:15 +02001080call HtmlIndent_CheckUserSettings()
Bram Moolenaar071d4272004-06-13 20:20:40 +00001081
Bram Moolenaar91170f82006-05-05 21:15:17 +00001082let &cpo = s:cpo_save
1083unlet s:cpo_save
1084
Bram Moolenaar8bb1c3e2014-07-04 16:43:17 +02001085" vim: fdm=marker ts=8 sw=2 tw=78