on main help sign in
fix(ui): filepicker: simplify tmux kitty image encoding
commitded1e6b277bbfac8168dc1f978de3da5f3bc0138 authorAyman Bagabas <ayman.bagabas@gmail.com> dateThu 15 Jan 2026, 12:00 (9 months ago) parentd4ffb55
3 files changed+17−47
go.mod+2−2 go.sum+4−4 internal/ui/image/image.go+11−41
go.mod+2−2
@@ -23,7 +23,7 @@ require (
2323 github.com/charmbracelet/colorprofile v0.4.1
2424 github.com/charmbracelet/fang v0.4.4
2525 github.com/charmbracelet/ultraviolet v0.0.0-20251212194010-b927aa605560
26− github.com/charmbracelet/x/ansi v0.11.3
26+ github.com/charmbracelet/x/ansi v0.11.4
2727 github.com/charmbracelet/x/editor v0.2.0
2828 github.com/charmbracelet/x/etag v0.2.0
2929 github.com/charmbracelet/x/exp/charmtone v0.0.0-20260109001716-2fbdffcb221f
@@ -104,7 +104,7 @@ require (
104104 github.com/charmbracelet/x/json v0.2.0 // indirect
105105 github.com/charmbracelet/x/termios v0.1.1 // indirect
106106 github.com/charmbracelet/x/windows v0.2.2 // indirect
107− github.com/clipperhouse/displaywidth v0.6.2 // indirect
107+ github.com/clipperhouse/displaywidth v0.7.0 // indirect
108108 github.com/clipperhouse/stringish v0.1.1 // indirect
109109 github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
110110 github.com/davecgh/go-spew v1.1.1 // indirect
go.sum+4−4
@@ -104,8 +104,8 @@ github.com/charmbracelet/fang v0.4.4 h1:G4qKxF6or/eTPgmAolwPuRNyuci3hTUGGX1rj1Yk
104104github.com/charmbracelet/fang v0.4.4/go.mod h1:P5/DNb9DddQ0Z0dbc0P3ol4/ix5Po7Ofr2KMBfAqoCo=
105105github.com/charmbracelet/ultraviolet v0.0.0-20251212194010-b927aa605560 h1:j3PW2hypGoPKBy3ooKzW0TFxaxhyHK3NbkLLn4KeRFc=
106106github.com/charmbracelet/ultraviolet v0.0.0-20251212194010-b927aa605560/go.mod h1:VWATWLRwYP06VYCEur7FsNR2B1xAo7Y+xl1PTbd1ePc=
107−github.com/charmbracelet/x/ansi v0.11.3 h1:6DcVaqWI82BBVM/atTyq6yBoRLZFBsnoDoX9GCu2YOI=
108−github.com/charmbracelet/x/ansi v0.11.3/go.mod h1:yI7Zslym9tCJcedxz5+WBq+eUGMJT0bM06Fqy1/Y4dI=
107+github.com/charmbracelet/x/ansi v0.11.4 h1:6G65PLu6HjmE858CnTUQY1LXT3ZUWwfvqEROLF8vqHI=
108+github.com/charmbracelet/x/ansi v0.11.4/go.mod h1:/5AZ+UfWExW3int5H5ugnsG/PWjNcSQcwYsHBlPFQN4=
109109github.com/charmbracelet/x/editor v0.2.0 h1:7XLUKtaRaB8jN7bWU2p2UChiySyaAuIfYiIRg8gGWwk=
110110github.com/charmbracelet/x/editor v0.2.0/go.mod h1:p3oQ28TSL3YPd+GKJ1fHWcp+7bVGpedHpXmo0D6t1dY=
111111github.com/charmbracelet/x/etag v0.2.0 h1:Euj1VkheoHfTYA9y+TCwkeXF/hN8Fb9l4LqZl79pt04=
@@ -130,8 +130,8 @@ github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8
130130github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
131131github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=
132132github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
133−github.com/clipperhouse/displaywidth v0.6.2 h1:ZDpTkFfpHOKte4RG5O/BOyf3ysnvFswpyYrV7z2uAKo=
134−github.com/clipperhouse/displaywidth v0.6.2/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
133+github.com/clipperhouse/displaywidth v0.7.0 h1:QNv1GYsnLX9QBrcWUtMlogpTXuM5FVnBwKWp1O5NwmE=
134+github.com/clipperhouse/displaywidth v0.7.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
135135github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
136136github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
137137github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
internal/ui/image/image.go+11−41
@@ -2,7 +2,6 @@ package image
22
33import (
44 "bytes"
5− "errors"
65 "fmt"
76 "hash/fnv"
87 "image"
@@ -195,50 +194,12 @@ func (e Encoding) Transmit(id string, img image.Image, cs CellSize, cols, rows i
195194 }
196195
197196 var buf bytes.Buffer
198− rp, wp := io.Pipe()
199− go func() {
200− for {
201− // Read single Kitty graphic chunks from the pipe and wrap them
202− // for tmux if needed.
203− var out bytes.Buffer
204− seenEsc := false
205− for {
206− var p [1]byte
207− n, err := rp.Read(p[:])
208− if n > 0 {
209− out.WriteByte(p[0])
210− if p[0] == ansi.ESC {
211− seenEsc = true
212− } else if seenEsc && p[0] == '\\' {
213− // End of Kitty graphics sequence
214− break
215− } else {
216− seenEsc = false
217− }
218− }
219− if err != nil {
220− if !errors.Is(err, io.EOF) {
221− slog.Error("error reading from pipe", "err", err)
222− }
223− return
224− }
225− }
226−
227− seq := out.String()
228− if tmux {
229− seq = ansi.TmuxPassthrough(seq)
230− }
231−
232− buf.WriteString(seq)
233− }
234− }()
235−
236197 img := fitImage(id, img, cs, cols, rows)
237198 bounds := img.Bounds()
238199 imgWidth := bounds.Dx()
239200 imgHeight := bounds.Dy()
240201 imgID := int(key.Hash())
241− if err := kitty.EncodeGraphics(wp, img, &kitty.Options{
202+ if err := kitty.EncodeGraphics(&buf, img, &kitty.Options{
242203 ID: imgID,
243204 Action: kitty.TransmitAndPut,
244205 Transmission: kitty.Direct,
@@ -250,9 +211,18 @@ func (e Encoding) Transmit(id string, img image.Image, cs CellSize, cols, rows i
250211 VirtualPlacement: true,
251212 Quite: 1,
252213 Chunk: true,
214+ ChunkFormatter: func(chunk string) string {
215+ if tmux {
216+ return ansi.TmuxPassthrough(chunk)
217+ }
218+ return chunk
219+ },
253220 }); err != nil {
254221 slog.Error("failed to encode image for kitty graphics", "err", err)
255− return uiutil.ReportError(fmt.Errorf("failed to encode image"))
222+ return uiutil.InfoMsg{
223+ Type: uiutil.InfoTypeError,
224+ Msg: "failed to encode image",
225+ }
256226 }
257227
258228 return tea.RawMsg{Msg: buf.String()}