image.nvim
image.nvim copied to clipboard
Help with rendering images within ObsidianVault
Hi, thank you for all your work in getting images inside of Neovim. I am really excited to try and get this working.
I am able to use the minimal setup and also configured my setup to work with this style image:

Inside of my ObsidianVault, I have an Attachments folder at the root where all of my images are stored.
How can I configure image.nvim to look in this directory when it sees something like this?
![[Test.png]]
I am probably missing something obvious or unsure how to configure resolve_image_path to make this work.
Thank you again for your time and work on this plugin!
Hey, we could probably add a hook for the Markdown integration, it must be trying to load the image from the same directory as the document when it should search for it in a special place. You can patch it in the markdown integration meanwhile.
Will do. Here's what I have hard coded and got it to render correctly.
elseif current_image and key == "url" then
current_image.url = "~/Documents/ObsidianVault/Attachments/" .. value
table.insert(images, current_image)
current_image = nil
end
@exosyphon In case that helps, you can use the resolve_image_path parameter in your config for this:
resolve_image_path = function(document_path, image_path, fallback)
local working_dir = vim.fn.getcwd()
-- Format image path for Obsidian notes
if (working_dir:find("path/to/your/vault")) then
return working_dir .. "/" .. image_path
end
-- Fallback to the default behavior
return fallback(document_path, image_path)
end,
@exosyphon In case that helps, you can use the
resolve_image_pathparameter in your config for this:resolve_image_path = function(document_path, image_path, fallback) local working_dir = vim.fn.getcwd() -- Format image path for Obsidian notes if (working_dir:find("path/to/your/vault")) then return working_dir .. "/" .. image_path end -- Fallback to the default behavior return fallback(document_path, image_path) end,
This is very helpful! One addition: find has an optional flag for the start position and a flag whether the string should be interpreted literally. The latter is important, so vault paths like obsidian-vault are detected. So change the line to this:
if (working_dir:find("path/to/match",1,true)) then
I'm having a problem getting this to work. The return paths are correct, but the plugin is not rendering any images.
resolve_image_path = function(document_path, image_path, fallback)
local working_dir = vim.fn.getcwd()
local notes_dir = vim.fn.expand(vim.env.NOTES_DIR)
-- Format image path for Obsidian notes
if working_dir:find(notes_dir, 1, true) then
return vim.fn.shellescape(notes_dir .. '/' .. image_path)
end
-- Fallback to the default behavior
return fallback(document_path, image_path)
end,
Here is an example doc:
File: $NOTES_DIR/folder/something.md
![[assets/something/hashmd5.png]]
image_path -> assets/something/hashmd5.png
The returned value from the resolve_image_path is correct: '/Users/ahmedelgabri/notes/assets/something/hashmd5.png'
If I run file '/Users/ahmedelgabri/notes/assets/something/hashmd5.png' I get this, so the image exists on disk which means the path is correct.
/Users/ahmedelgabri/notes/assets/something/hashmd5.png: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 5170x2802, components 3
But I don't see any previews, if I go an open a different .md file outside of the notes path, for example a README.md inside a repo, I can see image previews just fine.
@ahmedelgabri I investigated your problem a bit. I had to slightly modify your function to set the environment variable to my own Obsidian path:
vim.env.NOTES_DIR = "/home/fic/second-brain"
I was able to fix your function by removing the call to vim.fn.shellescape. In case it matter, I am on Linux. Please try the following:
local function resolver_ahmed_fixed(document_path, image_path, fallback)
-- I had to set my own vault root, but you
-- shouldn't need this line yourself
-- vim.env.NOTES_DIR = "/home/fic/second-brain"
local working_dir = vim.fn.getcwd()
local notes_dir = vim.fn.expand(vim.env.NOTES_DIR)
-- Format image path for Obsidian notes
if working_dir:find(notes_dir, 1, true) then
return (notes_dir .. "/" .. image_path)
end
-- Fallback to the default behavior
return fallback(document_path, image_path)
end
With this I was able to get your function to work with the environment variable.
Better Obsidian.nvim Integration
If you are like me, you often open Neovim in some other directory like $HOME, and then use obsidian.nvim's Quick Switcher to open your notes. In that case, the previous solutions won't work; the conditional check will fail because the vault path isn't in Neovim's cwd string.
The following solution works for me:
local function resolver(document_path, image_path, fallback)
local vault_dir = "path/to/your/vault"
-- Format path for Obsidian vault
if document_path:find(vault_dir, 1, true) then
return vault_dir .. "/" .. image_path
end
-- fallback to default
return fallback(document_path, image_path)
end
Simply checking whether the vault path is inside the document_path yields much more consistent results.
@ficcdaf thanks, I tried your fix but it still doesn't work. I don't use Obsidian.nvim, but I will give it a look
I found the way to make it work for me, you can try it. Here's the snipppet:
config = function()
require("image").setup({
backend = "kitty",
integrations = {
markdown = {
enabled = true,
clear_in_insert_mode = false,
download_remote_images = true,
only_render_image_at_cursor = true,
filetypes = { "markdown", "vimwiki" }, -- markdown extensions (ie. quarto) can go here
-- Custom function to resolve image paths
resolve_image_path = function(document_path, image_path, fallback)
-- Define the absolute path to your Assets directory
local assets_dir = vim.fn.expand("~/path/to/your/images/dir") -- not the path to vault, but to the assets dir
-- Check if the image_path is already an absolute path
if image_path:match("^/") then
-- If it's an absolute path, leave it unchanged
return image_path
end
-- Construct the new image path by prepending the Assets directory
local new_image_path = assets_dir .. "/" .. image_path
-- Check if the constructed path exists
if vim.fn.filereadable(new_image_path) == 1 then
return new_image_path
else
-- If the file doesn't exist in Assets, fallback to default behavior
return fallback(document_path, image_path)
end
end,
},
Sorry cos this is closed, I do it like this.. given that it is about obsidian integration, if you setup obsidian attachments like:
-- Specify how to handle attachments.
attachments = {
img_folder = "assets/imgs",
---@param client obsidian.Client
---@param path obsidian.Path the absolute path to the image file
---@return string
img_text_func = function(client, path)
path = client:vault_relative_path(path) or path
return string.format("", path.name, path)
end,
},
you can use the command ObsidianImgPaste to paste images from clipboard. and it will create a link like setup before in img_text_func.
and you could use this configuration in image:
require("image").setup({
backend = "kitty",
kitty_method = "normal",
integrations = {
markdown = {
enabled = true,
resolve_image_path = function(document_path, image_path, fallback)
local obsidian_client = require("obsidian").get_client()
local new_image_path = obsidian_client:vault_relative_path(image_path).filename
if vim.fn.filereadable(new_image_path) == 1 then
return new_image_path
else
return fallback(document_path, image_path)
end
end,
so if you change the assets directory in obsidian it keeps the integration with image. (you can add some checks on the require, in my case obsidian is loaded on markdown files so it just works)
@marcocofano I had to change the integration, the vault_relative_path of an asset doesn't return for me the subfolder...
local full_path = vault_img_folder .. "/" .. image_path
local new_image_path = obsidian_client:vault_relative_path(full_path).filename
vim.notify("new image path " .. new_image_path)
For anyone needing to format Obsidian links that have size properties set (if you use the Image Converter plugin these are set):
resolve_image_path = function(document_path, image_path, fallback)
local working_dir = vim.fn.getcwd()
-- Format image path for Obsidian notes
local obsidian_client = require("obsidian").get_client()
if working_dir:find(obsidian_client:vault_root().filename) then
if image_path:find("|") then
image_path = vim.split(image_path, "|")[1]
end
local assets_dir = "assets"
local result =
string.format("%s/%s/%s", obsidian_client:vault_root().filename, assets_dir, image_path)
return result
end
-- Fallback to the default behavior
return fallback(document_path, image_path)
end,
Not to necro this too hard, but I found the other solutions all didn't quite work for me. So, expanding a little on @ficcdaf's solution, I got
resolve_image_path = function(document_path, image_path, fallback)
local obs = require("obsidian").get_client()
local vault_root = obs:vault_root().filename
-- If we're in a vault, try using vault behavior
if document_path:find(vault_root, 1, 1) then
local obs_rel = obs:vault_relative_path(image_path)
if (
obs_rel
and vim.fn.filereadable(vault_root .. "/" .. obs_rel.filename) == 1
) then
return vault_root .. "/" .. obs_rel.filename
end
local attachment_path = document_path:match("(.*/)") .. "attachments/" .. image_path
if vim.fn.filereadable(attachment_path) then
return attachment_path
end
end
return fallback(document_path, image_path)
end,
I just added a little bit of fallibility so absolute paths work or things that actually were relative to the document path itself. obsidian.Client.vault_relative_path is also documented as having an optional return, though I'm not sure when that actually occurs.
Also added a thing for the subfolder under current folder behavior from the Obsidian editor.
There's still the minor note of this not handling base64 images in markdown correctly atm though, as the fallback is just resolve_absolute_path.
For any still finding their way here:
Obsidian.nvim supports images via snack.nvim, handles all these paths out of the box https://github.com/obsidian-nvim/obsidian.nvim/wiki/Images
If for any reason you don't want to install the obsidian plugin but still browse obsidian vaults without specifying the list of vault names, another interesting function is:
resolve_image_path = function(document_path, image_path, fallback)
local path = fallback(document_path, image_path)
local working_dir = vim.fn.getcwd()
local obsidian_conf = working_dir .. "/.obsidian/app.json"
if vim.uv.fs_stat(obsidian_conf) then
local fh = io.open(obsidian_conf, "r")
local json = nil
if fh then
json = fh:read("a")
fh:close()
json = vim.json.decode(json)
end
if json and json.attachmentFolderPath then
path = working_dir .. "/" .. json.attachmentFolderPath .. "/" .. image_path
end
end
return path
end
This works if your working directory is the vaults directory. I find that useful to quickly browse notes with a file picker.