Skip to content

Commit 3334117

Browse files
committed
fix(latex): use \( … \) for inline math and improve tex generation
Switch inline math wrapper from \[..\] to \(..\) to avoid edge cases where a closing brace could be lost next to the display delimiter. Use correct Lua pattern escapes for marker stripping, and always overwrite cached .tex files so stale/malformed files don't block regeneration. Also add a debug writer that logs empty/unbalanced tex and writes a debug copy for inspection. This fixes cases like $|\frac{1}{2}|$ that previously produced malformed .tex (e.g. `\[\frac{1}{2\]`) and did not render.
1 parent a06bf65 commit 3334117

File tree

1 file changed

+55
-33
lines changed

1 file changed

+55
-33
lines changed

lua/neorg/modules/core/latex/renderer/module.lua

Lines changed: 55 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,17 @@ local function create_latex_source(snippet, is_inline)
7272
or (module.config.public.font_size_block or "\\Large")
7373

7474
if is_inline then
75-
content = string.gsub(content, "^%$|", "")
76-
content = string.gsub(content, "|%$$", "")
77-
78-
-- --- FIX #1: ENSURE PROPER DISPLAY MATH ENVIRONMENT ---
79-
-- Wrap content in \[ ... \] to force display math mode, mirroring snacks.nvim's
80-
-- robust behavior and preventing typesetting errors.
81-
if not content:find("\\begin") then
82-
content = "\\[" .. content .. "\\]"
75+
-- Remove leading $| and trailing |$ exactly
76+
content = content:gsub("^%$%|", "") -- strip leading "$|"
77+
content = content:gsub("%|%$$", "") -- strip trailing "|$"
78+
79+
-- If user wrote plain $ ... $ or left stray $ markers, also strip them
80+
content = content:gsub("^%$", "") -- leading single $
81+
content = content:gsub("%$$", "") -- trailing single $
82+
83+
-- If content doesn't already look like a math environment, wrap it robustly.
84+
if not content:find("\\begin") and not content:match("^\\\\%(") and not content:match("^%$") then
85+
content = "\\(" .. content .. "\\)"
8386
end
8487
end
8588

@@ -98,6 +101,32 @@ local function create_latex_source(snippet, is_inline)
98101
return template:gsub("${color}", fg_hex):gsub("${content}", content)
99102
end
100103

104+
-- --- NEW AND IMPROVED DEBUG WRITER ---
105+
-- This function ALWAYS overwrites the cached .tex file to prevent stale-cache issues.
106+
-- It also includes robust checks for empty content or unbalanced braces.
107+
local function write_tex_file_with_debug(tex_source, snippet, tex_file)
108+
-- If tex_source is empty -> warn and still write for inspection
109+
if not tex_source:find("%S") then
110+
vim.notify("Neorg LaTeX: EMPTY tex source for snippet: " .. vim.inspect(snippet), vim.log.levels.WARN)
111+
end
112+
113+
-- Sanity check: balanced braces
114+
local opens = select(2, tex_source:gsub("{", ""))
115+
local closes = select(2, tex_source:gsub("}", ""))
116+
if opens ~= closes then
117+
vim.notify(
118+
("Neorg LaTeX: UNBALANCED braces (%d vs %d). Writing debug tex for inspection."):format(opens, closes),
119+
vim.log.levels.WARN
120+
)
121+
local debug_file = module.private.cache_dir .. "debug-" .. (vim.fn.strftime("%s")) .. ".tex"
122+
vim.fn.writefile(vim.split(tex_source, "\n"), debug_file)
123+
vim.notify("Wrote debug tex: " .. debug_file, vim.log.levels.INFO)
124+
end
125+
126+
-- ALWAYS overwrite so we don't keep stale/malformed files
127+
vim.fn.writefile(vim.split(tex_source, "\n"), tex_file)
128+
end
129+
101130
module.load = function()
102131
local snacks_ok, snacks_placement = pcall(require, "snacks.image.placement")
103132
if not snacks_ok then
@@ -106,24 +135,14 @@ module.load = function()
106135
end
107136
placement = snacks_placement
108137

