xref: /vim-8.2.3635/runtime/syntax/2html.vim (revision 6c391a74)
1" Vim syntax support file
2" Maintainer: Ben Fritz <[email protected]>
3" Last Change: 2020 Jan 05
4"
5" Additional contributors:
6"
7"             Original by Bram Moolenaar <[email protected]>
8"             Modified by David Ne\v{c}as (Yeti) <[email protected]>
9"             XHTML support by Panagiotis Issaris <[email protected]>
10"             Made w3 compliant by Edd Barrett <[email protected]>
11"             Added html_font. Edd Barrett <[email protected]>
12"             Progress bar based off code from "progressbar widget" plugin by
13"               Andreas Politz, heavily modified:
14"               http://www.vim.org/scripts/script.php?script_id=2006
15"
16"             See Mercurial change logs for more!
17
18" Transform a file into HTML, using the current syntax highlighting.
19
20" this file uses line continuations
21let s:cpo_sav = &cpo
22let s:ls  = &ls
23let s:ei_sav = &eventignore
24set cpo&vim
25
26" HTML filetype can take a while to load/highlight if the destination file
27" already exists.
28set eventignore+=FileType
29
30let s:end=line('$')
31
32" Font
33if exists("g:html_font")
34  if type(g:html_font) == type([])
35    let s:htmlfont = "'". join(g:html_font,"','") . "', monospace"
36  else
37    let s:htmlfont = "'". g:html_font . "', monospace"
38  endif
39else
40  let s:htmlfont = "monospace"
41endif
42
43let s:settings = tohtml#GetUserSettings()
44
45if s:settings.use_xhtml
46  let s:html5 = 0
47elseif s:settings.use_css && !s:settings.no_pre
48  let s:html5 = 1
49else
50  let s:html5 = 0
51endif
52
53if !exists('s:FOLDED_ID')
54  let s:FOLDED_ID  = hlID("Folded")     | lockvar s:FOLDED_ID
55  let s:FOLD_C_ID  = hlID("FoldColumn") | lockvar s:FOLD_C_ID
56  let s:LINENR_ID  = hlID('LineNr')     | lockvar s:LINENR_ID
57  let s:DIFF_D_ID  = hlID("DiffDelete") | lockvar s:DIFF_D_ID
58  let s:DIFF_A_ID  = hlID("DiffAdd")    | lockvar s:DIFF_A_ID
59  let s:DIFF_C_ID  = hlID("DiffChange") | lockvar s:DIFF_C_ID
60  let s:DIFF_T_ID  = hlID("DiffText")   | lockvar s:DIFF_T_ID
61  let s:CONCEAL_ID = hlID('Conceal')    | lockvar s:CONCEAL_ID
62endif
63
64" Whitespace
65if s:settings.pre_wrap
66  let s:whitespace = "white-space: pre-wrap; "
67else
68  let s:whitespace = ""
69endif
70
71if !empty(s:settings.prevent_copy)
72  if s:settings.no_invalid
73    " User has decided they don't want invalid markup. Still works in
74    " OpenOffice, and for text editors, but when pasting into Microsoft Word the
75    " input elements get pasted too and they cannot be deleted (at least not
76    " easily).
77    let s:unselInputType = ""
78  else
79    " Prevent from copy-pasting the input elements into Microsoft Word where
80    " they cannot be deleted easily by deliberately inserting invalid markup.
81    let s:unselInputType = " type='invalid_input_type'"
82  endif
83endif
84
85" When gui colors are not supported, we can only guess the colors.
86" TODO - is this true anymore? Is there a way to ask the terminal what colors
87" each number means or read them from some file?
88if &termguicolors || has("gui_running")
89  let s:whatterm = "gui"
90else
91  let s:whatterm = "cterm"
92  if &t_Co == 8
93    let s:cterm_color = {
94	    \   0: "#808080", 1: "#ff6060", 2: "#00ff00", 3: "#ffff00",
95	    \   4: "#8080ff", 5: "#ff40ff", 6: "#00ffff", 7: "#ffffff"
96	    \ }
97  else
98    let s:cterm_color = {
99	    \   0: "#000000", 1: "#c00000", 2: "#008000", 3: "#804000",
100	    \   4: "#0000c0", 5: "#c000c0", 6: "#008080", 7: "#c0c0c0",
101	    \   8: "#808080", 9: "#ff6060", 10: "#00ff00", 11: "#ffff00",
102	    \   12: "#8080ff", 13: "#ff40ff", 14: "#00ffff", 15: "#ffffff"
103	    \ }
104
105    " Colors for 88 and 256 come from xterm.
106    if &t_Co == 88
107      call extend(s:cterm_color, {
108	    \   16: "#000000", 17: "#00008b", 18: "#0000cd", 19: "#0000ff",
109	    \   20: "#008b00", 21: "#008b8b", 22: "#008bcd", 23: "#008bff",
110	    \   24: "#00cd00", 25: "#00cd8b", 26: "#00cdcd", 27: "#00cdff",
111	    \   28: "#00ff00", 29: "#00ff8b", 30: "#00ffcd", 31: "#00ffff",
112	    \   32: "#8b0000", 33: "#8b008b", 34: "#8b00cd", 35: "#8b00ff",
113	    \   36: "#8b8b00", 37: "#8b8b8b", 38: "#8b8bcd", 39: "#8b8bff",
114	    \   40: "#8bcd00", 41: "#8bcd8b", 42: "#8bcdcd", 43: "#8bcdff",
115	    \   44: "#8bff00", 45: "#8bff8b", 46: "#8bffcd", 47: "#8bffff",
116	    \   48: "#cd0000", 49: "#cd008b", 50: "#cd00cd", 51: "#cd00ff",
117	    \   52: "#cd8b00", 53: "#cd8b8b", 54: "#cd8bcd", 55: "#cd8bff",
118	    \   56: "#cdcd00", 57: "#cdcd8b", 58: "#cdcdcd", 59: "#cdcdff",
119	    \   60: "#cdff00", 61: "#cdff8b", 62: "#cdffcd", 63: "#cdffff",
120	    \   64: "#ff0000"
121	    \ })
122      call extend(s:cterm_color, {
123	    \   65: "#ff008b", 66: "#ff00cd", 67: "#ff00ff", 68: "#ff8b00",
124	    \   69: "#ff8b8b", 70: "#ff8bcd", 71: "#ff8bff", 72: "#ffcd00",
125	    \   73: "#ffcd8b", 74: "#ffcdcd", 75: "#ffcdff", 76: "#ffff00",
126	    \   77: "#ffff8b", 78: "#ffffcd", 79: "#ffffff", 80: "#2e2e2e",
127	    \   81: "#5c5c5c", 82: "#737373", 83: "#8b8b8b", 84: "#a2a2a2",
128	    \   85: "#b9b9b9", 86: "#d0d0d0", 87: "#e7e7e7"
129	    \ })
130    elseif &t_Co == 256
131      call extend(s:cterm_color, {
132	    \   16: "#000000", 17: "#00005f", 18: "#000087", 19: "#0000af",
133	    \   20: "#0000d7", 21: "#0000ff", 22: "#005f00", 23: "#005f5f",
134	    \   24: "#005f87", 25: "#005faf", 26: "#005fd7", 27: "#005fff",
135	    \   28: "#008700", 29: "#00875f", 30: "#008787", 31: "#0087af",
136	    \   32: "#0087d7", 33: "#0087ff", 34: "#00af00", 35: "#00af5f",
137	    \   36: "#00af87", 37: "#00afaf", 38: "#00afd7", 39: "#00afff",
138	    \   40: "#00d700", 41: "#00d75f", 42: "#00d787", 43: "#00d7af",
139	    \   44: "#00d7d7", 45: "#00d7ff", 46: "#00ff00", 47: "#00ff5f",
140	    \   48: "#00ff87", 49: "#00ffaf", 50: "#00ffd7", 51: "#00ffff",
141	    \   52: "#5f0000", 53: "#5f005f", 54: "#5f0087", 55: "#5f00af",
142	    \   56: "#5f00d7", 57: "#5f00ff", 58: "#5f5f00", 59: "#5f5f5f",
143	    \   60: "#5f5f87", 61: "#5f5faf", 62: "#5f5fd7", 63: "#5f5fff",
144	    \   64: "#5f8700"
145	    \ })
146      call extend(s:cterm_color, {
147	    \   65: "#5f875f", 66: "#5f8787", 67: "#5f87af", 68: "#5f87d7",
148	    \   69: "#5f87ff", 70: "#5faf00", 71: "#5faf5f", 72: "#5faf87",
149	    \   73: "#5fafaf", 74: "#5fafd7", 75: "#5fafff", 76: "#5fd700",
150	    \   77: "#5fd75f", 78: "#5fd787", 79: "#5fd7af", 80: "#5fd7d7",
151	    \   81: "#5fd7ff", 82: "#5fff00", 83: "#5fff5f", 84: "#5fff87",
152	    \   85: "#5fffaf", 86: "#5fffd7", 87: "#5fffff", 88: "#870000",
153	    \   89: "#87005f", 90: "#870087", 91: "#8700af", 92: "#8700d7",
154	    \   93: "#8700ff", 94: "#875f00", 95: "#875f5f", 96: "#875f87",
155	    \   97: "#875faf", 98: "#875fd7", 99: "#875fff", 100: "#878700",
156	    \   101: "#87875f", 102: "#878787", 103: "#8787af", 104: "#8787d7",
157	    \   105: "#8787ff", 106: "#87af00", 107: "#87af5f", 108: "#87af87",
158	    \   109: "#87afaf", 110: "#87afd7", 111: "#87afff", 112: "#87d700"
159	    \ })
160      call extend(s:cterm_color, {
161	    \   113: "#87d75f", 114: "#87d787", 115: "#87d7af", 116: "#87d7d7",
162	    \   117: "#87d7ff", 118: "#87ff00", 119: "#87ff5f", 120: "#87ff87",
163	    \   121: "#87ffaf", 122: "#87ffd7", 123: "#87ffff", 124: "#af0000",
164	    \   125: "#af005f", 126: "#af0087", 127: "#af00af", 128: "#af00d7",
165	    \   129: "#af00ff", 130: "#af5f00", 131: "#af5f5f", 132: "#af5f87",
166	    \   133: "#af5faf", 134: "#af5fd7", 135: "#af5fff", 136: "#af8700",
167	    \   137: "#af875f", 138: "#af8787", 139: "#af87af", 140: "#af87d7",
168	    \   141: "#af87ff", 142: "#afaf00", 143: "#afaf5f", 144: "#afaf87",
169	    \   145: "#afafaf", 146: "#afafd7", 147: "#afafff", 148: "#afd700",
170	    \   149: "#afd75f", 150: "#afd787", 151: "#afd7af", 152: "#afd7d7",
171	    \   153: "#afd7ff", 154: "#afff00", 155: "#afff5f", 156: "#afff87",
172	    \   157: "#afffaf", 158: "#afffd7"
173	    \ })
174      call extend(s:cterm_color, {
175	    \   159: "#afffff", 160: "#d70000", 161: "#d7005f", 162: "#d70087",
176	    \   163: "#d700af", 164: "#d700d7", 165: "#d700ff", 166: "#d75f00",
177	    \   167: "#d75f5f", 168: "#d75f87", 169: "#d75faf", 170: "#d75fd7",
178	    \   171: "#d75fff", 172: "#d78700", 173: "#d7875f", 174: "#d78787",
179	    \   175: "#d787af", 176: "#d787d7", 177: "#d787ff", 178: "#d7af00",
180	    \   179: "#d7af5f", 180: "#d7af87", 181: "#d7afaf", 182: "#d7afd7",
181	    \   183: "#d7afff", 184: "#d7d700", 185: "#d7d75f", 186: "#d7d787",
182	    \   187: "#d7d7af", 188: "#d7d7d7", 189: "#d7d7ff", 190: "#d7ff00",
183	    \   191: "#d7ff5f", 192: "#d7ff87", 193: "#d7ffaf", 194: "#d7ffd7",
184	    \   195: "#d7ffff", 196: "#ff0000", 197: "#ff005f", 198: "#ff0087",
185	    \   199: "#ff00af", 200: "#ff00d7", 201: "#ff00ff", 202: "#ff5f00",
186	    \   203: "#ff5f5f", 204: "#ff5f87"
187	    \ })
188      call extend(s:cterm_color, {
189	    \   205: "#ff5faf", 206: "#ff5fd7", 207: "#ff5fff", 208: "#ff8700",
190	    \   209: "#ff875f", 210: "#ff8787", 211: "#ff87af", 212: "#ff87d7",
191	    \   213: "#ff87ff", 214: "#ffaf00", 215: "#ffaf5f", 216: "#ffaf87",
192	    \   217: "#ffafaf", 218: "#ffafd7", 219: "#ffafff", 220: "#ffd700",
193	    \   221: "#ffd75f", 222: "#ffd787", 223: "#ffd7af", 224: "#ffd7d7",
194	    \   225: "#ffd7ff", 226: "#ffff00", 227: "#ffff5f", 228: "#ffff87",
195	    \   229: "#ffffaf", 230: "#ffffd7", 231: "#ffffff", 232: "#080808",
196	    \   233: "#121212", 234: "#1c1c1c", 235: "#262626", 236: "#303030",
197	    \   237: "#3a3a3a", 238: "#444444", 239: "#4e4e4e", 240: "#585858",
198	    \   241: "#626262", 242: "#6c6c6c", 243: "#767676", 244: "#808080",
199	    \   245: "#8a8a8a", 246: "#949494", 247: "#9e9e9e", 248: "#a8a8a8",
200	    \   249: "#b2b2b2", 250: "#bcbcbc", 251: "#c6c6c6", 252: "#d0d0d0",
201	    \   253: "#dadada", 254: "#e4e4e4", 255: "#eeeeee"
202	    \ })
203    endif
204  endif
205endif
206
207" Return good color specification: in GUI no transformation is done, in
208" terminal return RGB values of known colors and empty string for unknown
209if s:whatterm == "gui"
210  function! s:HtmlColor(color)
211    return a:color
212  endfun
213else
214  function! s:HtmlColor(color)
215    if has_key(s:cterm_color, a:color)
216      return s:cterm_color[a:color]
217    else
218      return ""
219    endif
220  endfun
221endif
222
223" Find out the background and foreground color for use later
224let s:fgc = s:HtmlColor(synIDattr(hlID("Normal"), "fg#", s:whatterm))
225let s:bgc = s:HtmlColor(synIDattr(hlID("Normal"), "bg#", s:whatterm))
226if s:fgc == ""
227  let s:fgc = ( &background == "dark" ? "#ffffff" : "#000000" )
228endif
229if s:bgc == ""
230  let s:bgc = ( &background == "dark" ? "#000000" : "#ffffff" )
231endif
232
233if !s:settings.use_css
234  " Return opening HTML tag for given highlight id
235  function! s:HtmlOpening(id, extra_attrs)
236    let a = ""
237    if synIDattr(a:id, "inverse")
238      " For inverse, we always must set both colors (and exchange them)
239      let x = s:HtmlColor(synIDattr(a:id, "fg#", s:whatterm))
240      let a = a . '<span '.a:extra_attrs.'style="background-color: ' . ( x != "" ? x : s:fgc ) . '">'
241      let x = s:HtmlColor(synIDattr(a:id, "bg#", s:whatterm))
242      let a = a . '<font color="' . ( x != "" ? x : s:bgc ) . '">'
243    else
244      let x = s:HtmlColor(synIDattr(a:id, "bg#", s:whatterm))
245      if x != ""
246	let a = a . '<span '.a:extra_attrs.'style="background-color: ' . x . '">'
247      elseif !empty(a:extra_attrs)
248	let a = a . '<span '.a:extra_attrs.'>'
249      endif
250      let x = s:HtmlColor(synIDattr(a:id, "fg#", s:whatterm))
251      if x != "" | let a = a . '<font color="' . x . '">' | endif
252    endif
253    if synIDattr(a:id, "bold") | let a = a . "<b>" | endif
254    if synIDattr(a:id, "italic") | let a = a . "<i>" | endif
255    if synIDattr(a:id, "underline") | let a = a . "<u>" | endif
256    return a
257  endfun
258
259  " Return closing HTML tag for given highlight id
260  function! s:HtmlClosing(id, has_extra_attrs)
261    let a = ""
262    if synIDattr(a:id, "underline") | let a = a . "</u>" | endif
263    if synIDattr(a:id, "italic") | let a = a . "</i>" | endif
264    if synIDattr(a:id, "bold") | let a = a . "</b>" | endif
265    if synIDattr(a:id, "inverse")
266      let a = a . '</font></span>'
267    else
268      let x = s:HtmlColor(synIDattr(a:id, "fg#", s:whatterm))
269      if x != "" | let a = a . '</font>' | endif
270      let x = s:HtmlColor(synIDattr(a:id, "bg#", s:whatterm))
271      if x != "" || a:has_extra_attrs | let a = a . '</span>' | endif
272    endif
273    return a
274  endfun
275endif
276
277" Use a different function for formatting based on user options. This way we
278" can avoid a lot of logic during the actual execution.
279"
280" Build the function line by line containing only what is needed for the options
281" in use for maximum code sharing with minimal branch logic for greater speed.
282"
283" Note, 'exec' commands do not recognize line continuations, so must concatenate
284" lines rather than continue them.
285if s:settings.use_css
286  " save CSS to a list of rules to add to the output at the end of processing
287
288  " first, get the style names we need
289  let wrapperfunc_lines = [
290	\ 'function! s:BuildStyleWrapper(style_id, diff_style_id, extra_attrs, text, make_unselectable, unformatted)',
291	\ '',
292	\ '  let l:style_name = synIDattr(a:style_id, "name", s:whatterm)'
293	\ ]
294  if &diff
295    let wrapperfunc_lines += [
296	\ '  let l:diff_style_name = synIDattr(a:diff_style_id, "name", s:whatterm)']
297
298  " Add normal groups and diff groups to separate lists so we can order them to
299  " allow diff highlight to override normal highlight
300
301  " if primary style IS a diff style, grab it from the diff cache instead
302  " (always succeeds because we pre-populate it)
303  let wrapperfunc_lines += [
304	\ '',
305	\ '  if a:style_id == s:DIFF_D_ID || a:style_id == s:DIFF_A_ID ||'.
306	\ '          a:style_id == s:DIFF_C_ID || a:style_id == s:DIFF_T_ID',
307	\ '    let l:saved_style = get(s:diffstylelist,a:style_id)',
308	\ '  else'
309	\ ]
310  endif
311
312  " get primary style info from cache or build it on the fly if not found
313  let wrapperfunc_lines += [
314	\ '    let l:saved_style = get(s:stylelist,a:style_id)',
315	\ '    if type(l:saved_style) == type(0)',
316	\ '      unlet l:saved_style',
317	\ '      let l:saved_style = s:CSS1(a:style_id)',
318	\ '      if l:saved_style != ""',
319	\ '        let l:saved_style = "." . l:style_name . " { " . l:saved_style . "}"',
320	\ '      endif',
321	\ '      let s:stylelist[a:style_id]= l:saved_style',
322	\ '    endif'
323	\ ]
324  if &diff
325    let wrapperfunc_lines += [ '  endif' ]
326  endif
327
328  " Build the wrapper tags around the text. It turns out that caching these
329  " gives pretty much zero performance gain and adds a lot of logic.
330
331  let wrapperfunc_lines += [
332	\ '',
333	\ '  if l:saved_style == "" && empty(a:extra_attrs)'
334	\ ]
335  if &diff
336    let wrapperfunc_lines += [
337	\ '    if a:diff_style_id <= 0'
338	\ ]
339  endif
340  " no surroundings if neither primary nor diff style has any info
341  let wrapperfunc_lines += [
342	\ '       return a:text'
343	\ ]
344  if &diff
345    " no primary style, but diff style
346    let wrapperfunc_lines += [
347	\ '     else',
348	\ '       return "<span class=\"" .l:diff_style_name . "\">".a:text."</span>"',
349	\ '     endif'
350	\ ]
351  endif
352  " open tag for non-empty primary style
353  let wrapperfunc_lines += [
354	\ '  else']
355  " non-empty primary style. handle either empty or non-empty diff style.
356  "
357  " separate the two classes by a space to apply them both if there is a diff
358  " style name, unless the primary style is empty, then just use the diff style
359  " name
360  let diffstyle =
361	  \ (&diff ? '(a:diff_style_id <= 0 ? "" : " ". l:diff_style_name) .'
362	  \        : "")
363  if s:settings.prevent_copy == ""
364    let wrapperfunc_lines += [
365	  \ '    return "<span ".a:extra_attrs."class=\"" . l:style_name .'.diffstyle.'"\">".a:text."</span>"'
366	  \ ]
367  else
368
369    " New method: use generated content in the CSS. The only thing needed here
370    " is a span with no content, with an attribute holding the desired text.
371    "
372    " Old method: use an <input> element when text is unsectable. This is still
373    " used in conditional comments for Internet Explorer, where the new method
374    " doesn't work.
375    "
376    " Wrap the <input> in a <span> to allow fixing the stupid bug in some fonts
377    " which cause browsers to display a 1px gap between lines when these
378    " <input>s have a background color (maybe not really a bug, this isn't
379    " well-defined)
380    "
381    " use strwidth, because we care only about how many character boxes are
382    " needed to size the input, we don't care how many characters (including
383    " separately counted composing chars, from strchars()) or bytes (from
384    " len())the string contains. strdisplaywidth() is not needed because none of
385    " the unselectable groups can contain tab characters (fold column, fold
386    " text, line number).
387    "
388    " Note, if maxlength property needs to be added in the future, it will need
389    " to use strchars(), because HTML specifies that the maxlength parameter
390    " uses the number of unique codepoints for its limit.
391    let wrapperfunc_lines += [
392	  \   '    if a:make_unselectable',
393	  \   '      return "<span ".a:extra_attrs."class=\"" . l:style_name .'.diffstyle.'"\"'
394	  \ ]
395    if s:settings.use_input_for_pc !=# 'all'
396      let wrapperfunc_lines[-1] .= ' " . "data-" . l:style_name . "-content=\"".a:text."\"'
397    endif
398    let wrapperfunc_lines[-1] .= '>'
399    if s:settings.use_input_for_pc !=# 'none'
400      let wrapperfunc_lines[-1] .=
401	    \                '<input'.s:unselInputType.' class=\"" . l:style_name .'.diffstyle.'"\"'.
402	    \                 ' value=\"".substitute(a:unformatted,''\s\+$'',"","")."\"'.
403	    \                 ' onselect=''this.blur(); return false;'''.
404	    \                 ' onmousedown=''this.blur(); return false;'''.
405	    \                 ' onclick=''this.blur(); return false;'''.
406	    \                 ' readonly=''readonly'''.
407	    \                 ' size=\"".strwidth(a:unformatted)."\"'.
408	    \                 (s:settings.use_xhtml ? '/' : '').'>'
409    endif
410    let wrapperfunc_lines[-1] .= '</span>"'
411    let wrapperfunc_lines += [
412	  \ '    else',
413	  \ '      return "<span ".a:extra_attrs."class=\"" . l:style_name .'. diffstyle .'"\">".a:text."</span>"'
414	  \ ]
415  endif
416  let wrapperfunc_lines += [
417	\ '  endif',
418	\ 'endfun'
419	\ ]
420else
421  " Non-CSS method just needs the wrapper.
422  "
423  " Functions used to get opening/closing automatically return null strings if
424  " no styles exist.
425  if &diff
426    let wrapperfunc_lines = [
427	  \ 'function! s:BuildStyleWrapper(style_id, diff_style_id, extra_attrs, text, unusedarg, unusedarg2)',
428	  \ '  return s:HtmlOpening(a:style_id, a:extra_attrs).(a:diff_style_id <= 0 ? "" :'.
429	  \                                     's:HtmlOpening(a:diff_style_id, "")).a:text.'.
430	  \   '(a:diff_style_id <= 0 ? "" : s:HtmlClosing(a:diff_style_id, 0)).s:HtmlClosing(a:style_id, !empty(a:extra_attrs))',
431	  \ 'endfun'
432	  \ ]
433  else
434    let wrapperfunc_lines = [
435	  \ 'function! s:BuildStyleWrapper(style_id, diff_style_id, extra_attrs, text, unusedarg, unusedarg2)',
436	  \ '  return s:HtmlOpening(a:style_id, a:extra_attrs).a:text.s:HtmlClosing(a:style_id, !empty(a:extra_attrs))',
437	  \ 'endfun'
438	  \ ]
439  endif
440endif
441
442" create the function we built line by line above
443exec join(wrapperfunc_lines, "\n")
444
445let s:diff_mode = &diff
446
447" Return HTML valid characters enclosed in a span of class style_name with
448" unprintable characters expanded and double spaces replaced as necessary.
449"
450" TODO: eliminate unneeded logic like done for BuildStyleWrapper
451function! s:HtmlFormat(text, style_id, diff_style_id, extra_attrs, make_unselectable)
452  " Replace unprintable characters
453  let unformatted = strtrans(a:text)
454
455  let formatted = unformatted
456
457  " Replace the reserved html characters
458  let formatted = substitute(formatted, '&', '\&amp;',  'g')
459  let formatted = substitute(formatted, '<', '\&lt;',   'g')
460  let formatted = substitute(formatted, '>', '\&gt;',   'g')
461  let formatted = substitute(formatted, '"', '\&quot;', 'g')
462  " &apos; is not valid in HTML but it is in XHTML, so just use the numeric
463  " reference for it instead. Needed because it could appear in quotes
464  " especially if unselectable regions is turned on.
465  let formatted = substitute(formatted, '"', '\&#0039;', 'g')
466
467  " Replace a "form feed" character with HTML to do a page break
468  " TODO: need to prevent this in unselectable areas? Probably it should never
469  " BE in an unselectable area...
470  let formatted = substitute(formatted, "\x0c", '<hr class="PAGE-BREAK">', 'g')
471
472  " Replace double spaces, leading spaces, and trailing spaces if needed
473  if ' ' != s:HtmlSpace
474    let formatted = substitute(formatted, '  ', s:HtmlSpace . s:HtmlSpace, 'g')
475    let formatted = substitute(formatted, '^ ', s:HtmlSpace, 'g')
476    let formatted = substitute(formatted, ' \+$', s:HtmlSpace, 'g')
477  endif
478
479  " Enclose in the correct format
480  return s:BuildStyleWrapper(a:style_id, a:diff_style_id, a:extra_attrs, formatted, a:make_unselectable, unformatted)
481endfun
482
483" set up functions to call HtmlFormat in certain ways based on whether the
484" element is supposed to be unselectable or not
485if s:settings.prevent_copy =~# 'n'
486  if s:settings.number_lines
487    if s:settings.line_ids
488      function! s:HtmlFormat_n(text, style_id, diff_style_id, lnr)
489	if a:lnr > 0
490	  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)
491	else
492	  return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, "", 1)
493	endif
494      endfun
495    else
496      function! s:HtmlFormat_n(text, style_id, diff_style_id, lnr)
497	return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, "", 1)
498      endfun
499    endif
500  elseif s:settings.line_ids
501    " if lines are not being numbered the only reason this function gets called
502    " is to put the line IDs on each line; "text" will be empty but lnr will
503    " always be non-zero, however we don't want to use the <input> because that
504    " won't work as nice for empty text
505    function! s:HtmlFormat_n(text, style_id, diff_style_id, lnr)
506      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)
507    endfun
508  endif
509else
510  if s:settings.line_ids
511    function! s:HtmlFormat_n(text, style_id, diff_style_id, lnr)
512      if a:lnr > 0
513	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)
514      else
515	return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, "", 0)
516      endif
517    endfun
518  else
519    function! s:HtmlFormat_n(text, style_id, diff_style_id, lnr)
520      return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, "", 0)
521    endfun
522  endif
523endif
524if s:settings.prevent_copy =~# 'd'
525  function! s:HtmlFormat_d(text, style_id, diff_style_id)
526    return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, "", 1)
527  endfun
528else
529  function! s:HtmlFormat_d(text, style_id, diff_style_id)
530    return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, "", 0)
531  endfun
532endif
533if s:settings.prevent_copy =~# 'f'
534  if s:settings.use_input_for_pc ==# 'none'
535    " Simply space-pad to the desired width inside the generated content (note
536    " that the FoldColumn definition includes a whitespace:pre rule)
537    function! s:FoldColumn_build(char, len, numfill, char2, class, click)
538      return "<a href='#' class='".a:class."' onclick='".a:click."' data-FoldColumn-content='".
539	    \ repeat(a:char, a:len).a:char2.repeat(' ', a:numfill).
540	    \ "'></a>"
541    endfun
542    function! s:FoldColumn_fill()
543      return s:HtmlFormat(repeat(' ', s:foldcolumn), s:FOLD_C_ID, 0, "", 1)
544    endfun
545  else
546    " Note the <input> elements for fill spaces will have a single space for
547    " content, to allow active cursor CSS selection to work.
548    "
549    " Wrap the whole thing in a span for the 1px padding workaround for gaps.
550    "
551    " Build the function line by line containing only what is needed for the
552    " options in use for maximum code sharing with minimal branch logic for
553    " greater speed.
554    "
555    " Note, 'exec' commands do not recognize line continuations, so must
556    " concatenate lines rather than continue them.
557    let build_fun_lines = [
558	  \ 'function! s:FoldColumn_build(char, len, numfill, char2, class, click)',
559	  \ '  let l:input_open = "<input readonly=''readonly''".s:unselInputType.'.
560	  \ '          " onselect=''this.blur(); return false;''".'.
561	  \ '          " onmousedown=''this.blur(); ".a:click." return false;''".'.
562	  \ '          " onclick=''return false;'' size=''".'.
563	  \ '          string(a:len + (empty(a:char2) ? 0 : 1) + a:numfill) .'.
564	  \ '          "'' "',
565	  \ '  let l:common_attrs = "class=''FoldColumn'' value=''"',
566	  \ '  let l:input_close = (s:settings.use_xhtml ? "'' />" : "''>")'
567	  \ ]
568    if s:settings.use_input_for_pc ==# 'fallback'
569      let build_fun_lines += [
570	    \ '  let l:gen_content_link ='.
571	    \ '          "<a href=''#'' class=''FoldColumn'' onclick=''".a:click."'' data-FoldColumn-content=''".'.
572	    \ '          repeat(a:char, a:len).a:char2.repeat('' '', a:numfill).'.
573	    \ '          "''></a>"'
574	    \ ]
575    endif
576    let build_fun_lines += [
577	  \ '  return "<span class=''".a:class."''>".'.
578	  \ '          l:input_open.l:common_attrs.repeat(a:char, a:len).(a:char2).'.
579	  \ '          l:input_close.'.
580	  \ (s:settings.use_input_for_pc ==# 'fallback' ? 'l:gen_content_link.' : "").
581	  \ '          "</span>"',
582	  \ 'endfun'
583	  \ ]
584    " create the function we built line by line above
585    exec join(build_fun_lines, "\n")
586
587    function! s:FoldColumn_fill()
588      return s:FoldColumn_build(' ', s:foldcolumn, 0, '', 'FoldColumn', '')
589    endfun
590  endif
591else
592  " For normal fold columns, simply space-pad to the desired width (note that
593  " the FoldColumn definition includes a whitespace:pre rule)
594  function! s:FoldColumn_build(char, len, numfill, char2, class, click)
595    return "<a href='#' class='".a:class."' onclick='".a:click."'>".
596	  \ repeat(a:char, a:len).a:char2.repeat(' ', a:numfill).
597	  \ "</a>"
598  endfun
599  function! s:FoldColumn_fill()
600    return s:HtmlFormat(repeat(' ', s:foldcolumn), s:FOLD_C_ID, 0, "", 0)
601  endfun
602endif
603if s:settings.prevent_copy =~# 't'
604  " put an extra empty span at the end for dynamic folds, so the linebreak can
605  " be surrounded. Otherwise do it as normal.
606  "
607  " TODO: isn't there a better way to do this, than placing it here and using a
608  " substitute later?
609  if s:settings.dynamic_folds
610    function! s:HtmlFormat_t(text, style_id, diff_style_id)
611      return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, "", 1) .
612	    \ s:HtmlFormat("", a:style_id, 0, "", 0)
613    endfun
614  else
615    function! s:HtmlFormat_t(text, style_id, diff_style_id)
616      return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, "", 1)
617    endfun
618  endif
619else
620  function! s:HtmlFormat_t(text, style_id, diff_style_id)
621    return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, "", 0)
622  endfun
623endif
624
625" Return CSS style describing given highlight id (can be empty)
626function! s:CSS1(id)
627  let a = ""
628  if synIDattr(a:id, "inverse")
629    " For inverse, we always must set both colors (and exchange them)
630    let x = s:HtmlColor(synIDattr(a:id, "bg#", s:whatterm))
631    let a = a . "color: " . ( x != "" ? x : s:bgc ) . "; "
632    let x = s:HtmlColor(synIDattr(a:id, "fg#", s:whatterm))
633    let a = a . "background-color: " . ( x != "" ? x : s:fgc ) . "; "
634  else
635    let x = s:HtmlColor(synIDattr(a:id, "fg#", s:whatterm))
636    if x != "" | let a = a . "color: " . x . "; " | endif
637    let x = s:HtmlColor(synIDattr(a:id, "bg#", s:whatterm))
638    if x != ""
639      let a = a . "background-color: " . x . "; "
640      " stupid hack because almost every browser seems to have at least one font
641      " which shows 1px gaps between lines which have background
642      let a = a . "padding-bottom: 1px; "
643    elseif (a:id == s:FOLDED_ID || a:id == s:LINENR_ID || a:id == s:FOLD_C_ID) && !empty(s:settings.prevent_copy)
644      " input elements default to a different color than the rest of the page
645      let a = a . "background-color: " . s:bgc . "; "
646    endif
647  endif
648  if synIDattr(a:id, "bold") | let a = a . "font-weight: bold; " | endif
649  if synIDattr(a:id, "italic") | let a = a . "font-style: italic; " | endif
650  if synIDattr(a:id, "underline") | let a = a . "text-decoration: underline; " | endif
651  return a
652endfun
653
654if s:settings.dynamic_folds
655  " compares two folds as stored in our list of folds
656  " A fold is "less" than another if it starts at an earlier line number,
657  " or ends at a later line number, ties broken by fold level
658  function! s:FoldCompare(f1, f2)
659    if a:f1.firstline != a:f2.firstline
660      " put it before if it starts earlier
661      return a:f1.firstline - a:f2.firstline
662    elseif a:f1.lastline != a:f2.lastline
663      " put it before if it ends later
664      return a:f2.lastline - a:f1.lastline
665    else
666      " if folds begin and end on the same lines, put lowest fold level first
667      return a:f1.level - a:f2.level
668    endif
669  endfunction
670
671endif
672
673
674" Set some options to make it work faster.
675" Don't report changes for :substitute, there will be many of them.
676" Don't change other windows; turn off scroll bind temporarily
677let s:old_title = &title
678let s:old_icon = &icon
679let s:old_et = &l:et
680let s:old_bind = &l:scrollbind
681let s:old_report = &report
682let s:old_search = @/
683let s:old_more = &more
684set notitle noicon
685setlocal et
686set nomore
687set report=1000000
688setlocal noscrollbind
689
690if exists(':ownsyntax') && exists('w:current_syntax')
691  let s:current_syntax = w:current_syntax
692elseif exists('b:current_syntax')
693  let s:current_syntax = b:current_syntax
694else
695  let s:current_syntax = 'none'
696endif
697
698if s:current_syntax == ''
699  let s:current_syntax = 'none'
700endif
701
702" If the user is sourcing this script directly then the plugin version isn't
703" known because the main plugin script didn't load. In the usual case where the
704" user still has the full Vim runtime installed, or has this full plugin
705" installed in a package or something, then we can extract the version from the
706" main plugin file at it's usual spot relative to this file. Otherwise the user
707" is assembling their runtime piecemeal and we have no idea what versions of
708" other files may be present so don't even try to make a guess or assume the
709" presence of other specific files with specific meaning.
710"
711" We don't want to actually source the main plugin file here because the user
712" may have a good reason not to (e.g. they define their own TOhtml command or
713" something).
714"
715" If this seems way too complicated and convoluted, it is. Probably I should
716" have put the version information in the autoload file from the start. But the
717" version has been in the global variable for so long that changing it could
718" break a lot of user scripts.
719if exists("g:loaded_2html_plugin")
720  let s:pluginversion = g:loaded_2html_plugin
721else
722  if !exists("g:unloaded_tohtml_plugin")
723    let s:main_plugin_path = expand("<sfile>:p:h:h")."/plugin/tohtml.vim"
724    if filereadable(s:main_plugin_path)
725      let s:lines = readfile(s:main_plugin_path, "", 20)
726      call filter(s:lines, 'v:val =~ "loaded_2html_plugin = "')
727      if empty(s:lines)
728	let g:unloaded_tohtml_plugin = "unknown"
729      else
730	let g:unloaded_tohtml_plugin = substitute(s:lines[0], '.*loaded_2html_plugin = \([''"]\)\(\%(\1\@!.\)\+\)\1', '\2', '')
731      endif
732      unlet s:lines
733    else
734      let g:unloaded_tohtml_plugin = "unknown"
735    endif
736    unlet s:main_plugin_path
737  endif
738  let s:pluginversion = g:unloaded_tohtml_plugin
739endif
740
741" Split window to create a buffer with the HTML file.
742let s:orgbufnr = winbufnr(0)
743let s:origwin_stl = &l:stl
744if expand("%") == ""
745  if exists('g:html_diff_win_num')
746    exec 'new Untitled_win'.g:html_diff_win_num.'.'.(s:settings.use_xhtml ? 'x' : '').'html'
747  else
748    exec 'new Untitled.'.(s:settings.use_xhtml ? 'x' : '').'html'
749  endif
750else
751  exec 'new %.'.(s:settings.use_xhtml ? 'x' : '').'html'
752endif
753
754" Resize the new window to very small in order to make it draw faster
755let s:old_winheight = winheight(0)
756let s:old_winfixheight = &l:winfixheight
757if s:old_winheight > 2
758  resize 1 " leave enough room to view one line at a time
759  norm! G
760  norm! zt
761endif
762setlocal winfixheight
763
764let s:newwin_stl = &l:stl
765
766" on the new window, set the least time-consuming fold method
767let s:old_fen = &foldenable
768setlocal foldmethod=manual
769setlocal nofoldenable
770
771let s:newwin = winnr()
772let s:orgwin = bufwinnr(s:orgbufnr)
773
774setlocal modifiable
775%d
776let s:old_paste = &paste
777set paste
778let s:old_magic = &magic
779set magic
780
781" set the fileencoding to match the charset we'll be using
782let &l:fileencoding=s:settings.vim_encoding
783
784" According to http://www.w3.org/TR/html4/charset.html#doc-char-set, the byte
785" order mark is highly recommend on the web when using multibyte encodings. But,
786" it is not a good idea to include it on UTF-8 files. Otherwise, let Vim
787" determine when it is actually inserted.
788if s:settings.vim_encoding == 'utf-8'
789  setlocal nobomb
790else
791  setlocal bomb
792endif
793
794let s:lines = []
795
796if s:settings.use_xhtml
797  if s:settings.encoding != ""
798    call add(s:lines, "<?xml version=\"1.0\" encoding=\"" . s:settings.encoding . "\"?>")
799  else
800    call add(s:lines, "<?xml version=\"1.0\"?>")
801  endif
802  let s:tag_close = ' />'
803else
804  let s:tag_close = '>'
805endif
806
807let s:HtmlSpace = ' '
808let s:LeadingSpace = ' '
809let s:HtmlEndline = ''
810if s:settings.no_pre
811  let s:HtmlEndline = '<br' . s:tag_close
812  let s:LeadingSpace = s:settings.use_xhtml ? '&#160;' : '&nbsp;'
813  let s:HtmlSpace = '\' . s:LeadingSpace
814endif
815
816" HTML header, with the title and generator ;-). Left free space for the CSS,
817" to be filled at the end.
818call extend(s:lines, [
819      \ "<html>",
820      \ "<head>"])
821" include encoding as close to the top as possible, but only if not already
822" contained in XML information (to avoid haggling over content type)
823if s:settings.encoding != "" && !s:settings.use_xhtml
824  if s:html5
825    call add(s:lines, '<meta charset="' . s:settings.encoding . '"' . s:tag_close)
826  else
827    call add(s:lines, "<meta http-equiv=\"content-type\" content=\"text/html; charset=" . s:settings.encoding . '"' . s:tag_close)
828  endif
829endif
830call extend(s:lines, [
831      \ ("<title>".expand("%:p:~")."</title>"),
832      \ ("<meta name=\"Generator\" content=\"Vim/".v:version/100.".".v:version%100.'"'.s:tag_close),
833      \ ("<meta name=\"plugin-version\" content=\"".s:pluginversion.'"'.s:tag_close)
834      \ ])
835call add(s:lines, '<meta name="syntax" content="'.s:current_syntax.'"'.s:tag_close)
836call add(s:lines, '<meta name="settings" content="'.
837      \ join(filter(keys(s:settings),'s:settings[v:val]'),',').
838      \ ',prevent_copy='.s:settings.prevent_copy.
839      \ ',use_input_for_pc='.s:settings.use_input_for_pc.
840      \ '"'.s:tag_close)
841call add(s:lines, '<meta name="colorscheme" content="'.
842      \ (exists('g:colors_name')
843      \ ? g:colors_name
844      \ : 'none'). '"'.s:tag_close)
845
846if s:settings.use_css
847  call extend(s:lines, [
848	\ "<style" . (s:html5 ? "" : " type=\"text/css\"") . ">",
849	\ s:settings.use_xhtml ? "" : "<!--"])
850  let s:ieonly = []
851  if s:settings.dynamic_folds
852    if s:settings.hover_unfold
853      " if we are doing hover_unfold, use css 2 with css 1 fallback for IE6
854      call extend(s:lines, [
855	    \ ".FoldColumn { text-decoration: none; white-space: pre; }",
856	    \ "",
857	    \ "body * { margin: 0; padding: 0; }", "",
858	    \ ".open-fold   > span.Folded { display: none;  }",
859	    \ ".open-fold   > .fulltext   { display: inline; }",
860	    \ ".closed-fold > .fulltext   { display: none;  }",
861	    \ ".closed-fold > span.Folded { display: inline; }",
862	    \ "",
863	    \ ".open-fold   > .toggle-open   { display: none;   }",
864	    \ ".open-fold   > .toggle-closed { display: inline; }",
865	    \ ".closed-fold > .toggle-open   { display: inline; }",
866	    \ ".closed-fold > .toggle-closed { display: none;   }",
867	    \ "", "",
868	    \ '/* opening a fold while hovering won''t be supported by IE6 and other',
869	    \ "similar browsers, but it should fail gracefully. */",
870	    \ ".closed-fold:hover > .fulltext      { display: inline; }",
871	    \ ".closed-fold:hover > .toggle-filler { display: none; }",
872	    \ ".closed-fold:hover > .Folded        { display: none; }"])
873      " TODO: IE6 is REALLY old and I can't even test it anymore. Maybe we
874      " should remove this? Leave it in for now, it was working at one point,
875      " and doesn't affect any modern browsers. Even newer IE versions should
876      " support the above code and ignore the following.
877      let s:ieonly = [
878	    \ "<!--[if lt IE 7]><style type=\"text/css\">",
879	    \ ".open-fold   .fulltext      { display: inline; }",
880	    \ ".open-fold   span.Folded    { display: none; }",
881	    \ ".open-fold   .toggle-open   { display: none; }",
882	    \ ".open-fold   .toggle-closed { display: inline; }",
883	    \ "",
884	    \ ".closed-fold .fulltext      { display: none; }",
885	    \ ".closed-fold span.Folded    { display: inline; }",
886	    \ ".closed-fold .toggle-open   { display: inline; }",
887	    \ ".closed-fold .toggle-closed { display: none; }",
888	    \ "</style>",
889	    \ "<![endif]-->",
890	    \]
891    else
892      " if we aren't doing hover_unfold, use CSS 1 only
893      call extend(s:lines, [
894	    \ ".FoldColumn { text-decoration: none; white-space: pre; }",
895	    \ ".open-fold   .fulltext      { display: inline; }",
896	    \ ".open-fold   span.Folded    { display: none; }",
897	    \ ".open-fold   .toggle-open   { display: none; }",
898	    \ ".open-fold   .toggle-closed { display: inline; }",
899	    \ "",
900	    \ ".closed-fold .fulltext      { display: none; }",
901	    \ ".closed-fold span.Folded    { display: inline; }",
902	    \ ".closed-fold .toggle-open   { display: inline; }",
903	    \ ".closed-fold .toggle-closed { display: none; }",
904	    \])
905    endif
906  endif
907  " else we aren't doing any dynamic folding, no need for any special rules
908
909  call extend(s:lines, [
910	  \ s:settings.use_xhtml ? "" : '-->',
911	  \ "</style>",
912	  \])
913  call extend(s:lines, s:ieonly)
914  unlet s:ieonly
915endif
916
917let s:uses_script = s:settings.dynamic_folds || s:settings.line_ids
918
919" insert script tag if needed
920if s:uses_script
921  call extend(s:lines, [
922        \ "",
923        \ "<script" . (s:html5 ? "" : " type='text/javascript'") . ">",
924        \ s:settings.use_xhtml ? '//<![CDATA[' : "<!--"])
925endif
926
927" insert javascript to toggle folds open and closed
928if s:settings.dynamic_folds
929  call extend(s:lines, [
930	\ "",
931	\ "function toggleFold(objID)",
932	\ "{",
933	\ "  var fold;",
934	\ "  fold = document.getElementById(objID);",
935	\ "  if (fold.className == 'closed-fold')",
936	\ "  {",
937	\ "    fold.className = 'open-fold';",
938	\ "  }",
939	\ "  else if (fold.className == 'open-fold')",
940	\ "  {",
941	\ "    fold.className = 'closed-fold';",
942	\ "  }",
943	\ "}"
944	\ ])
945endif
946
947if s:settings.line_ids
948  " insert javascript to get IDs from line numbers, and to open a fold before
949  " jumping to any lines contained therein
950  call extend(s:lines, [
951	\ "",
952	\ "/* function to open any folds containing a jumped-to line before jumping to it */",
953	\ "function JumpToLine()",
954	\ "{",
955	\ "  var lineNum;",
956	\ "  lineNum = window.location.hash;",
957	\ "  lineNum = lineNum.substr(1); /* strip off '#' */",
958	\ "",
959	\ "  if (lineNum.indexOf('L') == -1) {",
960	\ "    lineNum = 'L'+lineNum;",
961	\ "  }",
962	\ "  var lineElem = document.getElementById(lineNum);"
963	\ ])
964
965  if s:settings.dynamic_folds
966    call extend(s:lines, [
967	  \ "",
968	  \ "  /* navigate upwards in the DOM tree to open all folds containing the line */",
969	  \ "  var node = lineElem;",
970	  \ "  while (node && node.id != 'vimCodeElement".s:settings.id_suffix."')",
971	  \ "  {",
972	  \ "    if (node.className == 'closed-fold')",
973	  \ "    {",
974	  \ "      node.className = 'open-fold';",
975	  \ "    }",
976	  \ "    node = node.parentNode;",
977	  \ "  }",
978	  \ ])
979  endif
980  call extend(s:lines, [
981	\ "  /* Always jump to new location even if the line was hidden inside a fold, or",
982	\ "   * we corrected the raw number to a line ID.",
983	\ "   */",
984	\ "  if (lineElem) {",
985	\ "    lineElem.scrollIntoView(true);",
986	\ "  }",
987	\ "  return true;",
988	\ "}",
989	\ "if ('onhashchange' in window) {",
990	\ "  window.onhashchange = JumpToLine;",
991	\ "}"
992	\ ])
993endif
994
995" insert script closing tag if needed
996if s:uses_script
997  call extend(s:lines, [
998        \ '',
999        \ s:settings.use_xhtml ? '//]]>' : '-->',
1000        \ "</script>"
1001        \ ])
1002endif
1003
1004call extend(s:lines, ["</head>",
1005      \ "<body".(s:settings.line_ids ? " onload='JumpToLine();'" : "").">"])
1006
1007if s:settings.no_pre
1008  " if we're not using CSS we use a font tag which can't have a div inside
1009  if s:settings.use_css
1010    call extend(s:lines, ["<div id='vimCodeElement".s:settings.id_suffix."'>"])
1011  endif
1012else
1013  call extend(s:lines, ["<pre id='vimCodeElement".s:settings.id_suffix."'>"])
1014endif
1015
1016exe s:orgwin . "wincmd w"
1017
1018" caches of style data
1019" initialize to include line numbers if using them
1020if s:settings.number_lines
1021  let s:stylelist = { s:LINENR_ID : ".LineNr { " . s:CSS1( s:LINENR_ID ) . "}" }
1022else
1023  let s:stylelist = {}
1024endif
1025let s:diffstylelist = {
1026      \   s:DIFF_A_ID : ".DiffAdd { " . s:CSS1( s:DIFF_A_ID ) . "}",
1027      \   s:DIFF_C_ID : ".DiffChange { " . s:CSS1( s:DIFF_C_ID ) . "}",
1028      \   s:DIFF_D_ID : ".DiffDelete { " . s:CSS1( s:DIFF_D_ID ) . "}",
1029      \   s:DIFF_T_ID : ".DiffText { " . s:CSS1( s:DIFF_T_ID ) . "}"
1030      \ }
1031
1032" set up progress bar in the status line
1033if !s:settings.no_progress
1034  " ProgressBar Indicator
1035  let s:progressbar={}
1036
1037  " Progressbar specific functions
1038
1039  func! s:SetProgbarColor()
1040    if hlID("TOhtmlProgress") != 0
1041      hi! link TOhtmlProgress_auto TOhtmlProgress
1042    elseif hlID("TOhtmlProgress_auto")==0 ||
1043       \ !exists("s:last_colors_name") || !exists("g:colors_name") ||
1044       \ g:colors_name != s:last_colors_name
1045      let s:last_colors_name = exists("g:colors_name") ? g:colors_name : "none"
1046
1047      let l:diffatr = synIDattr(hlID("DiffDelete"), "reverse", s:whatterm) ? "fg#" : "bg#"
1048      let l:stlatr = synIDattr(hlID("StatusLine"), "reverse", s:whatterm) ? "fg#" : "bg#"
1049
1050      let l:progbar_color = synIDattr(hlID("DiffDelete"), l:diffatr, s:whatterm)
1051      let l:stl_color = synIDattr(hlID("StatusLine"), l:stlatr, s:whatterm)
1052
1053      if "" == l:progbar_color
1054	let l:progbar_color = synIDattr(hlID("DiffDelete"), "reverse", s:whatterm) ? s:fgc : s:bgc
1055      endif
1056      if "" == l:stl_color
1057	let l:stl_color = synIDattr(hlID("StatusLine"), "reverse", s:whatterm) ? s:fgc : s:bgc
1058      endif
1059
1060      if l:progbar_color == l:stl_color
1061	if s:whatterm == 'cterm'
1062	  if l:progbar_color >= (&t_Co/2)
1063	    let l:progbar_color-=1
1064	  else
1065	    let l:progbar_color+=1
1066	  endif
1067	else
1068	  let l:rgb = map(matchlist(l:progbar_color, '#\zs\x\x\ze\(\x\x\)\(\x\x\)')[:2], 'str2nr(v:val, 16)')
1069	  let l:avg = (l:rgb[0] + l:rgb[1] + l:rgb[2])/3
1070	  if l:avg >= 128
1071	    let l:avg_new = l:avg
1072	    while l:avg - l:avg_new < 0x15
1073	      let l:rgb = map(l:rgb, 'v:val * 3 / 4')
1074	      let l:avg_new = (l:rgb[0] + l:rgb[1] + l:rgb[2])/3
1075	    endwhile
1076	  else
1077	    let l:avg_new = l:avg
1078	    while l:avg_new - l:avg < 0x15
1079	      let l:rgb = map(l:rgb, 'min([max([v:val, 4]) * 5 / 4, 255])')
1080	      let l:avg_new = (l:rgb[0] + l:rgb[1] + l:rgb[2])/3
1081	    endwhile
1082	  endif
1083	  let l:progbar_color = printf("#%02x%02x%02x", l:rgb[0], l:rgb[1], l:rgb[2])
1084	endif
1085	echomsg "diff detected progbar color set to" l:progbar_color
1086      endif
1087      exe "hi TOhtmlProgress_auto" s:whatterm."bg=".l:progbar_color
1088    endif
1089  endfun
1090
1091  func! s:ProgressBar(title, max_value, winnr)
1092    let pgb=copy(s:progressbar)
1093    let pgb.title = a:title.' '
1094    let pgb.max_value = a:max_value
1095    let pgb.winnr = a:winnr
1096    let pgb.cur_value = 0
1097
1098    let pgb.items = { 'title'   : { 'color' : 'Statusline' },
1099	  \'bar'     : { 'color' : 'Statusline' , 'fillcolor' : 'TOhtmlProgress_auto' , 'bg' : 'Statusline' } ,
1100	  \'counter' : { 'color' : 'Statusline' } }
1101    let pgb.last_value = 0
1102    let pgb.needs_redraw = 0
1103    " Note that you must use len(split) instead of len() if you want to use
1104    " unicode in title.
1105    "
1106    " Subtract 3 for spacing around the title.
1107    " Subtract 4 for the percentage display.
1108    " Subtract 2 for spacing before this.
1109    " Subtract 2 more for the '|' on either side of the progress bar
1110    let pgb.subtractedlen=len(split(pgb.title, '\zs'))+3+4+2+2
1111    let pgb.max_len = 0
1112    set laststatus=2
1113    return pgb
1114  endfun
1115
1116  " Function: progressbar.calculate_ticks() {{{1
1117  func! s:progressbar.calculate_ticks(pb_len)
1118    if a:pb_len<=0
1119      let pb_len = 100
1120    else
1121      let pb_len = a:pb_len
1122    endif
1123    let self.progress_ticks = map(range(pb_len+1), "v:val * self.max_value / pb_len")
1124  endfun
1125
1126  "Function: progressbar.paint()
1127  func! s:progressbar.paint()
1128    " Recalculate widths.
1129    let max_len = winwidth(self.winnr)
1130    let pb_len = 0
1131    " always true on first call because of initial value of self.max_len
1132    if max_len != self.max_len
1133      let self.max_len = max_len
1134
1135      " Progressbar length
1136      let pb_len = max_len - self.subtractedlen
1137
1138      call self.calculate_ticks(pb_len)
1139
1140      let self.needs_redraw = 1
1141      let cur_value = 0
1142      let self.pb_len = pb_len
1143    else
1144      " start searching at the last found index to make the search for the
1145      " appropriate tick value normally take 0 or 1 comparisons
1146      let cur_value = self.last_value
1147      let pb_len = self.pb_len
1148    endif
1149
1150    let cur_val_max = pb_len > 0 ? pb_len : 100
1151
1152    " find the current progress bar position based on precalculated thresholds
1153    while cur_value < cur_val_max && self.cur_value > self.progress_ticks[cur_value]
1154      let cur_value += 1
1155    endwhile
1156
1157    " update progress bar
1158    if self.last_value != cur_value || self.needs_redraw || self.cur_value == self.max_value
1159      let self.needs_redraw = 1
1160      let self.last_value = cur_value
1161
1162      let t_color  = self.items.title.color
1163      let b_fcolor = self.items.bar.fillcolor
1164      let b_color  = self.items.bar.color
1165      let c_color  = self.items.counter.color
1166
1167      let stl =  "%#".t_color."#%-( ".self.title." %)".
1168	    \"%#".b_color."#".
1169	    \(pb_len>0 ?
1170	    \	('|%#'.b_fcolor."#%-(".repeat(" ",cur_value)."%)".
1171	    \	 '%#'.b_color."#".repeat(" ",pb_len-cur_value)."|"):
1172	    \	('')).
1173	    \"%=%#".c_color."#%( ".printf("%3.d ",100*self.cur_value/self.max_value)."%% %)"
1174      call setwinvar(self.winnr, '&stl', stl)
1175    endif
1176  endfun
1177
1178  func! s:progressbar.incr( ... )
1179    let self.cur_value += (a:0 ? a:1 : 1)
1180    " if we were making a general-purpose progress bar, we'd need to limit to a
1181    " lower limit as well, but since we always increment with a positive value
1182    " in this script, we only need limit the upper value
1183    let self.cur_value = (self.cur_value > self.max_value ? self.max_value : self.cur_value)
1184    call self.paint()
1185  endfun
1186  " }}}
1187  if s:settings.dynamic_folds
1188    " to process folds we make two passes through each line
1189    let s:pgb = s:ProgressBar("Processing folds:", line('$')*2, s:orgwin)
1190  endif
1191
1192  call s:SetProgbarColor()
1193endif
1194
1195" First do some preprocessing for dynamic folding. Do this for the entire file
1196" so we don't accidentally start within a closed fold or something.
1197let s:allfolds = []
1198
1199if s:settings.dynamic_folds
1200  let s:lnum = 1
1201  let s:end = line('$')
1202  " save the fold text and set it to the default so we can find fold levels
1203  let s:foldtext_save = &foldtext
1204  setlocal foldtext&
1205
1206  " we will set the foldcolumn in the html to the greater of the maximum fold
1207  " level and the current foldcolumn setting
1208  let s:foldcolumn = &foldcolumn
1209
1210  " get all info needed to describe currently closed folds
1211  while s:lnum <= s:end
1212    if foldclosed(s:lnum) == s:lnum
1213      " default fold text has '+-' and then a number of dashes equal to fold
1214      " level, so subtract 2 from index of first non-dash after the dashes
1215      " in order to get the fold level of the current fold
1216      let s:level = match(foldtextresult(s:lnum), '+-*\zs[^-]') - 2
1217      " store fold info for later use
1218      let s:newfold = {'firstline': s:lnum, 'lastline': foldclosedend(s:lnum), 'level': s:level,'type': "closed-fold"}
1219      call add(s:allfolds, s:newfold)
1220      " open the fold so we can find any contained folds
1221      execute s:lnum."foldopen"
1222    else
1223      if !s:settings.no_progress
1224	call s:pgb.incr()
1225	if s:pgb.needs_redraw
1226	  redrawstatus
1227	  let s:pgb.needs_redraw = 0
1228	endif
1229      endif
1230      let s:lnum = s:lnum + 1
1231    endif
1232  endwhile
1233
1234  " close all folds to get info for originally open folds
1235  silent! %foldclose!
1236  let s:lnum = 1
1237
1238  " the originally open folds will be all folds we encounter that aren't
1239  " already in the list of closed folds
1240  while s:lnum <= s:end
1241    if foldclosed(s:lnum) == s:lnum
1242      " default fold text has '+-' and then a number of dashes equal to fold
1243      " level, so subtract 2 from index of first non-dash after the dashes
1244      " in order to get the fold level of the current fold
1245      let s:level = match(foldtextresult(s:lnum), '+-*\zs[^-]') - 2
1246      let s:newfold = {'firstline': s:lnum, 'lastline': foldclosedend(s:lnum), 'level': s:level,'type': "closed-fold"}
1247      " only add the fold if we don't already have it
1248      if empty(s:allfolds) || index(s:allfolds, s:newfold) == -1
1249	let s:newfold.type = "open-fold"
1250	call add(s:allfolds, s:newfold)
1251      endif
1252      " open the fold so we can find any contained folds
1253      execute s:lnum."foldopen"
1254    else
1255      if !s:settings.no_progress
1256	call s:pgb.incr()
1257	if s:pgb.needs_redraw
1258	  redrawstatus
1259	  let s:pgb.needs_redraw = 0
1260	endif
1261      endif
1262      let s:lnum = s:lnum + 1
1263    endif
1264  endwhile
1265
1266  " sort the folds so that we only ever need to look at the first item in the
1267  " list of folds
1268  call sort(s:allfolds, "s:FoldCompare")
1269
1270  let &l:foldtext = s:foldtext_save
1271  unlet s:foldtext_save
1272
1273  " close all folds again so we can get the fold text as we go
1274  silent! %foldclose!
1275
1276  " Go through and remove folds we don't need to (or cannot) process in the
1277  " current conversion range
1278  "
1279  " If a fold is removed which contains other folds, which are included, we need
1280  " to adjust the level of the included folds as used by the conversion logic
1281  " (avoiding special cases is good)
1282  "
1283  " Note any time we remove a fold, either all of the included folds are in it,
1284  " or none of them, because we only remove a fold if neither its start nor its
1285  " end are within the conversion range.
1286  let leveladjust = 0
1287  for afold in s:allfolds
1288    let removed = 0
1289    if exists("g:html_start_line") && exists("g:html_end_line")
1290      if afold.firstline < g:html_start_line
1291	if afold.lastline <= g:html_end_line && afold.lastline >= g:html_start_line
1292	  " if a fold starts before the range to convert but stops within the
1293	  " range, we need to include it. Make it start on the first converted
1294	  " line.
1295	  let afold.firstline = g:html_start_line
1296	else
1297	  " if the fold lies outside the range or the start and stop enclose
1298	  " the entire range, don't bother parsing it
1299	  call remove(s:allfolds, index(s:allfolds, afold))
1300	  let removed = 1
1301	  if afold.lastline > g:html_end_line
1302	    let leveladjust += 1
1303	  endif
1304	endif
1305      elseif afold.firstline > g:html_end_line
1306	" If the entire fold lies outside the range we need to remove it.
1307	call remove(s:allfolds, index(s:allfolds, afold))
1308	let removed = 1
1309      endif
1310    elseif exists("g:html_start_line")
1311      if afold.firstline < g:html_start_line
1312	" if there is no last line, but there is a first line, the end of the
1313	" fold will always lie within the region of interest, so keep it
1314	let afold.firstline = g:html_start_line
1315      endif
1316    elseif exists("g:html_end_line")
1317      " if there is no first line we default to the first line in the buffer so
1318      " the fold start will always be included if the fold itself is included.
1319      " If however the entire fold lies outside the range we need to remove it.
1320      if afold.firstline > g:html_end_line
1321	call remove(s:allfolds, index(s:allfolds, afold))
1322	let removed = 1
1323      endif
1324    endif
1325    if !removed
1326      let afold.level -= leveladjust
1327      if afold.level+1 > s:foldcolumn
1328	let s:foldcolumn = afold.level+1
1329      endif
1330    endif
1331  endfor
1332
1333  " if we've removed folds containing the conversion range from processing,
1334  " getting foldtext as we go won't know to open the removed folds, so the
1335  " foldtext would be wrong; open them now.
1336  "
1337  " Note that only when a start and an end line is specified will a fold
1338  " containing the current range ever be removed.
1339  while leveladjust > 0
1340    exe g:html_start_line."foldopen"
1341    let leveladjust -= 1
1342  endwhile
1343endif
1344
1345" Now loop over all lines in the original text to convert to html.
1346" Use html_start_line and html_end_line if they are set.
1347if exists("g:html_start_line")
1348  let s:lnum = html_start_line
1349  if s:lnum < 1 || s:lnum > line("$")
1350    let s:lnum = 1
1351  endif
1352else
1353  let s:lnum = 1
1354endif
1355if exists("g:html_end_line")
1356  let s:end = html_end_line
1357  if s:end < s:lnum || s:end > line("$")
1358    let s:end = line("$")
1359  endif
1360else
1361  let s:end = line("$")
1362endif
1363
1364" stack to keep track of all the folds containing the current line
1365let s:foldstack = []
1366
1367if !s:settings.no_progress
1368  let s:pgb = s:ProgressBar("Processing lines:", s:end - s:lnum + 1, s:orgwin)
1369endif
1370
1371if s:settings.number_lines
1372  let s:margin = strlen(s:end) + 1
1373else
1374  let s:margin = 0
1375endif
1376
1377if has('folding') && !s:settings.ignore_folding
1378  let s:foldfillchar = &fillchars[matchend(&fillchars, 'fold:')]
1379  if s:foldfillchar == ''
1380    let s:foldfillchar = '-'
1381  endif
1382endif
1383let s:difffillchar = &fillchars[matchend(&fillchars, 'diff:')]
1384if s:difffillchar == ''
1385  let s:difffillchar = '-'
1386endif
1387
1388let s:foldId = 0
1389
1390if !s:settings.expand_tabs
1391  " If keeping tabs, add them to printable characters so we keep them when
1392  " formatting text (strtrans() doesn't replace printable chars)
1393  let s:old_isprint = &isprint
1394  setlocal isprint+=9
1395endif
1396
1397while s:lnum <= s:end
1398
1399  " If there are filler lines for diff mode, show these above the line.
1400  let s:filler = diff_filler(s:lnum)
1401  if s:filler > 0
1402    let s:n = s:filler
1403    while s:n > 0
1404      let s:new = repeat(s:difffillchar, 3)
1405
1406      if s:n > 2 && s:n < s:filler && !s:settings.whole_filler
1407	let s:new = s:new . " " . s:filler . " inserted lines "
1408	let s:n = 2
1409      endif
1410
1411      if !s:settings.no_pre
1412	" HTML line wrapping is off--go ahead and fill to the margin
1413	" TODO: what about when CSS wrapping is turned on?
1414	let s:new = s:new . repeat(s:difffillchar, &columns - strlen(s:new) - s:margin)
1415      else
1416	let s:new = s:new . repeat(s:difffillchar, 3)
1417      endif
1418
1419      let s:new = s:HtmlFormat_d(s:new, s:DIFF_D_ID, 0)
1420      if s:settings.number_lines
1421	" Indent if line numbering is on. Indent gets style of line number
1422	" column.
1423	let s:new = s:HtmlFormat_n(repeat(' ', s:margin), s:LINENR_ID, 0, 0) . s:new
1424      endif
1425      if s:settings.dynamic_folds && !s:settings.no_foldcolumn && s:foldcolumn > 0
1426	" Indent for foldcolumn if there is one. Assume it's empty, there should
1427	" not be a fold for deleted lines in diff mode.
1428	let s:new = s:FoldColumn_fill() . s:new
1429      endif
1430      call add(s:lines, s:new.s:HtmlEndline)
1431
1432      let s:n = s:n - 1
1433    endwhile
1434    unlet s:n
1435  endif
1436  unlet s:filler
1437
1438  " Start the line with the line number.
1439  if s:settings.number_lines
1440    let s:numcol = repeat(' ', s:margin - 1 - strlen(s:lnum)) . s:lnum . ' '
1441  endif
1442
1443  let s:new = ""
1444
1445  if has('folding') && !s:settings.ignore_folding && foldclosed(s:lnum) > -1 && !s:settings.dynamic_folds
1446    "
1447    " This is the beginning of a folded block (with no dynamic folding)
1448    let s:new = foldtextresult(s:lnum)
1449    if !s:settings.no_pre
1450      " HTML line wrapping is off--go ahead and fill to the margin
1451      let s:new = s:new . repeat(s:foldfillchar, &columns - strlen(s:new))
1452    endif
1453
1454    " put numcol in a separate group for sake of unselectable text
1455    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)
1456
1457    " Skip to the end of the fold
1458    let s:new_lnum = foldclosedend(s:lnum)
1459
1460    if !s:settings.no_progress
1461      call s:pgb.incr(s:new_lnum - s:lnum)
1462    endif
1463
1464    let s:lnum = s:new_lnum
1465
1466  else
1467    "
1468    " A line that is not folded, or doing dynamic folding.
1469    "
1470    let s:line = getline(s:lnum)
1471    let s:len = strlen(s:line)
1472
1473    if s:settings.dynamic_folds
1474      " First insert a closing for any open folds that end on this line
1475      while !empty(s:foldstack) && get(s:foldstack,0).lastline == s:lnum-1
1476	let s:new = s:new."</span></span>"
1477	call remove(s:foldstack, 0)
1478      endwhile
1479
1480      " Now insert an opening for any new folds that start on this line
1481      let s:firstfold = 1
1482      while !empty(s:allfolds) && get(s:allfolds,0).firstline == s:lnum
1483	let s:foldId = s:foldId + 1
1484	let s:new .= "<span id='"
1485	let s:new .= (exists('g:html_diff_win_num') ? "win".g:html_diff_win_num : "")
1486	let s:new .= "fold".s:foldId.s:settings.id_suffix."' class='".s:allfolds[0].type."'>"
1487
1488
1489	" Unless disabled, add a fold column for the opening line of a fold.
1490	"
1491	" Note that dynamic folds require using css so we just use css to take
1492	" care of the leading spaces rather than using &nbsp; in the case of
1493	" html_no_pre to make it easier
1494	if !s:settings.no_foldcolumn
1495	  " add fold column that can open the new fold
1496	  if s:allfolds[0].level > 1 && s:firstfold
1497	    let s:new = s:new . s:FoldColumn_build('|', s:allfolds[0].level - 1, 0, "",
1498		  \ 'toggle-open FoldColumn','javascript:toggleFold("fold'.s:foldstack[0].id.s:settings.id_suffix.'");')
1499	  endif
1500	  " add the filler spaces separately from the '+' char so that it can be
1501	  " shown/hidden separately during a hover unfold
1502	  let s:new = s:new . s:FoldColumn_build("+", 1, 0, "",
1503		\ 'toggle-open FoldColumn', 'javascript:toggleFold("fold'.s:foldId.s:settings.id_suffix.'");')
1504	  " If this is not the last fold we're opening on this line, we need
1505	  " to keep the filler spaces hidden if the fold is opened by mouse
1506	  " hover. If it is the last fold to open in the line, we shouldn't hide
1507	  " them, so don't apply the toggle-filler class.
1508	  let s:new = s:new . s:FoldColumn_build(" ", 1, s:foldcolumn - s:allfolds[0].level - 1, "",
1509		\ 'toggle-open FoldColumn'. (get(s:allfolds, 1, {'firstline': 0}).firstline == s:lnum ?" toggle-filler" :""),
1510		\ 'javascript:toggleFold("fold'.s:foldId.s:settings.id_suffix.'");')
1511
1512	  " add fold column that can close the new fold
1513	  " only add extra blank space if we aren't opening another fold on the
1514	  " same line
1515	  if get(s:allfolds, 1, {'firstline': 0}).firstline != s:lnum
1516	    let s:extra_space = s:foldcolumn - s:allfolds[0].level
1517	  else
1518	    let s:extra_space = 0
1519	  endif
1520	  if s:firstfold
1521	    " the first fold in a line has '|' characters from folds opened in
1522	    " previous lines, before the '-' for this fold
1523	    let s:new .= s:FoldColumn_build('|', s:allfolds[0].level - 1, s:extra_space, '-',
1524		  \ 'toggle-closed FoldColumn', 'javascript:toggleFold("fold'.s:foldId.s:settings.id_suffix.'");')
1525	  else
1526	    " any subsequent folds in the line only add a single '-'
1527	    let s:new = s:new . s:FoldColumn_build("-", 1, s:extra_space, "",
1528		  \ 'toggle-closed FoldColumn', 'javascript:toggleFold("fold'.s:foldId.s:settings.id_suffix.'");')
1529	  endif
1530	  let s:firstfold = 0
1531	endif
1532
1533	" Add fold text, moving the span ending to the next line so collapsing
1534	" of folds works correctly.
1535	" Put numcol in a separate group for sake of unselectable text.
1536	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', '')
1537	let s:new = s:new . "<span class='fulltext'>"
1538
1539	" open the fold now that we have the fold text to allow retrieval of
1540	" fold text for subsequent folds
1541	execute s:lnum."foldopen"
1542	call insert(s:foldstack, remove(s:allfolds,0))
1543	let s:foldstack[0].id = s:foldId
1544      endwhile
1545
1546      " Unless disabled, add a fold column for other lines.
1547      "
1548      " Note that dynamic folds require using css so we just use css to take
1549      " care of the leading spaces rather than using &nbsp; in the case of
1550      " html_no_pre to make it easier
1551      if !s:settings.no_foldcolumn
1552	if empty(s:foldstack)
1553	  " add the empty foldcolumn for unfolded lines if there is a fold
1554	  " column at all
1555	  if s:foldcolumn > 0
1556	    let s:new = s:new . s:FoldColumn_fill()
1557	  endif
1558	else
1559	  " add the fold column for folds not on the opening line
1560	  if get(s:foldstack, 0).firstline < s:lnum
1561	    let s:new = s:new . s:FoldColumn_build('|', s:foldstack[0].level, s:foldcolumn - s:foldstack[0].level, "",
1562		  \ 'FoldColumn', 'javascript:toggleFold("fold'.s:foldstack[0].id.s:settings.id_suffix.'");')
1563	  endif
1564	endif
1565      endif
1566    endif
1567
1568    " Now continue with the unfolded line text
1569    if s:settings.number_lines
1570      let s:new = s:new . s:HtmlFormat_n(s:numcol, s:LINENR_ID, 0, s:lnum)
1571    elseif s:settings.line_ids
1572      let s:new = s:new . s:HtmlFormat_n("", s:LINENR_ID, 0, s:lnum)
1573    endif
1574
1575    " Get the diff attribute, if any.
1576    let s:diffattr = diff_hlID(s:lnum, 1)
1577
1578    " initialize conceal info to act like not concealed, just in case
1579    let s:concealinfo = [0, '']
1580
1581    " Loop over each character in the line
1582    let s:col = 1
1583
1584    " most of the time we won't use the diff_id, initialize to zero
1585    let s:diff_id = 0
1586
1587    while s:col <= s:len || (s:col == 1 && s:diffattr)
1588      let s:startcol = s:col " The start column for processing text
1589      if !s:settings.ignore_conceal && has('conceal')
1590	let s:concealinfo = synconcealed(s:lnum, s:col)
1591      endif
1592      if !s:settings.ignore_conceal && s:concealinfo[0]
1593	let s:col = s:col + 1
1594	" Speed loop (it's small - that's the trick)
1595	" Go along till we find a change in the match sequence number (ending
1596	" the specific concealed region) or until there are no more concealed
1597	" characters.
1598	while s:col <= s:len && s:concealinfo == synconcealed(s:lnum, s:col) | let s:col = s:col + 1 | endwhile
1599      elseif s:diffattr
1600	let s:diff_id = diff_hlID(s:lnum, s:col)
1601	let s:id = synID(s:lnum, s:col, 1)
1602	let s:col = s:col + 1
1603	" Speed loop (it's small - that's the trick)
1604	" Go along till we find a change in hlID
1605	while s:col <= s:len && s:id == synID(s:lnum, s:col, 1)
1606	      \   && s:diff_id == diff_hlID(s:lnum, s:col) |
1607	      \     let s:col = s:col + 1 |
1608	      \ endwhile
1609	if s:len < &columns && !s:settings.no_pre
1610	  " Add spaces at the end of the raw text line to extend the changed
1611	  " line to the full width.
1612	  let s:line = s:line . repeat(' ', &columns - virtcol([s:lnum, s:len]) - s:margin)
1613	  let s:len = &columns
1614	endif
1615      else
1616	let s:id = synID(s:lnum, s:col, 1)
1617	let s:col = s:col + 1
1618	" Speed loop (it's small - that's the trick)
1619	" Go along till we find a change in synID
1620	while s:col <= s:len && s:id == synID(s:lnum, s:col, 1) | let s:col = s:col + 1 | endwhile
1621      endif
1622
1623      if s:settings.ignore_conceal || !s:concealinfo[0]
1624	" Expand tabs if needed
1625	let s:expandedtab = strpart(s:line, s:startcol - 1, s:col - s:startcol)
1626	if s:settings.expand_tabs
1627	  let s:offset = 0
1628	  let s:idx = stridx(s:expandedtab, "\t")
1629	  let s:tablist = split(&vts,',')
1630	  if empty(s:tablist)
1631	    let s:tablist = [ &ts ]
1632	  endif
1633	  let s:tabidx = 0
1634	  let s:tabwidth = 0
1635	  while s:idx >= 0
1636	    if s:startcol + s:idx == 1
1637	      let s:i = s:tablist[0]
1638	    else
1639	      " Get the character, which could be multiple bytes, which falls
1640	      " immediately before the found tab. Extract it by matching a
1641	      " character just prior to the column where the tab matches.
1642	      " We'll use this to get the byte index of the character
1643	      " immediately preceding the tab, so we can then look up the
1644	      " virtual column that character appears in, to determine how
1645	      " much of the current tabstop has been used up.
1646	      if s:idx == 0
1647		" if the found tab is the first character in the text being
1648		" processed, we need to get the character prior to the text,
1649		" given by startcol.
1650		let s:prevc = matchstr(s:line, '.\%' . (s:startcol + s:offset) . 'c')
1651	      else
1652		" Otherwise, the byte index of the tab into s:expandedtab is
1653		" given by s:idx.
1654		let s:prevc = matchstr(s:expandedtab, '.\%' . (s:idx + 1) . 'c')
1655	      endif
1656	      let s:vcol = virtcol([s:lnum, s:startcol + s:idx + s:offset - len(s:prevc)])
1657
1658	      " find the tabstop interval to use for the tab we just found. Keep
1659	      " adding tabstops (which could be variable) until we would exceed
1660	      " the virtual screen position of the start of the found tab.
1661	      while s:vcol >= s:tabwidth + s:tablist[s:tabidx]
1662		let s:tabwidth += s:tablist[s:tabidx]
1663		if s:tabidx < len(s:tablist)-1
1664		  let s:tabidx = s:tabidx+1
1665		endif
1666	      endwhile
1667	      let s:i = s:tablist[s:tabidx] - (s:vcol - s:tabwidth)
1668	    endif
1669	    " update offset to keep the index within the line corresponding to
1670	    " actual tab characters instead of replaced spaces; s:idx reflects
1671	    " replaced spaces in s:expandedtab, s:offset cancels out all but
1672	    " the tab character itself.
1673	    let s:offset -= s:i - 1
1674	    let s:expandedtab = substitute(s:expandedtab, '\t', repeat(' ', s:i), '')
1675	    let s:idx = stridx(s:expandedtab, "\t")
1676	  endwhile
1677	end
1678
1679	" get the highlight group name to use
1680	let s:id = synIDtrans(s:id)
1681      else
1682	" use Conceal highlighting for concealed text
1683	let s:id = s:CONCEAL_ID
1684	let s:expandedtab = s:concealinfo[1]
1685      endif
1686
1687      " Output the text with the same synID, with class set to the highlight ID
1688      " name, unless it has been concealed completely.
1689      if strlen(s:expandedtab) > 0
1690	let s:new = s:new . s:HtmlFormat(s:expandedtab,  s:id, s:diff_id, "", 0)
1691      endif
1692    endwhile
1693  endif
1694
1695  call extend(s:lines, split(s:new.s:HtmlEndline, '\n', 1))
1696  if !s:settings.no_progress && s:pgb.needs_redraw
1697    redrawstatus
1698    let s:pgb.needs_redraw = 0
1699  endif
1700  let s:lnum = s:lnum + 1
1701
1702  if !s:settings.no_progress
1703    call s:pgb.incr()
1704  endif
1705endwhile
1706
1707if s:settings.dynamic_folds
1708  " finish off any open folds
1709  while !empty(s:foldstack)
1710    let s:lines[-1].="</span></span>"
1711    call remove(s:foldstack, 0)
1712  endwhile
1713
1714  " add fold column to the style list if not already there
1715  let s:id = s:FOLD_C_ID
1716  if !has_key(s:stylelist, s:id)
1717    let s:stylelist[s:id] = '.FoldColumn { ' . s:CSS1(s:id) . '}'
1718  endif
1719endif
1720
1721if s:settings.no_pre
1722  if !s:settings.use_css
1723    " Close off the font tag that encapsulates the whole <body>
1724    call extend(s:lines, ["</font>", "</body>", "</html>"])
1725  else
1726    call extend(s:lines, ["</div>", "</body>", "</html>"])
1727  endif
1728else
1729  call extend(s:lines, ["</pre>", "</body>", "</html>"])
1730endif
1731
1732exe s:newwin . "wincmd w"
1733call setline(1, s:lines)
1734unlet s:lines
1735
1736" Mangle modelines so Vim doesn't try to use HTML text as a modeline if editing
1737" this file in the future; need to do this after generating all the text in case
1738" the modeline text has different highlight groups which all turn out to be
1739" stripped from the final output.
1740%s!\v(%(^|\s+)%([Vv]i%(m%([<=>]?\d+)?)?|ex)):!\1\&#0058;!ge
1741
1742" The generated HTML is admittedly ugly and takes a LONG time to fold.
1743" Make sure the user doesn't do syntax folding when loading a generated file,
1744" using a modeline.
1745call append(line('$'), "<!-- vim: set foldmethod=manual : -->")
1746
1747" Now, when we finally know which, we define the colors and styles
1748if s:settings.use_css
1749  1;/<style\>/+1
1750endif
1751
1752" Normal/global attributes
1753if s:settings.use_css
1754  if s:settings.no_pre
1755    call append('.', "body { color: " . s:fgc . "; background-color: " . s:bgc . "; font-family: ". s:htmlfont ."; }")
1756    +
1757  else
1758    call append('.', "pre { " . s:whitespace . "font-family: ". s:htmlfont ."; color: " . s:fgc . "; background-color: " . s:bgc . "; }")
1759    +
1760    yank
1761    put
1762    execute "normal! ^cwbody\e"
1763    " body should not have the wrap formatting, only the pre section
1764    if s:whitespace != ''
1765      exec 's#'.s:whitespace
1766    endif
1767  endif
1768  " fix browser inconsistencies (sometimes within the same browser) of different
1769  " default font size for different elements
1770  call append('.', '* { font-size: 1em; }')
1771  +
1772  " if we use any input elements for unselectable content, make sure they look
1773  " like normal text
1774  if !empty(s:settings.prevent_copy)
1775    if s:settings.use_input_for_pc !=# "none"
1776      call append('.', 'input { border: none; margin: 0; padding: 0; font-family: '.s:htmlfont.'; }')
1777      +
1778      " ch units for browsers which support them, em units for a somewhat
1779      " reasonable fallback.
1780      for w in range(1, 20, 1)
1781	call append('.', [
1782	      \ "input[size='".w."'] { width: ".w."em; width: ".w."ch; }"
1783	      \ ])
1784	+
1785      endfor
1786    endif
1787
1788    if s:settings.use_input_for_pc !=# 'all'
1789      let s:unselectable_styles = []
1790      if s:settings.prevent_copy =~# 'f'
1791	call add(s:unselectable_styles, 'FoldColumn')
1792      endif
1793      if s:settings.prevent_copy =~# 'n'
1794	call add(s:unselectable_styles, 'LineNr')
1795      endif
1796      if s:settings.prevent_copy =~# 't' && !s:settings.ignore_folding
1797	call add(s:unselectable_styles, 'Folded')
1798      endif
1799      if s:settings.prevent_copy =~# 'd'
1800	call add(s:unselectable_styles, 'DiffDelete')
1801      endif
1802      if s:settings.use_input_for_pc !=# 'none'
1803	call append('.', [
1804	      \ '/* Note: IE does not support @supports conditionals, but also does not fully support',
1805	      \ '   "content:" with custom content, so we *want* the check to fail */',
1806	      \ '@supports ( content: attr(data-custom-content) ) {'
1807	      \ ])
1808	+3
1809      endif
1810      " The line number column inside the foldtext is styled just like the fold
1811      " text in Vim, but it should use the prevent_copy settings of line number
1812      " rather than fold text. Apply the prevent_copy styles to foldtext
1813      " specifically for line numbers, which always come after the fold column,
1814      " or at the beginning of the line.
1815      if s:settings.prevent_copy =~# 'n' && !s:settings.ignore_folding
1816	call append('.', [
1817	      \ '  .FoldColumn + .Folded, .Folded:first-child { user-select: none; }',
1818	      \ '  .FoldColumn + [data-Folded-content]::before, [data-Folded-content]:first-child::before { content: attr(data-Folded-content); }',
1819	      \ '  .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 */ }',
1820	      \ '  .FoldColumn + span[data-Folded-content]::before, [data-Folded-content]:first-child::before { cursor: default; }',
1821	      \ ])
1822	+4
1823      endif
1824      for s:style_name in s:unselectable_styles
1825	call append('.', [
1826	      \ '  .'.s:style_name.' { user-select: none; }',
1827	      \ '  [data-'.s:style_name.'-content]::before { content: attr(data-'.s:style_name.'-content); }',
1828	      \ '  [data-'.s:style_name.'-content]::before { padding-bottom: 1px; display: inline-block; /* match the 1-px padding of standard items with background */ }',
1829	      \ '  span[data-'.s:style_name.'-content]::before { cursor: default; }',
1830	      \ ])
1831	+4
1832      endfor
1833      if s:settings.use_input_for_pc !=# 'none'
1834	call append('.', [
1835	      \ '  input { display: none; }',
1836	      \ '}'
1837	      \ ])
1838	+2
1839      endif
1840      unlet s:unselectable_styles
1841    endif
1842
1843    " Fix mouse cursor shape for the fallback <input> method of uncopyable text
1844    if s:settings.use_input_for_pc !=# 'none'
1845      if s:settings.prevent_copy =~# 'f'
1846	" Make the cursor show active fold columns as active areas, and empty fold
1847	" columns as not interactive.
1848	call append('.', ['input.FoldColumn { cursor: pointer; }',
1849	      \ 'input.FoldColumn[value="'.repeat(' ', s:foldcolumn).'"] { cursor: default; }'
1850	      \ ])
1851	+2
1852	if s:settings.use_input_for_pc !=# 'all'
1853	  call append('.', [
1854		\ 'a[data-FoldColumn-content="'.repeat(' ', s:foldcolumn).'"] { cursor: default; }'
1855		\ ])
1856	  +1
1857	end
1858      endif
1859      " make line number column show as non-interactive if not selectable
1860      if s:settings.prevent_copy =~# 'n'
1861	call append('.', 'input.LineNr { cursor: default; }')
1862	+
1863      endif
1864      " make fold text and line number column within fold text show as
1865      " non-interactive if not selectable
1866      if (s:settings.prevent_copy =~# 'n' || s:settings.prevent_copy =~# 't') && !s:settings.ignore_folding
1867	call append('.', 'input.Folded { cursor: default; }')
1868	+
1869      endif
1870      " make diff filler show as non-interactive if not selectable
1871      if s:settings.prevent_copy =~# 'd'
1872	call append('.', 'input.DiffDelete { cursor: default; }')
1873	+
1874      endif
1875    endif
1876  endif
1877else
1878  " For Netscape 4, set <body> attributes too, though, strictly speaking, it's
1879  " incorrect.
1880  execute '%s:<body\([^>]*\):<body bgcolor="' . s:bgc . '" text="' . s:fgc . '"\1>\r<font face="'. s:htmlfont .'"'
1881endif
1882
1883" Gather attributes for all other classes. Do diff first so that normal
1884" highlight groups are inserted before it.
1885if s:settings.use_css
1886  if s:diff_mode
1887    call append('.', filter(map(keys(s:diffstylelist), "s:diffstylelist[v:val]"), 'v:val != ""'))
1888  endif
1889  if !empty(s:stylelist)
1890    call append('.', filter(map(keys(s:stylelist), "s:stylelist[v:val]"), 'v:val != ""'))
1891  endif
1892endif
1893
1894" Add hyperlinks
1895" TODO: add option to not do this? Maybe just make the color the same as the
1896" text highlight group normally is?
1897%s+\(https\=://\S\{-}\)\(\([.,;:}]\=\(\s\|$\)\)\|[\\"'<>]\|&gt;\|&lt;\|&quot;\)+<a href="\1">\1</a>\2+ge
1898
1899" The DTD
1900if s:settings.use_xhtml
1901  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\">"
1902elseif s:html5
1903  exe "normal! gg0i<!DOCTYPE html>\n"
1904else
1905  exe "normal! gg0i<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n"
1906endif
1907
1908if s:settings.use_xhtml
1909  exe "normal! gg/<html/e\na xmlns=\"http://www.w3.org/1999/xhtml\"\e"
1910endif
1911
1912" Cleanup
1913%s:\s\+$::e
1914
1915" Restore old settings (new window first)
1916"
1917" Don't bother restoring foldmethod in case it was syntax because the markup is
1918" so weirdly formatted it can take a LONG time.
1919let &l:foldenable = s:old_fen
1920let &report = s:old_report
1921let &title = s:old_title
1922let &icon = s:old_icon
1923let &paste = s:old_paste
1924let &magic = s:old_magic
1925let @/ = s:old_search
1926let &more = s:old_more
1927
1928" switch to original window to restore those settings
1929exe s:orgwin . "wincmd w"
1930
1931if !s:settings.expand_tabs
1932  let &l:isprint = s:old_isprint
1933endif
1934let &l:stl = s:origwin_stl
1935let &l:et = s:old_et
1936let &l:scrollbind = s:old_bind
1937
1938" and back to the new window again to end there
1939exe s:newwin . "wincmd w"
1940
1941let &l:stl = s:newwin_stl
1942exec 'resize' s:old_winheight
1943let &l:winfixheight = s:old_winfixheight
1944
1945let &ls=s:ls
1946let &eventignore=s:ei_sav
1947
1948" Save a little bit of memory (worth doing?)
1949unlet s:htmlfont s:whitespace
1950unlet s:old_et s:old_paste s:old_icon s:old_report s:old_title s:old_search
1951unlet s:old_magic s:old_more s:old_fen s:old_winheight
1952unlet! s:old_isprint
1953unlet s:whatterm s:stylelist s:diffstylelist s:lnum s:end s:margin s:fgc s:bgc s:old_winfixheight
1954unlet! s:col s:id s:attr s:len s:line s:new s:expandedtab s:concealinfo s:diff_mode
1955unlet! s:orgwin s:newwin s:orgbufnr s:idx s:i s:offset s:ls s:ei_sav s:origwin_stl
1956unlet! s:newwin_stl s:current_syntax
1957if !v:profiling
1958  delfunc s:HtmlColor
1959  delfunc s:HtmlFormat
1960  delfunc s:CSS1
1961  delfunc s:BuildStyleWrapper
1962  if !s:settings.use_css
1963    delfunc s:HtmlOpening
1964    delfunc s:HtmlClosing
1965  endif
1966  if s:settings.dynamic_folds
1967    delfunc s:FoldCompare
1968  endif
1969
1970  if !s:settings.no_progress
1971    delfunc s:ProgressBar
1972    delfunc s:progressbar.paint
1973    delfunc s:progressbar.incr
1974    unlet s:pgb s:progressbar
1975  endif
1976endif
1977
1978unlet! s:new_lnum s:diffattr s:difffillchar s:foldfillchar s:HtmlSpace
1979unlet! s:LeadingSpace s:HtmlEndline s:firstfold s:numcol s:foldcolumn
1980unlet s:foldstack s:allfolds s:foldId s:settings
1981
1982let &cpo = s:cpo_sav
1983unlet! s:cpo_sav
1984
1985" Make sure any patches will probably use consistent indent
1986"   vim: ts=8 sw=2 sts=2 noet
1987