Skip to content

feature: Inline Rendering for Single-Line LaTeX Equations #508

@LexeyKhom

Description

@LexeyKhom

Hello!

I'd like to propose an enhancement: rendering single-line LaTeX equations
directly inline, rather than using virtual lines above the text. While the
current virtual line approach is functional, it often consumes significant
vertical space and can be distracting, especially for short equations.

My suggestion is to integrate these equations directly into the text flow,
which would significantly improve readability and document compactness.

Here are visual examples demonstrating the difference:

Before:

Image

After:

Image

I've implemented a prototype to demonstrate this idea and serve as a starting
point:

---@param root TSNode
---@return render.md.Mark[]
function Handler:run(root)
    if self.context:skip(self.config) 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 indent = self:indent(node.start_row, node.start_col)
    local lines = iter.list.map(self:expressions(node), function(expression)
        local line = vim.list_extend({}, indent) ---@type render.md.mark.Line
        line[#line + 1] = { expression, self.config.highlight }
        return line
    end)

    local marks = Marks.new(self.context, true)

    if #lines == 1 then
        marks:add(true, node.start_row, node.start_col, {
            end_row = node.end_row,
            end_col = node.end_col,
            virt_text = lines[1],
            virt_text_pos = 'inline',
            conceal = '',
        })
        return marks:get()
    end

    local above = self.config.position == 'above'
    local row = above and node.start_row or node.end_row

    marks:add('virtual_lines', row, 0, {
        virt_lines = lines,
        virt_lines_above = above,
    })
    return marks:get()
end

---@private
---@param node render.md.Node
---@return string[]
function Handler:expressions(node)
    local lines = str.split(self:convert(node.text), '\n', true)

    if #lines == 1 then
        return lines
    end

    local col = node.start_col
    local _, first = node:line('first', 0)
    local prefix = str.pad(first and str.width(first:sub(1, col)) or col)
    local width = vim.fn.max(iter.list.map(lines, str.width))
    local result = {} ---@type string[]

    for _ = 1, self.config.top_pad do
        result[#result + 1] = ''
    end

    for _, line in ipairs(lines) do
        local suffix = str.pad(width - str.width(line))
        result[#result + 1] = prefix .. line .. suffix
    end

    for _ = 1, self.config.bottom_pad do
        result[#result + 1] = ''
    end

    return result
end

Further Observations and Challenges (regarding multi-line equations):

I also explored applying a similar inline approach to multi-line LaTeX
equations but encountered several challenges:

  • Challenge: Detecting End of File/Last Formula: While the line break logic
    (if cur_start_row ~= node.start_row) worked, I couldn't find a reliable
    mechanism to detect the very last formula in a file. The absence of a clear
    end-of-file indicator (e.g., nil) or a subscribable event prevented me from
    correctly rendering the final group of equations.
  • Usability for Multi-line Equations: Furthermore, for multi-line
    equations, strict inline rendering isn't always ideal. Ideally, it would be
    great to have full rendering visible during editing (e.g., displaying the
    equation as a virtual line when the cursor hovers over it). However, I
    haven't found a simple and performant solution for such interactive
    rendering.

I'd appreciate your thoughts on this proposal and any insights or suggestions
regarding the multi-line challenges, especially detecting the end of a formula
block or file.

P.S. I used AI to help formulate this issue, aiming for maximum clarity and
precision, as my English proficiency sometimes hinders expressing nuanced
ideas. I hope this is acceptable.

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