--- @brief ---
help
---: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 hide_count = 0
--- @type integer[]
local stack = {}
local function loop(row)
local style_line = styletable[row]
if style_line.hide and (styletable[row - 1] or {}).hide then
return
end
_extend_virt_lines(out, state, row)
--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 = ''
s = s .. _pre_text_to_html(state, row)
for col = 1, #line + 1 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] then
s = s .. _virt_text_to_html(state, cell)
end
char = cell[4][#cell[4]]
end
if col == #line + 1 and not char then
break
end
if hide_count == 0 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
table.insert(out, s)
end
for row = 1, state.buflen + 1 do
loop(row)
end
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 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,
buflen = vim.api.nvim_buf_line_count(bufnr),
}, { __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.api.nvim_win_call(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[]|integer
--- @param opt? vim.tohtml.opt
--- @return string[]
local function win_to_html(winid, opt)
if type(winid) == 'number' then
winid = { winid }
end
--- @cast winid integer[]
assert(#winid > 0, 'no window specified')
opt = opt or {}
local title = table.concat(
vim.tbl_map(vim.api.nvim_buf_get_name, vim.tbl_map(vim.api.nvim_win_get_buf, winid)),
','
)
local global_state = opt_to_global_state(opt, title)
--- @type vim.tohtml.state[]
local states = {}
for _, i in ipairs(winid) do
local state = global_state_to_state(i, global_state)
state_generate_style(state)
table.insert(states, state)
end
local html = {}
extend_html(html, function()
extend_head(html, global_state)
extend_body(html, function()
for _, state in ipairs(states) do
extend_pre(html, state)
end
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
--- 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