Skip to content

fix: Correct padding calculation for virtual LaTeX formula lines considering concealed Markdown characters #512

@LexeyKhom

Description

@LexeyKhom

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:

Image

Example After (expected behavior):

Image

P.S. Next steps:

  1. Merging the ASCII art of multiple LaTeX formulas located on a single logical line into a unified horizontal block of virtual lines.
  2. Inline rendering of multi-line formulas.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions