]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
runtime(2html): Convert to Vim9 script
authorMao-Yining <mao.yining@outlook.com>
Fri, 29 May 2026 18:02:01 +0000 (18:02 +0000)
committerChristian Brabandt <cb@256bit.org>
Fri, 29 May 2026 18:02:01 +0000 (18:02 +0000)
closes: #19915

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: fritzophrenic <fritzophrenic@gmail.com>
Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
Signed-off-by: Mao-Yining <mao.yining@outlook.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
.github/MAINTAINERS
runtime/autoload/tohtml.vim
runtime/doc/syntax.txt
runtime/doc/version9.txt
runtime/plugin/tohtml.vim
runtime/syntax/2html.vim
src/testdir/samples/Test_tohtml_basic.c.html
src/testdir/samples/Test_tohtml_basic_no_css.c.html

index 614dd13e216004793fa5d023e6c714366c5b5832..4c4873507defd02cfef1259265c4374ff719e256 100644 (file)
@@ -18,6 +18,7 @@ runtime/autoload/javascriptcomplete.vim                       @jsit
 runtime/autoload/modula2.vim                           @dkearns
 runtime/autoload/rubycomplete.vim                      @segfault @dkearns
 runtime/autoload/rust.vim                              @lilyball
+runtime/autoload/tohtml.vim                            @fritzophrenic
 runtime/autoload/typeset.vim                           @lifepillar
 runtime/autoload/xmlformat.vim                         @chrisbra
 runtime/autoload/dist/json.vim                         @habamax
@@ -470,7 +471,8 @@ runtime/pack/dist/opt/helptoc/                              @kennypete
 runtime/pack/dist/opt/matchit/                         @chrisbra
 runtime/pack/dist/opt/nohlsearch/                      @habamax
 runtime/plugin/manpager.vim                            @Konfekt
-runtime/syntax/shared/hgcommitDiff.vim                 @vegerot
+runtime/plugin/tohtml.vim                              @fritzophrenic
+runtime/syntax/2html.vim                               @fritzophrenic
 runtime/syntax/abaqus.vim                              @costerwi
 runtime/syntax/abnf.vim                                        @A4-Tacks
 runtime/syntax/aidl.vim                                        @dpelle
@@ -581,17 +583,17 @@ runtime/syntax/java.vim                                   @zzzyxwvut
 runtime/syntax/javascript.vim                          @fleiner
 runtime/syntax/jinja.vim                               @gpanders
 runtime/syntax/jjdescription.vim                       @gpanders
+runtime/syntax/jq.vim                                  @vito-c
 runtime/syntax/json.vim                                        @vito-c
 runtime/syntax/jsonc.vim                               @izhakjakov
 runtime/syntax/julia.vim                               @carlobaldassi
 runtime/syntax/just.vim                                        @pbnj
-runtime/syntax/jq.vim                                  @vito-c
 runtime/syntax/karel.vim                               @kirillmorozov
 runtime/syntax/kconfig.vim                             @chrisbra
+runtime/syntax/kdl.vim                                 @imsnif @jiangyinzuo
 runtime/syntax/kitty.vim                               @OXY2DEV
 runtime/syntax/kivy.vim                                        @prophittcorey
 runtime/syntax/kotlin.vim                              @udalov
-runtime/syntax/kdl.vim                                 @imsnif @jiangyinzuo
 runtime/syntax/krl.vim                                 @KnoP-01
 runtime/syntax/leex.vim                                        @jparise
 runtime/syntax/less.vim                                        @genoma
@@ -608,8 +610,8 @@ runtime/syntax/m3quake.vim                          @dkearns
 runtime/syntax/mailcap.vim                             @dkearns
 runtime/syntax/mallard.vim                             @jhradilek
 runtime/syntax/markdown.vim                            @tpope
-runtime/syntax/mbsync.vim                              @fymyte
 runtime/syntax/mason.vim                               @petdance
+runtime/syntax/mbsync.vim                              @fymyte
 runtime/syntax/mediawiki.vim                           @avidseeker
 runtime/syntax/meson.vim                               @Liambeguin
 runtime/syntax/mf.vim                                  @lifepillar
@@ -658,8 +660,8 @@ runtime/syntax/qml.vim                                      @ChaseKnowlden
 runtime/syntax/racket.vim                              @benknoble
 runtime/syntax/raml.vim                                        @in3d
 runtime/syntax/rapid.vim                               @KnoP-01
-runtime/syntax/ratpoison.vim                           @trapd00r
 runtime/syntax/rasi.vim                                        @fymyte
+runtime/syntax/ratpoison.vim                           @trapd00r
 runtime/syntax/rc.vim                                  @chrisbra
 runtime/syntax/rcs.vim                                 @hdima
 runtime/syntax/rebol.vim                               @mrdubya
@@ -675,8 +677,9 @@ runtime/syntax/scala.vim                            @derekwyatt
 runtime/syntax/scheme.vim                              @evhan
 runtime/syntax/scss.vim                                        @tpope
 runtime/syntax/sed.vim                                 @dkearns
-runtime/syntax/shared/debversions.vim                  @jamessan
 runtime/syntax/shaderslang.vim                         @mTvare6
+runtime/syntax/shared/debversions.vim                  @jamessan
+runtime/syntax/shared/hgcommitDiff.vim                 @vegerot
 runtime/syntax/skhd.vim                                        @kiyoon
 runtime/syntax/solidity.vim                            @coti-z
 runtime/syntax/spajson.vim                             @dseomn
index d2722a4021334e6589915394243441b946ea88cd..054deabcbaefd7835f8df9279c07255c0ce26af4 100644 (file)
-" Vim autoload file for the tohtml plugin.
-" Maintainer: Ben Fritz <fritzophrenic@gmail.com>
-" Last Change: 2023 Sep 03
-"
-" Additional contributors:
-"
-"            Original by Bram Moolenaar <Bram@vim.org>
-"            Diff2HTML() added by Christian Brabandt <cb@256bit.org>
-"
-"            See Mercurial change logs for more!
-
-" this file uses line continuations
-let s:cpo_sav = &cpo
-set cpo&vim
-
-" Automatically find charsets from all encodings supported natively by Vim. With
-" the 8bit- and 2byte- prefixes, Vim can actually support more encodings than
-" this. Let the user specify these however since they won't be supported on
-" every system.
-"
-" Note, not all of Vim's supported encodings have a charset to use.
-"
-" Names in this list are from:
-"   http://www.iana.org/assignments/character-sets
-" g:tohtml#encoding_to_charset: {{{
-let g:tohtml#encoding_to_charset = {
-      \ 'latin1' : 'ISO-8859-1',
-      \ 'iso-8859-2' : 'ISO-8859-2',
-      \ 'iso-8859-3' : 'ISO-8859-3',
-      \ 'iso-8859-4' : 'ISO-8859-4',
-      \ 'iso-8859-5' : 'ISO-8859-5',
-      \ 'iso-8859-6' : 'ISO-8859-6',
-      \ 'iso-8859-7' : 'ISO-8859-7',
-      \ 'iso-8859-8' : 'ISO-8859-8',
-      \ 'iso-8859-9' : 'ISO-8859-9',
-      \ 'iso-8859-10' : '',
-      \ 'iso-8859-13' : 'ISO-8859-13',
-      \ 'iso-8859-14' : '',
-      \ 'iso-8859-15' : 'ISO-8859-15',
-      \ 'koi8-r' : 'KOI8-R',
-      \ 'koi8-u' : 'KOI8-U',
-      \ 'macroman' : 'macintosh',
-      \ 'cp437' : '',
-      \ 'cp775' : '',
-      \ 'cp850' : '',
-      \ 'cp852' : '',
-      \ 'cp855' : '',
-      \ 'cp857' : '',
-      \ 'cp860' : '',
-      \ 'cp861' : '',
-      \ 'cp862' : '',
-      \ 'cp863' : '',
-      \ 'cp865' : '',
-      \ 'cp866' : 'IBM866',
-      \ 'cp869' : '',
-      \ 'cp874' : '',
-      \ 'cp1250' : 'windows-1250',
-      \ 'cp1251' : 'windows-1251',
-      \ 'cp1253' : 'windows-1253',
-      \ 'cp1254' : 'windows-1254',
-      \ 'cp1255' : 'windows-1255',
-      \ 'cp1256' : 'windows-1256',
-      \ 'cp1257' : 'windows-1257',
-      \ 'cp1258' : 'windows-1258',
-      \ 'euc-jp' : 'EUC-JP',
-      \ 'sjis' : 'Shift_JIS',
-      \ 'cp932' : 'Shift_JIS',
-      \ 'cp949' : '',
-      \ 'euc-kr' : 'EUC-KR',
-      \ 'cp936' : 'GBK',
-      \ 'euc-cn' : 'GB2312',
-      \ 'big5' : 'Big5',
-      \ 'cp950' : 'Big5',
-      \ 'utf-8' : 'UTF-8',
-      \ 'ucs-2' : 'UTF-8',
-      \ 'ucs-2le' : 'UTF-8',
-      \ 'utf-16' : 'UTF-8',
-      \ 'utf-16le' : 'UTF-8',
-      \ 'ucs-4' : 'UTF-8',
-      \ 'ucs-4le' : 'UTF-8',
-      \ }
-lockvar g:tohtml#encoding_to_charset
-" Notes:
-"   1. All UCS/UTF are converted to UTF-8 because it is much better supported
-"   2. Any blank spaces are there because Vim supports it but at least one major
-"      web browser does not according to http://wiki.whatwg.org/wiki/Web_Encodings.
-" }}}
-
-" Only automatically find encodings supported natively by Vim, let the user
-" specify the encoding if it's not natively supported. This function is only
-" used when the user specifies the charset, they better know what they are
-" doing!
-"
-" Names in this list are from:
-"   http://www.iana.org/assignments/character-sets
-" g:tohtml#charset_to_encoding: {{{
-let g:tohtml#charset_to_encoding = {
-      \ 'iso_8859-1:1987' : 'latin1',
-      \ 'iso-ir-100' : 'latin1',
-      \ 'iso_8859-1' : 'latin1',
-      \ 'iso-8859-1' : 'latin1',
-      \ 'latin1' : 'latin1',
-      \ 'l1' : 'latin1',
-      \ 'ibm819' : 'latin1',
-      \ 'cp819' : 'latin1',
-      \ 'csisolatin1' : 'latin1',
-      \ 'iso_8859-2:1987' : 'iso-8859-2',
-      \ 'iso-ir-101' : 'iso-8859-2',
-      \ 'iso_8859-2' : 'iso-8859-2',
-      \ 'iso-8859-2' : 'iso-8859-2',
-      \ 'latin2' : 'iso-8859-2',
-      \ 'l2' : 'iso-8859-2',
-      \ 'csisolatin2' : 'iso-8859-2',
-      \ 'iso_8859-3:1988' : 'iso-8859-3',
-      \ 'iso-ir-109' : 'iso-8859-3',
-      \ 'iso_8859-3' : 'iso-8859-3',
-      \ 'iso-8859-3' : 'iso-8859-3',
-      \ 'latin3' : 'iso-8859-3',
-      \ 'l3' : 'iso-8859-3',
-      \ 'csisolatin3' : 'iso-8859-3',
-      \ 'iso_8859-4:1988' : 'iso-8859-4',
-      \ 'iso-ir-110' : 'iso-8859-4',
-      \ 'iso_8859-4' : 'iso-8859-4',
-      \ 'iso-8859-4' : 'iso-8859-4',
-      \ 'latin4' : 'iso-8859-4',
-      \ 'l4' : 'iso-8859-4',
-      \ 'csisolatin4' : 'iso-8859-4',
-      \ 'iso_8859-5:1988' : 'iso-8859-5',
-      \ 'iso-ir-144' : 'iso-8859-5',
-      \ 'iso_8859-5' : 'iso-8859-5',
-      \ 'iso-8859-5' : 'iso-8859-5',
-      \ 'cyrillic' : 'iso-8859-5',
-      \ 'csisolatincyrillic' : 'iso-8859-5',
-      \ 'iso_8859-6:1987' : 'iso-8859-6',
-      \ 'iso-ir-127' : 'iso-8859-6',
-      \ 'iso_8859-6' : 'iso-8859-6',
-      \ 'iso-8859-6' : 'iso-8859-6',
-      \ 'ecma-114' : 'iso-8859-6',
-      \ 'asmo-708' : 'iso-8859-6',
-      \ 'arabic' : 'iso-8859-6',
-      \ 'csisolatinarabic' : 'iso-8859-6',
-      \ 'iso_8859-7:1987' : 'iso-8859-7',
-      \ 'iso-ir-126' : 'iso-8859-7',
-      \ 'iso_8859-7' : 'iso-8859-7',
-      \ 'iso-8859-7' : 'iso-8859-7',
-      \ 'elot_928' : 'iso-8859-7',
-      \ 'ecma-118' : 'iso-8859-7',
-      \ 'greek' : 'iso-8859-7',
-      \ 'greek8' : 'iso-8859-7',
-      \ 'csisolatingreek' : 'iso-8859-7',
-      \ 'iso_8859-8:1988' : 'iso-8859-8',
-      \ 'iso-ir-138' : 'iso-8859-8',
-      \ 'iso_8859-8' : 'iso-8859-8',
-      \ 'iso-8859-8' : 'iso-8859-8',
-      \ 'hebrew' : 'iso-8859-8',
-      \ 'csisolatinhebrew' : 'iso-8859-8',
-      \ 'iso_8859-9:1989' : 'iso-8859-9',
-      \ 'iso-ir-148' : 'iso-8859-9',
-      \ 'iso_8859-9' : 'iso-8859-9',
-      \ 'iso-8859-9' : 'iso-8859-9',
-      \ 'latin5' : 'iso-8859-9',
-      \ 'l5' : 'iso-8859-9',
-      \ 'csisolatin5' : 'iso-8859-9',
-      \ 'iso-8859-10' : 'iso-8859-10',
-      \ 'iso-ir-157' : 'iso-8859-10',
-      \ 'l6' : 'iso-8859-10',
-      \ 'iso_8859-10:1992' : 'iso-8859-10',
-      \ 'csisolatin6' : 'iso-8859-10',
-      \ 'latin6' : 'iso-8859-10',
-      \ 'iso-8859-13' : 'iso-8859-13',
-      \ 'iso-8859-14' : 'iso-8859-14',
-      \ 'iso-ir-199' : 'iso-8859-14',
-      \ 'iso_8859-14:1998' : 'iso-8859-14',
-      \ 'iso_8859-14' : 'iso-8859-14',
-      \ 'latin8' : 'iso-8859-14',
-      \ 'iso-celtic' : 'iso-8859-14',
-      \ 'l8' : 'iso-8859-14',
-      \ 'iso-8859-15' : 'iso-8859-15',
-      \ 'iso_8859-15' : 'iso-8859-15',
-      \ 'latin-9' : 'iso-8859-15',
-      \ 'koi8-r' : 'koi8-r',
-      \ 'cskoi8r' : 'koi8-r',
-      \ 'koi8-u' : 'koi8-u',
-      \ 'macintosh' : 'macroman',
-      \ 'mac' : 'macroman',
-      \ 'csmacintosh' : 'macroman',
-      \ 'ibm437' : 'cp437',
-      \ 'cp437' : 'cp437',
-      \ '437' : 'cp437',
-      \ 'cspc8codepage437' : 'cp437',
-      \ 'ibm775' : 'cp775',
-      \ 'cp775' : 'cp775',
-      \ 'cspc775baltic' : 'cp775',
-      \ 'ibm850' : 'cp850',
-      \ 'cp850' : 'cp850',
-      \ '850' : 'cp850',
-      \ 'cspc850multilingual' : 'cp850',
-      \ 'ibm852' : 'cp852',
-      \ 'cp852' : 'cp852',
-      \ '852' : 'cp852',
-      \ 'cspcp852' : 'cp852',
-      \ 'ibm855' : 'cp855',
-      \ 'cp855' : 'cp855',
-      \ '855' : 'cp855',
-      \ 'csibm855' : 'cp855',
-      \ 'ibm857' : 'cp857',
-      \ 'cp857' : 'cp857',
-      \ '857' : 'cp857',
-      \ 'csibm857' : 'cp857',
-      \ 'ibm860' : 'cp860',
-      \ 'cp860' : 'cp860',
-      \ '860' : 'cp860',
-      \ 'csibm860' : 'cp860',
-      \ 'ibm861' : 'cp861',
-      \ 'cp861' : 'cp861',
-      \ '861' : 'cp861',
-      \ 'cp-is' : 'cp861',
-      \ 'csibm861' : 'cp861',
-      \ 'ibm862' : 'cp862',
-      \ 'cp862' : 'cp862',
-      \ '862' : 'cp862',
-      \ 'cspc862latinhebrew' : 'cp862',
-      \ 'ibm863' : 'cp863',
-      \ 'cp863' : 'cp863',
-      \ '863' : 'cp863',
-      \ 'csibm863' : 'cp863',
-      \ 'ibm865' : 'cp865',
-      \ 'cp865' : 'cp865',
-      \ '865' : 'cp865',
-      \ 'csibm865' : 'cp865',
-      \ 'ibm866' : 'cp866',
-      \ 'cp866' : 'cp866',
-      \ '866' : 'cp866',
-      \ 'csibm866' : 'cp866',
-      \ 'ibm869' : 'cp869',
-      \ 'cp869' : 'cp869',
-      \ '869' : 'cp869',
-      \ 'cp-gr' : 'cp869',
-      \ 'csibm869' : 'cp869',
-      \ 'windows-1250' : 'cp1250',
-      \ 'windows-1251' : 'cp1251',
-      \ 'windows-1253' : 'cp1253',
-      \ 'windows-1254' : 'cp1254',
-      \ 'windows-1255' : 'cp1255',
-      \ 'windows-1256' : 'cp1256',
-      \ 'windows-1257' : 'cp1257',
-      \ 'windows-1258' : 'cp1258',
-      \ 'extended_unix_code_packed_format_for_japanese' : 'euc-jp',
-      \ 'cseucpkdfmtjapanese' : 'euc-jp',
-      \ 'euc-jp' : 'euc-jp',
-      \ 'shift_jis' : 'sjis',
-      \ 'ms_kanji' : 'sjis',
-      \ 'sjis' : 'sjis',
-      \ 'csshiftjis' : 'sjis',
-      \ 'ibm-thai' : 'cp874',
-      \ 'csibmthai' : 'cp874',
-      \ 'ks_c_5601-1987' : 'cp949',
-      \ 'iso-ir-149' : 'cp949',
-      \ 'ks_c_5601-1989' : 'cp949',
-      \ 'ksc_5601' : 'cp949',
-      \ 'korean' : 'cp949',
-      \ 'csksc56011987' : 'cp949',
-      \ 'euc-kr' : 'euc-kr',
-      \ 'cseuckr' : 'euc-kr',
-      \ 'gbk' : 'cp936',
-      \ 'cp936' : 'cp936',
-      \ 'ms936' : 'cp936',
-      \ 'windows-936' : 'cp936',
-      \ 'gb_2312-80' : 'euc-cn',
-      \ 'iso-ir-58' : 'euc-cn',
-      \ 'chinese' : 'euc-cn',
-      \ 'csiso58gb231280' : 'euc-cn',
-      \ 'big5' : 'big5',
-      \ 'csbig5' : 'big5',
-      \ 'utf-8' : 'utf-8',
-      \ 'iso-10646-ucs-2' : 'ucs-2',
-      \ 'csunicode' : 'ucs-2',
-      \ 'utf-16' : 'utf-16',
-      \ 'utf-16be' : 'utf-16',
-      \ 'utf-16le' : 'utf-16le',
-      \ 'utf-32' : 'ucs-4',
-      \ 'utf-32be' : 'ucs-4',
-      \ 'utf-32le' : 'ucs-4le',
-      \ 'iso-10646-ucs-4' : 'ucs-4',
-      \ 'csucs4' : 'ucs-4'
-      \ }
-lockvar g:tohtml#charset_to_encoding
-"}}}
-
-func! tohtml#Convert2HTML(line1, line2) "{{{
-  let s:settings = tohtml#GetUserSettings()
-
-  if !&diff || s:settings.diff_one_file "{{{
-    if a:line2 >= a:line1
-      let g:html_start_line = a:line1
-      let g:html_end_line = a:line2
+vim9script
+# Vim autoload file for the tohtml plugin.
+# Maintainer: Ben Fritz <fritzophrenic@gmail.com>
+# Last Change: 2026-05-22
+#
+# Additional contributors:
+#
+#         Original by Bram Moolenaar <Bram@vim.org>
+#         Diff2HTML() added by Christian Brabandt <cb@256bit.org>
+#         Converted to Vim9 by Mao-Yining Vim PR #19915
+#
+#         See git change logs for more!
+
+# Automatically find charsets from all encodings supported natively by Vim. With
+# the 8bit- and 2byte- prefixes, Vim can actually support more encodings than
+# this. Let the user specify these however since they won't be supported on
+# every system.
+#
+# Note, not all of Vim's supported encodings have a charset to use.
+#
+# Names in this list are from:
+#   http://www.iana.org/assignments/character-sets
+# export const encoding_to_charset: {{{
+export const encoding_to_charset = {
+  'latin1': 'ISO-8859-1',
+  'iso-8859-2': 'ISO-8859-2',
+  'iso-8859-3': 'ISO-8859-3',
+  'iso-8859-4': 'ISO-8859-4',
+  'iso-8859-5': 'ISO-8859-5',
+  'iso-8859-6': 'ISO-8859-6',
+  'iso-8859-7': 'ISO-8859-7',
+  'iso-8859-8': 'ISO-8859-8',
+  'iso-8859-9': 'ISO-8859-9',
+  'iso-8859-10': '',
+  'iso-8859-13': 'ISO-8859-13',
+  'iso-8859-14': '',
+  'iso-8859-15': 'ISO-8859-15',
+  'koi8-r': 'KOI8-R',
+  'koi8-u': 'KOI8-U',
+  'macroman': 'macintosh',
+  'cp437': '',
+  'cp775': '',
+  'cp850': '',
+  'cp852': '',
+  'cp855': '',
+  'cp857': '',
+  'cp860': '',
+  'cp861': '',
+  'cp862': '',
+  'cp863': '',
+  'cp865': '',
+  'cp866': 'IBM866',
+  'cp869': '',
+  'cp874': '',
+  'cp1250': 'windows-1250',
+  'cp1251': 'windows-1251',
+  'cp1253': 'windows-1253',
+  'cp1254': 'windows-1254',
+  'cp1255': 'windows-1255',
+  'cp1256': 'windows-1256',
+  'cp1257': 'windows-1257',
+  'cp1258': 'windows-1258',
+  'euc-jp': 'EUC-JP',
+  'sjis': 'Shift_JIS',
+  'cp932': 'Shift_JIS',
+  'cp949': '',
+  'euc-kr': 'EUC-KR',
+  'cp936': 'GBK',
+  'euc-cn': 'GB2312',
+  'big5': 'Big5',
+  'cp950': 'Big5',
+  'utf-8': 'UTF-8',
+  'ucs-2': 'UTF-8',
+  'ucs-2le': 'UTF-8',
+  'utf-16': 'UTF-8',
+  'utf-16le': 'UTF-8',
+  'ucs-4': 'UTF-8',
+  'ucs-4le': 'UTF-8',
+}
+
+# Notes:
+#   1. All UCS/UTF are converted to UTF-8 because it is much better supported
+#   2. Any blank spaces are there because Vim supports it but at least one major
+#      web browser does not according to http://wiki.whatwg.org/wiki/Web_Encodings.
+# }}}
+
+# Only automatically find encodings supported natively by Vim, let the user
+# specify the encoding if it's not natively supported. This function is only
+# used when the user specifies the charset, they better know what they are
+# doing!
+#
+# Names in this list are from:
+#   http://www.iana.org/assignments/character-sets
+# export const charset_to_encoding: {{{
+export const charset_to_encoding = {
+  'iso_8859-1:1987': 'latin1',
+  'iso-ir-100': 'latin1',
+  'iso_8859-1': 'latin1',
+  'iso-8859-1': 'latin1',
+  'latin1': 'latin1',
+  'l1': 'latin1',
+  'ibm819': 'latin1',
+  'cp819': 'latin1',
+  'csisolatin1': 'latin1',
+  'iso_8859-2:1987': 'iso-8859-2',
+  'iso-ir-101': 'iso-8859-2',
+  'iso_8859-2': 'iso-8859-2',
+  'iso-8859-2': 'iso-8859-2',
+  'latin2': 'iso-8859-2',
+  'l2': 'iso-8859-2',
+  'csisolatin2': 'iso-8859-2',
+  'iso_8859-3:1988': 'iso-8859-3',
+  'iso-ir-109': 'iso-8859-3',
+  'iso_8859-3': 'iso-8859-3',
+  'iso-8859-3': 'iso-8859-3',
+  'latin3': 'iso-8859-3',
+  'l3': 'iso-8859-3',
+  'csisolatin3': 'iso-8859-3',
+  'iso_8859-4:1988': 'iso-8859-4',
+  'iso-ir-110': 'iso-8859-4',
+  'iso_8859-4': 'iso-8859-4',
+  'iso-8859-4': 'iso-8859-4',
+  'latin4': 'iso-8859-4',
+  'l4': 'iso-8859-4',
+  'csisolatin4': 'iso-8859-4',
+  'iso_8859-5:1988': 'iso-8859-5',
+  'iso-ir-144': 'iso-8859-5',
+  'iso_8859-5': 'iso-8859-5',
+  'iso-8859-5': 'iso-8859-5',
+  'cyrillic': 'iso-8859-5',
+  'csisolatincyrillic': 'iso-8859-5',
+  'iso_8859-6:1987': 'iso-8859-6',
+  'iso-ir-127': 'iso-8859-6',
+  'iso_8859-6': 'iso-8859-6',
+  'iso-8859-6': 'iso-8859-6',
+  'ecma-114': 'iso-8859-6',
+  'asmo-708': 'iso-8859-6',
+  'arabic': 'iso-8859-6',
+  'csisolatinarabic': 'iso-8859-6',
+  'iso_8859-7:1987': 'iso-8859-7',
+  'iso-ir-126': 'iso-8859-7',
+  'iso_8859-7': 'iso-8859-7',
+  'iso-8859-7': 'iso-8859-7',
+  'elot_928': 'iso-8859-7',
+  'ecma-118': 'iso-8859-7',
+  'greek': 'iso-8859-7',
+  'greek8': 'iso-8859-7',
+  'csisolatingreek': 'iso-8859-7',
+  'iso_8859-8:1988': 'iso-8859-8',
+  'iso-ir-138': 'iso-8859-8',
+  'iso_8859-8': 'iso-8859-8',
+  'iso-8859-8': 'iso-8859-8',
+  'hebrew': 'iso-8859-8',
+  'csisolatinhebrew': 'iso-8859-8',
+  'iso_8859-9:1989': 'iso-8859-9',
+  'iso-ir-148': 'iso-8859-9',
+  'iso_8859-9': 'iso-8859-9',
+  'iso-8859-9': 'iso-8859-9',
+  'latin5': 'iso-8859-9',
+  'l5': 'iso-8859-9',
+  'csisolatin5': 'iso-8859-9',
+  'iso-8859-10': 'iso-8859-10',
+  'iso-ir-157': 'iso-8859-10',
+  'l6': 'iso-8859-10',
+  'iso_8859-10:1992': 'iso-8859-10',
+  'csisolatin6': 'iso-8859-10',
+  'latin6': 'iso-8859-10',
+  'iso-8859-13': 'iso-8859-13',
+  'iso-8859-14': 'iso-8859-14',
+  'iso-ir-199': 'iso-8859-14',
+  'iso_8859-14:1998': 'iso-8859-14',
+  'iso_8859-14': 'iso-8859-14',
+  'latin8': 'iso-8859-14',
+  'iso-celtic': 'iso-8859-14',
+  'l8': 'iso-8859-14',
+  'iso-8859-15': 'iso-8859-15',
+  'iso_8859-15': 'iso-8859-15',
+  'latin-9': 'iso-8859-15',
+  'koi8-r': 'koi8-r',
+  'cskoi8r': 'koi8-r',
+  'koi8-u': 'koi8-u',
+  'macintosh': 'macroman',
+  'mac': 'macroman',
+  'csmacintosh': 'macroman',
+  'ibm437': 'cp437',
+  'cp437': 'cp437',
+  '437': 'cp437',
+  'cspc8codepage437': 'cp437',
+  'ibm775': 'cp775',
+  'cp775': 'cp775',
+  'cspc775baltic': 'cp775',
+  'ibm850': 'cp850',
+  'cp850': 'cp850',
+  '850': 'cp850',
+  'cspc850multilingual': 'cp850',
+  'ibm852': 'cp852',
+  'cp852': 'cp852',
+  '852': 'cp852',
+  'cspcp852': 'cp852',
+  'ibm855': 'cp855',
+  'cp855': 'cp855',
+  '855': 'cp855',
+  'csibm855': 'cp855',
+  'ibm857': 'cp857',
+  'cp857': 'cp857',
+  '857': 'cp857',
+  'csibm857': 'cp857',
+  'ibm860': 'cp860',
+  'cp860': 'cp860',
+  '860': 'cp860',
+  'csibm860': 'cp860',
+  'ibm861': 'cp861',
+  'cp861': 'cp861',
+  '861': 'cp861',
+  'cp-is': 'cp861',
+  'csibm861': 'cp861',
+  'ibm862': 'cp862',
+  'cp862': 'cp862',
+  '862': 'cp862',
+  'cspc862latinhebrew': 'cp862',
+  'ibm863': 'cp863',
+  'cp863': 'cp863',
+  '863': 'cp863',
+  'csibm863': 'cp863',
+  'ibm865': 'cp865',
+  'cp865': 'cp865',
+  '865': 'cp865',
+  'csibm865': 'cp865',
+  'ibm866': 'cp866',
+  'cp866': 'cp866',
+  '866': 'cp866',
+  'csibm866': 'cp866',
+  'ibm869': 'cp869',
+  'cp869': 'cp869',
+  '869': 'cp869',
+  'cp-gr': 'cp869',
+  'csibm869': 'cp869',
+  'windows-1250': 'cp1250',
+  'windows-1251': 'cp1251',
+  'windows-1253': 'cp1253',
+  'windows-1254': 'cp1254',
+  'windows-1255': 'cp1255',
+  'windows-1256': 'cp1256',
+  'windows-1257': 'cp1257',
+  'windows-1258': 'cp1258',
+  'extended_unix_code_packed_format_for_japanese': 'euc-jp',
+  'cseucpkdfmtjapanese': 'euc-jp',
+  'euc-jp': 'euc-jp',
+  'shift_jis': 'sjis',
+  'ms_kanji': 'sjis',
+  'sjis': 'sjis',
+  'csshiftjis': 'sjis',
+  'ibm-thai': 'cp874',
+  'csibmthai': 'cp874',
+  'ks_c_5601-1987': 'cp949',
+  'iso-ir-149': 'cp949',
+  'ks_c_5601-1989': 'cp949',
+  'ksc_5601': 'cp949',
+  'korean': 'cp949',
+  'csksc56011987': 'cp949',
+  'euc-kr': 'euc-kr',
+  'cseuckr': 'euc-kr',
+  'gbk': 'cp936',
+  'cp936': 'cp936',
+  'ms936': 'cp936',
+  'windows-936': 'cp936',
+  'gb_2312-80': 'euc-cn',
+  'iso-ir-58': 'euc-cn',
+  'chinese': 'euc-cn',
+  'csiso58gb231280': 'euc-cn',
+  'big5': 'big5',
+  'csbig5': 'big5',
+  'utf-8': 'utf-8',
+  'iso-10646-ucs-2': 'ucs-2',
+  'csunicode': 'ucs-2',
+  'utf-16': 'utf-16',
+  'utf-16be': 'utf-16',
+  'utf-16le': 'utf-16le',
+  'utf-32': 'ucs-4',
+  'utf-32be': 'ucs-4',
+  'utf-32le': 'ucs-4le',
+  'iso-10646-ucs-4': 'ucs-4',
+  'csucs4': 'ucs-4'
+}
+#}}}
+
+var settings: dict<any>
+
+export def Convert2HTML(line1: number, line2: number) #{{{
+  settings = GetUserSettings()
+
+  if !&diff || settings.diff_one_file #{{{
+    if line2 >= line1
+      g:html_start_line = line1
+      g:html_end_line = line2
     else
-      let g:html_start_line = a:line2
-      let g:html_end_line = a:line1
+      g:html_start_line = line2
+      g:html_end_line = line1
     endif
-    runtime syntax/2html.vim "}}}
-  else "{{{
-    let win_list = []
-    let buf_list = []
-    windo if &diff | call add(win_list, winbufnr(0)) | endif
-    let s:settings.whole_filler = 1
-    let g:html_diff_win_num = 0
+    runtime syntax/2html.vim #}}}
+  else #{{{
+    var win_list = range(1, winnr('$'))
+      ->filter((_, w) => getwinvar(w, '&diff'))
+      ->mapnew((_, w) => winbufnr(w))
+    var buf_list: list<number>
+    settings.whole_filler = 1
+    g:html_diff_win_num = 0
     for window in win_list
-      " switch to the next buffer to convert
-      exe ":" .. bufwinnr(window) .. "wincmd w"
+      # switch to the next buffer to convert
+      win_gotoid(bufwinid(window))
 
-      " figure out whether current charset and encoding will work, if not
-      " default to UTF-8
+      # figure out whether current charset and encoding will work, if not
+      # default to UTF-8
       if !exists('g:html_use_encoding') &&
