junegunn / fzf

:cherry_blossom: A command-line fuzzy finder
https://junegunn.github.io/fzf/
MIT License
63.89k stars 2.37k forks source link

Image preview not working with Wezterm's implementation of iTerm2 graphics protocol #3646

Open unrealapex opened 6 months ago

unrealapex commented 6 months ago

Info

Problem / Steps to reproduce

Is it possible to get Wezterm's implementation of iTerm2's image protocol working in FZF preview? iTerm2 image support integration is available, however, it seems that it does not extend to Wezterm's implementation:

 fzf --preview 'wezterm imgcat {}'

See https://github.com/wez/wezterm/issues/5049

junegunn commented 6 months ago

What is the difference between wezterm imgcat and imgcat? Is there any reason you have to use the former?

unrealapex commented 6 months ago

I am not sure regarding the technical reasons why they are different, but I believe Wezterm's implementation is done natively in Rust and also renders images with better performance than iTerm2's imgcat script. You may wish to look at its help page. As for using the former, Wezterm's imgcat comes out of the box...

junegunn commented 6 months ago

So here's what I've found.

The output of imgcat ends with ^G as documented in the official specification (See "Protocol" section of https://iterm2.com/documentation-images.html). However, the output of wezterm imgcat ends with <ESC>\.

$ imgcat fzf.png | tr -d '\n' | tail -c 1 | ruby -ne 'p $_'
"\a"

$ wezterm imgcat fzf.png | tr -d '\n' | tail -c 2 | ruby -ne 'p $_'
"\e\\"

Is this intentional? Anyway, for some reason, iTerm2 also allows this case and prints the image.

It is trivial for fzf to change its protocol parser to support this alternative sequence.

diff --git a/src/terminal.go b/src/terminal.go
index d267aec..2069571 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -67,7 +67,7 @@ func init() {
    // * https://sw.kovidgoyal.net/kitty/graphics-protocol
    // * https://en.wikipedia.org/wiki/Sixel
    // * https://iterm2.com/documentation-images.html
-   passThroughRegex = regexp.MustCompile(`\x1bPtmux;\x1b\x1b.*?[^\x1b]\x1b\\|\x1b(_G|P[0-9;]*q).*?\x1b\\\r?|\x1b]1337;.*?\a`)
+   passThroughRegex = regexp.MustCompile(`\x1bPtmux;\x1b\x1b.*?[^\x1b]\x1b\\|\x1b(_G|P[0-9;]*q).*?\x1b\\\r?|\x1b]1337;.*?(\a|\x1b\\)`)
 }

 type jumpMode int
unrealapex commented 6 months ago

I'm going to assume this alternate escape sequence was unintentional. @wez may have a different answer.

wez commented 6 months ago

OSC sequences are defined as being terminated by the ST sequence, which is ESC \. They can also be terminated by ^G. Since the iterm2 protocol is using OSC sequences, parsers must support both of these forms of termination.

junegunn commented 6 months ago

Thanks for the comment. I will update the regex.

But,

# Dump the output to a file
wezterm imgcat fzf.png > /tmp/image

# With the updated regex, fzf can render the image in the preview window
fzf --preview 'cat /tmp/image'

# However, this doesn't work. 'wezterm imgcat' process doesn't finish and just hang.
fzf --preview 'wezterm imgcat fzf.png'
wez commented 6 months ago

hmm, wezterm also tries to query terminal properties via the XTVERSION escape sequence, and then the screen dimensions via a variety of probing escape sequences. Perhaps those also need to be allowed through fzf's processing layer and the responses allowed to be read?

wezterm imgcat assumes that every terminal will be able to respond to the \u001b[c sequence, and uses that to avoid waiting forever to receive a response. I think in this case fzf may be preventing that from happening.

Here's a snippet from an ascii-cast showing the probe escapes that are emitted prior to emitting the iterm2 image data OSC sequence:

[9.833448,"o","\u001b[>q\u001b[c"]
[9.837395,"o","\u001b[>q\u001b[c"]
[9.841322,"o","\u001b[18t\u001b[16t\u001b[c"]
junegunn commented 6 months ago

Thanks for the pointers.

I've discovered that the hanging has something to do with setpgid. This is a minimal code to reproduce the problem.

package main

import (
    "os/exec"
    "syscall"
)

func main() {
    cmd := exec.Command("/bin/sh", "-c", "wezterm imgcat ../images/fzf.png")
    cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
    cmd.Start()
    cmd.Wait()
}

This program hangs, and not setting setpgid fixes the issue. However, fzf sets the flag so that it can easily kill the preview command and its child processes at once.

https://github.com/junegunn/fzf/blob/686f9288fc841924d7fdad6f4b1740afd5e83c86/src/util/util_unix.go#L25-L27

Not setting this in fzf, unfortunately, doesn't make wezterm imgcat work as a preview command. fzf reads the response sequences directly from /dev/tty as the user input and pastes them on its prompt.

image

So there doesn't seem to be an easy way to fix it right now, and I recommend using one of the alternatives for now, such as chafa.

# You need to pass the size of the preview window
fzf --preview 'chafa -f iterm -s ${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES} {}'