diff options
Diffstat (limited to 'kitty-graphics.el')
| -rw-r--r-- | kitty-graphics.el | 165 |
1 files changed, 122 insertions, 43 deletions
diff --git a/kitty-graphics.el b/kitty-graphics.el index 210d52b..04158ff 100644 --- a/kitty-graphics.el +++ b/kitty-graphics.el @@ -1494,6 +1494,19 @@ to prevent flicker." (lambda (win) (with-current-buffer (window-buffer win) (when kitty-gfx--overlays + ;; A single overlay can be visible in multiple windows showing + ;; the same buffer. Its legacy last-row/last-col properties + ;; mirror the placement for the window currently being + ;; refreshed; per-window placement tracking below keeps the + ;; individual terminal regions deletable when one window later + ;; disappears. + (dolist (ov kitty-gfx--overlays) + (let ((placement (kitty-gfx--image-placement ov win))) + (unless (overlay-get ov 'kitty-gfx-heading) + (overlay-put ov 'kitty-gfx-last-row + (plist-get (cdr placement) :row)) + (overlay-put ov 'kitty-gfx-last-col + (plist-get (cdr placement) :col))))) ;; Prune dead overlays (overlay-buffer returns nil) (let ((before (length kitty-gfx--overlays))) (setq kitty-gfx--overlays @@ -1534,10 +1547,13 @@ refresh based on overlay type." ;; OSC 66 emission happens in phase 2 (kitty-gfx--emit-heading-overlays). (kitty-gfx--refresh-heading-overlay ov win win-bottom) ;; Image overlay refresh - (let ((pos (kitty-gfx--overlay-screen-pos ov win)) - (rows (overlay-get ov 'kitty-gfx-rows)) - (last-row (overlay-get ov 'kitty-gfx-last-row)) - (last-col (overlay-get ov 'kitty-gfx-last-col))) + (let* ((pos (kitty-gfx--overlay-screen-pos ov win)) + (rows (overlay-get ov 'kitty-gfx-rows)) + (cols (overlay-get ov 'kitty-gfx-cols)) + (placement (kitty-gfx--image-placement ov win)) + (placement-data (cdr placement)) + (last-row (plist-get placement-data :row)) + (last-col (plist-get placement-data :col))) (let ((pid (overlay-get ov 'kitty-gfx-pid)) (id (overlay-get ov 'kitty-gfx-id))) (if (and pos @@ -1560,15 +1576,19 @@ refresh based on overlay type." new-row new-col) (overlay-put ov 'kitty-gfx-last-row new-row) (overlay-put ov 'kitty-gfx-last-col new-col) + (kitty-gfx--record-image-placement ov win new-row new-col cols rows pid) + (setq placement (kitty-gfx--image-placement ov win) + pid (plist-get (cdr placement) :pid)) (funcall (kitty-gfx--backend-fn 'place) - ov id pid (overlay-get ov 'kitty-gfx-cols) rows new-row new-col))) - ;; Not visible or overflows — delete if was placed - (when last-row - (kitty-gfx--log "refresh-ov: pid=%d hiding (was row=%d col=%d)" - pid last-row last-col) + ov id pid cols rows new-row new-col))) + ;; Not visible or overflows — delete if was placed in this window + (when placement + (kitty-gfx--log "refresh-ov: pid=%d hiding in win=%s (was row=%d col=%d)" + pid win last-row last-col) + (kitty-gfx--delete-image-placement ov placement) + (kitty-gfx--forget-image-placement ov win) (overlay-put ov 'kitty-gfx-last-row nil) - (overlay-put ov 'kitty-gfx-last-col nil) - (funcall (kitty-gfx--backend-fn 'delete) ov id pid))))))) + (overlay-put ov 'kitty-gfx-last-col nil))))))) (defun kitty-gfx--refresh-heading-overlay (ov win win-bottom) "Refresh heading overlay OV in WIN. @@ -1663,15 +1683,9 @@ then invalidates position caches and schedules a refresh." (kitty-gfx--erase-heading ov) (overlay-put ov 'kitty-gfx-last-row nil) (overlay-put ov 'kitty-gfx-last-col nil)) - ;; Image overlay — delete terminal placement - (let ((id (overlay-get ov 'kitty-gfx-id)) - (pid (overlay-get ov 'kitty-gfx-pid))) - (if (overlay-get ov 'kitty-gfx-last-row) - (when (and id pid) - (kitty-gfx--delete-placement id pid)) - (kitty-gfx--log "on-buffer-change: ov pid=%s has no last-row (never placed or already cleaned)" pid))) - (overlay-put ov 'kitty-gfx-last-row nil) - (overlay-put ov 'kitty-gfx-last-col nil)))))))) + ;; Image overlay — delete all terminal placements, including + ;; multiple windows that were showing this buffer. + (kitty-gfx--delete-image-placements ov)))))))) ;; Reset cache for visible buffers so they re-place correctly. ;; Heading overlays preserve cache (same rationale as on-window-change). (dolist (buf (buffer-list)) @@ -1679,6 +1693,7 @@ then invalidates position caches and schedules a refresh." (dolist (ov kitty-gfx--overlays) (when (and (overlay-buffer ov) (not (overlay-get ov 'kitty-gfx-heading))) + (overlay-put ov 'kitty-gfx-placements nil) (overlay-put ov 'kitty-gfx-last-row nil) (overlay-put ov 'kitty-gfx-last-col nil))))) ;; Longer debounce: cancel any fast leading-edge cooldown and @@ -1705,12 +1720,13 @@ settling to one)." kitty-gfx--cell-pixel-height nil) ;; Delete image placements before clearing their cached positions. ;; Window splits/resizes can move an image from the middle of the old - ;; window to the center of the new pane(s). Some terminals do not - ;; reliably erase the old direct placement when it is re-placed at a - ;; different geometry, and Sixel is stateless and must be explicitly - ;; overwritten. If we clear the cache first, the backend no longer - ;; knows where to delete/erase the old placement, leaving artifacts - ;; over newly-created window separators. + ;; window to the center of the new pane(s). A buffer can also be shown + ;; in multiple windows, and closing one of those windows leaves terminal + ;; pixels that no remaining window can discover from `posn-at-point'. + ;; Therefore image placements are tracked and deleted per window before + ;; the cache is reset. Some terminals do not reliably erase an old + ;; direct placement when it is re-placed at a different geometry, and + ;; Sixel is stateless and must be explicitly overwritten. ;; ;; Heading overlays PRESERVE their cache — the refresh cycle needs ;; old→new position comparison to erase multicell blocks properly. @@ -1721,17 +1737,7 @@ settling to one)." (dolist (ov kitty-gfx--overlays) (when (and (overlay-buffer ov) (not (overlay-get ov 'kitty-gfx-heading))) - (let ((id (overlay-get ov 'kitty-gfx-id)) - (pid (overlay-get ov 'kitty-gfx-pid))) - (when (and id pid kitty-gfx--active-backend - (overlay-get ov 'kitty-gfx-last-row)) - (condition-case err - (funcall (kitty-gfx--backend-fn 'delete) ov id pid) - (error - (kitty-gfx--log "on-window-change: error deleting placement: %s" - (error-message-string err)))))) - (overlay-put ov 'kitty-gfx-last-row nil) - (overlay-put ov 'kitty-gfx-last-col nil))))) + (kitty-gfx--delete-image-placements ov))))) (kitty-gfx--sync-end)) ;; Longer debounce: cancel any fast leading-edge cooldown and ;; schedule a 0.1s delayed refresh to let window layout settle. @@ -1924,6 +1930,82 @@ delete step, avoiding visual glitches in some terminals." image-id pid cols rows beg end (buffer-name) (length kitty-gfx--overlays)) ov)) +(defun kitty-gfx--image-placement (ov win) + "Return OV's recorded image placement for WIN, or nil." + (assq win (overlay-get ov 'kitty-gfx-placements))) + +(defun kitty-gfx--record-image-placement (ov win row col cols rows pid) + "Record that OV is placed in WIN at ROW COL with COLS ROWS and PID." + (unless (kitty-gfx--image-placement ov win) + (setq pid (kitty-gfx--alloc-placement-id))) + (let ((placements (assq-delete-all win (copy-sequence + (overlay-get ov 'kitty-gfx-placements))))) + (overlay-put ov 'kitty-gfx-placements + (cons (cons win (list :row row :col col + :cols cols :rows rows + :pid pid)) + placements)))) + +(defun kitty-gfx--forget-image-placement (ov win) + "Forget OV's recorded image placement for WIN." + (overlay-put ov 'kitty-gfx-placements + (assq-delete-all win (copy-sequence + (overlay-get ov 'kitty-gfx-placements))))) + +(defun kitty-gfx--delete-image-placement (ov placement) + "Delete one recorded image PLACEMENT for OV." + (let* ((data (cdr placement)) + (id (overlay-get ov 'kitty-gfx-id)) + (pid (plist-get data :pid)) + (row (plist-get data :row)) + (col (plist-get data :col)) + (cols (plist-get data :cols)) + (rows (plist-get data :rows)) + (old-row (overlay-get ov 'kitty-gfx-last-row)) + (old-col (overlay-get ov 'kitty-gfx-last-col)) + (old-cols (overlay-get ov 'kitty-gfx-cols)) + (old-rows (overlay-get ov 'kitty-gfx-rows))) + (when (and id pid row col cols rows kitty-gfx--active-backend) + ;; Sixel deletion is position-based and reads these properties from OV; + ;; Kitty deletion ignores them and deletes by PID. Temporarily bind the + ;; recorded geometry so both backends can share the same helper. + (unwind-protect + (progn + (overlay-put ov 'kitty-gfx-last-row row) + (overlay-put ov 'kitty-gfx-last-col col) + (overlay-put ov 'kitty-gfx-cols cols) + (overlay-put ov 'kitty-gfx-rows rows) + (funcall (kitty-gfx--backend-fn 'delete) ov id pid)) + (overlay-put ov 'kitty-gfx-last-row old-row) + (overlay-put ov 'kitty-gfx-last-col old-col) + (overlay-put ov 'kitty-gfx-cols old-cols) + (overlay-put ov 'kitty-gfx-rows old-rows))))) + +(defun kitty-gfx--delete-image-placements (ov) + "Delete all recorded terminal placements for image overlay OV." + (let ((placements (overlay-get ov 'kitty-gfx-placements))) + (if placements + (dolist (placement placements) + (condition-case err + (kitty-gfx--delete-image-placement ov placement) + (error + (kitty-gfx--log "delete-image-placements: error: %s" + (error-message-string err))))) + ;; Backward-compatible fallback for overlays created before per-window + ;; placement tracking or for callers that only populated last-row/col. + (let ((id (overlay-get ov 'kitty-gfx-id)) + (pid (overlay-get ov 'kitty-gfx-pid))) + (when (and id pid kitty-gfx--active-backend + (overlay-get ov 'kitty-gfx-last-row)) + (condition-case err + (funcall (kitty-gfx--backend-fn 'delete) ov id pid) + (error + (kitty-gfx--log "delete-image-placements: fallback error: %s" + (error-message-string err)))))))) + (overlay-put ov 'kitty-gfx-placements nil) + (overlay-put ov 'kitty-gfx-last-row nil) + (overlay-put ov 'kitty-gfx-last-col nil)) + (defun kitty-gfx--delete-placement (id pid) "Delete a specific placement PID of image ID from terminal. Uses d=i (lowercase) to remove the placement but keep stored image @@ -1939,17 +2021,14 @@ the placement ID can be reused by a subsequent overlay (avoids visual glitches from delete+re-place sequences in some terminals)." (let ((id (overlay-get ov 'kitty-gfx-id)) (pid (overlay-get ov 'kitty-gfx-pid))) + (when keep-placement + (overlay-put ov 'kitty-gfx-placements nil)) (kitty-gfx--log "remove-overlay: id=%s pid=%s keep=%s buf=%s" id pid keep-placement (when (overlay-buffer ov) (buffer-name (overlay-buffer ov)))) (when (overlay-buffer ov) (unless keep-placement - (condition-case err - (when (and id pid kitty-gfx--active-backend) - (funcall (kitty-gfx--backend-fn 'delete) ov id pid)) - (error - (kitty-gfx--log "remove-overlay: error deleting placement: %s" - (error-message-string err))))) + (kitty-gfx--delete-image-placements ov)) (delete-overlay ov)) (setq kitty-gfx--overlays (delq ov kitty-gfx--overlays)) (kitty-gfx--log "remove-overlay: done (remaining=%d)" (length kitty-gfx--overlays)))) |
