This plugin attempts to add image support to Neovim.
It works wonderfully with Kitty + Tmux, and it handles painful things like rendering an image at a given position in a buffer, scrolling, windows, etc.
It has built-in Markdown and Neorg integrations that you can use right now. \ It can also render image files as images when opened.
Join on Discord: https://discord.gg/GTwbCxBNgz
https://github.com/3rd/image.nvim/assets/59587503/9a9a1792-6476-4d96-8b8e-d3cdd7f5759e
This plugin requires a few external dependencies. Here is a list, there are instructions for specific plugin managers below.
Mandatory Deps:
magick_rock
image processor, but you can use the magick_cli
processor insteadYou need one of:
kitty
backendueberzug
backendFully optional:
Note: There is now a
magick_cli
processor that uses ImageMagick's CLI tools instead of the LuaRock bindings, you can switch to that by settingprocessor = "magick_cli"
in your configuration.
We use ImageMagick to process images, make sure you have it installed.
If you are using the default magick_rock
processor, you need the dev version of ImageMagick installed so that the rock can bind to it.
sudo apt install libmagickwand-dev
brew install imagemagick
$(brew --prefix)/lib
to
DYLD_FALLBACK_LIBRARY_PATH
by adding something like
export DYLD_FALLBACK_LIBRARY_PATH="$(brew --prefix)/lib:$DYLD_FALLBACK_LIBRARY_PATH"
to your shell profile (probably .zshrc
or .bashrc
)sudo port install imagemagick
/opt/local/lib
to DYLD_FALLBACK_LIBRARY_PATH
, similar to homebrew.sudo dnf install ImageMagick-devel
sudo pacman -Syu imagemagick
-- default config
require("image").setup({
backend = "kitty",
processor = "magick_rock", -- or "magick_cli"
integrations = {
markdown = {
enabled = true,
clear_in_insert_mode = false,
download_remote_images = true,
only_render_image_at_cursor = false,
filetypes = { "markdown", "vimwiki" }, -- markdown extensions (ie. quarto) can go here
},
neorg = {
enabled = true,
clear_in_insert_mode = false,
download_remote_images = true,
only_render_image_at_cursor = false,
filetypes = { "norg" },
},
html = {
enabled = false,
},
css = {
enabled = false,
},
},
max_width = nil,
max_height = nil,
max_width_window_percentage = nil,
max_height_window_percentage = 50,
window_overlap_clear_enabled = false, -- toggles images when windows are overlapped
window_overlap_clear_ft_ignore = { "cmp_menu", "cmp_docs", "" },
editor_only_render_when_focused = false, -- auto show/hide images when the editor gains/looses focus
tmux_show_only_in_active_window = false, -- auto show/hide images in the correct Tmux window (needs visual-activity off)
hijack_file_patterns = { "*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp", "*.avif" }, -- render image files as images when opened
})
set -gq allow-passthrough on
tmux_show_only_in_active_window = true
), set: set -g visual-activity off
Download minimal-setup.lua from the root of this repository and run the demo with:
nvim --clean -c ":luafile minimal-setup.lua"
All the backends support rendering inside Tmux.
kitty
- best in class, works great and is very snappyueberzug
- backed by ueberzugpp, supports any terminal, but has lower performance
markdown
- uses tree-sitter-markdown and supports any Markdown-based grammars (Quarto, VimWiki Markdown)neorg
- uses tree-sitter-norg (also check https://github.com/nvim-neorg/neorg/issues/971)You can configure where images are searched for on a per-integration basis by passing a function to
resolve_image_path
as shown below:
require('image').setup({
integrations = {
markdown = {
resolve_image_path = function(document_path, image_path, fallback)
-- document_path is the path to the file that contains the image
-- image_path is the potentially relative path to the image. for
-- markdown it's `![](this text)`
-- you can call the fallback function to get the default behavior
return fallback(document_path, image_path)
end,
}
}
})
Check types.lua for a better overview of how everything is modeled.
local api = require("image")
-- from a file (absolute path)
local image = api.from_file("/path/to/image.png", {
id = "my_image_id", -- optional, defaults to a random string
window = 1000, -- optional, binds image to a window and its bounds
buffer = 1000, -- optional, binds image to a buffer (paired with window binding)
with_virtual_padding = true, -- optional, pads vertically with extmarks, defaults to false
-- optional, binds image to an extmark which it follows. Forced to be true when
-- `with_virtual_padding` is true. defaults to false.
inline = true,
-- geometry (optional)
x = 1,
y = 1,
width = 10,
height = 10
})
-- from a URL
api.from_url("https://gist.ro/s/remote.png", {
-- all the same options from above
}, function(img)
-- do stuff with the image
end
)
image:render() -- render image
image:render(geometry) -- update image geometry and render it
image:clear()
image:move(x, y) -- move image
image:brightness(value) -- change brightness
image:saturation(value) -- change saturation
image:hue(value) -- change hue
-- create a report, also available as :ImageReport
require("image").create_report()
Some years ago, I took a trip to Emacs land for a few months to learn Elisp and also research what Org-mode is, how it works, and look for features of interest for my workflow. I already had my own document syntax, albeit a very simple one, hacked together with Vimscript and a lot of Regex, and I was looking for ideas to improve it and build features on top of it.
I kept working on my syntax over the years, rewrote it many times, and today it's a proper Tree-sitter grammar, that I use for all my needs, from second braining to managing my tasks and time. It's helped me control my ADHD and be productive long before I was diagnosed, and it's still helping me be so much better than I'd be without it today.
One thing Emacs and Org-mode had that I liked was the ability to embed images in the document. Of course, we don't "need" it, but... I really wanted to have images in my documents.
About 3 years ago, I made my first attempt at solving this problem but didn't get far. If you have similar interests, you might have seen the vimage.nvim demo video on YouTube.
It was using ueberzug, which is now dead. It was buggy and didn't handle things like window-relative positioning, attaching images to windows and buffers, folds, etc.
Kitty's graphics protocol was a thing, but it didn't work with Tmux, which I'll probably use forever or replace it with something of my own.
Now, things have changed, and I'm happy to announce that rendering images using Kitty's graphics protocol from Neovim inside Tmux is working, and it's working pretty well!
My plan for this plugin is to support multiple backends, provide a few core integrations, and an easy-to-use API for other plugin authors to build on top of. There is a lot of logic that deals with positioning, cropping, bounds, folds, extmarks, etc. that is painful and unrealistic to write from scratch for every plugin that wants to use images.