109-
-- --- FIX #2: REMOVE THE AGGRESSIVE INLINE COLLAPSE MONKEY-PATCH ---
110-
-- The original patch forced multi-line fractions into a single row, causing
111-
-- visual distortion. By removing it, we allow snacks.nvim's default, correct
112-
-- rendering to take over.
113-
-- (The empty `do ... end` block below is intentional; we are no longer patching `placement:state`)
114-
do
115-
-- No-op: The problematic monkey-patch has been removed.
116-
end
117-
118-
-- Use snacks.nvim's high-density strategy for crisp, correctly-sized images.
119138
local read_density = 192
120139
local magick_args = {
121140
"-density",
122141
read_density,
123-
"{src}[0]", -- Rasterize first page of PDF
142+
"{src}[0]",
124143
"-background",
125-
"none", -- Use transparent background for inline math
126-
"-trim", -- Remove whitespace
144+
"none",
145+
"-trim",
127146
"-alpha",
128147
"remove",
129148
}
@@ -143,6 +162,7 @@ module.load = function()
143162
module.required["core.autocommands"].enable_autocommand("BufWinEnter")
144163
module.required["core.autocommands"].enable_autocommand("CursorMoved")
145164
module.required["core.autocommands"].enable_autocommand("TextChanged")
165+
module.required["core.autocommands"].enable_autocommand("TextChangedI")
146166
module.required["core.autocommands"].enable_autocommand("InsertLeave")
147167
module.required["core.autocommands"].enable_autocommand("Colorscheme")
148168

@@ -182,11 +202,9 @@ function module.private.update_placements(buf)
182202
local current_placements = module.private.placements[buf] or {}
183203
module.private.placements[buf] = current_placements
184204

185-
-- First, handle inline math with Treesitter for performance and accuracy
186205
module.required["core.integrations.treesitter"].execute_query(
187206
[[
188207
(inline_math) @latex.inline
189-
(#offset! @latex.inline 0 1 0 -1)
190208
]],
191209
function(query, id, node)
192210
local node_id = tostring(node:id())
@@ -196,7 +214,9 @@ function module.private.update_placements(buf)
196214
return
197215
end
198216

199-
local snippet = module.required["core.integrations.treesitter"].get_node_text(node, buf)
217+
local snippet =
218+
module.required["core.integrations.treesitter"].get_node_text(node, buf, { metadata = true })
219+
200220
if #snippet < module.config.public.min_length then
201221
return
202222
end
@@ -205,9 +225,8 @@ function module.private.update_placements(buf)
205225
local source_hash = vim.fn.sha256(tex_source)
206226
local tex_file = module.private.cache_dir .. source_hash:sub(1, 12) .. ".tex"
207227

208-
if vim.fn.filereadable(tex_file) == 0 then
209-
vim.fn.writefile(vim.split(tex_source, "\n"), tex_file)
210-
end
228+
-- Use the new debug-aware writer function that always overwrites
229+
write_tex_file_with_debug(tex_source, snippet, tex_file)
211230

212231
local range = { node:range() }
213232
local pos = { range[1] + 1, range[2] }
@@ -230,7 +249,7 @@ function module.private.update_placements(buf)
230249
buf
231250
)
232251

233-
-- Second, handle @math blocks by manually parsing lines for robustness.
252+
-- Handle @math blocks
234253
local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
235254
local i = 1
236255
while i <= #lines do
@@ -262,9 +281,8 @@ function module.private.update_placements(buf)
262281
local source_hash = vim.fn.sha256(tex_source)
263282
local tex_file = module.private.cache_dir .. source_hash:sub(1, 12) .. ".tex"
264283

265-
if vim.fn.filereadable(tex_file) == 0 then
266-
vim.fn.writefile(vim.split(tex_source, "\n"), tex_file)
267-
end
284+
-- Use the new debug-aware writer function that always overwrites
285+
write_tex_file_with_debug(tex_source, snippet, tex_file)
268286

269287
local full_range = { start_line + 1, 0, end_line + 1, #lines[end_line] }
270288

@@ -291,7 +309,7 @@ function module.private.update_placements(buf)
291309
end
292310
end
293311

294-
-- Finally, clean up any stale placements
312+
-- Clean up stale placements
295313
for node_id, p in pairs(current_placements) do
296314
if not active_nodes[node_id] then
297315
p:close()
@@ -363,7 +381,9 @@ local function show_all_placements(buf)
363381
p:show()
364382
end
365383
end
366-
module.private.hidden_by_cursor[buf] = {}
384+
if module.private.hidden_by_cursor[buf] then
385+
module.private.hidden_by_cursor[buf] = {}
386+
end
367387
end
368388

369389
local function disable_rendering()
@@ -418,6 +438,7 @@ local event_handlers = {
418438
end,
419439
["core.autocommands.events.cursormoved"] = clear_at_cursor,
420440
["core.autocommands.events.textchanged"] = render_latex,
441+
["core.autocommands.events.textchangedi"] = render_latex,
421442
["core.autocommands.events.insertleave"] = render_latex,
422443
["core.autocommands.events.colorscheme"] = colorscheme_change,
423444
}
@@ -444,6 +465,7 @@ module.events.subscribed = {
444465
bufwinenter = true,
445466
cursormoved = true,
446467
textchanged = true,
468+
textchangedi = true,
447469
insertleave = true,
448470
colorscheme = true,
449471
},

0 commit comments

Comments
 (0)