diff --git a/CHANGELOG.md b/CHANGELOG.md index a02e12cd..0a041f8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,12 @@ CHANGELOG ```sh fzf --preview='fzf-preview.sh {}' ``` -- (Experimental) Sixel and Kitty image support now also available on Windows +- (Experimental) iTerm2 inline image protocol support in preview window + ```sh + # Using https://iterm2.com/utilities/imgcat + fzf --preview 'imgcat -W $FZF_PREVIEW_COLUMNS -H $FZF_PREVIEW_LINES {}' + ``` +- (Experimental) Sixel, Kitty, and iTerm2 image support now also available on Windows - HTTP server can be configured to accept remote connections ```sh # FZF_API_KEY is required for a non-localhost listen address diff --git a/bin/fzf-preview.sh b/bin/fzf-preview.sh index d72cd2d4..8991e3de 100755 --- a/bin/fzf-preview.sh +++ b/bin/fzf-preview.sh @@ -4,8 +4,9 @@ # image in the preview window of fzf. # # Dependencies: -# - https://github.com/hpjansson/chafa # - https://github.com/sharkdp/bat +# - https://github.com/hpjansson/chafa +# - https://iterm2.com/utilities/imgcat if [[ $# -ne 1 ]]; then >&2 echo "usage: $0 FILENAME" @@ -44,6 +45,7 @@ elif ! [[ $KITTY_WINDOW_ID ]] && (( FZF_PREVIEW_TOP + FZF_PREVIEW_LINES == $(stt dim=${FZF_PREVIEW_COLUMNS}x$((FZF_PREVIEW_LINES - 1)) fi +# 1. Use kitty icat on kitty terminal if [[ $KITTY_WINDOW_ID ]]; then # 1. 'memory' is the fastest option but if you want the image to be scrollable, # you have to use 'stream'. @@ -52,10 +54,21 @@ if [[ $KITTY_WINDOW_ID ]]; then # This confuses fzf and makes it render scroll offset indicator. # So we remove the last line and append the reset code to its previous line. kitty icat --clear --transfer-mode=memory --stdin=no --place="$dim@0x0" "$file" | sed '$d' | sed $'$s/$/\e[m/' + +# 2. Use chafa with Sixel output elif command -v chafa > /dev/null; then chafa -f sixel -s "$dim" "$file" # Add a new line character so that fzf can display multiple images in the preview window echo + +# 3. If chafa is not found but imgcat is available, use it on iTerm2 +elif command -v imgcat > /dev/null; then + # NOTE: We should use https://iterm2.com/utilities/it2check to check if the + # user is running iTerm2. But for the sake of simplicty, we just assume + # that's the case here. + imgcat -W "${dim%%x*}" -H "${dim##*x}" "$file" + +# 4. Cannot find any suitable method to preview the image else file "$file" fi diff --git a/src/terminal.go b/src/terminal.go index 747001ef..f48e5f25 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -66,7 +66,8 @@ func init() { // * https://github.com/tmux/tmux/wiki/FAQ#what-is-the-passthrough-escape-sequence-and-how-do-i-use-it // * https://sw.kovidgoyal.net/kitty/graphics-protocol // * https://en.wikipedia.org/wiki/Sixel - passThroughRegex = regexp.MustCompile(`\x1bPtmux;\x1b\x1b.*?[^\x1b]\x1b\\|\x1b(_G|P[0-9;]*q).*?\x1b\\\r?`) + // * 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`) } type jumpMode int @@ -125,7 +126,7 @@ type previewed struct { numLines int offset int filled bool - sixel bool + image bool wipe bool wireframe bool } @@ -2027,7 +2028,7 @@ func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unc var ansi *ansiState spinnerRedraw := t.pwindow.Y() == 0 wiped := false - sixel := false + image := false wireframe := false Loop: for _, line := range lines { @@ -2055,18 +2056,25 @@ Loop: t.pwindow.Move(y, x) } for idx, passThrough := range passThroughs { - // Handling Sixel output + // Handling Sixel/iTerm image requiredLines := 0 isSixel := strings.HasPrefix(passThrough, "\x1bP") - if isSixel { + isItermImage := strings.HasPrefix(passThrough, "\x1b]1337;") + isImage := isSixel || isItermImage + if isImage { t.previewed.wipe = true - if t.termSize.PxHeight > 0 { + // NOTE: We don't have a good way to get the height of an iTerm image, + // so we assume that it requires the full height of the preview + // window. + requiredLines = height + + if isSixel && t.termSize.PxHeight > 0 { rows := strings.Count(passThrough, "-") requiredLines = int(math.Ceil(float64(rows*6*t.termSize.Lines) / float64(t.termSize.PxHeight))) } } - // Render wireframe when Sixel image cannot be displayed entirely + // Render wireframe when the image cannot be displayed entirely if requiredLines > 0 && y+requiredLines > height { top := true for ; y < height; y++ { @@ -2081,17 +2089,17 @@ Loop: } // Clear previous wireframe or any other text - if (t.previewed.wireframe || isSixel && !t.previewed.sixel) && !wiped { + if (t.previewed.wireframe || isImage && !t.previewed.image) && !wiped { wiped = true for i := y + 1; i < height; i++ { t.pwindow.MoveAndClear(i, 0) } // Required for tcell to clear the previous text - if !t.previewed.sixel { + if !t.previewed.image { t.tui.Sync(false) } } - sixel = sixel || isSixel + image = image || isImage if idx == 0 { t.pwindow.MoveAndClear(y, x) } else { @@ -2154,7 +2162,7 @@ Loop: } lineNo++ } - t.previewed.sixel = sixel + t.previewed.image = image t.previewed.wireframe = wireframe }