mirror of
https://github.com/folke/snacks.nvim
synced 2025-07-16 01:25:14 +00:00
feat(image): added support for math expressions in latex and markdown doc + images in latex. Closes #1223
This commit is contained in:
parent
0bf0c6223d
commit
1bca71a133
1 changed files with 71 additions and 27 deletions
|
@ -6,16 +6,35 @@ local M = {}
|
|||
---@field win snacks.win
|
||||
---@field buf number
|
||||
|
||||
---@alias snacks.image.transform fun(buf:number, src:string, anchor: TSNode, image: TSNode): string
|
||||
---@alias TSMatch {node:TSNode, meta:vim.treesitter.query.TSMetadata}
|
||||
---@alias snacks.image.ctx {buf:number, pos?: TSMatch, src?: TSMatch, content?: TSMatch}
|
||||
---@alias snacks.image.match {id: string, pos: snacks.image.Pos, src?: string, content?: string, ext?: string}
|
||||
---@alias snacks.image.transform fun(match: snacks.image.match, ctx: snacks.image.ctx)
|
||||
|
||||
---@type table<string, snacks.image.transform>
|
||||
M.transforms = {
|
||||
---@param anchor TSNode
|
||||
---@param img TSNode
|
||||
norg = function(buf, _, anchor, img)
|
||||
local row, col = img:start()
|
||||
local line = vim.api.nvim_buf_get_lines(buf, row, row + 1, false)[1]
|
||||
return line:sub(col + 1)
|
||||
norg = function(img, ctx)
|
||||
local row, col = ctx.src.node:start()
|
||||
local line = vim.api.nvim_buf_get_lines(ctx.buf, row, row + 1, false)[1]
|
||||
img.src = line:sub(col + 1)
|
||||
end,
|
||||
latex = function(img, ctx)
|
||||
local fg = Snacks.util.color({ "@markup.math.latex", "Special", "Normal" }) or "#000000"
|
||||
img.ext = "tex"
|
||||
local content = vim.trim(img.content or "")
|
||||
content = content:gsub("^%$+`?", ""):gsub("`?%$+$", "")
|
||||
content = content:gsub("^\\%[", ""):gsub("\\%]$", "")
|
||||
if not content:find("^\\begin") then
|
||||
content = ("\\[%s\\]"):format(content)
|
||||
end
|
||||
img.content = ([[
|
||||
\documentclass[preview,border=2pt,varwidth]{standalone}
|
||||
\usepackage{xcolor, amsmath, amssymb}
|
||||
\begin{document}
|
||||
{ \large \color[HTML]{%s}
|
||||
%s}
|
||||
\end{document}
|
||||
]]):format(fg:upper():sub(2), content)
|
||||
end,
|
||||
}
|
||||
|
||||
|
@ -62,7 +81,7 @@ function M.find(buf, from, to)
|
|||
return {}
|
||||
end
|
||||
parser:parse(from and to and { from, to } or true)
|
||||
local ret = {} ---@type {id:string, pos:snacks.image.Pos, src:string}[]
|
||||
local ret = {} ---@type snacks.image.match[]
|
||||
parser:for_each_tree(function(tstree, tree)
|
||||
if not tstree then
|
||||
return
|
||||
|
@ -72,31 +91,56 @@ function M.find(buf, from, to)
|
|||
return
|
||||
end
|
||||
for _, match, meta in query:iter_matches(tstree:root(), buf, from and from - 1 or nil, to and to - 1 or nil) do
|
||||
local src, pos, nid ---@type string, snacks.image.Pos, string
|
||||
local anchor, image ---@type TSNode, TSNode
|
||||
local ctx = {} ---@type snacks.image.ctx
|
||||
local lang = meta["injection.language"] or tree:lang()
|
||||
for id, nodes in pairs(match) do
|
||||
nodes = type(nodes) == "userdata" and { nodes } or nodes
|
||||
local name = query.captures[id]
|
||||
for _, node in ipairs(nodes) do
|
||||
if name == "image" then
|
||||
image = node
|
||||
src = vim.treesitter.get_node_text(node, buf, { metadata = meta[id] })
|
||||
elseif name == "anchor" then
|
||||
anchor = node
|
||||
local range = { node:range() }
|
||||
pos = { range[1] + 1, range[2] }
|
||||
nid = node:id()
|
||||
end
|
||||
local field = name == "image" and "pos" or name:match("^image%.(.*)$")
|
||||
if field then
|
||||
ctx[field] = { node = nodes[1], meta = meta[id] or {} }
|
||||
end
|
||||
end
|
||||
if src and pos and nid then
|
||||
local transform = M.transforms[tree:lang()]
|
||||
if transform then
|
||||
src = transform(buf, src, anchor, image)
|
||||
end
|
||||
src = M.resolve(buf, src)
|
||||
ret[#ret + 1] = { id = nid, pos = pos, src = src }
|
||||
assert(ctx.src or ctx.content, "no image src or content")
|
||||
ctx.pos = ctx.pos or ctx.src or ctx.content
|
||||
assert(ctx.pos, "no image node")
|
||||
|
||||
local range = vim.treesitter.get_range(ctx.pos.node, buf, ctx.pos.meta)
|
||||
---@type snacks.image.match
|
||||
local img = {
|
||||
ext = meta.ext,
|
||||
id = ctx.pos.node:id(),
|
||||
pos = {
|
||||
range[1] == range[4] and (range[1] + 1) or (range[4] + 1),
|
||||
math.min(range[2], range[5]),
|
||||
},
|
||||
}
|
||||
img.pos[1] = math.min(img.pos[1], vim.api.nvim_buf_line_count(buf))
|
||||
if ctx.src then
|
||||
img.src = vim.treesitter.get_node_text(ctx.src.node, buf, { metadata = ctx.src.meta })
|
||||
end
|
||||
if ctx.content then
|
||||
img.content = vim.treesitter.get_node_text(ctx.content.node, buf, { metadata = ctx.content.meta })
|
||||
end
|
||||
|
||||
local transform = M.transforms[lang]
|
||||
if transform then
|
||||
transform(img, ctx)
|
||||
end
|
||||
if img.src then
|
||||
img.src = M.resolve(buf, img.src)
|
||||
end
|
||||
if img.content and not img.src then
|
||||
local root = Snacks.image.config.cache
|
||||
vim.fn.mkdir(root, "p")
|
||||
img.src = root .. "/" .. vim.fn.sha256(img.content):sub(1, 8) .. "-content." .. (img.ext or "png")
|
||||
if vim.fn.filereadable(img.src) == 0 then
|
||||
local fd = assert(io.open(img.src, "w"), "failed to open " .. img.src)
|
||||
fd:write(img.content)
|
||||
fd:close()
|
||||
end
|
||||
end
|
||||
ret[#ret + 1] = img
|
||||
end
|
||||
end)
|
||||
return ret
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue