changeset 6032:b8f703a4e55f

Updated runtime files. Overhauled HTML indent script.
author Bram Moolenaar <bram@vim.org>
date Fri, 04 Jul 2014 16:43:17 +0200
parents 65e33fdac4ae
children 34465a179ce2
files runtime/doc/change.txt runtime/doc/eval.txt runtime/doc/options.txt runtime/doc/spell.txt runtime/doc/syntax.txt runtime/doc/todo.txt runtime/indent/html.vim
diffstat 7 files changed, 959 insertions(+), 456 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/change.txt
+++ b/runtime/doc/change.txt
@@ -1,4 +1,4 @@
-*change.txt*    For Vim version 7.4.  Last change: 2014 Feb 11
+*change.txt*    For Vim version 7.4.  Last change: 2014 Jun 26
 
 
 		  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -1717,8 +1717,8 @@ Note that using `:sort` with `:global` d
 quite useless.
 
 The details about sorting depend on the library function used.  There is no
-guarantee that sorting is "stable" or obeys the current locale.  You will have
-to try it out.
+guarantee that sorting obeys the current locale.  You will have to try it out.
+Vim does do a "stable" sort.
 
 The sorting can be interrupted, but if you interrupt it too late in the
 process you may end up with duplicated lines.  This also depends on the system
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1,4 +1,4 @@
-*eval.txt*	For Vim version 7.4.  Last change: 2014 Jun 25
+*eval.txt*	For Vim version 7.4.  Last change: 2014 Jul 02
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -5651,6 +5651,11 @@ sort({list} [, {func} [, {dict}]])			*so
 		{dict} is for functions with the "dict" attribute.  It will be
 		used to set the local variable "self". |Dictionary-function|
 
+		The sort is stable, items which compare equal (as number or as
+		string) will keep their relative position. E.g., when sorting
+		on numbers, text strings will sort next to eachother, in the
+		same order as they were originally.
+
 		Also see |uniq()|.
 
 		Example: >
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -1,4 +1,4 @@
-*options.txt*	For Vim version 7.4.  Last change: 2014 Jun 25
+*options.txt*	For Vim version 7.4.  Last change: 2014 Jun 26
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
--- a/runtime/doc/spell.txt
+++ b/runtime/doc/spell.txt
@@ -1,4 +1,4 @@
-*spell.txt*	For Vim version 7.4.  Last change: 2013 Nov 12
+*spell.txt*	For Vim version 7.4.  Last change: 2014 Jul 02
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -939,9 +939,10 @@ be combined without errors.
 
 If you get an E763 warning that the word tables differ you need to update your
 ".spl" spell files.  If you downloaded the files, get the latest version of
-all spell files you use.  Otherwise generate the .spl file again with
-|:mkspell|.  If you still get errors check the FOL, LOW and UPP lines in the
-used .aff files.
+all spell files you use.  If you are only using one, e.g., German, then also
+download the recent English spell files.  Otherwise generate the .spl file
+again with |:mkspell|.  If you still get errors check the FOL, LOW and UPP
+lines in the used .aff files.
 
 The XX.ascii.spl spell file generated with the "-ascii" argument will not
 contain the table with characters, so that it can be combine with spell files
--- a/runtime/doc/syntax.txt
+++ b/runtime/doc/syntax.txt
@@ -1,4 +1,4 @@
-*syntax.txt*	For Vim version 7.4.  Last change: 2014 Jun 03
+*syntax.txt*	For Vim version 7.4.  Last change: 2014 Jun 27
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -1688,7 +1688,7 @@ vimrc file: >
 HTML comments are rather special (see an HTML reference document for the
 details), and the syntax coloring scheme will highlight all errors.
 However, if you prefer to use the wrong style (starts with <!-- and
-ends with --!>) you can define >
+ends with -->) you can define >
 	:let html_wrong_comments=1
 
 JavaScript and Visual Basic embedded inside HTML documents are highlighted as
--- a/runtime/doc/todo.txt
+++ b/runtime/doc/todo.txt
@@ -1,4 +1,4 @@
-*todo.txt*      For Vim version 7.4.  Last change: 2014 Jun 25
+*todo.txt*      For Vim version 7.4.  Last change: 2014 Jul 04
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -34,6 +34,14 @@ not be repeated below, unless there is e
 							*known-bugs*
 -------------------- Known bugs and current work -----------------------
 
+Sort is still not stable, add an index in the array.
+See patch from Takimoto.
+
+Another follow-up patch for breakindent. (Christian, 2014 Jun 28)
+
+After patch 7.4.305 the termresponse isn't requested at all?
+(Tomas Janousek, 2014 Jul 1, Jul 2)
+
 Regexp problems:
 - Bug with pattern: '\vblock (\d+)\.\n.*\d+%(\1)@<!\.$'
   (Lech Lorens, 2014 Feb 3)
@@ -52,13 +60,30 @@ work. (ZyX, 2013 Sep 28)
 
 Completion for :buf does not use 'wildignorecase'. (Akshay H, 2014 May 31)
 
+Patch to avoid inconsistency of using \v and \V after $ in regexp.
+(Ozaki Kiichi, 2014 Jul 2)
+
 ":cd C:\Windows\System32\drivers\etc*" does not work, even though the
 directory exists. (Sergio Gallelli, 2013 Dec 29)
 
+Make matchparen faster by not using winsaveview()/winrestview().
+(Alexey Radkov, 2014 Jun 28)
+
+Patch to avoid flicker when filling the preview window.
+(Hirohito Higashi, 2014 Jul 2)
+
+matchaddpos() doesn't handle wrong byte length well, e.g. using 1 on a
+multi-byte charcter. (lcd47, 2014 Jun 29)
+
+The entries added by matchaddpos() are returned by getmatches() but can't be
+set with setmatches(). (lcd47, 2014 Jun 29)
+
 Problem using ":try" inside ":execute". (ZyX, 2013 Sep 15)
 
 Python: ":py raw_input('prompt')" doesn't work. (Manu Hack)
 
+When using an undo file, also restore the changelist, so that "g;" works.
+
 Value returned by virtcol() changes depending on how lines wrap.  This is
 inconsistant with the documentation.
 
@@ -123,7 +148,7 @@ 2014 Jun 8)
 
 When 'clipboard' is "unnamed", :g/pat/d is very slow.  Only set the clipboard
 after the last delete? (Praful, 2014 May 28)
-Patch by Christian Brabandt, 2014 Jun 18.
+Patch by Christian Brabandt, 2014 Jun 18.  Update Jun 25.
 
 Include a plugin manager with Vim? Neobundle seems to be the best currently.
 Long message about this from ZyX, 2014 Mar 23.  And following replies.
@@ -162,6 +187,9 @@ Out of scope:
 Setting the spell file in a session only reads the local additions, not the
 normal spell file. (Enno Nagel, 2014 Mar 29)
 
+CTRL-] in Visual mode uses the selected text as a tag.  This does not work
+when preceded with CTRL-W. (Patrick Hemmer, 2014 Jun 28)
+
 When typing the first character of a command, e.g. "f", then using a menu, the
 menu item doesn't work.  Clear typeahead when using a menu?
 
@@ -171,6 +199,9 @@ Editing an ascii file as ucs-2 or ucs-4 
 ":Next 1 some-arg" does not complain about trailing argument.  Also for
 various other commands.  (ZyX, 2014 Mar 30)
 
+patch to skip sort if no line matches the expression.
+(Christian Brabandt, 2014 Jun 25)
+
 Patch to add sortuniq(). (Cade Forester, 2014 Mar 19)
 Or add uniq() instead?  Patch by lcd47, but it has problems.
 
--- a/runtime/indent/html.vim
+++ b/runtime/indent/html.vim
@@ -1,537 +1,1003 @@
 " Vim indent script for HTML
