Update runtime files.
diff --git a/runtime/autoload/dist/vimindent.vim b/runtime/autoload/dist/vimindent.vim
index 898f22f..8d86543 100644
--- a/runtime/autoload/dist/vimindent.vim
+++ b/runtime/autoload/dist/vimindent.vim
@@ -2,12 +2,12 @@
 
 # Language:     Vim script
 # Maintainer:   github user lacygoill
-# Last Change:  2023 Jan 03
+# Last Change:  2023 Feb 01
 
 # NOTE: Whenever you change the code, make sure the tests are still passing:
 #
 #     $ cd runtime/indent/
-#     $ make clean; make test || vimdiff testdir/vim.{fail,ok}
+#     $ make clean; make test || vimdiff testdir/vim.{ok,fail}
 
 # Config {{{1
 
@@ -112,6 +112,10 @@
     .. '\)'
     .. ':\%(\s\|$\)'
 
+# NOT_A_DICT_KEY {{{3
+
+const NOT_A_DICT_KEY: string = ':\@!'
+
 # END_OF_COMMAND {{{3
 
 const END_OF_COMMAND: string = $'\s*\%($\|||\@!\|{INLINE_COMMENT}\)'
@@ -144,19 +148,43 @@
 #
 # But sometimes, it can be too costly and cause `E363` to be given.
 const PATTERN_DELIMITER: string = '[-+*/%]\%(=\s\)\@!'
-
-# QUOTE {{{3
-
-const QUOTE: string = '["'']'
 # }}}2
 # Syntaxes {{{2
-# ASSIGNS_HEREDOC {{{3
+# BLOCKS {{{3
 
-const ASSIGNS_HEREDOC: string = $'^\%({COMMENT}\)\@!.*\%({HEREDOC_OPERATOR}\)\s\+\zs[A-Z]\+{END_OF_LINE}'
+const BLOCKS: list<list<string>> = [
+    ['if', 'el\%[se]', 'elseif\=', 'en\%[dif]'],
+    ['for', 'endfor\='],
+    ['wh\%[ile]', 'endw\%[hile]'],
+    ['try', 'cat\%[ch]', 'fina\|finally\=', 'endt\%[ry]'],
+    ['def', 'enddef'],
+    ['fu\%[nction](\@!', 'endf\%[unction]'],
+    ['class', 'endclass'],
+    ['interface', 'endinterface'],
+    ['enum', 'endenum'],
+    ['aug\%[roup]\%(\s\+[eE][nN][dD]\)\@!\s\+\S\+', 'aug\%[roup]\s\+[eE][nN][dD]'],
+]
 
-# CD_COMMAND {{{3
+# MODIFIERS {{{3
 
-const CD_COMMAND: string = $'\<[lt]\=cd!\=\s\+-{END_OF_COMMAND}'
+# some keywords can be prefixed by modifiers (e.g. `def` can be prefixed by `export`)
+const MODIFIERS: dict<string> = {
+    def: ['export', 'static'],
+    class: ['export', 'abstract', 'export abstract'],
+    interface: ['export'],
+}
+#     ...
+#     class: ['export', 'abstract', 'export abstract'],
+#     ...
+#     →
+#     ...
+#     class: '\%(export\|abstract\|export\s\+abstract\)\s\+',
+#     ...
+->map((_, mods: list<string>): string =>
+    '\%(' .. mods
+    ->join('\|')
+    ->substitute('\s\+', '\\s\\+', 'g')
+    .. '\)' .. '\s\+')
 
 # HIGHER_ORDER_COMMAND {{{3
 
@@ -174,58 +202,102 @@
     g\%[lobal]!\={PATTERN_DELIMITER}.*
     v\%[global]!\={PATTERN_DELIMITER}.*
 END
-const HIGHER_ORDER_COMMAND: string = $'\%(^\|{BAR_SEPARATION}\)\s*\<\%(' .. patterns->join('\|') .. '\):\@!'
 
-# MAPPING_COMMAND {{{3
+const HIGHER_ORDER_COMMAND: string = $'\%(^\|{BAR_SEPARATION}\)\s*\<\%({patterns->join('\|')}\){NOT_A_DICT_KEY}'
 
-const MAPPING_COMMAND: string = '\%(\<sil\%[ent]!\=\s\+\)\=\<[nvxsoilct]\=\%(nore\|un\)map!\=\s'
+# START_MIDDLE_END {{{3
 
-# NORMAL_COMMAND {{{3
+# Let's derive this constant from `BLOCKS`:
+#
+#     [['if', 'el\%[se]', 'elseif\=', 'en\%[dif]'],
+#      ['for', 'endfor\='],
+#      ...,
+#      [...]]
+#     →
+#     {
+#      'for': ['for', '', 'endfor\='],
+#      'endfor': ['for', '', 'endfor\='],
+#      'if': ['if', 'el\%[se]\|elseif\=', 'en\%[dif]'],
+#      'else': ['if', 'el\%[se]\|elseif\=', 'en\%[dif]'],
+#      'elseif': ['if', 'el\%[se]\|elseif\=', 'en\%[dif]'],
+#      'endif': ['if', 'el\%[se]\|elseif\=', 'en\%[dif]'],
+#      ...
+#     }
+var START_MIDDLE_END: dict<list<string>>
 
