From 118ae7e5ed6cfab6a49ec70c21da2b850161289c Mon Sep 17 00:00:00 2001 From: altermo <107814000+altermo@users.noreply.github.com> Date: Thu, 11 Jul 2024 18:16:51 +0200 Subject: fix(tohtml): support ranges again --- runtime/doc/lua.txt | 4 +- runtime/lua/tohtml.lua | 90 +++++++++++++++++++--------------- runtime/plugin/tohtml.lua | 4 +- test/functional/plugin/tohtml_spec.lua | 38 ++++++++++++++ 4 files changed, 94 insertions(+), 42 deletions(-) diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 57796a4716..c1ffc41c24 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -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[]`) diff --git a/runtime/lua/tohtml.lua b/runtime/lua/tohtml.lua index 7bb4a26ad6..a4b5a741bc 100644 --- a/runtime/lua/tohtml.lua +++ b/runtime/lua/tohtml.lua @@ -1,6 +1,6 @@ --- @brief ---
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
       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
   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, '
')
+  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, '
') 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) diff --git a/runtime/plugin/tohtml.lua b/runtime/plugin/tohtml.lua index 79f2794a40..0cb4562938 100644 --- a/runtime/plugin/tohtml.lua +++ b/runtime/plugin/tohtml.lua @@ -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 = '%' }) diff --git a/test/functional/plugin/tohtml_spec.lua b/test/functional/plugin/tohtml_spec.lua index be5bada901..827db8c0f3 100644 --- a/test/functional/plugin/tohtml_spec.lua +++ b/test/functional/plugin/tohtml_spec.lua @@ -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({ + '', + '', + '', + '', + '', + (''):format(api.nvim_get_var('colors_name')), + '', + '', + '', + '
',
+      'line2',
+      '',
+      '
', + '', + '', + }, 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({ -- cgit