Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Telescope preview issue #183

Open
miszo opened this issue Jun 22, 2024 · 13 comments
Open

Telescope preview issue #183

miszo opened this issue Jun 22, 2024 · 13 comments

Comments

@miszo
Copy link

miszo commented Jun 22, 2024

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.

1st preview attempt 2nd preview 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,
  },
@miszo
Copy link
Author

miszo commented Jun 22, 2024

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

CleanShot 2024-06-22 at 21 00 33@2x

But on the second try it returns the image.is_rendered: true

CleanShot 2024-06-22 at 21 02 26@2x

@miszo
Copy link
Author

miszo commented Jun 22, 2024

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

@exosyphon
Copy link

@miszo I'd love to play around with this telescope previewer. Do you have a repo to install this as a telescope extension?

@miszo
Copy link
Author

miszo commented Jul 2, 2024

@exosyphon, didn't go that deep to set up this as a telescope extension.

Here's the code for:

@sand4rt
Copy link

sand4rt commented Aug 12, 2024

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 }
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 },
    },
})

@sand4rt
Copy link

sand4rt commented Aug 25, 2024

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?

@3rd
Copy link
Owner

3rd commented Aug 25, 2024

Could be something going wrong with the decorations provider or auto-commands.

@sand4rt
Copy link

sand4rt commented Aug 26, 2024

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: nvim-neotest/neotest#441, i'll wait for that

@3rd
Copy link
Owner

3rd commented Aug 26, 2024

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: nvim-neotest/neotest#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!

@sand4rt
Copy link

sand4rt commented Aug 26, 2024

But did image.nvim ever render an image in the session

No it did not, thanks!

@Kimononono
Copy link

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 }
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 },
    },
})

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

@3rd
Copy link
Owner

3rd commented Sep 8, 2024

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

@carlos-algms
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants