fix(tohtml): support ranges again

This commit is contained in:
altermo 2024-07-11 18:16:51 +02:00 committed by Christian Clason
parent a5d4e3467d
commit 118ae7e5ed
4 changed files with 94 additions and 42 deletions

View File

@ -4412,7 +4412,7 @@ vim.text.hexencode({str}) *vim.text.hexencode()*
Lua module: tohtml *vim.tohtml*
:TOhtml {file} *:TOhtml*
:[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.
@ -4434,6 +4434,8 @@ tohtml.tohtml({winid}, {opt}) *tohtml.tohtml.tohtml()*
• {width}? (`integer`, default: 'textwidth' if non-zero or
window width otherwise) Width used for items which are
either right aligned or repeat a character infinitely.
• {range}? (`integer[]`, default: entire buffer) Range of
rows to use.
Return: ~
(`string[]`)

View File

@ -1,6 +1,6 @@
--- @brief
---<pre>help
---:TOhtml {file} *:TOhtml*
---:[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.
@ -40,7 +40,8 @@
--- @field winid integer
--- @field bufnr integer
--- @field width integer
--- @field buflen integer
--- @field start integer
--- @field end_ integer
--- @class (private) vim.tohtml.styletable
--- @field [integer] vim.tohtml.line (integer: (1-index, exclusive))
@ -393,7 +394,7 @@ end
--- @param state vim.tohtml.state
local function styletable_syntax(state)
for row = 1, state.buflen do
for row = state.start, state.end_ do
local prev_id = 0
local prev_col = nil
for col = 1, #vim.fn.getline(row) + 1 do
@ -413,7 +414,7 @@ end
--- @param state vim.tohtml.state
local function styletable_diff(state)
local styletable = state.style
for row = 1, state.buflen do
for row = state.start, state.end_ do
local style_line = styletable[row]
local filler = vim.fn.diff_filler(row)
if filler ~= 0 then
@ -423,7 +424,7 @@ local function styletable_diff(state)
{ { fill:rep(state.width), register_hl(state, 'DiffDelete') } }
)
end
if row == state.buflen + 1 then
if row == state.end_ + 1 then
break
end
local prev_id = 0
@ -465,7 +466,9 @@ local function styletable_treesitter(state)
if not query then
return
end
for capture, node, metadata in query:iter_captures(root, buf_highlighter.bufnr, 0, state.buflen) do
for capture, node, metadata in
query:iter_captures(root, buf_highlighter.bufnr, state.start - 1, state.end_)
do
local srow, scol, erow, ecol = node:range()
--- @diagnostic disable-next-line: invisible
local c = q._query.captures[capture]
@ -519,7 +522,7 @@ local function _styletable_extmarks_virt_text(state, extmark, namespaces)
--- @type integer,integer
local row, col = extmark[2], extmark[3]
if
row < state.buflen
row < vim.api.nvim_buf_line_count(state.bufnr)
and (
extmark[4].virt_text_pos == 'inline'
or extmark[4].virt_text_pos == 'eol'
@ -628,7 +631,7 @@ end
local function styletable_folds(state)
local styletable = state.style
local has_folded = false
for row = 1, state.buflen do
for row = state.start, state.end_ do
if vim.fn.foldclosed(row) > 0 then
has_folded = true
styletable[row].hide = true
@ -650,7 +653,7 @@ end
local function styletable_conceal(state)
local bufnr = state.bufnr
vim._with({ buf = bufnr }, function()
for row = 1, state.buflen do
for row = state.start, state.end_ do
--- @type table<integer,[integer,integer,string]>
local conceals = {}
local line_len_exclusive = #vim.fn.getline(row) + 1
@ -768,7 +771,7 @@ local function styletable_statuscolumn(state)
local max = tonumber(foldcolumn:match('^%w-:(%d)')) or 1
local maxfold = 0
vim._with({ buf = state.bufnr }, function()
for row = 1, vim.api.nvim_buf_line_count(state.bufnr) do
for row = state.start, state.end_ do
local foldlevel = vim.fn.foldlevel(row)
if foldlevel > maxfold then
maxfold = foldlevel
@ -783,7 +786,7 @@ local function styletable_statuscolumn(state)
--- @type table<integer,any>
local statuses = {}
for row = 1, state.buflen do
for row = state.start, state.end_ do
local status = vim.api.nvim_eval_statusline(
statuscolumn,
{ winid = state.winid, use_statuscol_lnum = row, highlights = true }
@ -833,7 +836,7 @@ local function styletable_listchars(state)
})
if listchars.eol then
for row = 1, state.buflen do
for row = state.start, state.end_ do
local style_line = state.style[row]
style_line_insert_overlay_char(
style_line,
@ -1127,16 +1130,22 @@ end
local function extend_pre(out, state)
local styletable = state.style
table.insert(out, '<pre>')
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
_extend_virt_lines(out, state, row)
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,
@ -1146,7 +1155,9 @@ local function extend_pre(out, state)
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)
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
@ -1193,7 +1204,7 @@ local function extend_pre(out, state)
end
end
if cell[3] then
if cell[3] and inside then
s = s .. _virt_text_to_html(state, cell)
end
@ -1204,7 +1215,7 @@ local function extend_pre(out, state)
break
end
if hide_count == 0 then
if hide_count == 0 and inside then
s = s
.. _char_to_html(
state,
@ -1213,12 +1224,20 @@ local function extend_pre(out, state)
)
end
end
table.insert(out, s)
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, state.buflen + 1 do
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, '</pre>')
end
@ -1250,6 +1269,7 @@ local function global_state_to_state(winid, global_state)
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],
@ -1257,7 +1277,8 @@ local function global_state_to_state(winid, global_state)
bufnr = bufnr,
tabstop = (' '):rep(vim.bo[bufnr].tabstop),
width = width,
buflen = vim.api.nvim_buf_line_count(bufnr),
start = range[1],
end_ = range[2],
}, { __index = global_state })
return state --[[@as vim.tohtml.state]]
end
@ -1316,35 +1337,22 @@ local function state_generate_style(state)
end)
end
--- @param winid integer[]|integer
--- @param winid 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 title = vim.api.nvim_buf_get_name(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 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()
for _, state in ipairs(states) do
extend_pre(html, state)
end
extend_pre(html, state)
end)
end)
return html
@ -1371,6 +1379,10 @@ local M = {}
--- 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)

