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, '&', '\&', 'g') 459 let formatted = substitute(formatted, '<', '\<', 'g') 460 let formatted = substitute(formatted, '>', '\>', 'g') 461 let formatted = substitute(formatted, '"', '\"', 'g') 462 " ' 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, '"', '\'', '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 ? ' ' : ' ' 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 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 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\:!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\|$\)\)\|[\\"'<>]\|>\|<\|"\)+<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