-           \ (((&l:fileencoding=='' || (&l:buftype!='' && &l:buftype!=?'help'))
-           \      && &encoding!=?s:settings.vim_encoding)
-           \ || &l:fileencoding!='' && &l:fileencoding!=?s:settings.vim_encoding)
+         (((&l:fileencoding == '' || (&l:buftype != '' && &l:buftype !=? 'help'))
+         && &encoding !=? settings.vim_encoding)
+         || &l:fileencoding != '' && &l:fileencoding !=? settings.vim_encoding)
        echohl WarningMsg
        echomsg "TOhtml: mismatched file encodings in Diff buffers, using UTF-8"
        echohl None
-       let s:settings.vim_encoding = 'utf-8'
-       let s:settings.encoding = 'UTF-8'
+       settings.vim_encoding = 'utf-8'
+       settings.encoding = 'UTF-8'
       endif
 
-      " set up for diff-mode conversion
-      let g:html_start_line = 1
-      let g:html_end_line = line('$')
-      let g:html_diff_win_num += 1
+      # set up for diff-mode conversion
+      g:html_start_line = 1
+      g:html_end_line = line('$')
+      g:html_diff_win_num += 1
 
-      " convert this file
+      # convert this file
       runtime syntax/2html.vim
 
-      " remember the HTML buffer for later combination
-      call add(buf_list, bufnr('%'))
+      # remember the HTML buffer for later combination
+      add(buf_list, bufnr('%'))
     endfor
     unlet g:html_diff_win_num
-    call tohtml#Diff2HTML(win_list, buf_list)
-  endif "}}}
-
+    Diff2HTML(win_list, buf_list)
+  endif #}}}
   unlet g:html_start_line
   unlet g:html_end_line
-  unlet s:settings
-endfunc "}}}
+  settings = null_dict
+enddef #}}}
 
-func! tohtml#Diff2HTML(win_list, buf_list) "{{{
-  let xml_line = ""
-  let tag_close = '>'
+export def Diff2HTML(win_list: list<number>, buf_list: list<number>) #{{{
+  var xml_line = ''
+  var tag_close = '>'
 
-  let s:old_paste = &paste
+  var old_paste = &paste
   set paste
-  let s:old_magic = &magic
+  var old_magic = &magic
   set magic
 
-  let html = []
-  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 .. "\"?>"
+  var html = []
+  var style: list<string>
+  var body_line: string
+  var body_end_line: string
+  var body_line_num: number
+  var html5 = 0
+  if !settings.no_doc
+    if settings.use_xhtml
+      if settings.encoding != ''
+       xml_line = "<?xml version=\"1.0\" encoding=\"" .. settings.encoding .. "\"?>"
       else
-       let xml_line = "<?xml version=\"1.0\"?>"
+       xml_line = "<?xml version=\"1.0\"?>"
       endif
-      let tag_close = ' />'
+      tag_close = ' />'
     endif
 
-    let style = [s:settings.use_xhtml ? "" : '-->']
-    let body_line = ''
+    style = [settings.use_xhtml ? '' : '-->']
 
-    let s:html5 = 0
-    if s:settings.use_xhtml
-      call add(html, xml_line)
+    if settings.use_xhtml
+      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
+    if settings.use_xhtml
+      add(html, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">")
+      add(html, '<html xmlns="http://www.w3.org/1999/xhtml">')
+    elseif settings.use_css && !settings.no_pre
+      add(html, "<!DOCTYPE html>")
+      add(html, '<html>')
+      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>')
+      add(html, '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"')
+      add(html, '  "http://www.w3.org/TR/html4/loose.dtd">')
+      add(html, '<html>')
     endif
-    call add(html, '<head>')
+    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)
+    # include encoding as close to the top as possible, but only if not already
+    # contained in XML information
+    if settings.encoding != '' && !settings.use_xhtml
+      if html5
+       add(html, '<meta charset="' .. settings.encoding .. '"' .. tag_close)
       else
