Telescope preview issue
I'm trying to use your plugin to render previews in telescope. I've created a bufferpreviewer_maker like this:
local previewers = require('telescope.previewers')
local image_api = require('image')
local M = {}
local is_image_preview = false
local supported_images = { 'png', 'jpg', 'jpeg', 'heic', 'avif', 'gif', 'webp' }
local image = nil
local last_file_path = ''
local get_extension = function(filepath)
local split_path = vim.split(filepath:lower(), '.', { plain = true })
return split_path[#split_path]
end
local is_supported_image = function(filepath)
local extension = get_extension(filepath)
return vim.tbl_contains(supported_images, extension)
end
local delete_image = function()
if not image then
return
end
image:clear()
is_image_preview = false
end
local create_image = function(filepath, winid, bufnr)
image = image_api.from_file(filepath, { window = winid, buffer = bufnr })
if not image then
return
end
image:render()
is_image_preview = true
end
M.buffer_previewer_maker = function(filepath, bufnr, opts)
-- NOTE: Clear image when preview other file
if is_image_preview and last_file_path ~= filepath then
delete_image()
end
last_file_path = filepath
local extension = get_extension(filepath)
if is_supported_image(extension) then
create_image(filepath, opts.winid, bufnr)
else
previewers.buffer_previewer_maker(filepath, bufnr, opts)
end
end
M.teardown = function()
if is_image_preview then
delete_image()
end
end
return M
And I have the problem when I try to preview the image for the first time – it doesn't render, it renders on the second attempt.
I think that the problem is in the create_image function.
local create_image = function(filepath, winid, bufnr)
image = image_api.from_file(filepath, { window = winid, buffer = bufnr })
if not image then
return
end
image:render()
is_image_preview = true
end
Any ideas how to resolve the issue?
My env:
- OS: macOS Sonoma Version 14.5 (23F79)
- terminal: kitty 0.35.1
- NeoVim: 0.10
Plugin setup:
{
'vhyrro/luarocks.nvim',
priority = 1001, -- this plugin needs to run before anything else
opts = {
rocks = { 'magick' },
},
},
{
'3rd/image.nvim',
dependencies = { 'luarocks.nvim' },
config = function(_, opts)
opts.integrations = opts.integrations or {}
opts.integrations.markdown = opts.integrations.markdown or {}
opts.integrations.markdown.only_render_image_at_cursor = true
opts.hijack_file_patterns = opts.hijack_file_patterns or {}
opts.hijack_file_patterns = { '*.png', '*.jpg', '*.jpeg', '*.gif', '*.webp', '*.avif', '*.heic' }
opts.window_overlap_clear_enabled = true -- tried to remove or change it to false, the outcome was the same
require('image').setup(opts)
end,
},
I've added the echo statement in the create_image function
local create_image = function(filepath, winid, bufnr)
image = image_api.from_file(filepath, { window = winid, buffer = bufnr })
if not image then
return
end
image:render()
vim.api.nvim_echo({
{ 'image.is_rendered: ' .. tostring(image.is_rendered), nil },
}, false, {})
is_image_preview = true
end
And on the first try it prints image.is_rendered: false
But on the second try it returns the image.is_rendered: true
I'm not sure whether it should be the actual solution, but I've wrapped it with the vim.defer_fn and it works every time 😅
local create_image = function(filepath, winid, bufnr)
image = image_api.from_file(filepath, { window = winid, buffer = bufnr })
if not image then
return
end
vim.defer_fn(function()
image:render()
end, 0)
is_image_preview = true
end
@miszo I'd love to play around with this telescope previewer. Do you have a repo to install this as a telescope extension?
@exosyphon, didn't go that deep to set up this as a telescope extension.
Here's the code for:
- buffer_previewer
- image
- how I hooked it up to telescope
Moved the code into a single file, based on example above, would be awesome if there was an extension for this tho:
The change filepath = string.gsub(filepath, " ", "%%20"):gsub("\\", "") was from @jugarpeupv: https://github.com/3rd/image.nvim/issues/183#issuecomment-2574790371
function telescope_image_preview()
local supported_images = { "svg", "png", "jpg", "jpeg", "gif", "webp", "avif" }
local from_entry = require("telescope.from_entry")
local Path = require("plenary.path")
local conf = require("telescope.config").values
local Previewers = require("telescope.previewers")
local previewers = require("telescope.previewers")
local image_api = require("image")
local is_image_preview = false
local image = nil
local last_file_path = ""
local is_supported_image = function(filepath)
local split_path = vim.split(filepath:lower(), ".", { plain = true })
local extension = split_path[#split_path]
return vim.tbl_contains(supported_images, extension)
end
local delete_image = function()
if not image then
return
end
image:clear()
is_image_preview = false
end
local create_image = function(filepath, winid, bufnr)
image = image_api.hijack_buffer(filepath, winid, bufnr)
if not image then
return
end
vim.schedule(function()
image:render()
end)
is_image_preview = true
end
local function defaulter(f, default_opts)
default_opts = default_opts or {}
return {
new = function(opts)
if conf.preview == false and not opts.preview then
return false
end
opts.preview = type(opts.preview) ~= "table" and {} or opts.preview
if type(conf.preview) == "table" then
for k, v in pairs(conf.preview) do
opts.preview[k] = vim.F.if_nil(opts.preview[k], v)
end
end
return f(opts)
end,
__call = function()
local ok, err = pcall(f(default_opts))
if not ok then
error(debug.traceback(err))
end
end,
}
end
-- NOTE: Add teardown to cat previewer to clear image when close Telescope
local file_previewer = defaulter(function(opts)
opts = opts or {}
local cwd = opts.cwd or vim.loop.cwd()
return Previewers.new_buffer_previewer({
title = "File Preview",
dyn_title = function(_, entry)
return Path:new(from_entry.path(entry, true)):normalize(cwd)
end,
get_buffer_by_name = function(_, entry)
return from_entry.path(entry, true)
end,
define_preview = function(self, entry, _)
local p = from_entry.path(entry, true)
if p == nil or p == "" then
return
end
conf.buffer_previewer_maker(p, self.state.bufnr, {
bufname = self.state.bufname,
winid = self.state.winid,
preview = opts.preview,
})
end,
teardown = function(_)
if is_image_preview then
delete_image()
end
end,
})
end, {})
local buffer_previewer_maker = function(filepath, bufnr, opts)
-- NOTE: Clear image when preview other file
if is_image_preview and last_file_path ~= filepath then
delete_image()
end
last_file_path = filepath
if is_supported_image(filepath) then
filepath = string.gsub(filepath, " ", "%%20"):gsub("\\", "")
create_image(filepath, opts.winid, bufnr)
else
previewers.buffer_previewer_maker(filepath, bufnr, opts)
end
end
return { buffer_previewer_maker = buffer_previewer_maker, file_previewer = file_previewer.new }
end
local image_preview = telescope_image_preview()
require("telescope").setup({
defaults = {
file_previewer = image_preview.file_previewer,
buffer_previewer_maker = image_preview.buffer_previewer_maker,
},
extensions = {
file_browser = { hijack_netrw = true },
},
})
Nvim freezes somehow when i open the neotest summary. The require of image seems to be causing it (when i turn this off, neotest summary works):
local image_api = require("image")
@3rd @miszo any idea?
Could be something going wrong with the decorations provider or auto-commands.
Thanks for the reply, i turned my custom auto-commands and plugins off and nvim only crashes sometimes now in combination with local image_api = require("image").
Maybe it has something to do with: https://github.com/nvim-neotest/neotest/issues/441, i'll wait for that
Thanks for the reply, i turned my custom auto-commands and plugins off and nvim only crashes sometimes now in combination with
local image_api = require("image").Maybe it has something to do with: https://github.com/nvim-neotest/neotest/issues/441, i'll wait for that
But did image.nvim ever render an image in the session? I think we shouldn't even set up the handlers and providers until an image needs to be rendered, I'll looking into it and make sure we don't!
But did image.nvim ever render an image in the session
No it did not, thanks!
Moved the code into a single file, based on example above, would be awesome if there was an extension for this tho:
function telescope_image_preview() local supported_images = { "svg", "png", "jpg", "jpeg", "gif", "webp", "avif" } local from_entry = require("telescope.from_entry") local Path = require("plenary.path") local conf = require("telescope.config").values local Previewers = require("telescope.previewers") local previewers = require("telescope.previewers") local image_api = require("image") local is_image_preview = false local image = nil local last_file_path = "" local is_supported_image = function(filepath) local split_path = vim.split(filepath:lower(), ".", { plain = true }) local extension = split_path[#split_path] return vim.tbl_contains(supported_images, extension) end local delete_image = function() if not image then return end image:clear() is_image_preview = false end local create_image = function(filepath, winid, bufnr) image = image_api.hijack_buffer(filepath, winid, bufnr) if not image then return end vim.schedule(function() image:render() end) is_image_preview = true end local function defaulter(f, default_opts) default_opts = default_opts or {} return { new = function(opts) if conf.preview == false and not opts.preview then return false end opts.preview = type(opts.preview) ~= "table" and {} or opts.preview if type(conf.preview) == "table" then for k, v in pairs(conf.preview) do opts.preview[k] = vim.F.if_nil(opts.preview[k], v) end end return f(opts) end, __call = function() local ok, err = pcall(f(default_opts)) if not ok then error(debug.traceback(err)) end end, } end -- NOTE: Add teardown to cat previewer to clear image when close Telescope local file_previewer = defaulter(function(opts) opts = opts or {} local cwd = opts.cwd or vim.loop.cwd() return Previewers.new_buffer_previewer({ title = "File Preview", dyn_title = function(_, entry) return Path:new(from_entry.path(entry, true)):normalize(cwd) end, get_buffer_by_name = function(_, entry) return from_entry.path(entry, true) end, define_preview = function(self, entry, _) local p = from_entry.path(entry, true) if p == nil or p == "" then return end conf.buffer_previewer_maker(p, self.state.bufnr, { bufname = self.state.bufname, winid = self.state.winid, preview = opts.preview, }) end, teardown = function(_) if is_image_preview then delete_image() end end, }) end, {}) local buffer_previewer_maker = function(filepath, bufnr, opts) -- NOTE: Clear image when preview other file if is_image_preview and last_file_path ~= filepath then delete_image() end last_file_path = filepath if is_supported_image(filepath) then create_image(filepath, opts.winid, bufnr) else previewers.buffer_previewer_maker(filepath, bufnr, opts) end end return { buffer_previewer_maker = buffer_previewer_maker, file_previewer = file_previewer.new } endlocal image_preview = telescope_image_preview() require("telescope").setup({ defaults = { file_previewer = image_preview.file_previewer, buffer_previewer_maker = image_preview.buffer_previewer_maker, }, extensions = { file_browser = { hijack_netrw = true }, }, })
Is anyone else getting noticeable hang in the main loop when first selecting / rendering an image entry using this code?
I tried removing all Image:render() calls and replacing create_image with just
local create_image = async.void(function(filepath, winid, bufnr) --image = image_api.hijack_buffer(filepath, winid, bufnr) -- calls image.render internally image = image_api.from_file(filepath, { window = winid, buffer = bufnr }) end)
and there's still a freeze whenever selecting an image entry for the first time (Nothings rendered either, as expected). It doesn't happen if an image is already cached in state.images, so the freeze is due to image creation.
The freezing seems to happen in image.lua's from_file for me.
Am I misunderstanding how plenary's async.void can be used? Hows/is it possible to create the image objects outside the main loop to prevent freezing neovim? @3rd @sand4rt
https://www.lua.org/pil/9.4.html#:~:text=However%2C%20unlike%20%22real%22%20multithreading%2C%20coroutines%20are%20non%20preemptive It would be nice to run the image processing on a different thread, but didn't get into that yet as we'll need a system to also abort/invalidate operations.
Edit: this is again a limitation of working with the rock, we'll have better alternatives that non-blocking, but we can do it in Lua as well if the magick rock will survive http://lua-users.org/wiki/ThreadsTutorial
Since image.nvim "hijacks" a buffer, wouldn't it be possible to just bypass Telescope's check for "binary" files?
if Telescope just renders the buffer normally, image.nvim should hijack it and display the image properly, without additional code 🤔 ?
https://github.com/nvim-telescope/telescope.nvim/blob/85922dde3767e01d42a08e750a773effbffaea3e/lua/telescope/previewers/buffer_previewer.lua#L224
if (opts.ft == nil or opts.ft == "") and possible_binary then
putils.set_preview_message(bufnr, opts.winid, "Binary cannot be previewed", opts.preview.msg_bg_fillchar)
return
end
Moved the code into a single file, based on example above, would be awesome if there was an extension for this tho:
function telescope_image_preview() local supported_images = { "svg", "png", "jpg", "jpeg", "gif", "webp", "avif" } local from_entry = require("telescope.from_entry") local Path = require("plenary.path") local conf = require("telescope.config").values local Previewers = require("telescope.previewers") local previewers = require("telescope.previewers") local image_api = require("image") local is_image_preview = false local image = nil local last_file_path = "" local is_supported_image = function(filepath) local split_path = vim.split(filepath:lower(), ".", { plain = true }) local extension = split_path[#split_path] return vim.tbl_contains(supported_images, extension) end local delete_image = function() if not image then return end image:clear() is_image_preview = false end local create_image = function(filepath, winid, bufnr) image = image_api.hijack_buffer(filepath, winid, bufnr) if not image then return end vim.schedule(function() image:render() end) is_image_preview = true end local function defaulter(f, default_opts) default_opts = default_opts or {} return { new = function(opts) if conf.preview == false and not opts.preview then return false end opts.preview = type(opts.preview) ~= "table" and {} or opts.preview if type(conf.preview) == "table" then for k, v in pairs(conf.preview) do opts.preview[k] = vim.F.if_nil(opts.preview[k], v) end end return f(opts) end, __call = function() local ok, err = pcall(f(default_opts)) if not ok then error(debug.traceback(err)) end end, } end -- NOTE: Add teardown to cat previewer to clear image when close Telescope local file_previewer = defaulter(function(opts) opts = opts or {} local cwd = opts.cwd or vim.loop.cwd() return Previewers.new_buffer_previewer({ title = "File Preview", dyn_title = function(_, entry) return Path:new(from_entry.path(entry, true)):normalize(cwd) end, get_buffer_by_name = function(_, entry) return from_entry.path(entry, true) end, define_preview = function(self, entry, _) local p = from_entry.path(entry, true) if p == nil or p == "" then return end conf.buffer_previewer_maker(p, self.state.bufnr, { bufname = self.state.bufname, winid = self.state.winid, preview = opts.preview, }) end, teardown = function(_) if is_image_preview then delete_image() end end, }) end, {}) local buffer_previewer_maker = function(filepath, bufnr, opts) -- NOTE: Clear image when preview other file if is_image_preview and last_file_path ~= filepath then delete_image() end last_file_path = filepath if is_supported_image(filepath) then create_image(filepath, opts.winid, bufnr) else previewers.buffer_previewer_maker(filepath, bufnr, opts) end end return { buffer_previewer_maker = buffer_previewer_maker, file_previewer = file_previewer.new } endlocal image_preview = telescope_image_preview() require("telescope").setup({ defaults = { file_previewer = image_preview.file_previewer, buffer_previewer_maker = image_preview.buffer_previewer_maker, }, extensions = { file_browser = { hijack_netrw = true }, }, })
Shouldn't it be vim.fn.getcwd()?
Ey, thanks for the snippet code. I am having a problem though, when the .png file has white space it its filename, image.nvim is complaining, do you know how to solve it? i do not know how white space is handled, will have a look at source code but in case someone knows
Ok i just found the solution
if is_supported_image(filepath) then
filepath = string.gsub(filepath, " ", "%%20"):gsub("\\", "")
create_image(filepath, opts.winid, bufnr)
else
Ok i just found the solution
if is_supported_image(filepath) then filepath = string.gsub(filepath, " ", "%%20"):gsub("\\", "") create_image(filepath, opts.winid, bufnr) else
Ey thanks!
There is a minor problem with the script. If we go too fast in telescope, then the image will stay on top of the preview of the file.
@miszo @MuntasirSZN @jugarpeupv
You guys ok if I push this into a plugin repo? would be nice a Telescope + image.nvim plugin
Seems functional enough
@jcarlos7121 Feel free, maybe we can close the issue? Because it is resolved, in my opinion, and the dedicated plugin would help people as well.
Since
image.nvim"hijacks" a buffer, wouldn't it be possible to just bypass Telescope's check for "binary" files? if Telescope just renders the buffer normally, image.nvim should hijack it and display the image properly, without additional code 🤔 ?https://github.com/nvim-telescope/telescope.nvim/blob/85922dde3767e01d42a08e750a773effbffaea3e/lua/telescope/previewers/buffer_previewer.lua#L224
if (opts.ft == nil or opts.ft == "") and possible_binary then putils.set_preview_message(bufnr, opts.winid, "Binary cannot be previewed", opts.preview.msg_bg_fillchar) return end
@jcarlos7121 Before you create a plugin, please, consider my comment above. image.nvim can't hijack Telescope's preview because Telescope itself doesn't render the preview if it is a "binary" like file.
If we can bypass that, we won't need a new Plugin, as the hijack would do its thing.
Anyone been able to fix(or reduce) lag when previewing images in telescope?
carlos-algms wrote:
Since
image.nvim"hijacks" a buffer, wouldn't it be possible to just bypass Telescope's check for "binary" files? if Telescope just renders the buffer normally, image.nvim should hijack it and display the image properly, without additional code 🤔 ?https://github.com/nvim-telescope/telescope.nvim/blob/85922dde3767e01d42a08e750a773effbffaea3e/lua/telescope/previewers/buffer_previewer.lua#L224
if (opts.ft == nil or opts.ft == "") and possible_binary then putils.set_preview_message(bufnr, opts.winid, "Binary cannot be previewed", opts.preview.msg_bg_fillchar) return end
Good suggestion! Maybe something like this quick prototype in its place?
-- if we still dont have a ft we need to display the binary message
if (opts.ft == nil or opts.ft == "") and possible_binary then
putils.set_preview_message(bufnr, opts.winid, "")
local image_api = require("image")
local image = image_api.from_file(filepath, { window = opts.winid, buf = bufnr })
if image then
vim.defer_fn(function()
image:render()
end, 0)
end
return
end
That's just a quick test as I don't really am not very familiar with this extension, but it does successfully show e.g. jpeg images, while seemingly crashing when encountering gifs.
Additional limitations of the above:
- It doesn't clear the previously shown image.
- ~~It simultaneously displays some text version of the binary contents below the rendred images as well.~~ Update: Edited the example to get rid of that printed binary text in the background. Now it displays the error message background msg_bg_fillchar behind it instead as it's slightly less annoying. But still not ideal.
Obviously one should also verify that the file is in fact an image, and not just try to render an image from anything detected as a binary.
Ey @miyl @jcarlos7121 @exosyphon @miszo @carlos-algms! Is this code still working for you guys?
For me it is not working anymore
I am pointing to commit 21909e3eb03bc738cce497f45602bf157b396672, since it seems a "bug" was introduced recently, i can load images well etc
Sorry @jugarpeupv, can't help you here. I've dropped usage of both this plugin and telescope in favor of the folke/snacks.nvim.
Same, I dropped it in favour of Snacks, it just works ™️. Telescope was never meant to it, and I'm afraid it would require too many changes to achieve a proper "plugin" integration 😞