tohtml_spec.lua (11619B)
1 local t = require('test.testutil') 2 local n = require('test.functional.testnvim')() 3 local Screen = require('test.functional.ui.screen') 4 5 local clear = n.clear 6 local exec = n.exec 7 local exec_lua = n.exec_lua 8 local eq = t.eq 9 local fn = n.fn 10 local api = n.api 11 local insert = n.insert 12 13 local function html_syntax_match() 14 local styles = 15 vim.split(api.nvim_exec2([[/<style>/+,/<\/style>/-p]], { output = true }).output, '\n') 16 local attrnames = { 17 ['font%-weight: bold'] = 'bold', 18 ['text%-decoration%-line: [^;]*underline'] = 'underline', 19 ['font%-style: italic'] = 'italic', 20 ['text%-decoration%-line: [^;]*line%-through'] = 'strikethrough', 21 } 22 local hls = {} 23 for _, style in ipairs(styles) do 24 local attr = {} 25 for match, attrname in pairs(attrnames) do 26 if style:find(match) then 27 ---@type boolean 28 attr[attrname] = true 29 end 30 end 31 if style:find('text%-decoration%-style: wavy') and attr.underline then 32 ---@type boolean 33 attr.underline = nil 34 attr.undercurl = true 35 end 36 attr.sp = style:match('text%-decoration%-color: #(%x+)') 37 if attr.sp then 38 attr.sp = tonumber(attr.sp, 16) 39 end 40 attr.bg = style:match('background%-color: #(%x+)') 41 if attr.bg then 42 attr.bg = tonumber(attr.bg, 16) 43 end 44 attr.fg = style:match('[^%-]color: #(%x+)') 45 if attr.fg then 46 attr.fg = tonumber(attr.fg, 16) 47 end 48 if style:match('^%.(%w+)') then 49 ---@type table 50 hls[style:match('^%.(%w+)')] = attr 51 end 52 end 53 local whitelist = { 54 'fg', 55 'bg', 56 'sp', 57 --'blend', 58 'bold', 59 --'standout', 60 'underline', 61 'undercurl', 62 --'underdouble', 63 --'underdotted', 64 --'underdashed', 65 'strikethrough', 66 'italic', 67 --'reverse', 68 --'nocombine', 69 } 70 for name, attrs_old in 71 pairs(api.nvim_get_hl(0, { link = true }) --[[@as table<string,table>]]) 72 do 73 ---@type table 74 local other = hls[name:gsub('%.', '-'):gsub('@', '-')] 75 if other then 76 local attrs = {} 77 for _, attrname in ipairs(whitelist) do 78 ---@type table 79 attrs[attrname] = attrs_old[attrname] 80 end 81 eq(attrs, other) 82 end 83 end 84 return hls 85 end 86 87 local function html_to_extmarks() 88 local buf = api.nvim_get_current_buf() 89 local ns = api.nvim_create_namespace 'test-namespace' 90 api.nvim_buf_clear_namespace(buf, ns, 0, -1) 91 exec 'silent! norm! ggd/^<pre>$\rddG3dk' 92 local stack = {} 93 exec [[set filetype=]] 94 exec [[silent! %s/</¤/g]] 95 exec [[silent! %s/"/"/g]] 96 exec [[silent! %s/&/\&/g]] 97 exec [[silent! %s/>/>/g]] 98 exec [[silent! %s/</</g]] 99 for _, match in 100 ipairs( 101 fn.matchbufline(buf, [[¤span class="\([^"]\+\)">\|¤/span>]], 1, '$', { submatches = true }) --[[@as (table[])]] 102 ) 103 do 104 if match.text == '¤/span>' then 105 local val = table.remove(stack) 106 api.nvim_buf_set_extmark(buf, ns, val.lnum - 1, val.byteidx, { 107 hl_group = val.submatches[1], 108 end_row = match.lnum - 1, 109 end_col = match.byteidx, 110 }) 111 else 112 table.insert(stack, match) 113 end 114 end 115 exec [[silent! %s/¤\/span>//g]] 116 exec [[silent! %s/¤span[^>]*>//g]] 117 end 118 119 ---@param screen test.functional.ui.screen 120 ---@param func function? 121 local function run_tohtml_and_assert(screen, func) 122 exec('norm! ggO--;') 123 screen:expect({ any = vim.pesc('--^;') }) 124 exec('norm! :\rh') 125 screen:expect({ any = vim.pesc('-^-;') }) 126 local expected = screen:get_snapshot() 127 do 128 (func or exec)('TOhtml') 129 end 130 exec('only') 131 html_syntax_match() 132 html_to_extmarks() 133 exec('norm! gg0f;') 134 screen:expect({ any = vim.pesc('--^;') }) 135 exec('norm! :\rh') 136 screen:expect({ grid = expected.grid, attr_ids = expected.attr_ids }) 137 end 138 139 ---@param guifont boolean 140 local function test_generates_html(guifont, expect_font) 141 insert([[line]]) 142 exec('set termguicolors') 143 local bg = fn.synIDattr(fn.hlID('Normal'), 'bg#', 'gui') 144 local fg = fn.synIDattr(fn.hlID('Normal'), 'fg#', 'gui') 145 local tmpfile = t.tmpname() 146 147 exec_lua( 148 [[ 149 local guifont, outfile = ... 150 local html = (guifont 151 and require('tohtml').tohtml(0,{title="title"}) 152 or require('tohtml').tohtml(0,{title="title",font={ "dumyfont","anotherfont" }})) 153 vim.fn.writefile(html, outfile) 154 vim.cmd.split(outfile) 155 ]], 156 guifont, 157 tmpfile 158 ) 159 160 local out_file = api.nvim_buf_get_name(api.nvim_get_current_buf()) 161 eq({ 162 '<!-- vim: set nomodeline: -->', 163 '<!DOCTYPE html>', 164 '<html>', 165 '<head>', 166 '<meta charset="UTF-8">', 167 '<title>title</title>', 168 ('<meta name="colorscheme" content="%s"></meta>'):format(api.nvim_get_var('colors_name')), 169 '<style>', 170 ('* {font-family: %s,monospace}'):format(expect_font), 171 ('body {background-color: %s; color: %s}'):format(bg, fg), 172 '</style>', 173 '</head>', 174 '<body style="display: flex">', 175 '<pre>', 176 'line', 177 '', 178 '</pre>', 179 '</body>', 180 '</html>', 181 }, fn.readfile(out_file)) 182 end 183 184 describe(':TOhtml', function() 185 --- @type test.functional.ui.screen 186 local screen 187 before_each(function() 188 clear({ args = { '--clean' } }) 189 screen = Screen.new(80, 80, { term_name = 'xterm' }) 190 exec('colorscheme default') 191 end) 192 193 it('generates html with given font', function() 194 test_generates_html(false, '"dumyfont","anotherfont"') 195 end) 196 197 it("generates html, respects 'guifont'", function() 198 exec_lua [[vim.o.guifont='Font,Escape\\,comma, Ignore space after comma']] 199 test_generates_html(true, '"Font","Escape,comma","Ignore space after comma"') 200 end) 201 202 it('generates html from range', function() 203 insert([[ 204 line1 205 line2 206 line3 207 ]]) 208 local ns = api.nvim_create_namespace '' 209 api.nvim_buf_set_extmark(0, ns, 0, 0, { end_col = 1, end_row = 1, hl_group = 'Visual' }) 210 exec('set termguicolors') 211 local bg = fn.synIDattr(fn.hlID('Normal'), 'bg#', 'gui') 212 local fg = fn.synIDattr(fn.hlID('Normal'), 'fg#', 'gui') 213 exec_lua [[vim.o.guifont='Courier New' ]] 214 n.command('2,2TOhtml') 215 local out_file = api.nvim_buf_get_name(api.nvim_get_current_buf()) 216 eq({ 217 '<!-- vim: set nomodeline: -->', 218 '<!DOCTYPE html>', 219 '<html>', 220 '<head>', 221 '<meta charset="UTF-8">', 222 '<title></title>', 223 ('<meta name="colorscheme" content="%s"></meta>'):format(api.nvim_get_var('colors_name')), 224 '<style>', 225 ('* {font-family: "%s",monospace}'):format(n.eval('&guifont')), 226 ('body {background-color: %s; color: %s}'):format(bg, fg), 227 '.Visual {background-color: #9b9ea4}', 228 '</style>', 229 '</head>', 230 '<body style="display: flex">', 231 '<pre><span class="Visual">', 232 'l</span>ine2', 233 '', 234 '</pre>', 235 '</body>', 236 '</html>', 237 }, fn.readfile(out_file)) 238 end) 239 240 it('generates highlight attributes', function() 241 --Make sure to uncomment the attribute in `html_syntax_match()` 242 exec('hi LINE guisp=#00ff00 gui=' .. table.concat({ 243 'bold', 244 'underline', 245 'italic', 246 'strikethrough', 247 }, ',')) 248 exec('hi UNDERCURL gui=undercurl') 249 exec('syn keyword LINE line') 250 exec('syn keyword UNDERCURL undercurl') 251 insert('line\nundercurl') 252 run_tohtml_and_assert(screen) 253 end) 254 255 it('syntax', function() 256 insert [[ 257 function main() 258 print("hello world") 259 end 260 ]] 261 exec('set termguicolors') 262 exec('syntax enable') 263 exec('setf lua') 264 exec_lua('vim.treesitter.stop()') -- Ensure that legacy syntax (not just TS) is tested. 265 run_tohtml_and_assert(screen) 266 end) 267 268 it('diff', function() 269 exec('set diffopt=') 270 insert [[ 271 diffadd 272 nochage 273 diffchange1 274 ]] 275 exec('new') 276 insert [[ 277 nochage 278 diffchange2 279 diffremove 280 ]] 281 exec('set diff') 282 exec('close') 283 exec('set diff') 284 run_tohtml_and_assert(screen) 285 end) 286 287 it('treesitter', function() 288 insert [[ 289 function main() 290 print("hello world") 291 end 292 ]] 293 exec('setf lua') 294 exec_lua('vim.treesitter.start()') 295 run_tohtml_and_assert(screen) 296 end) 297 298 it('matchadd', function() 299 insert [[ 300 line 301 ]] 302 fn.matchadd('Visual', 'line') 303 run_tohtml_and_assert(screen) 304 end) 305 306 describe('conceallevel', function() 307 local function run(level) 308 insert([[ 309 line0 310 line1 311 line2 312 line3 313 ]]) 314 local ns = api.nvim_create_namespace '' 315 fn.matchadd('Conceal', 'line1', 3, 5, { conceal = 'a' }) 316 api.nvim_buf_set_extmark(0, ns, 2, 0, { conceal = 'a', end_col = 5 }) 317 exec(':syntax match Conceal "line3" conceal cchar=a') 318 exec('set conceallevel=' .. level) 319 run_tohtml_and_assert(screen) 320 end 321 it('conceallevel=0', function() 322 run(0) 323 end) 324 it('conceallevel=1', function() 325 run(1) 326 end) 327 it('conceallevel=2', function() 328 run(2) 329 end) 330 it('conceallevel=3', function() 331 run(3) 332 end) 333 end) 334 335 describe('extmarks', function() 336 it('virt_text', function() 337 insert [[ 338 line1 339 line2 340 line3 341 line4 342 ]] 343 local ns = api.nvim_create_namespace '' 344 api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = { { 'foo' } } }) 345 api.nvim_buf_set_extmark( 346 0, 347 ns, 348 1, 349 0, 350 { virt_text = { { 'foo' } }, virt_text_pos = 'overlay' } 351 ) 352 api.nvim_buf_set_extmark( 353 0, 354 ns, 355 2, 356 0, 357 { virt_text = { { 'fo┊o', { 'Conceal', 'Comment' } } }, virt_text_pos = 'inline' } 358 ) 359 --api.nvim_buf_set_extmark(0,ns,3,0,{virt_text={{'foo'}},virt_text_pos='right_align'}) 360 run_tohtml_and_assert(screen) 361 end) 362 it('highlight', function() 363 insert [[ 364 line1 365 ]] 366 local ns = api.nvim_create_namespace '' 367 api.nvim_buf_set_extmark(0, ns, 0, 0, { end_col = 2, hl_group = 'Visual' }) 368 run_tohtml_and_assert(screen) 369 end) 370 it('virt_line', function() 371 insert [[ 372 line1 373 line2 374 ]] 375 local ns = api.nvim_create_namespace '' 376 api.nvim_buf_set_extmark(0, ns, 1, 0, { end_col = 2, virt_lines = { { { 'foo' } } } }) 377 run_tohtml_and_assert(screen) 378 end) 379 end) 380 381 it('listchars', function() 382 exec('setlocal list') 383 exec( 384 'setlocal listchars=eol:$,tab:<->,space:-,multispace:++,lead:_,leadmultispace:##,trail:&,nbsp:%' 385 ) 386 fn.setline(1, '\tfoo\t') 387 fn.setline(2, ' foo foo ') 388 fn.setline(3, ' foo foo ') 389 fn.setline(4, 'foo\194\160 \226\128\175foo') 390 run_tohtml_and_assert(screen) 391 exec('new|only') 392 fn.setline(1, '\tfoo\t') 393 exec('setlocal list') 394 exec('setlocal listchars=tab:a-') 395 run_tohtml_and_assert(screen) 396 end) 397 398 it('folds', function() 399 insert([[ 400 line1 401 line2 402 ]]) 403 exec('set foldtext=foldtext()') 404 exec('%fo') 405 run_tohtml_and_assert(screen) 406 end) 407 408 it('statuscol', function() 409 local function run() 410 local buf = api.nvim_get_current_buf() 411 run_tohtml_and_assert(screen, function() 412 exec_lua(function() 413 local outfile = vim.fn.tempname() .. '.html' 414 local html = require('tohtml').tohtml(0, { number_lines = true }) 415 vim.fn.writefile(html, outfile) 416 vim.cmd.split(outfile) 417 end) 418 end) 419 api.nvim_set_current_buf(buf) 420 end 421 insert([[ 422 line1 423 line2 424 ]]) 425 exec('setlocal relativenumber') 426 run() 427 exec('setlocal norelativenumber') 428 exec('setlocal number') 429 run() 430 exec('setlocal relativenumber') 431 run() 432 exec('setlocal signcolumn=yes:2') 433 run() 434 exec('setlocal foldcolumn=2') 435 run() 436 exec('setlocal norelativenumber') 437 run() 438 exec('setlocal signcolumn=no') 439 run() 440 end) 441 end)