-
Notifications
You must be signed in to change notification settings - Fork 75
Description
Phew, I had to sweat for several days before I realized there was already a ready-made solution in Conceal
...
Description:
When displaying multi-line LaTeX formulas using virtual lines (e.g., virt_lines_above/below
), an issue arose with inaccurate horizontal alignment. The left-hand padding was calculated based on the "raw" width of the text in the buffer, without accounting for the fact that some Markdown characters (such as _
, **
, $
, `
, #
, etc.) could be concealed by Neovim. This led to the ASCII art of the formulas being horizontally shifted to the right relative to their actual visible position.
Solution:
---@param root TSNode
---@return render.md.Mark[]
function Handler:run(root)
if not self.config.enabled then
return {}
end
if vim.fn.executable(self.config.converter) ~= 1 then
log.add('debug', 'ConverterNotFound', self.config.converter)
return {}
end
local node = Node.new(self.context.buf, root)
log.node('latex', node)
local marks = Marks.new(self.context, true)
local lines = str.split(self:convert(node.text), '\n', true)
if self.config.virtual or #lines > 1 then
local above = self.config.position == 'above'
self:render_virtual_lines(marks, node, lines, above)
else
self:render_baseline(marks, node, lines[1])
end
return marks:get()
end
---@param marks render.md.Marks
---@param node render.md.Node
---@param output_lines string[]
---@param above boolean
function Handler:render_virtual_lines(marks, node, output_lines, above)
local prefix = str.pad(self:get_prefix_width(node))
local width = vim.fn.max(iter.list.map(output_lines, str.width))
local text = {} ---@type string[]
for _ = 1, self.config.top_pad do
text[#text + 1] = ''
end
for _, line in ipairs(output_lines) do
local suffix = str.pad(width - str.width(line))
text[#text + 1] = prefix .. line .. suffix
end
for _ = 1, self.config.bottom_pad do
text[#text + 1] = ''
end
local indent = self:indent(node.start_row, node.start_col)
local lines = iter.list.map(text, function(part)
local line = vim.list_extend({}, indent) ---@type render.md.mark.Line
line[#line + 1] = { part, self.config.highlight }
return line
end)
local row = above and node.start_row or node.end_row
marks:add(self.config, 'virtual_lines', row, 0, {
virt_lines = lines,
virt_lines_above = above,
})
end
---@param marks render.md.Marks
---@param node render.md.Node
---@param baseline string
function Handler:render_baseline(marks, node, baseline)
marks:over(self.config, true, node, {
virt_text = { { baseline, self.config.highlight } },
virt_text_pos = 'inline',
conceal = '',
})
end
---@param node render.md.Node
---@return integer
function Handler:get_prefix_width(node)
local _, first = node:line('first', 0)
local temp_prefix_node = {
start_row = node.start_row,
start_col = 0,
end_row = node.start_row,
end_col = node.start_col,
}
local raw_prefix_width = first and str.width(first:sub(1, node.start_col))
or node.start_col
local concealed_prefix_width = self.context.conceal:get(temp_prefix_node)
local final_prefix_width = raw_prefix_width - concealed_prefix_width
return math.max(0, final_prefix_width)
end
The Handler:get_prefix_width(node)
function now utilizes self.context.conceal:get(temp_prefix_node)
to obtain the precise total width of all concealed characters located in the prefix part of the line (from the start of the line up to node.start_col
). This concealed width is then subtracted from the total "raw" prefix width, ensuring correct visual alignment of the virtual LaTeX formula lines.
Example Before:

Example After (expected behavior):

P.S. Next steps:
- Merging the ASCII art of multiple LaTeX formulas located on a single logical line into a unified horizontal block of virtual lines.
- Inline rendering of multi-line formulas.