-" General: "{{{
-" File:		html.vim (Vimscript #2075)
-" Author:	Andy Wokula <anwoku@yahoo.de>
-" Last Change:	2014 Jun 19
-" Rev Days:     13
-" Version:	0.9
-" Vim Version:	Vim7
-" Description:
-"   Improved version of the distributed html indent script, faster on a
-"   range of lines.
+" Header: "{{{
+" Maintainer:	Bram Moolenaar
+" Original Author: Andy Wokula <anwoku@yahoo.de>
+" Last Change:	2014 Jul 04
+" Version:	1.0
+" Description:	HTML indent script with cached state for faster indenting on a
+"		range of lines.
+"		Supports template systems through hooks.
+"		Supports Closure stylesheets.
 "
 " Credits:
 "	indent/html.vim (2006 Jun 05) from J. Zellner
 "	indent/css.vim (2006 Dec 20) from N. Weibull
 "
 " History:
+" 2014 June	(v1.0) overhaul (Bram)
 " 2012 Oct 21	(v0.9) added support for shiftwidth()
 " 2011 Sep 09	(v0.8) added HTML5 tags (thx to J. Zuckerman)
 " 2008 Apr 28	(v0.6) revised customization
 " 2008 Mar 09	(v0.5) fixed 'indk' issue (thx to C.J. Robinson)
-" }}}
+"}}}
 
-" Init Folklore, check user settings (2nd time ++) "{{{
-if exists("b:did_indent")
-    finish
+" Init Folklore, check user settings (2nd time ++)
+if exists("b:did_indent") "{{{
+  finish
 endif
 let b:did_indent = 1
 
 setlocal indentexpr=HtmlIndent()
 setlocal indentkeys=o,O,<Return>,<>>,{,},!^F
 
-" Needed for % to work when finding start of a tag.
+" "j1" is included to make cindent() work better with Javascript.
+setlocal cino=j1
+" "J1" should be included, but it doen't work properly before 7.4.355.
+if has("patch-7.4.355")
+  setlocal cino+=J1
+endif
+" Before patch 7.4.355 indenting after "(function() {" does not work well, add
+" )2 to limit paren search.
+if !has("patch-7.4.355")
+  setlocal cino+=)2
+endif
+
+" Needed for % to work when finding start/end of a tag.
 setlocal matchpairs+=<:>
 
-let b:indent = {"lnum": -1}
-let b:undo_indent = "set inde< indk<| unlet b:indent"
+let b:undo_indent = "setlocal inde< indk< cino<"
+
+" b:hi_indent keeps state to speed up indenting consecutive lines.
+let b:hi_indent = {"lnum": -1}
 
-" Load Once:
-if exists("*HtmlIndent")
-    call HtmlIndent_CheckUserSettings()
-    finish
+"""""" Code below this is loaded only once. """""
+if exists("*HtmlIndent") && !exists('g:force_reload_html')
+  call HtmlIndent_CheckUserSettings()
+  finish
 endif
 
-" Patch 7.3.694
+" shiftwidth() exists since patch 7.3.694
 if exists('*shiftwidth')
-    let s:ShiftWidth = function('shiftwidth')
+  let s:ShiftWidth = function('shiftwidth')
 else
-    func! s:ShiftWidth()
-	return &shiftwidth
-    endfunc
+  func! s:ShiftWidth()
+    return &shiftwidth
+  endfunc
 endif
 
+" Allow for line continuation below.
 let s:cpo_save = &cpo
 set cpo-=C
 "}}}
 
-func! HtmlIndent_CheckUserSettings() "{{{
-    if exists("g:html_indent_inctags")
-	call s:AddITags(split(g:html_indent_inctags, ","))
-    endif
-    if exists("g:html_indent_autotags")
-	call s:RemoveITags(split(g:html_indent_autotags, ","))
-    endif
+" Check and process settings from b:html_indent and g:html_indent... variables.
+" Prefer using buffer-local settings over global settings, so that there can
+" be defaults for all HTML files and exceptions for specific types of HTML
+" files.
+func! HtmlIndent_CheckUserSettings()
+  "{{{
+  let inctags = ''
+  if exists("b:html_indent_inctags")
+    let inctags = b:html_indent_inctags
+  elseif exists("g:html_indent_inctags")
+    let inctags = g:html_indent_inctags
+  endif
+  let b:hi_tags = {}
+  if len(inctags) > 0
+    call s:AddITags(b:hi_tags, split(inctags, ","))
+  endif
+
+  let autotags = ''
+  if exists("b:html_indent_autotags")
+    let autotags = b:html_indent_autotags
+  elseif exists("g:html_indent_autotags")
+    let autotags = g:html_indent_autotags
+  endif
+  let b:hi_removed_tags = {}
+  if autotags
+    call s:RemoveITags(b:hi_removed_tags, split(autotags, ","))
+  endif
+
+  " Syntax names indicating being inside a string of an attribute value.
+  let string_names = []
+  if exists("b:html_indent_string_names")
+    let string_names = b:html_indent_string_names
+  elseif exists("g:html_indent_string_names")
+    let string_names = g:html_indent_string_names
+  endif
+  let b:hi_insideStringNames = ['htmlString']
+  if len(string_names) > 0
+    for s in string_names
+      call add(b:hi_insideStringNames, s)
+    endfor
+  endif
 
-    let indone = {"zero": 0
-		\,"auto": "indent(prevnonblank(v:lnum-1))"
-		\,"inc": "b:indent.blocktagind + s:ShiftWidth()"}
-    if exists("g:html_indent_script1")
-	let s:js1indent = get(indone, g:html_indent_script1, indone.zero)
+  " Syntax names indicating being inside a tag.
+  let tag_names = []
+  if exists("b:html_indent_tag_names")
+    let tag_names = b:html_indent_tag_names
+  elseif exists("g:html_indent_tag_names")
+    let tag_names = g:html_indent_tag_names
+  endif
+  let b:hi_insideTagNames = ['htmlTag', 'htmlScriptTag']
+  if len(tag_names) > 0
+    for s in tag_names
+      call add(b:hi_insideTagNames, s)
+    endfor
+  endif
+
+  let indone = {"zero": 0
+              \,"auto": "indent(prevnonblank(v:lnum-1))"
+              \,"inc": "b:hi_indent.blocktagind + s:ShiftWidth()"}
+
+  let script1 = ''
+  if exists("b:html_indent_script1")
+    let script1 = b:html_indent_script1
+  elseif exists("g:html_indent_script1")
+    let script1 = g:html_indent_script1
+  endif
+  if len(script1) > 0
+    let b:hi_js1indent = get(indone, script1, indone.zero)
+  else
+    let b:hi_js1indent = 0
+  endif
+
+  let style1 = ''
+  if exists("b:html_indent_style1")
+    let style1 = b:html_indent_style1
+  elseif exists("g:html_indent_style1")
+    let style1 = g:html_indent_style1
+  endif
+  if len(style1) > 0
+    let b:hi_css1indent = get(indone, style1, indone.zero)
+  else
+    let b:hi_css1indent = 0
+  endif
+
+  if !exists('b:html_indent_line_limit')
+    if exists('g:html_indent_line_limit')
+      let b:html_indent_line_limit = g:html_indent_line_limit
+    else
+      let b:html_indent_line_limit = 200
     endif
-    if exists("g:html_indent_style1")
-	let s:css1indent = get(indone, g:html_indent_style1, indone.zero)
-    endif
+  endif
 endfunc "}}}
 
-" Init Script Vars  "{{{
-let s:lasttick = 0
-let s:css1indent = 0
-let s:js1indent = 0
-" not to be changed:
-let s:endtags = [0,0,0,0,0,0,0,0]   " some places unused
-let s:newstate = {}
+" Init Script Vars
+"{{{
+let b:hi_lasttick = 0
+let b:hi_newstate = {}
 let s:countonly = 0
  "}}}
-func! s:AddITags(taglist) "{{{
-    for itag in a:taglist
-	let s:indent_tags[itag] = 1
-	let s:indent_tags['/'.itag] = -1
-    endfor
-endfunc "}}}
-func! s:AddBlockTag(tag, id, ...) "{{{
-    if !(a:id >= 2 && a:id < 2+len(s:endtags))
-	return
-    endif
-    let s:indent_tags[a:tag] = a:id
-    if a:0 == 0
-	let s:indent_tags['/'.a:tag] = -a:id
-	let s:endtags[a:id-2] = "</".a:tag.">"
-    else
-	let s:indent_tags[a:1] = -a:id
-	let s:endtags[a:id-2] = a:1
-    endif
+
+" Fill the s:indent_tags dict with known tags.
+" The key is "tagname" or "/tagname".  {{{
+" The value is:
+" 1   opening tag
+" 2   "pre"
+" 3   "script"
+" 4   "style"
+" 5   comment start
+" -1  closing tag
+" -2  "/pre"
+" -3  "/script"
+" -4  "/style"
+" -5  comment end
+let s:indent_tags = {}
+let s:endtags = [0,0,0,0,0,0]   " long enough for the highest index
+"}}}
+
+" Add a list of tag names for a pair of <tag> </tag> to "tags".
+func! s:AddITags(tags, taglist)
+  "{{{
+  for itag in a:taglist
+    let a:tags[itag] = 1
+    let a:tags['/' . itag] = -1
+  endfor
 endfunc "}}}
-func! s:RemoveITags(taglist) "{{{
-    " remove itags (protect blocktags from being removed)
-    for itag in a:taglist
-	if !has_key(s:indent_tags, itag) || s:indent_tags[itag] != 1
-	    continue
-	endif
-	unlet s:indent_tags[itag]
-	if itag =~ '^\w\+$'
-	    unlet s:indent_tags["/".itag]
-	endif
-    endfor
+
+" Take a list of tag name pairs that are not to be used as tag pairs.
+func! s:RemoveITags(tags, taglist)
+  "{{{
+  for itag in a:taglist
+    let a:tags[itag] = 1
+    let a:tags['/' . itag] = 1
+  endfor
 endfunc "}}}
-" Add Indent Tags: {{{
-if !exists("s:indent_tags")
-    let s:indent_tags = {}
-endif
 
-" old tags:
-call s:AddITags(['a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big',
-    \ 'blockquote', 'button', 'caption', 'center', 'cite', 'code', 'colgroup',
-    \ 'del', 'dfn', 'dir', 'div', 'dl', 'em', 'fieldset', 'font', 'form',
-    \ 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'i', 'iframe', 'ins', 'kbd',
-    \ 'label', 'legend', 'map', 'menu', 'noframes', 'noscript', 'object', 'ol',
+" Add a block tag, that is a tag with a different kind of indenting.
+func! s:AddBlockTag(tag, id, ...)
+  "{{{
+  if !(a:id >= 2 && a:id < len(s:endtags))
+    echoerr 'AddBlockTag ' . a:id
+    return
+  endif
+  let s:indent_tags[a:tag] = a:id
+  if a:0 == 0
+    let s:indent_tags['/' . a:tag] = -a:id
+    let s:endtags[a:id] = "</" . a:tag . ">"
+  else
+    let s:indent_tags[a:1] = -a:id
+    let s:endtags[a:id] = a:1
+  endif
+endfunc "}}}
+
+" Add known tag pairs.
+" Self-closing tags and tags that are sometimes {{{
+" self-closing (e.g., <p>) are not here (when encountering </p> we can find
+" the matching <p>, but not the other way around).
+" Old HTML tags:
+call s:AddITags(s:indent_tags, [
+    \ 'a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big',
+    \ 'blockquote', 'body', 'button', 'caption', 'center', 'cite', 'code',
+    \ 'colgroup', 'del', 'dfn', 'dir', 'div', 'dl', 'em', 'fieldset', 'font',
+    \ 'form', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'html',
+    \ 'i', 'iframe', 'ins', 'kbd', 'label', 'legend', 'li',
+    \ 'map', 'menu', 'noframes', 'noscript', 'object', 'ol',
     \ 'optgroup', 'q', 's', 'samp', 'select', 'small', 'span', 'strong', 'sub',
     \ 'sup', 'table', 'textarea', 'title', 'tt', 'u', 'ul', 'var', 'th', 'td',
-    \ 'tr', 'tfoot', 'thead'])
+    \ 'tr', 'tbody', 'tfoot', 'thead'])
 
-" tags added 2011 Sep 09 (especially HTML5 tags):
-call s:AddITags(['area', 'article', 'aside', 'audio', 'bdi', 'canvas',
+" Tags added 2011 Sep 09 (especially HTML5 tags):
+call s:AddITags(s:indent_tags, [
+    \ 'area', 'article', 'aside', 'audio', 'bdi', 'canvas',
     \ 'command', 'datalist', 'details', 'embed', 'figure', 'footer',
     \ 'header', 'group', 'keygen', 'mark', 'math', 'meter', 'nav', 'output',
     \ 'progress', 'ruby', 'section', 'svg', 'texture', 'time', 'video',
     \ 'wbr', 'text'])
+"}}}
 
-"}}}
-" Add Block Tags: contain alien content "{{{
+" Add Block Tags: these contain alien content
+"{{{
 call s:AddBlockTag('pre', 2)
 call s:AddBlockTag('script', 3)
 call s:AddBlockTag('style', 4)
 call s:AddBlockTag('<!--', 5, '-->')
 "}}}
 
-func! s:CountITags(...) "{{{
+" Return non-zero when "tagname" is an opening tag, not being a block tag, for
+" which there should be a closing tag.  Can be used by scripts that include
+" HTML indenting.
+func! HtmlIndent_IsOpenTag(tagname)
+  "{{{
+  if get(s:indent_tags, a:tagname) == 1
+    return 1
+  endif
+  return get(b:hi_tags, a:tagname) == 1
+endfunc "}}}
 
-    " relative indent steps for current line [unit &sw]:
-    let s:curind = 0
-    " relative indent steps for next line [unit &sw]:
-    let s:nextrel = 0
+" Get the value for "tagname", taking care of buffer-local tags.
+func! s:get_tag(tagname)
+  "{{{
+  let i = get(s:indent_tags, a:tagname)
+  if (i == 1 || i == -1) && get(b:hi_removed_tags, a:tagname) != 0
+    return 0
+  endif
+  if i == 0
+    let i = get(b:hi_tags, a:tagname)
+  endif
+  return i
+endfunc "}}}
 
-    if a:0==0
-	let s:block = s:newstate.block
-	let tmpline = substitute(s:curline, '<\zs/\=\w\+\>\|<!--\|-->', '\=s:CheckTag(submatch(0))', 'g')
-	if s:block == 3
-	    let s:newstate.scripttype = s:GetScriptType(matchstr(tmpline, '\C.*<SCRIPT\>\zs[^>]*'))
-	endif
-	let s:newstate.block = s:block
-    else
-	let s:block = 0		" assume starting outside of a block
-	let s:countonly = 1	" don't change state
-	let tmpline = substitute(s:altline, '<\zs/\=\w\+\>\|<!--\|-->', '\=s:CheckTag(submatch(0))', 'g')
-	let s:countonly = 0
-    endif
+" Count the number of start and end tags in "text".
+func! s:CountITags(text)
+  "{{{
+  " Store the result in s:curind and s:nextrel.
+  let s:curind = 0  " relative indent steps for current line [unit &sw]:
+  let s:nextrel = 0  " relative indent steps for next line [unit &sw]:
+  let s:block = 0		" assume starting outside of a block
+  let s:countonly = 1	" don't change state
+  call substitute(a:text, '<\zs/\=\w\+\>\|<!--\|-->', '\=s:CheckTag(submatch(0))', 'g')
+  let s:countonly = 0
+endfunc "}}}
+
+" Count the number of start and end tags in text.
+func! s:CountTagsAndState(text)
+  "{{{
+  " Store the result in s:curind and s:nextrel.  Update b:hi_newstate.block.
+  let s:curind = 0  " relative indent steps for current line [unit &sw]:
+  let s:nextrel = 0  " relative indent steps for next line [unit &sw]:
+
+  let s:block = b:hi_newstate.block
+  let tmp = substitute(a:text, '<\zs/\=\w\+\>\|<!--\|-->', '\=s:CheckTag(submatch(0))', 'g')
+  if s:block == 3
+    let b:hi_newstate.scripttype = s:GetScriptType(matchstr(tmp, '\C.*<SCRIPT\>\zs[^>]*'))
+  endif
+  let b:hi_newstate.block = s:block
 endfunc "}}}
-func! s:CheckTag(itag) "{{{
-    " "tag" or "/tag" or "<!--" or "-->"
-    let ind = get(s:indent_tags, a:itag)
-    if ind == -1
-	" closing tag
-	if s:block != 0
-	    " ignore itag within a block
-	    return "foo"
-	endif
-	if s:nextrel == 0
-	    let s:curind -= 1
-	else
-	    let s:nextrel -= 1
-	endif
-	" if s:curind >= 1
-	"     let s:curind -= 1
-	" else
-	"     let s:nextrel -= 1
-	" endif
-    elseif ind == 1
-	" opening tag
-	if s:block != 0
-	    return "foo"
-	endif
-	let s:nextrel += 1
-    elseif ind != 0
-	" block-tag (opening or closing)
-	return s:Blocktag(a:itag, ind)
+
+" Used by s:CountITags() and s:CountTagsAndState().
+func! s:CheckTag(itag)
+  "{{{
+  " Returns an empty string or "SCRIPT".
+  " a:itag can be "tag" or "/tag" or "<!--" or "-->"
+  let ind = s:get_tag(a:itag)
+  if ind == -1
+    " closing tag
+    if s:block != 0
+      " ignore itag within a block
+      return ""
     endif
-    " else ind==0 (other tag found): keep indent
-    return "foo"   " no matter
+    if s:nextrel == 0
+      let s:curind -= 1
+    else
+      let s:nextrel -= 1
+    endif
+  elseif ind == 1
+    " opening tag
+    if s:block != 0
+      return ""
+    endif
+    let s:nextrel += 1
+  elseif ind != 0
+    " block-tag (opening or closing)
+    return s:CheckBlockTag(a:itag, ind)
+  " else ind==0 (other tag found): keep indent
+  endif
+  return ""
 endfunc "}}}
-func! s:Blocktag(blocktag, ind) "{{{
-    if a:ind > 0
-	" a block starts here
-	if s:block != 0
-	    " already in a block (nesting) - ignore
-	    " especially ignore comments after other blocktags
-	    return "foo"
-	endif
-	let s:block = a:ind		" block type
-	if s:countonly
-	    return "foo"
-	endif
-	let s:newstate.blocklnr = v:lnum
-	" save allover indent for the endtag
-	let s:newstate.blocktagind = b:indent.baseindent + (s:nextrel + s:curind) * s:ShiftWidth()
-	if a:ind == 3
-	    return "SCRIPT"    " all except this must be lowercase
-	    " line is to be checked again for the type attribute
-	endif
-    else
-	let s:block = 0
-	" we get here if starting and closing block-tag on same line
+
+" Used by s:CheckTag(). Returns an empty string or "SCRIPT".
+func! s:CheckBlockTag(blocktag, ind)
+  "{{{
+  if a:ind > 0
+    " a block starts here
+    if s:block != 0
+      " already in a block (nesting) - ignore
+      " especially ignore comments after other blocktags
+      return ""
+    endif
+    let s:block = a:ind		" block type
+    if s:countonly
+      return ""
     endif
-    return "foo"
+    let b:hi_newstate.blocklnr = v:lnum
+    " save allover indent for the endtag
+    let b:hi_newstate.blocktagind = b:hi_indent.baseindent + (s:nextrel + s:curind) * s:ShiftWidth()
+    if a:ind == 3
+      return "SCRIPT"    " all except this must be lowercase
+      " line is to be checked again for the type attribute
+    endif
+  else
+    let s:block = 0
+    " we get here if starting and closing a block-tag on the same line
+  endif
+  return ""
 endfunc "}}}
-func! s:GetScriptType(str) "{{{
-    if a:str == "" || a:str =~ "java"
-	return "javascript"
-    else
-	return ""
-    endif
+
+" Return the <script> type: either "javascript" or ""
+func! s:GetScriptType(str)
+  "{{{
+  if a:str == "" || a:str =~ "java"
+    return "javascript"
+  else
+    return ""
+  endif
 endfunc "}}}
 
-func! s:FreshState(lnum) "{{{
-    " Look back in the file (lines 1 to a:lnum-1) to calc a state for line
-    " a:lnum.  A state is to know ALL relevant details about the lines
-    " 1..a:lnum-1, initial calculating (here!) can be slow, but updating is
-    " fast (incremental).
-    " State:
-    "	lnum		last indented line == prevnonblank(a:lnum - 1)
-    "	block = 0	a:lnum located within special tag: 0:none, 2:<pre>,
-    "			3:<script>, 4:<style>, 5:<!--
-    "	baseindent	use this indent for line a:lnum as a start - kind of
-    "			autoindent (if block==0)
-    "	scripttype = ''	type attribute of a script tag (if block==3)
-    "	blocktagind	indent for current opening (get) and closing (set)
-    "			blocktag (if block!=0)
-    "	blocklnr	lnum of starting blocktag (if block!=0)
-    "	inattr		line {lnum} starts with attributes of a tag
-    let state = {}
-    let state.lnum = prevnonblank(a:lnum - 1)
-    let state.scripttype = ""
-    let state.blocktagind = -1
-    let state.block = 0
-    let state.baseindent = 0
-    let state.blocklnr = 0
-    let state.inattr = 0
+" Look back in the file, starting at a:lnum - 1, to compute a state for the
+" start of line a:lnum.  Return the new state.
+func! s:FreshState(lnum)
+  "{{{
+  " A state is to know ALL relevant details about the
+  " lines 1..a:lnum-1, initial calculating (here!) can be slow, but updating is
+  " fast (incremental).
+  " TODO: this should be split up in detecting the block type and computing the
+  " indent for the block type, so that when we do not know the indent we do
+  " not need to clear the whole state and re-detect the block type again.
+  " State:
+  "	lnum		last indented line == prevnonblank(a:lnum - 1)
+  "	block = 0	a:lnum located within special tag: 0:none, 2:<pre>,
+  "			3:<script>, 4:<style>, 5:<!--
+  "	baseindent	use this indent for line a:lnum as a start - kind of
+  "			autoindent (if block==0)
+  "	scripttype = ''	type attribute of a script tag (if block==3)
+  "	blocktagind	indent for current opening (get) and closing (set)
+  "			blocktag (if block!=0)
+  "	blocklnr	lnum of starting blocktag (if block!=0)
+  "	inattr		line {lnum} starts with attributes of a tag
+  let state = {}
+  let state.lnum = prevnonblank(a:lnum - 1)
+  let state.scripttype = ""
+  let state.blocktagind = -1
+  let state.block = 0
+  let state.baseindent = 0
+  let state.blocklnr = 0
+  let state.inattr = 0
 
-    if state.lnum == 0
-	return state
-    endif
+  if state.lnum == 0
+    return state
+  endif
 
-    " Heuristic:
-    " remember startline state.lnum
-    " look back for <pre, </pre, <script, </script, <style, </style tags
-    " remember stopline
-    " if opening tag found,
-    "	assume a:lnum within block
-    " else
-    "	look back in result range (stopline, startline) for comment
-    "	    \ delimiters (<!--, -->)
-    "	if comment opener found,
-    "	    assume a:lnum within comment
-    "	else
-    "	    assume usual html for a:lnum
-    "	    if a:lnum-1 has a closing comment
-    "		look back to get indent of comment opener
-    " FI
+  " Heuristic:
+  " remember startline state.lnum
+  " look back for <pre, </pre, <script, </script, <style, </style tags
+  " remember stopline
+  " if opening tag found,
+  "	assume a:lnum within block
+  " else
+  "	look back in result range (stopline, startline) for comment
+  "	    \ delimiters (<!--, -->)
+  "	if comment opener found,
+  "	    assume a:lnum within comment
+  "	else
+  "	    assume usual html for a:lnum
+  "	    if a:lnum-1 has a closing comment
+  "		look back to get indent of comment opener
+  " FI
 
-    " look back for blocktag
-    call cursor(a:lnum, 1)
-    let [stopline, stopcol] = searchpos('\c<\zs\/\=\%(pre\>\|script\>\|style\>\)', "bW")
+  " look back for a blocktag
+  call cursor(a:lnum, 1)
+  let [stopline, stopcol] = searchpos('\c<\zs\/\=\%(pre\>\|script\>\|style\>\)', "bW")
+  if stopline > 0
     " fugly ... why isn't there searchstr()
     let tagline = tolower(getline(stopline))
-    let blocktag = matchstr(tagline, '\/\=\%(pre\>\|script\>\|style\>\)', stopcol-1)
-    if stopline > 0 && blocktag[0] != "/"
-	" opening tag found, assume a:lnum within block
-	let state.block = s:indent_tags[blocktag]
-	if state.block == 3
-	    let state.scripttype = s:GetScriptType(matchstr(tagline, '\>[^>]*', stopcol))
-	endif
-	let state.blocklnr = stopline
-	" check preceding tags in the line:
-	let s:altline = tagline[: stopcol-2]
-	call s:CountITags(1)
-	let state.blocktagind = indent(stopline) + (s:curind + s:nextrel) * s:ShiftWidth()
-	return state
+    let blocktag = matchstr(tagline, '\/\=\%(pre\>\|script\>\|style\>\)', stopcol - 1)
+    if blocktag[0] != "/"
+      " opening tag found, assume a:lnum within block
+      let state.block = s:indent_tags[blocktag]
+      if state.block == 3
+        let state.scripttype = s:GetScriptType(matchstr(tagline, '\>[^>]*', stopcol))
+      endif
+      let state.blocklnr = stopline
+      " check preceding tags in the line:
+      call s:CountITags(tagline[: stopcol-2])
+      let state.blocktagind = indent(stopline) + (s:curind + s:nextrel) * s:ShiftWidth()
+      return state
     elseif stopline == state.lnum
-	" handle special case: previous line (= state.lnum) contains a
-	" closing blocktag which is preceded by line-noise;
-	" blocktag == "/..."
-	let swendtag = match(tagline, '^\s*</') >= 0
-	if !swendtag
-	    let [bline, bcol] = searchpos('<'.blocktag[1:].'\>', "bW")
-	    let s:altline = tolower(getline(bline)[: bcol-2])
-	    call s:CountITags(1)
-	    let state.baseindent = indent(bline) + (s:nextrel+s:curline) * s:ShiftWidth()
-	    return state
-	endif
+      " handle special case: previous line (= state.lnum) contains a
+      " closing blocktag which is preceded by line-noise;
+      " blocktag == "/..."
+      let swendtag = match(tagline, '^\s*</') >= 0
+      if !swendtag
+        let [bline, bcol] = searchpos('<'.blocktag[1:].'\>', "bW")
+        call s:CountITags(tolower(getline(bline)[: bcol-2]))
+        let state.baseindent = indent(bline) + (s:curind + s:nextrel) * s:ShiftWidth()
+        return state
+      endif
+    endif
+  endif
+
+  " else look back for comment
+  call cursor(a:lnum, 1)
+  let [comlnum, comcol, found] = searchpos('\(<!--\)\|-->', 'bpW', stopline)
+  if found == 2
+    " comment opener found, assume a:lnum within comment
+    let state.block = 5
+    let state.blocklnr = comlnum
+    " check preceding tags in the line:
+    call s:CountITags(tolower(getline(comlnum)[: comcol-2]))
+    let state.blocktagind = indent(comlnum) + (s:curind + s:nextrel) * s:ShiftWidth()
+    return state
+  endif
+
+  " else within usual HTML
+  let text = tolower(getline(state.lnum))
+
+  " Check a:lnum-1 for closing comment (we need indent from the opening line).
+  " Not when other tags follow (might be --> inside a string).
+  let comcol = stridx(text, '-->')
+  if comcol >= 0 && match(text, '[<>]', comcol) <= 0
+    call cursor(state.lnum, comcol + 1)
+    let [comlnum, comcol] = searchpos('<!--', 'bW')
+    if comlnum == state.lnum
+      let text = text[: comcol-2]
+    else
+      let text = tolower(getline(comlnum)[: comcol-2])
+    endif
+    call s:CountITags(text)
+    let state.baseindent = indent(comlnum) + (s:curind + s:nextrel) * s:ShiftWidth()
+    " TODO check tags that follow "-->"
+    return state
+  endif
+
+  " Check if the previous line starts with end tag.
+  let swendtag = match(text, '^\s*</') >= 0
+
+  " If previous line ended in a closing tag, line up with the opening tag.
+  if !swendtag && text =~ '</\w\+\s*>\s*$'
+    call cursor(state.lnum, 99999)
+    normal F<
+    let start_lnum = HtmlIndent_FindStartTag()
+    if start_lnum > 0
+      let state.baseindent = indent(start_lnum)
+      if col('.') > 2
+        " check for tags before the matching opening tag.
+        let text = getline(start_lnum)
+        let swendtag = match(text, '^\s*</') >= 0
+        call s:CountITags(text[: col('.') - 2])
+        let state.baseindent += s:nextrel * s:ShiftWidth()
+        if !swendtag
+          let state.baseindent += s:curind * s:ShiftWidth()
+        endif
+      endif
+      return state
     endif
+  endif
+
+  " Else: no comments. Skip backwards to find the tag we're inside.
+  let [state.lnum, found] = HtmlIndent_FindTagStart(state.lnum)
+  " Check if that line starts with end tag.
+  let text = getline(state.lnum)
+  let swendtag = match(text, '^\s*</') >= 0
+  call s:CountITags(tolower(text))
+  let state.baseindent = indent(state.lnum) + s:nextrel * s:ShiftWidth()
+  if !swendtag
+    let state.baseindent += s:curind * s:ShiftWidth()
+  endif
+  return state
+endfunc "}}}
+
+" Indent inside a <pre> block: Keep indent as-is.
+func! s:Alien2()
+  "{{{
+  return -1
+endfunc "}}}
+
+" Return the indent inside a <script> block for javascript.
+func! s:Alien3()
+  "{{{
+  let lnum = prevnonblank(v:lnum - 1)
+  while lnum > 1 && getline(lnum) =~ '^\s*/[/*]'
+    " Skip over comments to avoid that cindent() aligns with the <script> tag
+    let lnum = prevnonblank(lnum - 1)
+  endwhile
+  if lnum == b:hi_indent.blocklnr
+    " indent for the first line after <script>
+    return eval(b:hi_js1indent)
+  endif
+  if b:hi_indent.scripttype == "javascript"
+    return cindent(v:lnum)
+  else
+    return -1
+  endif
+endfunc "}}}
+
+" Return the indent inside a <style> block.
+func! s:Alien4()
+  "{{{
+  if prevnonblank(v:lnum-1) == b:hi_indent.blocklnr
+    " indent for first content line
+    return eval(b:hi_css1indent)
+  endif
+  return s:CSSIndent()
+endfunc "}}}
+
+" Indending inside a <style> block.  Returns the indent.
+func! s:CSSIndent()
+  "{{{
+  " This handles standard CSS and also Closure stylesheets where special lines
+  " start with @.
+  " When the line starts with '*' or the previous line starts with "/*"
+  " and does not end in "*/", use C indenting to format the comment.
+  " Adopted $VIMRUNTIME/indent/css.vim
+  let curtext = getline(v:lnum)
+  if curtext =~ '^\s*[*]'
+        \ || (v:lnum > 1 && getline(v:lnum - 1) =~ '\s*/\*'
+        \     && getline(v:lnum - 1) !~ '\*/\s*$')
+    return cindent(v:lnum)
+  endif
+
+  let min_lnum = b:hi_indent.blocklnr
+  let prev_lnum = s:CssPrevNonComment(v:lnum - 1, min_lnum)
+  let [prev_lnum, found] = HtmlIndent_FindTagStart(prev_lnum)
+  if prev_lnum <= min_lnum
+    " Just below the <style> tag, indent for first content line after comments.
+    return eval(b:hi_css1indent)
+  endif
+
+  " If the current line starts with "}" align with it's match.
+  if curtext =~ '^\s*}'
+    call cursor(v:lnum, 1)
+    try
+      normal! %
+      " Found the matching "{", align with it after skipping unfinished lines.
+      let align_lnum = s:CssFirstUnfinished(line('.'), min_lnum)
+      return indent(align_lnum)
+    catch
+      " can't find it, try something else, but it's most likely going to be
+      " wrong
+    endtry
+  endif
 
-    " else look back for comment
-    call cursor(a:lnum, 1)
-    let [comline, comcol, found] = searchpos('\(<!--\)\|-->', 'bpW', stopline)
-    if found == 2
-	" comment opener found, assume a:lnum within comment
-	let state.block = 5
-	let state.blocklnr = comline
-	" check preceding tags in the line:
-	let s:altline = tolower(getline(comline)[: comcol-2])
-	call s:CountITags(1)
-	let state.blocktagind = indent(comline) + (s:curind + s:nextrel) * s:ShiftWidth()
-	return state
+  " add indent after {
+  let brace_counts = HtmlIndent_CountBraces(prev_lnum)
+  let extra = brace_counts.c_open * s:ShiftWidth()
+
+  let prev_text = getline(prev_lnum)
+  let below_end_brace = prev_text =~ '}\s*$'
+
+  " Search back to align with the first line that's unfinished.
+  let align_lnum = s:CssFirstUnfinished(prev_lnum, min_lnum)
+
+  " Handle continuation lines if aligning with previous line and not after a
+  " "}".
+  if extra == 0 && align_lnum == prev_lnum && !below_end_brace
+    let prev_hasfield = prev_text =~ '^\s*[a-zA-Z0-9-]\+:'
+    let prev_special = prev_text =~ '^\s*\(/\*\|@\)'
+    if curtext =~ '^\s*\(/\*\|@\)'
+      " if the current line is not a comment or starts with @ (used by template
+      " systems) reduce indent if previous line is a continuation line
+      if !prev_hasfield && !prev_special
+        let extra = -s:ShiftWidth()
+      endif
+    else
+      let cur_hasfield = curtext =~ '^\s*[a-zA-Z0-9-]\+:'
+      let prev_unfinished = s:CssUnfinished(prev_text)
+      if !cur_hasfield && (prev_hasfield || prev_unfinished)
+        " Continuation line has extra indent if the previous line was not a
+        " continuation line.
+        let extra = s:ShiftWidth()
+        " Align with @if
+        if prev_text =~ '^\s*@if '
+          let extra = 4
+        endif
+      elseif cur_hasfield && !prev_hasfield && !prev_special
+        " less indent below a continuation line
+        let extra = -s:ShiftWidth()
+      endif
+    endif
+  endif
+
+  if below_end_brace
+    " find matching {, if that line starts with @ it's not the start of a rule
+    " but something else from a template system
+    call cursor(prev_lnum, 1)
+    call search('}\s*$')
+    try
+      normal! %
+      " Found the matching "{", align with it.
+      let align_lnum = s:CssFirstUnfinished(line('.'), min_lnum)
+      let special = getline(align_lnum) =~ '^\s*@'
+    catch
+      let special = 0
+    endtry
+    if special
+      " do not reduce indent below @{ ... }
+      if extra < 0
+        let extra += s:ShiftWidth()
+      endif
+    else
+      let extra -= (brace_counts.c_close - (prev_text =~ '^\s*}')) * s:ShiftWidth()
+    endif
+  endif
+
+  " if no extra indent yet...
+  if extra == 0
+    if brace_counts.p_open > brace_counts.p_close
+      " previous line has more ( than ): add a shiftwidth
+      let extra = s:ShiftWidth()
+    elseif brace_counts.p_open < brace_counts.p_close
+      " previous line has more ) than (: subtract a shiftwidth
+      let extra = -s:ShiftWidth()
+    endif
+  endif
+
+  return indent(align_lnum) + extra
+endfunc "}}}
+
+" Inside <style>: Whether a line is unfinished.
+func! s:CssUnfinished(text)
+  "{{{
+  return a:text =~ '\s\(||\|&&\|:\)\s*$'
+endfunc "}}}
+
+" Search back for the first unfinished line above "lnum".
+func! s:CssFirstUnfinished(lnum, min_lnum)
+  "{{{
+  let align_lnum = a:lnum
+  while align_lnum > a:min_lnum && s:CssUnfinished(getline(align_lnum - 1))
+    let align_lnum -= 1
+  endwhile
+  return align_lnum
+endfunc "}}}
+
+" Find the non-empty line at or before "lnum" that is not a comment.
+func! s:CssPrevNonComment(lnum, stopline)
+  "{{{
+  " caller starts from a line a:lnum + 1 that is not a comment
+  let lnum = prevnonblank(a:lnum)
+  while 1
+    let ccol = match(getline(lnum), '\*/')
+    if ccol < 0
+      " No comment end thus its something else.
+      return lnum
+    endif
+    call cursor(lnum, ccol + 1)
+    " Search back for the /* that starts the comment
+    let lnum = search('/\*', 'bW', a:stopline)
+    if indent(".") == virtcol(".") - 1
+      " The  found /* is at the start of the line. Now go back to the line
+      " above it and again check if it is a comment.
+      let lnum = prevnonblank(lnum - 1)
+    else
+      " /* is after something else, thus it's not a comment line.
+      return lnum
+    endif
+  endwhile
+endfunc "}}}
+
+" Check the number of {} and () in line "lnum". Return a dict with the counts.
+func! HtmlIndent_CountBraces(lnum)
+  "{{{
+  let brs = substitute(getline(a:lnum), '[''"].\{-}[''"]\|/\*.\{-}\*/\|/\*.*$\|[^{}()]', '', 'g')
+  let c_open = 0
+  let c_close = 0
+  let p_open = 0
+  let p_close = 0
+  for brace in split(brs, '\zs')
+    if brace == "{"
+      let c_open += 1
+    elseif brace == "}"
+      if c_open > 0
+        let c_open -= 1
+      else
+        let c_close += 1
+      endif
+    elseif brace == '('
+      let p_open += 1
+    elseif brace == ')'
+      if p_open > 0
+        let p_open -= 1
+      else
+        let p_close += 1
+      endif
+    endif
+  endfor
+  return {'c_open': c_open,
+        \ 'c_close': c_close,
+        \ 'p_open': p_open,
+        \ 'p_close': p_close}
+endfunc "}}}
+
+" Return the indent for a comment: <!-- -->
+func! s:Alien5()
+  "{{{
+  let curtext = getline(v:lnum)
+  if curtext =~ '^\s*\zs-->'
+    " current line starts with end of comment, line up with comment start.
+    call cursor(v:lnum, 0)
+    let lnum = search('<!--', 'b')
+    if lnum > 0
+      " TODO: what if <!-- is not at the start of the line?
+      return indent(lnum)
     endif
 
-    " else within usual html
-    let s:altline = tolower(getline(state.lnum))
+    " Strange, can't find it.
+    return -1
+  endif
 
-    " check a:lnum-1 for closing comment (we need indent from the opening line)
-    let comcol = stridx(s:altline, '-->')
-    if comcol >= 0
-	call cursor(state.lnum, comcol+1)
-	let [comline, comcol] = searchpos('<!--', 'bW')
-	if comline == state.lnum
-	    let s:altline = s:altline[: comcol-2]
-	else
-	    let s:altline = tolower(getline(comline)[: comcol-2])
-	endif
-	call s:CountITags(1)
-	let state.baseindent = indent(comline) + (s:nextrel+s:curline) * s:ShiftWidth()
-	return state
-	" TODO check tags that follow "-->"
-    endif
+  let prevlnum = prevnonblank(v:lnum - 1)
+  let prevtext = getline(prevlnum)
+  let idx = match(prevtext, '^\s*\zs<!--')
+  if idx >= 0
+    " just below comment start, add a shiftwidth
+    return idx + s:ShiftWidth()
+  endif
 
-    " Check if the previous line starts with end tag.
-    let swendtag = match(s:altline, '^\s*</') >= 0
+  " Some files add 4 spaces just below a TODO line.  It's difficult to detect
+  " the end of the TODO, so let's not do that.
+
+  " Align with the previous non-blank line.
+  return indent(prevlnum)
+endfunc "}}}
 
-    " If previous line ended in a closing tag, line up with the opening tag.
-    " Avoids aligning with continuation lines.
-    " TODO: this assumes the start tag is at the start of a line.
-    if !swendtag && s:altline =~ '</\w\+\s*>\s*$'
-	call cursor(state.lnum, 99999)
-        normal F<h
-        " TODO: doesn't work for nested <br> and the like
-        let slnum = searchpair('<\w\+', '', '</\w', 'bW')
-        if slnum > 0
-          let state.baseindent = indent(slnum)
-          return state
-        endif
+" When the "lnum" line ends in ">" find the line containing the matching "<".
+func! HtmlIndent_FindTagStart(lnum)
+  "{{{
+  " Avoids using the indent of a continuation line.
+  " Moves the cursor.
+  " Return two values:
+  " - the matching line number or "lnum".
+  " - a flag indicating whether we found the end of a tag.
+  " This method is global so that HTML-like indenters can use it.
+  " To avoid matching " > " or " < " inside a string require that the opening
+  " "<" is followed by a word character and the closing ">" comes after a
+  " non-white character.
+  let idx = match(getline(a:lnum), '\S>\s*$')
+  if idx > 0
+    call cursor(a:lnum, idx)
+    let lnum = searchpair('<\w', '' , '\S>', 'bW', '', max([a:lnum - b:html_indent_line_limit, 0]))
+    if lnum > 0
+      return [lnum, 1]
     endif
+  endif
+  return [a:lnum, 0]
+endfunc "}}}
 
-    " else no comments
-    let state.lnum = s:FindTagStart(state.lnum)
-    let s:altline = tolower(getline(state.lnum))
-    call s:CountITags(1)
-    let state.baseindent = indent(state.lnum) + s:nextrel * s:ShiftWidth()
-    if !swendtag
-	let state.baseindent += s:curind * s:ShiftWidth()
-    endif
-    return state
+" Find the unclosed start tag from the current cursor position.
+func! HtmlIndent_FindStartTag()
+  "{{{
+  " The cursor must be on or before a closing tag.
+  " If found, positions the cursor at the match and returns the line number.
+  " Otherwise returns 0.
+  let tagname = matchstr(getline('.')[col('.') - 1:], '</\zs\w\+\ze')
+  let start_lnum = searchpair('<' . tagname . '\>', '', '</' . tagname . '\>', 'bW')
+  if start_lnum > 0
+    return start_lnum
+  endif
+  return 0
+endfunc "}}}
+
+" Moves the cursor from a "<" to the matching ">".
+func! HtmlIndent_FindTagEnd()
+  "{{{
+  " Call this with the cursor on the "<" of a start tag.
+  " This will move the cursor to the ">" of the matching end tag or, when it's
+  " a self-closing tag, to the matching ">".
+  " Limited to look up to b:html_indent_line_limit lines away.
+  let text = getline('.')
+  let tagname = matchstr(text, '\w\+\|!--', col('.'))
+  if tagname == '!--'
+    call search('--\zs>')
+  elseif s:get_tag('/' . tagname) != 0
+    " tag with a closing tag, find matching "</tag>"
+    call searchpair('<' . tagname, '', '</' . tagname . '\zs>', 'W', '', line('.') + b:html_indent_line_limit)
+  else
+    " self-closing tag, find the ">"
+    call search('\S\zs>')
+  endif
 endfunc "}}}
 
-func! s:Alien2() "{{{
-    " <pre> block
-    return -1
-endfunc "}}}
-func! s:Alien3() "{{{
-    " <script> javascript
-    if prevnonblank(v:lnum-1) == b:indent.blocklnr
-	" indent for the first line after <script>
-	return eval(s:js1indent)
-    endif
-    if b:indent.scripttype == "javascript"
-	return cindent(v:lnum)
-    else
-	return -1
-    endif
-endfunc "}}}
-func! s:Alien4() "{{{
-    " <style>
-    if prevnonblank(v:lnum-1) == b:indent.blocklnr
-	" indent for first content line
-	return eval(s:css1indent)
-    endif
-    return s:CSSIndent()
-endfunc
-
-func! s:CSSIndent() "{{{
-    " adopted $VIMRUNTIME/indent/css.vim
-    if getline(v:lnum) =~ '^\s*[*}]'
-	return cindent(v:lnum)
-    endif
-    let minline = b:indent.blocklnr
-    let pnum = s:css_prevnoncomment(v:lnum - 1, minline)
-    let pnum = s:FindTagStart(pnum)
-    if pnum <= minline
-	" < is to catch errors
-	" indent for first content line after comments
-	return eval(s:css1indent)
+" Indenting inside a start tag. Return the correct indent or -1 if unknown.
+func! s:InsideTag(foundHtmlString)
+  "{{{
+  if a:foundHtmlString
+    " Inside an attribute string.
+    " Align with the previous line or use an external function.
+    let lnum = v:lnum - 1
+    if lnum > 1
+      if exists('b:html_indent_tag_string_func')
+        return b:html_indent_tag_string_func(lnum)
+      endif
+      return indent(lnum)
     endif
-    let ind = indent(pnum) + s:css_countbraces(pnum, 1) * s:ShiftWidth()
-    let pline = getline(pnum)
-    if pline =~ '}\s*$'
-	let ind -= (s:css_countbraces(pnum, 0) - (pline =~ '^\s*}')) * s:ShiftWidth()
-    endif
-    return ind
-endfunc "}}}
-func! s:css_prevnoncomment(lnum, stopline) "{{{
-    " caller starts from a line a:lnum-1 that is not a comment
-    let lnum = prevnonblank(a:lnum)
-    let ccol = match(getline(lnum), '\*/')
-    if ccol < 0
-	return lnum
-    endif
-    call cursor(lnum, ccol+1)
-    let lnum = search('/\*', 'bW', a:stopline)
-    if indent(".") == virtcol(".")-1
-	return prevnonblank(lnum-1)
+  endif
+
+  " Should be another attribute: " attr="val".  Align with the previous
+  " attribute start.
+  let lnum = v:lnum
+  while lnum > 1
+    let lnum -= 1
+    let text = getline(lnum)
+    " Find a match with one of these, align with "attr":
+    "       attr=
+    "  <tag attr=
+    "  text<tag attr=
+    "  <tag>text</tag>text<tag attr=
+    " For long lines search for the first match, finding the last match
+    " gets very slow.
+    if len(text) < 300
+      let idx = match(text, '.*\s\zs[_a-zA-Z0-9-]\+="')
     else
-	return lnum
+      let idx = match(text, '\s\zs[_a-zA-Z0-9-]\+="')
     endif
-endfunc "}}}
-func! s:css_countbraces(lnum, count_open) "{{{
-    let brs = substitute(getline(a:lnum),'[''"].\{-}[''"]\|/\*.\{-}\*/\|/\*.*$\|[^{}]','','g')
-    let n_open = 0
-    let n_close = 0
-    for brace in split(brs, '\zs')
-	if brace == "{"
-	    let n_open += 1
-	elseif brace == "}"
-	    if n_open > 0
-		let n_open -= 1
-	    else
-		let n_close += 1
-	    endif
-	endif
-    endfor
-    return a:count_open ? n_open : n_close
+    if idx > 0
+      " Found the attribute.  TODO: assumes spaces, no Tabs.
+      return idx
+    endif
+  endwhile
+  return -1
 endfunc "}}}
 
-"}}}
-func! s:Alien5() "{{{
-    " <!-- -->
-    return -1
-endfunc "}}}
+" THE MAIN INDENT FUNCTION. Return the amount of indent for v:lnum.
+func! HtmlIndent()
+  "{{{
+  if prevnonblank(v:lnum - 1) <= 1
+    " First non-blank line has no indent.
+    return 0
+  endif
+
+  let curtext = tolower(getline(v:lnum))
+  let indentunit = s:ShiftWidth()
+
+  let b:hi_newstate = {}
+  let b:hi_newstate.lnum = v:lnum
+
+  " When syntax HL is enabled, detect we are inside a tag.  Indenting inside
+  " a tag works very differently. Do not do this when the line starts with
+  " "<", it gets the "htmlTag" ID but we are not inside a tag then.
+  if curtext !~ '^\s*<'
+    normal ^
+    let stack = synstack(v:lnum, col('.'))  " assumes there are no tabs
+    let foundHtmlString = 0
+    for synid in reverse(stack)
+      let name = synIDattr(synid, "name")
+      if index(b:hi_insideStringNames, name) >= 0
+        let foundHtmlString = 1
+      elseif index(b:hi_insideTagNames, name) >= 0
+        " Yes, we are inside a tag.
+        let indent = s:InsideTag(foundHtmlString)
+        if indent >= 0
+          " Do not keep the state. TODO: could keep the block type.
+          let b:hi_indent.lnum = 0
+          return indent
+        endif
+      endif
+    endfor
+  endif
+
+  " does the line start with a closing tag?
+  let swendtag = match(curtext, '^\s*</') >= 0
+
+  if prevnonblank(v:lnum - 1) == b:hi_indent.lnum && b:hi_lasttick == b:changedtick - 1
+    " use state (continue from previous line)
+  else
+    " start over (know nothing)
+    let b:hi_indent = s:FreshState(v:lnum)
+  endif
 
-" When the "lnum" line ends in ">" find the line containing the
-" matching "<".  Avoids using the indent of a continuation line.
-" Moves the cursor.
-" Return the matching line number or "lnum".
-func! s:FindTagStart(lnum) "{{{
-  if getline(a:lnum) =~ '>\s*$'
-    call cursor(a:lnum, 99999)
-    normal! %
-    return line('.')
+  if b:hi_indent.block >= 2
+    " within block
+    let endtag = s:endtags[b:hi_indent.block]
+    let blockend = stridx(curtext, endtag)
+    if blockend >= 0
+      " block ends here
+      let b:hi_newstate.block = 0
+      " calc indent for REST OF LINE (may start more blocks):
+      call s:CountTagsAndState(strpart(curtext, blockend + strlen(endtag)))
+      if swendtag && b:hi_indent.block != 5
+        let indent = b:hi_indent.blocktagind + s:curind * indentunit
+        let b:hi_newstate.baseindent = indent + s:nextrel * indentunit
+      else
+        let indent = s:Alien{b:hi_indent.block}()
+        let b:hi_newstate.baseindent = b:hi_indent.blocktagind + s:nextrel * indentunit
+      endif
+    else
+      " block continues
+      " indent this line with alien method
+      let indent = s:Alien{b:hi_indent.block}()
+    endif
+  else
+    " not within a block - within usual html
+    let b:hi_newstate.block = b:hi_indent.block
+    if swendtag
+      " The current line starts with an end tag, align with its start tag.
+      call cursor(v:lnum, 1)
+      let start_lnum = HtmlIndent_FindStartTag()
+      if start_lnum > 0
+        " check for the line starting with something inside a tag:
+        " <sometag               <- align here
+        "    attr=val><open>     not here
+        let text = getline(start_lnum)
+        let angle = matchstr(text, '[<>]')
+        if angle == '>'
+          call cursor(start_lnum, 1)
+          normal! f>%
+          let start_lnum = line('.')
+          let text = getline(start_lnum)
+        endif
+
+        let indent = indent(start_lnum)
+        if col('.') > 2
+          let swendtag = match(text, '^\s*</') >= 0
+          call s:CountITags(text[: col('.') - 2])
+          let indent += s:nextrel * s:ShiftWidth()
+          if !swendtag
+            let indent += s:curind * s:ShiftWidth()
+          endif
+        endif
+      else
+        " not sure what to do
+        let indent = b:hi_indent.baseindent
+      endif
+      let b:hi_newstate.baseindent = indent
+    else
+      call s:CountTagsAndState(curtext)
+      let indent = b:hi_indent.baseindent
+      let b:hi_newstate.baseindent = indent + (s:curind + s:nextrel) * indentunit
+    endif
   endif
-  return a:lnum
+
+  let b:hi_lasttick = b:changedtick
+  call extend(b:hi_indent, b:hi_newstate, "force")
+  return indent
 endfunc "}}}
 
-func! HtmlIndent() "{{{
-    let s:curline = tolower(getline(v:lnum))
-    let indentunit = s:ShiftWidth()
-
-    let s:newstate = {}
-    let s:newstate.lnum = v:lnum
-
-    " does the line start with a closing tag?
-    let swendtag = match(s:curline, '^\s*</') >= 0
-
-    if prevnonblank(v:lnum-1) == b:indent.lnum && s:lasttick == b:changedtick - 1
-	" use state (continue from previous line)
-    else
-	" start over (know nothing)
-	let b:indent = s:FreshState(v:lnum)
-    endif
-
-    if b:indent.block >= 2
-	" within block
-	let endtag = s:endtags[b:indent.block-2]
-	let blockend = stridx(s:curline, endtag)
-	if blockend >= 0
-	    " block ends here
-	    let s:newstate.block = 0
-	    " calc indent for REST OF LINE (may start more blocks):
-	    let s:curline = strpart(s:curline, blockend+strlen(endtag))
-	    call s:CountITags()
-	    if swendtag && b:indent.block != 5
-		let indent = b:indent.blocktagind + s:curind * indentunit
-		let s:newstate.baseindent = indent + s:nextrel * indentunit
-	    else
-		let indent = s:Alien{b:indent.block}()
-		let s:newstate.baseindent = b:indent.blocktagind + s:nextrel * indentunit
-	    endif
-	else
-	    " block continues
-	    " indent this line with alien method
-	    let indent = s:Alien{b:indent.block}()
-	endif
-    else
-	" not within a block - within usual html
-	" if < 2 then always 0
-	let s:newstate.block = b:indent.block
-	call s:CountITags()
-	if swendtag
-	    let indent = b:indent.baseindent + s:curind * indentunit
-	    let s:newstate.baseindent = indent + s:nextrel * indentunit
-	else
-	    let indent = b:indent.baseindent
-	    let s:newstate.baseindent = indent + (s:curind + s:nextrel) * indentunit
-	endif
-    endif
-    let s:lasttick = b:changedtick
-    call extend(b:indent, s:newstate, "force")
-    return indent
-
-endfunc "}}}
-
-" check user settings (first time), clear cpo, Modeline: {{{1
-
-" DEBUG:
-com! -nargs=* IndHtmlLocal <args>
-
+" Check user settings when loading this script the first time.
 call HtmlIndent_CheckUserSettings()
 
 let &cpo = s:cpo_save
 unlet s:cpo_save
 
-" vim:set fdm=marker ts=8:
+" vim: fdm=marker ts=8 sw=2 tw=78