-const NORMAL_COMMAND: string = '\<norm\%[al]!\=\s*\S\+$'
+def Unshorten(kwd: string): string
+    return BlockStartKeyword(kwd)
+enddef
 
-# PLUS_MINUS_COMMAND {{{3
+def BlockStartKeyword(line: string): string
+    var kwd: string = line->matchstr('\l\+')
+    return fullcommand(kwd, false)
+enddef
 
-# In legacy, the `:+` and `:-` commands are not required to be preceded by a colon.
-# As a result, when `+` or `-` is alone on a line, there is ambiguity.
-# It might be an operator or a command.
-# To not break the indentation in legacy scripts, we might need to consider such
-# lines as commands.
-const PLUS_MINUS_COMMAND: string = '^\s*[+-]\s*$'
+{
+    for kwds: list<string> in BLOCKS
+        var [start: string, middle: string, end: string] = [kwds[0], '', kwds[-1]]
+        if MODIFIERS->has_key(start->Unshorten())
+            start = $'\%({MODIFIERS[start]}\)\={start}'
+        endif
+        if kwds->len() > 2
+            middle = kwds[1 : -2]->join('\|')
+        endif
+        for kwd: string in kwds
+            START_MIDDLE_END->extend({[kwd->Unshorten()]: [start, middle, end]})
+        endfor
+    endfor
+}
+
+START_MIDDLE_END = START_MIDDLE_END
+    ->map((_, kwds: list<string>) =>
+        kwds->map((_, kwd: string) => kwd == ''
+        ? ''
+        : $'\%(^\|{BAR_SEPARATION}\|\<sil\%[ent]\|{HIGHER_ORDER_COMMAND}\)\s*'
+        .. $'\<\%({kwd}\)\>\%(\s*{OPERATOR}\)\@!'))
+
+lockvar! START_MIDDLE_END
 
 # ENDS_BLOCK {{{3
 
 const ENDS_BLOCK: string = '^\s*\%('
-    .. 'en\%[dif]'
-    .. '\|' .. 'endfor\='
-    .. '\|' .. 'endw\%[hile]'
-    .. '\|' .. 'endt\%[ry]'
-    .. '\|' .. 'enddef'
-    .. '\|' .. 'endclass'
-    .. '\|' .. 'endf\%[unction]'
-    .. '\|' .. 'aug\%[roup]\s\+[eE][nN][dD]'
+    .. BLOCKS
+    ->copy()
+    ->map((_, kwds: list<string>): string => kwds[-1])
+    ->join('\|')
     .. '\|' .. CLOSING_BRACKET
     .. $'\){END_OF_COMMAND}'
 
 # ENDS_BLOCK_OR_CLAUSE {{{3
 
-patterns =<< trim END
-    en\%[dif]
-    el\%[se]
-    endfor\=
-    endclass
-    endw\%[hile]
-    endt\%[ry]
-    fina\|finally\=
-    enddef
-    endf\%[unction]
-    aug\%[roup]\s\+[eE][nN][dD]
-END
+patterns = BLOCKS
+    ->copy()
+    ->map((_, kwds: list<string>) => kwds[1 :])
+    ->flattennew()
+    # `catch` and `elseif` need to be handled as special cases
+    ->filter((_, pat: string): bool => pat->Unshorten() !~ '^\%(catch\|elseif\)\>')
 
 const ENDS_BLOCK_OR_CLAUSE: string = '^\s*\%(' .. patterns->join('\|') .. $'\){END_OF_COMMAND}'
     .. $'\|^\s*cat\%[ch]\%(\s\+\({PATTERN_DELIMITER}\).*\1\)\={END_OF_COMMAND}'
     .. $'\|^\s*elseif\=\>\%({OPERATOR}\)\@!'
 
+# STARTS_NAMED_BLOCK {{{3
+
+patterns = []
+{
+    for kwds: list<string> in BLOCKS
+        for kwd: string in kwds[0 : -2]
+            if MODIFIERS->has_key(kwd->Unshorten())
+                patterns += [$'\%({MODIFIERS[kwd]}\)\={kwd}']
+            else
+                patterns += [kwd]
+            endif
+        endfor
+    endfor
+}
+
+const STARTS_NAMED_BLOCK: string = $'^\s*\%(sil\%[ent]\s\+\)\=\%({patterns->join('\|')}\)\>{NOT_A_DICT_KEY}'
+
 # STARTS_CURLY_BLOCK {{{3
 
 # TODO: `{` alone on a line is not necessarily the start of a block.
@@ -238,73 +310,57 @@
     .. '\|' ..  $'^\%(\s*\|.*{BAR_SEPARATION}\s*\)\%(com\%[mand]\|au\%[tocmd]\).*\zs\s{{'
     .. '\)' .. END_OF_COMMAND
 
