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