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* 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 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 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. 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 • {width}? (`integer`, default: 'textwidth' if non-zero or
window width otherwise) Width used for items which are window width otherwise) Width used for items which are
either right aligned or repeat a character infinitely. either right aligned or repeat a character infinitely.
• {range}? (`integer[]`, default: entire buffer) Range of
rows to use.
Return: ~ Return: ~
(`string[]`) (`string[]`)

View File

@ -1,6 +1,6 @@
--- @brief --- @brief
---<pre>help ---<pre>help
---:TOhtml {file} *:TOhtml* ---:[range]TOhtml {file} *:TOhtml*
---Converts the buffer shown in the current window to HTML, opens the generated ---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 ---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. ---given, a temporary file (created by |tempname()|) is used.
@ -40,7 +40,8 @@
--- @field winid integer --- @field winid integer
--- @field bufnr integer --- @field bufnr integer
--- @field width integer --- @field width integer
--- @field buflen integer --- @field start integer
--- @field end_ integer
--- @class (private) vim.tohtml.styletable --- @class (private) vim.tohtml.styletable
--- @field [integer] vim.tohtml.line (integer: (1-index, exclusive)) --- @field [integer] vim.tohtml.line (integer: (1-index, exclusive))
@ -393,7 +394,7 @@ end
--- @param state vim.tohtml.state --- @param state vim.tohtml.state
local function styletable_syntax(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_id = 0
local prev_col = nil local prev_col = nil
for col = 1, #vim.fn.getline(row) + 1 do for col = 1, #vim.fn.getline(row) + 1 do
@ -413,7 +414,7 @@ end
--- @param state vim.tohtml.state --- @param state vim.tohtml.state
local function styletable_diff(state) local function styletable_diff(state)
local styletable = state.style local styletable = state.style
for row = 1, state.buflen do for row = state.start, state.end_ do
local style_line = styletable[row] local style_line = styletable[row]
local filler = vim.fn.diff_filler(row) local filler = vim.fn.diff_filler(row)
if filler ~= 0 then if filler ~= 0 then
@ -423,7 +424,7 @@ local function styletable_diff(state)
{ { fill:rep(state.width), register_hl(state, 'DiffDelete') } } { { fill:rep(state.width), register_hl(state, 'DiffDelete') } }
) )
end end
if row == state.buflen + 1 then if row == state.end_ + 1 then
break break
end end
local prev_id = 0 local prev_id = 0
@ -465,7 +466,9 @@ local function styletable_treesitter(state)
if not query then if not query then
return return
end 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() local srow, scol, erow, ecol = node:range()
--- @diagnostic disable-next-line: invisible --- @diagnostic disable-next-line: invisible
local c = q._query.captures[capture] local c = q._query.captures[capture]
@ -519,7 +522,7 @@ local function _styletable_extmarks_virt_text(state, extmark, namespaces)
--- @type integer,integer --- @type integer,integer
local row, col = extmark[2], extmark[3] local row, col = extmark[2], extmark[3]
if if
row < state.buflen row < vim.api.nvim_buf_line_count(state.bufnr)
and ( and (
extmark[4].virt_text_pos == 'inline' extmark[4].virt_text_pos == 'inline'
or extmark[4].virt_text_pos == 'eol' or extmark[4].virt_text_pos == 'eol'
@ -628,7 +631,7 @@ end
local function styletable_folds(state) local function styletable_folds(state)
local styletable = state.style local styletable = state.style
local has_folded = false local has_folded = false
for row = 1, state.buflen do for row = state.start, state.end_ do
if vim.fn.foldclosed(row) > 0 then if vim.fn.foldclosed(row) > 0 then
has_folded = true has_folded = true
styletable[row].hide = true styletable[row].hide = true
@ -650,7 +653,7 @@ end
local function styletable_conceal(state) local function styletable_conceal(state)
local bufnr = state.bufnr local bufnr = state.bufnr
vim._with({ buf = bufnr }, function() vim._with({ buf = bufnr }, function()
for row = 1, state.buflen do for row = state.start, state.end_ do
--- @type table<integer,[integer,integer,string]> --- @type table<integer,[integer,integer,string]>
local conceals = {} local conceals = {}
local line_len_exclusive = #vim.fn.getline(row) + 1 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 max = tonumber(foldcolumn:match('^%w-:(%d)')) or 1
local maxfold = 0 local maxfold = 0
vim._with({ buf = state.bufnr }, function() 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) local foldlevel = vim.fn.foldlevel(row)
if foldlevel > maxfold then if foldlevel > maxfold then
maxfold = foldlevel maxfold = foldlevel
@ -783,7 +786,7 @@ local function styletable_statuscolumn(state)
--- @type table<integer,any> --- @type table<integer,any>
local statuses = {} local statuses = {}
for row = 1, state.buflen do for row = state.start, state.end_ do
local status = vim.api.nvim_eval_statusline( local status = vim.api.nvim_eval_statusline(
statuscolumn, statuscolumn,
{ winid = state.winid, use_statuscol_lnum = row, highlights = true } { winid = state.winid, use_statuscol_lnum = row, highlights = true }
@ -833,7 +836,7 @@ local function styletable_listchars(state)
}) })
if listchars.eol then if listchars.eol then
for row = 1, state.buflen do for row = state.start, state.end_ do
local style_line = state.style[row] local style_line = state.style[row]
style_line_insert_overlay_char( style_line_insert_overlay_char(
style_line, style_line,
@ -1127,16 +1130,22 @@ end
local function extend_pre(out, state) local function extend_pre(out, state)
local styletable = state.style local styletable = state.style
table.insert(out, '<pre>') table.insert(out, '<pre>')
local out_start = #out
local hide_count = 0 local hide_count = 0
--- @type integer[] --- @type integer[]
local stack = {} local stack = {}
local before = ''
local after = ''
local function loop(row) local function loop(row)
local inside = row <= state.end_ and row >= state.start
local style_line = styletable[row] local style_line = styletable[row]
if style_line.hide and (styletable[row - 1] or {}).hide then if style_line.hide and (styletable[row - 1] or {}).hide then
return return
end end
_extend_virt_lines(out, state, row) if inside then
_extend_virt_lines(out, state, row)
end
--Possible improvement (altermo): --Possible improvement (altermo):
--Instead of looping over all the buffer characters per line, --Instead of looping over all the buffer characters per line,
--why not loop over all the style_line cells, --why not loop over all the style_line cells,
@ -1146,7 +1155,9 @@ local function extend_pre(out, state)
end end
local line = vim.api.nvim_buf_get_lines(state.bufnr, row - 1, row, false)[1] or '' local line = vim.api.nvim_buf_get_lines(state.bufnr, row - 1, row, false)[1] or ''
local s = '' 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 local true_line_len = #line + 1
for k in pairs(style_line) do for k in pairs(style_line) do
if type(k) == 'number' and k > true_line_len then if type(k) == 'number' and k > true_line_len then
@ -1193,7 +1204,7 @@ local function extend_pre(out, state)
end end
end end
if cell[3] then if cell[3] and inside then
s = s .. _virt_text_to_html(state, cell) s = s .. _virt_text_to_html(state, cell)
end end
@ -1204,7 +1215,7 @@ local function extend_pre(out, state)
break break
end end
if hide_count == 0 then if hide_count == 0 and inside then
s = s s = s
.. _char_to_html( .. _char_to_html(
state, state,
@ -1213,12 +1224,20 @@ local function extend_pre(out, state)
) )
end end
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 end
for row = 1, state.buflen + 1 do for row = 1, vim.api.nvim_buf_line_count(state.bufnr) + 1 do
loop(row) loop(row)
end end
out[out_start] = out[out_start] .. before
out[#out] = out[#out] .. after
assert(#stack == 0, 'an open HTML tag was never closed') assert(#stack == 0, 'an open HTML tag was never closed')
table.insert(out, '</pre>') table.insert(out, '</pre>')
end end
@ -1250,6 +1269,7 @@ local function global_state_to_state(winid, global_state)
if not width or width < 1 then if not width or width < 1 then
width = vim.api.nvim_win_get_width(winid) width = vim.api.nvim_win_get_width(winid)
end end
local range = opt.range or { 1, vim.api.nvim_buf_line_count(bufnr) }
local state = setmetatable({ local state = setmetatable({
winid = winid == 0 and vim.api.nvim_get_current_win() or winid, winid = winid == 0 and vim.api.nvim_get_current_win() or winid,
opt = vim.wo[winid], opt = vim.wo[winid],
@ -1257,7 +1277,8 @@ local function global_state_to_state(winid, global_state)
bufnr = bufnr, bufnr = bufnr,
tabstop = (' '):rep(vim.bo[bufnr].tabstop), tabstop = (' '):rep(vim.bo[bufnr].tabstop),
width = width, width = width,
buflen = vim.api.nvim_buf_line_count(bufnr), start = range[1],
end_ = range[2],
}, { __index = global_state }) }, { __index = global_state })
return state --[[@as vim.tohtml.state]] return state --[[@as vim.tohtml.state]]
end end
@ -1316,35 +1337,22 @@ local function state_generate_style(state)
end) end)
end end
--- @param winid integer[]|integer --- @param winid integer
--- @param opt? vim.tohtml.opt --- @param opt? vim.tohtml.opt
--- @return string[] --- @return string[]
local function win_to_html(winid, opt) 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 {} opt = opt or {}
local title = table.concat( local title = vim.api.nvim_buf_get_name(vim.api.nvim_win_get_buf(winid))
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) local global_state = opt_to_global_state(opt, title)
--- @type vim.tohtml.state[] local state = global_state_to_state(winid, global_state)
local states = {} state_generate_style(state)
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 = {} local html = {}
extend_html(html, function() extend_html(html, function()
extend_head(html, global_state) extend_head(html, global_state)
extend_body(html, function() extend_body(html, function()
for _, state in ipairs(states) do extend_pre(html, state)
extend_pre(html, state)
end
end) end)
end) end)
return html return html
@ -1371,6 +1379,10 @@ local M = {}
--- infinitely. --- infinitely.
--- (default: 'textwidth' if non-zero or window width otherwise) --- (default: 'textwidth' if non-zero or window width otherwise)
--- @field width? integer --- @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. --- 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 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) vim.api.nvim_create_user_command('TOhtml', function(args)
local outfile = args.args ~= '' and args.args or vim.fn.tempname() .. '.html' 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.fn.writefile(html, outfile)
vim.cmd.split(outfile) vim.cmd.split(outfile)
vim.bo.filetype = 'html' 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)) }, fn.readfile(out_file))
end) 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() it('highlight attributes generated', function()
--Make sure to uncomment the attribute in `html_syntax_match()` --Make sure to uncomment the attribute in `html_syntax_match()`
exec('hi LINE gui=' .. table.concat({ exec('hi LINE gui=' .. table.concat({