Skip to content

Commit b7dad79

Browse files
feat: inline render single line latex equations
## Details Request: #508 The request for inline rendering latex has been around for a while, and is one I haven't implemented due to the difficulty of aligning multi-line outputs with inputs and handling multiple formulas on the same line. However when the output of the converter is a single line, we can essentially ignore all of this and use an inline / conceal `extmark` to overlay our content instead of the raw formula. This is the new default behavior out of the box and will apply to anyone using latex support through this plugin. A new option has been added `latex.virtual` that when set to `true` forces the plugin to always use virtual lines rather than trying to inline, which replicates the old behavior of this plugin. Co-authored-by: Lexey Khom <92052116+LexeyKhom@users.noreply.github.com>
1 parent 84d4928 commit b7dad79

File tree

8 files changed

+70
-61
lines changed

8 files changed

+70
-61
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@
1616
- prioritize hide over highlight_border [7b37aab](https://github.com/MeanderingProgrammer/render-markdown.nvim/commit/7b37aaba005df5744fc7a6bd4225983576b2a950)
1717
- add option to restart treesitter highlighter to clear invalid state [#488](https://github.com/MeanderingProgrammer/render-markdown.nvim/issues/488)
1818
[ec74afa](https://github.com/MeanderingProgrammer/render-markdown.nvim/commit/ec74afa498b6ce3ee686131adf2b3941b713f307)
19+
- use display width for latex formula alignment [c4ff9ac](https://github.com/MeanderingProgrammer/render-markdown.nvim/commit/c4ff9acddcf0f79b3187393319adb5cac5865bd3)
20+
- anti-conceal ignore modes [8074a9c](https://github.com/MeanderingProgrammer/render-markdown.nvim/commit/8074a9cc9a6f737320b7a0d76b2c4c3485155688)
21+
- component level render modes [84d4928](https://github.com/MeanderingProgrammer/render-markdown.nvim/commit/84d4928cb4b508847294002835989880da4ce13b)
22+
23+
### Collaborator Shoutouts
24+
25+
- @LexeyKhom
1926

2027
## 8.7.0 (2025-08-08)
2128

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,8 @@ require('render-markdown').setup({
297297
top_pad = 0,
298298
-- Number of empty lines below latex blocks.
299299
bottom_pad = 0,
300+
-- Always use virtual lines for rendering instead of attempting to inline.
301+
virtual = false,
300302
},
301303
on = {
302304
-- Called when plugin initially attaches to a buffer.

doc/render-markdown.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,8 @@ Default Configuration ~
363363
top_pad = 0,
364364
-- Number of empty lines below latex blocks.
365365
bottom_pad = 0,
366+
-- Always use virtual lines for rendering instead of attempting to inline.
367+
virtual = false,
366368
},
367369
on = {
368370
-- Called when plugin initially attaches to a buffer.

lua/render-markdown/config/latex.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
---@field position render.md.latex.Position
55
---@field top_pad integer
66
---@field bottom_pad integer
7+
---@field virtual boolean
78

89
---@enum render.md.latex.Position
910
local Position = {
@@ -32,6 +33,8 @@ M.default = {
3233
top_pad = 0,
3334
-- Number of empty lines below latex blocks.
3435
bottom_pad = 0,
36+
-- Always use virtual lines for rendering instead of attempting to inline.
37+
virtual = false,
3538
}
3639

3740
---@param spec render.md.debug.ValidatorSpec
@@ -42,6 +45,7 @@ function M.validate(spec)
4245
spec:one_of('position', vim.tbl_values(Position))
4346
spec:type('top_pad', 'number')
4447
spec:type('bottom_pad', 'number')
48+
spec:type('virtual', 'boolean')
4549
spec:check()
4650
end
4751

lua/render-markdown/handler/latex.lua

Lines changed: 39 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -39,49 +39,48 @@ function Handler:run(root)
3939
local node = Node.new(self.context.buf, root)
4040
log.node('latex', node)
4141

42-
local indent = self:indent(node.start_row, node.start_col)
43-
local lines = iter.list.map(self:expressions(node), function(expression)
44-
local line = vim.list_extend({}, indent) ---@type render.md.mark.Line
45-
line[#line + 1] = { expression, self.config.highlight }
46-
return line
47-
end)
48-
49-
local above = self.config.position == 'above'
50-
local row = above and node.start_row or node.end_row
51-
5242
local marks = Marks.new(self.context, true)
53-
marks:add(self.config, 'virtual_lines', row, 0, {
54-
virt_lines = lines,
55-
virt_lines_above = above,
56-
})
57-
return marks:get()
58-
end
59-
60-
---@private
61-
---@param node render.md.Node
62-
---@return string[]
63-
function Handler:expressions(node)
64-
local col = node.start_col
65-
local _, first = node:line('first', 0)
66-
local prefix = str.pad(first and str.width(first:sub(1, col)) or col)
67-
68-
local result = {} ---@type string[]
69-
for _ = 1, self.config.top_pad do
70-
result[#result + 1] = ''
71-
end
72-
73-
local lines = str.split(self:convert(node.text), '\n', true)
74-
local width = vim.fn.max(iter.list.map(lines, str.width))
75-
for _, line in ipairs(lines) do
76-
local suffix = str.pad(width - str.width(line))
77-
result[#result + 1] = prefix .. line .. suffix
78-
end
43+
local output = str.split(self:convert(node.text), '\n', true)
44+
if self.config.virtual or #output > 1 then
45+
local col = node.start_col
46+
local _, first = node:line('first', 0)
47+
local prefix = str.pad(first and str.width(first:sub(1, col)) or col)
48+
local width = vim.fn.max(iter.list.map(output, str.width))
49+
50+
local text = {} ---@type string[]
51+
for _ = 1, self.config.top_pad do
52+
text[#text + 1] = ''
53+
end
54+
for _, line in ipairs(output) do
55+
local suffix = str.pad(width - str.width(line))
56+
text[#text + 1] = prefix .. line .. suffix
57+
end
58+
for _ = 1, self.config.bottom_pad do
59+
text[#text + 1] = ''
60+
end
7961

80-
for _ = 1, self.config.bottom_pad do
81-
result[#result + 1] = ''
62+
local indent = self:indent(node.start_row, col)
63+
local lines = iter.list.map(text, function(part)
64+
local line = vim.list_extend({}, indent) ---@type render.md.mark.Line
65+
line[#line + 1] = { part, self.config.highlight }
66+
return line
67+
end)
68+
69+
local above = self.config.position == 'above'
70+
local row = above and node.start_row or node.end_row
71+
72+
marks:add(self.config, 'virtual_lines', row, 0, {
73+
virt_lines = lines,
74+
virt_lines_above = above,
75+
})
76+
else
77+
marks:over(self.config, true, node, {
78+
virt_text = { { output[1], self.config.highlight } },
79+
virt_text_pos = 'inline',
80+
conceal = '',
81+
})
8282
end
83-
84-
return result
83+
return marks:get()
8584
end
8685

8786
---@private

lua/render-markdown/health.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ local state = require('render-markdown.state')
55
local M = {}
66

77
---@private
8-
M.version = '8.7.12'
8+
M.version = '8.7.13'
99

1010
function M.check()
1111
M.start('versions')

lua/render-markdown/types.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@
197197
---@field position? render.md.latex.Position
198198
---@field top_pad? integer
199199
---@field bottom_pad? integer
200+
---@field virtual? boolean
200201

201202
---@class (exact) render.md.link.UserConfig: render.md.base.UserConfig
202203
---@field footnote? render.md.link.footnote.UserConfig

tests/latex_spec.lua

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,11 @@ local function set_responses(converter, responses)
1313
stub.new(vim.fn, 'system', function(cmd, input)
1414
assert.same(converter, cmd)
1515
local result = responses[input]
16-
assert.is_true(result ~= nil, 'No output for: ' .. input)
16+
assert.is_true(result ~= nil, 'missing output for: ' .. input)
1717
return result
1818
end)
1919
end
2020

21-
---@param lines string[]
22-
---@param width integer
23-
---@return vim.api.keyset.set_extmark
24-
local function latex(lines, width)
25-
local virt_lines = vim.iter(lines)
26-
:map(function(line)
27-
return { { line .. (' '):rep(width - #line), 'RmMath' } }
28-
end)
29-
:totable()
30-
---@type vim.api.keyset.set_extmark
31-
return {
32-
virt_lines = virt_lines,
33-
virt_lines_above = true,
34-
}
35-
end
36-
3721
---@param lines string[]
3822
---@param prefix string
3923
---@param suffix string
@@ -71,14 +55,24 @@ describe('latex.md', function()
7155
marks:add(row:get(0, 0), { 0, 1 }, util.heading.icon(1))
7256
marks:add(row:get(0, 1), { 0, 0 }, util.heading.bg(1))
7357

74-
marks:add(row:get(1), 0, latex(inline.out, 17))
75-
marks:add(row:get(2), 0, latex(block.out, 28))
58+
marks:add(row:get(1, 0), { 0, 21 }, {
59+
virt_text = { { inline.out[1], 'RmMath' } },
60+
virt_text_pos = 'inline',
61+
conceal = '',
62+
})
63+
marks:add(row:get(2), 0, {
64+
virt_lines = vim.iter(block.out)
65+
:map(function(line)
66+
return { { line .. (' '):rep(28 - #line), 'RmMath' } }
67+
end)
68+
:totable(),
69+
virt_lines_above = true,
70+
})
7671

7772
util.assert_view(marks, {
7873
'󰫎 󰲡 LaTeX',
7974
'',
8075
' ' .. inline.out[1],
81-
' ' .. inline.raw[1],
8276
'',
8377
' ' .. block.out[1],
8478
' ' .. block.out[2],

0 commit comments

Comments
 (0)