xref: /vim-8.2.3635/runtime/syntax/2html.vim (revision dfccaf0f)
1" Vim syntax support file
2" Maintainer: Bram Moolenaar <[email protected]>
3" Last Change: 2004 Dec 14
4"	       (modified by David Ne\v{c}as (Yeti) <[email protected]>)
5"	       (XHTML support by Panagiotis Issaris <[email protected]>)
6
7" Transform a file into HTML, using the current syntax highlighting.
8
9" Number lines when explicitely requested or when `number' is set
10if exists("html_number_lines")
11  let s:numblines = html_number_lines
12else
13  let s:numblines = &number
14endif
15
16" When not in gui we can only guess the colors.
17if has("gui_running")
18  let s:whatterm = "gui"
19else
20  let s:whatterm = "cterm"
21  if &t_Co == 8
22    let s:cterm_color0  = "#808080"
23    let s:cterm_color1  = "#ff6060"
24    let s:cterm_color2  = "#00ff00"
25    let s:cterm_color3  = "#ffff00"
26    let s:cterm_color4  = "#8080ff"
27    let s:cterm_color5  = "#ff40ff"
28    let s:cterm_color6  = "#00ffff"
29    let s:cterm_color7  = "#ffffff"
30  else
31    let s:cterm_color0  = "#000000"
32    let s:cterm_color1  = "#c00000"
33    let s:cterm_color2  = "#008000"
34    let s:cterm_color3  = "#804000"
35    let s:cterm_color4  = "#0000c0"
36    let s:cterm_color5  = "#c000c0"
37    let s:cterm_color6  = "#008080"
38    let s:cterm_color7  = "#c0c0c0"
39    let s:cterm_color8  = "#808080"
40    let s:cterm_color9  = "#ff6060"
41    let s:cterm_color10 = "#00ff00"
42    let s:cterm_color11 = "#ffff00"
43    let s:cterm_color12 = "#8080ff"
44    let s:cterm_color13 = "#ff40ff"
45    let s:cterm_color14 = "#00ffff"
46    let s:cterm_color15 = "#ffffff"
47  endif
48endif
49
50" Return good color specification: in GUI no transformation is done, in
51" terminal return RGB values of known colors and empty string on unknown
52if s:whatterm == "gui"
53  function! s:HtmlColor(color)
54    return a:color
55  endfun
56else
57  function! s:HtmlColor(color)
58    if exists("s:cterm_color" . a:color)
59      execute "return s:cterm_color" . a:color
60    else
61      return ""
62    endif
63  endfun
64endif
65
66if !exists("html_use_css")
67  " Return opening HTML tag for given highlight id
68  function! s:HtmlOpening(id)
69    let a = ""
70    if synIDattr(a:id, "inverse")
71      " For inverse, we always must set both colors (and exchange them)
72      let x = s:HtmlColor(synIDattr(a:id, "fg#", s:whatterm))
73      let a = a . '<span style="background-color: ' . ( x != "" ? x : s:fgc ) . '">'
74      let x = s:HtmlColor(synIDattr(a:id, "bg#", s:whatterm))
75      let a = a . '<font color="' . ( x != "" ? x : s:bgc ) . '">'
76    else
77      let x = s:HtmlColor(synIDattr(a:id, "bg#", s:whatterm))
78      if x != "" | let a = a . '<span style="background-color: ' . x . '">' | endif
79      let x = s:HtmlColor(synIDattr(a:id, "fg#", s:whatterm))
80      if x != "" | let a = a . '<font color="' . x . '">' | endif
81    endif
82    if synIDattr(a:id, "bold") | let a = a . "<b>" | endif
83    if synIDattr(a:id, "italic") | let a = a . "<i>" | endif
84    if synIDattr(a:id, "underline") | let a = a . "<u>" | endif
85    return a
86  endfun
87
88  " Return closing HTML tag for given highlight id
89  function s:HtmlClosing(id)
90    let a = ""
91    if synIDattr(a:id, "underline") | let a = a . "</u>" | endif
92    if synIDattr(a:id, "italic") | let a = a . "</i>" | endif
93    if synIDattr(a:id, "bold") | let a = a . "</b>" | endif
94    if synIDattr(a:id, "inverse")
95      let a = a . '</font></span>'
96    else
97      let x = s:HtmlColor(synIDattr(a:id, "fg#", s:whatterm))
98      if x != "" | let a = a . '</font>' | endif
99      let x = s:HtmlColor(synIDattr(a:id, "bg#", s:whatterm))
100      if x != "" | let a = a . '</span>' | endif
101    endif
102    return a
103  endfun
104endif
105
106" Return HTML valid characters enclosed in a span of class style_name with
107" unprintable characters expanded and double spaces replaced as necessary.
108function! s:HtmlFormat(text, style_name)
109  " Replace unprintable characters
110  let formatted = strtrans(a:text)
111
112  " Replace the reserved html characters
113  let formatted = substitute(substitute(substitute(substitute(substitute(formatted, '&', '\&amp;', 'g'), '<', '\&lt;', 'g'), '>', '\&gt;', 'g'), '"', '\&quot;', 'g'), "\x0c", '<hr class="PAGE-BREAK">', 'g')
114
115  " Replace double spaces
116  if ' ' != s:HtmlSpace
117    let formatted = substitute(formatted, '  ', s:HtmlSpace . s:HtmlSpace, 'g')
118  endif
119
120  " Enclose in a span of class style_name
121  let formatted = '<span class="' . a:style_name . '">' . formatted . '</span>'
122
123  " Add the class to class list if it's not there yet
124  let s:id = hlID(a:style_name)
125  if stridx(s:idlist, "," . s:id . ",") == -1
126    let s:idlist = s:idlist . s:id . ","
127  endif
128
129  return formatted
130endfun
131
132" Return CSS style describing given highlight id (can be empty)
133function! s:CSS1(id)
134  let a = ""
135  if synIDattr(a:id, "inverse")
136    " For inverse, we always must set both colors (and exchange them)
137    let x = s:HtmlColor(synIDattr(a:id, "bg#", s:whatterm))
138    let a = a . "color: " . ( x != "" ? x : s:bgc ) . "; "
139    let x = s:HtmlColor(synIDattr(a:id, "fg#", s:whatterm))
140    let a = a . "background-color: " . ( x != "" ? x : s:fgc ) . "; "
141  else
142    let x = s:HtmlColor(synIDattr(a:id, "fg#", s:whatterm))
143    if x != "" | let a = a . "color: " . x . "; " | endif
144    let x = s:HtmlColor(synIDattr(a:id, "bg#", s:whatterm))
145    if x != "" | let a = a . "background-color: " . x . "; " | endif
146  endif
147  if synIDattr(a:id, "bold") | let a = a . "font-weight: bold; " | endif
148  if synIDattr(a:id, "italic") | let a = a . "font-style: italic; " | endif
149  if synIDattr(a:id, "underline") | let a = a . "text-decoration: underline; " | endif
150  return a
151endfun
152
153" Figure out proper MIME charset from the 'encoding' option.
154if exists("html_use_encoding")
155  let s:html_encoding = html_use_encoding
156else
157  let s:vim_encoding = &encoding
158  if s:vim_encoding =~ '^8bit\|^2byte'
159    let s:vim_encoding = substitute(s:vim_encoding, '^8bit-\|^2byte-', '', '')
160  endif
161  if s:vim_encoding == 'latin1'
162    let s:html_encoding = 'iso-8859-1'
163  elseif s:vim_encoding =~ "^cp12"
164    let s:html_encoding = substitute(s:vim_encoding, 'cp', 'windows-', '')
165  elseif s:vim_encoding == 'sjis'
166    let s:html_encoding = 'Shift_JIS'
167  elseif s:vim_encoding == 'euc-cn'
168    let s:html_encoding = 'GB_2312-80'
169  elseif s:vim_encoding == 'euc-tw'
170    let s:html_encoding = ""
171  elseif s:vim_encoding =~ '^euc\|^iso\|^koi'
172    let s:html_encoding = substitute(s:vim_encoding, '.*', '\U\0', '')
173  elseif s:vim_encoding == 'cp949'
174    let s:html_encoding = 'KS_C_5601-1987'
175  elseif s:vim_encoding == 'cp936'
176    let s:html_encoding = 'GBK'
177  elseif s:vim_encoding =~ '^ucs\|^utf'
178    let s:html_encoding = 'UTF-8'
179  else
180    let s:html_encoding = ""
181  endif
182endif
183
184
185" Set some options to make it work faster.
186" Don't report changes for :substitute, there will be many of them.
187let s:old_title = &title
188let s:old_icon = &icon
189let s:old_et = &l:et
190let s:old_report = &report
191let s:old_search = @/
192set notitle noicon
193setlocal et
194set report=1000000
195
196" Split window to create a buffer with the HTML file.
197let s:orgbufnr = winbufnr(0)
198if expand("%") == ""
199  new Untitled.html
200else
201  new %.html
202endif
203let s:newwin = winnr()
204let s:orgwin = bufwinnr(s:orgbufnr)
205
206set modifiable
207%d
208let s:old_paste = &paste
209set paste
210let s:old_magic = &magic
211set magic
212
213if exists("use_xhtml")
214  if s:html_encoding != ""
215    exe "normal!  a<?xml version=\"1.0\" encoding=\"" . s:html_encoding . "\"?>\n\e"
216  else
217    exe "normal! a<?xml version=\"1.0\"?>\n\e"
218  endif
219  let s:tag_close = '/>'
220else
221  let s:tag_close = '>'
222endif
223
224let s:HtmlSpace = ' '
225let s:HtmlEndline = ''
226if exists("html_no_pre")
227  let s:HtmlEndline = '<br' . s:tag_close
228  if exists("use_xhtml")
229    let s:HtmlSpace = '\&#x20;'
230  else
231    let s:HtmlSpace = '\&nbsp;'
232  endif
233endif
234
235" HTML header, with the title and generator ;-). Left free space for the CSS,
236" to be filled at the end.
237exe "normal! a<html>\n\e"
238exe "normal! a<head>\n<title>" . expand("%:p:~") . "</title>\n\e"
239exe "normal! a<meta name=\"Generator\" content=\"Vim/" . v:version/100 . "." . v:version %100 . '"' . s:tag_close . "\n\e"
240if s:html_encoding != ""
241  exe "normal! a<meta http-equiv=\"content-type\" content=\"text/html; charset=" . s:html_encoding . '"' . s:tag_close . "\n\e"
242endif
243if exists("html_use_css")
244  exe "normal! a<style type=\"text/css\">\n<!--\n-->\n</style>\n\e"
245endif
246if exists("html_no_pre")
247  exe "normal! a</head>\n<body>\n\e"
248else
249  exe "normal! a</head>\n<body>\n<pre>\n\e"
250endif
251
252exe s:orgwin . "wincmd w"
253
254" List of all id's
255let s:idlist = ","
256
257" Loop over all lines in the original text.
258" Use html_start_line and html_end_line if they are set.
259if exists("html_start_line")
260  let s:lnum = html_start_line
261  if s:lnum < 1 || s:lnum > line("$")
262    let s:lnum = 1
263  endif
264else
265  let s:lnum = 1
266endif
267if exists("html_end_line")
268  let s:end = html_end_line
269  if s:end < s:lnum || s:end > line("$")
270    let s:end = line("$")
271  endif
272else
273  let s:end = line("$")
274endif
275
276if has('folding')
277  let s:foldfillchar = &fillchars[matchend(&fillchars, 'fold:')]
278  if s:foldfillchar == ''
279    let s:foldfillchar = '-'
280  endif
281endif
282let s:difffillchar = &fillchars[matchend(&fillchars, 'diff:')]
283if s:difffillchar == ''
284  let s:difffillchar = '-'
285endif
286
287
288while s:lnum <= s:end
289
290  " If there are filler lines for diff mode, show these above the line.
291  let s:filler = diff_filler(s:lnum)
292  if s:filler > 0
293    let s:n = s:filler
294    while s:n > 0
295      if s:numblines
296        " Indent if line numbering is on
297        let s:new = repeat(' ', strlen(s:end) + 1) . repeat(s:difffillchar, 3)
298      else
299        let s:new = repeat(s:difffillchar, 3)
300      endif
301
302      if s:n > 2 && s:n < s:filler && !exists("html_whole_filler")
303	let s:new = s:new . " " . s:filler . " inserted lines "
304	let s:n = 2
305      endif
306
307      if !exists("html_no_pre")
308        " HTML line wrapping is off--go ahead and fill to the margin
309        let s:new = s:new . repeat(s:difffillchar, &columns - strlen(s:new))
310      endif
311
312      let s:new = s:HtmlFormat(s:new, "DiffDelete")
313      exe s:newwin . "wincmd w"
314      exe "normal! a" . s:new . s:HtmlEndline . "\n\e"
315      exe s:orgwin . "wincmd w"
316
317      let s:n = s:n - 1
318    endwhile
319    unlet s:n
320  endif
321  unlet s:filler
322
323  " Start the line with the line number.
324  if s:numblines
325    let s:new = repeat(' ', strlen(s:end) - strlen(s:lnum)) . s:lnum . ' '
326  else
327    let s:new = ""
328  endif
329
330  if has('folding') && foldclosed(s:lnum) > -1
331    "
332    " This is the beginning of a folded block
333    "
334    let s:new = s:new . foldtextresult(s:lnum)
335    if !exists("html_no_pre")
336      " HTML line wrapping is off--go ahead and fill to the margin
337      let s:new = s:new . repeat(s:foldfillchar, &columns - strlen(s:new))
338    endif
339
340    let s:new = s:HtmlFormat(s:new, "Folded")
341
342    " Skip to the end of the fold
343    let s:lnum = foldclosedend(s:lnum)
344
345  else
346    "
347    " A line that is not folded.
348    "
349    let s:line = getline(s:lnum)
350
351    let s:len = strlen(s:line)
352
353    if s:numblines
354      let s:new = '<span class="lnr">' . s:new . '</span>'
355    endif
356
357    " Get the diff attribute, if any.
358    let s:diffattr = diff_hlID(s:lnum, 1)
359
360    " Loop over each character in the line
361    let s:col = 1
362    while s:col <= s:len || (s:col == 1 && s:diffattr)
363      let s:startcol = s:col " The start column for processing text
364      if s:diffattr
365	let s:id = diff_hlID(s:lnum, s:col)
366	let s:col = s:col + 1
367	" Speed loop (it's small - that's the trick)
368	" Go along till we find a change in hlID
369	while s:col <= s:len && s:id == diff_hlID(s:lnum, s:col) | let s:col = s:col + 1 | endwhile
370        if s:len < &columns && !exists("html_no_pre")
371	  " Add spaces at the end to mark the changed line.
372          let s:line = s:line . repeat(' ', &columns - s:len)
373          let s:len = &columns
374        endif
375      else
376	let s:id = synID(s:lnum, s:col, 1)
377	let s:col = s:col + 1
378	" Speed loop (it's small - that's the trick)
379	" Go along till we find a change in synID
380	while s:col <= s:len && s:id == synID(s:lnum, s:col, 1) | let s:col = s:col + 1 | endwhile
381      endif
382
383      " Expand tabs
384      let s:expandedtab = strpart(s:line, s:startcol - 1, s:col - s:startcol)
385      let idx = stridx(s:expandedtab, "\t")
386      while idx >= 0
387        let i = &ts - ((idx + s:startcol - 1) % &ts)
388        let s:expandedtab = substitute(s:expandedtab, '\t', repeat(' ', i), '')
389        let idx = stridx(s:expandedtab, "\t")
390      endwhile
391
392      " Output the text with the same synID, with class set to {s:id_name}
393      let s:id = synIDtrans(s:id)
394      let s:id_name = synIDattr(s:id, "name", s:whatterm)
395      let s:new = s:new . s:HtmlFormat(s:expandedtab,  s:id_name)
396    endwhile
397  endif
398
399  exe s:newwin . "wincmd w"
400  exe "normal! a" . s:new . s:HtmlEndline . "\n\e"
401  exe s:orgwin . "wincmd w"
402  let s:lnum = s:lnum + 1
403  +
404endwhile
405" Finish with the last line
406exe s:newwin . "wincmd w"
407if exists("html_no_pre")
408  exe "normal! a\n</body>\n</html>\e"
409else
410  exe "normal! a</pre>\n</body>\n</html>\e"
411endif
412
413
414" Now, when we finally know which, we define the colors and styles
415if exists("html_use_css")
416  1;/<style type="text/+1
417endif
418
419" Find out the background and foreground color.
420let s:fgc = s:HtmlColor(synIDattr(hlID("Normal"), "fg#", s:whatterm))
421let s:bgc = s:HtmlColor(synIDattr(hlID("Normal"), "bg#", s:whatterm))
422if s:fgc == ""
423  let s:fgc = ( &background == "dark" ? "#ffffff" : "#000000" )
424endif
425if s:bgc == ""
426  let s:bgc = ( &background == "dark" ? "#000000" : "#ffffff" )
427endif
428
429" Normal/global attributes
430" For Netscape 4, set <body> attributes too, though, strictly speaking, it's
431" incorrect.
432if exists("html_use_css")
433  if exists("html_no_pre")
434    execute "normal! A\nbody { color: " . s:fgc . "; background-color: " . s:bgc . "; font-family: Courier, monospace; }\e"
435  else
436    execute "normal! A\npre { color: " . s:fgc . "; background-color: " . s:bgc . "; }\e"
437    yank
438    put
439    execute "normal! ^cwbody\e"
440  endif
441else
442  if exists("html_no_pre")
443    execute '%s:<body>:<body ' . 'bgcolor="' . s:bgc . '" text="' . s:fgc . '" style="font-family\: Courier, monospace;">'
444  else
445    execute '%s:<body>:<body ' . 'bgcolor="' . s:bgc . '" text="' . s:fgc . '">'
446  endif
447endif
448
449" Line numbering attributes
450if s:numblines
451  if exists("html_use_css")
452    execute "normal! A\n.lnr { " . s:CSS1(hlID("LineNr")) . "}\e"
453  else
454    execute '%s+<span class="lnr">\([^<]*\)</span>+' . s:HtmlOpening(hlID("LineNr")) . '\1' . s:HtmlClosing(hlID("LineNr")) . '+g'
455  endif
456endif
457
458" Gather attributes for all other classes
459let s:idlist = strpart(s:idlist, 1)
460while s:idlist != ""
461  let s:attr = ""
462  let s:col = stridx(s:idlist, ",")
463  let s:id = strpart(s:idlist, 0, s:col)
464  let s:idlist = strpart(s:idlist, s:col + 1)
465  let s:attr = s:CSS1(s:id)
466  let s:id_name = synIDattr(s:id, "name", s:whatterm)
467  " If the class has some attributes, export the style, otherwise DELETE all
468  " its occurences to make the HTML shorter
469  if s:attr != ""
470    if exists("html_use_css")
471      execute "normal! A\n." . s:id_name . " { " . s:attr . "}"
472    else
473      execute '%s+<span class="' . s:id_name . '">\([^<]*\)</span>+' . s:HtmlOpening(s:id) . '\1' . s:HtmlClosing(s:id) . '+g'
474    endif
475  else
476    execute '%s+<span class="' . s:id_name . '">\([^<]*\)</span>+\1+g'
477    if exists("html_use_css")
478      1;/<style type="text/+1
479    endif
480  endif
481endwhile
482
483" Add hyperlinks
484%s+\(https\=://\S\{-}\)\(\([.,;:}]\=\(\s\|$\)\)\|[\\"'<>]\|&gt;\|&lt;\|&quot;\)+<a href="\1">\1</a>\2+ge
485
486" The DTD
487if exists("html_use_css")
488  if exists("use_xhtml")
489    exe "normal! gg$a\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\e"
490  else
491    exe "normal! gg0i<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n\e"
492  endif
493endif
494
495if exists("use_xhtml")
496  exe "normal! gg/<html/e\na xmlns=\"http://www.w3.org/1999/xhtml\"\e"
497endif
498
499" Cleanup
500%s:\s\+$::e
501
502" Restore old settings
503let &report = s:old_report
504let &title = s:old_title
505let &icon = s:old_icon
506let &paste = s:old_paste
507let &magic = s:old_magic
508let @/ = s:old_search
509exe s:orgwin . "wincmd w"
510let &l:et = s:old_et
511exe s:newwin . "wincmd w"
512
513" Save a little bit of memory (worth doing?)
514unlet s:old_et s:old_paste s:old_icon s:old_report s:old_title s:old_search
515unlet s:whatterm s:idlist s:lnum s:end s:fgc s:bgc s:old_magic
516unlet! s:col s:id s:attr s:len s:line s:new s:expandedtab s:numblines
517unlet s:orgwin s:newwin s:orgbufnr
518delfunc s:HtmlColor
519delfunc s:HtmlFormat
520delfunc s:CSS1
521if !exists("html_use_css")
522  delfunc s:HtmlOpening
523  delfunc s:HtmlClosing
524endif
525silent! unlet s:diffattr s:difffillchar s:foldfillchar s:HtmlSpace s:HtmlEndline
526