-       call add(html, "<meta http-equiv=\"content-type\" content=\"text/html; charset=" .. s:settings.encoding .. '"' .. tag_close)
+       add(html, "<meta http-equiv=\"content-type\" content=\"text/html; charset=" .. 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();"' : '')..'>')
+    add(html, '<title>diff</title>')
+    add(html, $'<meta name="Generator" content="Vim/{v:version / 100}.{v:version % 100}"{tag_close}')
+    add(html, $'<meta name="plugin-version" content="{g:loaded_2html_plugin}"{tag_close}')
+    add(html, '<meta name="settings" content="' ..
+      join(filter(keys(settings), (_, k) => {
+       var v = settings[k]
+       if type(v) == v:t_number
+         return v != 0
+       elseif type(v) == v:t_bool
+         return !empty(v)
+       else
+         return false
+       endif
+      }), ',') ..
+      ',prevent_copy=' .. settings.prevent_copy ..
+      ',use_input_for_pc=' .. settings.use_input_for_pc ..
+      '"' .. tag_close)
+    add(html, '<meta name="colorscheme" content="' ..
+      (exists('g:colors_name')
+      ? g:colors_name
+      : 'none') .. '"' .. tag_close)
+
+    add(html, '</head>')
+    body_line_num = len(html)
+    add(html, '<body' .. (settings.line_ids ? ' onload="JumpToLine();"' : '') .. '>')
   endif
-  call add(html, "<table "..(s:settings.use_css? "" : "border='1' width='100%' ").."id='vimCodeElement"..s:settings.id_suffix.."'>")
+  add(html, "<table " .. (settings.use_css ? "" : "border='1' width='100%' ") .. "id='vimCodeElement" .. settings.id_suffix .. "'>")
 
-  call add(html, '<tr>')
-  for buf in a:win_list
-    call add(html, '<th>'..bufname(buf)..'</th>')
+  add(html, '<tr>')
+  for buf in win_list
+    add(html, '<th>' .. bufname(buf) .. '</th>')
   endfor
-  call add(html, '</tr><tr>')
+  add(html, '</tr><tr>')
 
-  let diff_style_start = 0
-  let insert_index = 0
+  var diff_style_start = 0
+  var insert_index = 0
 
-  for buf in a:buf_list
-    let temp = []
-    exe bufwinnr(buf) .. 'wincmd w'
+  for buf in buf_list
+    var temp = []
+    win_gotoid(bufwinid(buf))
 
-    " If text is folded because of user foldmethod settings, etc. we don't want
-    " to act on everything in a fold by mistake.
+    # If text is folded because of user foldmethod settings, etc. we don't want
+    # to act on everything in a fold by mistake.
     setlocal nofoldenable
 
-    " 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 !s:settings.no_doc
+    # 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 !settings.no_doc
       if body_line == ''
-       1
-       call search('<body')
-       let body_line = getline('.')
-       $
-       call search('</body>', 'b')
-       let s:body_end_line = getline('.')
+       :1
+       search('<body')
+       body_line = getline('.')
+       :$
+       search('</body>', 'b')
+       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>')
+      # Grab the style information. Some of this will be duplicated so only insert
+      # it if it's not already there. {{{
+      :1
+      var style_start = search('^<style\( type="text/css"\)\?>')
+      :1
+      var style_end = search('^</style>')
       if style_start > 0 && style_end > 0
-       let buf_styles = getline(style_start + 1, style_end - 1)
+       var 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
+               diff_style_start = len(style) - 1
              endif
            endif
-           call insert(style, a_style, insert_index)
-           let insert_index += 1
+           insert(style, a_style, insert_index)
+           insert_index += 1
          endif
        endfor
-      endif " }}}
+      endif # }}}
 
-      " everything new will get added before the diff styles so diff highlight
-      " properly overrides normal highlight
+      # 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
+       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
+      # 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 !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
-    let temp = getline(1,'$')
-    " clean out id on the main content container because we already set it on
-    " the table
-    let temp[0] = substitute(temp[0], " id='vimCodeElement[^']*'", "", "")
-    " 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?
-    if !s:settings.no_doc
+    temp = getline(1, '$')
+    # clean out id on the main content container because we already set it on
+    # the table
+    temp[0] = substitute(temp[0], " id='vimCodeElement[^']*'", "", "")
+    # 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?
+    if !settings.no_doc
       normal! 2u
-    elseif !s:settings.no_modeline
+    elseif !settings.no_modeline
       normal! u
     endif
-    if s:settings.use_css
-      call add(html, '<td><div>')
-    elseif s:settings.use_xhtml
-      call add(html, '<td nowrap="nowrap" valign="top"><div>')
+    if settings.use_css
+      add(html, '<td><div>')
+    elseif settings.use_xhtml
+      add(html, '<td nowrap="nowrap" valign="top"><div>')
     else
-      call add(html, '<td nowrap valign="top"><div>')
+      add(html, '<td nowrap valign="top"><div>')
     endif
-    let html += temp
-    call add(html, '</div></td>')
+    html += temp
+    add(html, '</div></td>')
 
-    " Close this buffer
-    " TODO: the comment above says we're going to allow saving the file
-    " later...but here we discard it?
+    # Close this buffer
+    # TODO: the comment above says we're going to allow saving the file
+    # later...but here we discard it?
     quit!
   endfor
 
-  if !s:settings.no_doc
-    let html[body_line_num] = body_line
+  if !settings.no_doc
+    html[body_line_num] = body_line
   endif
 
-  call add(html, '</tr>')
-  call add(html, '</table>')
-  if !s:settings.no_doc
-    call add(html, s:body_end_line)
-    call add(html, '</html>')
+  add(html, '</tr>')
+  add(html, '</table>')
+  if !settings.no_doc
+    add(html, body_end_line)
+    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.
-  if !s:settings.no_modeline
-    call add(html, '<!-- vim: set foldmethod=manual : -->')
+  # 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.
+  if !settings.no_modeline
+    add(html, '<!-- vim: set foldmethod=manual : -->')
   endif
 
-  let i = 1
-  let name = "Diff" .. (s:settings.use_xhtml ? ".xhtml" : ".html")
-  " Find an unused file name if current file name is already in use
+  var i = 1
+  var name = "Diff" .. (settings.use_xhtml ? ".xhtml" : ".html")
+  # Find an unused file name if current file name is already in use
   while filereadable(name)
-    let name = substitute(name, '\d*\.x\?html$', '', '') .. i .. '.' .. fnamemodify(copy(name), ":t:e")
-    let i += 1
+    name = substitute(name, '\d*\.x\?html$', '', '') .. i .. '.' .. fnamemodify(copy(name), ":t:e")
+    i += 1
   endwhile
 
-  let s:ei_sav = &eventignore
+  var ei_sav = &eventignore
   set eventignore+=FileType
-  exe "topleft new " .. name
-  let &eventignore=s:ei_sav
-  unlet s:ei_sav
+  execute "topleft new" name
+  &eventignore = ei_sav
 
   setlocal modifiable
 
-  " just in case some user autocmd creates content in the new buffer, make sure
-  " it is empty before proceeding
-  %d
+  # just in case some user autocmd creates content in the new buffer, make sure
+  # it is empty before proceeding
+  deletebufline(bufnr(), 1, '$')
 
-  " set the fileencoding to match the charset we'll be using
-  let &l:fileencoding=s:settings.vim_encoding
+  # set the fileencoding to match the charset we'll be using
+  &l:fileencoding = settings.vim_encoding
 
-  " According to http://www.w3.org/TR/html4/charset.html#doc-char-set, the byte
-  " order mark is highly recommend on the web when using multibyte encodings. But,
-  " it is not a good idea to include it on UTF-8 files. Otherwise, let Vim
-  " determine when it is actually inserted.
-  if s:settings.vim_encoding == 'utf-8'
+  # According to http://www.w3.org/TR/html4/charset.html#doc-char-set, the byte
+  # order mark is highly recommend on the web when using multibyte encodings. But,
+  # it is not a good idea to include it on UTF-8 files. Otherwise, let Vim
+  # determine when it is actually inserted.
+  if settings.vim_encoding == 'utf-8'
     setlocal nobomb
   else
     setlocal bomb
   endif
 
-  call append(0, html)
+  append(0, html)
 
-  if !s:settings.no_doc
+  if !settings.no_doc
     if len(style) > 0
-      1
-      let style_start = search('^</head>')-1
+      :1
+      var 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
+      var uses_script = settings.dynamic_folds || settings.line_ids
 
-      " insert script closing tag if needed
-      if s:uses_script
-       call append(style_start, [
-             \ '',
-             \ s:settings.use_xhtml ? '//]]>' : '-->',
-             \ "</script>"
-             \ ])
+      # insert script closing tag if needed
+      if uses_script
+       append(style_start, [
+         '',
+         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
-         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;",
-               \ "  }",
-               \ ])
+      # insert javascript to get IDs from line numbers, and to open a fold before
+      # jumping to any lines contained therein
+      if settings.line_ids
+       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 settings.dynamic_folds
+         append(style_start, [
+           "",
+         "  /* navigate upwards in the DOM tree to open all folds containing the line */",
+         "  var node = lineElem;",
+         "  while (node && node.id != 'vimCodeElement" .. 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: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);"
-             \ ])
+      if settings.line_ids
+       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 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';",
-             \  "      }",
-             \  "    }",
-             \  "  }",
-             \ ])
+      # Insert javascript to toggle matching folds open and closed in all windows,
+      # if dynamic folding is active.
+      if settings.dynamic_folds
+       append(style_start, [
+       "  function toggleFold(objID)",
+       "  {",
+       "    for (win_num = 1; win_num <= " .. len(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[' : "<!--"])
+      if uses_script
+       # insert script tag if needed
+       append(style_start, [
+         "<script" .. (html5 ? "" : " type='text/javascript'") .. ">",
+         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 "}}}
+      # 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 settings.use_css
+       append(style_start,
+         ['<style' .. (html5 ? '' : ' type="text/css"') .. '>'] +
+         style +
+         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(win_list)) .. '%; }',
+           'td div { overflow: auto; }',
+           settings.use_xhtml ? '' : '-->',
+           '</style>'
+         ])
+      endif #}}}
     endif
   endif
 
-  let &paste = s:old_paste
-  let &magic = s:old_magic
-endfunc "}}}
-
-" Gets a single user option and sets it in the passed-in Dict, or gives it the
-" default value if the option doesn't actually exist.
-func! tohtml#GetOption(settings, option, default) "{{{
-  if exists('g:html_'..a:option)
-    let a:settings[a:option] = g:html_{a:option}
+  &paste = old_paste
+  &magic = old_magic
+enddef #}}}
+
+# Gets a single user option and sets it in the passed-in Dict, or gives it the
+# default value if the option doesn't actually exist.
+export def GetOption(_settings: dict<any>, option: string, default: any) #{{{
+  _settings[option] = get(g:, $'html_{option}', default)
+enddef #}}}
+
+# returns a Dict containing the values of all user options for 2html, including
+# default values for those not given an explicit value by the user. Discards the
+# html_ prefix of the option for nicer looking code.
+export def GetUserSettings(): dict<any> #{{{
+  if !empty(settings)
+    # just restore the known options if we've already retrieved them
+    return settings
   else
-    let a:settings[a:option] = a:default
-  endif
-endfunc "}}}
-
-" returns a Dict containing the values of all user options for 2html, including
-" default values for those not given an explicit value by the user. Discards the
-" html_ prefix of the option for nicer looking code.
-func! tohtml#GetUserSettings() "{{{
-  if exists('s:settings')
-    " just restore the known options if we've already retrieved them
-    return s:settings
-  else
-    " otherwise figure out which options are set
-    let user_settings = {}
+    # otherwise figure out which options are set
+    var user_settings = {}
 
-    " Define the correct option if the old option name exists and we haven't
-    " already defined the correct one.
+    # Define the correct option if the old option name exists and we haven't
+    # already defined the correct one.
     if exists('g:use_xhtml') && !exists("g:html_use_xhtml")
       echohl WarningMsg
       echomsg "Warning: g:use_xhtml is deprecated, use g:html_use_xhtml"
       echohl None
-      let g:html_use_xhtml = g:use_xhtml
+      g:html_use_xhtml = g:use_xhtml
     endif
 
-    " get current option settings with appropriate defaults {{{
-    call tohtml#GetOption(user_settings,       'no_progress', !has("statusline") )
-    call tohtml#GetOption(user_settings,     'diff_one_file', 0 )
-    call tohtml#GetOption(user_settings,      'number_lines', &number )
-    call tohtml#GetOption(user_settings,          'pre_wrap', &wrap )
-    call tohtml#GetOption(user_settings,           'use_css', 1 )
-    call tohtml#GetOption(user_settings,    'ignore_conceal', 0 )
-    call tohtml#GetOption(user_settings,    'ignore_folding', 0 )
-    call tohtml#GetOption(user_settings,     'dynamic_folds', 0 )
-    call tohtml#GetOption(user_settings,     'no_foldcolumn', user_settings.ignore_folding)
-    call tohtml#GetOption(user_settings,      'hover_unfold', 0 )
-    call tohtml#GetOption(user_settings,            'no_pre', 0 )
-    call tohtml#GetOption(user_settings,            'no_doc', 0 )
-    call tohtml#GetOption(user_settings,          'no_links', 0 )
-    call tohtml#GetOption(user_settings,       'no_modeline', 0 )
-    call tohtml#GetOption(user_settings,        'no_invalid', 0 )
-    call tohtml#GetOption(user_settings,      'whole_filler', 0 )
-    call tohtml#GetOption(user_settings,         'use_xhtml', 0 )
-    call tohtml#GetOption(user_settings,          'line_ids', user_settings.number_lines )
-    call tohtml#GetOption(user_settings, 'use_input_for_pc', 'none')
-    " }}}
-    
-    " override those settings that need it {{{
-
-    " hover opening implies dynamic folding
+    # get current option settings with appropriate defaults {{{
+    GetOption(user_settings,       'no_progress', !has("statusline") )
+    GetOption(user_settings,     'diff_one_file', 0 )
+    GetOption(user_settings,      'number_lines', &number )
+    GetOption(user_settings,          'pre_wrap', &wrap )
+    GetOption(user_settings,           'use_css', 1 )
+    GetOption(user_settings,    'ignore_conceal', 0 )
+    GetOption(user_settings,    'ignore_folding', 0 )
+    GetOption(user_settings,     'dynamic_folds', 0 )
+    GetOption(user_settings,     'no_foldcolumn', user_settings.ignore_folding)
+    GetOption(user_settings,      'hover_unfold', 0 )
+    GetOption(user_settings,            'no_pre', 0 )
+    GetOption(user_settings,            'no_doc', 0 )
+    GetOption(user_settings,          'no_links', 0 )
+    GetOption(user_settings,       'no_modeline', 0 )
+    GetOption(user_settings,        'no_invalid', 0 )
+    GetOption(user_settings,      'whole_filler', 0 )
+    GetOption(user_settings,         'use_xhtml', 0 )
+    GetOption(user_settings,          'line_ids', user_settings.number_lines )
+    GetOption(user_settings, 'use_input_for_pc', 'none')
+    # }}}
+
+    # override those settings that need it {{{
+
+    # hover opening implies dynamic folding
     if user_settings.hover_unfold
-      let user_settings.dynamic_folds = 1
+      user_settings.dynamic_folds = 1
     endif
 
-    " ignore folding overrides dynamic folding
+    # ignore folding overrides dynamic folding
     if user_settings.ignore_folding && user_settings.dynamic_folds
-      let user_settings.dynamic_folds = 0
-      let user_settings.hover_unfold = 0
+      user_settings.dynamic_folds = 0
+      user_settings.hover_unfold = 0
     endif
 
-    " dynamic folding with no foldcolumn implies hover opens
+    # dynamic folding with no foldcolumn implies hover opens
     if user_settings.dynamic_folds && user_settings.no_foldcolumn
-      let user_settings.hover_unfold = 1
+      user_settings.hover_unfold = 1
     endif
 
-    " dynamic folding implies css
+    # dynamic folding implies css
     if user_settings.dynamic_folds
-      let user_settings.use_css = 1
+      user_settings.use_css = 1
     else
-      let user_settings.no_foldcolumn = 1 " won't do anything but for consistency and for the test suite
+      user_settings.no_foldcolumn = 1 # won't do anything but for consistency and for the test suite
     endif
 
-    " if we're not using CSS we cannot use a pre section because <font> tags
-    " aren't allowed inside a <pre> block
+    # if we're not using CSS we cannot use a pre section because <font> tags
+    # aren't allowed inside a <pre> block
     if !user_settings.use_css
-      let user_settings.no_pre = 1
+      user_settings.no_pre = 1
     endif
 
-    " pre_wrap doesn't do anything if not using pre or not using CSS
+    # pre_wrap doesn't do anything if not using pre or not using CSS
     if user_settings.no_pre || !user_settings.use_css
-      let user_settings.pre_wrap = 0
+      user_settings.pre_wrap = 0
     endif
-    "}}}
+    #}}}
 
-    " set up expand_tabs option after all the overrides so we know the
-    " appropriate defaults {{{
+    # set up expand_tabs option after all the overrides so we know the
+    # appropriate defaults #{{{
     if user_settings.no_pre == 0
-      call tohtml#GetOption(user_settings,
-           \ 'expand_tabs',
-           \ &expandtab || &ts != 8 || &vts != '' || user_settings.number_lines ||
-           \   (user_settings.dynamic_folds && !user_settings.no_foldcolumn))
+      GetOption(user_settings,
+       'expand_tabs',
+       &expandtab || &ts != 8 || &vts != '' || user_settings.number_lines ||
+       (user_settings.dynamic_folds && !user_settings.no_foldcolumn))
     else
-      let user_settings.expand_tabs = 1
+      user_settings.expand_tabs = 1
     endif
-    " }}}
-
-    " textual options
-    if exists("g:html_use_encoding") "{{{
-      " user specified the desired MIME charset, figure out proper
-      " 'fileencoding' from it or warn the user if we cannot
-      let user_settings.encoding = g:html_use_encoding
-      let user_settings.vim_encoding = tohtml#EncodingFromCharset(g:html_use_encoding)
+    # }}}
+
+    # textual options
+    if exists("g:html_use_encoding") #{{{
+      # user specified the desired MIME charset, figure out proper
+      # 'fileencoding' from it or warn the user if we cannot
+      user_settings.encoding = g:html_use_encoding
+      user_settings.vim_encoding = EncodingFromCharset(g:html_use_encoding)
       if user_settings.vim_encoding == ''
        echohl WarningMsg
-       echomsg "TOhtml: file encoding for"
-             \ g:html_use_encoding
-             \ "unknown, please set 'fileencoding'"
+       echomsg "TOhtml: file encoding for" g:html_use_encoding "unknown, please set 'fileencoding'"
        echohl None
       endif
     else
-      " Figure out proper MIME charset from 'fileencoding' if possible
-      if &l:fileencoding != '' 
-       " If the buffer is not a "normal" type, the 'fileencoding' value may not
-       " be trusted; since the buffer should not be written the fileencoding is
-       " not intended to be used.
-       if &l:buftype=='' || &l:buftype==?'help'
-         let user_settings.vim_encoding = &l:fileencoding
-         call tohtml#CharsetFromEncoding(user_settings)
+      # Figure out proper MIME charset from 'fileencoding' if possible
+      if &l:fileencoding != ''
+       # If the buffer is not a "normal" type, the 'fileencoding' value may not
+       # be trusted; since the buffer should not be written the fileencoding is
+       # not intended to be used.
+       if &l:buftype == '' || &l:buftype ==? 'help'
+         user_settings.vim_encoding = &l:fileencoding
+         CharsetFromEncoding(user_settings)
        else
-         let user_settings.encoding = '' " trigger detection using &encoding
+         user_settings.encoding = '' # trigger detection using &encoding
        endif
       endif
 
-      " else from 'encoding' if possible
+      # else from 'encoding' if possible
       if &l:fileencoding == '' || user_settings.encoding == ''
-       let user_settings.vim_encoding = &encoding
-       call tohtml#CharsetFromEncoding(user_settings)
+       user_settings.vim_encoding = &encoding
+       CharsetFromEncoding(user_settings)
       endif
 
-      " else default to UTF-8 and warn user
+      # else default to UTF-8 and warn user
       if user_settings.encoding == ''
-       let user_settings.vim_encoding = 'utf-8'
-       let user_settings.encoding = 'UTF-8'
+       user_settings.vim_encoding = 'utf-8'
+       user_settings.encoding = 'UTF-8'
        echohl WarningMsg
        echomsg "TOhtml: couldn't determine MIME charset, using UTF-8"
        echohl None
       endif
-    endif "}}}
-
-    " Default to making nothing uncopyable, because we default to
-    " not-standards way of doing things, and also because Microsoft Word and
-    " others paste the <input> elements anyway.
-    "
-    " html_prevent_copy only has an effect when using CSS.
-    "
-    " All options:
-    "    f - fold column
-    "    n - line numbers (also within fold text)
-    "    t - fold text
-    "    d - diff filler
-    "    c - concealed text (reserved future)
-    "    l - listchars (reserved possible future)
-    "    s - signs (reserved possible future)
-    "
-    " Normal text is always selectable.
-    let user_settings.prevent_copy = ""
+    endif #}}}
+
+    # Default to making nothing uncopyable, because we default to
+    # not-standards way of doing things, and also because Microsoft Word and
+    # others paste the <input> elements anyway.
+    #
+    # html_prevent_copy only has an effect when using CSS.
+    #
+    # All options:
+    #    f - fold column
+    #    n - line numbers (also within fold text)
+    #    t - fold text
+    #    d - diff filler
+    #    c - concealed text (reserved future)
+    #    l - listchars (reserved possible future)
+    #    s - signs (reserved possible future)
+    #
+    # Normal text is always selectable.
+    user_settings.prevent_copy = ""
     if user_settings.use_css
       if exists("g:html_prevent_copy")
        if user_settings.dynamic_folds && !user_settings.no_foldcolumn && g:html_prevent_copy =~# 'f'
-         let user_settings.prevent_copy ..= 'f'
+         user_settings.prevent_copy ..= 'f'
        endif
        if user_settings.number_lines && g:html_prevent_copy =~# 'n'
-         let user_settings.prevent_copy ..= 'n'
+         user_settings.prevent_copy ..= 'n'
        endif
        if &diff && g:html_prevent_copy =~# 'd'
-         let user_settings.prevent_copy ..= 'd'
+         user_settings.prevent_copy ..= 'd'
        endif
        if !user_settings.ignore_folding && g:html_prevent_copy =~# 't'
-         let user_settings.prevent_copy ..= 't'
+         user_settings.prevent_copy ..= 't'
        endif
       else
-       let user_settings.prevent_copy = ""
+       user_settings.prevent_copy = ""
       endif
     endif
     if empty(user_settings.prevent_copy)
-      let user_settings.no_invalid = 0
+      user_settings.no_invalid = 0
     endif
 
-    " enforce valid values for use_input_for_pc
+    # enforce valid values for use_input_for_pc
     if user_settings.use_input_for_pc !~# 'fallback\|none\|all'
-      let user_settings.use_input_for_pc = 'none'
+      user_settings.use_input_for_pc = 'none'
       echohl WarningMsg
       echomsg '2html: "' .. g:html_use_input_for_pc .. '" is not valid for g:html_use_input_for_pc'
       echomsg '2html: defaulting to "' .. user_settings.use_input_for_pc .. '"'
@@ -885,67 +889,64 @@ func! tohtml#GetUserSettings() "{{{
     endif
 
     if exists('g:html_id_expr')
-      let user_settings.id_suffix = eval(g:html_id_expr)
+      user_settings.id_suffix = eval(g:html_id_expr)
       if user_settings.id_suffix !~ '^[-_:.A-Za-z0-9]*$'
        echohl WarningMsg
        echomsg '2html: g:html_id_expr evaluated to invalid string for HTML id attributes'
        echomsg '2html: Omitting user-specified suffix'
        echohl None
        sleep 3
-       let user_settings.id_suffix=""
+       user_settings.id_suffix = ""
       endif
     else
-      let user_settings.id_suffix=""
+      user_settings.id_suffix = ""
     endif
 
-    " TODO: font
+    # TODO: font
 
     return user_settings
   endif
-endfunc "}}}
+enddef #}}}
 
-" get the proper HTML charset name from a Vim encoding option.
-function! tohtml#CharsetFromEncoding(settings) "{{{
-  let l:vim_encoding = a:settings.vim_encoding
-  if exists('g:html_charset_override') && has_key(g:html_charset_override, l:vim_encoding)
-    let a:settings.encoding = g:html_charset_override[l:vim_encoding]
+# get the proper HTML charset name from a Vim encoding option.
+export def CharsetFromEncoding(_settings: dict<any>) #{{{
+  var vim_encoding = _settings.vim_encoding
+  if exists('g:html_charset_override') && has_key(g:html_charset_override, vim_encoding)
+    _settings.encoding = g:html_charset_override[vim_encoding]
   else
-    if l:vim_encoding =~ '^8bit\|^2byte'
-      " 8bit- and 2byte- prefixes are to indicate encodings available on the
-      " system that Vim will convert with iconv(), look up just the encoding name,
-      " not Vim's prefix.
-      let l:vim_encoding = substitute(l:vim_encoding, '^8bit-\|^2byte-', '', '')
+    if vim_encoding =~ '^8bit\|^2byte'
+      # 8bit- and 2byte- prefixes are to indicate encodings available on the
+      # system that Vim will convert with iconv(), look up just the encoding name,
+      # not Vim's prefix.
+      vim_encoding = substitute(vim_encoding, '^8bit-\|^2byte-', '', '')
     endif
-    if has_key(g:tohtml#encoding_to_charset, l:vim_encoding)
-      let a:settings.encoding = g:tohtml#encoding_to_charset[l:vim_encoding]
+    if has_key(encoding_to_charset, vim_encoding)
+      _settings.encoding = encoding_to_charset[vim_encoding]
     else
-      let a:settings.encoding = ""
+      _settings.encoding = ""
     endif
   endif
-  if a:settings.encoding != ""
-    let l:vim_encoding = tohtml#EncodingFromCharset(a:settings.encoding)
-    if l:vim_encoding != ""
-      " if the Vim encoding to HTML encoding conversion is set up (by default or
-      " by the user) to convert to a different encoding, we need to also change
-      " the Vim encoding of the new buffer
-      let a:settings.vim_encoding = l:vim_encoding
+  if _settings.encoding != ""
+    var vim_encoding2 = EncodingFromCharset(_settings.encoding)
+    if vim_encoding2 != ""
+      # if the Vim encoding to HTML encoding conversion is set up (by default or
+      # by the user) to convert to a different encoding, we need to also change
+      # the Vim encoding of the new buffer
+      _settings.vim_encoding = vim_encoding2
     endif
   endif
-endfun "}}}
-
-" Get the proper Vim encoding option setting from an HTML charset name.
-function! tohtml#EncodingFromCharset(encoding) "{{{
-  if exists('g:html_encoding_override') && has_key(g:html_encoding_override, a:encoding)
-    return g:html_encoding_override[a:encoding]
-  elseif has_key(g:tohtml#charset_to_encoding, tolower(a:encoding))
-    return g:tohtml#charset_to_encoding[tolower(a:encoding)]
+enddef #}}}
+
+# Get the proper Vim encoding option setting from an HTML charset name.
+export def EncodingFromCharset(encoding: string): string #{{{
+  if exists('g:html_encoding_override') && has_key(g:html_encoding_override, encoding)
+    return g:html_encoding_override[encoding]
+  elseif has_key(charset_to_encoding, tolower(encoding))
+    return charset_to_encoding[tolower(encoding)]
   else
     return ""
   endif
-endfun "}}}
-
-let &cpo = s:cpo_sav
-unlet s:cpo_sav
+enddef #}}}
 
-" Make sure any patches will probably use consistent indent
-"   vim: ts=8 sw=2 sts=2 noet fdm=marker
+# Make sure any patches will probably use consistent indent
+#   vim: ts=8 sw=2 sts=2 noet fdm=marker
index 9a69c312ef51f8869bc88c9d175d159832962316..ca5daf1b73413de2e15c211f6bd2493a6c18ed41 100644 (file)
@@ -402,9 +402,8 @@ the desired value, or restored to their default by removing the variable using
 Remarks:
 - Some truly ancient browsers may not show the background colors.
 - From most browsers you can also print the file (in color)!
-- The latest TOhtml may actually work with older versions of Vim, but some
-  features such as conceal support will not function, and the colors may be
-  incorrect for an old Vim without GUI support compiled in.
+- Colors may be incorrect for Vim without GUI support compiled in.
+- The latest TOhtml requires Vim 9.1 or later.
 
 Here is an example how to run the script over all .c and .h files from a
 Unix shell: >
index 7c2ace84e76a807eadf8b77e8d7d79d525f405d6..29882b4d5592c7ccacc6bfbb2852482793f4e709 100644 (file)
@@ -52622,6 +52622,8 @@ Other ~
 - New "leadtab" value for the 'listchars' setting.
 - Improved |:set+=|, |:set^=| and |:set-=| handling of comma-separated "key:value"
   pairs individually (e.g. 'listchars', 'fillchars', 'diffopt').
+- The |:TOhtml| command has been completely rewritten in Vim9 script, improving
+  performance and maintainability.
 - |system()| and |systemlist()| functions accept a list as first argument,
   bypassing the shell completely.
 - Allow mouse clickable regions in the 'statusline', 'tabline' and the
index 56eb2c15bfbe8d26da010406f4e7784d764813e2..536bc910479f8915e06cb8cd303493269d3c9a4e 100644 (file)
-" Vim plugin for converting a syntax highlighted file to HTML.
-" Maintainer: Ben Fritz <fritzophrenic@gmail.com>
-" Last Change: 2023 Sep 07
-"
-" The core of the code is in $VIMRUNTIME/autoload/tohtml.vim and
-" $VIMRUNTIME/syntax/2html.vim
-"
+vim9script
+# Vim plugin for converting a syntax highlighted file to HTML.
+# Maintainer: Ben Fritz <fritzophrenic@gmail.com>
+# Last Change: 2026 Apr 9
+#
+# The core of the code is in $VIMRUNTIME/autoload/tohtml.vim and
+# $VIMRUNTIME/syntax/2html.vim
+#
 if exists('g:loaded_2html_plugin')
   finish
 endif
-let g:loaded_2html_plugin = 'vim9.0_v2'
+g:loaded_2html_plugin = 'vim9.2_v1'
 
-"
-" Changelog: {{{
-"   9.0_v2  (this version): - Warn if using deprecated g:use_xhtml option
-"                           - Change default g:html_use_input_for_pc to "none"
-"                             instead of "fallback". All modern browsers support
-"                             the "user-select: none" and "content:" CSS
-"                             properties so the older method relying on extra
-"                             markup and unspecified browser/app clipboard
-"                             handling is only needed in rare special cases.
-"                           - Fix SourceForge issue #33: generate diff filler
-"                             correctly when new lines have been added to or
-"                             removed from end of buffer.
-"                           - Fix SourceForge issue #32/Vim Github issue #8547:
-"                             use translated highlight ID for styling the
-"                             special-use group names (e.g. LineNr) used
-"                             directly by name in the 2html processing.
-"                           - Fix SourceForge issue #26, refactoring to use
-"                             :let-heredoc style string assignment and
-"                             additional fixes for ".." vs. "." style string
-"                             concatenation. Requires Vim v8.1.1354 or higher.
-"   9.0_v1  (Vim 9.0.1275): - Implement g:html_no_doc and g:html_no_modeline
-"                             for diff mode. Add tests.
-"           (Vim 9.0.1122): NOTE: no version string update for this version!
-"                           - Bugfix for variable name in g:html_no_doc
-"           (Vim 9.0.0819): NOTE: no version string update for this version!
-"                           - Add options g:html_no_doc, g:html_no_lines,
-"                             and g:html_no_modeline (partially included in Vim
-"                             runtime prior to version string update).
-"                           - Updates for new Vim9 string append style (i.e. use
-"                             ".." instead of "."). Requires Vim version
-"                             8.1.1114 or higher.
-"
-"   8.1 updates: {{{
-"   8.1_v2  (Vim 8.1.2312): - Fix SourceForge issue #19: fix calculation of tab
-"                             stop position to use in expanding a tab, when that
-"                             tab occurs after a syntax match which in turn
-"                             comes after previously expanded tabs.
-"                           - Set eventignore while splitting a window for the
-"                             destination file to ignore FileType events;
-"                             speeds up processing when the destination file
-"                             already exists and HTML highlight takes too long.
-"                           - Fix SourceForge issue #20: progress bar could not be
-"                             seen when DiffDelete background color matched
-"                             StatusLine background color. Added TOhtmlProgress
-"                             highlight group for manual user override, but
-"                             calculate it to be visible compared to StatusLine
-"                             by default.
-"                           - Fix SourceForge issue #1: Remove workaround for old
-"                             browsers which don't support 'ch' CSS unit, since
-"                             all modern browsers, including IE>=9, support it.
-"                           - Fix SourceForge issue #10: support termguicolors
-"                           - Fix SourceForge issue #21: default to using
-"                             generated content instead of <input> tags for
-"                             uncopyable text, so that text is correctly
-"                             prevented from being copied in chrome. Use
-"                             g:html_use_input_for_pc option to control the
-"                             method used.
-"                           - Switch to HTML5 to allow using vnu as a validator
-"                             in unit test.
-"                           - Fix fallback sizing of <input> tags for browsers
-"                             without "ch" support.
-"                           - Fix cursor on unselectable diff filler text.
-"   8.1_v1  (Vim 8.1.0528): - Fix SourceForge issue #6: Don't generate empty
-"                             script tag.
-"                           - Fix SourceForge issue #5: javascript should
-"                             declare variables with "var".
-"                           - Fix SourceForge issue #13: errors thrown sourcing
-"                             2html.vim directly when plugins not loaded.
-"                           - Fix SourceForge issue #16: support 'vartabstop'.
-"}}}
-"
-"   7.4 updates: {{{
-"   7.4_v2  (Vim 7.4.0899): Fix error raised when converting a diff containing
-"                           an empty buffer. Jan Stocker: allow g:html_font to
-"                           take a list so it is easier to specfiy fallback
-"                           fonts in the generated CSS.
-"   7.4_v1  (Vim 7.4.0000): Fix modeline mangling for new "Vim:" format, and
-"                          also for version-specific modelines like "vim>703:".
-"}}}
-"
-"   7.3 updates: {{{
-"   7.3_v14 (Vim 7.3.1246): Allow suppressing line number anchors using
-"                          g:html_line_ids=0. Allow customizing
-"                          important IDs (like line IDs and fold IDs) using
-"                          g:html_id_expr evaluated when the buffer conversion
-"                          is started.
-"   7.3_v13 (Vim 7.3.1088): Keep foldmethod at manual in the generated file and
-"                          insert modeline to set it to manual.
-"                          Fix bug: diff mode with 2 unsaved buffers creates a
-"                          duplicate of one buffer instead of including both.
-"                          Add anchors to each line so you can put '#L123'
-"                          or '#123' at the end of the URL to jump to line 123
-"                          (idea by Andy Spencer). Add javascript to open folds
-"                          to show the anchor being jumped to if it is hidden.
-"                          Fix XML validation error: &nsbp; not part of XML.
-"                          Allow TOhtml to chain together with other commands
-"                          using |.
-"   7.3_v12 (Vim 7.3.0616): Fix modeline mangling to also work for when multiple
-"                          highlight groups make up the start-of-modeline text.
-"                          Improve render time of page with uncopyable regions
-"                          by not using one-input-per-char. Change name of
-"                          uncopyable option from html_unselectable to
-"                          html_prevent_copy. Added html_no_invalid option and
-"                          default to inserting invalid markup for uncopyable
-"                          regions to prevent MS Word from pasting undeletable
-"                          <input> elements. Fix 'cpo' handling (Thilo Six).
-"               7.3_v12b1: Add html_unselectable option. Rework logic to
-"                          eliminate post-processing substitute commands in
-"                          favor of doing the work up front. Remove unnecessary
-"                          special treatment of 'LineNr' highlight group. Minor
-"                          speed improvements. Fix modeline mangling in
-"                          generated output so it works for text in the first
-"                          column. Fix missing line number and fold column in
-"                          diff filler lines. Fix that some fonts have a 1px
-"                          gap (using a dirty hack, improvements welcome). Add
-"                          "colorscheme" meta tag. Does NOT include support for
-"                          the new default foldtext added in v11, as the patch
-"                          adding it has not yet been included in Vim.
-"   7.3_v11 ( unreleased ): Support new default foldtext from patch by Christian
-"                          Brabandt in
-"                          http://groups.google.com/d/topic/vim_dev/B6FSGfq9VoI/discussion.
-"                          This patch has not yet been included in Vim, thus
-"                          these changes are removed in the next version.
-"   7.3_v10 (Vim 7.3.0227): Fix error E684 when converting a range wholly inside
-"                          multiple nested folds with dynamic folding on.
-"                          Also fix problem with foldtext in this situation.
-"   7.3_v9  (Vim 7.3.0170): Add html_pre_wrap option active with html_use_css
-"                          and without html_no_pre, default value same as
-"                          'wrap' option, (Andy Spencer). Don't use
-"                          'fileencoding' for converted document encoding if
-"                          'buftype' indicates a special buffer which isn't
-"                          written.
-"   7.3_v8  (Vim 7.3.0100): Add html_expand_tabs option to allow leaving tab
-"                          characters in generated output (Andy Spencer).
-"                          Escape text that looks like a modeline so Vim
-"                          doesn't use anything in the converted HTML as a
-"                          modeline. Bugfixes: Fix folding when a fold starts
-"                          before the conversion range. Remove fold column when
-"                          there are no folds.
-"   7.3_v7  (Vim 7-3-0063): see betas released on vim_dev below:
-"                7.3_v7b3: Fixed bug, convert Unicode to UTF-8 all the way.
-"                7.3_v7b2: Remove automatic detection of encodings that are not
-"                          supported by all major browsers according to
-"                          http://wiki.whatwg.org/wiki/Web_Encodings and
-"                          convert to UTF-8 for all Unicode encodings. Make
-"                          HTML encoding to Vim encoding detection be
-"                          case-insensitive for built-in pairs.
-"                7.3_v7b1: Remove use of setwinvar() function which cannot be
-"                          called in restricted mode (Andy Spencer). Use
-"                          'fencoding' instead of 'encoding' to determine by
-"                          charset, and make sure the 'fenc' of the generated
-"                          file matches its indicated charset. Add charsets for
-"                          all of Vim's natively supported encodings.
-"   7.3_v6  (Vim 7.3.0000): Really fix bug with 'nowrapscan', 'magic' and other
-"                          user settings interfering with diff mode generation,
-"                          trailing whitespace (e.g. line number column) when
-"                          using html_no_pre, and bugs when using
-"                          html_hover_unfold.
-"   7.3_v5  ( unreleased ): Fix bug with 'nowrapscan' and also with out-of-sync
-"                          folds in diff mode when first line was folded.
-"   7.3_v4  (Vim 7.3.0000): Bugfixes, especially for xhtml markup, and diff mode
-"   7.3_v3  (Vim 7.3.0000): Refactor option handling and make html_use_css
-"                          default to true when not set to anything. Use strict
-"                          doctypes where possible. Rename use_xhtml option to
-"                          html_use_xhtml for consistency. Use .xhtml extension
-"                          when using this option. Add meta tag for settings.
-"   7.3_v2  (Vim 7.3.0000): Fix syntax highlighting in diff mode to use both the
-"                          diff colors and the normal syntax colors
-"   7.3_v1  (Vim 7.3.0000): Add conceal support and meta tags in output
-"}}}
-"}}}
+#
+# Changelog: {{{
+#   9.2_v1  (this version): - mao-yining: Convert main script to vim9script
+#                             (SourceForge issue #28/Vim Github PR #19915).
+#
+#   Unspecified version:    - Inherit links colors from colorscheme styles for
+#                             auto-generated links in HTML output (Vim Github
+#                             issue #10191).
+#
+#   9.0 updates: {{{
+#   9.0_v2  (Vim 9.0.1885): - Warn if using deprecated g:use_xhtml option
+#                           - Change default g:html_use_input_for_pc to "none"
+#                             instead of "fallback". All modern browsers support
+#                             the "user-select: none" and "content:" CSS
+#                             properties so the older method relying on extra
+#                             markup and unspecified browser/app clipboard
+#                             handling is only needed in rare special cases.
+#                           - Fix SourceForge issue #33: generate diff filler
+#                             correctly when new lines have been added to or
+#                             removed from end of buffer.
+#                           - Fix SourceForge issue #32/Vim Github issue #8547:
+#                             use translated highlight ID for styling the
+#                             special-use group names (e.g. LineNr) used
+#                             directly by name in the 2html processing.
+#                           - Fix SourceForge issue #26, refactoring to use
+#                             :let-heredoc style string assignment and
+#                             additional fixes for ".." vs. "." style string
+#                             concatenation. Requires Vim v8.1.1354 or higher.
+#   9.0_v1  (Vim 9.0.1275): - Implement g:html_no_doc and g:html_no_modeline
+#                             for diff mode. Add tests.
+#           (Vim 9.0.1122): NOTE: no version string update for this version!
+#                           - Bugfix for variable name in g:html_no_doc
+#           (Vim 9.0.0819): NOTE: no version string update for this version!
+#                           - Add options g:html_no_doc, g:html_no_lines,
+#                             and g:html_no_modeline (partially included in Vim
+#                             runtime prior to version string update).
+#                           - Updates for new Vim9 string append style (i.e. use
+#                             ".." instead of "."). Requires Vim version
+#                             8.1.1114 or higher.
+#}}}
+#
+#   8.1 updates: {{{
+#   8.1_v2  (Vim 8.1.2312): - Fix SourceForge issue #19: fix calculation of tab
+#                             stop position to use in expanding a tab, when that
+#                             tab occurs after a syntax match which in turn
+#                             comes after previously expanded tabs.
+#                           - Set eventignore while splitting a window for the
+#                             destination file to ignore FileType events;
+#                             speeds up processing when the destination file
+#                             already exists and HTML highlight takes too long.
+#                           - Fix SourceForge issue #20: progress bar could not be
+#                             seen when DiffDelete background color matched
+#                             StatusLine background color. Added TOhtmlProgress
+#                             highlight group for manual user override, but
+#                             calculate it to be visible compared to StatusLine
+#                             by default.
+#                           - Fix SourceForge issue #1: Remove workaround for old
+#                             browsers which don't support 'ch' CSS unit, since
+#                             all modern browsers, including IE>=9, support it.
+#                           - Fix SourceForge issue #10: support termguicolors
+#                           - Fix SourceForge issue #21: default to using
+#                             generated content instead of <input> tags for
+#                             uncopyable text, so that text is correctly
+#                             prevented from being copied in chrome. Use
+#                             g:html_use_input_for_pc option to control the
+#                             method used.
+#                           - Switch to HTML5 to allow using vnu as a validator
+#                             in unit test.
+#                           - Fix fallback sizing of <input> tags for browsers
+#                             without "ch" support.
+#                           - Fix cursor on unselectable diff filler text.
+#   8.1_v1  (Vim 8.1.0528): - Fix SourceForge issue #6: Don't generate empty
+#                             script tag.
+#                           - Fix SourceForge issue #5: javascript should
+#                             declare variables with "var".
+#                           - Fix SourceForge issue #13: errors thrown sourcing
+#                             2html.vim directly when plugins not loaded.
+#                           - Fix SourceForge issue #16: support 'vartabstop'.
+#}}}
+#
+#   7.4 updates: {{{
+#   7.4_v2  (Vim 7.4.0899): Fix error raised when converting a diff containing
+#                           an empty buffer. Jan Stocker: allow g:html_font to
+#                           take a list so it is easier to specfiy fallback
+#                           fonts in the generated CSS.
+#   7.4_v1  (Vim 7.4.0000): Fix modeline mangling for new "Vim:" format, and
+#                          also for version-specific modelines like "vim>703:".
+#}}}
+#
+#   7.3 updates: {{{
+#   7.3_v14 (Vim 7.3.1246): Allow suppressing line number anchors using
+#                          g:html_line_ids=0. Allow customizing
+#                          important IDs (like line IDs and fold IDs) using
+#                          g:html_id_expr evaluated when the buffer conversion
+#                          is started.
+#   7.3_v13 (Vim 7.3.1088): Keep foldmethod at manual in the generated file and
+#                          insert modeline to set it to manual.
+#                          Fix bug: diff mode with 2 unsaved buffers creates a
+#                          duplicate of one buffer instead of including both.
+#                          Add anchors to each line so you can put '#L123'
+#                          or '#123' at the end of the URL to jump to line 123
+#                          (idea by Andy Spencer). Add javascript to open folds
+#                          to show the anchor being jumped to if it is hidden.
+#                          Fix XML validation error: &nsbp; not part of XML.
+#                          Allow TOhtml to chain together with other commands
+#                          using |.
+#   7.3_v12 (Vim 7.3.0616): Fix modeline mangling to also work for when multiple
+#                          highlight groups make up the start-of-modeline text.
+#                          Improve render time of page with uncopyable regions
+#                          by not using one-input-per-char. Change name of
+#                          uncopyable option from html_unselectable to
+#                          html_prevent_copy. Added html_no_invalid option and
+#                          default to inserting invalid markup for uncopyable
+#                          regions to prevent MS Word from pasting undeletable
+#                          <input> elements. Fix 'cpo' handling (Thilo Six).
+#               7.3_v12b1: Add html_unselectable option. Rework logic to
+#                          eliminate post-processing substitute commands in
+#                          favor of doing the work up front. Remove unnecessary
+#                          special treatment of 'LineNr' highlight group. Minor
+#                          speed improvements. Fix modeline mangling in
+#                          generated output so it works for text in the first
+#                          column. Fix missing line number and fold column in
+#                          diff filler lines. Fix that some fonts have a 1px
+#                          gap (using a dirty hack, improvements welcome). Add
+#                          "colorscheme" meta tag. Does NOT include support for
+#                          the new default foldtext added in v11, as the patch
+#                          adding it has not yet been included in Vim.
+#   7.3_v11 ( unreleased ): Support new default foldtext from patch by Christian
+#                          Brabandt in
+#                          http://groups.google.com/d/topic/vim_dev/B6FSGfq9VoI/discussion.
+#                          This patch has not yet been included in Vim, thus
+#                          these changes are removed in the next version.
+#   7.3_v10 (Vim 7.3.0227): Fix error E684 when converting a range wholly inside
+#                          multiple nested folds with dynamic folding on.
+#                          Also fix problem with foldtext in this situation.
+#   7.3_v9  (Vim 7.3.0170): Add html_pre_wrap option active with html_use_css
+#                          and without html_no_pre, default value same as
+#                          'wrap' option, (Andy Spencer). Don't use
+#                          'fileencoding' for converted document encoding if
+#                          'buftype' indicates a special buffer which isn't
+#                          written.
+#   7.3_v8  (Vim 7.3.0100): Add html_expand_tabs option to allow leaving tab
+#                          characters in generated output (Andy Spencer).
+#                          Escape text that looks like a modeline so Vim
+#                          doesn't use anything in the converted HTML as a
+#                          modeline. Bugfixes: Fix folding when a fold starts
+#                          before the conversion range. Remove fold column when
+#                          there are no folds.
+#   7.3_v7  (Vim 7-3-0063): see betas released on vim_dev below:
+#                7.3_v7b3: Fixed bug, convert Unicode to UTF-8 all the way.
+#                7.3_v7b2: Remove automatic detection of encodings that are not
+#                          supported by all major browsers according to
+#                          http://wiki.whatwg.org/wiki/Web_Encodings and
+#                          convert to UTF-8 for all Unicode encodings. Make
+#                          HTML encoding to Vim encoding detection be
+#                          case-insensitive for built-in pairs.
+#                7.3_v7b1: Remove use of setwinvar() function which cannot be
+#                          called in restricted mode (Andy Spencer). Use
+#                          'fencoding' instead of 'encoding' to determine by
+#                          charset, and make sure the 'fenc' of the generated
+#                          file matches its indicated charset. Add charsets for
+#                          all of Vim's natively supported encodings.
+#   7.3_v6  (Vim 7.3.0000): Really fix bug with 'nowrapscan', 'magic' and other
+#                          user settings interfering with diff mode generation,
+#                          trailing whitespace (e.g. line number column) when
+#                          using html_no_pre, and bugs when using
+#                          html_hover_unfold.
+#   7.3_v5  ( unreleased ): Fix bug with 'nowrapscan' and also with out-of-sync
+#                          folds in diff mode when first line was folded.
+#   7.3_v4  (Vim 7.3.0000): Bugfixes, especially for xhtml markup, and diff mode
+#   7.3_v3  (Vim 7.3.0000): Refactor option handling and make html_use_css
+#                          default to true when not set to anything. Use strict
+#                          doctypes where possible. Rename use_xhtml option to
+#                          html_use_xhtml for consistency. Use .xhtml extension
+#                          when using this option. Add meta tag for settings.
+#   7.3_v2  (Vim 7.3.0000): Fix syntax highlighting in diff mode to use both the
+#                          diff colors and the normal syntax colors
+#   7.3_v1  (Vim 7.3.0000): Add conceal support and meta tags in output
+#}}}
+#}}}
+#
+# TODO: {{{
+#   * Check the issue tracker:
+#     https://sourceforge.net/p/vim-tohtml/issues/search/?q=%21status%3Aclosed
+#   * Options for generating the CSS in external style sheets. New :TOcss
+#     command to convert the current color scheme into a (mostly) generic CSS
+#     stylesheet which can be re-used. Alternate stylesheet support? Good start
+#     by Erik Falor
+#     ( https://groups.google.com/d/topic/vim_use/7XTmC4D22dU/discussion ).
+#   * Add optional argument to :TOhtml command to specify mode (gui, cterm,
+#     term) to use for the styling. Suggestion by "nacitar".
+#   * Add way to override or specify which RGB colors map to the color numbers
+#     in cterm. Get better defaults than just guessing? Suggestion by "nacitar".
+#   * Disable filetype detection until after all processing is done.
+#   * Add option for not generating the hyperlink on stuff that looks like a
+#     URL? Or just color the link to fit with the colorscheme (and only special
+#     when hovering)?
+#   * Bug: Opera does not allow printing more than one page if uncopyable
+#     regions is turned on. Possible solution: Add normal text line numbers with
+#     display:none, set to display:inline for print style sheets, and hide
+#     <input> elements for print, to allow Opera printing multiple pages (and
+#     other uncopyable areas?). May need to make the new text invisible to IE
+#     with conditional comments to prevent copying it, IE for some reason likes
+#     to copy hidden text. Other browsers too?
+#   * Bug: still a 1px gap throughout the fold column when html_prevent_copy is
+#     "fn" in some browsers. Specifically, in Chromium on Ubuntu (but not Chrome
+#     on Windows). Perhaps it is font related?
+#   * Bug: still some gaps in the fold column when html_prevent_copy contains
+#     'd' and showing the whole diff (observed in multiple browsers). Only gaps
+#     on diff lines though.
+#   * Undercurl support via CSS3, with fallback to dotted or something:
+#      https://groups.google.com/d/topic/vim_use/BzXA6He1pHg/discussion
+#   * Redo updates for modified default foldtext (v11) when/if the patch is
+#     accepted to modify it.
+#   * Test case +diff_one_file-dynamic_folds+expand_tabs-hover_unfold
+#              +ignore_conceal-ignore_folding+no_foldcolumn+no_pre+no_progress
+#              +number_lines-pre_wrap-use_css+use_xhtml+whole_filler.xhtml
+#     does not show the whole diff filler as it is supposed to?
+#   * Bug: when 'isprint' is wrong for the current encoding, will generate
+#     invalid content. Can/should anything be done about this? Maybe a separate
+#     plugin to correct 'isprint' based on encoding?
+#   * Check to see if the windows-125\d encodings actually work in Unix without
+#     the 8bit- prefix. Add prefix to autoload dictionaries for Unix if not.
+#   * Font auto-detection similar to
+#     http://www.vim.org/scripts/script.php?script_id=2384 but for a variety of
+#     platforms.
+#   * Pull in code from http://www.vim.org/scripts/script.php?script_id=3113 :
+#      - listchars support
+#      - full-line background highlight
+#      - other?
+#   * Make it so deleted lines in a diff don't create side-scrolling (get it
+#     free with full-line background highlight above).
+#   * Restore open/closed folds and cursor position after processing each file
+#     with option not to restore for speed increase.
+#   * Add extra meta info (generation time, etc.)?
+#   * Tidy up so we can use strict doctype in even more situations
+#   * Implementation detail: add threshold for writing the lines to the html
+#     buffer before we're done (5000 or so lines should do it)
+#   * TODO comments for code cleanup scattered throughout
+#}}}
 
-" TODO: {{{
-"   * Check the issue tracker:
-"     https://sourceforge.net/p/vim-tohtml/issues/search/?q=%21status%3Aclosed
-"   * Options for generating the CSS in external style sheets. New :TOcss
-"     command to convert the current color scheme into a (mostly) generic CSS
-"     stylesheet which can be re-used. Alternate stylesheet support? Good start
-"     by Erik Falor
-"     ( https://groups.google.com/d/topic/vim_use/7XTmC4D22dU/discussion ).
-"   * Add optional argument to :TOhtml command to specify mode (gui, cterm,
-"     term) to use for the styling. Suggestion by "nacitar".
-"   * Add way to override or specify which RGB colors map to the color numbers
-"     in cterm. Get better defaults than just guessing? Suggestion by "nacitar".
-"   * Disable filetype detection until after all processing is done.
-"   * Add option for not generating the hyperlink on stuff that looks like a
-"     URL? Or just color the link to fit with the colorscheme (and only special
-"     when hovering)?
-"   * Bug: Opera does not allow printing more than one page if uncopyable
-"     regions is turned on. Possible solution: Add normal text line numbers with
-"     display:none, set to display:inline for print style sheets, and hide
-"     <input> elements for print, to allow Opera printing multiple pages (and
-"     other uncopyable areas?). May need to make the new text invisible to IE
-"     with conditional comments to prevent copying it, IE for some reason likes
-"     to copy hidden text. Other browsers too?
-"   * Bug: still a 1px gap throughout the fold column when html_prevent_copy is
-"     "fn" in some browsers. Specifically, in Chromium on Ubuntu (but not Chrome
-"     on Windows). Perhaps it is font related?
-"   * Bug: still some gaps in the fold column when html_prevent_copy contains
-"     'd' and showing the whole diff (observed in multiple browsers). Only gaps
-"     on diff lines though.
-"   * Undercurl support via CSS3, with fallback to dotted or something:
-"      https://groups.google.com/d/topic/vim_use/BzXA6He1pHg/discussion
-"   * Redo updates for modified default foldtext (v11) when/if the patch is
-"     accepted to modify it.
-"   * Test case +diff_one_file-dynamic_folds+expand_tabs-hover_unfold
-"              +ignore_conceal-ignore_folding+no_foldcolumn+no_pre+no_progress
-"              +number_lines-pre_wrap-use_css+use_xhtml+whole_filler.xhtml
-"     does not show the whole diff filler as it is supposed to?
-"   * Bug: when 'isprint' is wrong for the current encoding, will generate
-"     invalid content. Can/should anything be done about this? Maybe a separate
-"     plugin to correct 'isprint' based on encoding?
-"   * Check to see if the windows-125\d encodings actually work in Unix without
-"     the 8bit- prefix. Add prefix to autoload dictionaries for Unix if not.
-"   * Font auto-detection similar to
-"     http://www.vim.org/scripts/script.php?script_id=2384 but for a variety of
-"     platforms.
-"   * Pull in code from http://www.vim.org/scripts/script.php?script_id=3113 :
-"      - listchars support
-"      - full-line background highlight
-"      - other?
-"   * Make it so deleted lines in a diff don't create side-scrolling (get it
-"     free with full-line background highlight above).
-"   * Restore open/closed folds and cursor position after processing each file
-"     with option not to restore for speed increase.
-"   * Add extra meta info (generation time, etc.)?
-"   * Tidy up so we can use strict doctype in even more situations
-"   * Implementation detail: add threshold for writing the lines to the html
-"     buffer before we're done (5000 or so lines should do it)
-"   * TODO comments for code cleanup scattered throughout
-"}}}
-
-" Define the :TOhtml command when:
-" - 'compatible' is not set
-" - this plugin or user override was not already loaded
-" - user commands are available. {{{
+# Define the :TOhtml command when:
+# - 'compatible' is not set
+# - this plugin or user override was not already loaded
+# - user commands are available. {{{
 if !&cp && !exists(":TOhtml") && has("user_commands")
   command -range=% -bar TOhtml :call tohtml#Convert2HTML(<line1>, <line2>)
 endif "}}}
 
-" Make sure any patches will probably use consistent indent
-"   vim: ts=8 sw=2 sts=2 noet fdm=marker
+# Make sure any patches will probably use consistent indent
+#   vim: ts=8 sw=2 sts=2 noet fdm=marker
index ca40810e80570af163e1e290888ba497b8cefbf8..527e7d38b7ad0d489c2a2f9ef4fc4c2b4fa65728 100644 (file)
-" Vim syntax support file
-" Maintainer: Ben Fritz <fritzophrenic@gmail.com>
-" Last Change: 2024 Nov 02
-"
-" Additional contributors:
-"
-"             Original by Bram Moolenaar <Bram@vim.org>
-"             Modified by David Ne\v{c}as (Yeti) <yeti@physics.muni.cz>
-"             XHTML support by Panagiotis Issaris <takis@lumumba.luc.ac.be>
-"             Made w3 compliant by Edd Barrett <vext01@gmail.com>
-"             Added html_font. Edd Barrett <vext01@gmail.com>
-"             Progress bar based off code from "progressbar widget" plugin by
-"               Andreas Politz, heavily modified:
-"               http://www.vim.org/scripts/script.php?script_id=2006
-"
-"             See Mercurial change logs for more!
-
-" Transform a file into HTML, using the current syntax highlighting.
-
-" this file uses line continuations
-let s:cpo_sav = &cpo
-let s:ls  = &ls
-let s:ei_sav = &eventignore
-set cpo&vim
-
-" HTML filetype can take a while to load/highlight if the destination file
-" already exists.
+vim9script
+# Vim syntax support file
+# Maintainer: Ben Fritz <fritzophrenic@gmail.com>
+# Last Change: 2026-05-22
+#
+# Additional contributors:
+#
+#             Original by Bram Moolenaar <Bram@vim.org>
+#             Modified by David Ne\v{c}as (Yeti) <yeti@physics.muni.cz>
+#             XHTML support by Panagiotis Issaris <takis@lumumba.luc.ac.be>
+#             Made w3 compliant by Edd Barrett <vext01@gmail.com>
+#             Added html_font. Edd Barrett <vext01@gmail.com>
+#             Progress bar based off code from "progressbar widget" plugin by
+#               Andreas Politz, heavily modified:
+#               http://www.vim.org/scripts/script.php?script_id=2006
+#             Converted to Vim9 by Mao-Yining Vim PR #19915
+#
+#             See git change logs for more!
+
+# Transform a file into HTML, using the current syntax highlighting.
+
+var ls_sav = &laststatus
+var ei_sav = &eventignore
 set eventignore+=FileType
 
-let s:end=line('$')
+var lnum: number
+var end = line('$')
+var trim_tmp: list<string>
 
-" Font
+# Font
+var htmlfont: string
 if exists("g:html_font")
   if type(g:html_font) == type([])
-    let s:htmlfont = "'".. join(g:html_font,"','") .. "', monospace"
+    htmlfont = "'" .. join(g:html_font, "','") .. "', monospace"
   else
-    let s:htmlfont = "'".. g:html_font .. "', monospace"
+    htmlfont = "'" .. g:html_font .. "', monospace"
   endif
 else
-  let s:htmlfont = "monospace"
+  htmlfont = "monospace"
 endif
 
-let s:settings = tohtml#GetUserSettings()
+var settings = tohtml#GetUserSettings()
 
-if s:settings.use_xhtml
-  let s:html5 = 0
-elseif s:settings.use_css && !s:settings.no_pre
-  let s:html5 = 1
+var html5: number
+if settings.use_xhtml
+  html5 = 0
+elseif settings.use_css && !settings.no_pre
+  html5 = 1
 else
-  let s:html5 = 0
+  html5 = 0
 endif
 
-if !exists('s:FOLDED_ID')
-  let s:FOLDED_ID  = hlID("Folded")     | lockvar s:FOLDED_ID
-  let s:FOLD_C_ID  = hlID("FoldColumn") | lockvar s:FOLD_C_ID
-  let s:LINENR_ID  = hlID('LineNr')     | lockvar s:LINENR_ID
-  let s:DIFF_D_ID  = hlID("DiffDelete") | lockvar s:DIFF_D_ID
-  let s:DIFF_A_ID  = hlID("DiffAdd")    | lockvar s:DIFF_A_ID
-  let s:DIFF_C_ID  = hlID("DiffChange") | lockvar s:DIFF_C_ID
-  let s:DIFF_T_ID  = hlID("DiffText")   | lockvar s:DIFF_T_ID
-  let s:CONCEAL_ID = hlID('Conceal')    | lockvar s:CONCEAL_ID
-endif
-
-" Whitespace
-if s:settings.pre_wrap
-  let s:whitespace = "white-space: pre-wrap; "
+const FOLDED_ID  = hlID("Folded")
+const FOLD_C_ID  = hlID("FoldColumn")
+const LINENR_ID  = hlID('LineNr')
+const DIFF_D_ID  = hlID("DiffDelete")
+const DIFF_A_ID  = hlID("DiffAdd")
+const DIFF_C_ID  = hlID("DiffChange")
+const DIFF_T_ID  = hlID("DiffText")
+const CONCEAL_ID = hlID('Conceal')
+
+# Whitespace
+var whitespace: string
+if settings.pre_wrap
+  whitespace = "white-space: pre-wrap; "
 else
-  let s:whitespace = ""
+  whitespace = ""
 endif
 
-if !empty(s:settings.prevent_copy)
-  if s:settings.no_invalid
-    " User has decided they don't want invalid markup. Still works in
-    " OpenOffice, and for text editors, but when pasting into Microsoft Word the
-    " input elements get pasted too and they cannot be deleted (at least not
-    " easily).
-    let s:unselInputType = ""
+var unselInputType: string
+if !empty(settings.prevent_copy)
+  if settings.no_invalid
+    # User has decided they don't want invalid markup. Still works in
+    # OpenOffice, and for text editors, but when pasting into Microsoft Word the
+    # input elements get pasted too and they cannot be deleted (at least not
+    # easily).
+    unselInputType = ""
   else
-    " Prevent from copy-pasting the input elements into Microsoft Word where
-    " they cannot be deleted easily by deliberately inserting invalid markup.
-    let s:unselInputType = " type='invalid_input_type'"
+    # Prevent from copy-pasting the input elements into Microsoft Word where
+    # they cannot be deleted easily by deliberately inserting invalid markup.
+    unselInputType = " type='invalid_input_type'"
   endif
 endif
 
-" When gui colors are not supported, we can only guess the colors.
-" TODO - is this true anymore? Is there a way to ask the terminal what colors
-" each number means or read them from some file?
+# When gui colors are not supported, we can only guess the colors.
+# TODO - is this true anymore? Is there a way to ask the terminal what colors
+# each number means or read them from some file?
+var whatterm: string
+var cterm_color: dict<string>
 if &termguicolors || has("gui_running")
-  let s:whatterm = "gui"
+  whatterm = "gui"
 else
-  let s:whatterm = "cterm"
-  if &t_Co == 8
-    let s:cterm_color = {
-           \   0: "#808080", 1: "#ff6060", 2: "#00ff00", 3: "#ffff00",
-           \   4: "#8080ff", 5: "#ff40ff", 6: "#00ffff", 7: "#ffffff"
-           \ }
+  whatterm = "cterm"
+  if &t_Co == '8'
+    cterm_color = {
+      0: "#808080", 1: "#ff6060", 2: "#00ff00", 3: "#ffff00",
+      4: "#8080ff", 5: "#ff40ff", 6: "#00ffff", 7: "#ffffff"
+    }
   else
-    let s:cterm_color = {
-           \   0: "#000000", 1: "#c00000", 2: "#008000", 3: "#804000", 
-           \   4: "#0000c0", 5: "#c000c0", 6: "#008080", 7: "#c0c0c0", 
-           \   8: "#808080", 9: "#ff6060", 10: "#00ff00", 11: "#ffff00",
-           \   12: "#8080ff", 13: "#ff40ff", 14: "#00ffff", 15: "#ffffff"
-           \ }
-
-    " Colors for 88 and 256 come from xterm.
-    if &t_Co == 88
-      call extend(s:cterm_color, {
-           \   16: "#000000", 17: "#00008b", 18: "#0000cd", 19: "#0000ff",
-           \   20: "#008b00", 21: "#008b8b", 22: "#008bcd", 23: "#008bff",
-           \   24: "#00cd00", 25: "#00cd8b", 26: "#00cdcd", 27: "#00cdff",
-           \   28: "#00ff00", 29: "#00ff8b", 30: "#00ffcd", 31: "#00ffff",
-           \   32: "#8b0000", 33: "#8b008b", 34: "#8b00cd", 35: "#8b00ff",
-           \   36: "#8b8b00", 37: "#8b8b8b", 38: "#8b8bcd", 39: "#8b8bff",
-           \   40: "#8bcd00", 41: "#8bcd8b", 42: "#8bcdcd", 43: "#8bcdff",
-           \   44: "#8bff00", 45: "#8bff8b", 46: "#8bffcd", 47: "#8bffff",
-           \   48: "#cd0000", 49: "#cd008b", 50: "#cd00cd", 51: "#cd00ff",
-           \   52: "#cd8b00", 53: "#cd8b8b", 54: "#cd8bcd", 55: "#cd8bff",
-           \   56: "#cdcd00", 57: "#cdcd8b", 58: "#cdcdcd", 59: "#cdcdff",
-           \   60: "#cdff00", 61: "#cdff8b", 62: "#cdffcd", 63: "#cdffff",
-           \   64: "#ff0000"
-           \ })
-      call extend(s:cterm_color, {
-           \   65: "#ff008b", 66: "#ff00cd", 67: "#ff00ff", 68: "#ff8b00",
-           \   69: "#ff8b8b", 70: "#ff8bcd", 71: "#ff8bff", 72: "#ffcd00",
-           \   73: "#ffcd8b", 74: "#ffcdcd", 75: "#ffcdff", 76: "#ffff00",
-           \   77: "#ffff8b", 78: "#ffffcd", 79: "#ffffff", 80: "#2e2e2e",
-           \   81: "#5c5c5c", 82: "#737373", 83: "#8b8b8b", 84: "#a2a2a2",
-           \   85: "#b9b9b9", 86: "#d0d0d0", 87: "#e7e7e7"
-           \ })
-    elseif &t_Co == 256
-      call extend(s:cterm_color, {
-           \   16: "#000000", 17: "#00005f", 18: "#000087", 19: "#0000af",
-           \   20: "#0000d7", 21: "#0000ff", 22: "#005f00", 23: "#005f5f",
-           \   24: "#005f87", 25: "#005faf", 26: "#005fd7", 27: "#005fff",
-           \   28: "#008700", 29: "#00875f", 30: "#008787", 31: "#0087af",
-           \   32: "#0087d7", 33: "#0087ff", 34: "#00af00", 35: "#00af5f",
-           \   36: "#00af87", 37: "#00afaf", 38: "#00afd7", 39: "#00afff",
-           \   40: "#00d700", 41: "#00d75f", 42: "#00d787", 43: "#00d7af",
-           \   44: "#00d7d7", 45: "#00d7ff", 46: "#00ff00", 47: "#00ff5f",
-           \   48: "#00ff87", 49: "#00ffaf", 50: "#00ffd7", 51: "#00ffff",
-           \   52: "#5f0000", 53: "#5f005f", 54: "#5f0087", 55: "#5f00af",
-           \   56: "#5f00d7", 57: "#5f00ff", 58: "#5f5f00", 59: "#5f5f5f",
-           \   60: "#5f5f87", 61: "#5f5faf", 62: "#5f5fd7", 63: "#5f5fff",
-           \   64: "#5f8700"
-           \ })
-      call extend(s:cterm_color, {
-           \   65: "#5f875f", 66: "#5f8787", 67: "#5f87af", 68: "#5f87d7",
-           \   69: "#5f87ff", 70: "#5faf00", 71: "#5faf5f", 72: "#5faf87",
-           \   73: "#5fafaf", 74: "#5fafd7", 75: "#5fafff", 76: "#5fd700",
-           \   77: "#5fd75f", 78: "#5fd787", 79: "#5fd7af", 80: "#5fd7d7",
-           \   81: "#5fd7ff", 82: "#5fff00", 83: "#5fff5f", 84: "#5fff87",
-           \   85: "#5fffaf", 86: "#5fffd7", 87: "#5fffff", 88: "#870000",
-           \   89: "#87005f", 90: "#870087", 91: "#8700af", 92: "#8700d7",
-           \   93: "#8700ff", 94: "#875f00", 95: "#875f5f", 96: "#875f87",
-           \   97: "#875faf", 98: "#875fd7", 99: "#875fff", 100: "#878700",
-           \   101: "#87875f", 102: "#878787", 103: "#8787af", 104: "#8787d7",
-           \   105: "#8787ff", 106: "#87af00", 107: "#87af5f", 108: "#87af87",
-           \   109: "#87afaf", 110: "#87afd7", 111: "#87afff", 112: "#87d700"
-           \ })
-      call extend(s:cterm_color, {
-           \   113: "#87d75f", 114: "#87d787", 115: "#87d7af", 116: "#87d7d7",
-           \   117: "#87d7ff", 118: "#87ff00", 119: "#87ff5f", 120: "#87ff87",
-           \   121: "#87ffaf", 122: "#87ffd7", 123: "#87ffff", 124: "#af0000",
-           \   125: "#af005f", 126: "#af0087", 127: "#af00af", 128: "#af00d7",
-           \   129: "#af00ff", 130: "#af5f00", 131: "#af5f5f", 132: "#af5f87",
-           \   133: "#af5faf", 134: "#af5fd7", 135: "#af5fff", 136: "#af8700",
-           \   137: "#af875f", 138: "#af8787", 139: "#af87af", 140: "#af87d7",
-           \   141: "#af87ff", 142: "#afaf00", 143: "#afaf5f", 144: "#afaf87",
-           \   145: "#afafaf", 146: "#afafd7", 147: "#afafff", 148: "#afd700",
-           \   149: "#afd75f", 150: "#afd787", 151: "#afd7af", 152: "#afd7d7",
-           \   153: "#afd7ff", 154: "#afff00", 155: "#afff5f", 156: "#afff87",
-           \   157: "#afffaf", 158: "#afffd7"
-           \ })
-      call extend(s:cterm_color, {
-           \   159: "#afffff", 160: "#d70000", 161: "#d7005f", 162: "#d70087",
-           \   163: "#d700af", 164: "#d700d7", 165: "#d700ff", 166: "#d75f00",
-           \   167: "#d75f5f", 168: "#d75f87", 169: "#d75faf", 170: "#d75fd7",
-           \   171: "#d75fff", 172: "#d78700", 173: "#d7875f", 174: "#d78787",
-           \   175: "#d787af", 176: "#d787d7", 177: "#d787ff", 178: "#d7af00",
-           \   179: "#d7af5f", 180: "#d7af87", 181: "#d7afaf", 182: "#d7afd7",
-           \   183: "#d7afff", 184: "#d7d700", 185: "#d7d75f", 186: "#d7d787",
-           \   187: "#d7d7af", 188: "#d7d7d7", 189: "#d7d7ff", 190: "#d7ff00",
-           \   191: "#d7ff5f", 192: "#d7ff87", 193: "#d7ffaf", 194: "#d7ffd7",
-           \   195: "#d7ffff", 196: "#ff0000", 197: "#ff005f", 198: "#ff0087",
-           \   199: "#ff00af", 200: "#ff00d7", 201: "#ff00ff", 202: "#ff5f00",
-           \   203: "#ff5f5f", 204: "#ff5f87"
-           \ })
-      call extend(s:cterm_color, {
-           \   205: "#ff5faf", 206: "#ff5fd7", 207: "#ff5fff", 208: "#ff8700",
-           \   209: "#ff875f", 210: "#ff8787", 211: "#ff87af", 212: "#ff87d7",
-           \   213: "#ff87ff", 214: "#ffaf00", 215: "#ffaf5f", 216: "#ffaf87",
-           \   217: "#ffafaf", 218: "#ffafd7", 219: "#ffafff", 220: "#ffd700",
-           \   221: "#ffd75f", 222: "#ffd787", 223: "#ffd7af", 224: "#ffd7d7",
-           \   225: "#ffd7ff", 226: "#ffff00", 227: "#ffff5f", 228: "#ffff87",
-           \   229: "#ffffaf", 230: "#ffffd7", 231: "#ffffff", 232: "#080808",
-           \   233: "#121212", 234: "#1c1c1c", 235: "#262626", 236: "#303030",
-           \   237: "#3a3a3a", 238: "#444444", 239: "#4e4e4e", 240: "#585858",
-           \   241: "#626262", 242: "#6c6c6c", 243: "#767676", 244: "#808080",
-           \   245: "#8a8a8a", 246: "#949494", 247: "#9e9e9e", 248: "#a8a8a8",
-           \   249: "#b2b2b2", 250: "#bcbcbc", 251: "#c6c6c6", 252: "#d0d0d0",
-           \   253: "#dadada", 254: "#e4e4e4", 255: "#eeeeee"
-           \ })
+    cterm_color = {
+      0: "#000000", 1: "#c00000", 2: "#008000", 3: "#804000",
+      4: "#0000c0", 5: "#c000c0", 6: "#008080", 7: "#c0c0c0",
+      8: "#808080", 9: "#ff6060", 10: "#00ff00", 11: "#ffff00",
+      12: "#8080ff", 13: "#ff40ff", 14: "#00ffff", 15: "#ffffff"
+    }
+
+    # Colors for 88 and 256 come from xterm.
+    if &t_Co == '88'
+      extend(cterm_color, {
+       16: "#000000", 17: "#00008b", 18: "#0000cd", 19: "#0000ff",
+       20: "#008b00", 21: "#008b8b", 22: "#008bcd", 23: "#008bff",
+       24: "#00cd00", 25: "#00cd8b", 26: "#00cdcd", 27: "#00cdff",
+       28: "#00ff00", 29: "#00ff8b", 30: "#00ffcd", 31: "#00ffff",
+       32: "#8b0000", 33: "#8b008b", 34: "#8b00cd", 35: "#8b00ff",
+       36: "#8b8b00", 37: "#8b8b8b", 38: "#8b8bcd", 39: "#8b8bff",
+       40: "#8bcd00", 41: "#8bcd8b", 42: "#8bcdcd", 43: "#8bcdff",
+       44: "#8bff00", 45: "#8bff8b", 46: "#8bffcd", 47: "#8bffff",
+       48: "#cd0000", 49: "#cd008b", 50: "#cd00cd", 51: "#cd00ff",
+       52: "#cd8b00", 53: "#cd8b8b", 54: "#cd8bcd", 55: "#cd8bff",
+       56: "#cdcd00", 57: "#cdcd8b", 58: "#cdcdcd", 59: "#cdcdff",
+       60: "#cdff00", 61: "#cdff8b", 62: "#cdffcd", 63: "#cdffff",
+       64: "#ff0000"
+      })
+      extend(cterm_color, {
+       65: "#ff008b", 66: "#ff00cd", 67: "#ff00ff", 68: "#ff8b00",
+       69: "#ff8b8b", 70: "#ff8bcd", 71: "#ff8bff", 72: "#ffcd00",
+       73: "#ffcd8b", 74: "#ffcdcd", 75: "#ffcdff", 76: "#ffff00",
+       77: "#ffff8b", 78: "#ffffcd", 79: "#ffffff", 80: "#2e2e2e",
+       81: "#5c5c5c", 82: "#737373", 83: "#8b8b8b", 84: "#a2a2a2",
+       85: "#b9b9b9", 86: "#d0d0d0", 87: "#e7e7e7"
+      })
+    elseif &t_Co == '256'
+      extend(cterm_color, {
+       16: "#000000", 17: "#00005f", 18: "#000087", 19: "#0000af",
+       20: "#0000d7", 21: "#0000ff", 22: "#005f00", 23: "#005f5f",
+       24: "#005f87", 25: "#005faf", 26: "#005fd7", 27: "#005fff",
+       28: "#008700", 29: "#00875f", 30: "#008787", 31: "#0087af",
+       32: "#0087d7", 33: "#0087ff", 34: "#00af00", 35: "#00af5f",
+       36: "#00af87", 37: "#00afaf", 38: "#00afd7", 39: "#00afff",
+       40: "#00d700", 41: "#00d75f", 42: "#00d787", 43: "#00d7af",
+       44: "#00d7d7", 45: "#00d7ff", 46: "#00ff00", 47: "#00ff5f",
+       48: "#00ff87", 49: "#00ffaf", 50: "#00ffd7", 51: "#00ffff",
+       52: "#5f0000", 53: "#5f005f", 54: "#5f0087", 55: "#5f00af",
+       56: "#5f00d7", 57: "#5f00ff", 58: "#5f5f00", 59: "#5f5f5f",
+       60: "#5f5f87", 61: "#5f5faf", 62: "#5f5fd7", 63: "#5f5fff",
+       64: "#5f8700"
+      })
+      extend(cterm_color, {
+       65: "#5f875f", 66: "#5f8787", 67: "#5f87af", 68: "#5f87d7",
+       69: "#5f87ff", 70: "#5faf00", 71: "#5faf5f", 72: "#5faf87",
+       73: "#5fafaf", 74: "#5fafd7", 75: "#5fafff", 76: "#5fd700",
+       77: "#5fd75f", 78: "#5fd787", 79: "#5fd7af", 80: "#5fd7d7",
+       81: "#5fd7ff", 82: "#5fff00", 83: "#5fff5f", 84: "#5fff87",
+       85: "#5fffaf", 86: "#5fffd7", 87: "#5fffff", 88: "#870000",
+       89: "#87005f", 90: "#870087", 91: "#8700af", 92: "#8700d7",
+       93: "#8700ff", 94: "#875f00", 95: "#875f5f", 96: "#875f87",
+       97: "#875faf", 98: "#875fd7", 99: "#875fff", 100: "#878700",
+       101: "#87875f", 102: "#878787", 103: "#8787af", 104: "#8787d7",
+       105: "#8787ff", 106: "#87af00", 107: "#87af5f", 108: "#87af87",
+       109: "#87afaf", 110: "#87afd7", 111: "#87afff", 112: "#87d700"
+      })
+      extend(cterm_color, {
+       113: "#87d75f", 114: "#87d787", 115: "#87d7af", 116: "#87d7d7",
+       117: "#87d7ff", 118: "#87ff00", 119: "#87ff5f", 120: "#87ff87",
+       121: "#87ffaf", 122: "#87ffd7", 123: "#87ffff", 124: "#af0000",
+       125: "#af005f", 126: "#af0087", 127: "#af00af", 128: "#af00d7",
+       129: "#af00ff", 130: "#af5f00", 131: "#af5f5f", 132: "#af5f87",
+       133: "#af5faf", 134: "#af5fd7", 135: "#af5fff", 136: "#af8700",
+       137: "#af875f", 138: "#af8787", 139: "#af87af", 140: "#af87d7",
+       141: "#af87ff", 142: "#afaf00", 143: "#afaf5f", 144: "#afaf87",
+       145: "#afafaf", 146: "#afafd7", 147: "#afafff", 148: "#afd700",
+       149: "#afd75f", 150: "#afd787", 151: "#afd7af", 152: "#afd7d7",
+       153: "#afd7ff", 154: "#afff00", 155: "#afff5f", 156: "#afff87",
+       157: "#afffaf", 158: "#afffd7"
+      })
+      extend(cterm_color, {
+       159: "#afffff", 160: "#d70000", 161: "#d7005f", 162: "#d70087",
+       163: "#d700af", 164: "#d700d7", 165: "#d700ff", 166: "#d75f00",
+       167: "#d75f5f", 168: "#d75f87", 169: "#d75faf", 170: "#d75fd7",
+       171: "#d75fff", 172: "#d78700", 173: "#d7875f", 174: "#d78787",
+       175: "#d787af", 176: "#d787d7", 177: "#d787ff", 178: "#d7af00",
+       179: "#d7af5f", 180: "#d7af87", 181: "#d7afaf", 182: "#d7afd7",
+       183: "#d7afff", 184: "#d7d700", 185: "#d7d75f", 186: "#d7d787",
+       187: "#d7d7af", 188: "#d7d7d7", 189: "#d7d7ff", 190: "#d7ff00",
+       191: "#d7ff5f", 192: "#d7ff87", 193: "#d7ffaf", 194: "#d7ffd7",
+       195: "#d7ffff", 196: "#ff0000", 197: "#ff005f", 198: "#ff0087",
+       199: "#ff00af", 200: "#ff00d7", 201: "#ff00ff", 202: "#ff5f00",
+       203: "#ff5f5f", 204: "#ff5f87"
+      })
+      extend(cterm_color, {
+       205: "#ff5faf", 206: "#ff5fd7", 207: "#ff5fff", 208: "#ff8700",
+       209: "#ff875f", 210: "#ff8787", 211: "#ff87af", 212: "#ff87d7",
+       213: "#ff87ff", 214: "#ffaf00", 215: "#ffaf5f", 216: "#ffaf87",
+       217: "#ffafaf", 218: "#ffafd7", 219: "#ffafff", 220: "#ffd700",
+       221: "#ffd75f", 222: "#ffd787", 223: "#ffd7af", 224: "#ffd7d7",
+       225: "#ffd7ff", 226: "#ffff00", 227: "#ffff5f", 228: "#ffff87",
+       229: "#ffffaf", 230: "#ffffd7", 231: "#ffffff", 232: "#080808",
+       233: "#121212", 234: "#1c1c1c", 235: "#262626", 236: "#303030",
+       237: "#3a3a3a", 238: "#444444", 239: "#4e4e4e", 240: "#585858",
+       241: "#626262", 242: "#6c6c6c", 243: "#767676", 244: "#808080",
+       245: "#8a8a8a", 246: "#949494", 247: "#9e9e9e", 248: "#a8a8a8",
+       249: "#b2b2b2", 250: "#bcbcbc", 251: "#c6c6c6", 252: "#d0d0d0",
+       253: "#dadada", 254: "#e4e4e4", 255: "#eeeeee"
+      })
     endif
   endif
 endif
 
-" Return good color specification: in GUI no transformation is done, in
-" terminal return RGB values of known colors and empty string for unknown
-if s:whatterm == "gui"
-  function! s:HtmlColor(color)
-    return a:color
-  endfun
+# Return good color specification: in GUI no transformation is done, in
+# terminal return RGB values of known colors and empty string for unknown
+if whatterm == "gui"
+  def HtmlColor(color: string): string
+    return color
+  enddef
 else
-  function! s:HtmlColor(color)
-    if has_key(s:cterm_color, a:color)
-      return s:cterm_color[a:color]
+  def HtmlColor(color: string): string
+    if has_key(cterm_color, color)
+      return cterm_color[color]
     else
       return ""
     endif
-  endfun
+  enddef
 endif
 
-" Find out the background and foreground color for use later
-let s:fgc = s:HtmlColor(synIDattr(hlID("Normal")->synIDtrans(), "fg#", s:whatterm))
-let s:bgc = s:HtmlColor(synIDattr(hlID("Normal")->synIDtrans(), "bg#", s:whatterm))
-if s:fgc == ""
-  let s:fgc = ( &background == "dark" ? "#ffffff" : "#000000" )
+# Find out the background and foreground color for use later
+var fgc = HtmlColor(synIDattr(synIDtrans(hlID("Normal")), "fg#", whatterm))
+var bgc = HtmlColor(synIDattr(synIDtrans(hlID("Normal")), "bg#", whatterm))
+if fgc == ""
+  fgc = (&background == "dark" ? "#ffffff" : "#000000")
 endif
-if s:bgc == ""
-  let s:bgc = ( &background == "dark" ? "#000000" : "#ffffff" )
+if bgc == ""
+  bgc = (&background == "dark" ? "#000000" : "#ffffff")
 endif
 
-if !s:settings.use_css
-  " Return opening HTML tag for given highlight id
-  function! s:HtmlOpening(id, extra_attrs)
-    let a = ""
-    let translated_ID = synIDtrans(a:id)
-    if synIDattr(translated_ID, "inverse")
-      " For inverse, we always must set both colors (and exchange them)
-      let x = s:HtmlColor(synIDattr(translated_ID, "fg#", s:whatterm))
-      let a = a .. '<span '..a:extra_attrs..'style="background-color: ' .. ( x != "" ? x : s:fgc ) .. '">'
-      let x = s:HtmlColor(synIDattr(translated_ID, "bg#", s:whatterm))
-      let a = a .. '<font color="' .. ( x != "" ? x : s:bgc ) .. '">'
+if !settings.use_css
+  # Return opening HTML tag for given highlight id
+  def HtmlOpening(id: number, extra_attrs: string): string
+    var a = ""
+    var translated_ID = synIDtrans(id)
+    if synIDattr(translated_ID, "inverse") == '1'
+      # For inverse, we always must set both colors (and exchange them)
+      var x = HtmlColor(synIDattr(translated_ID, "fg#", whatterm))
+      a ..= '<span ' .. extra_attrs .. 'style="background-color: ' .. (x != "" ? x : fgc) .. '">'
+      x = HtmlColor(synIDattr(translated_ID, "bg#", whatterm))
+      a ..= '<font color="' .. (x != "" ? x : bgc) .. '">'
     else
-      let x = s:HtmlColor(synIDattr(translated_ID, "bg#", s:whatterm))
+      var x = HtmlColor(synIDattr(translated_ID, "bg#", whatterm))
       if x != ""
-       let a = a .. '<span '..a:extra_attrs..'style="background-color: ' .. x .. '">'
-      elseif !empty(a:extra_attrs)
-       let a = a .. '<span '..a:extra_attrs..'>'
+       a ..= '<span ' .. extra_attrs .. 'style="background-color: ' .. x .. '">'
+      elseif !empty(extra_attrs)
+       a ..= '<span ' .. extra_attrs .. '>'
       endif
-      let x = s:HtmlColor(synIDattr(translated_ID, "fg#", s:whatterm))
-      if x != "" | let a = a .. '<font color="' .. x .. '">' | endif
+      x = HtmlColor(synIDattr(translated_ID, "fg#", whatterm))
+      if x != "" | a ..= '<font color="' .. x .. '">' | endif
     endif
-    if synIDattr(translated_ID, "bold") | let a = a .. "<b>" | endif
-    if synIDattr(translated_ID, "italic") | let a = a .. "<i>" | endif
-    if synIDattr(translated_ID, "underline") | let a = a .. "<u>" | endif
+    if synIDattr(translated_ID, "bold") == '1' | a ..= "<b>" | endif
+    if synIDattr(translated_ID, "italic") == '1' | a ..= "<i>" | endif
+    if synIDattr(translated_ID, "underline") == '1' | a ..= "<u>" | endif
     return a
-  endfun
-
-  " Return closing HTML tag for given highlight id
-  function! s:HtmlClosing(id, has_extra_attrs)
-    let a = ""
-    let translated_ID = synIDtrans(a:id)
-    if synIDattr(translated_ID, "underline") | let a = a .. "</u>" | endif
-    if synIDattr(translated_ID, "italic") | let a = a .. "</i>" | endif
-    if synIDattr(translated_ID, "bold") | let a = a .. "</b>" | endif
-    if synIDattr(translated_ID, "inverse")
-      let a = a .. '</font></span>'
+  enddef
+
+  # Return closing HTML tag for given highlight id
+  def HtmlClosing(id: number, has_extra_attrs: bool): string
+    var a = ""
+    var translated_ID = synIDtrans(id)
+    if synIDattr(translated_ID, "underline") == '1' | a ..= "</u>" | endif
+    if synIDattr(translated_ID, "italic") == '1' | a ..= "</i>" | endif
+    if synIDattr(translated_ID, "bold") == '1' | a ..= "</b>" | endif
+    if synIDattr(translated_ID, "inverse") == '1'
+      a ..= '</font></span>'
     else
-      let x = s:HtmlColor(synIDattr(translated_ID, "fg#", s:whatterm))
-      if x != "" | let a = a .. '</font>' | endif
-      let x = s:HtmlColor(synIDattr(translated_ID, "bg#", s:whatterm))
-      if x != "" || a:has_extra_attrs | let a = a .. '</span>' | endif
+      var x = HtmlColor(synIDattr(translated_ID, "fg#", whatterm))
+      if x != "" | a ..= '</font>' | endif
+      x = HtmlColor(synIDattr(translated_ID, "bg#", whatterm))
+      if x != "" || has_extra_attrs | a ..= '</span>' | endif
     endif
     return a
-  endfun
+  enddef
 endif
 
-" Use a different function for formatting based on user options. This way we
-" can avoid a lot of logic during the actual execution.
-"
-" Build the function line by line containing only what is needed for the options
-" in use for maximum code sharing with minimal branch logic for greater speed.
-"
-" Note, 'exec' commands do not recognize line continuations, so must concatenate
-" lines rather than continue them.
-if s:settings.use_css
-  " save CSS to a list of rules to add to the output at the end of processing
-
-  " first, get the style names we need
-  let s:wrapperfunc_lines = []
-  call add(s:wrapperfunc_lines, [])
-  let s:wrapperfunc_lines[-1] =<< trim ENDLET
-       function! s:BuildStyleWrapper(style_id, diff_style_id, extra_attrs, text, make_unselectable, unformatted)
-       
-         let l:style_name = synIDattr(a:style_id, "name", s:whatterm)
+# Use a different function for formatting based on user options. This way we
+# can avoid a lot of logic during the actual execution.
+#
+# Build the function line by line containing only what is needed for the options
+# in use for maximum code sharing with minimal branch logic for greater speed.
+if settings.use_css
+  # save CSS to a list of rules to add to the output at the end of processing
+
+  # Note, 'exec' commands do not recognize line continuations, so must concatenate
+  # lines rather than continue them.
+  var wrapperfunc_lines: list<string>
+  # first, get the style names we need
+  wrapperfunc_lines =<< trim eval ENDLET
+    def BuildStyleWrapper(style_id: number, diff_style_id: number, extra_attrs: string, text: string, make_unselectable: bool, unformatted: string): string
+      var style_name = synIDattr(style_id, "name", whatterm)
+      var saved_style: string
   ENDLET
   if &diff
-    call add(s:wrapperfunc_lines, [])
-    let s:wrapperfunc_lines[-1] =<< trim ENDLET
-         let l:diff_style_name = synIDattr(a:diff_style_id, "name", s:whatterm)
+    trim_tmp =<< trim eval ENDLET
+      var diff_style_name = synIDattr(diff_style_id, "name", whatterm)
     ENDLET
+    wrapperfunc_lines += trim_tmp
 
-    " Add normal groups and diff groups to separate lists so we can order them to
-    " allow diff highlight to override normal highlight
-
-    " if primary style IS a diff style, grab it from the diff cache instead
-    " (always succeeds because we pre-populate it)
-    call add(s:wrapperfunc_lines, [])
-    let s:wrapperfunc_lines[-1] =<< trim ENDLET
+    # Add normal groups and diff groups to separate lists so we can order them to
+    # allow diff highlight to override normal highlight
 
-         if a:style_id == s:DIFF_D_ID || a:style_id == s:DIFF_A_ID || a:style_id == s:DIFF_C_ID || a:style_id == s:DIFF_T_ID
-           let l:saved_style = get(s:diffstylelist,a:style_id)
-         else
+    # if primary style IS a diff style, grab it from the diff cache instead
+    # (always succeeds because we pre-populate it)
+    trim_tmp =<< trim eval ENDLET
+      if style_id == DIFF_D_ID || style_id == DIFF_A_ID || style_id == DIFF_C_ID || style_id == DIFF_T_ID
+       saved_style = get(diffstylelist, style_id)
+      else
     ENDLET
+    wrapperfunc_lines += trim_tmp
   endif
 
-  " get primary style info from cache or build it on the fly if not found
-  call add(s:wrapperfunc_lines, [])
-  let s:wrapperfunc_lines[-1] =<< trim ENDLET
-           let l:saved_style = get(s:stylelist,a:style_id)
-           if type(l:saved_style) == type(0)
-             unlet l:saved_style
-             let l:saved_style = s:CSS1(a:style_id)
-             if l:saved_style != ""
-               let l:saved_style = "." .. l:style_name .. " { " .. l:saved_style .. "}"
-             endif
-             let s:stylelist[a:style_id] = l:saved_style
-           endif
+  # get primary style info from cache or build it on the fly if not found
+  trim_tmp =<< trim ENDLET
+    if has_key(stylelist, style_id)
+       saved_style = stylelist[style_id]
+    else
+       saved_style = CSS1(style_id)
+       if !empty(saved_style)
+           saved_style = "." .. style_name .. " { " .. saved_style .. "}"
+       endif
+       stylelist[style_id] = saved_style
+    endif
   ENDLET
+  wrapperfunc_lines += trim_tmp
   if &diff
-    call add(s:wrapperfunc_lines, [])
-    let s:wrapperfunc_lines[-1] =<< trim ENDLET
-         endif
+    trim_tmp =<< trim eval ENDLET
+      endif
     ENDLET
+    wrapperfunc_lines += trim_tmp
   endif
-" Ignore this comment, just bypassing a highlighting issue: if
-
-  " Build the wrapper tags around the text. It turns out that caching these
-  " gives pretty much zero performance gain and adds a lot of logic.
+  # Ignore this comment, just bypassing a highlighting issue: if
 
-  call add(s:wrapperfunc_lines, [])
-  let s:wrapperfunc_lines[-1] =<< trim ENDLET
+  # Build the wrapper tags around the text. It turns out that caching these
+  # gives pretty much zero performance gain and adds a lot of logic.
 
-         if l:saved_style == "" && empty(a:extra_attrs)
+  trim_tmp =<< trim eval ENDLET
+      if saved_style == "" && empty(extra_attrs)
   ENDLET
+  wrapperfunc_lines += trim_tmp
   if &diff
-    call add(s:wrapperfunc_lines, [])
-    let s:wrapperfunc_lines[-1] =<< trim ENDLET
-           if a:diff_style_id <= 0
+    trim_tmp =<< trim eval ENDLET
+       if diff_style_id <= 0
     ENDLET
+    wrapperfunc_lines += trim_tmp
   endif
-  " no surroundings if neither primary nor diff style has any info
-  call add(s:wrapperfunc_lines, [])
-  let s:wrapperfunc_lines[-1] =<< trim ENDLET
-             return a:text
+  # no surroundings if neither primary nor diff style has any info
+  trim_tmp =<< trim eval ENDLET
+         return text
   ENDLET
+  wrapperfunc_lines += trim_tmp
   if &diff
-    " no primary style, but diff style
-    call add(s:wrapperfunc_lines, [])
-    let s:wrapperfunc_lines[-1] =<< trim ENDLET
-           else
-             return '<span class="' ..l:diff_style_name .. '">'..a:text.."</span>"
-           endif
+    # no primary style, but diff style
+    trim_tmp =<< trim ENDLET
+       else
+         return $'<span class="{diff_style_name}">{text}</span>'
+       endif
     ENDLET
+    wrapperfunc_lines += trim_tmp
   endif
-  " Ignore this comment, just bypassing a highlighting issue: if
+  # Ignore this comment, just bypassing a highlighting issue: if
 
-  " open tag for non-empty primary style
-  call add(s:wrapperfunc_lines, [])
-  let s:wrapperfunc_lines[-1] =<< trim ENDLET
-         else
+  # open tag for non-empty primary style
+  trim_tmp =<< trim eval ENDLET
+      else
   ENDLET
-  " non-empty primary style. handle either empty or non-empty diff style.
-  "
-  " separate the two classes by a space to apply them both if there is a diff
-  " style name, unless the primary style is empty, then just use the diff style
-  " name
-  let s:diffstyle =
-         \ (&diff ? '(a:diff_style_id <= 0 ? "" : " " .. l:diff_style_name)..'
-         \        : '')
-  if s:settings.prevent_copy == ""
-    call add(s:wrapperfunc_lines, [])
-    let s:wrapperfunc_lines[-1] =<< trim eval ENDLET
-           return "<span "..a:extra_attrs..'class="' .. l:style_name ..{s:diffstyle}'">'..a:text.."</span>"
+  wrapperfunc_lines += trim_tmp
+  # non-empty primary style. handle either empty or non-empty diff style.
+  #
+  # separate the two classes by a space to apply them both if there is a diff
+  # style name, unless the primary style is empty, then just use the diff style
+  # name
+  var diffstyle: string
+  if &diff
+    diffstyle = '(diff_style_id <= 0 ? "" : " " .. diff_style_name) .. '
+  else
+    diffstyle = ''
+  endif
+  if settings.prevent_copy == ""
+    trim_tmp =<< trim eval ENDLET
+       return "<span " .. extra_attrs .. 'class="' .. style_name .. {diffstyle}'">' .. text .. "</span>"
     ENDLET
+    wrapperfunc_lines += trim_tmp
   else
-
-    " New method: use generated content in the CSS. The only thing needed here
-    " is a span with no content, with an attribute holding the desired text.
-    "
-    " Old method: use an <input> element when text is unsectable. This is still
-    " used in conditional comments for Internet Explorer, where the new method
-    " doesn't work.
-    "
-    " Wrap the <input> in a <span> to allow fixing the stupid bug in some fonts
-    " which cause browsers to display a 1px gap between lines when these
-    " <input>s have a background color (maybe not really a bug, this isn't
-    " well-defined)
-    "
-    " use strwidth, because we care only about how many character boxes are
-    " needed to size the input, we don't care how many characters (including
-    " separately counted composing chars, from strchars()) or bytes (from
-    " len())the string contains. strdisplaywidth() is not needed because none of
-    " the unselectable groups can contain tab characters (fold column, fold
-    " text, line number).
-    "
-    " Note, if maxlength property needs to be added in the future, it will need
-    " to use strchars(), because HTML specifies that the maxlength parameter
-    " uses the number of unique codepoints for its limit.
-    call add(s:wrapperfunc_lines, [])
-    let s:wrapperfunc_lines[-1] =<< trim eval ENDLET
-           if a:make_unselectable
-             let return_span = "<span "..a:extra_attrs..'class="' .. l:style_name ..{s:diffstyle}'"'
+    # New method: use generated content in the CSS. The only thing needed here
+    # is a span with no content, with an attribute holding the desired text.
+    #
+    # Old method: use an <input> element when text is unsectable. This is still
+    # used in conditional comments for Internet Explorer, where the new method
+    # doesn't work.
+    #
+    # Wrap the <input> in a <span> to allow fixing the stupid bug in some fonts
+    # which cause browsers to display a 1px gap between lines when these
+    # <input>s have a background color (maybe not really a bug, this isn't
+    # well-defined)
+    #
+    # use strwidth, because we care only about how many character boxes are
+    # needed to size the input, we don't care how many characters (including
+    # separately counted composing chars, from strchars()) or bytes (from
+    # len())the string contains. strdisplaywidth() is not needed because none of
+    # the unselectable groups can contain tab characters (fold column, fold
+    # text, line number).
+    #
+    # Note, if maxlength property needs to be added in the future, it will need
+    # to use strchars(), because HTML specifies that the maxlength parameter
+    # uses the number of unique codepoints for its limit.
+    trim_tmp =<< trim eval ENDLET
+       if make_unselectable
+         var return_span = "<span " .. extra_attrs .. 'class="' .. style_name .. {diffstyle}'"'
     ENDLET
-    if s:settings.use_input_for_pc !=# 'all'
-      call add(s:wrapperfunc_lines, [])
-      let s:wrapperfunc_lines[-1] =<< trim ENDLET
-             let return_span ..= " data-" .. l:style_name .. '-content="'..a:text..'"'
+    wrapperfunc_lines += trim_tmp
+    if settings.use_input_for_pc !=# 'all'
+      trim_tmp =<< trim eval ENDLET
+         return_span ..= " data-" .. style_name .. '-content="' .. text .. '"'
       ENDLET
+      wrapperfunc_lines += trim_tmp
     endif
-    call add(s:wrapperfunc_lines, [])
-    let s:wrapperfunc_lines[-1] =<< trim ENDLET
-             let return_span ..= '>'
+    trim_tmp =<< trim eval ENDLET
+         return_span ..= '>'
     ENDLET
-    if s:settings.use_input_for_pc !=# 'none'
-      call add(s:wrapperfunc_lines, [])
-      let s:wrapperfunc_lines[-1] =<< trim eval ENDLET
-             let return_span ..=   '<input'..s:unselInputType..' class="' .. l:style_name ..{s:diffstyle}'"'
-             let return_span ..=   ' value="'..substitute(a:unformatted,'\s\+$',"","")..'"'
-             let return_span ..=   " onselect='this.blur(); return false;'"
-             let return_span ..=   " onmousedown='this.blur(); return false;'"
-             let return_span ..=   " onclick='this.blur(); return false;'"
-             let return_span ..=   " readonly='readonly'"
-             let return_span ..=   ' size="'..strwidth(a:unformatted)..'"'
-             let return_span ..=   (s:settings.use_xhtml ? '/>' : '>')
+    wrapperfunc_lines += trim_tmp
+    if settings.use_input_for_pc !=# 'none'
+      trim_tmp =<< trim eval ENDLET
+         return_span ..= '<input' .. unselInputType .. ' class="' .. style_name .. {diffstyle}'"'
+         return_span ..= ' value="' .. substitute(unformatted, '\s\+$', "", "") .. '"'
+         return_span ..= " onselect='this.blur(); return false;'"
+         return_span ..= " onmousedown='this.blur(); return false;'"
+         return_span ..= " onclick='this.blur(); return false;'"
+         return_span ..= " readonly='readonly'"
+         return_span ..= ' size="' .. strwidth(unformatted) .. '"'
+         return_span ..= (settings.use_xhtml ? '/>' : '>')
       ENDLET
+      wrapperfunc_lines += trim_tmp
     endif
-    call add(s:wrapperfunc_lines, [])
-    let s:wrapperfunc_lines[-1] =<< trim eval ENDLET
-             return return_span..'</span>'
-           else
-             return "<span "..a:extra_attrs..'class="' .. l:style_name .. {s:diffstyle}'">'..a:text.."</span>"
-           endif
+    trim_tmp =<< trim eval ENDLET
+         return return_span .. '</span>'
+       else
+         return "<span " .. extra_attrs .. 'class="' .. style_name .. {diffstyle}'">' .. text .. "</span>"
+       endif
     ENDLET
+    wrapperfunc_lines += trim_tmp
   endif
-  call add(s:wrapperfunc_lines, [])
-  let s:wrapperfunc_lines[-1] =<< trim ENDLET
-         endif
-       endfun
+  trim_tmp =<< trim eval ENDLET
+      endif
+    enddef
   ENDLET
+  wrapperfunc_lines += trim_tmp
+
+  # create the function we built line by line above
+  execute join(wrapperfunc_lines, "\n")
 else
-  " Non-CSS method just needs the wrapper.
-  "
-  " Functions used to get opening/closing automatically return null strings if
-  " no styles exist.
+  # Non-CSS method just needs the wrapper.
+  #
+  # Functions used to get opening/closing automatically return null strings if
+  # no styles exist.
   if &diff
-    let s:wrapperfunc_lines =<< trim ENDLET
-       function! s:BuildStyleWrapper(style_id, diff_style_id, extra_attrs, text, unusedarg, unusedarg2)
-         if a:diff_style_id <= 0
-           let l:diff_opening = s:HtmlOpening(a:diff_style_id, "")
-           let l:diff_closing = s:HtmlClosing(a:diff_style_id, 0)
-         else
-           let l:diff_opening = ""
-           let l:diff_closing = ""
-         endif
-         return s:HtmlOpening(a:style_id, a:extra_attrs)..l:diff_opening..a:text..l:diff_closing..s:HtmlClosing(a:style_id, !empty(a:extra_attrs))
-       endfun
-    ENDLET
+    def BuildStyleWrapper(style_id: number, diff_style_id: number, extra_attrs: string, text: string, unusedarg: bool, unusedarg2: string): string
+      var diff_opening: string
+      var diff_closing: string
+      if diff_style_id > 0
+       diff_opening = HtmlOpening(diff_style_id, "")
+       diff_closing = HtmlClosing(diff_style_id, false)
+      endif
+      return HtmlOpening(style_id, extra_attrs) .. diff_opening .. text .. diff_closing .. HtmlClosing(style_id, !empty(extra_attrs))
+    enddef
   else
-    let s:wrapperfunc_lines =<< trim ENDLET
-       function! s:BuildStyleWrapper(style_id, diff_style_id, extra_attrs, text, unusedarg, unusedarg2)
-         return s:HtmlOpening(a:style_id, a:extra_attrs)..a:text..s:HtmlClosing(a:style_id, !empty(a:extra_attrs))
-       endfun
-    ENDLET
+    def BuildStyleWrapper(style_id: number, diff_style_id: number, extra_attrs: string, text: string, unusedarg: bool, unusedarg2: string): string
+      return HtmlOpening(style_id, extra_attrs) .. text .. HtmlClosing(style_id, !empty(extra_attrs))
+    enddef
   endif
 endif
 
-" create the function we built line by line above
-exec join(flatten(s:wrapperfunc_lines), "\n")
-
-let s:diff_mode = &diff
-
-" Return HTML valid characters enclosed in a span of class style_name with
-" unprintable characters expanded and double spaces replaced as necessary.
-"
-" TODO: eliminate unneeded logic like done for BuildStyleWrapper
-function! s:HtmlFormat(text, style_id, diff_style_id, extra_attrs, make_unselectable)
-  " Replace unprintable characters
-  let unformatted = strtrans(a:text)
-
-  let formatted = unformatted
-
-  " Replace the reserved html characters
-  let formatted = substitute(formatted, '&', '\&amp;',  'g')
-  let formatted = substitute(formatted, '<', '\&lt;',   'g')
-  let formatted = substitute(formatted, '>', '\&gt;',   'g')
-  let formatted = substitute(formatted, '"', '\&quot;', 'g')
-  " &apos; is not valid in HTML but it is in XHTML, so just use the numeric
-  " reference for it instead. Needed because it could appear in quotes
-  " especially if unselectable regions is turned on.
-  let formatted = substitute(formatted, '"', '\&#0039;', 'g')
-
-  " Replace a "form feed" character with HTML to do a page break
-  " TODO: need to prevent this in unselectable areas? Probably it should never
-  " BE in an unselectable area...
-  let formatted = substitute(formatted, "\x0c", '<hr class="PAGE-BREAK">', 'g')
-
-  " Replace double spaces, leading spaces, and trailing spaces if needed
-  if ' ' != s:HtmlSpace
-    let formatted = substitute(formatted, '  ', s:HtmlSpace .. s:HtmlSpace, 'g')
-    let formatted = substitute(formatted, '^ ', s:HtmlSpace, 'g')
-    let formatted = substitute(formatted, ' \+$', s:HtmlSpace, 'g')
+var diff_mode = &diff
+
+# Return HTML valid characters enclosed in a span of class style_name with
+# unprintable characters expanded and double spaces replaced as necessary.
+#
+# TODO: eliminate unneeded logic like done for BuildStyleWrapper
+def HtmlFormat(text: string, style_id: number, diff_style_id: number, extra_attrs: string, make_unselectable: bool): string
+  # Replace unprintable characters
+  var unformatted = strtrans(text)
+
+  var formatted = unformatted
+
+  # Replace the reserved html characters
+  formatted = substitute(formatted, '&', '\&amp;',  'g')
+  formatted = substitute(formatted, '<', '\&lt;',   'g')
+  formatted = substitute(formatted, '>', '\&gt;',   'g')
+  formatted = substitute(formatted, '"', '\&quot;', 'g')
+  # &apos; is not valid in HTML but it is in XHTML, so just use the numeric
+  # reference for it instead. Needed because it could appear in quotes
+  # especially if unselectable regions is turned on.
+  formatted = substitute(formatted, '"', '\&#0039;', 'g')
+
+  # Replace a "form feed" character with HTML to do a page break
+  # TODO: need to prevent this in unselectable areas? Probably it should never
+  # BE in an unselectable area...
+  formatted = substitute(formatted, "\x0c", '<hr class="PAGE-BREAK">', 'g')
+
+  # Replace double spaces, leading spaces, and trailing spaces if needed
+  if ' ' != HtmlSpace
+    formatted = substitute(formatted, '  ', HtmlSpace .. HtmlSpace, 'g')
+    formatted = substitute(formatted, '^ ', HtmlSpace, 'g')
+    formatted = substitute(formatted, ' \+$', HtmlSpace, 'g')
   endif
 
-  " Enclose in the correct format
-  return s:BuildStyleWrapper(a:style_id, a:diff_style_id, a:extra_attrs, formatted, a:make_unselectable, unformatted)
-endfun
-
-" set up functions to call HtmlFormat in certain ways based on whether the
-" element is supposed to be unselectable or not
-if s:settings.prevent_copy =~# 'n'
-  if s:settings.number_lines
-    if s:settings.line_ids
-      function! s:HtmlFormat_n(text, style_id, diff_style_id, lnr)
-       if a:lnr > 0
-         return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, 'id="'..(exists('g:html_diff_win_num') ? 'W'..g:html_diff_win_num : "")..'L'..a:lnr..s:settings.id_suffix..'" ', 1)
+  # Enclose in the correct format
+  return BuildStyleWrapper(style_id, diff_style_id, extra_attrs, formatted, make_unselectable, unformatted)
+enddef
+
+# set up functions to call HtmlFormat in certain ways based on whether the
+# element is supposed to be unselectable or not
+if settings.prevent_copy =~# 'n'
+  if settings.number_lines
+    if settings.line_ids
+      def HtmlFormat_n(text: string, style_id: number, diff_style_id: number, lnr: number): string
+       if lnr > 0
+         return HtmlFormat(text, style_id, diff_style_id, 'id="' .. (exists('g:html_diff_win_num') ? 'W' .. g:html_diff_win_num : "") .. 'L' .. lnr .. settings.id_suffix .. '" ', true)
        else
-         return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, "", 1)
+         return HtmlFormat(text, style_id, diff_style_id, "", true)
        endif
-      endfun
+      enddef
     else
-      function! s:HtmlFormat_n(text, style_id, diff_style_id, lnr)
-       return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, "", 1)
-      endfun
+      def HtmlFormat_n(text: string, style_id: number, diff_style_id: number, lnr: number): string
+       return HtmlFormat(text, style_id, diff_style_id, "", true)
+      enddef
     endif
-  elseif s:settings.line_ids
-    " if lines are not being numbered the only reason this function gets called
-    " is to put the line IDs on each line; "text" will be empty but lnr will
-    " always be non-zero, however we don't want to use the <input> because that
-    " won't work as nice for empty text
-    function! s:HtmlFormat_n(text, style_id, diff_style_id, lnr)
-      return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, 'id="'..(exists('g:html_diff_win_num') ? 'W'..g:html_diff_win_num : "")..'L'..a:lnr..s:settings.id_suffix..'" ', 0)
-    endfun
+  elseif settings.line_ids
+    # if lines are not being numbered the only reason this function gets called
+    # is to put the line IDs on each line; "text" will be empty but lnr will
+    # always be non-zero, however we don't want to use the <input> because that
+    # won't work as nice for empty text
+    def HtmlFormat_n(text: string, style_id: number, diff_style_id: number, lnr: number): string
+      return HtmlFormat(text, style_id, diff_style_id, 'id="' .. (exists('g:html_diff_win_num') ? 'W' .. g:html_diff_win_num : "") .. 'L' .. lnr .. settings.id_suffix .. '" ', false)
+    enddef
   endif
 else
-  if s:settings.line_ids
-    function! s:HtmlFormat_n(text, style_id, diff_style_id, lnr)
-      if a:lnr > 0
-       return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, 'id="'..(exists('g:html_diff_win_num') ? 'W'..g:html_diff_win_num : "")..'L'..a:lnr..s:settings.id_suffix..'" ', 0)
+  if settings.line_ids
+    def HtmlFormat_n(text: string, style_id: number, diff_style_id: number, lnr: number): string
+      if lnr > 0
+       return HtmlFormat(text, style_id, diff_style_id, 'id="' .. (exists('g:html_diff_win_num') ? 'W' .. g:html_diff_win_num : "") .. 'L' .. lnr .. settings.id_suffix .. '" ', false)
       else
