kamiyaa / joshuto

ranger-like terminal file manager written in Rust
https://crates.io/crates/joshuto
GNU Lesser General Public License v3.0
3.43k stars 150 forks source link

Image Previews w/ Kitty's Icat #127

Closed is0n closed 2 years ago

is0n commented 2 years ago

Kitty is the terminal emulator I use and it supports images with the help of icat.

The following is what I use to preview images in both Joshuto and Lf...

#!/usr/bin/env bash

case $(file -b --mime-type "$1") in
    image/*)
        kitty +kitten icat --transfer-mode file --place "$2x$3@$4x$5" "$1"
        ;;
esac

exit 0

The only problem with the script above is that Joshuto positions the image incorrectly (but the dimensions are accurate).

Lf Demo:

Screen Shot 2022-01-15 at 10 43 43 PM

Joshuto Demo:

Screen Shot 2022-01-15 at 10 43 28 PM

Both programs use the same script, however, Joshuto is unable to position the image correctly.

Is there a way to position the image correctly?

DLFW commented 2 years ago

Hi @is0n!

It seems the order of dimension and position are different in lf. Maybe you can try kitty +kitten icat --transfer-mode file --place "$4x$5@$2x$3" "$1".

If you want to use the same script, maybe you can create a wrapper script for one of the file managers which just flips the order of the parameters and then calls your image script, or let the script check it's parent process.

However, I'm not sure if that will be sufficient to get the image preview working. For my understanding, icat does not remove the last image automatically when showing a new one. Also, when selecting a directory or a file without a preview (e.g. file size > limit for preview), the image must be removed. So, I expected some kitty +kitten icat --clear or something in your script.

Were you able to handle this already?

is0n commented 2 years ago

Image Viewing

Under closer inspection, I found that $4 returns 0 and $5 returns 1. I believe this is what is causing the odd positioning. Could you maybe use the following when previewing an image and tell me the values of $4 and $5?

#!/usr/bin/env bash

case $(file -b --mime-type "$1") in
    image/*)
        echo "$2x$3@$4x$5"
        ;;
esac

exit 0

Demo w/ Script Above:

Screen Shot 2022-01-16 at 8 08 15 AM

Image Cleaning:

So, I expected some kitty +kitten icat --clear or something in your script

Lf has a cleaner option and I have that set to a script that really is one line long...

#!/usr/bin/env bash

kitty +kitten icat --transfer-mode file --clear

What you could do instead is just place that one line at the top of the preview script. like this...

#!/usr/bin/env bash

kitty +kitten icat --transfer-mode file --clear

case $(file -b --mime-type "$1") in
    image/*)
        kitty +kitten icat --transfer-mode file --place "$2x$3@$4x$5" "$1"
        ;;
    text/*)
        cat "$1"
        ;;
esac

exit 0

Some Info on Lf's Cleaner Option:

Set the path of a cleaner file. This file will be called if previewing is enabled, the previewer is set, and the previously selected file had its preview cache disabled. The file should be executable. One argument is passed to the file; the path to the file whose preview should be cleaned. Preview clearing is disabled when the value of this option is left empty.

DLFW commented 2 years ago

I think we have a misunderstanding. :) Seems we are talking about different scripts.

Using the preview script for showing image previews is not a good idea because Joshuto caches the preview and the script won't be called every time a preview appears. Also, there is no "cleaner" option as in lf.

Instead, Joshuto provides two hooks to invoke separate scripts for showing and removing image previews.

The "preview shown" hook script is the script which gets the parameters as I described above, not the preview script which creates the textual output.

I think Joshuto needs some more documentation on this topic.

is0n commented 2 years ago

So if I were to use preview_shown_hook_script = "~/bin/joshuto-preview" instead of preview_script = "~/bin/joshuto-preview" would this work?

DLFW commented 2 years ago

No.

In short: Text preview and image preview are handled in different scripts and are indepedent of each other in Joshuto.

preview_script is for the textual preview, you likely want to keep that. The result is cached, so this script is only called once per file.

preview_shown_hook_script is for any external preview functions like image previews. Nothing is cached, the script is called every time the preview pane appears or the previewed file changes. preview_removed_hook is the counter part. It's called every time the preview view in Joshuto disappears, e.g. when the selection changes from a file to a directory. The stdouts of these hook scripts are ignored. They can't be used for a textual preview. If you don't want to have any textual output for images, just exit your joshuto-preview for image/* without writing anything to stdout.

BTW, the little documentation we have also shows how information can be passed from the preview script to the image-preview-scripts.

Hope that helps.

is0n commented 2 years ago

Alright, I think I might understand, however, I still can't get image previews.

~/.config/joshuto/joshuto.toml:

[preview]
max_preview_size = 2097152 # 2MB
preview_images = true

# This Works
preview_script = "~/bin/joshuto-preview"

# These Do Not Work
preview_shown_hook_script = "~/.config/joshuto/on_preview_shown"
preview_removed_hook_script = "~/.config/joshuto/on_preview_removed"

~/bin/joshuto-preview:

#!/usr/bin/env bash

case $(file -b --mime-type "$1") in
    text/*)
        cat "$1"
        ;;
esac

exit 0

~/.config/joshuto/on_preview_shown:

#!/usr/bin/env bash

case $(file -b --mime-type "$1") in
    image/*)
        kitty +kitten icat --transfer-mode file --place "$4x$5@$2x$3" "$1"
        ;;
    *)
        kitty +kitten icat --clear
        ;;
esac

~/.config/joshuto/on_preview_removed:

#!/usr/bin/env bash

kitty +kitten icat --transfer-mode file --clear

No images are shown, however, an empty box does show. I tried echo "$1 $2 $3 $4 $5 >> ~/test so see what values are being used but nothing is written. This makes me believe that not even the file path/name is being given to ~/.config/joshuto/on_preview_shown or that the script is not being ran.

Demo:

Screen Shot 2022-01-16 at 10 34 08 AM

kamiyaa commented 2 years ago

I'm doing some testing atm with kitty. Might modify scope.sh to include x,y coordinates as well as positional independent parameters so its easier to manage.

DLFW commented 2 years ago

Might modify scope.sh to include x,y coordinates

If the result of scope.sh depends (potentially) on the geometry, Joshuto must discard all cached previews when the terminal's geometry changes. Just a thought.

kamiyaa commented 2 years ago

Seems like it works if we pass in the x/y coordinates of where the preview needs to be :+1: .

Frame 1

However, I'm still running into issues with it not clearing correctly. kitty +kitten icat --clear says the terminal doesn't support graphics (even though the image preview is working fine)

local mimetype="${1}"
case "${mimetype}" in
    ## Image
    image/*)
        # kitty +kitten icat --clear
        kitty +kitten icat \
            --transfer-mode file \
            --place "${PREVIEW_WIDTH}x${PREVIEW_HEIGHT}@${PREVIEW_X_COORD}x${PREVIEW_Y_COORD}" \
            "${FILE_PATH}"
        exit 7
        ;;
esac
kamiyaa commented 2 years ago

Now joshuto will pass arguments by first stating the option, instead of depending on positional arguments. Here

You can take a look at https://github.com/kamiyaa/joshuto/blob/main/config/preview_file.sh for reference. And here is the code for getting kitty previews to work (but not removing the previews): https://github.com/kamiyaa/joshuto/blob/main/config/preview_file.sh#L171

DLFW commented 2 years ago

@kamiyaa: You're calling icat from the normal preview script, right? How does that work with caching?

I also did a quick try and I have the same error message from Kitty for both, showing and clearing. But I also use the hook scripts for both. If I call the hook scripts directly from the shell, they work just fine. Maybe Kitty has some issue with the spawning or something. On the Kitty website, I only find a statement that icat might not work in a terminal multiplexer. In a Kitty issue, the Kitty maintainer mentioned that for example TERM must be set for icat to work.

It might be doable with some wrapper script like for Überzug.

BTW, using Kitty 0.24.1 from the Arch repo.

is0n commented 2 years ago

This is the script I'm using to preview images and I can confirm that images are only previewed once because of caching.

Script:

#!/usr/bin/env bash

kitty +kitten icat --transfer-mode file --clear

while [ "$#" -gt 0 ]; do
    case "$1" in
        "--path")
            shift
            FILE_PATH="$1"
            ;;
        "--preview-width")
            shift
            PREVIEW_WIDTH="$1"
            ;;
        "--preview-height")
            shift
            PREVIEW_HEIGHT="$1"
            ;;
        "--x-coord")
            shift
            PREVIEW_X_COORD="$1"
            ;;
        "--y-coord")
            shift
            PREVIEW_Y_COORD="$1"
            ;;
    esac
    shift
done

case $(file -b --mime-type "${FILE_PATH}" | ghead -n 1) in
    image/*)
    kitty +kitten icat \
        --transfer-mode file \
        --place "${PREVIEW_WIDTH}x${PREVIEW_HEIGHT}@${PREVIEW_X_COORD}x${PREVIEW_Y_COORD}" \
        "${FILE_PATH}"
            exit 7
            ;;
    text/*)
        cat "${FILE_PATH}"
        exit 0
        ;;
esac

exit 1

I think that adding the option to not cache previews would be nice. It would be even better if you could specify which previews would be cached based on the file's mimetype.

kovidgoyal commented 2 years ago

https://github.com/kovidgoyal/kitty/commit/92075a44d88d79286d3787201fbb9be480ee9e7e

is0n commented 2 years ago

Update:

I have gotten image previews to work in Joshuto! 🥳

Image Previews in Joshuto with Kitty's Icat

Demo:

https://user-images.githubusercontent.com/57725322/149826780-37778eae-28b8-4986-9d0a-133cef4aa0b1.mp4

A few notes:

  1. Joshuto will experience a slow down if too many files are being hovered over as it is executing on_preview_removed.sh over and over again*
  2. Previewing with Überzug is a lot faster (but is limited to X11)
  3. This script is not as fast as Ranger's script
  4. Image Previewing does not work in tmux
  5. Works on MacOS (and Linux) but is limited to Kitty

I also think that the file path used in preview.sh should be assigned to $1 so that the while loop in not needed.

*This method of clearing images is similar to Lf's but I haven't experienced any slow down in Lf.

kamiyaa commented 2 years ago

The slow down could be attributed to joshuto synchronously calling these scripts. It might be possible to push it to a separate thread, but will probably need better coordination so situations where image2 preview loads first then image1 preivew loads and overrides image2 won't occur.

@DLFW probably has more to say on this as he architect the majority of this work 👍

is0n commented 2 years ago

The slow down could be attributed to joshuto synchronously calling these scripts. It might be possible to push it to a separate thread, but will probably need better coordination so situations where image2 preview loads first then image1 preivew loads and overrides image2 won't occur

In my cleaner script for Lf (which is the exact same as on_preview_removed.sh), I tested to see how many times Lf would call the script.

I found that...

  1. The cleaner script was only called when the preview window of an image was closed (ie. when a new file was being previewed).
  2. If the previewer script had not finished loading the image before another file had been selected, the script would then be terminated and the cleaner script would not be called. It follows that the previewer script would be called for the new file.

By following what Lf does, it's possible to avoid this whole situation.

DLFW commented 2 years ago

Hey there, nice that it's at least working with icat! Congrats! :)

So, it should not be an issue with synchronous calls. The hook-scripts are spawned and Joshuto just continues. It' just that Joshuto is not taking care what happens in these scripts and from the video I can see that loading the thumbs takes really long in this case.

I would be in favor of keeping Joshuto's part simple. The main reason is that the preview-hooks can be used for anything. Send a message to another program, send something to the window manger, whatever. I would not put too many assumptions on what these scripts are used for by the creative people out there. ;)

Of course it's a matter of taste, but I would prefer to provide easy-to-apply recipes for these things instead of putting too many use-case specific features into Joshuto. Handling images is - unfortunately - always a hack. Opinions?

is0n commented 2 years ago

What do you think about passing the formerly previewed file to the removed-hook and as a 2nd argument also to the shown-hook? Then this could be solved by the icat-specific scripts.

I think that it may help to know this but I think the problem is more with the removed-hook and how it is always being called. As I said before, Lf would only call the cleaner script when the preview window on an image was closed so I think that giving the removed hook the formerly previewed file and current file would be a better idea.

For now I'm not using the removed_hook script as the only time it's ever needed in my experience if when going from a file to a directory. If I could run the removed_hook (only once) when previewing a directory, that would remove the image and not cause a slowdown.

An example with pseudo code:

#!/usr/bin/env bash

file="$1"
prev_file="$2"

# checks if current file is a directory and then checks if the previous file was an image
if [ $(file -b --mime-type "$file") == "inode/directory" && $(file -b --mime-type "$prev_file") == "image/*" ]; then
  kitty +kitten icat --transfer-mode=file --clear
fi

exit 0
DLFW commented 2 years ago

Well, the removed-hook should only be called when the preview area disappears. This may happen if...

Except for this “first-preview delay”, each change of the selection should cause only one call of a hook script, either the remove-hook, or the shown-hook. So, if the removed-hook is called, there is no preview to be shown ergo there is no "current file" to hand over to the script.

Your pseudo code should reduce to something like

#!/usr/bin/env bash
prev_file="$1"

if [ $(file -b --mime-type "$prev_file") == "image/*" ]; then
  kitty +kitten icat --transfer-mode=file --clear
fi

exit 0

The same piece of logic would be needed in the shown hook to remove a former preview. (Still wondering if the ...icat... --clear is so expensive that it's worth the extra check...)

However, I just tested this again to be sure and it seems we have a bug here. On some directories, the removed-hook is called over and over again, even without changing the selection. I need to further investigate that.

DLFW commented 2 years ago

The bug got just fixed and merged (#128). Maybe this was the main part of the problem. @is0n, can you check if this solves your problem already without any checks on the former file type? Basically with the simple logic you described before.

is0n commented 2 years ago

The bug got just fixed and merged (#128). Maybe this was the main part of the problem.

This definitely solves the problem and makes viewing images so much faster 😄

I'll be updating the gist to include some minor changes. Do you think it would be a good idea to mention this in image_previews.md?

DLFW commented 2 years ago

That's really good to hear! :smiley:

I'll be updating the gist

Thanks!

Do you think it would be a good idea to mention this in image_previews.md?

Absolutely! Maybe we could split this document into three: a general description, an Überzug recipe, and an icat recipe... Feel free to provide a PR, otherwise I will pick this up the next days/next week, after trying the Kitty solution myself.

So, to conclude, can you confirm that it works properly without Joshuto sending a sigterm to the shown-hook?

is0n commented 2 years ago

So, to conclude, can you confirm that it works properly without Joshuto sending a sigterm to the shown-hook?

I can confirm that Image Previews w/ Kitty's Icat is working perfectly (for me at least) 🥳

Absolutely! Maybe we could split this document into three: a general description, an Überzug recipe, and an icat recipe...

Alright then! I will be including a section for icat in image_previews.md with explanations for the code in the very near future :)