- Image viewer using the Kitty Graphics Protocol.
- open images in a wide range of formats:
pdf
,png
,jpg
,jpeg
,gif
,bmp
,webp
,tiff
,heic
,avif
,mp4
,mov
,avi
,mkv
,webm
- Supports inline image rendering in:
markdown
,html
,norg
,tsx
,javascript
,css
,vue
,svelte
,scss
,latex
,typst
- LaTex math expressions in
markdown
andlatex
documents
Terminal support:
- kitty
- ghostty
- wezterm Wezterm has only limited support for the kitty graphics protocol. Inline image rendering is not supported.
- tmux
Snacks automatically tries to enable
allow-passthrough=on
for tmux, but you may need to enable it manually in your tmux configuration. - zellij is not supported, since they don't have any support for passthrough
Image will be transferred to the terminal by filename or by sending the image
date in case ssh
is detected.
In some cases you may need to force snacks to detect or not detect a certain
environment. You can do this by setting SNACKS_${ENV_NAME}
to true
or false
.
For example, to force detection of ghostty you can set SNACKS_GHOSTTY=true
.
In order to automatically display the image when opening an image file,
or to have imaged displayed in supported document formats like markdown
or html
,
you need to enable the image
plugin in your snacks
config.
ImageMagick is required to convert images to the supported formats (all except PNG).
In case of issues, make sure to run :checkhealth snacks
.
-- lazy.nvim
{
"folke/snacks.nvim",
---@type snacks.Config
opts = {
image = {
-- your image configuration comes here
-- or leave it empty to use the default settings
-- refer to the configuration section below
}
}
}
---@class snacks.image.Config
---@field enabled? boolean enable image viewer
---@field wo? vim.wo|{} options for windows showing the image
---@field bo? vim.bo|{} options for the image buffer
---@field formats? string[]
--- Resolves a reference to an image with src in a file (currently markdown only).
--- Return the absolute path or url to the image.
--- When `nil`, the path is resolved relative to the file.
---@field resolve? fun(file: string, src: string): string?
---@field convert? snacks.image.convert.Config
{
formats = {
"png",
"jpg",
"jpeg",
"gif",
"bmp",
"webp",
"tiff",
"heic",
"avif",
"mp4",
"mov",
"avi",
"mkv",
"webm",
"pdf",
},
force = false, -- try displaying the image, even if the terminal does not support it
doc = {
-- enable image viewer for documents
-- a treesitter parser must be available for the enabled languages.
enabled = true,
-- render the image inline in the buffer
-- if your env doesn't support unicode placeholders, this will be disabled
-- takes precedence over `opts.float` on supported terminals
inline = true,
-- render the image in a floating window
-- only used if `opts.inline` is disabled
float = true,
max_width = 80,
max_height = 40,
-- Set to `true`, to conceal the image text when rendering inline.
-- (experimental)
---@param lang string tree-sitter language
---@param type snacks.image.Type image type
conceal = function(lang, type)
-- only conceal math expressions
return type == "math"
end,
},
img_dirs = { "img", "images", "assets", "static", "public", "media", "attachments" },
-- window options applied to windows displaying image buffers
-- an image buffer is a buffer with `filetype=image`
wo = {
wrap = false,
number = false,
relativenumber = false,
cursorcolumn = false,
signcolumn = "no",
foldcolumn = "0",
list = false,
spell = false,
statuscolumn = "",
},
cache = vim.fn.stdpath("cache") .. "/snacks/image",
debug = {
request = false,
convert = false,
placement = false,
},
env = {},
-- icons used to show where an inline image is located that is
-- rendered below the text.
icons = {
math = " ",
chart = " ",
image = " ",
},
---@class snacks.image.convert.Config
convert = {
notify = true, -- show a notification on error
---@type snacks.image.args
mermaid = function()
local theme = vim.o.background == "light" and "neutral" or "dark"
return { "-i", "{src}", "-o", "{file}", "-b", "transparent", "-t", theme, "-s", "{scale}" }
end,
---@type table<string,snacks.image.args>
magick = {
default = { "{src}[0]", "-scale", "1920x1080>" }, -- default for raster images
vector = { "-density", 192, "{src}[0]" }, -- used by vector images like svg
math = { "-density", 192, "{src}[0]", "-trim" },
pdf = { "-density", 192, "{src}[0]", "-background", "white", "-alpha", "remove", "-trim" },
},
},
math = {
enabled = true, -- enable math expression rendering
-- in the templates below, `${header}` comes from any section in your document,
-- between a start/end header comment. Comment syntax is language-specific.
-- * start comment: `// snacks: header start`
-- * end comment: `// snacks: header end`
typst = {
tpl = [[
#set page(width: auto, height: auto, margin: (x: 2pt, y: 2pt))
#show math.equation.where(block: false): set text(top-edge: "bounds", bottom-edge: "bounds")
#set text(size: 12pt, fill: rgb("${color}"))
${header}
${content}]],
},
latex = {
font_size = "Large", -- see https://www.sascha-frank.com/latex-font-size.html
-- for latex documents, the doc packages are included automatically,
-- but you can add more packages here. Useful for markdown documents.
packages = { "amsmath", "amssymb", "amsfonts", "amscd", "mathtools" },
tpl = [[
\documentclass[preview,border=0pt,varwidth,12pt]{standalone}
\usepackage{${packages}}
\begin{document}
${header}
{ \${font_size} \selectfont
\color[HTML]{${color}}
${content}}
\end{document}]],
},
},
}
Check the styles docs for more information on how to customize these styles
{
relative = "cursor",
border = "rounded",
focusable = false,
backdrop = false,
row = 1,
col = 1,
-- width/height are automatically set by the image size unless specified below
}
---@alias snacks.image.Size {width: number, height: number}
---@alias snacks.image.Pos {[1]: number, [2]: number}
---@alias snacks.image.Loc snacks.image.Pos|snacks.image.Size|{zindex?: number}
---@alias snacks.image.Type "image"|"math"|"chart"
---@class snacks.image.Env
---@field name string
---@field env table<string, string|true>
---@field supported? boolean default: false
---@field placeholders? boolean default: false
---@field setup? fun(): boolean?
---@field transform? fun(data: string): string
---@field detected? boolean
---@field remote? boolean this is a remote client, so full transfer of the image data is required
---@class snacks.image.Opts
---@field pos? snacks.image.Pos (row, col) (1,0)-indexed. defaults to the top-left corner
---@field range? Range4
---@field conceal? boolean
---@field inline? boolean render the image inline in the buffer
---@field width? number
---@field min_width? number
---@field max_width? number
---@field height? number
---@field min_height? number
---@field max_height? number
---@field on_update? fun(placement: snacks.image.Placement)
---@field on_update_pre? fun(placement: snacks.image.Placement)
---@field type? snacks.image.Type
---@field auto_resize? boolean
---@class snacks.image
---@field terminal snacks.image.terminal
---@field image snacks.Image
---@field placement snacks.image.Placement
---@field util snacks.image.util
---@field buf snacks.image.buf
---@field doc snacks.image.doc
---@field convert snacks.image.convert
---@field inline snacks.image.inline
Snacks.image = {}
Show the image at the cursor in a floating window
Snacks.image.hover()
---@return string[]
Snacks.image.langs()
Check if the file format is supported and the terminal supports the kitty graphics protocol
---@param file string
Snacks.image.supports(file)
Check if the file format is supported
---@param file string
Snacks.image.supports_file(file)
Check if the terminal supports the kitty graphics protocol
Snacks.image.supports_terminal()