-       return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, "", 0)
+       return HtmlFormat(text, style_id, diff_style_id, "", false)
       endif
-    endfun
+    enddef
   else
-    function! s:HtmlFormat_n(text, style_id, diff_style_id, lnr)
-      return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, "", 0)
-    endfun
+    def HtmlFormat_n(text: string, style_id: number, diff_style_id: number, lnr: number): string
+      return HtmlFormat(text, style_id, diff_style_id, "", false)
+    enddef
   endif
 endif
-if s:settings.prevent_copy =~# 'd'
-  function! s:HtmlFormat_d(text, style_id, diff_style_id)
-    return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, "", 1)
-  endfun
+
+if settings.prevent_copy =~# 'd'
+  def HtmlFormat_d(text: string, style_id: number, diff_style_id: number): string
+    return HtmlFormat(text, style_id, diff_style_id, "", true)
+  enddef
 else
-  function! s:HtmlFormat_d(text, style_id, diff_style_id)
-    return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, "", 0)
-  endfun
+  def HtmlFormat_d(text: string, style_id: number, diff_style_id: number): string
+    return HtmlFormat(text, style_id, diff_style_id, "", false)
+  enddef
 endif
-if s:settings.prevent_copy =~# 'f'
-  if s:settings.use_input_for_pc ==# 'none'
-    " Simply space-pad to the desired width inside the generated content (note
-    " that the FoldColumn definition includes a whitespace:pre rule)
-    function! s:FoldColumn_build(char, len, numfill, char2, class, click)
-      return "<a href='#' class='"..a:class.."' onclick='"..a:click.."' data-FoldColumn-content='".
-           \ repeat(a:char, a:len)..a:char2..repeat(' ', a:numfill).
-           \ "'></a>"
-    endfun
-    function! s:FoldColumn_fill()
-      return s:HtmlFormat(repeat(' ', s:foldcolumn), s:FOLD_C_ID, 0, "", 1)
-    endfun
+if settings.prevent_copy =~# 'f'
+  if settings.use_input_for_pc ==# 'none'
+    # If not using the method at all, define functions that will simply
+    # space-pad to the desired width inside the generated content (note that
+    # the FoldColumn definition includes a whitespace:pre rule)
+    def FoldColumn_build(char: string, len: number, numfill: number, char2: string, class: string, click: string): string
+      return "<a href='#' class='" .. class .. "' onclick='" .. click .. "' data-FoldColumn-content='" ..
+       repeat(char, len) .. char2 .. repeat(' ', numfill) ..
+       "'></a>"
+    enddef
+    def FoldColumn_fill(): string
+      return HtmlFormat(repeat(' ', foldcolumn), FOLD_C_ID, 0, "", true)
+    enddef
   else
-    " Note the <input> elements for fill spaces will have a single space for
-    " content, to allow active cursor CSS selection to work.
-    "
-    " Wrap the whole thing in a span for the 1px padding workaround for gaps.
-    "
-    " Build the function line by line containing only what is needed for the
-    " options in use for maximum code sharing with minimal branch logic for
-    " greater speed.
-    "
-    " Note, 'exec' commands do not recognize line continuations, so must
-    " concatenate lines rather than continue them.
-    let s:build_fun_lines = []
-    call add(s:build_fun_lines, [])
-    let s:build_fun_lines[-1] =<< trim ENDLET
-         function! s:FoldColumn_build(char, len, numfill, char2, class, click)
-           let l:input_open = "<input readonly='readonly'"..s:unselInputType
-           let l:input_open ..= " onselect='this.blur(); return false;'"
-           let l:input_open ..= " onmousedown='this.blur(); "..a:click.." return false;'"
-           let l:input_open ..= " onclick='return false;' size='"
-           let l:input_open ..= string(a:len + (empty(a:char2) ? 0 : 1) + a:numfill) .. "' "
-           let l:common_attrs = "class='FoldColumn' value='"
-           let l:input_close = (s:settings.use_xhtml ? "' />" : "'>")
-           let l:return_span = "<span class='"..a:class.."'>"
-           let l:return_span ..= l:input_open..l:common_attrs..repeat(a:char, a:len)..(a:char2)
-           let l:return_span ..= l:input_close
+    # Note the <input> elements for fill spaces will have a single space for
+    # content, to allow active cursor CSS selection to work.
+    #
+    # Wrap the whole thing in a span for the 1px padding workaround for gaps.
+    #
+    # Build the function line by line containing only what is needed for the
+    # options in use for maximum code sharing with minimal branch logic for
+    # greater speed.
+    #
+    # Note, 'exec' commands do not recognize line continuations, so must
+    # concatenate lines rather than continue them.
+    var build_fun_lines: list<string> = []
+    build_fun_lines =<< trim ENDLET
+      def FoldColumn_build(char: string, len: number, numfill: number, char2: string, class: string, click: string): string
+       var input_open = "<input readonly='readonly'" .. unselInputType
+       input_open ..= " onselect='this.blur(); return false;'"
+       input_open ..= " onmousedown='this.blur(); " .. click .. " return false;'"
+       input_open ..= " onclick='return false;' size='"
+       input_open ..= string(len + (empty(char2) ? 0 : 1) + numfill) .. "' "
+       var common_attrs = "class='FoldColumn' value='"
+       var input_close = settings.use_xhtml ? "' />" : "'>"
+       var return_span = "<span class='" .. class .. "'>"
+       return_span ..= input_open .. common_attrs .. repeat(char, len) .. char2
+       return_span ..= input_close
     ENDLET
-    if s:settings.use_input_for_pc ==# 'fallback'
-      call add(s:build_fun_lines, [])
-      let s:build_fun_lines[-1] =<< trim ENDLET
-           let l:return_span ..= "<a href='#' class='FoldColumn' onclick='"..a:click.."'"
-           let l:return_span ..= " data-FoldColumn-content='"
-           let l:return_span ..= repeat(a:char, a:len)..a:char2..repeat(' ', a:numfill)
-           let l:return_span ..= "'></a>"
+    if settings.use_input_for_pc ==# 'fallback'
+      trim_tmp =<< trim ENDLET
+       return_span ..= "<a href='#' class='FoldColumn' onclick='" .. click .. "'"
+       return_span ..= " data-FoldColumn-content='"
+       return_span ..= repeat(char, len) .. char2 .. repeat(' ', numfill)
+       return_span ..= "'></a>"
       ENDLET
+      build_fun_lines += trim_tmp
     endif
-    call add(s:build_fun_lines, [])
-    let s:build_fun_lines[-1] =<< trim ENDLET
-           let l:return_span ..= "</span>"
-           return l:return_span
-         endfun
+    trim_tmp =<< trim ENDLET
+       return_span ..= "</span>"
+       return return_span
+      enddef
     ENDLET
-    " create the function we built line by line above
-    exec join(flatten(s:build_fun_lines), "\n")
+    build_fun_lines += trim_tmp
+    execute join(build_fun_lines, "\n")
 
-    function! s:FoldColumn_fill()
-      return s:FoldColumn_build(' ', s:foldcolumn, 0, '', 'FoldColumn', '')
-    endfun
+    def FoldColumn_fill(): string
+      return FoldColumn_build(' ', foldcolumn, 0, '', 'FoldColumn', '')
+    enddef
   endif
 else
-  " For normal fold columns, simply space-pad to the desired width (note that
-  " the FoldColumn definition includes a whitespace:pre rule)
-  function! s:FoldColumn_build(char, len, numfill, char2, class, click)
-    return "<a href='#' class='"..a:class.."' onclick='"..a:click.."'>".
-         \ repeat(a:char, a:len)..a:char2..repeat(' ', a:numfill).
-         \ "</a>"
-  endfun
-  function! s:FoldColumn_fill()
-    return s:HtmlFormat(repeat(' ', s:foldcolumn), s:FOLD_C_ID, 0, "", 0)
-  endfun
+  # For normal fold columns, simply space-pad to the desired width (note that
+  # the FoldColumn definition includes a whitespace:pre rule)
+  def FoldColumn_build(char: string, len: number, numfill: number, char2: string, class: string, click: string): string
+    return "<a href='#' class='" .. class .. "' onclick='" .. click .. "'>" ..
+      repeat(char, len) .. char2 .. repeat(' ', numfill) ..
+      "</a>"
+  enddef
+  def FoldColumn_fill(): string
+    return HtmlFormat(repeat(' ', foldcolumn), FOLD_C_ID, 0, "", false)
+  enddef
 endif
-if s:settings.prevent_copy =~# 't'
-  " put an extra empty span at the end for dynamic folds, so the linebreak can
-  " be surrounded. Otherwise do it as normal.
-  "
-  " TODO: isn't there a better way to do this, than placing it here and using a
-  " substitute later?
-  if s:settings.dynamic_folds
-    function! s:HtmlFormat_t(text, style_id, diff_style_id)
-      return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, "", 1) .
-           \ s:HtmlFormat("", a:style_id, 0, "", 0)
-    endfun
+if settings.prevent_copy =~# 't'
+  # put an extra empty span at the end for dynamic folds, so the linebreak can
+  # be surrounded. Otherwise do it as normal.
+  #
+  # TODO: isn't there a better way to do this, than placing it here and using a
+  # substitute later?
+  if settings.dynamic_folds
+    def HtmlFormat_t(text: string, style_id: number, diff_style_id: number): string
+      return HtmlFormat(text, style_id, diff_style_id, "", true) ..
+       HtmlFormat("", style_id, 0, "", false)
+    enddef
   else
-    function! s:HtmlFormat_t(text, style_id, diff_style_id)
-      return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, "", 1)
-    endfun
+    def HtmlFormat_t(text: string, style_id: number, diff_style_id: number): string
+      return HtmlFormat(text, style_id, diff_style_id, "", true)
+    enddef
   endif
 else
-  function! s:HtmlFormat_t(text, style_id, diff_style_id)
-    return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, "", 0)
-  endfun
+  def HtmlFormat_t(text: string, style_id: number, diff_style_id: number): string
+    return HtmlFormat(text, style_id, diff_style_id, "", false)
+  enddef
 endif
 
-" Return CSS style describing given highlight id (can be empty)
-function! s:CSS1(id)
-  let a = ""
-  let translated_ID = synIDtrans(a:id)
-  if synIDattr(translated_ID, "inverse")
-    " For inverse, we always must set both colors (and exchange them)
-    let x = s:HtmlColor(synIDattr(translated_ID, "bg#", s:whatterm))
-    let a = a .. "color: " .. ( x != "" ? x : s:bgc ) .. "; "
-    let x = s:HtmlColor(synIDattr(translated_ID, "fg#", s:whatterm))
-    let a = a .. "background-color: " .. ( x != "" ? x : s:fgc ) .. "; "
+# Return CSS style describing given highlight id (can be empty)
+def CSS1(id: number): string
+  var a = ""
+  var translated_ID = synIDtrans(id)
+  if synIDattr(translated_ID, "inverse") == '1'
+    # For inverse, we always must set both colors (and exchange them)
+    var x = HtmlColor(synIDattr(translated_ID, "bg#", whatterm))
+    a ..= "color: " .. (x != "" ? x : bgc) .. "; "
+    x = HtmlColor(synIDattr(translated_ID, "fg#", whatterm))
+    a ..= "background-color: " .. (x != "" ? x : fgc) .. "; "
   else
-    let x = s:HtmlColor(synIDattr(translated_ID, "fg#", s:whatterm))
-    if x != "" | let a = a .. "color: " .. x .. "; " | endif
-    let x = s:HtmlColor(synIDattr(translated_ID, "bg#", s:whatterm))
+    var x = HtmlColor(synIDattr(translated_ID, "fg#", whatterm))
+    if x != "" | a ..= "color: " .. x .. "; " | endif
+    x = HtmlColor(synIDattr(translated_ID, "bg#", whatterm))
     if x != ""
-      let a = a .. "background-color: " .. x .. "; "
-      " stupid hack because almost every browser seems to have at least one font
-      " which shows 1px gaps between lines which have background
-      let a = a .. "padding-bottom: 1px; "
-    elseif (translated_ID == s:FOLDED_ID || translated_ID == s:LINENR_ID || translated_ID == s:FOLD_C_ID) && !empty(s:settings.prevent_copy)
-      " input elements default to a different color than the rest of the page
-      let a = a .. "background-color: " .. s:bgc .. "; "
+      a ..= "background-color: " .. x .. "; "
+      # stupid hack because almost every browser seems to have at least one font
+      # which shows 1px gaps between lines which have background
+      a ..= "padding-bottom: 1px; "
+    elseif (translated_ID == FOLDED_ID || translated_ID == LINENR_ID || translated_ID == FOLD_C_ID) && !empty(settings.prevent_copy)
+      # input elements default to a different color than the rest of the page
+      a ..= "background-color: " .. bgc .. "; "
     endif
   endif
-  if synIDattr(translated_ID, "bold") | let a = a .. "font-weight: bold; " | endif
-  if synIDattr(translated_ID, "italic") | let a = a .. "font-style: italic; " | endif
-  if synIDattr(translated_ID, "underline") | let a = a .. "text-decoration: underline; " | endif
+  if synIDattr(translated_ID, "bold") == '1' | a ..= "font-weight: bold; " | endif
+  if synIDattr(translated_ID, "italic") == '1' | a ..= "font-style: italic; " | endif
+  if synIDattr(translated_ID, "underline") == '1' | a ..= "text-decoration: underline; " | endif
   return a
-endfun
-
-if s:settings.dynamic_folds
-  " compares two folds as stored in our list of folds
-  " A fold is "less" than another if it starts at an earlier line number,
-  " or ends at a later line number, ties broken by fold level
-  function! s:FoldCompare(f1, f2)
-    if a:f1.firstline != a:f2.firstline
-      " put it before if it starts earlier
-      return a:f1.firstline - a:f2.firstline
-    elseif a:f1.lastline != a:f2.lastline
-      " put it before if it ends later
-      return a:f2.lastline - a:f1.lastline
+enddef
+
+if settings.dynamic_folds
+  # compares two folds as stored in our list of folds
+  # A fold is "less" than another if it starts at an earlier line number,
+  # or ends at a later line number, ties broken by fold level
+  def FoldCompare(f1: dict<any>, f2: dict<any>): number
+    if f1.firstline != f2.firstline
+      # put it before if it starts earlier
+      return f1.firstline - f2.firstline
+    elseif f1.lastline != f2.lastline
+      # put it before if it ends later
+      return f2.lastline - f1.lastline
     else
-      " if folds begin and end on the same lines, put lowest fold level first
-      return a:f1.level - a:f2.level
+      # if folds begin and end on the same lines, put lowest fold level first
+      return f1.level - f2.level
     endif
-  endfunction
-
+  enddef
 endif
 
 
-" Set some options to make it work faster.
-" Don't report changes for :substitute, there will be many of them.
-" Don't change other windows; turn off scroll bind temporarily
-let s:old_title = &title
-let s:old_icon = &icon
-let s:old_et = &l:et
-let s:old_bind = &l:scrollbind
-let s:old_report = &report
-let s:old_search = @/
-let s:old_more = &more
+# Set some options to make it work faster.
+# Don't report changes for :substitute, there will be many of them.
+# Don't change other windows; turn off scroll bind temporarily
+var old_title = &title
+var old_icon = &icon
+var old_et = &l:et
+var old_bind = &l:scrollbind
+var old_report = &report
+var old_search = @/
+var old_more = &more
 set notitle noicon
 setlocal et
 set nomore
 set report=1000000
 setlocal noscrollbind
 
-if exists(':ownsyntax') && exists('w:current_syntax')
-  let s:current_syntax = w:current_syntax
+var current_syntax: string
+if exists(':ownsyntax') == 2 && exists('w:current_syntax')
+  current_syntax = w:current_syntax
 elseif exists('b:current_syntax')
-  let s:current_syntax = b:current_syntax
+  current_syntax = b:current_syntax
 else
-  let s:current_syntax = 'none'
+  current_syntax = 'none'
 endif
 
-if s:current_syntax == ''
-  let s:current_syntax = 'none'
+if current_syntax == ''
+  current_syntax = 'none'
 endif
 
-" If the user is sourcing this script directly then the plugin version isn't
-" known because the main plugin script didn't load. In the usual case where the
-" user still has the full Vim runtime installed, or has this full plugin
-" installed in a package or something, then we can extract the version from the
-" main plugin file at it's usual spot relative to this file. Otherwise the user
-" is assembling their runtime piecemeal and we have no idea what versions of
-" other files may be present so don't even try to make a guess or assume the
-" presence of other specific files with specific meaning.
-"
-" We don't want to actually source the main plugin file here because the user
-" may have a good reason not to (e.g. they define their own TOhtml command or
-" something).
-"
-" If this seems way too complicated and convoluted, it is. Probably I should
-" have put the version information in the autoload file from the start. But the
-" version has been in the global variable for so long that changing it could
-" break a lot of user scripts.
+# If the user is sourcing this script directly then the plugin version isn't
+# known because the main plugin script didn't load. In the usual case where the
+# user still has the full Vim runtime installed, or has this full plugin
+# installed in a package or something, then we can extract the version from the
+# main plugin file at it's usual spot relative to this file. Otherwise the user
+# is assembling their runtime piecemeal and we have no idea what versions of
+# other files may be present so don't even try to make a guess or assume the
+# presence of other specific files with specific meaning.
+#
+# We don't want to actually source the main plugin file here because the user
+# may have a good reason not to (e.g. they define their own TOhtml command or
+# something).
+#
+# If this seems way too complicated and convoluted, it is. Probably I should
+# have put the version information in the autoload file from the start. But the
+# version has been in the global variable for so long that changing it could
+# break a lot of user scripts.
+var pluginversion: string
 if exists("g:loaded_2html_plugin")
-  let s:pluginversion = g:loaded_2html_plugin
+  pluginversion = g:loaded_2html_plugin
 else
   if !exists("g:unloaded_tohtml_plugin")
-    let s:main_plugin_path = expand("<sfile>:p:h:h").."/plugin/tohtml.vim"
-    if filereadable(s:main_plugin_path)
-      let s:lines = readfile(s:main_plugin_path, "", 20)
-      call filter(s:lines, 'v:val =~ "loaded_2html_plugin = "')
-      if empty(s:lines)
-       let g:unloaded_tohtml_plugin = "unknown"
+    var main_plugin_path = expand("<sfile>:p:h:h") .. "/plugin/tohtml.vim"
+    if filereadable(main_plugin_path)
+      var plugin_lines = readfile(main_plugin_path, "", 20)
+      filter(plugin_lines, (_, val) => val =~ "loaded_2html_plugin = ")
+      if empty(plugin_lines)
+       g:unloaded_tohtml_plugin = "unknown"
       else
-       let g:unloaded_tohtml_plugin = substitute(s:lines[0], '.*loaded_2html_plugin = \([''"]\)\(\%(\1\@!.\)\+\)\1', '\2', '')
+       g:unloaded_tohtml_plugin = substitute(plugin_lines[0], '.*loaded_2html_plugin = \([''"]\)\(\%(\1\@!.\)\+\)\1', '\2', '')
       endif
-      unlet s:lines
     else
-      let g:unloaded_tohtml_plugin = "unknown"
+      g:unloaded_tohtml_plugin = "unknown"
     endif
-    unlet s:main_plugin_path
   endif
-  let s:pluginversion = g:unloaded_tohtml_plugin
+  pluginversion = g:unloaded_tohtml_plugin
 endif
 
-" Split window to create a buffer with the HTML file.
-let s:orgbufnr = winbufnr(0)
-let s:origwin_stl = &l:stl
+# Split window to create a buffer with the HTML file.
+var orgbufnr = winbufnr(0)
+var origwin_stl = &l:stl
 if expand("%") == ""
   if exists('g:html_diff_win_num')
-    exec 'new Untitled_win'..g:html_diff_win_num..'.'.(s:settings.use_xhtml ? 'xhtml' : 'html')
+    execute 'new Untitled_win' .. g:html_diff_win_num .. '.' .. (settings.use_xhtml ? 'xhtml' : 'html')
   else
-    exec 'new Untitled.'..(s:settings.use_xhtml ? 'xhtml' : 'html')
+    execute 'new Untitled.' .. (settings.use_xhtml ? 'xhtml' : 'html')
   endif
 else
-  exec 'new %.'..(s:settings.use_xhtml ? 'xhtml' : 'html')
+  execute 'new %.' .. (settings.use_xhtml ? 'xhtml' : 'html')
 endif
 
-" Resize the new window to very small in order to make it draw faster
-let s:old_winheight = winheight(0)
-let s:old_winfixheight = &l:winfixheight
-if s:old_winheight > 2
-  resize 1 " leave enough room to view one line at a time
+# Resize the new window to very small in order to make it draw faster
+var old_winheight = winheight(0)
+var old_winfixheight = &l:winfixheight
+if old_winheight > 2
+  resize 1 # leave enough room to view one line at a time
   norm! G
   norm! zt
 endif
 setlocal winfixheight
 
-let s:newwin_stl = &l:stl
+var newwin_stl = &l:stl
 
-" on the new window, set the least time-consuming fold method
-let s:old_fen = &foldenable
+# on the new window, set the least time-consuming fold method
+var old_fen = &foldenable
 setlocal foldmethod=manual
 setlocal nofoldenable
 
-let s:newwin = winnr()
-let s:orgwin = bufwinnr(s:orgbufnr)
+var newwin = winnr()
+var orgwin = bufwinnr(orgbufnr)
 
 setlocal modifiable
-%d
-let s:old_paste = &paste
+:%d
+var old_paste = &paste
 set paste
-let s:old_magic = &magic
+var old_magic = &magic
 set magic
 
-" set the fileencoding to match the charset we'll be using
-let &l:fileencoding=s:settings.vim_encoding
+# set the fileencoding to match the charset we'll be using
+&l:fileencoding = settings.vim_encoding
 
-" According to http://www.w3.org/TR/html4/charset.html#doc-char-set, the byte
-" order mark is highly recommend on the web when using multibyte encodings. But,
-" it is not a good idea to include it on UTF-8 files. Otherwise, let Vim
-" determine when it is actually inserted.
-if s:settings.vim_encoding == 'utf-8'
+# According to http://www.w3.org/TR/html4/charset.html#doc-char-set, the byte
+# order mark is highly recommend on the web when using multibyte encodings. But,
+# it is not a good idea to include it on UTF-8 files. Otherwise, let Vim
+# determine when it is actually inserted.
+if settings.vim_encoding == 'utf-8'
   setlocal nobomb
 else
   setlocal bomb
 endif
 
-let s:lines = []
+var lines = []
 
-if s:settings.use_xhtml
-  if s:settings.encoding != ""
-    call add(s:lines, "<?xml version=\"1.0\" encoding=\"" .. s:settings.encoding .. "\"?>")
+var tag_close: string
+if settings.use_xhtml
+  if settings.encoding != ""
+    add(lines, "<?xml version=\"1.0\" encoding=\"" .. settings.encoding .. "\"?>")
   else
-    call add(s:lines, "<?xml version=\"1.0\"?>")
+    add(lines, "<?xml version=\"1.0\"?>")
   endif
-  let s:tag_close = ' />'
+  tag_close = ' />'
 else
-  let s:tag_close = '>'
+  tag_close = '>'
 endif
 
-let s:HtmlSpace = ' '
-let s:LeadingSpace = ' '
-let s:HtmlEndline = ''
-if s:settings.no_pre
-  let s:HtmlEndline = '<br' .. s:tag_close
-  let s:LeadingSpace = s:settings.use_xhtml ? '&#160;' : '&nbsp;'
-  let s:HtmlSpace = '\' .. s:LeadingSpace
+var HtmlSpace = ' '
+var LeadingSpace = ' '
+var HtmlEndline = ''
+if settings.no_pre
+  HtmlEndline = '<br' .. tag_close
+  LeadingSpace = settings.use_xhtml ? '&#160;' : '&nbsp;'
+  HtmlSpace = '\' .. LeadingSpace
 endif
 
-" HTML header, with the title and generator ;-). Left free space for the CSS,
-" to be filled at the end.
-if !s:settings.no_doc
-  call extend(s:lines, [
-       \ "<html>",
-       \ "<head>"])
-  " include encoding as close to the top as possible, but only if not already
-  " contained in XML information (to avoid haggling over content type)
-  if s:settings.encoding != "" && !s:settings.use_xhtml
-    if s:html5
-      call add(s:lines, '<meta charset="' .. s:settings.encoding .. '"' .. s:tag_close)
+# HTML header, with the title and generator ;-). Left free space for the CSS,
+# to be filled at the end.
+if !settings.no_doc
+  extend(lines, [
+    "<html>",
+    "<head>"])
+  # include encoding as close to the top as possible, but only if not already
+  # contained in XML information (to avoid haggling over content type)
+  if settings.encoding != "" && !settings.use_xhtml
+    if html5
+      add(lines, '<meta charset="' .. settings.encoding .. '"' .. tag_close)
     else
-      call add(s:lines, "<meta http-equiv=\"content-type\" content=\"text/html; charset=" .. s:settings.encoding .. '"' .. s:tag_close)
+      add(lines, "<meta http-equiv=\"content-type\" content=\"text/html; charset=" .. settings.encoding .. '"' .. tag_close)
     endif
   endif
-  call extend(s:lines, [
-       \ ("<title>"..expand("%:p:~").."</title>"),
-       \ ("<meta name=\"Generator\" content=\"Vim/"..v:version/100.."."..v:version%100..'"'..s:tag_close),
-       \ ("<meta name=\"plugin-version\" content=\""..s:pluginversion..'"'..s:tag_close)
-       \ ])
-  call add(s:lines, '<meta name="syntax" content="'..s:current_syntax..'"'..s:tag_close)
-  call add(s:lines, '<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..
-       \ '"'..s:tag_close)
-  call add(s:lines, '<meta name="colorscheme" content="'..
-       \ (exists('g:colors_name')
-       \ ? g:colors_name
-       \ : 'none').. '"'..s:tag_close)
-
-  if s:settings.use_css
-    call extend(s:lines, [
-         \ "<style" .. (s:html5 ? "" : " type=\"text/css\"") .. ">",
-         \ s:settings.use_xhtml ? "" : "<!--"])
-    let s:ieonly = []
-    if s:settings.dynamic_folds
-      if s:settings.hover_unfold
-       " if we are doing hover_unfold, use css 2 with css 1 fallback for IE6
-       call extend(s:lines, [
-             \ ".FoldColumn { text-decoration: none; white-space: pre; }",
-             \ "",
-             \ "body * { margin: 0; padding: 0; }", "",
-             \ ".open-fold   > span.Folded { display: none;  }",
-             \ ".open-fold   > .fulltext   { display: inline; }",
-             \ ".closed-fold > .fulltext   { display: none;  }",
-             \ ".closed-fold > span.Folded { display: inline; }",
-             \ "",
-             \ ".open-fold   > .toggle-open   { display: none;   }",
-             \ ".open-fold   > .toggle-closed { display: inline; }",
-             \ ".closed-fold > .toggle-open   { display: inline; }",
-             \ ".closed-fold > .toggle-closed { display: none;   }",
-             \ "", "",
-             \ '/* opening a fold while hovering won''t be supported by IE6 and other',
-             \ "similar browsers, but it should fail gracefully. */",
-             \ ".closed-fold:hover > .fulltext      { display: inline; }",
-             \ ".closed-fold:hover > .toggle-filler { display: none; }",
-             \ ".closed-fold:hover > .Folded        { display: none; }"])
-       " TODO: IE6 is REALLY old and I can't even test it anymore. Maybe we
-       " should remove this? Leave it in for now, it was working at one point,
-       " and doesn't affect any modern browsers. Even newer IE versions should
-       " support the above code and ignore the following.
-       let s:ieonly = [
-             \ "<!--[if lt IE 7]><style type=\"text/css\">",
-             \ ".open-fold   .fulltext      { display: inline; }",
-             \ ".open-fold   span.Folded    { display: none; }",
-             \ ".open-fold   .toggle-open   { display: none; }",
-             \ ".open-fold   .toggle-closed { display: inline; }",
-             \ "",
-             \ ".closed-fold .fulltext      { display: none; }",
-             \ ".closed-fold span.Folded    { display: inline; }",
-             \ ".closed-fold .toggle-open   { display: inline; }",
-             \ ".closed-fold .toggle-closed { display: none; }",
-             \ "</style>",
-             \ "<![endif]-->",
-             \]
+  extend(lines, [
+    ("<title>" .. expand("%:p:~") .. "</title>"),
+    ($'<meta name="Generator" content="Vim/{v:version / 100}.{v:version % 100}"{tag_close}'),
+    ("<meta name=\"plugin-version\" content=\"" .. pluginversion .. '"' .. tag_close)
+  ])
+  add(lines, '<meta name="syntax" content="' .. current_syntax .. '"' .. tag_close)
+  add(lines, '<meta name="settings" content="' ..
+    join(filter(keys(settings), (_, k) => {
+      var v = settings[k]
+      if type(v) == v:t_number
+       return v != 0
+      elseif type(v) == v:t_bool
+       return !empty(v)
+      else
+       return false
+      endif
+    }), ',') ..
+    ',prevent_copy=' .. settings.prevent_copy ..
+    ',use_input_for_pc=' .. settings.use_input_for_pc ..
+    '"' .. tag_close)
+  add(lines, '<meta name="colorscheme" content="' ..
+    (exists('g:colors_name')
+    ? g:colors_name
+    : 'none') .. '"' .. tag_close)
+
+  if settings.use_css
+    extend(lines, [
+      "<style" .. (html5 ? "" : " type=\"text/css\"") .. ">",
+      settings.use_xhtml ? "" : "<!--"])
+    var ieonly: list<string> = []
+    if settings.dynamic_folds
+      if settings.hover_unfold
+       # if we are doing hover_unfold, use css 2 with css 1 fallback for IE6
+       extend(lines, [
+         ".FoldColumn { text-decoration: none; white-space: pre; }",
+         "",
+         "body * { margin: 0; padding: 0; }", "",
+         ".open-fold   > span.Folded { display: none;  }",
+         ".open-fold   > .fulltext   { display: inline; }",
+         ".closed-fold > .fulltext   { display: none;  }",
+         ".closed-fold > span.Folded { display: inline; }",
+         "",
+         ".open-fold   > .toggle-open   { display: none;   }",
+         ".open-fold   > .toggle-closed { display: inline; }",
+         ".closed-fold > .toggle-open   { display: inline; }",
+         ".closed-fold > .toggle-closed { display: none;   }",
+         "", "",
+         '/* opening a fold while hovering won''t be supported by IE6 and other',
+         "similar browsers, but it should fail gracefully. */",
+         ".closed-fold:hover > .fulltext      { display: inline; }",
+         ".closed-fold:hover > .toggle-filler { display: none; }",
+         ".closed-fold:hover > .Folded        { display: none; }"])
+       # TODO: IE6 is REALLY old and I can't even test it anymore. Maybe we
+       # should remove this? Leave it in for now, it was working at one point,
+       # and doesn't affect any modern browsers. Even newer IE versions should
+       # support the above code and ignore the following.
+       ieonly = [
+         "<!--[if lt IE 7]><style type=\"text/css\">",
+         ".open-fold   .fulltext      { display: inline; }",
+         ".open-fold   span.Folded    { display: none; }",
+         ".open-fold   .toggle-open   { display: none; }",
+         ".open-fold   .toggle-closed { display: inline; }",
+         "",
+         ".closed-fold .fulltext      { display: none; }",
+         ".closed-fold span.Folded    { display: inline; }",
+         ".closed-fold .toggle-open   { display: inline; }",
+         ".closed-fold .toggle-closed { display: none; }",
+         "</style>",
+         "<![endif]-->",
+       ]
       else
-       " if we aren't doing hover_unfold, use CSS 1 only
-       call extend(s:lines, [
-             \ ".FoldColumn { text-decoration: none; white-space: pre; }",
-             \ ".open-fold   .fulltext      { display: inline; }",
-             \ ".open-fold   span.Folded    { display: none; }",
-             \ ".open-fold   .toggle-open   { display: none; }",
-             \ ".open-fold   .toggle-closed { display: inline; }",
-             \ "",
-             \ ".closed-fold .fulltext      { display: none; }",
-             \ ".closed-fold span.Folded    { display: inline; }",
-             \ ".closed-fold .toggle-open   { display: inline; }",
-             \ ".closed-fold .toggle-closed { display: none; }",
-             \])
+       # if we aren't doing hover_unfold, use CSS 1 only
+       extend(lines, [
+         ".FoldColumn { text-decoration: none; white-space: pre; }",
+         ".open-fold   .fulltext      { display: inline; }",
+         ".open-fold   span.Folded    { display: none; }",
+         ".open-fold   .toggle-open   { display: none; }",
+         ".open-fold   .toggle-closed { display: inline; }",
+         "",
+         ".closed-fold .fulltext      { display: none; }",
+         ".closed-fold span.Folded    { display: inline; }",
+         ".closed-fold .toggle-open   { display: inline; }",
+         ".closed-fold .toggle-closed { display: none; }",
+       ])
       endif
     endif
-    " else we aren't doing any dynamic folding, no need for any special rules
-
-    call extend(s:lines, [
-           \ s:settings.use_xhtml ? "" : '-->',
-           \ "</style>",
-           \])
-    call extend(s:lines, s:ieonly)
-    unlet s:ieonly
+    # else we aren't doing any dynamic folding, no need for any special rules
+
+    extend(lines, [
+      settings.use_xhtml ? "" : '-->',
+      "</style>",
+    ])
+    extend(lines, ieonly)
   endif
 
-  let s:uses_script = s:settings.dynamic_folds || s:settings.line_ids
+  var uses_script = settings.dynamic_folds || settings.line_ids
 
-  " insert script tag if needed
-  if s:uses_script
-    call extend(s:lines, [
-         \ "",
-         \ "<script" .. (s:html5 ? "" : " type='text/javascript'") .. ">",
-         \ s:settings.use_xhtml ? '//<![CDATA[' : "<!--"])
+  # insert script tag if needed
+  if uses_script
+    extend(lines, [
+      "",
+      "<script" .. (html5 ? "" : " type='text/javascript'") .. ">",
+      settings.use_xhtml ? '//<![CDATA[' : "<!--"])
   endif
 
-  " insert javascript to toggle folds open and closed
-  if s:settings.dynamic_folds
-    call extend(s:lines, [
-         \ "",
-         \ "function toggleFold(objID)",
-         \ "{",
-         \ "  var fold;",
-         \ "  fold = document.getElementById(objID);",
-         \ "  if (fold.className == 'closed-fold')",
-         \ "  {",
-         \ "    fold.className = 'open-fold';",
-         \ "  }",
-         \ "  else if (fold.className == 'open-fold')",
-         \ "  {",
-         \ "    fold.className = 'closed-fold';",
-         \ "  }",
-         \ "}"
-         \ ])
+  # insert javascript to toggle folds open and closed
+  if settings.dynamic_folds
+    extend(lines, [
+      "",
+      "function toggleFold(objID)",
+      "{",
+      "  var fold;",
+      "  fold = document.getElementById(objID);",
+      "  if (fold.className == 'closed-fold')",
+      "  {",
+      "    fold.className = 'open-fold';",
+      "  }",
+      "  else if (fold.className == 'open-fold')",
+      "  {",
+      "    fold.className = 'closed-fold';",
+      "  }",
+      "}"
+    ])
   endif
 
-  if s:settings.line_ids
-    " insert javascript to get IDs from line numbers, and to open a fold before
-    " jumping to any lines contained therein
-    call extend(s:lines, [
-         \ "",
-         \ "/* 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;",
-         \ "  }",
-         \ "  var lineElem = document.getElementById(lineNum);"
-         \ ])
-
-    if s:settings.dynamic_folds
-      call extend(s:lines, [
-           \ "",
-           \ "  /* 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')",
-           \ "    {",
-           \ "      node.className = 'open-fold';",
-           \ "    }",
-           \ "    node = node.parentNode;",
-           \ "  }",
-           \ ])
+  if settings.line_ids
+    # insert javascript to get IDs from line numbers, and to open a fold before
+    # jumping to any lines contained therein
+    extend(lines, [
+      "",
+      "/* 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;",
+    "  }",
+    "  var lineElem = document.getElementById(lineNum);"
+    ])
+
+    if settings.dynamic_folds
+      extend(lines, [
+       "",
+      "  /* navigate upwards in the DOM tree to open all folds containing the line */",
+      "  var node = lineElem;",
+      "  while (node && node.id != 'vimCodeElement" .. settings.id_suffix .. "')",
+      "  {",
+      "    if (node.className == 'closed-fold')",
+      "    {",
+      "      node.className = 'open-fold';",
+      "    }",
+      "    node = node.parentNode;",
+      "  }",
+      ])
     endif
-    call extend(s:lines, [
-         \ "  /* 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;",
-         \ "}"
-         \ ])
+    extend(lines, [
+      "  /* 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;",
+      "}"
+    ])
   endif
 
-  " insert script closing tag if needed
-  if s:uses_script
-    call extend(s:lines, [
-         \ '',
-         \ s:settings.use_xhtml ? '//]]>' : '-->',
-         \ "</script>"
-         \ ])
+  # insert script closing tag if needed
+  if uses_script
+    extend(lines, [
+      '',
+      settings.use_xhtml ? '//]]>' : '-->',
+      "</script>"
+    ])
   endif
 
-  call extend(s:lines, ["</head>",
-       \ "<body"..(s:settings.line_ids ? " onload='JumpToLine();'" : "")..">"])
+  extend(lines, ["</head>",
+    "<body" .. (settings.line_ids ? " onload='JumpToLine();'" : "") .. ">"])
 endif
 
-if s:settings.no_pre
-  " if we're not using CSS we use a font tag which can't have a div inside
-  if s:settings.use_css
-    call extend(s:lines, ["<div id='vimCodeElement" .. s:settings.id_suffix .. "'>"])
+if settings.no_pre
+  # if we're not using CSS we use a font tag which can't have a div inside
+  if settings.use_css
+    extend(lines, ["<div id='vimCodeElement" .. settings.id_suffix .. "'>"])
   endif
 else
-  call extend(s:lines, ["<pre id='vimCodeElement" .. s:settings.id_suffix .. "'>"])
+  extend(lines, ["<pre id='vimCodeElement" .. settings.id_suffix .. "'>"])
 endif
 
-exe s:orgwin .. "wincmd w"
+execute $":{orgwin}wincmd w"
 
-" caches of style data
-" initialize to include line numbers if using them
-if s:settings.number_lines
-  let s:stylelist = { s:LINENR_ID : ".LineNr { " .. s:CSS1( s:LINENR_ID ) .. "}" }
+# caches of style data
+# initialize to include line numbers if using them
+var stylelist: dict<string>
+if settings.number_lines
+  stylelist = { [LINENR_ID]: ".LineNr { " .. CSS1(LINENR_ID) .. "}" }
 else
-  let s:stylelist = {}
+  stylelist = {}
 endif
-let s:diffstylelist = {
-      \   s:DIFF_A_ID : ".DiffAdd { " .. s:CSS1( s:DIFF_A_ID ) .. "}",
-      \   s:DIFF_C_ID : ".DiffChange { " .. s:CSS1( s:DIFF_C_ID ) .. "}",
-      \   s:DIFF_D_ID : ".DiffDelete { " .. s:CSS1( s:DIFF_D_ID ) .. "}",
-      \   s:DIFF_T_ID : ".DiffText { " .. s:CSS1( s:DIFF_T_ID ) .. "}"
-      \ }
-
-" set up progress bar in the status line
-if !s:settings.no_progress
-  " ProgressBar Indicator
-  let s:progressbar={}
-
-  " Progressbar specific functions
-
-  func! s:SetProgbarColor()
-    if hlID("TOhtmlProgress") != 0
-      hi! link TOhtmlProgress_auto TOhtmlProgress
-    elseif hlID("TOhtmlProgress_auto")==0 ||
-       \ !exists("s:last_colors_name") || !exists("g:colors_name") ||
-       \ g:colors_name != s:last_colors_name
-      let s:last_colors_name = exists("g:colors_name") ? g:colors_name : "none"
-
-      let l:diffatr = synIDattr(hlID("DiffDelete")->synIDtrans(), "reverse", s:whatterm) ? "fg#" : "bg#"
-      let l:stlatr = synIDattr(hlID("StatusLine")->synIDtrans(), "reverse", s:whatterm) ? "fg#" : "bg#"
-
-      let l:progbar_color = synIDattr(hlID("DiffDelete")->synIDtrans(), l:diffatr, s:whatterm)
-      let l:stl_color = synIDattr(hlID("StatusLine")->synIDtrans(), l:stlatr, s:whatterm)
-
-      if "" == l:progbar_color
-       let l:progbar_color = synIDattr(hlID("DiffDelete")->synIDtrans(), "reverse", s:whatterm) ? s:fgc : s:bgc
-      endif
-      if "" == l:stl_color
-       let l:stl_color = synIDattr(hlID("StatusLine")->synIDtrans(), "reverse", s:whatterm) ? s:fgc : s:bgc
-      endif
+var diffstylelist: dict<string> = {
+  [DIFF_A_ID]: ".DiffAdd { " .. CSS1(DIFF_A_ID) .. "}",
+  [DIFF_C_ID]: ".DiffChange { " .. CSS1(DIFF_C_ID) .. "}",
+  [DIFF_D_ID]: ".DiffDelete { " .. CSS1(DIFF_D_ID) .. "}",
+  [DIFF_T_ID]: ".DiffText { " .. CSS1(DIFF_T_ID) .. "}"
+}
+
+var last_colors_name: string
+# set up progress bar in the status line
+# ProgressBar Indicator
+# Progressbar specific functions
+def SetProgbarColor()
+  if hlID("TOhtmlProgress") != 0
+    hi! link TOhtmlProgress_auto TOhtmlProgress
+  elseif hlID("TOhtmlProgress_auto") == 0 ||
+      last_colors_name == null_string || !exists("g:colors_name") ||
+      g:colors_name != last_colors_name
+    last_colors_name = exists("g:colors_name") ? g:colors_name : "none"
+
+    var diffatr = synIDattr(synIDtrans(hlID("DiffDelete")), "reverse", whatterm) == '1' ? "fg#" : "bg#"
+    var stlatr = synIDattr(synIDtrans(hlID("StatusLine")), "reverse", whatterm) == '1' ? "fg#" : "bg#"
+
+    var progbar_color = synIDattr(synIDtrans(hlID("DiffDelete")), diffatr, whatterm)
+    var stl_color = synIDattr(synIDtrans(hlID("StatusLine")), stlatr, whatterm)
+
+    if empty(progbar_color)
+      progbar_color = synIDattr(synIDtrans(hlID("DiffDelete")), "reverse", whatterm) == '1' ? fgc : bgc
+    endif
+    if empty(stl_color)
+      stl_color = synIDattr(synIDtrans(hlID("StatusLine")), "reverse", whatterm) == '1' ? fgc : bgc
+    endif
 
-      if l:progbar_color == l:stl_color
-       if s:whatterm == 'cterm'
-         if l:progbar_color >= (&t_Co/2)
-           let l:progbar_color-=1
-         else
-           let l:progbar_color+=1
-         endif
+    if progbar_color == stl_color
+      if whatterm == 'cterm'
+       var nr = str2nr(progbar_color)
+       if nr >= (str2nr(&t_Co) / 2)
+         nr -= 1
        else
-         let l:rgb = map(matchlist(l:progbar_color, '#\zs\x\x\ze\(\x\x\)\(\x\x\)')[:2], 'str2nr(v:val, 16)')
-         let l:avg = (l:rgb[0] + l:rgb[1] + l:rgb[2])/3
-         if l:avg >= 128
-           let l:avg_new = l:avg
-           while l:avg - l:avg_new < 0x15
-             let l:rgb = map(l:rgb, 'v:val * 3 / 4')
-             let l:avg_new = (l:rgb[0] + l:rgb[1] + l:rgb[2])/3
-           endwhile
-         else
-           let l:avg_new = l:avg
-           while l:avg_new - l:avg < 0x15
-             let l:rgb = map(l:rgb, 'min([max([v:val, 4]) * 5 / 4, 255])')
-             let l:avg_new = (l:rgb[0] + l:rgb[1] + l:rgb[2])/3
-           endwhile
-         endif
-         let l:progbar_color = printf("#%02x%02x%02x", l:rgb[0], l:rgb[1], l:rgb[2])
+         nr += 1
        endif
-       echomsg "diff detected progbar color set to" l:progbar_color
+       progbar_color = string(nr)
+      else
+       var rgb = map(matchlist(progbar_color, '#\zs\x\x\ze\(\x\x\)\(\x\x\)')[: 2], (_, v) => str2nr(v, 16))
+       var avg = (rgb[0] + rgb[1] + rgb[2]) / 3
+       if avg >= 128
+         var avg_new = avg
+         while avg - avg_new < 0x15
+           rgb = map(rgb, (_, v) => v * 3 / 4)
+           avg_new = (rgb[0] + rgb[1] + rgb[2]) / 3
+         endwhile
+       else
+         var avg_new = avg
+         while avg_new - avg < 0x15
+           rgb = map(rgb, (_, v) => min([max([v, 4]) * 5 / 4, 255]))
+           avg_new = (rgb[0] + rgb[1] + rgb[2]) / 3
+         endwhile
+       endif
+       progbar_color = printf("#%02x%02x%02x", rgb[0], rgb[1], rgb[2])
       endif
-      exe "hi TOhtmlProgress_auto" s:whatterm.."bg="..l:progbar_color
+      echomsg "diff detected progbar color set to" progbar_color
     endif
-  endfun
-
-  func! s:ProgressBar(title, max_value, winnr)
-    let pgb=copy(s:progressbar)
-    let pgb.title = a:title..' '
-    let pgb.max_value = a:max_value
-    let pgb.winnr = a:winnr
-    let pgb.cur_value = 0
-
-    let pgb.items = { 'title'   : { 'color' : 'Statusline' },
-         \'bar'     : { 'color' : 'Statusline' , 'fillcolor' : 'TOhtmlProgress_auto' , 'bg' : 'Statusline' } ,
-         \'counter' : { 'color' : 'Statusline' } }
-    let pgb.last_value = 0
-    let pgb.needs_redraw = 0
-    " Note that you must use len(split) instead of len() if you want to use 
-    " unicode in title.
-    "
-    " Subtract 3 for spacing around the title.
-    " Subtract 4 for the percentage display.
-    " Subtract 2 for spacing before this.
-    " Subtract 2 more for the '|' on either side of the progress bar
-    let pgb.subtractedlen=len(split(pgb.title, '\zs'))+3+4+2+2
-    let pgb.max_len = 0
+    execute "hi TOhtmlProgress_auto" whatterm .. "bg=" .. progbar_color
+  endif
+enddef
+
+class ProgressBar #{{{
+  public var title: string
+  public var max_value: number
+  public var winnr: number
+  public var cur_value: number = 0
+  public var items: dict<any>
+  public var last_value: number = 0
+  public var needs_redraw: number = 0
+  public var pb_len: number = 0
+  public var max_len: number = 0
+  public var progress_ticks: list<number> = []
+  var subtractedlen: number
+
+  def new(title: string, max_value: number, winnr: number)
+    this.title = title .. ' '
+    this.max_value = max_value
+    this.winnr = winnr
+
+    this.items = {
+      'title': { 'color': 'Statusline' },
+      'bar': { 'color': 'Statusline', 'fillcolor': 'TOhtmlProgress_auto', 'bg': 'Statusline' },
+      'counter': { 'color': 'Statusline' }
+    }
+    # Note that you must use len(split) instead of len() if you want to use
+    # unicode in title.
+    #
+    # Subtract 3 for spacing around the title.
+    # Subtract 4 for the percentage display.
+    # Subtract 2 for spacing before this.
+    # Subtract 2 more for the '|' on either side of the progress bar
+    this.subtractedlen = len(split(this.title, '\zs')) + 3 + 4 + 2 + 2
     set laststatus=2
-    return pgb
-  endfun
+  enddef
 
-  " Function: progressbar.calculate_ticks() {{{1
-  func! s:progressbar.calculate_ticks(pb_len)
-    if a:pb_len<=0
-      let pb_len = 100
-    else
-      let pb_len = a:pb_len
-    endif
-    let self.progress_ticks = map(range(pb_len+1), "v:val * self.max_value / pb_len")
-  endfun
-
-  "Function: progressbar.paint()
-  func! s:progressbar.paint()
-    " Recalculate widths.
-    let max_len = winwidth(self.winnr)
-    let pb_len = 0
-    " always true on first call because of initial value of self.max_len
-    if max_len != self.max_len
-      let self.max_len = max_len
-
-      " Progressbar length
-      let pb_len = max_len - self.subtractedlen
-
-      call self.calculate_ticks(pb_len)
-
-      let self.needs_redraw = 1
-      let cur_value = 0
-      let self.pb_len = pb_len
+  def Paint()
+    # Recalculate widths.
+    var max_len = winwidth(this.winnr)
+    var pb_len = 0
+    var cur_value: number
+
+    def CalculateTicks(len: number)
+      if len <= 0
+       pb_len = 100
+      else
+       pb_len = len
+      endif
+      this.progress_ticks = range(pb_len + 1)->map((_, v) => v * this.max_value / pb_len)
+    enddef
+
+    # always true on first call because of initial value of this.max_len
+    if max_len != this.max_len
+      this.max_len = max_len
+
+      # Progressbar length
+      pb_len = max_len - this.subtractedlen
+
+      CalculateTicks(pb_len)
+
+      this.needs_redraw = 1
+      cur_value = 0
+      this.pb_len = pb_len
     else
-      " start searching at the last found index to make the search for the
-      " appropriate tick value normally take 0 or 1 comparisons
-      let cur_value = self.last_value
-      let pb_len = self.pb_len
+      # start searching at the last found index to make the search for the
+      # appropriate tick value normally take 0 or 1 comparisons
+      cur_value = this.last_value
+      pb_len = this.pb_len
     endif
 
-    let cur_val_max = pb_len > 0 ? pb_len : 100
+    var cur_val_max = pb_len > 0 ? pb_len : 100
 
-    " find the current progress bar position based on precalculated thresholds
-    while cur_value < cur_val_max && self.cur_value > self.progress_ticks[cur_value]
-      let cur_value += 1
+    # find the current progress bar position based on precalculated thresholds
+    while cur_value < cur_val_max && this.cur_value > this.progress_ticks[cur_value]
+      cur_value += 1
     endwhile
 
