mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	fix(tohtml): support ranges again
This commit is contained in:
		 altermo
					altermo
				
			
				
					committed by
					
						 Christian Clason
						Christian Clason
					
				
			
			
				
	
			
			
			 Christian Clason
						Christian Clason
					
				
			
						parent
						
							a5d4e3467d
						
					
				
				
					commit
					118ae7e5ed
				
			| @@ -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[]`) | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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 = '%' }) | ||||
|   | ||||
| @@ -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({ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user