Warning
This is very much a work in progress, there are many bugs, and there’s lots to improve, but… we’re getting there!
image.nvim is an attempt to add image support to Neovim.
image-2.mp4
Requirements
These are things you have to setup on your own:
- ImageMagick – mandatory
- magick LuaRock – mandatory (
luarocks --local install magick
or through your package manager) - Kitty – for the
kitty
backend - ueberzugpp – for the
ueberzug
backend - curl – for remote images
After installing the magick
LuaRock, you need to change your config to load it.
-- Example for configuring Neovim to load user-installed installed Lua rocks: package.path = package.path .. ";" .. vim.fn.expand("$HOME") .. "/.luarocks/share/lua/5.1/?/init.lua;" package.path = package.path .. ";" .. vim.fn.expand("$HOME") .. "/.luarocks/share/lua/5.1/?.lua;"
NixOS users need to install imageMagick
and luajitPackages.magick
(thanks to @donovanglover).
If you don’t want to deal with setting up LuaRocks, you can just build your Neovim with the rock installed:
# https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/editors/neovim/utils.nix#L27 { pkgs, neovimUtils, wrapNeovimUnstable, ... }: let config = pkgs.neovimUtils.makeNeovimConfig { extraLuaPackages = p: [ p.luarocks p.magick ]; withNodeJs = false; withRuby = false; withPython3 = false; # https://github.com/NixOS/nixpkgs/issues/211998 customRC = "luafile ~/.config/nvim/init.lua"; }; in { nixpkgs.overlays = [ (_: super: { neovim-custom = pkgs.wrapNeovimUnstable (super.neovim-unwrapped.overrideAttrs (oldAttrs: { version = "master"; buildInputs = oldAttrs.buildInputs ++ [ super.tree-sitter ]; })) config; }) ]; environment.systemPackages = with pkgs; [ neovim-custom ]; }
Configuration
-- default config require("image").setup({ backend = "kitty", integrations = { markdown = { enabled = true, sizing_strategy = "auto", download_remote_images = true, clear_in_insert_mode = false, }, neorg = { enabled = true, download_remote_images = true, clear_in_insert_mode = false, }, }, max_width = nil, max_height = nil, max_width_window_percentage = nil, max_height_window_percentage = 50, kitty_method = "normal", kitty_tmux_write_delay = 10, -- makes rendering more reliable with Kitty+Tmux window_overlap_clear_enabled = false, -- toggles images when windows are overlapped window_overlap_clear_ft_ignore = { "cmp_menu", "cmp_docs", "" }, })
Try it out with a minimal setup
Download minimal-setup.lua from the root of this repository and run the demo with:
nvim --clean -c ":luafile minimal-setup.lua"
Backends
All the backends support rendering inside Tmux.
kitty
– best in class- Works great, is snappy and has very few artifacts (on my machine, at least).
- Use the default mode, the unicode placeholder method is buggy for now.
ueberzug
– backed by ueberzugpp- More genera, on-par with Kitty in terms of features, but slower.
- Supports multiple images thanks to @jstkdng.
sixels
– not implemented yet
Integrations
- Markdown
- Neorg (nvim-neorg/neorg#971)
API
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 ...geometry, -- optional, { x, y, width, height } }) -- from a URL local image = api.from_file("https://gist.ro/s/remote.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 ...geometry, -- optional, { x, y, width, height } }) 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
Thanks
- @edluffy for hologram.nvim – of which I borrowed a lot of code.
- @vhyrro for his great ideas and hologram.nvim fork changes.
- @kovidgoyal for Kitty – the program I spend most of my time in.
- @jstkdng for ueberzugpp – the revived version of ueberzug.
The story behind
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