-    " update progress bar
-    if self.last_value != cur_value || self.needs_redraw || self.cur_value == self.max_value
-      let self.needs_redraw = 1
-      let self.last_value = cur_value
-
-      let t_color  = self.items.title.color
-      let b_fcolor = self.items.bar.fillcolor
-      let b_color  = self.items.bar.color
-      let c_color  = self.items.counter.color
-
-      let stl =  "%#".t_color."#%-( ".self.title." %)".
-           \"%#".b_color."#".
-           \(pb_len>0 ?
-           \   ('|%#'.b_fcolor."#%-(".repeat(" ",cur_value)."%)".
-           \    '%#'.b_color."#".repeat(" ",pb_len-cur_value)."|"):
-           \   ('')).
-           \"%=%#".c_color."#%( ".printf("%3.d ",100*self.cur_value/self.max_value)."%% %)"
-      call setwinvar(self.winnr, '&stl', stl)
+    # update progress bar
+    if this.last_value != cur_value || this.needs_redraw || this.cur_value == this.max_value
+      this.needs_redraw = 1
+      this.last_value = cur_value
+
+      var t_color  = this.items.title.color
+      var b_fcolor = this.items.bar.fillcolor
+      var b_color  = this.items.bar.color
+      var c_color  = this.items.counter.color
+
+      var stl =  $"%#{t_color}#%-( {this.title} %)%#{b_color}#" ..
+       (pb_len > 0 ? $'|%#{b_fcolor}#%-({repeat(" ", cur_value)}%)%#{b_color}#{repeat(" ", pb_len - cur_value)}|' : (''))
+       .. $"%=%#{c_color}#%( {printf("%3.d ", 100 * this.cur_value / this.max_value)}%% %)"
+      setwinvar(this.winnr, '&stl', stl)
     endif
-  endfun
-
-  func! s:progressbar.incr( ... )
-    let self.cur_value += (a:0 ? a:1 : 1)
-    " if we were making a general-purpose progress bar, we'd need to limit to a
-    " lower limit as well, but since we always increment with a positive value
-    " in this script, we only need limit the upper value
-    let self.cur_value = (self.cur_value > self.max_value ? self.max_value : self.cur_value)
-    call self.paint()
-  endfun
-  " }}}
-  if s:settings.dynamic_folds
-    " to process folds we make two passes through each line
-    let s:pgb = s:ProgressBar("Processing folds:", line('$')*2, s:orgwin)
+  enddef
+
+  def Incr(delta: number = 1)
+    this.cur_value += delta
+    # if we were making a general-purpose progress bar, we'd need to limit to a
+    # lower limit as well, but since we always increment with a positive value
+    # in this script, we only need limit the upper value
+    this.cur_value = (this.cur_value > this.max_value ? this.max_value : this.cur_value)
+    this.Paint()
+  enddef
+endclass #}}}
+
+var pgb: ProgressBar
+if !settings.no_progress
+  if settings.dynamic_folds
+    # to process folds we make two passes through each line
+    pgb = ProgressBar.new("Processing folds:", line('$') * 2, orgwin)
   endif
 
-  call s:SetProgbarColor()
+  SetProgbarColor()
 endif
 
-let s:build_fun_lines = []
-call add(s:build_fun_lines, [])
-let s:build_fun_lines[-1] =<< trim ENDLET
-    func! s:Add_diff_fill(lnum)
-      let l:filler = diff_filler(a:lnum)
-      if l:filler > 0
-       let l:to_insert = l:filler
-       while l:to_insert > 0
-         let l:new = repeat(s:difffillchar, 3)
-
-         if l:to_insert > 2 && l:to_insert < l:filler && !s:settings.whole_filler
-           let l:new = l:new .. " " .. l:filler .. " inserted lines "
-           let l:to_insert = 2
-         endif
+var build_fun_lines: list<string>
+build_fun_lines =<< trim ENDLET
+  def Add_diff_fill(_lnum: number)
+    var filler = diff_filler(_lnum)
+    if filler > 0
+      var to_insert = filler
+      while to_insert > 0
+       var new = repeat(difffillchar, 3)
+
+       if to_insert > 2 && to_insert < filler && !settings.whole_filler
+         new = new .. " " .. filler .. " inserted lines "
+         to_insert = 2
+       endif
 ENDLET
-call add(s:build_fun_lines, [])
-if !s:settings.no_pre
-  let s:build_fun_lines[-1] =<< trim ENDLET
-         " HTML line wrapping is off--go ahead and fill to the margin
-         " TODO: what about when CSS wrapping is turned on?
-         let l:new = l:new .. repeat(s:difffillchar, &columns - strlen(l:new) - s:margin)
+if !settings.no_pre
+  trim_tmp =<< trim ENDLET
+       # HTML line wrapping is off--go ahead and fill to the margin
+       # TODO: what about when CSS wrapping is turned on?
+       new = new .. repeat(difffillchar, &columns - strlen(new) - margin)
   ENDLET
+  build_fun_lines += trim_tmp
 else
-  let s:build_fun_lines[-1] =<< trim ENDLET
-         let l:new = l:new .. repeat(s:difffillchar, 3)
+  trim_tmp =<< trim ENDLET
+       new = new .. repeat(difffillchar, 3)
   ENDLET
+  build_fun_lines += trim_tmp
 endif
-call add(s:build_fun_lines, [])
-let s:build_fun_lines[-1] =<< trim ENDLET
-       let l:new = s:HtmlFormat_d(l:new, s:DIFF_D_ID, 0)
+trim_tmp =<< trim ENDLET
+       new = HtmlFormat_d(new, DIFF_D_ID, 0)
 ENDLET
-if s:settings.number_lines
-  call add(s:build_fun_lines, [])
-  let s:build_fun_lines[-1] =<< trim ENDLET
-         " Indent if line numbering is on. Indent gets style of line number
-         " column.
-         let l:new = s:HtmlFormat_n(repeat(' ', s:margin), s:LINENR_ID, 0, 0) .. l:new
+build_fun_lines += trim_tmp
+if settings.number_lines
+  trim_tmp =<< trim ENDLET
+       # Indent if line numbering is on. Indent gets style of line number
+       # column.
+       new = HtmlFormat_n(repeat(' ', margin), LINENR_ID, 0, 0) .. new
   ENDLET
+  build_fun_lines += trim_tmp
 endif
-if s:settings.dynamic_folds && !s:settings.no_foldcolumn 
-  call add(s:build_fun_lines, [])
-  let s:build_fun_lines[-1] =<< trim ENDLET
-         if s:foldcolumn > 0
-           " Indent for foldcolumn if there is one. Assume it's empty, there should
-           " not be a fold for deleted lines in diff mode.
-           let l:new = s:FoldColumn_fill() .. l:new
-         endif
+if settings.dynamic_folds && !settings.no_foldcolumn
+  trim_tmp =<< trim ENDLET
+       if foldcolumn > 0
+         # Indent for foldcolumn if there is one. Assume it's empty, there should
+         # not be a fold for deleted lines in diff mode.
+         new = FoldColumn_fill() .. new
+       endif
   ENDLET
+  build_fun_lines += trim_tmp
 endif
-" Ignore this comment, just bypassing a highlighting issue: if
-call add(s:build_fun_lines, [])
-let s:build_fun_lines[-1] =<< trim ENDLET
-       call add(s:lines, l:new..s:HtmlEndline)
-       let l:to_insert = l:to_insert - 1
+# Ignore this comment, just bypassing a highlighting issue: if
+trim_tmp =<< trim ENDLET
+       add(lines, new .. HtmlEndline)
+       to_insert -= 1
       endwhile
     endif
-  endfun
+  enddef
 ENDLET
-exec join(flatten(s:build_fun_lines), "\n")
-
-" First do some preprocessing for dynamic folding. Do this for the entire file
-" so we don't accidentally start within a closed fold or something.
-let s:allfolds = []
-
-if s:settings.dynamic_folds
-  let s:lnum = 1
-  let s:end = line('$')
-  " save the fold text and set it to the default so we can find fold levels
-  let s:foldtext_save = &foldtext
-  setlocal foldtext&
-
-  " we will set the foldcolumn in the html to the greater of the maximum fold
-  " level and the current foldcolumn setting
-  let s:foldcolumn = &foldcolumn
-
-  " get all info needed to describe currently closed folds
-  while s:lnum <= s:end
-    if foldclosed(s:lnum) == s:lnum
-      " default fold text has '+-' and then a number of dashes equal to fold
-      " level, so subtract 2 from index of first non-dash after the dashes
-      " in order to get the fold level of the current fold
-      let s:level = match(foldtextresult(s:lnum), '+-*\zs[^-]') - 2
-      " store fold info for later use
-      let s:newfold = {'firstline': s:lnum, 'lastline': foldclosedend(s:lnum), 'level': s:level,'type': "closed-fold"}
-      call add(s:allfolds, s:newfold)
-      " open the fold so we can find any contained folds
-      execute s:lnum.."foldopen"
-    else
-      if !s:settings.no_progress
-       call s:pgb.incr()
-       if s:pgb.needs_redraw
-         redrawstatus
-         let s:pgb.needs_redraw = 0
+build_fun_lines += trim_tmp
+execute join(build_fun_lines, "\n")
+
+# First do some preprocessing for dynamic folding. Do this for the entire file
+# so we don't accidentally start within a closed fold or something.
+var allfolds: list<any> = []
+
+var foldcolumn: number
+if settings.dynamic_folds
+  def ProcessFolds()
+    lnum = 1
+    end = line('$')
+    # save the fold text and set it to the default so we can find fold levels
+    var foldtext_save = &foldtext
+    setlocal foldtext&
+
+    # we will set the foldcolumn in the html to the greater of the maximum fold
+    # level and the current foldcolumn setting
+    foldcolumn = &foldcolumn
+
+    # get all info needed to describe currently closed folds
+    while lnum <= end
+      if foldclosed(lnum) == lnum
+       # default fold text has '+-' and then a number of dashes equal to fold
+       # level, so subtract 2 from index of first non-dash after the dashes
+       # in order to get the fold level of the current fold
+       var level = match(foldtextresult(lnum), '+-*\zs[^-]') - 2
+       # store fold info for later use
+       var newfold = {firstline: lnum, lastline: foldclosedend(lnum), level: level, type: "closed-fold"}
+       add(allfolds, newfold)
+       # open the fold so we can find any contained folds
+       execute $":{lnum}foldopen"
+      else
+       if !settings.no_progress
+         pgb.Incr()
+         if pgb.needs_redraw
+           redrawstatus
+           pgb.needs_redraw = 0
+         endif
        endif
+       lnum = lnum + 1
       endif
-      let s:lnum = s:lnum + 1
-    endif
-  endwhile
+    endwhile
 
-  " close all folds to get info for originally open folds
-  silent! %foldclose!
-  let s:lnum = 1
-
-  " the originally open folds will be all folds we encounter that aren't
-  " already in the list of closed folds
-  while s:lnum <= s:end
-    if foldclosed(s:lnum) == s:lnum
-      " default fold text has '+-' and then a number of dashes equal to fold
-      " level, so subtract 2 from index of first non-dash after the dashes
-      " in order to get the fold level of the current fold
-      let s:level = match(foldtextresult(s:lnum), '+-*\zs[^-]') - 2
-      let s:newfold = {'firstline': s:lnum, 'lastline': foldclosedend(s:lnum), 'level': s:level,'type': "closed-fold"}
-      " only add the fold if we don't already have it
-      if empty(s:allfolds) || index(s:allfolds, s:newfold) == -1
-       let s:newfold.type = "open-fold"
-       call add(s:allfolds, s:newfold)
-      endif
-      " open the fold so we can find any contained folds
-      execute s:lnum.."foldopen"
-    else
-      if !s:settings.no_progress
-       call s:pgb.incr()
-       if s:pgb.needs_redraw
-         redrawstatus
-         let s:pgb.needs_redraw = 0
+    # close all folds to get info for originally open folds
+    silent! :%foldclose!
+    lnum = 1
+
+    # the originally open folds will be all folds we encounter that aren't
+    # already in the list of closed folds
+    while lnum <= end
+      if foldclosed(lnum) == lnum
+       # default fold text has '+-' and then a number of dashes equal to fold
+       # level, so subtract 2 from index of first non-dash after the dashes
+       # in order to get the fold level of the current fold
+       var level = match(foldtextresult(lnum), '+-*\zs[^-]') - 2
+       var newfold = {firstline: lnum, lastline: foldclosedend(lnum), level: level, type: "closed-fold"}
+       # only add the fold if we don't already have it
+       if empty(allfolds) || index(allfolds, newfold) == -1
+         newfold.type = "open-fold"
+         add(allfolds, newfold)
+       endif
+       # open the fold so we can find any contained folds
+       execute $":{lnum}foldopen"
+      else
+       if !settings.no_progress
+         pgb.Incr()
+         if pgb.needs_redraw
+           redrawstatus
+           pgb.needs_redraw = 0
+         endif
        endif
+       lnum = lnum + 1
       endif
-      let s:lnum = s:lnum + 1
-    endif
-  endwhile
+    endwhile
 
-  " sort the folds so that we only ever need to look at the first item in the
-  " list of folds
-  call sort(s:allfolds, "s:FoldCompare")
-
-  let &l:foldtext = s:foldtext_save
-  unlet s:foldtext_save
-
-  " close all folds again so we can get the fold text as we go
-  silent! %foldclose!
-
-  " Go through and remove folds we don't need to (or cannot) process in the
-  " current conversion range
-  "
-  " If a fold is removed which contains other folds, which are included, we need
-  " to adjust the level of the included folds as used by the conversion logic
-  " (avoiding special cases is good)
-  "
-  " Note any time we remove a fold, either all of the included folds are in it,
-  " or none of them, because we only remove a fold if neither its start nor its
-  " end are within the conversion range.
-  let leveladjust = 0
-  for afold in s:allfolds
-    let removed = 0
-    if exists("g:html_start_line") && exists("g:html_end_line")
-      if afold.firstline < g:html_start_line
-       if afold.lastline <= g:html_end_line && afold.lastline >= g:html_start_line
-         " if a fold starts before the range to convert but stops within the
-         " range, we need to include it. Make it start on the first converted
-         " line.
-         let afold.firstline = g:html_start_line
-       else
-         " if the fold lies outside the range or the start and stop enclose
-         " the entire range, don't bother parsing it
-         call remove(s:allfolds, index(s:allfolds, afold))
-         let removed = 1
-         if afold.lastline > g:html_end_line
-           let leveladjust += 1
+    # sort the folds so that we only ever need to look at the first item in the
+    # list of folds
+    sort(allfolds, FoldCompare)
+
+    &l:foldtext = foldtext_save
+
+    # close all folds again so we can get the fold text as we go
+    silent! :%foldclose!
+
+    # Go through and remove folds we don't need to (or cannot) process in the
+    # current conversion range
+    #
+    # If a fold is removed which contains other folds, which are included, we need
+    # to adjust the level of the included folds as used by the conversion logic
+    # (avoiding special cases is good)
+    #
+    # Note any time we remove a fold, either all of the included folds are in it,
+    # or none of them, because we only remove a fold if neither its start nor its
+    # end are within the conversion range.
+    var leveladjust = 0
+    for afold in allfolds
+      var removed = 0
+      if exists("g:html_start_line") && exists("g:html_end_line")
+       if afold.firstline < g:html_start_line
+         if afold.lastline <= g:html_end_line && afold.lastline >= g:html_start_line
+           # if a fold starts before the range to convert but stops within the
+           # range, we need to include it. Make it start on the first converted
+           # line.
+           afold.firstline = g:html_start_line
+         else
+           # if the fold lies outside the range or the start and stop enclose
+           # the entire range, don't bother parsing it
+           remove(allfolds, index(allfolds, afold))
+           removed = 1
+           if afold.lastline > g:html_end_line
+             leveladjust += 1
+           endif
          endif
+       elseif afold.firstline > g:html_end_line
+         # If the entire fold lies outside the range we need to remove it.
+         remove(allfolds, index(allfolds, afold))
+         removed = 1
+       endif
+      elseif exists("g:html_start_line")
+       if afold.firstline < g:html_start_line
+         # if there is no last line, but there is a first line, the end of the
+         # fold will always lie within the region of interest, so keep it
+         afold.firstline = g:html_start_line
+       endif
+      elseif exists("g:html_end_line")
+       # if there is no first line we default to the first line in the buffer so
+       # the fold start will always be included if the fold itself is included.
+       # If however the entire fold lies outside the range we need to remove it.
+       if afold.firstline > g:html_end_line
+         remove(allfolds, index(allfolds, afold))
+         removed = 1
        endif
-      elseif afold.firstline > g:html_end_line
-       " If the entire fold lies outside the range we need to remove it.
-       call remove(s:allfolds, index(s:allfolds, afold))
-       let removed = 1
-      endif
-    elseif exists("g:html_start_line")
-      if afold.firstline < g:html_start_line
-       " if there is no last line, but there is a first line, the end of the
-       " fold will always lie within the region of interest, so keep it
-       let afold.firstline = g:html_start_line
-      endif
-    elseif exists("g:html_end_line")
-      " if there is no first line we default to the first line in the buffer so
-      " the fold start will always be included if the fold itself is included.
-      " If however the entire fold lies outside the range we need to remove it.
-      if afold.firstline > g:html_end_line
-       call remove(s:allfolds, index(s:allfolds, afold))
-       let removed = 1
       endif
-    endif
-    if !removed
-      let afold.level -= leveladjust
-      if afold.level+1 > s:foldcolumn
-       let s:foldcolumn = afold.level+1
+      if !removed
+       afold.level -= leveladjust
+       if afold.level + 1 > foldcolumn
+         foldcolumn = afold.level + 1
+       endif
       endif
-    endif
-  endfor
-
-  " if we've removed folds containing the conversion range from processing,
-  " getting foldtext as we go won't know to open the removed folds, so the
-  " foldtext would be wrong; open them now.
-  "
-  " Note that only when a start and an end line is specified will a fold
-  " containing the current range ever be removed.
-  while leveladjust > 0
-    exe g:html_start_line.."foldopen"
-    let leveladjust -= 1
-  endwhile
+    endfor
+
+    # if we've removed folds containing the conversion range from processing,
+    # getting foldtext as we go won't know to open the removed folds, so the
+    # foldtext would be wrong; open them now.
+    #
+    # Note that only when a start and an end line is specified will a fold
+    # containing the current range ever be removed.
+    while leveladjust > 0
+      execute $":{g:html_start_line}foldopen"
+      leveladjust -= 1
+    endwhile
+  enddef
+  ProcessFolds()
 endif
 
-" Now loop over all lines in the original text to convert to html.
-" Use html_start_line and html_end_line if they are set.
+# Now loop over all lines in the original text to convert to html.
+# Use html_start_line and html_end_line if they are set.
 if exists("g:html_start_line")
-  let s:lnum = html_start_line
-  if s:lnum < 1 || s:lnum > line("$")
-    let s:lnum = 1
+  lnum = g:html_start_line
+  if lnum < 1 || lnum > line("$")
+    lnum = 1
   endif
 else
-  let s:lnum = 1
+  lnum = 1
 endif
 if exists("g:html_end_line")
-  let s:end = html_end_line
-  if s:end < s:lnum || s:end > line("$")
-    let s:end = line("$")
+  end = g:html_end_line
+  if end < lnum || end > line("$")
+    end = line("$")
   endif
 else
-  let s:end = line("$")
+  end = line("$")
 endif
 
-" stack to keep track of all the folds containing the current line
-let s:foldstack = []
+# stack to keep track of all the folds containing the current line
+var foldstack: list<any> = []
 
-if !s:settings.no_progress
-  let s:pgb = s:ProgressBar("Processing lines:", s:end - s:lnum + 1, s:orgwin)
+if !settings.no_progress
+  pgb = ProgressBar.new("Processing lines:", end - lnum + 1, orgwin)
 endif
 
-if s:settings.number_lines
-  let s:margin = strlen(s:end) + 1
+var margin: number
+if settings.number_lines
+  margin = strlen(end) + 1
 else
-  let s:margin = 0
+  margin = 0
 endif
 
-if has('folding') && !s:settings.ignore_folding
-  let s:foldfillchar = &fillchars[matchend(&fillchars, 'fold:')]
-  if s:foldfillchar == ''
-    let s:foldfillchar = '-'
+var foldfillchar: string
+var charidx: number
+if has('folding') && !settings.ignore_folding
+  charidx = matchend(&fillchars, 'fold:')
+  if charidx >= 0
+    foldfillchar = &fillchars[charidx(&fillchars, charidx)]
+  endif
+  if foldfillchar->empty()
+    foldfillchar = '-'
   endif
 endif
-let s:difffillchar = &fillchars[matchend(&fillchars, 'diff:')]
-if s:difffillchar == ''
-  let s:difffillchar = '-'
+var difffillchar: string
+charidx = matchend(&fillchars, 'diff:')
+if charidx >= 0
+  difffillchar = &fillchars[charidx(&fillchars, charidx)]
+endif
+if difffillchar->empty()
+  difffillchar = '-'
 endif
 
-let s:foldId = 0
-
-if !s:settings.expand_tabs
-  " If keeping tabs, add them to printable characters so we keep them when
-  " formatting text (strtrans() doesn't replace printable chars)
-  let s:old_isprint = &isprint
+var foldId = 0
+var old_isprint: string
+if !settings.expand_tabs
+  # If keeping tabs, add them to printable characters so we keep them when
+  # formatting text (strtrans() doesn't replace printable chars)
+  old_isprint = &isprint
   setlocal isprint+=9
 endif
 
-while s:lnum <= s:end
-
-  " If there are filler lines for diff mode, show these above the line.
-  call s:Add_diff_fill(s:lnum)
+def ProcessLines()
+  var all_lines = getline(lnum, end)
+  var start_lnum = lnum
 
-  " Start the line with the line number.
-  if s:settings.number_lines
-    let s:numcol = repeat(' ', s:margin - 1 - strlen(s:lnum)) .. s:lnum .. ' '
-  endif
+  while lnum <= end
 
-  let s:new = ""
+    # If there are filler lines for diff mode, show these above the line.
+    Add_diff_fill(lnum)
 
-  if has('folding') && !s:settings.ignore_folding && foldclosed(s:lnum) > -1 && !s:settings.dynamic_folds
-    "
-    " This is the beginning of a folded block (with no dynamic folding)
-    let s:new = foldtextresult(s:lnum)
-    if !s:settings.no_pre
-      " HTML line wrapping is off--go ahead and fill to the margin
-      let s:new = s:new .. repeat(s:foldfillchar, &columns - strlen(s:new))
+    # Start the line with the line number.
+    var numcol: string
+    if settings.number_lines
+      numcol = repeat(' ', margin - 1 - strlen(lnum)) .. lnum .. ' '
     endif
 
-    " put numcol in a separate group for sake of unselectable text
-    let s:new = (s:settings.number_lines ? s:HtmlFormat_n(s:numcol, s:FOLDED_ID, 0, s:lnum): "") .. s:HtmlFormat_t(s:new, s:FOLDED_ID, 0)
-
-    " Skip to the end of the fold
-    let s:new_lnum = foldclosedend(s:lnum)
+    var new: string
 
-    if !s:settings.no_progress
-      call s:pgb.incr(s:new_lnum - s:lnum)
-    endif
+    if has('folding') && !settings.ignore_folding && foldclosed(lnum) > -1 && !settings.dynamic_folds
+      #
+      # This is the beginning of a folded block (with no dynamic folding)
+      new = foldtextresult(lnum)
+      if !settings.no_pre
+       # HTML line wrapping is off--go ahead and fill to the margin
+       new ..= repeat(foldfillchar, &columns - strlen(new))
+      endif
 
-    let s:lnum = s:new_lnum
+      # put numcol in a separate group for sake of unselectable text
+      new = (settings.number_lines ? HtmlFormat_n(numcol, FOLDED_ID, 0, lnum) : "") .. HtmlFormat_t(new, FOLDED_ID, 0)
 
-  else
-    "
-    " A line that is not folded, or doing dynamic folding.
-    "
-    let s:line = getline(s:lnum)
-    let s:len = strlen(s:line)
-
-    if s:settings.dynamic_folds
-      " First insert a closing for any open folds that end on this line
-      while !empty(s:foldstack) && get(s:foldstack,0).lastline == s:lnum-1
-       let s:new = s:new.."</span></span>"
-       call remove(s:foldstack, 0)
-      endwhile
+      # Skip to the end of the fold
+      var new_lnum = foldclosedend(lnum)
 
-      " Now insert an opening for any new folds that start on this line
-      let s:firstfold = 1
-      while !empty(s:allfolds) && get(s:allfolds,0).firstline == s:lnum
-       let s:foldId = s:foldId + 1
-       let s:new ..= "<span id='"
-       let s:new ..= (exists('g:html_diff_win_num') ? "win"..g:html_diff_win_num : "")
-       let s:new ..= "fold"..s:foldId..s:settings.id_suffix.."' class='"..s:allfolds[0].type.."'>"
-
-
-       " Unless disabled, add a fold column for the opening line of a fold.
-       "
-       " Note that dynamic folds require using css so we just use css to take
-       " care of the leading spaces rather than using &nbsp; in the case of
-       " html_no_pre to make it easier
-       if !s:settings.no_foldcolumn
-         " add fold column that can open the new fold
-         if s:allfolds[0].level > 1 && s:firstfold
-           let s:new = s:new .. s:FoldColumn_build('|', s:allfolds[0].level - 1, 0, "",
-                 \ 'toggle-open FoldColumn','javascript:toggleFold("fold'..s:foldstack[0].id..s:settings.id_suffix..'");')
-         endif
-         " add the filler spaces separately from the '+' char so that it can be
-         " shown/hidden separately during a hover unfold
-         let s:new = s:new .. s:FoldColumn_build("+", 1, 0, "",
-               \ 'toggle-open FoldColumn', 'javascript:toggleFold("fold'..s:foldId..s:settings.id_suffix..'");')
-         " If this is not the last fold we're opening on this line, we need
-         " to keep the filler spaces hidden if the fold is opened by mouse
-         " hover. If it is the last fold to open in the line, we shouldn't hide
-         " them, so don't apply the toggle-filler class.
-         let s:new = s:new .. s:FoldColumn_build(" ", 1, s:foldcolumn - s:allfolds[0].level - 1, "",
-               \ 'toggle-open FoldColumn'.. (get(s:allfolds, 1, {'firstline': 0}).firstline == s:lnum ?" toggle-filler" :""),
-               \ 'javascript:toggleFold("fold'..s:foldId..s:settings.id_suffix..'");')
-
-         " add fold column that can close the new fold
-         " only add extra blank space if we aren't opening another fold on the
-         " same line
-         if get(s:allfolds, 1, {'firstline': 0}).firstline != s:lnum
-           let s:extra_space = s:foldcolumn - s:allfolds[0].level
-         else
-           let s:extra_space = 0
-         endif
-         if s:firstfold
-           " the first fold in a line has '|' characters from folds opened in
-           " previous lines, before the '-' for this fold
-           let s:new ..= s:FoldColumn_build('|', s:allfolds[0].level - 1, s:extra_space, '-',
-                 \ 'toggle-closed FoldColumn', 'javascript:toggleFold("fold'..s:foldId..s:settings.id_suffix..'");')
-         else
-           " any subsequent folds in the line only add a single '-'
-           let s:new = s:new .. s:FoldColumn_build("-", 1, s:extra_space, "",
-                 \ 'toggle-closed FoldColumn', 'javascript:toggleFold("fold'..s:foldId..s:settings.id_suffix..'");')
-         endif
-         let s:firstfold = 0
-       endif
+      if !settings.no_progress
+       pgb.Incr(new_lnum - lnum)
+      endif
 
-       " Add fold text, moving the span ending to the next line so collapsing
-       " of folds works correctly.
-       " Put numcol in a separate group for sake of unselectable text.
-       let s:new = s:new .. (s:settings.number_lines ? s:HtmlFormat_n(s:numcol, s:FOLDED_ID, 0, 0) : "") .. substitute(s:HtmlFormat_t(foldtextresult(s:lnum), s:FOLDED_ID, 0), '</span>', s:HtmlEndline..'\n\0', '')
-       let s:new = s:new .. "<span class='fulltext'>"
-
-       " open the fold now that we have the fold text to allow retrieval of
-       " fold text for subsequent folds
-       execute s:lnum.."foldopen"
-       call insert(s:foldstack, remove(s:allfolds,0))
-       let s:foldstack[0].id = s:foldId
-      endwhile
+      lnum = new_lnum
 
-      " Unless disabled, add a fold column for other lines.
-      "
-      " Note that dynamic folds require using css so we just use css to take
-      " care of the leading spaces rather than using &nbsp; in the case of
-      " html_no_pre to make it easier
-      if !s:settings.no_foldcolumn
-       if empty(s:foldstack)
-         " add the empty foldcolumn for unfolded lines if there is a fold
-         " column at all
-         if s:foldcolumn > 0
-           let s:new = s:new .. s:FoldColumn_fill()
+    else
+      #
+      # A line that is not folded, or doing dynamic folding.
+      #
+      var line = all_lines[lnum - start_lnum]
+      var len = strlen(line)
+
+      if settings.dynamic_folds
+       # First insert a closing for any open folds that end on this line
+       while !empty(foldstack) && get(foldstack, 0).lastline == lnum - 1
+         new ..= "</span></span>"
+         remove(foldstack, 0)
+       endwhile
+
+       # Now insert an opening for any new folds that start on this line
+       var firstfold = 1
+       while !empty(allfolds) && get(allfolds, 0).firstline == lnum
+         foldId = foldId + 1
+         new ..= "<span id='"
+         new ..= (exists('g:html_diff_win_num') ? "win" .. g:html_diff_win_num : "")
+         new ..= "fold" .. foldId .. settings.id_suffix .. "' class='" .. allfolds[0].type .. "'>"
+
+
+         # Unless disabled, add a fold column for the opening line of a fold.
+         #
+         # Note that dynamic folds require using css so we just use css to take
+         # care of the leading spaces rather than using &nbsp; in the case of
+         # html_no_pre to make it easier
+         if !settings.no_foldcolumn
+           # add fold column that can open the new fold
+           if allfolds[0].level > 1 && firstfold
+             new ..= FoldColumn_build('|', allfolds[0].level - 1, 0, "",
+               'toggle-open FoldColumn', 'javascript:toggleFold("fold' .. foldstack[0].id .. settings.id_suffix .. '");')
+           endif
+           # add the filler spaces separately from the '+' char so that it can be
+           # shown/hidden separately during a hover unfold
+           new ..= FoldColumn_build("+", 1, 0, "",
+             'toggle-open FoldColumn', 'javascript:toggleFold("fold' .. foldId .. settings.id_suffix .. '");')
+           # If this is not the last fold we're opening on this line, we need
+           # to keep the filler spaces hidden if the fold is opened by mouse
+           # hover. If it is the last fold to open in the line, we shouldn't hide
+           # them, so don't apply the toggle-filler class.
+           new ..= FoldColumn_build(" ", 1, foldcolumn - allfolds[0].level - 1, "",
+             'toggle-open FoldColumn' .. (get(allfolds, 1, {firstline: 0}).firstline == lnum ? " toggle-filler" : ""),
+             'javascript:toggleFold("fold' .. foldId .. settings.id_suffix .. '");')
+
+           # add fold column that can close the new fold
+           # only add extra blank space if we aren't opening another fold on the
+           # same line
+           var extra_space: number
+           if get(allfolds, 1, {firstline: 0}).firstline != lnum
+             extra_space = foldcolumn - allfolds[0].level
+           else
+             extra_space = 0
+           endif
+           if firstfold
+             # the first fold in a line has '|' characters from folds opened in
+             # previous lines, before the '-' for this fold
+             new ..= FoldColumn_build('|', allfolds[0].level - 1, extra_space, '-',
+               'toggle-closed FoldColumn', 'javascript:toggleFold("fold' .. foldId .. settings.id_suffix .. '");')
+           else
+             # any subsequent folds in the line only add a single '-'
+             new ..= FoldColumn_build("-", 1, extra_space, "",
+               'toggle-closed FoldColumn', 'javascript:toggleFold("fold' .. foldId .. settings.id_suffix .. '");')
+           endif
+           firstfold = 0
          endif
-       else
-         " add the fold column for folds not on the opening line
-         if get(s:foldstack, 0).firstline < s:lnum
-           let s:new = s:new .. s:FoldColumn_build('|', s:foldstack[0].level, s:foldcolumn - s:foldstack[0].level, "",
-                 \ 'FoldColumn', 'javascript:toggleFold("fold'..s:foldstack[0].id..s:settings.id_suffix..'");')
+
+         # Add fold text, moving the span ending to the next line so collapsing
+         # of folds works correctly.
+         # Put numcol in a separate group for sake of unselectable text.
+         new ..= (settings.number_lines ? HtmlFormat_n(numcol, FOLDED_ID, 0, 0) : "") .. substitute(HtmlFormat_t(foldtextresult(lnum), FOLDED_ID, 0), '</span>', HtmlEndline .. '\n\0', '')
+         new ..= "<span class='fulltext'>"
+
+         # open the fold now that we have the fold text to allow retrieval of
+         # fold text for subsequent folds
+         execute $":{lnum}foldopen"
+         insert(foldstack, remove(allfolds, 0), 0)
+         foldstack[0].id = foldId
+       endwhile
+
+       # Unless disabled, add a fold column for other lines.
+       #
+       # Note that dynamic folds require using css so we just use css to take
+       # care of the leading spaces rather than using &nbsp; in the case of
+       # html_no_pre to make it easier
+       if !settings.no_foldcolumn
+         if empty(foldstack)
+           # add the empty foldcolumn for unfolded lines if there is a fold
+           # column at all
+           if foldcolumn > 0
+             new = new .. FoldColumn_fill()
+           endif
+         else
+           # add the fold column for folds not on the opening line
+           if get(foldstack, 0).firstline < lnum
+             new = new .. FoldColumn_build('|', foldstack[0].level, foldcolumn - foldstack[0].level, "",
+               'FoldColumn', 'javascript:toggleFold("fold' .. foldstack[0].id .. settings.id_suffix .. '");')
+           endif
          endif
        endif
       endif
