Commit: patch 9.2.0962: popup images are not using the kitty protocol properly
Christian Brabandt <[email protected]>
| Newsgroups | gmane.editors.vim.devel |
|---|---|
| Message-ID | <[email protected]> |
patch 9.2.0962: popup images are not using the kitty protocol properly Commit: https://github.com/vim/vim/commit/89caa43b7882e34067e0ea8e3d64a7d29581744a Author: Foxe Chen <[email protected]> Date: Mon Aug 17 20:21:28 2026 +0000 patch 9.2.0962: popup images are not using the kitty protocol properly Problem: The kitty image backend re-encodes and retransmits the whole image on every redraw, since the transmit and the placement are sent as a single "a=T" sequence built by popup_encode_image(). Solution: Transmit the image once with "a=t" and send only a placement "a=p" on each redraw, cropping via the protocol's x/y/w/h instead of re-cropping the pixel data (Foxe Chen). closes: #21018 Signed-off-by: Foxe Chen <[email protected]> Signed-off-by: Christian Brabandt <[email protected]> diff --git a/src/kitty.c b/src/kitty.c index 2b46b1454..279eba842 100644 --- a/src/kitty.c +++ b/src/kitty.c @@ -13,163 +13,136 @@ * The popup's image bytes are sent in 4096-byte chunks of base64 * inside ` _G...;<chunk> \` envelopes. * Spec: https://sw.kovidgoyal.net/kitty/graphics-protocol/ - * No external dependency; the base64 alphabet is inlined here. + * Base64 encoding is shared with misc2 base64_encode()/decode() */ #include "vim.h" #if defined(FEAT_IMAGE_KITTY) || defined(PROTO) -static const char_u kitty_b64_table[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +// Max base64 chars per envelope, per the kitty graphics protocol. +#define KITTY_CHUNK_B64 4096 +// Source bytes that encode into KITTY_CHUNK_B64 base64 chars. +#define KITTY_CHUNK_SRC (KITTY_CHUNK_B64 * 3 / 4) +// header + base64 chunk + " \" trailer + NUL. Use 128 extra bytes padding +// for header, should be more than enough. +#define KITTY_BUF_SIZE (128 + KITTY_CHUNK_B64 + 2 + 1) /* - * Append a NUL-terminated string to "ga". Returns OK / FAIL so the caller - * can abort on allocation failure (unlike ga_concat(), which silently no-ops - * and would leave a truncated, invalid kitty APC sequence behind). + * Return the kitty image id to use for window id "id". Kitty image ids are + * global to the terminal, so mix in the process id: another Vim in the same + * terminal would otherwise use the same ids and its "a=d,d=I" would free our + * image data. */ static int -kitty_ga_concat(garray_T *ga, const char_u *s) +kitty_image_id(int id) { - int len = (int)STRLEN(s); + static int base = 0; - if (len == 0) - return OK; - if (ga_grow(ga, len) == FAIL) - return FAIL; - mch_memmove((char_u *)ga->ga_data + ga->ga_len, s, (size_t)len); - ga->ga_len += len; - return OK; + if (base == 0) + base = (((int)mch_get_pid() & 0x7fff) + 1) << 16; + return base | (id & 0xffff); } /* - * Append base64-encoded bytes from "src[len]" to growarray "ga". - * Returns OK / FAIL so the caller can propagate OOM. + * Transmit an RGB(A) image to the terminal (does not display it!). It will + * have an id of "id", so that it can be placed later. */ - static int -kitty_b64_append(garray_T *ga, char_u *src, long len) + int +kitty_transmit(image_rgb_T *img, int id) { - long i; - long out_len = ((len + 2) / 3) * 4; - char_u *dst; - - if (out_len == 0) - return OK; - if (ga_grow(ga, (int)out_len) == FAIL) - return FAIL; - dst = (char_u *)ga->ga_data + ga->ga_len; - for (i = 0; i < len; i += 3) - { - unsigned a = src[i]; - unsigned b = (i + 1 < len) ? src[i + 1] : 0; - unsigned c = (i + 2 < len) ? src[i + 2] : 0; - unsigned triple = (a << 16) | (b << 8) | c; - - *dst++ = kitty_b64_table[(triple >> 18) & 0x3f]; - *dst++ = kitty_b64_table[(triple >> 12) & 0x3f]; - *dst++ = (i + 1 < len) - ? kitty_b64_table[(triple >> 6) & 0x3f] : '='; - *dst++ = (i + 2 < len) - ? kitty_b64_table[triple & 0x3f] : '='; - } - ga->ga_len += (int)out_len; - return OK; -} + static char buf[KITTY_BUF_SIZE]; -/* - * Encode an RGB(A) image into a kitty graphics protocol APC sequence. - * Returns a malloced char_u* containing the full sequence - * (one or more ` _G... \` envelopes), or NULL on OOM. - * - * The sequence is emitted with `a=T` (transmit + display), `q=2` (no - * status responses), `f=24` for RGB or `f=32` for RGBA, and chunked - * via `m=1`/`m=0` so the per-envelope payload stays under kitty's - * 4096-byte limit. When "id" is non-zero it is sent as `i=<id>` so - * the resulting placement can later be removed via kitty_delete(). - * "zindex" is sent as `z=<zindex>` so overlapping placements stack in - * popup zindex order no matter in which order they were (re)created. - */ - char_u * -kitty_encode(image_rgb_T *img, int id, int zindex) -{ - garray_T ga; long pix_bytes; long payload_len; - long b64_total; long offset = 0; int fmt; int first = TRUE; - char_u hdr[80]; if (img == NULL || img->data == NULL || img->width <= 0 || img->height <= 0) - return NULL; + return FAIL; pix_bytes = img->has_alpha ? 4 : 3; payload_len = (long)img->width * img->height * pix_bytes; - b64_total = ((payload_len + 2) / 3) * 4; fmt = img->has_alpha ? 32 : 24; - ga_init2(&ga, 1, (int)b64_total + 256); - - // Emit one envelope per 4096 base64 chars. The first envelope - // carries the full geometry/format header; later envelopes only - // need the chunk-continuation marker `m=`. - while (offset < b64_total) + // Emit one envelope per KITTY_CHUNK_SRC source bytes (= 4096 base64 + // chars). The first envelope carries the full geometry/format + // header; later envelopes only need the chunk-continuation marker + // `m=`. + while (offset < payload_len) { - long this_chunk = b64_total - offset; + long this_chunk = payload_len - offset; int more; + int hdr_len; + long b64_len; - if (this_chunk > 4096) - this_chunk = 4096; - more = (offset + this_chunk < b64_total); + if (this_chunk > KITTY_CHUNK_SRC) + this_chunk = KITTY_CHUNK_SRC; + more = (offset + this_chunk < payload_len); if (first) { - if (id != 0) - vim_snprintf((char *)hdr, sizeof(hdr), - " _Ga=T,f=%d,s=%d,v=%d,i=%d,z=%d,q=2,m=%d;", - fmt, img->width, img->height, id, zindex, - more ? 1 : 0); - else - vim_snprintf((char *)hdr, sizeof(hdr), - " _Ga=T,f=%d,s=%d,v=%d,z=%d,q=2,m=%d;", - fmt, img->width, img->height, zindex, more ? 1 : 0); + hdr_len = vim_snprintf(buf, sizeof(buf), + " _Ga=t,i=%d,f=%d,s=%d,v=%d,q=2,m=%d;", + kitty_image_id(id), fmt, img->width, + img->height, more ? 1 : 0); first = FALSE; } else - { - vim_snprintf((char *)hdr, sizeof(hdr), - " _Gm=%d;", more ? 1 : 0); - } - if (kitty_ga_concat(&ga, hdr) == FAIL) - goto fail; + hdr_len = vim_snprintf(buf, sizeof(buf), " _Gm=%d;", + more ? 1 : 0); - // Encode the matching slice of the source bytes. Each base64 - // chunk consumes (this_chunk / 4) base64 quartets, which means - // (this_chunk * 3 / 4) source bytes. - { - long src_offset = offset * 3 / 4; - long src_len = this_chunk * 3 / 4; + b64_len = base64_encode_buf((char_u *)buf + hdr_len, + img->data + offset, this_chunk); - if (src_offset + src_len > payload_len) - src_len = payload_len - src_offset; - if (kitty_b64_append(&ga, img->data + src_offset, src_len) == FAIL) - goto fail; - } - - if (kitty_ga_concat(&ga, (char_u *)" \") == FAIL) - goto fail; + buf[hdr_len + b64_len] = ' '; + buf[hdr_len + b64_len + 1] = '\'; + buf[hdr_len + b64_len + 2] = NUL; + out_str((char_u *)buf); offset += this_chunk; } - if (ga_append(&ga, NUL) == FAIL) - goto fail; - return (char_u *)ga.ga_data; + out_flush(); + return OK; +} + +/* + * Place the image with the given id, which should have already been + * transmitted. Its placement id will always be its image id, so that the image + * is moved if it was previously placed. + */ + void +kitty_place(int id, int row, int col, int src_x, int src_y, int w, int h, int z) +{ + vim_snprintf((char *)IObuff, IOSIZE, + " _Ga=p,i=%d,p=%d,x=%d,y=%d,w=%d,h=%d,z=%d,q=2 \", + kitty_image_id(id), kitty_image_id(id), src_x, src_y, w, h, z); + + term_windgoto(row, col); + out_str((char_u *)IObuff); + screen_start(); + setcursor_mayforce(TRUE); + out_flush(); +} + +/* + * Delete image placement with image id "id" (which is also its placement id). + * If "del_data" is true, then its data will be freed by the terminal (see + * https://sw.kovidgoyal.net/kitty/graphics-protocol/#deleting-images). + */ + void +kitty_delete(int id, bool del_data) +{ + char d_key = del_data ? 'I' : 'i'; + + vim_snprintf((char *)IObuff, IOSIZE, + " _Ga=d,d=%c,i=%d,p=%d,q=2 \", d_key, kitty_image_id(id), + kitty_image_id(id)); -fail: - ga_clear(&ga); - return NULL; + out_str((char_u *)IObuff); + out_flush(); } /* @@ -240,25 +213,4 @@ kitty_probe_parse(char *buf, int n) return strstr(buf, "_Gi=31;OK") != NULL; } -/* - * Build a kitty "delete image" APC sequence for the placement created - * by kitty_encode() with the matching `id`. The caller must - * vim_free() the returned buffer. Returns NULL on OOM or id <= 0. - * - * Sequence: ` _Ga=d,i=<id>,q=2 \` - * a=d -> action: delete - * i= -> image id (target placement) - * q=2 -> suppress status reply - */ - char_u * -kitty_delete(int id) -{ - char_u buf[40]; - - if (id <= 0) - return NULL; - vim_snprintf((char *)buf, sizeof(buf), " _Ga=d,i=%d,q=2 \", id); - return vim_strsave(buf); -} - #endif // FEAT_IMAGE_KITTY || PROTO diff --git a/src/popupwin.c b/src/popupwin.c index 07e0b90e2..cbfe72f97 100644 --- a/src/popupwin.c +++ b/src/popupwin.c @@ -119,7 +119,7 @@ static void redraw_overlapped_opacity_popups(int winrow, int wincol, int height, int width, int leftoff, int zindex); static void redraw_win_under_opacity_popup(win_T *wp); #ifdef FEAT_IMAGE_KITTY -static void popup_image_clear_kitty(win_T *wp); +static void popup_image_clear_kitty(win_T *wp, bool del_data); #endif // GDI and cairo paint the image straight into the window, so the area has to // be redrawn when the popup goes away. GTK4 keeps a list of images to render @@ -974,7 +974,7 @@ apply_general_options(win_T *wp, dict_T *dict) || wp->w_popup_image_w > 0 || wp->w_popup_image_h > 0) { # ifdef FEAT_IMAGE_KITTY - popup_image_clear_kitty(wp); + popup_image_clear_kitty(wp, true); # endif # ifdef FEAT_IMAGE_GDK if (gui.in_use) @@ -993,7 +993,6 @@ apply_general_options(win_T *wp, dict_T *dict) wp->w_popup_image_seq_crop_y = 0; wp->w_popup_image_seq_cells_w = 0; wp->w_popup_image_seq_cells_h = 0; - wp->w_popup_image_emit_valid = false; # endif # if defined(FEAT_IMAGE_GDI) || defined(FEAT_IMAGE_CAIRO) || defined(FEAT_IMAGE_GDK) # ifdef FEAT_GUI @@ -1054,9 +1053,6 @@ apply_general_options(win_T *wp, dict_T *dict) # ifdef FEAT_IMAGE_SIXEL VIM_CLEAR(wp->w_popup_image_seq); wp->w_popup_image_seq_h = -1; -# endif -# ifdef FEAT_IMAGE_KITTY - wp->w_popup_image_emit_valid = false; # endif if (wp->w_popup_image_data != NULL) { @@ -1771,7 +1767,7 @@ popup_compute_clip(win_T *wp, popup_clip_T *cl) cl->eff_width = cl->eff_left_extra + w + cl->eff_right_extra; } -#ifdef FEAT_IMAGE_SIXEL +#if defined(FEAT_IMAGE_SIXEL) || defined(FEAT_IMAGE_KITTY) /* * Re-encode the popup's sixel image so its pixel rows fit above the bottom of * the screen. Many sixel-capable terminals scroll the screen when an image @@ -1896,12 +1892,12 @@ popup_image_backend(void) return detected; } +# ifdef FEAT_IMAGE_SIXEL static void popup_encode_image(win_T *wp) { image_rgb_T si; int target_w, target_h; - int backend; int cell_x = 8; int cell_y = 16; int crop_top_px, crop_bot_px; @@ -1912,30 +1908,28 @@ popup_encode_image(win_T *wp) if (wp->w_popup_image_data == NULL || wp->w_popup_image_w <= 0 || wp->w_popup_image_h <= 0) return; -# ifdef FEAT_GUI +# ifdef FEAT_GUI // The GUI backend renders the image from the device bitmap, not from a // terminal escape sequence -- skip the encoder entirely in that case. if (gui.in_use) return; -# endif - - backend = popup_image_backend(); +# endif -# if defined(UNIX) || defined(MSWIN) || defined(VMS) || defined(AMIGA) +# if defined(UNIX) || defined(MSWIN) || defined(VMS) || defined(AMIGA) { struct cellsize cs; cs.cs_xpixel = -1; cs.cs_ypixel = -1; -# if defined(UNIX) || defined(MSWIN) +# if defined(UNIX) || defined(MSWIN) mch_calc_cell_size(&cs); -# endif +# endif if (cs.cs_xpixel > 0) cell_x = cs.cs_xpixel; if (cs.cs_ypixel > 0) cell_y = cs.cs_ypixel; } -# endif +# endif // For "clipwindow" popups, crop the image to the portion that lies inside // the host window. popup_compute_clip() turns topoff/bottomoff/leftclip/ @@ -1953,7 +1947,6 @@ popup_encode_image(win_T *wp) { VIM_CLEAR(wp->w_popup_image_seq); wp->w_popup_image_seq_h = 0; - wp->w_popup_image_emit_valid = false; return; } @@ -1981,11 +1974,10 @@ popup_encode_image(win_T *wp) target_h = host_avail_h; } - if (backend != IMAGE_BACKEND_KITTY) { // Reserve the bottom-most cell row to keep the sixel image away // from the edge that triggers scrolling on terminals with sixel- - // scrolling enabled. Kitty has no such scroll trigger. + // scrolling enabled. int sixel_cells = Rows - 1 - img_top_row; int sixel_cap = sixel_cells > 0 ? sixel_cells * cell_y : 0; @@ -2005,19 +1997,17 @@ popup_encode_image(win_T *wp) { VIM_CLEAR(wp->w_popup_image_seq); wp->w_popup_image_seq_h = 0; - wp->w_popup_image_emit_valid = false; return; } + // already encoded for this geometry if (wp->w_popup_image_seq != NULL && wp->w_popup_image_seq_w == target_w && wp->w_popup_image_seq_h == target_h && wp->w_popup_image_seq_crop_x == crop_left_px - && wp->w_popup_image_seq_crop_y == crop_top_px - && wp->w_popup_image_seq_zindex == wp->w_zindex) - return; // already encoded for this geometry and zindex + && wp->w_popup_image_seq_crop_y == crop_top_px) + return; VIM_CLEAR(wp->w_popup_image_seq); - wp->w_popup_image_emit_valid = false; // The sixel/kitty encoders read data tightly packed as width*height // pixels. When the source row width changes (left or right clipped), @@ -2052,15 +2042,8 @@ popup_encode_image(win_T *wp) si.width = target_w; si.height = target_h; si.has_alpha = wp->w_popup_image_alpha; -# ifdef FEAT_IMAGE_KITTY - if (backend == IMAGE_BACKEND_KITTY) - // Use the popup's window-id as the kitty image id so that - // popup_image_clear_kitty() can target the placement when the - // popup is later hidden or closed. - wp->w_popup_image_seq = kitty_encode(&si, wp->w_id, wp->w_zindex); - else -# endif - wp->w_popup_image_seq = sixel_encode(&si); + + wp->w_popup_image_seq = sixel_encode(&si); vim_free(crop_buf); @@ -2072,7 +2055,6 @@ popup_encode_image(win_T *wp) wp->w_popup_image_seq_crop_y = crop_top_px; wp->w_popup_image_seq_cells_w = (target_w + cell_x - 1) / cell_x; wp->w_popup_image_seq_cells_h = (target_h + cell_y - 1) / cell_y; - wp->w_popup_image_seq_zindex = wp->w_zindex; } else { @@ -2081,7 +2063,8 @@ popup_encode_image(win_T *wp) wp->w_popup_image_seq_cells_h = 0; } } -#endif +# endif // FEAT_IMAGE_SIXEL +#endif // FEAT_IMAGE_SIXEL || FEAT_IMAGE_KITTY #ifdef FEAT_IMAGE /* @@ -2420,7 +2403,7 @@ popup_adjust_position(win_T *wp) #ifdef FEAT_IMAGE_KITTY // Kitty placements need to be deleted explicitly before // the popup goes hidden -- see popup_hide(). - popup_image_clear_kitty(wp); + popup_image_clear_kitty(wp, false); #endif #ifdef FEAT_IMAGE_GDK if (gui.in_use) @@ -2962,7 +2945,7 @@ popup_adjust_position(win_T *wp) { #ifdef FEAT_IMAGE_KITTY // delete the kitty placement before hiding, like popup_hide() - popup_image_clear_kitty(wp); + popup_image_clear_kitty(wp, false); #endif #ifdef FEAT_IMAGE_GDK if (gui.in_use) @@ -4344,7 +4327,7 @@ popup_hide(win_T *wp) // Sixel pixels disappear when the cells underneath are redrawn, but // a kitty placement persists until explicitly deleted -- send the // delete APC before hiding so the image goes away with the popup. - popup_image_clear_kitty(wp); + popup_image_clear_kitty(wp, false); #endif #ifdef FEAT_IMAGE_GDK if (gui.in_use) @@ -4556,7 +4539,7 @@ popup_free(win_T *wp) #ifdef FEAT_IMAGE_KITTY // Remove the kitty placement before win_free_popup() invalidates wp. - popup_image_clear_kitty(wp); + popup_image_clear_kitty(wp, true); #endif #ifdef FEAT_IMAGE_GDK if (gui.in_use) @@ -6940,7 +6923,7 @@ popup_image_gui_clip( || defined(FEAT_IMAGE_GDI) || defined(FEAT_IMAGE_CAIRO) \ || defined(FEAT_IMAGE_GDK) static void -popup_invalidate_prev_image_rect(win_T *wp, popup_clip_T *cl) +popup_invalidate_prev_image_rect(win_T *wp, popup_clip_T *cl UNUSED) { int old_row, old_col, old_cells_w, old_cells_h; int new_row = 0, new_col = 0, new_cells_w = 0, new_cells_h = 0; @@ -6978,7 +6961,7 @@ popup_invalidate_prev_image_rect(win_T *wp, popup_clip_T *cl) new_cells_h = (draw_h + cell_y - 1) / cell_y; } # endif -# if defined(FEAT_IMAGE_SIXEL) || defined(FEAT_IMAGE_KITTY) +# if defined(FEAT_IMAGE_SIXEL) # if defined(FEAT_GUI) && (defined(FEAT_IMAGE_GDI) || defined(FEAT_IMAGE_CAIRO)) \ || defined(FEAT_IMAGE_GDK) else @@ -7066,6 +7049,7 @@ popup_emit_image(win_T *wp) // leaving the image stuck on screen until the cell is overwritten. if (wp->w_popup_flags & POPF_HIDDEN) return; + row = wp->w_winrow + wp->w_popup_border[0] + wp->w_popup_padding[0]; col = wp->w_wincol + wp->w_popup_border[3] + wp->w_popup_padding[3]; @@ -7097,14 +7081,92 @@ popup_emit_image(win_T *wp) return; } # endif -# if defined(FEAT_IMAGE_SIXEL) || defined(FEAT_IMAGE_KITTY) -# ifdef FEAT_GUI +# ifdef FEAT_GUI // GUI builds without a GUI image backend (e.g. Motif) reach here when // gui.in_use is true; emitting sixel/kitty escape sequences via out_str() // would print them as raw text on the GUI canvas, so bail out. if (gui.in_use) return; +# endif +# ifdef FEAT_IMAGE_KITTY + if (popup_image_backend() == IMAGE_BACKEND_KITTY) + { + popup_clip_T cl; + int cell_x = 8; + int cell_y = 16; + int visible_w; + int visible_h; + int src_x, src_y, w, h; + + if (row < 0 || col < 0) + return; + +# if defined(UNIX) || defined(MSWIN) || defined(VMS) || defined(AMIGA) + { + struct cellsize cs; + + cs.cs_xpixel = -1; + cs.cs_ypixel = -1; +# if defined(UNIX) || defined(MSWIN) + mch_calc_cell_size(&cs); +# endif + if (cs.cs_xpixel > 0) + cell_x = cs.cs_xpixel; + if (cs.cs_ypixel > 0) + cell_y = cs.cs_ypixel; + } # endif + popup_compute_clip(wp, &cl); + + visible_w = wp->w_width - cl.clip_left_content - cl.clip_right_content; + visible_h = wp->w_height - cl.clip_top_content - cl.clip_bot_content; + + if (visible_w <= 0 || visible_h <= 0) + return; + + row += cl.clip_top_content; + col += cl.clip_left_content; + + src_x = cl.clip_left_content * cell_x; + src_y = cl.clip_top_content * cell_y; + w = wp->w_popup_image_w - src_x - cl.clip_right_content * cell_x; + h = wp->w_popup_image_h - src_y - cl.clip_bot_content * cell_y; + + // Clamp to the popup's actual visible cell box in pixels, so the + // crop can never claim more cells than the popup has + if (w > visible_w * cell_x) + w = visible_w * cell_x; + if (h > visible_h * cell_y) + h = visible_h * cell_y; + + if (w <= 0 || h <= 0) + return; + + // Transmit the image to the terminal if it hasn't already + if (!wp->w_popup_image_transmit) + { + image_rgb_T si; + + si.data = wp->w_popup_image_data; + si.width = wp->w_popup_image_w; + si.height = wp->w_popup_image_h; + si.has_alpha = wp->w_popup_image_alpha; + + if (kitty_transmit(&si, wp->w_id) == FAIL) + return; + wp->w_popup_image_transmit = true; + } + kitty_place(wp->w_id, row, col, src_x, src_y, w, h, wp->w_zindex); + + wp->w_popup_image_emit_row = row; + wp->w_popup_image_emit_col = col; + wp->w_popup_image_emit_cells_w = (w + cell_x - 1) / cell_x; + wp->w_popup_image_emit_cells_h = (h + cell_y - 1) / cell_y; + wp->w_popup_image_px_dirty = false; + return; + } +# endif +# ifdef FEAT_IMAGE_SIXEL if (wp->w_popup_image_seq == NULL) return; // For "clipwindow" popups the encoded sequence already covers only the @@ -7120,18 +7182,6 @@ popup_emit_image(win_T *wp) } if (row < 0 || col < 0) return; -# ifdef FEAT_IMAGE_KITTY - // A kitty placement persists on the terminal and is drawn above the - // text layer, so when it is already showing at this position there is - // nothing to repair: skip the (potentially multi-MB) retransmission. - // The flag is reset when the image is re-encoded, the placement is - // deleted, or the terminal screen is cleared. - if (popup_image_backend() == IMAGE_BACKEND_KITTY - && wp->w_popup_image_emit_valid - && wp->w_popup_image_emit_row == row - && wp->w_popup_image_emit_col == col) - return; -# endif // Hide the cursor across the move + image emit, then restore it to // the current text-cursor position before showing it; otherwise the // cursor can briefly flicker below its scrolled-to position because @@ -7153,33 +7203,28 @@ popup_emit_image(win_T *wp) // including cells that a higher zindex popup draws on top of this image. // Invalidate those cells in ScreenLines so the higher popup's draw, // later in this same update_popups() walk, actually rewrites them to - // the terminal instead of skipping them as unchanged. Not needed for - // kitty, where the placement is layered by its z= value instead. -# ifdef FEAT_IMAGE_KITTY - if (popup_image_backend() != IMAGE_BACKEND_KITTY) -# endif + // the terminal instead of skipping them as unchanged. + + for (int rr = row; rr < row + wp->w_popup_image_seq_cells_h; ++rr) { - for (int rr = row; rr < row + wp->w_popup_image_seq_cells_h; ++rr) - { - if (rr < 0 || rr >= screen_Rows) - continue; + if (rr < 0 || rr >= screen_Rows) + continue; - int off_base = LineOffset[rr]; + int off_base = LineOffset[rr]; - for (int cc = col; cc < col + wp->w_popup_image_seq_cells_w; ++cc) - { - if (cc < 0 || cc >= screen_Columns) - continue; - if (popup_mask[rr * screen_Columns + cc] <= wp->w_zindex) - continue; + for (int cc = col; cc < col + wp->w_popup_image_seq_cells_w; ++cc) + { + if (cc < 0 || cc >= screen_Columns) + continue; + if (popup_mask[rr * screen_Columns + cc] <= wp->w_zindex) + continue; - int off = off_base + cc; + int off = off_base + cc; - ScreenLines[off] = ' '; - if (enc_utf8 && ScreenLinesUC != NULL) - ScreenLinesUC[off] = 0; - ScreenAttrs[off] = -1; - } + ScreenLines[off] = ' '; + if (enc_utf8 && ScreenLinesUC != NULL) + ScreenLinesUC[off] = 0; + ScreenAttrs[off] = -1; } } @@ -7192,39 +7237,32 @@ popup_emit_image(win_T *wp) wp->w_popup_image_emit_col = col; wp->w_popup_image_emit_cells_w = wp->w_popup_image_seq_cells_w; wp->w_popup_image_emit_cells_h = wp->w_popup_image_seq_cells_h; - wp->w_popup_image_emit_valid = true; wp->w_popup_image_px_dirty = false; # endif } # ifdef FEAT_IMAGE_KITTY /* - * Send a kitty `a=d,i=<id>` APC to remove the placement made for "wp" - * by an earlier kitty_encode(). Called when the popup goes away (via - * popup_hide / popup_close / textprop scrolling out of view), because - * unlike sixel pixels -- which the next text overwrite clears -- kitty - * placements persist until explicitly deleted. + * Remove the kitty image placement for "wp". Called when the popup goes + * away. + * When "del_data" is true the terminal also frees the transmitted image. */ static void -popup_image_clear_kitty(win_T *wp) +popup_image_clear_kitty(win_T *wp, bool del_data) { - char_u *seq; - # ifdef FEAT_GUI if (gui.in_use) return; # endif + if (wp == NULL || wp->w_popup_image_data == NULL || wp->w_id <= 0) return; + if (popup_image_backend() != IMAGE_BACKEND_KITTY) return; - seq = kitty_delete(wp->w_id); - if (seq == NULL) - return; - out_str(seq); - out_flush(); - vim_free(seq); - wp->w_popup_image_emit_valid = false; + kitty_delete(wp->w_id, del_data); + if (del_data) + wp->w_popup_image_transmit = false; } # endif @@ -7251,27 +7289,6 @@ popup_image_clear_gui(win_T *wp) } # endif -# if defined(FEAT_IMAGE_SIXEL) || defined(FEAT_IMAGE_KITTY) -/* - * Called after the terminal screen has been cleared: kitty deletes - * placements that intersect the erased area, so the cached "already on - * screen" state no longer holds and the next popup_emit_image() must - * retransmit. - */ - void -popup_images_invalidate(void) -{ - win_T *wp; - tabpage_T *tp; - - FOR_ALL_POPUPWINS(wp) - wp->w_popup_image_emit_valid = false; - FOR_ALL_TABPAGES(tp) - FOR_ALL_POPUPWINS_IN_TAB(tp, wp) - wp->w_popup_image_emit_valid = false; -} -# endif - /* * Re-paint every popup's image after the rest of the screen update has * settled. Only needed for the GUI, where the cursor redraw and other diff --git a/src/proto/kitty.pro b/src/proto/kitty.pro index 8c2970c7d..001e79c31 100644 --- a/src/proto/kitty.pro +++ b/src/proto/kitty.pro @@ -1,5 +1,6 @@ /* kitty.c */ -char_u *kitty_encode(image_rgb_T *img, int id, int zindex); +int kitty_transmit(image_rgb_T *img, int id); +void kitty_place(int id, int row, int col, int src_x, int src_y, int w, int h, int z); +void kitty_delete(int id, bool del_data); int kitty_probe_parse(char *buf, int n); -char_u *kitty_delete(int id); /* vim: set ft=c : */ diff --git a/src/proto/popupwin.pro b/src/proto/popupwin.pro index 758695c35..f29aef9d0 100644 --- a/src/proto/popupwin.pro +++ b/src/proto/popupwin.pro @@ -60,7 +60,6 @@ void may_update_popup_mask(int type); void may_update_popup_position(void); int popup_get_base_screen_cell(int row, int col, schar_T *linep, int *attrp, u8char_T *ucp); void popup_set_base_screen_cell(int row, int col, schar_T line, int attr, u8char_T uc); -void popup_images_invalidate(void); void update_popup_images(void); void update_popup_images_rect(int left, int top, int right, int bottom); void update_popups(void (*win_update)(win_T *wp)); diff --git a/src/screen.c b/src/screen.c index b4cf42ee6..662bc0ed7 100644 --- a/src/screen.c +++ b/src/screen.c @@ -3636,11 +3636,6 @@ screenclear2(int doclear) if (suppressed_cells != NULL) vim_memset(suppressed_cells, 0, (size_t)suppressed_rows * suppressed_cols); -#endif -#if defined(FEAT_IMAGE_SIXEL) || defined(FEAT_IMAGE_KITTY) - // Clearing the display removes kitty image placements; force the - // next redraw to retransmit popup images. - popup_images_invalidate(); #endif } else diff --git a/src/structs.h b/src/structs.h index fdba4ee83..d41cd182b 100644 --- a/src/structs.h +++ b/src/structs.h @@ -4277,7 +4277,7 @@ struct window_S // visible under the new frame's transparent pixels. bool w_popup_image_px_dirty; # ifdef FEAT_IMAGE_SIXEL - char_u *w_popup_image_seq; // cached sixel DCS sequence (terminal) + char_u *w_popup_image_seq; // cached sixel DCS sequence int w_popup_image_seq_w; // pixel width of cached seq int w_popup_image_seq_h; // pixel height used for cached seq; // -1 means cache is invalid @@ -4285,10 +4285,10 @@ struct window_S int w_popup_image_seq_crop_y; // pixel offset (top) into source int w_popup_image_seq_cells_w; // cell width spanning seq pixels int w_popup_image_seq_cells_h; // cell height spanning seq pixels - int w_popup_image_seq_zindex; // zindex encoded into seq (kitty z=) - bool w_popup_image_emit_valid; // true while the kitty placement - // emitted at w_popup_image_emit_* - // is still on the terminal +# endif +# ifdef FEAT_IMAGE_KITTY + bool w_popup_image_transmit; // If image has been transmitted to + // terminal # endif # ifdef FEAT_IMAGE_GDI // Pre-built Windows GUI image cache. The bitmap is a 32-bit top-down diff --git a/src/version.c b/src/version.c index 2033abfa4..a5c38c915 100644 --- a/src/version.c +++ b/src/version.c @@ -763,6 +763,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 962, /**/ 961, /**/ -- -- You received this message from the "vim_dev" maillist. Do not top-post! Type your reply below the text you are replying to. For more information, visit http://www.vim.org/maillist.php --- You received this message because you are subscribed to the Google Groups "vim_dev" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To view this discussion visit https://groups.google.com/d/msgid/vim_dev/E1ww3yC-0091tr-0j%40256bit.org.