-# STARTS_NAMED_BLOCK {{{3
-
-# All of these will be used at the start of a line (or after a bar).
-# NOTE: Don't replace `\%x28` with `(`.{{{
-#
-# Otherwise, the paren would be unbalanced which might cause syntax highlighting
-# issues much  later in the  code of the  current script (sometimes,  the syntax
-# highlighting plugin fails  to correctly recognize a heredoc which  is far away
-# and/or not displayed because inside a fold).
-# }}}
-patterns =<< trim END
-    if
-    el\%[se]
-    elseif\=
-    for
-    class
-    wh\%[ile]
-    try
-    cat\%[ch]
-    fina\|finally\=
-    fu\%[nction]\%x28\@!
-    \%(export\s\+\)\=def
-    aug\%[roup]\%(\s\+[eE][nN][dD]\)\@!\s\+\S\+
-END
-const STARTS_NAMED_BLOCK: string = '^\s*\%(sil\%[ent]\s\+\)\=\%(' .. patterns->join('\|') .. '\)\>:\@!'
-
 # STARTS_FUNCTION {{{3
 
-const STARTS_FUNCTION: string = '^\s*\%(export\s\+\)\=def\>:\@!'
+const STARTS_FUNCTION: string = $'^\s*\%({MODIFIERS.def}\)\=def\>{NOT_A_DICT_KEY}'
 
 # ENDS_FUNCTION {{{3
 
-const ENDS_FUNCTION: string = $'^\s*enddef\>:\@!{END_OF_COMMAND}'
+const ENDS_FUNCTION: string = $'^\s*enddef\>{END_OF_COMMAND}'
 
-# START_MIDDLE_END {{{3
+# ASSIGNS_HEREDOC {{{3
 
-const START_MIDDLE_END: dict<list<string>> = {
-    if: ['if', 'el\%[se]\|elseif\=', 'en\%[dif]'],
-    else: ['if', 'el\%[se]\|elseif\=', 'en\%[dif]'],
-    elseif: ['if', 'el\%[se]\|elseif\=', 'en\%[dif]'],
-    endif: ['if', 'el\%[se]\|elseif\=', 'en\%[dif]'],
-    for: ['for', '', 'endfor\='],
-    endfor: ['for', '', 'endfor\='],
-    class: ['class', '', 'endclass'],
-    endclass: ['class', '', 'endclass'],
-    while: ['wh\%[ile]', '', 'endw\%[hile]'],
-    endwhile: ['wh\%[ile]', '', 'endw\%[hile]'],
-    try: ['try', 'cat\%[ch]\|fina\|finally\=', 'endt\%[ry]'],
-    catch: ['try', 'cat\%[ch]\|fina\|finally\=', 'endt\%[ry]'],
-    finally: ['try', 'cat\%[ch]\|fina\|finally\=', 'endt\%[ry]'],
-    endtry: ['try', 'cat\%[ch]\|fina\|finally\=', 'endt\%[ry]'],
-    def: ['\%(export\s\+\)\=def', '', 'enddef'],
-    enddef: ['\%(export\s\+\)\=def', '', 'enddef'],
-    function: ['fu\%[nction]', '', 'endf\%[unction]'],
-    endfunction: ['fu\%[nction]', '', 'endf\%[unction]'],
-    augroup: ['aug\%[roup]\%(\s\+[eE][nN][dD]\)\@!\s\+\S\+', '', 'aug\%[roup]\s\+[eE][nN][dD]'],
-}->map((_, kwds: list<string>) =>
-    kwds->map((_, kwd: string) => kwd == ''
-    ? ''
-    : $'\%(^\|{BAR_SEPARATION}\|\<sil\%[ent]\|{HIGHER_ORDER_COMMAND}\)\s*'
-    .. $'\%({printf('\C\<\%%(%s\)\>:\@!\%%(\s*%s\)\@!', kwd, OPERATOR)}\)'))
+const ASSIGNS_HEREDOC: string = $'^\%({COMMENT}\)\@!.*\%({HEREDOC_OPERATOR}\)\s\+\zs[A-Z]\+{END_OF_LINE}'
+
+# PLUS_MINUS_COMMAND {{{3
+
+# In legacy, the `:+` and `:-` commands are not required to be preceded by a colon.
+# As a result, when `+` or `-` is alone on a line, there is ambiguity.
+# It might be an operator or a command.
+# To not break the indentation in legacy scripts, we might need to consider such
+# lines as commands.
+const PLUS_MINUS_COMMAND: string = '^\s*[+-]\s*$'
+
+# TRICKY_COMMANDS {{{3
+
+# Some  commands  are tricky  because  they  accept  an  argument which  can  be
+# conflated with an operator.  Examples:
+#
+#     argdelete *
+#     cd -
+#     normal! ==
+#     nunmap <buffer> (
+#
+# TODO: Other commands might accept operators as argument.  Handle them too.
+patterns =<< trim eval END
+    {'\'}<argd\%[elete]\s\+\*\s*$
+    \<[lt]\=cd!\=\s\+-\s*$
+    \<norm\%[al]!\=\s*\S\+$
+    \%(\<sil\%[ent]!\=\s\+\)\=\<[nvxsoilct]\=\%(nore\|un\)map!\=\s
+    {PLUS_MINUS_COMMAND}
+END
+
+const TRICKY_COMMANDS: string = patterns->join('\|')
 # }}}2
 # EOL {{{2
 # OPENING_BRACKET_AT_EOL {{{3
 
 const OPENING_BRACKET_AT_EOL: string = OPENING_BRACKET .. END_OF_VIM9_LINE
 
+# CLOSING_BRACKET_AT_EOL {{{3
+
+const CLOSING_BRACKET_AT_EOL: string = CLOSING_BRACKET .. END_OF_VIM9_LINE
+
 # COMMA_AT_EOL {{{3
 
 const COMMA_AT_EOL: string = $',{END_OF_VIM9_LINE}'
@@ -392,6 +448,7 @@
     endif
 
     if line_A->AtStartOf('FuncHeader')
+            && !IsInInterface()
         line_A.lnum->CacheFuncHeader()
     elseif line_A.lnum->IsInside('FuncHeader')
         return b:vimindent.startindent + 2 * shiftwidth()
@@ -430,6 +487,7 @@
     if line_A.text->ContinuesBelowBracketBlock(line_B, past_bracket_block)
             && line_A.text !~ CLOSING_BRACKET_AT_SOL
         return past_bracket_block.startindent
+            + (past_bracket_block.startline =~ STARTS_NAMED_BLOCK ? 2 * shiftwidth() : 0)
     endif
 
     # Problem: If we press `==` on the line right below the start of a multiline
@@ -438,6 +496,18 @@
     if line_B->EndsWithLambdaArrow()
         return Indent(line_B.lnum) + shiftwidth() + IndentMoreInBracketBlock()
     endif
+    # FIXME: Similar issue here:
+    #
+    #     var x = []
+    #         ->filter((_, _) =>
+    #             true)
+    #         ->items()
+    #
+    # Press `==` on last line.
+    # Expected: The `->items()` line is indented like `->filter(...)`.
+    # Actual: It's indented like `true)`.
+    # Is it worth fixing? `=ip` gives  the correct indentation, because then the
+    # cache is used.
 
     # Don't move this block before the heredoc one.{{{
     #
@@ -536,8 +606,13 @@
         line_B: dict<any>,
         ): number
 