-    endif
 
-    " Now continue with the unfolded line text
-    if s:settings.number_lines
-      let s:new = s:new .. s:HtmlFormat_n(s:numcol, s:LINENR_ID, 0, s:lnum)
-    elseif s:settings.line_ids
-      let s:new = s:new .. s:HtmlFormat_n("", s:LINENR_ID, 0, s:lnum)
-    endif
+      # Now continue with the unfolded line text
+      if settings.number_lines
+       new ..= HtmlFormat_n(numcol, LINENR_ID, 0, lnum)
+      elseif settings.line_ids
+       new ..= HtmlFormat_n("", LINENR_ID, 0, lnum)
+      endif
 
-    " Get the diff attribute, if any.
-    let s:diffattr = diff_hlID(s:lnum, 1)
+      # Get the diff attribute, if any.
+      var diffattr = diff_hlID(lnum, 1)
 
-    " initialize conceal info to act like not concealed, just in case
-    let s:concealinfo = [0, '']
+      # initialize conceal info to act like not concealed, just in case
+      var concealinfo = [0, '']
 
-    " Loop over each character in the line
-    let s:col = 1
+      # Loop over each character in the line
+      var col = 1
 
-    " most of the time we won't use the diff_id, initialize to zero
-    let s:diff_id = 0
+      # most of the time we won't use the diff_id, initialize to zero
+      var diff_id = 0
 
-    while s:col <= s:len || (s:col == 1 && s:diffattr)
-      let s:startcol = s:col " The start column for processing text
-      if !s:settings.ignore_conceal && has('conceal')
-       let s:concealinfo = synconcealed(s:lnum, s:col)
-      endif
-      if !s:settings.ignore_conceal && s:concealinfo[0]
-       let s:col = s:col + 1
-       " Speed loop (it's small - that's the trick)
-       " Go along till we find a change in the match sequence number (ending
-       " the specific concealed region) or until there are no more concealed
-       " characters.
-       while s:col <= s:len && s:concealinfo == synconcealed(s:lnum, s:col) | let s:col = s:col + 1 | endwhile
-      elseif s:diffattr
-       let s:diff_id = diff_hlID(s:lnum, s:col)
-       let s:id = synID(s:lnum, s:col, 1)
-       let s:col = s:col + 1
-       " Speed loop (it's small - that's the trick)
-       " Go along till we find a change in hlID
-       while s:col <= s:len && s:id == synID(s:lnum, s:col, 1)
-             \   && s:diff_id == diff_hlID(s:lnum, s:col) |
-             \     let s:col = s:col + 1 |
-             \ endwhile
-       if s:len < &columns && !s:settings.no_pre
-         " Add spaces at the end of the raw text line to extend the changed
-         " line to the full width.
-         let s:line = s:line .. repeat(' ', &columns - virtcol([s:lnum, s:len]) - s:margin)
-         let s:len = &columns
+      while col <= len || (col == 1 && diffattr != 0)
+       var id: number
+       var startcol = col # The start column for processing text
+       if !settings.ignore_conceal && has('conceal')
+         concealinfo = synconcealed(lnum, col)
        endif
-      else
-       let s:id = synID(s:lnum, s:col, 1)
-       let s:col = s:col + 1
-       " Speed loop (it's small - that's the trick)
-       " Go along till we find a change in synID
-       while s:col <= s:len && s:id == synID(s:lnum, s:col, 1) | let s:col = s:col + 1 | endwhile
-      endif
-
-      if s:settings.ignore_conceal || !s:concealinfo[0]
-       " Expand tabs if needed
-       let s:expandedtab = strpart(s:line, s:startcol - 1, s:col - s:startcol)
-       if s:settings.expand_tabs
-         let s:offset = 0
-         let s:idx = stridx(s:expandedtab, "\t")
-         let s:tablist = split(&vts,',')
-         if empty(s:tablist)
-           let s:tablist = [ &ts ]
+       if !settings.ignore_conceal && concealinfo[0]
+         col = col + 1
+         # Speed loop (it's small - that's the trick)
+         # Go along till we find a change in the match sequence number (ending
+         # the specific concealed region) or until there are no more concealed
+         # characters.
+         while col <= len && concealinfo == synconcealed(lnum, col) | col = col + 1 | endwhile
+       elseif diffattr != 0
+         diff_id = diff_hlID(lnum, col)
+         id = synID(lnum, col, 1)
+         col = col + 1
+         # Speed loop (it's small - that's the trick)
+         # Go along till we find a change in hlID
+         while col <= len && id == synID(lnum, col, 1) && diff_id == diff_hlID(lnum, col)
+           col = col + 1
+         endwhile
+         if len < &columns && !settings.no_pre
+           # Add spaces at the end of the raw text line to extend the changed
+           # line to the full width.
+           line = line .. repeat(' ', &columns - virtcol([lnum, len]) - margin)
+           len = &columns
          endif
-         let s:tabidx = 0
-         let s:tabwidth = 0
-         while s:idx >= 0
-           if s:startcol + s:idx == 1
-             let s:i = s:tablist[0]
-           else
-             " Get the character, which could be multiple bytes, which falls
-             " immediately before the found tab. Extract it by matching a
-             " character just prior to the column where the tab matches.
-             " We'll use this to get the byte index of the character
-             " immediately preceding the tab, so we can then look up the
-             " virtual column that character appears in, to determine how
-             " much of the current tabstop has been used up.
-             if s:idx == 0
-               " if the found tab is the first character in the text being
-               " processed, we need to get the character prior to the text,
-               " given by startcol.
-               let s:prevc = matchstr(s:line, '.\%' .. (s:startcol + s:offset) .. 'c')
+       else
+         id = synID(lnum, col, 1)
+         col = col + 1
+         # Speed loop (it's small - that's the trick)
+         # Go along till we find a change in synID
+         while col <= len && id == synID(lnum, col, 1) | col = col + 1 | endwhile
+       endif
+       var expandedtab: string
+       if settings.ignore_conceal || !concealinfo[0]
+         # Expand tabs if needed
+         expandedtab = strpart(line, startcol - 1, col - startcol)
+         if settings.expand_tabs
+           var offset = 0
+           var idx = stridx(expandedtab, "\t")
+           var tablist = mapnew(split(&vts, ','), (_, v) => str2nr(v))
+           if empty(tablist)
+             tablist = [&ts]
+           endif
+           var tabidx = 0
+           var tabwidth = 0
+           var i: number
+           while idx >= 0
+             if startcol + idx == 1
+               i = tablist[0]
              else
-               " Otherwise, the byte index of the tab into s:expandedtab is
-               " given by s:idx.
-               let s:prevc = matchstr(s:expandedtab, '.\%' .. (s:idx + 1) .. 'c')
-             endif
-             let s:vcol = virtcol([s:lnum, s:startcol + s:idx + s:offset - len(s:prevc)])
-
-             " find the tabstop interval to use for the tab we just found. Keep
-             " adding tabstops (which could be variable) until we would exceed
-             " the virtual screen position of the start of the found tab.
-             while s:vcol >= s:tabwidth + s:tablist[s:tabidx]
-               let s:tabwidth += s:tablist[s:tabidx]
-               if s:tabidx < len(s:tablist)-1
-                 let s:tabidx = s:tabidx+1
+               # Get the character, which could be multiple bytes, which falls
+               # immediately before the found tab. Extract it by matching a
+               # character just prior to the column where the tab matches.
+               # We'll use this to get the byte index of the character
+               # immediately preceding the tab, so we can then look up the
+               # virtual column that character appears in, to determine how
+               # much of the current tabstop has been used up.
+               var prevc: string
+               if idx == 0
+                 # if the found tab is the first character in the text being
+                 # processed, we need to get the character prior to the text,
+                 # given by startcol.
+                 prevc = matchstr(line, '.\%' .. (startcol + offset) .. 'c')
+               else
+                 # Otherwise, the byte index of the tab into expandedtab is
+                 # given by idx.
+                 prevc = matchstr(expandedtab, '.\%' .. (idx + 1) .. 'c')
                endif
-             endwhile
-             let s:i = s:tablist[s:tabidx] - (s:vcol - s:tabwidth)
-           endif
-           " update offset to keep the index within the line corresponding to
-           " actual tab characters instead of replaced spaces; s:idx reflects
-           " replaced spaces in s:expandedtab, s:offset cancels out all but
-           " the tab character itself.
-           let s:offset -= s:i - 1
-           let s:expandedtab = substitute(s:expandedtab, '\t', repeat(' ', s:i), '')
-           let s:idx = stridx(s:expandedtab, "\t")
-         endwhile
-       end
-
-       " get the highlight group name to use
-       let s:id = synIDtrans(s:id)
-      else
-       " use Conceal highlighting for concealed text
-       let s:id = s:CONCEAL_ID
-       let s:expandedtab = s:concealinfo[1]
-      endif
-
-      " Output the text with the same synID, with class set to the highlight ID
-      " name, unless it has been concealed completely.
-      if strlen(s:expandedtab) > 0
-       let s:new = s:new .. s:HtmlFormat(s:expandedtab,  s:id, s:diff_id, "", 0)
-      endif
-    endwhile
-  endif
+               var vcol = virtcol([lnum, startcol + idx + offset - len(prevc)])
+
+               # find the tabstop interval to use for the tab we just found. Keep
+               # adding tabstops (which could be variable) until we would exceed
+               # the virtual screen position of the start of the found tab.
+               while vcol >= tabwidth + tablist[tabidx]
+                 tabwidth += tablist[tabidx]
+                 if tabidx < len(tablist) - 1
+                   tabidx = tabidx + 1
+                 endif
+               endwhile
+               i = tablist[tabidx] - (vcol - tabwidth)
+             endif
+             # update offset to keep the index within the line corresponding to
+             # actual tab characters instead of replaced spaces; idx reflects
+             # replaced spaces in expandedtab, offset cancels out all but
+             # the tab character itself.
+             offset -= i - 1
+             expandedtab = substitute(expandedtab, '\t', repeat(' ', i), '')
+             idx = stridx(expandedtab, "\t")
+           endwhile
+         endif
 
-  call extend(s:lines, split(s:new..s:HtmlEndline, '\n', 1))
-  if !s:settings.no_progress && s:pgb.needs_redraw
-    redrawstatus
-    let s:pgb.needs_redraw = 0
-  endif
-  let s:lnum = s:lnum + 1
+         # get the highlight group name to use
+         id = synIDtrans(id)
+       else
+         # use Conceal highlighting for concealed text
+         id = CONCEAL_ID
+         expandedtab = concealinfo[1]
+       endif
 
-  if !s:settings.no_progress
-    call s:pgb.incr()
-  endif
-endwhile
+       # Output the text with the same synID, with class set to the highlight ID
+       # name, unless it has been concealed completely.
+       if strlen(expandedtab) > 0
+         new = new .. HtmlFormat(expandedtab, id, diff_id, "", false)
+       endif
+      endwhile
+    endif
 
-" Diff filler is returned based on what needs inserting *before* the given line.
-" So to get diff filler at the end of the buffer, we need to use last line + 1
-call s:Add_diff_fill(s:end+1)
+    extend(lines, split(new .. HtmlEndline, '\n', 1))
+    if !settings.no_progress && pgb.needs_redraw
+      redrawstatus
+      pgb.needs_redraw = 0
+    endif
+    lnum = lnum + 1
 
-if s:settings.dynamic_folds
-  " finish off any open folds
-  while !empty(s:foldstack)
-    let s:lines[-1]..="</span></span>"
-    call remove(s:foldstack, 0)
+    if !settings.no_progress
+      pgb.Incr()
+    endif
+  endwhile
+enddef
+ProcessLines()
+
+# Diff filler is returned based on what needs inserting *before* the given line.
+# So to get diff filler at the end of the buffer, we need to use last line + 1
+Add_diff_fill(end + 1)
+
+if settings.dynamic_folds
+  # finish off any open folds
+  while !empty(foldstack)
+    lines[-1] ..= "</span></span>"
+    remove(foldstack, 0)
   endwhile
 
-  " add fold column to the style list if not already there
-  let s:id = s:FOLD_C_ID
-  if !has_key(s:stylelist, s:id)
-    let s:stylelist[s:id] = '.FoldColumn { ' .. s:CSS1(s:id) .. '}'
+  # add fold column to the style list if not already there
+  var id = FOLD_C_ID
+  if !has_key(stylelist, id)
+    stylelist[id] = '.FoldColumn { ' .. CSS1(id) .. '}'
   endif
 endif
 
-if s:settings.no_pre
-  if !s:settings.use_css
-    " Close off the font tag that encapsulates the whole <body>
-    call extend(s:lines, ["</font>"])
+if settings.no_pre
+  if !settings.use_css
+    # Close off the font tag that encapsulates the whole <body>
+    extend(lines, ["</font>"])
   else
-    call extend(s:lines, ["</div>"])
+    extend(lines, ["</div>"])
   endif
 else
-  call extend(s:lines, ["</pre>"])
+  extend(lines, ["</pre>"])
 endif
-if !s:settings.no_doc
-  call extend(s:lines, ["</body>", "</html>"])
+if !settings.no_doc
+  extend(lines, ["</body>", "</html>"])
 endif
 
-exe s:newwin .. "wincmd w"
-call setline(1, s:lines)
-unlet s:lines
-
-" Mangle modelines so Vim doesn't try to use HTML text as a modeline if editing
-" this file in the future; need to do this after generating all the text in case
-" the modeline text has different highlight groups which all turn out to be
-" stripped from the final output.
-%s!\v(%(^|\s+)%([Vv]i%(m%([<=>]?\d+)?)?|ex)):!\1\&#0058;!ge
-
-" 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.
-if !s:settings.no_modeline
-  call append(line('$'), "<!-- vim: set foldmethod=manual : -->")
+execute $":{newwin}wincmd w"
+setline(1, lines)
+
+# Mangle modelines so Vim doesn't try to use HTML text as a modeline if editing
+# this file in the future; need to do this after generating all the text in case
+# the modeline text has different highlight groups which all turn out to be
+# stripped from the final output.
+:%s!\v(%(^|\s+)%([Vv]i%(m%([<=>]?\d+)?)?|ex)):!\1\&#0058;!ge
+
+# 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.
+if !settings.no_modeline
+  append(line('$'), "<!-- vim: set foldmethod=manual : -->")
 endif
 
-" Now, when we finally know which, we define the colors and styles
-if s:settings.use_css && !s:settings.no_doc
-  1;/<style\>/+1
+# Now, when we finally know which, we define the colors and styles
+if settings.use_css && !settings.no_doc
+  :1;/<style\>/+1
 
-  " Normal/global attributes
-  if s:settings.no_pre
-    call append('.', "body { color: " .. s:fgc .. "; background-color: " .. s:bgc .. "; font-family: ".. s:htmlfont .."; }")
-    +
+  # Normal/global attributes
+  if settings.no_pre
+    append('.', "body { color: " .. fgc .. "; background-color: " .. bgc .. "; font-family: " .. htmlfont .. "; }")
+    :+
   else
-    call append('.', "pre { " .. s:whitespace .. "font-family: ".. s:htmlfont .."; color: " .. s:fgc .. "; background-color: " .. s:bgc .. "; }")
-    +
+    append('.', "pre { " .. whitespace .. "font-family: " .. htmlfont .. "; color: " .. fgc .. "; background-color: " .. bgc .. "; }")
+    :+
     yank
     put
     execute "normal! ^cwbody\e"
-    " body should not have the wrap formatting, only the pre section
-    if s:whitespace != ''
-      exec 's#'..s:whitespace
+    # body should not have the wrap formatting, only the pre section
+    if whitespace != ''
+      execute 's#' .. whitespace
     endif
   endif
-  " fix browser inconsistencies (sometimes within the same browser) of different
-  " default font size for different elements
-  call append('.', '* { font-size: 1em; }')
-  +
-  " use color scheme styles for links
-  " browser-default blue/purple colors for links don't look like the existing theme and are unreadable on dark backgrounds
-  call append('.', 'a { color: inherit; }')
-  +
-  " if we use any input elements for unselectable content, make sure they look
-  " like normal text
-  if !empty(s:settings.prevent_copy)
-    if s:settings.use_input_for_pc !=# "none"
-      call append('.', 'input { border: none; margin: 0; padding: 0; font-family: '..s:htmlfont..'; }')
-      +
-      " ch units for browsers which support them, em units for a somewhat
-      " reasonable fallback.
+  # fix browser inconsistencies (sometimes within the same browser) of different
+  # default font size for different elements
+  append('.', '* { font-size: 1em; }')
+  :+
+  # use color scheme styles for links
+  # browser-default blue/purple colors for links don't look like the existing theme and are unreadable on dark backgrounds
+  append('.', 'a { color: inherit; }')
+  :+
+  # if we use any input elements for unselectable content, make sure they look
+  # like normal text
+  if !empty(settings.prevent_copy)
+    if settings.use_input_for_pc !=# "none"
+      append('.', 'input { border: none; margin: 0; padding: 0; font-family: ' .. htmlfont .. '; }')
+      :+
+      # ch units for browsers which support them, em units for a somewhat
+      # reasonable fallback.
       for w in range(1, 20, 1)
-       call append('.', [
-             \ "input[size='"..w.."'] { width: "..w.."em; width: "..w.."ch; }"
-             \ ])
-       +
+       append('.', [
+         "input[size='" .. w .. "'] { width: " .. w .. "em; width: " .. w .. "ch; }"
+       ])
+       :+
       endfor
     endif
 
-    if s:settings.use_input_for_pc !=# 'all'
-      let s:unselectable_styles = []
-      if s:settings.prevent_copy =~# 'f'
-       call add(s:unselectable_styles, 'FoldColumn')
+    if settings.use_input_for_pc !=# 'all'
+      var unselectable_styles: list<string> = []
+      if settings.prevent_copy =~# 'f'
+       add(unselectable_styles, 'FoldColumn')
       endif
-      if s:settings.prevent_copy =~# 'n'
-       call add(s:unselectable_styles, 'LineNr')
+      if settings.prevent_copy =~# 'n'
+       add(unselectable_styles, 'LineNr')
       endif
-      if s:settings.prevent_copy =~# 't' && !s:settings.ignore_folding
-       call add(s:unselectable_styles, 'Folded')
+      if settings.prevent_copy =~# 't' && !settings.ignore_folding
+       add(unselectable_styles, 'Folded')
       endif
-      if s:settings.prevent_copy =~# 'd'
-       call add(s:unselectable_styles, 'DiffDelete')
+      if settings.prevent_copy =~# 'd'
+       add(unselectable_styles, 'DiffDelete')
       endif
-      if s:settings.use_input_for_pc !=# 'none'
-       call append('.', [
-             \ '/* Note: IE does not support @supports conditionals, but also does not fully support',
-             \ '   "content:" with custom content, so we *want* the check to fail */',
-             \ '@supports ( content: attr(data-custom-content) ) {'
-             \ ])
-       +3
+      if settings.use_input_for_pc !=# 'none'
+       append('.', [
+         '/* Note: IE does not support @supports conditionals, but also does not fully support',
+         '   "content:" with custom content, so we *want* the check to fail */',
+         '@supports ( content: attr(data-custom-content) ) {'
+       ])
+       :+3
       endif
-      " The line number column inside the foldtext is styled just like the fold
-      " text in Vim, but it should use the prevent_copy settings of line number
-      " rather than fold text. Apply the prevent_copy styles to foldtext
-      " specifically for line numbers, which always come after the fold column,
-      " or at the beginning of the line.
-      if s:settings.prevent_copy =~# 'n' && !s:settings.ignore_folding
-       call append('.', [
-             \ '  .FoldColumn + .Folded, .Folded:first-child { user-select: none; }',
-             \ '  .FoldColumn + [data-Folded-content]::before, [data-Folded-content]:first-child::before { content: attr(data-Folded-content); }',
-             \ '  .FoldColumn + [data-Folded-content]::before, [data-Folded-content]:first-child::before { padding-bottom: 1px; display: inline-block; /* match the 1-px padding of standard items with background */ }',
-             \ '  .FoldColumn + span[data-Folded-content]::before, [data-Folded-content]:first-child::before { cursor: default; }',
-             \ ])
-       +4
+      # The line number column inside the foldtext is styled just like the fold
+      # text in Vim, but it should use the prevent_copy settings of line number
+      # rather than fold text. Apply the prevent_copy styles to foldtext
+      # specifically for line numbers, which always come after the fold column,
+      # or at the beginning of the line.
+      if settings.prevent_copy =~# 'n' && !settings.ignore_folding
+       append('.', [
+         '  .FoldColumn + .Folded, .Folded:first-child { user-select: none; }',
+         '  .FoldColumn + [data-Folded-content]::before, [data-Folded-content]:first-child::before { content: attr(data-Folded-content); }',
+         '  .FoldColumn + [data-Folded-content]::before, [data-Folded-content]:first-child::before { padding-bottom: 1px; display: inline-block; /* match the 1-px padding of standard items with background */ }',
+         '  .FoldColumn + span[data-Folded-content]::before, [data-Folded-content]:first-child::before { cursor: default; }',
+       ])
+       :+4
       endif
-      for s:style_name in s:unselectable_styles
-       call append('.', [
-             \ '  .'..s:style_name..' { user-select: none; }',
-             \ '  [data-'..s:style_name..'-content]::before { content: attr(data-'..s:style_name..'-content); }',
-             \ '  [data-'..s:style_name..'-content]::before { padding-bottom: 1px; display: inline-block; /* match the 1-px padding of standard items with background */ }',
-             \ '  span[data-'..s:style_name..'-content]::before { cursor: default; }',
-             \ ])
-       +4
+      for style_name in unselectable_styles
+       append('.', [
+         '  .' .. style_name .. ' { user-select: none; }',
+         '  [data-' .. style_name .. '-content]::before { content: attr(data-' .. style_name .. '-content); }',
+         '  [data-' .. style_name .. '-content]::before { padding-bottom: 1px; display: inline-block; /* match the 1-px padding of standard items with background */ }',
+         '  span[data-' .. style_name .. '-content]::before { cursor: default; }',
+       ])
+       :+4
       endfor
-      if s:settings.use_input_for_pc !=# 'none'
-       " Note, the extra '}' is to match the "@supports" above
-       call append('.', [
-             \ '  input { display: none; }',
-             \ '}'
-             \ ])
-       +2
+      if settings.use_input_for_pc !=# 'none'
+       # Note, the extra '}' is to match the "@supports" above
+       append('.', [
+         '  input { display: none; }',
+         '}'
+       ])
+       :+2
       endif
-      unlet s:unselectable_styles
     endif
 
-    " Fix mouse cursor shape for the fallback <input> method of uncopyable text
-    if s:settings.use_input_for_pc !=# 'none'
-      if s:settings.prevent_copy =~# 'f'
-       " Make the cursor show active fold columns as active areas, and empty fold
-       " columns as not interactive.
-       call append('.', ['input.FoldColumn { cursor: pointer; }',
-             \ 'input.FoldColumn[value="'..repeat(' ', s:foldcolumn)..'"] { cursor: default; }'
-             \ ])
-       +2
-       if s:settings.use_input_for_pc !=# 'all'
-         call append('.', [
-               \ 'a[data-FoldColumn-content="'..repeat(' ', s:foldcolumn)..'"] { cursor: default; }'
-               \ ])
-         +1
-       end
+    # Fix mouse cursor shape for the fallback <input> method of uncopyable text
+    if settings.use_input_for_pc !=# 'none'
+      if settings.prevent_copy =~# 'f'
+       # Make the cursor show active fold columns as active areas, and empty fold
+       # columns as not interactive.
+       append('.', ['input.FoldColumn { cursor: pointer; }',
+         'input.FoldColumn[value="' .. repeat(' ', foldcolumn) .. '"] { cursor: default; }'
+       ])
+       :+2
+       if settings.use_input_for_pc !=# 'all'
+         append('.', [
+           'a[data-FoldColumn-content="' .. repeat(' ', foldcolumn) .. '"] { cursor: default; }'
+         ])
+         :+1
+       endif
       endif
-      " make line number column show as non-interactive if not selectable
-      if s:settings.prevent_copy =~# 'n'
-       call append('.', 'input.LineNr { cursor: default; }')
-       +
+      # make line number column show as non-interactive if not selectable
+      if settings.prevent_copy =~# 'n'
+       append('.', 'input.LineNr { cursor: default; }')
+       :+
       endif
-      " make fold text and line number column within fold text show as
-      " non-interactive if not selectable
-      if (s:settings.prevent_copy =~# 'n' || s:settings.prevent_copy =~# 't') && !s:settings.ignore_folding
-       call append('.', 'input.Folded { cursor: default; }')
-       +
+      # make fold text and line number column within fold text show as
+      # non-interactive if not selectable
+      if (settings.prevent_copy =~# 'n' || settings.prevent_copy =~# 't') && !settings.ignore_folding
+       append('.', 'input.Folded { cursor: default; }')
+       :+
       endif
-      " make diff filler show as non-interactive if not selectable
-      if s:settings.prevent_copy =~# 'd'
-       call append('.', 'input.DiffDelete { cursor: default; }')
-       +
+      # make diff filler show as non-interactive if not selectable
+      if settings.prevent_copy =~# 'd'
+       append('.', 'input.DiffDelete { cursor: default; }')
+       :+
       endif
     endif
   endif
 endif
 
-if !s:settings.use_css && !s:settings.no_doc
-  " For Netscape 4, set <body> attributes too, though, strictly speaking, it's
-  " incorrect.
-  execute '%s:<body\([^>]*\):<body bgcolor="' .. s:bgc .. '" text="' .. s:fgc .. '"\1>\r<font face="'.. s:htmlfont ..'"'
+if !settings.use_css && !settings.no_doc
+  # For Netscape 4, set <body> attributes too, though, strictly speaking, it's
+  # incorrect.
+  execute $':%s/<body\([^>]*\)/<body bgcolor="{bgc}" text="{fgc}"\1>\r<font face="{htmlfont}"'
 endif
 
-" Gather attributes for all other classes. Do diff first so that normal
-" highlight groups are inserted before it.
-if s:settings.use_css && !s:settings.no_doc
-  if s:diff_mode
-    call append('.', filter(map(keys(s:diffstylelist), "s:diffstylelist[v:val]"), 'v:val != ""'))
+# Gather attributes for all other classes. Do diff first so that normal
+# highlight groups are inserted before it.
+if settings.use_css && !settings.no_doc
+  if diff_mode
+    append('.', filter(map(keys(diffstylelist), (_, k) => diffstylelist[k]), (_, v) => v != ""))
   endif
-  if !empty(s:stylelist)
-    call append('.', filter(map(keys(s:stylelist), "s:stylelist[v:val]"), 'v:val != ""'))
+  if !empty(stylelist)
+    append('.', filter(map(keys(stylelist), (_, k) => stylelist[k]), (_, v) => v != ""))
   endif
 endif
 
-" Add hyperlinks
-if !s:settings.no_links
-  %s+\(https\=://\S\{-}\)\(\([.,;:}]\=\(\s\|$\)\)\|[\\"'<>]\|&gt;\|&lt;\|&quot;\)+<a href="\1">\1</a>\2+ge
+# Add hyperlinks
+if !settings.no_links
+  :%s+\(https\=://\S\{-}\)\(\([.,;:}]\=\(\s\|$\)\)\|[\\"'<>]\|&gt;\|&lt;\|&quot;\)+<a href="\1">\1</a>\2+ge
 endif
 
-" The DTD
-if !s:settings.no_doc
-  if s:settings.use_xhtml
-    exe "normal! gg$a\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"
-  elseif s:html5
-    exe "normal! gg0i<!DOCTYPE html>\n"
+# The DTD
+if !settings.no_doc
+  if settings.use_xhtml
+    execute "normal! gg$a\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"
+  elseif html5
+    execute "normal! gg0i<!DOCTYPE html>\n"
   else
-    exe "normal! gg0i<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n"
+    execute "normal! gg0i<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n"
   endif
 endif
 
-if s:settings.use_xhtml && !s:settings.no_doc
-  exe "normal! gg/<html/e\na xmlns=\"http://www.w3.org/1999/xhtml\"\e"
+if settings.use_xhtml && !settings.no_doc
+  execute "normal! gg/<html/e\na xmlns=\"http://www.w3.org/1999/xhtml\"\e"
 endif
 
-" Cleanup
-%s:\s\+$::e
-
-" Restore old settings (new window first)
-"
-" Don't bother restoring foldmethod in case it was syntax because the markup is
-" so weirdly formatted it can take a LONG time.
-let &l:foldenable = s:old_fen
-let &report = s:old_report
-let &title = s:old_title
-let &icon = s:old_icon
-let &paste = s:old_paste
-let &magic = s:old_magic
-let @/ = s:old_search
-let &more = s:old_more
-
-" switch to original window to restore those settings
-exe s:orgwin .. "wincmd w"
-
-if !s:settings.expand_tabs
-  let &l:isprint = s:old_isprint
+# Cleanup
+:%s/\s\+$//e
+
+# Restore old settings (new window first)
+#
+# Don't bother restoring foldmethod in case it was syntax because the markup is
+# so weirdly formatted it can take a LONG time.
+&l:foldenable = old_fen
+&report = old_report
+&title = old_title
+&icon = old_icon
+&paste = old_paste
+&magic = old_magic
+@/ = old_search
+&more = old_more
+
+# switch to original window to restore those settings
+execute $":{orgwin}wincmd w"
+
+if !settings.expand_tabs
+  &l:isprint = old_isprint
 endif
-let &l:stl = s:origwin_stl
-let &l:et = s:old_et
-let &l:scrollbind = s:old_bind
-
-" and back to the new window again to end there
-exe s:newwin .. "wincmd w"
-
-let &l:stl = s:newwin_stl
-exec 'resize' s:old_winheight
-let &l:winfixheight = s:old_winfixheight
-
-let &ls=s:ls
-let &eventignore=s:ei_sav
-
-" Save a little bit of memory (worth doing?)
-unlet s:htmlfont s:whitespace
-unlet s:old_et s:old_paste s:old_icon s:old_report s:old_title s:old_search
-unlet s:old_magic s:old_more s:old_fen s:old_winheight
-unlet! s:old_isprint
-unlet s:whatterm s:stylelist s:diffstylelist s:lnum s:end s:margin s:fgc s:bgc s:old_winfixheight
-unlet! s:col s:id s:attr s:len s:line s:new s:expandedtab s:concealinfo s:diff_mode
-unlet! s:orgwin s:newwin s:orgbufnr s:idx s:i s:offset s:ls s:ei_sav s:origwin_stl
-unlet! s:newwin_stl s:current_syntax
-if !v:profiling
-  delfunc s:HtmlColor
-  delfunc s:HtmlFormat
-  delfunc s:CSS1
-  delfunc s:BuildStyleWrapper
-  if !s:settings.use_css
-    delfunc s:HtmlOpening
-    delfunc s:HtmlClosing
-  endif
-  if s:settings.dynamic_folds
-    delfunc s:FoldCompare
-  endif
+&l:stl = origwin_stl
+&l:et = old_et
+&l:scrollbind = old_bind
 
-  if !s:settings.no_progress
-    delfunc s:ProgressBar
-    delfunc s:progressbar.paint
-    delfunc s:progressbar.incr
-    unlet s:pgb s:progressbar
-  endif
-
-  delfunc s:Add_diff_fill
-endif
+# and back to the new window again to end there
+execute $":{newwin}wincmd w"
 
-unlet! s:new_lnum s:diffattr s:difffillchar s:foldfillchar s:HtmlSpace s:diffstyle
-unlet! s:LeadingSpace s:HtmlEndline s:firstfold s:numcol s:foldcolumn
-unlet! s:wrapperfunc_lines s:build_fun_lines
-unlet s:foldstack s:allfolds s:foldId s:settings
+&l:stl = newwin_stl
+execute 'resize' old_winheight
+&l:winfixheight = old_winfixheight
 
-let &cpo = s:cpo_sav
-unlet! s:cpo_sav
+&laststatus = ls_sav
+&eventignore = ei_sav
 
-" Make sure any patches will probably use consistent indent
-"   vim: ts=8 sw=2 sts=2 noet
+# Make sure any patches will probably use consistent indent
+#   vim: ts=8 sw=2 sts=2 noet
index 9046d847c7ca0b126c3f5978c66aad76aa8a0860..ecabe8e367b26c9e891e54b2cd36dcb8d502491b 100644 (file)
@@ -4,7 +4,7 @@
 <meta charset="UTF-8">
 <title>/home/jiangyinzuo/vim/src/testdir/Test_tohtml_basic.c.html</title>
 <meta name="Generator" content="Vim/9.2">
-<meta name="plugin-version" content="vim9.0_v2">
+<meta name="plugin-version" content="vim9.2_v1">
 <meta name="syntax" content="none">
 <meta name="settings" content="use_css,no_foldcolumn,pre_wrap,prevent_copy=,use_input_for_pc=none">
 <meta name="colorscheme" content="none">
index 272b71248adfb9677db504f88d1bcc589e27d125..0cba690cf72e1a8433ffcecd25ef130c018fc766 100644 (file)
@@ -4,7 +4,7 @@
 <meta http-equiv="content-type" content="text/html; charset=UTF-8">
 <title>/home/jiangyinzuo/vim/src/testdir/Test_tohtml_basic_no_css.c.html</title>
 <meta name="Generator" content="Vim/9.2">
-<meta name="plugin-version" content="vim9.0_v2">
+<meta name="plugin-version" content="vim9.2_v1">
 <meta name="syntax" content="none">
 <meta name="settings" content="no_pre,no_foldcolumn,expand_tabs,prevent_copy=,use_input_for_pc=none">
 <meta name="colorscheme" content="none">