View File

@ -5,8 +5,8 @@ vim.g.loaded_2html_plugin = true
vim.api.nvim_create_user_command('TOhtml', function(args)
local outfile = args.args ~= '' and args.args or vim.fn.tempname() .. '.html'
local html = require('tohtml').tohtml()
local html = require('tohtml').tohtml(0, { range = { args.line1, args.line2 } })
vim.fn.writefile(html, outfile)
vim.cmd.split(outfile)
vim.bo.filetype = 'html'
end, { bar = true, nargs = '?' })
end, { bar = true, nargs = '?', range = '%' })

View File

@ -176,6 +176,44 @@ describe(':TOhtml', function()
}, fn.readfile(out_file))
end)
it('expected internal html generated from range', function()
insert([[
line1
line2
line3
]])
local ns = api.nvim_create_namespace ''
api.nvim_buf_set_extmark(0, ns, 0, 0, { end_col = 1, end_row = 1, hl_group = 'Visual' })
exec('set termguicolors')
local bg = fn.synIDattr(fn.hlID('Normal'), 'bg#', 'gui')
local fg = fn.synIDattr(fn.hlID('Normal'), 'fg#', 'gui')
exec_lua [[
local html = vim.cmd'2,2TOhtml'
]]
local out_file = api.nvim_buf_get_name(api.nvim_get_current_buf())
eq({
'<!DOCTYPE html>',
'<html>',
'<head>',
'<meta charset="UTF-8">',
'<title></title>',
('<meta name="colorscheme" content="%s"></meta>'):format(api.nvim_get_var('colors_name')),
'<style>',
'* {font-family: monospace}',
('body {background-color: %s; color: %s}'):format(bg, fg),
'.Visual {background-color: #9b9ea4}',
'</style>',
'</head>',
'<body style="display: flex">',
'<pre><span class="Visual">',
'l</span>ine2',
'',
'</pre>',
'</body>',
'</html>',
}, fn.readfile(out_file))
end)
it('highlight attributes generated', function()
--Make sure to uncomment the attribute in `html_syntax_match()`
exec('hi LINE gui=' .. table.concat({