neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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/&quot;/"/g]]
     96  exec [[silent! %s/&amp;/\&/g]]
     97  exec [[silent! %s/&gt;/>/g]]
     98  exec [[silent! %s/&lt;/</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)