--- @brief ---
help ---:[range]TOhtml {file} *:TOhtml* ---Converts the buffer shown in the current window to HTML, opens the generated ---HTML in a new split window, and saves its contents to {file}. If {file} is not ---given, a temporary file (created by |tempname()|) is used. ----- The HTML conversion script is different from Vim's one. If you want to use -- Vim's TOhtml converter, download it from the vim GitHub repo. -- Here are the Vim files related to this functionality: -- - https://github.com/vim/vim/blob/master/runtime/syntax/2html.vim -- - https://github.com/vim/vim/blob/master/runtime/autoload/tohtml.vim -- - https://github.com/vim/vim/blob/master/runtime/plugin/tohtml.vim -- -- Main differences between this and the vim version: -- - No "ignore some visual thing" settings (just set the right Vim option) -- - No support for legacy web engines -- - No support for legacy encoding (supports only UTF-8) -- - No interactive webpage -- - No specifying the internal HTML (no XHTML, no use_css=false) -- - No multiwindow diffs -- - No ranges -- -- Remarks: -- - Not all visuals are supported, so it may differ. --- @class (private) vim.tohtml.state.global --- @field background string --- @field foreground string --- @field title string|false --- @field font string --- @field highlights_name table
') local out_start = #out local hide_count = 0 --- @type integer[] local stack = {} local before = '' local after = '' local function loop(row) local inside = row <= state.end_ and row >= state.start local style_line = styletable[row] if style_line.hide and (styletable[row - 1] or {}).hide then return end if inside then _extend_virt_lines(out, state, row) end --Possible improvement (altermo): --Instead of looping over all the buffer characters per line, --why not loop over all the style_line cells, --and then calculating the amount of text. if style_line.hide then return end local line = vim.api.nvim_buf_get_lines(state.bufnr, row - 1, row, false)[1] or '' local s = '' if inside then s = s .. _pre_text_to_html(state, row) end local true_line_len = #line + 1 for k in pairs(style_line) do if type(k) == 'number' and k > true_line_len then true_line_len = k end end for col = 1, true_line_len do local cell = style_line[col] --- @type table? local char if cell then for i = #cell[2], 1, -1 do local hlid = cell[2][i] if hlid < 0 then if hlid == HIDE_ID then hide_count = hide_count - 1 end else --- @type integer? local index for idx = #stack, 1, -1 do s = s .. (name_to_closetag(state.highlights_name[stack[idx]])) if stack[idx] == hlid then index = idx break end end assert(index, 'a coles tag which has no corresponding open tag') for idx = index + 1, #stack do s = s .. (name_to_tag(state.highlights_name[stack[idx]])) end table.remove(stack, index) end end for _, hlid in ipairs(cell[1]) do if hlid < 0 then if hlid == HIDE_ID then hide_count = hide_count + 1 end else table.insert(stack, hlid) s = s .. (name_to_tag(state.highlights_name[hlid])) end end if cell[3] and inside then s = s .. _virt_text_to_html(state, cell) end char = cell[4][#cell[4]] end if col == true_line_len and not char then break end if hide_count == 0 and inside then s = s .. _char_to_html( state, char or { vim.api.nvim_buf_get_text(state.bufnr, row - 1, col - 1, row - 1, col, {})[1] } ) end end if row > state.end_ + 1 then after = after .. s elseif row < state.start then before = s .. before else table.insert(out, s) end end for row = 1, vim.api.nvim_buf_line_count(state.bufnr) + 1 do loop(row) end out[out_start] = out[out_start] .. before out[#out] = out[#out] .. after assert(#stack == 0, 'an open HTML tag was never closed') table.insert(out, '') end --- @param out string[] --- @param fn fun() local function extend_body(out, fn) table.insert(out, '') fn() table.insert(out, '') end --- @param out string[] --- @param fn fun() local function extend_html(out, fn) table.insert(out, '') table.insert(out, '') fn() table.insert(out, '') end --- @param winid integer --- @param global_state vim.tohtml.state.global --- @return vim.tohtml.state local function global_state_to_state(winid, global_state) local bufnr = vim.api.nvim_win_get_buf(winid) local opt = global_state.conf local width = opt.width or vim.bo[bufnr].textwidth if not width or width < 1 then width = vim.api.nvim_win_get_width(winid) end local range = opt.range or { 1, vim.api.nvim_buf_line_count(bufnr) } local state = setmetatable({ winid = winid == 0 and vim.api.nvim_get_current_win() or winid, opt = vim.wo[winid], style = generate_styletable(bufnr), bufnr = bufnr, tabstop = (' '):rep(vim.bo[bufnr].tabstop), width = width, start = range[1], end_ = range[2], }, { __index = global_state }) return state --[[@as vim.tohtml.state]] end --- @param opt vim.tohtml.opt --- @param title? string --- @return vim.tohtml.state.global local function opt_to_global_state(opt, title) local fonts = {} if opt.font then fonts = type(opt.font) == 'string' and { opt.font } or opt.font --[[@as (string[])]] elseif vim.o.guifont:match('^[^:]+') then table.insert(fonts, vim.o.guifont:match('^[^:]+')) end table.insert(fonts, 'monospace') --- @type vim.tohtml.state.global local state = { background = get_background_color(), foreground = get_foreground_color(), title = opt.title or title or false, font = table.concat(fonts, ','), highlights_name = {}, conf = opt, } return state end --- @type fun(state: vim.tohtml.state)[] local styletable_funcs = { styletable_syntax, styletable_diff, styletable_treesitter, styletable_match, styletable_extmarks, styletable_conceal, styletable_listchars, styletable_folds, styletable_statuscolumn, } --- @param state vim.tohtml.state local function state_generate_style(state) vim._with({ win = state.winid }, function() for _, fn in ipairs(styletable_funcs) do --- @type string? local cond if type(fn) == 'table' then cond = fn[2] --[[@as string]] --- @type function fn = fn[1] end if not cond or cond(state) then fn(state) end end end) end --- @param winid integer --- @param opt? vim.tohtml.opt --- @return string[] local function win_to_html(winid, opt) opt = opt or {} local title = vim.api.nvim_buf_get_name(vim.api.nvim_win_get_buf(winid)) local global_state = opt_to_global_state(opt, title) local state = global_state_to_state(winid, global_state) state_generate_style(state) local html = {} extend_html(html, function() extend_head(html, global_state) extend_body(html, function() extend_pre(html, state) end) end) return html end local M = {} --- @class vim.tohtml.opt --- @inlinedoc --- --- Title tag to set in the generated HTML code. --- (default: buffer name) --- @field title? string|false --- --- Show line numbers. --- (default: `false`) --- @field number_lines? boolean --- --- Fonts to use. --- (default: `guifont`) --- @field font? string[]|string --- --- Width used for items which are either right aligned or repeat a character --- infinitely. --- (default: 'textwidth' if non-zero or window width otherwise) --- @field width? integer --- --- Range of rows to use. --- (default: entire buffer) --- @field range? integer[] --- Converts the buffer shown in the window {winid} to HTML and returns the output as a list of string. --- @param winid? integer Window to convert (defaults to current window) --- @param opt? vim.tohtml.opt Optional parameters. --- @return string[] function M.tohtml(winid, opt) return win_to_html(winid or 0, opt) end return M