feat: built-in image preview via Kitty graphics protocol#2621
Conversation
Prevent old text from previous file previews lingering on screen when switching to a new image. **image preview (kitty.go)** - Unlock the old region in clearKitty after deleting images so tcell can redraw the full pane for the new preview - Fill preview pane cells with spaces in tcells buffer to erase old text before rendering the new image - Emit clear-to-end-of-line ANSI codes for each row directly to the terminal for additional terminal-level cleanup - Merge clear sequence and kitty image output into one synchronized update to avoid flickering
|
Just a few notes:
|
|
@valoq |
CatsDeservePets
left a comment
There was a problem hiding this comment.
In general, I am a huge fan of adding support for the kitty image protocol to lf. Thanks for your work so far.
I am not a big fan of the built-in preview generation. Just like with sixel, lf should only parse (and display) the kitty protocol when passed by the user inside his previewer script.
Always forcing a kitty preview for every supported image file is bad:
- What if the terminal emulator doesn't support it (like the default
macOSandWindowsterminals)? - What if the user wants to have a different preview (like different scaling and placement, or no image preview at all)?
Extend the built-in Kitty graphics protocol preview to support additional image formats (BMP, TIFF, WebP) using the golang.org/x/image package. Remove the dead no-op "kitty" config option handler. **Kitty image preview (kitty.go)** - Register BMP, TIFF, and WebP decoders via blank imports from golang.org/x/image - Add ".bmp", ".tiff", ".tif", ".webp" to the imageExtensions map - Reorganize imports into standard-library, tcell, and x/image groups **Config (eval.go)** - Remove the "kitty"/"nokitty"/"kitty!" case in setExpr.eval() — this was a no-op (err = nil) that existed only to silently accept the option in config files; Kitty protocol is now always active **Dependencies (go.mod, go.sum)** - Add golang.org/x/image v0.42.0 (direct) - Bump golang.org/x/text v0.37.0 → v0.38.0 (indirect)
Consolidate the separate `sixel` and `kitty` bool fields on the `reg` struct into a single `previewKind` enum, eliminating the impossible state where both could be true simultaneously. **previewKind type (ui.go)** - Introduce `previewKind` enum with `previewText`, `previewSixel`, and `previewKitty` constants - Replace `reg.sixel bool` + `reg.kitty bool` with `reg.kind previewKind` - Update `printReg()` conditions from field access to comparison (`reg.sixel` → `reg.kind == previewSixel`) **readLines (misc.go)** - Change return signature from `(lines, binary, sixel, kitty)` to `(lines, binary, kind previewKind)` - Set `kind = previewSixel` / `kind = previewKitty` in place of separate bool assignments **Tests (misc_test.go)** - Update all 30+ test cases: replace `sixel`/`kitty` bool fields with single `kind` field using the new constants - Update `TestReadLinesChafa` assertions accordingly **Preview / nav (nav.go)** - Update `readLines()` callsites to use single `kind` return value - Replace `reg.sixel = true` / `reg.kitty = true` assignments with `reg.kind = previewKitty` - Simplify resize cache eviction: `r.sixel || r.kitty` → `r.kind != previewText`
Old text from a previous file preview would linger around or behind the sixel image because nothing explicitly cleared the pane beforehand. **sixelScreen (sixel.go)** - Clear the preview pane in tcell's buffer by writing spaces to every cell in `printSixel()` before rendering - Emit `\033[Y;XH\033[0K` sequences directly to the terminal for each preview row to erase any leftover text outside tcell's control - Flush tcell with `screen.Show()` before writing sixel data so the cleared buffer is visible
Fully agree here. Lf was created as a minimal file manager with basic default features and great extensibility through the config. External previews is exactly for this kind of feature. |
|
Well, if someone has a better solution, just told me, and I will close this PR. And thanks. |
|
@RenovZ Then In my case, I'm using kitty as my terminal simulator, it's enough to do it by redirecting kitty protocol content to The problem is you have to position image content to the right place, so that it will not ruins content drawn by lf. If you do nothing Workaround is simple, you prepend cursor positioning ANSI control sequence to the content, so that image content shows up in preview area. Then print it to Like this: chafa -f kitty -s $"($context.width)x($context.height)" $context.file e> /dev/null
| $"\x1b[2;149H" + $in
| save -f /dev/tty(This snippet is written in nushell script, but I think it's clear enough to understand). Here, |
|
@SirZenith I don't like your solution, it's just like a very hack way fully bypass lf, I have tried this with bash or fish before, but failed. The problem still stay there. |
In my opinion, previewer script itself is an extension (or plugin) to lf, and its purpose is to draw thing in preview window. Since lf pass preview window's scale and position to previewer script, it makes no difference if the content is drawn by lf or not. So I chose to tell lf "draw nothing there", then ask kitty to put an image there for me. Usage of ANSI control sequence is just what kitty does with its
By the way, I think it has nothing to do with what shell you're using. May I ask what have you done and what do you see in your terminal? |
This PR adds native image preview support using the Kitty graphics protocol, allowing
lfto display image previews (PNG, JPEG, GIF) directly in any terminal emulator that supports the protocol — no external dependencies required.Previously,
lfonly supported Sixel-based image previews. Kitty protocol support broadens compatibility to modern terminals like Kitty, WezTerm, Ghostty...use case:
chafa -f kitty -s "${w}x${h}" "$img" 2>/dev/nullin preview scripts.Besides I have spent too many time on configuring preview in terminal with lf. So it's time to support this feature.
One fix for
sixelimage rendering bug:I found this bug when use
chafa -s "${w}x${h}" "$img" 2>/dev/nullin preview scripts.This triggers a problem the text and image stick together, the situation seems like image has a background text or the text has a overlay image.