+    if line_B->AtStartOf('FuncHeader')
+            && IsInInterface()
+        return 0
+
     # increase indentation inside a block
-    if line_B.text =~ STARTS_NAMED_BLOCK || line_B->EndsWithCurlyBlock()
+    elseif line_B.text =~ STARTS_NAMED_BLOCK
+            || line_B->EndsWithCurlyBlock()
         # But don't indent if the line starting the block also closes it.
         if line_B->AlsoClosesBlock()
             return 0
@@ -807,11 +882,6 @@
     return indent(lnum)
 enddef
 
-def BlockStartKeyword(line: string): string # {{{3
-    var kwd: string = line->matchstr('\l\+')
-    return fullcommand(kwd, false)
-enddef
-
 def MatchingOpenBracket(line: dict<any>): number # {{{3
     var end: string = line.text->matchstr(CLOSING_BRACKET)
     var start: string = {']': '[', '}': '{', ')': '('}[end]
@@ -908,7 +978,8 @@
     if end == '[' || end == ']'
         e = e->escape('[]')
     endif
-    return searchpair(s, middle, e, flags, (): bool => InCommentOrString(), stopline, TIMEOUT)
+    return searchpair('\C' .. s, (middle == '' ? '' : '\C' .. middle), '\C' .. e,
+        flags, (): bool => InCommentOrString(), stopline, TIMEOUT)
 enddef
 
 def SearchPairStart( # {{{3
@@ -1016,6 +1087,10 @@
     return line_A.lnum <= end
 enddef
 
+def IsInInterface(): bool # {{{3
+    return SearchPair('interface', '', 'endinterface', 'nW') > 0
+enddef
+
 def IsFirstLineOfCommand(line_1: dict<any>, line_2: dict<any>): bool # {{{3
     if line_1.text->Is_IN_KeywordForLoop(line_2.text)
         return false
@@ -1096,6 +1171,10 @@
     return NonCommentedMatch(line, OPENING_BRACKET_AT_EOL)
 enddef
 
+def EndsWithClosingBracket(line: dict<any>): bool # {{{3
+    return NonCommentedMatch(line, CLOSING_BRACKET_AT_EOL)
+enddef
+
 def NonCommentedMatch(line: dict<any>, pat: string): bool # {{{3
     # Could happen if there is no code above us, and we're not on the 1st line.
     # In that case, `PrevCodeLine()` returns `{lnum: 0, line: ''}`.
@@ -1103,16 +1182,6 @@
         return false
     endif
 
-    if line.text =~ PLUS_MINUS_COMMAND
-        return false
-    endif
-
-    # In `argdelete *`, `*` is not a multiplication operator.
-    # TODO: Other commands can accept `*` as an argument.  Handle them too.
-    if line.text =~ '\<argd\%[elete]\s\+\*\s*$'
-        return false
-    endif
-
     # Technically, that's wrong.  A  line might start with a range  and end with a
     # line continuation symbol.  But it's unlikely.  And it's useful to assume the
     # opposite because it  prevents us from conflating a mark  with an operator or
@@ -1179,24 +1248,7 @@
         return false
     endif
 
-    # `:help cd-`
-    if line.text =~ CD_COMMAND
-        return false
-    endif
-
-    # At the end of a mapping, any character might appear; e.g. a paren:
-    #
-    #     nunmap <buffer> (
-    #
-    # Don't conflate this with a line continuation symbol.
-    if line.text =~ MAPPING_COMMAND
-        return false
-    endif
-
-    #             not a comparison operator
-    #             vv
-    #     normal! ==
-    if line.text =~ NORMAL_COMMAND
+    if line.text =~ TRICKY_COMMANDS
         return false
     endif
 
diff --git a/runtime/autoload/tohtml.vim b/runtime/autoload/tohtml.vim
index 270891a..1086347 100644
--- a/runtime/autoload/tohtml.vim
+++ b/runtime/autoload/tohtml.vim
@@ -1,6 +1,6 @@
 " Vim autoload file for the tohtml plugin.
 " Maintainer: Ben Fritz <fritzophrenic@gmail.com>
-" Last Change: 2019 Aug 16
+" Last Change: 2023 Jan 01
 "
 " Additional contributors:
 "
@@ -351,63 +351,65 @@
   let s:old_magic = &magic
   set magic
 
-  if s:settings.use_xhtml
-    if s:settings.encoding != ""
-      let xml_line = "<?xml version=\"1.0\" encoding=\"" . s:settings.encoding . "\"?>"
-    else
-      let xml_line = "<?xml version=\"1.0\"?>"
-    endif
-    let tag_close = ' />'
-  endif
-
-  let style = [s:settings.use_xhtml ? "" : '-->']
-  let body_line = ''
-
   let html = []
-  let s:html5 = 0
-  if s:settings.use_xhtml
-    call add(html, xml_line)
-  endif
-  if s:settings.use_xhtml
-    call add(html, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">")
-    call add(html, '<html xmlns="http://www.w3.org/1999/xhtml">')
-  elseif s:settings.use_css && !s:settings.no_pre
-    call add(html, "<!DOCTYPE html>")
-    call add(html, '<html>')
-    let s:html5 = 1
-  else
-    call add(html, '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"')
-    call add(html, '  "http://www.w3.org/TR/html4/loose.dtd">')
-    call add(html, '<html>')
-  endif
-  call add(html, '<head>')
-
-  " include encoding as close to the top as possible, but only if not already
-  " contained in XML information
-  if s:settings.encoding != "" && !s:settings.use_xhtml
-    if s:html5
-      call add(html, '<meta charset="' . s:settings.encoding . '"' . tag_close)
-    else
-      call add(html, "<meta http-equiv=\"content-type\" content=\"text/html; charset=" . s:settings.encoding . '"' . tag_close)
+  if !s:settings.no_doc
+    if s:settings.use_xhtml
+      if s:settings.encoding != ""
+	let xml_line = "<?xml version=\"1.0\" encoding=\"" . s:settings.encoding . "\"?>"
+      else
+	let xml_line = "<?xml version=\"1.0\"?>"
+      endif
+      let tag_close = ' />'
     endif
+
+    let style = [s:settings.use_xhtml ? "" : '-->']
+    let body_line = ''
+
+    let s:html5 = 0
+    if s:settings.use_xhtml
+      call add(html, xml_line)
+    endif
+    if s:settings.use_xhtml
+      call add(html, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">")
+      call add(html, '<html xmlns="http://www.w3.org/1999/xhtml">')
+    elseif s:settings.use_css && !s:settings.no_pre
+      call add(html, "<!DOCTYPE html>")
+      call add(html, '<html>')
+      let s:html5 = 1
+    else
+      call add(html, '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"')
+      call add(html, '  "http://www.w3.org/TR/html4/loose.dtd">')
+      call add(html, '<html>')
+    endif
+    call add(html, '<head>')
+
+    " include encoding as close to the top as possible, but only if not already
+    " contained in XML information
+    if s:settings.encoding != "" && !s:settings.use_xhtml
+      if s:html5
+	call add(html, '<meta charset="' . s:settings.encoding . '"' . tag_close)
+      else
+	call add(html, "<meta http-equiv=\"content-type\" content=\"text/html; charset=" . s:settings.encoding . '"' . tag_close)
+      endif
+    endif
+
+    call add(html, '<title>diff</title>')
+    call add(html, '<meta name="Generator" content="Vim/'.v:version/100.'.'.v:version%100.'"'.tag_close)
+    call add(html, '<meta name="plugin-version" content="'.g:loaded_2html_plugin.'"'.tag_close)
+    call add(html, '<meta name="settings" content="'.
+	  \ join(filter(keys(s:settings),'s:settings[v:val]'),',').
+	  \ ',prevent_copy='.s:settings.prevent_copy.
+	  \ ',use_input_for_pc='.s:settings.use_input_for_pc.
+	  \ '"'.tag_close)
+    call add(html, '<meta name="colorscheme" content="'.
+	  \ (exists('g:colors_name')
+	  \ ? g:colors_name
+	  \ : 'none'). '"'.tag_close)
+
+    call add(html, '</head>')
+    let body_line_num = len(html)
+    call add(html, '<body'.(s:settings.line_ids ? ' onload="JumpToLine();"' : '').'>')
   endif
-
-  call add(html, '<title>diff</title>')
-  call add(html, '<meta name="Generator" content="Vim/'.v:version/100.'.'.v:version%100.'"'.tag_close)
-  call add(html, '<meta name="plugin-version" content="'.g:loaded_2html_plugin.'"'.tag_close)
-  call add(html, '<meta name="settings" content="'.
-	\ join(filter(keys(s:settings),'s:settings[v:val]'),',').
-	\ ',prevent_copy='.s:settings.prevent_copy.
-	\ ',use_input_for_pc='.s:settings.use_input_for_pc.
-	\ '"'.tag_close)
-  call add(html, '<meta name="colorscheme" content="'.
-	\ (exists('g:colors_name')
-	\ ? g:colors_name
-	\ : 'none'). '"'.tag_close)
-
-  call add(html, '</head>')
-  let body_line_num = len(html)
-  call add(html, '<body'.(s:settings.line_ids ? ' onload="JumpToLine();"' : '').'>')
   call add(html, "<table ".(s:settings.use_css? "" : "border='1' width='100%' ")."id='vimCodeElement".s:settings.id_suffix."'>")
 
   call add(html, '<tr>')
@@ -430,47 +432,53 @@
     " When not using CSS or when using xhtml, the <body> line can be important.
     " Assume it will be the same for all buffers and grab it from the first
     " buffer. Similarly, need to grab the body end line as well.
-    if body_line == ''
+    if !s:settings.no_doc
+      if body_line == ''
+	1
+	call search('<body')
+	let body_line = getline('.')
+	$
+	call search('</body>', 'b')
+	let s:body_end_line = getline('.')
+      endif
+
+      " Grab the style information. Some of this will be duplicated so only insert
+      " it if it's not already there. {{{
       1
-      call search('<body')
-      let body_line = getline('.')
-      $
-      call search('</body>', 'b')
-      let s:body_end_line = getline('.')
-    endif
-
-    " Grab the style information. Some of this will be duplicated so only insert
-    " it if it's not already there. {{{
-    1
-    let style_start = search('^<style\( type="text/css"\)\?>')
-    1
-    let style_end = search('^</style>')
-    if style_start > 0 && style_end > 0
-      let buf_styles = getline(style_start + 1, style_end - 1)
-      for a_style in buf_styles
-	if index(style, a_style) == -1
-	  if diff_style_start == 0
-	    if a_style =~ '\<Diff\(Change\|Text\|Add\|Delete\)'
-	      let diff_style_start = len(style)-1
+      let style_start = search('^<style\( type="text/css"\)\?>')
+      1
+      let style_end = search('^</style>')
+      if style_start > 0 && style_end > 0
+	let buf_styles = getline(style_start + 1, style_end - 1)
+	for a_style in buf_styles
+	  if index(style, a_style) == -1
+	    if diff_style_start == 0
+	      if a_style =~ '\<Diff\(Change\|Text\|Add\|Delete\)'
+		let diff_style_start = len(style)-1
+	      endif
 	    endif
+	    call insert(style, a_style, insert_index)
+	    let insert_index += 1
 	  endif
-	  call insert(style, a_style, insert_index)
-	  let insert_index += 1
-	endif
-      endfor
-    endif " }}}
+	endfor
+      endif " }}}
 
-    " everything new will get added before the diff styles so diff highlight
-    " properly overrides normal highlight
-    if diff_style_start != 0
-      let insert_index = diff_style_start
+      " everything new will get added before the diff styles so diff highlight
+      " properly overrides normal highlight
+      if diff_style_start != 0
+	let insert_index = diff_style_start
+      endif
+
+      " Delete those parts that are not needed so we can include the rest into the
+      " resulting table.
+      1,/^<body.*\%(\n<!--.*-->\_s\+.*id='oneCharWidth'.*\_s\+.*id='oneInputWidth'.*\_s\+.*id='oneEmWidth'\)\?\zs/d_
+      $
+      ?</body>?,$d_
+    elseif !s:settings.no_modeline
+      " remove modeline from source files if it is included and we haven't deleted
+      " due to removing html footer already
+      $d
     endif
-
-    " Delete those parts that are not needed so we can include the rest into the
-    " resulting table.
-    1,/^<body.*\%(\n<!--.*-->\_s\+.*id='oneCharWidth'.*\_s\+.*id='oneInputWidth'.*\_s\+.*id='oneEmWidth'\)\?\zs/d_
-    $
-    ?</body>?,$d_
     let temp = getline(1,'$')
     " clean out id on the main content container because we already set it on
     " the table
@@ -478,7 +486,11 @@
     " undo deletion of start and end part
     " so we can later save the file as valid html
     " TODO: restore using grabbed lines if undolevel is 1?
-    normal! 2u
+    if !s:settings.no_doc
+      normal! 2u
+    elseif !s:settings.no_modeline
+      normal! u
+    endif
     if s:settings.use_css
       call add(html, '<td><div>')
     elseif s:settings.use_xhtml
@@ -495,17 +507,23 @@
     quit!
   endfor
 
-  let html[body_line_num] = body_line
+  if !s:settings.no_doc
+    let html[body_line_num] = body_line
+  endif
 
   call add(html, '</tr>')
   call add(html, '</table>')
-  call add(html, s:body_end_line)
-  call add(html, '</html>')
+  if !s:settings.no_doc
+    call add(html, s:body_end_line)
+    call add(html, '</html>')
+  endif
 
   " The generated HTML is admittedly ugly and takes a LONG time to fold.
   " Make sure the user doesn't do syntax folding when loading a generated file,
   " using a modeline.
-  call add(html, '<!-- vim: set foldmethod=manual : -->')
+  if !s:settings.no_modeline
+    call add(html, '<!-- vim: set foldmethod=manual : -->')
+  endif
 
   let i = 1
   let name = "Diff" . (s:settings.use_xhtml ? ".xhtml" : ".html")
@@ -542,129 +560,131 @@
 
   call append(0, html)
 
-  if len(style) > 0
-    1
-    let style_start = search('^</head>')-1
+  if !s:settings.no_doc
+    if len(style) > 0
+      1
+      let style_start = search('^</head>')-1
 
-    " add required javascript in reverse order so we can just call append again
-    " and again without adjusting {{{
+      " add required javascript in reverse order so we can just call append again
+      " and again without adjusting {{{
 
-    let s:uses_script = s:settings.dynamic_folds || s:settings.line_ids
+      let s:uses_script = s:settings.dynamic_folds || s:settings.line_ids
 
-    " insert script closing tag if needed
-    if s:uses_script
-      call append(style_start, [
-	    \ '',
-	    \ s:settings.use_xhtml ? '//]]>' : '-->',
-	    \ "</script>"
-	    \ ])
-    endif
-
-    " insert javascript to get IDs from line numbers, and to open a fold before
-    " jumping to any lines contained therein
-    if s:settings.line_ids
-      call append(style_start, [
-	    \ "  /* Always jump to new location even if the line was hidden inside a fold, or",
-	    \ "   * we corrected the raw number to a line ID.",
-	    \ "   */",
-	    \ "  if (lineElem) {",
-	    \ "    lineElem.scrollIntoView(true);",
-	    \ "  }",
-	    \ "  return true;",
-	    \ "}",
-	    \ "if ('onhashchange' in window) {",
-	    \ "  window.onhashchange = JumpToLine;",
-	    \ "}"
-	    \ ])
-
-      if s:settings.dynamic_folds
+      " insert script closing tag if needed
+      if s:uses_script
 	call append(style_start, [
-	      \ "",
-	      \ "  /* navigate upwards in the DOM tree to open all folds containing the line */",
-	      \ "  var node = lineElem;",
-	      \ "  while (node && node.id != 'vimCodeElement".s:settings.id_suffix."')",
-	      \ "  {",
-	      \ "    if (node.className == 'closed-fold')",
-	      \ "    {",
-	      \ "      /* toggle open the fold ID (remove window ID) */",
-	      \ "      toggleFold(node.id.substr(4));",
-	      \ "    }",
-	      \ "    node = node.parentNode;",
-	      \ "  }",
+	      \ '',
+	      \ s:settings.use_xhtml ? '//]]>' : '-->',
+	      \ "</script>"
 	      \ ])
       endif
-    endif
 
-    if s:settings.line_ids
-      call append(style_start, [
-	    \ "",
-	    \ "/* function to open any folds containing a jumped-to line before jumping to it */",
-	    \ "function JumpToLine()",
-	    \ "{",
-	    \ "  var lineNum;",
-	    \ "  lineNum = window.location.hash;",
-	    \ "  lineNum = lineNum.substr(1); /* strip off '#' */",
-	    \ "",
-	    \ "  if (lineNum.indexOf('L') == -1) {",
-	    \ "    lineNum = 'L'+lineNum;",
-	    \ "  }",
-	    \ "  if (lineNum.indexOf('W') == -1) {",
-	    \ "    lineNum = 'W1'+lineNum;",
-	    \ "  }",
-	    \ "  var lineElem = document.getElementById(lineNum);"
-	    \ ])
-    endif
+      " insert javascript to get IDs from line numbers, and to open a fold before
+      " jumping to any lines contained therein
+      if s:settings.line_ids
+	call append(style_start, [
+	      \ "  /* Always jump to new location even if the line was hidden inside a fold, or",
+	      \ "   * we corrected the raw number to a line ID.",
+	      \ "   */",
+	      \ "  if (lineElem) {",
+	      \ "    lineElem.scrollIntoView(true);",
+	      \ "  }",
+	      \ "  return true;",
+	      \ "}",
+	      \ "if ('onhashchange' in window) {",
+	      \ "  window.onhashchange = JumpToLine;",
+	      \ "}"
+	      \ ])
 
-    " Insert javascript to toggle matching folds open and closed in all windows,
-    " if dynamic folding is active.
-    if s:settings.dynamic_folds
-      call append(style_start, [
-	    \  "  function toggleFold(objID)",
-	    \  "  {",
-	    \  "    for (win_num = 1; win_num <= ".len(a:buf_list)."; win_num++)",
-	    \  "    {",
-	    \  "      var fold;",
-	    \  '      fold = document.getElementById("win"+win_num+objID);',
-	    \  "      if(fold.className == 'closed-fold')",
-	    \  "      {",
-	    \  "        fold.className = 'open-fold';",
-	    \  "      }",
-	    \  "      else if (fold.className == 'open-fold')",
-	    \  "      {",
-	    \  "        fold.className = 'closed-fold';",
-	    \  "      }",
-	    \  "    }",
-	    \  "  }",
-	    \ ])
-    endif
+	if s:settings.dynamic_folds
+	  call append(style_start, [
+		\ "",
+		\ "  /* navigate upwards in the DOM tree to open all folds containing the line */",
+		\ "  var node = lineElem;",
+		\ "  while (node && node.id != 'vimCodeElement".s:settings.id_suffix."')",
+		\ "  {",
+		\ "    if (node.className == 'closed-fold')",
+		\ "    {",
+		\ "      /* toggle open the fold ID (remove window ID) */",
+		\ "      toggleFold(node.id.substr(4));",
+		\ "    }",
+		\ "    node = node.parentNode;",
+		\ "  }",
+		\ ])
+	endif
+      endif
 
-    if s:uses_script
-      " insert script tag if needed
-      call append(style_start, [
-	    \ "<script" . (s:html5 ? "" : " type='text/javascript'") . ">",
-	    \ s:settings.use_xhtml ? '//<![CDATA[' : "<!--"])
-    endif
+      if s:settings.line_ids
+	call append(style_start, [
+	      \ "",
+	      \ "/* function to open any folds containing a jumped-to line before jumping to it */",
+	      \ "function JumpToLine()",
+	      \ "{",
+	      \ "  var lineNum;",
+	      \ "  lineNum = window.location.hash;",
+	      \ "  lineNum = lineNum.substr(1); /* strip off '#' */",
+	      \ "",
+	      \ "  if (lineNum.indexOf('L') == -1) {",
+	      \ "    lineNum = 'L'+lineNum;",
+	      \ "  }",
+	      \ "  if (lineNum.indexOf('W') == -1) {",
+	      \ "    lineNum = 'W1'+lineNum;",
+	      \ "  }",
+	      \ "  var lineElem = document.getElementById(lineNum);"
+	      \ ])
+      endif
 
-    " Insert styles from all the generated html documents and additional styles
-    " for the table-based layout of the side-by-side diff. The diff should take
-    " up the full browser window (but not more), and be static in size,
-    " horizontally scrollable when the lines are too long. Otherwise, the diff
-    " is pretty useless for really long lines. {{{
-    if s:settings.use_css
-      call append(style_start,
-	    \ ['<style' . (s:html5 ? '' : 'type="text/css"') . '>']+
-	    \ style+
-	    \ [ s:settings.use_xhtml ? '' : '<!--',
-	    \   'table { table-layout: fixed; }',
-	    \   'html, body, table, tbody { width: 100%; margin: 0; padding: 0; }',
-	    \   'table, td, th { border: 1px solid; }',
-	    \   'td { vertical-align: top; }',
-	    \   'th, td { width: '.printf("%.1f",100.0/len(a:win_list)).'%; }',
-	    \   'td div { overflow: auto; }',
-	    \   s:settings.use_xhtml ? '' : '-->',
-	    \   '</style>'
-	    \])
-    endif "}}}
+      " Insert javascript to toggle matching folds open and closed in all windows,
+      " if dynamic folding is active.
+      if s:settings.dynamic_folds
+	call append(style_start, [
+	      \  "  function toggleFold(objID)",
+	      \  "  {",
+	      \  "    for (win_num = 1; win_num <= ".len(a:buf_list)."; win_num++)",
+	      \  "    {",
+	      \  "      var fold;",
+	      \  '      fold = document.getElementById("win"+win_num+objID);',
+	      \  "      if(fold.className == 'closed-fold')",
+	      \  "      {",
+	      \  "        fold.className = 'open-fold';",
+	      \  "      }",
+	      \  "      else if (fold.className == 'open-fold')",
+	      \  "      {",
+	      \  "        fold.className = 'closed-fold';",
+	      \  "      }",
+	      \  "    }",
+	      \  "  }",
+	      \ ])
+      endif
+
+      if s:uses_script
+	" insert script tag if needed
+	call append(style_start, [
+	      \ "<script" . (s:html5 ? "" : " type='text/javascript'") . ">",
+	      \ s:settings.use_xhtml ? '//<![CDATA[' : "<!--"])
+      endif
+
+      " Insert styles from all the generated html documents and additional styles
+      " for the table-based layout of the side-by-side diff. The diff should take
+      " up the full browser window (but not more), and be static in size,
+      " horizontally scrollable when the lines are too long. Otherwise, the diff
+      " is pretty useless for really long lines. {{{
+      if s:settings.use_css
+	call append(style_start,
+	      \ ['<style' . (s:html5 ? '' : 'type="text/css"') . '>']+
+	      \ style+
+	      \ [ s:settings.use_xhtml ? '' : '<!--',
+	      \   'table { table-layout: fixed; }',
+	      \   'html, body, table, tbody { width: 100%; margin: 0; padding: 0; }',
+	      \   'table, td, th { border: 1px solid; }',
+	      \   'td { vertical-align: top; }',
+	      \   'th, td { width: '.printf("%.1f",100.0/len(a:win_list)).'%; }',
+	      \   'td div { overflow: auto; }',
+	      \   s:settings.use_xhtml ? '' : '-->',
+	      \   '</style>'
+	      \])
+      endif "}}}
+    endif
   endif
 
   let &paste = s:old_paste